fix(update): require applied gateway restarts

Require Control UI updates to observe a real gateway process replacement, surface skipped/error update outcomes, and verify the running gateway version after restart.\n\nAdds update.status restart-sentinel plumbing, docs, generated protocol model updates, and changelog attribution.\n\nLocal verification:\n- pnpm test src/gateway/server-methods/update.test.ts src/cli/gateway-cli/run-loop.test.ts src/infra/restart-sentinel.test.ts src/infra/process-respawn.test.ts src/infra/update-runner.test.ts ui/src/ui/app-gateway.node.test.ts ui/src/ui/controllers/config.test.ts\n- git diff --check\n- pnpm exec oxfmt --check --threads=1 CHANGELOG.md docs/gateway/protocol.md docs/gateway/configuration.md docs/web/control-ui.md\n- pnpm docs:check-mdx
This commit is contained in:
Samuel Rodda
2026-04-27 18:37:43 +09:30
committed by GitHub
parent b74f35ee6f
commit 6c252cc54c
41 changed files with 1265 additions and 54 deletions

View File

@@ -15,6 +15,7 @@ import {
} from "../../config/config.js";
import { formatConfigIssueLines } from "../../config/issue-format.js";
import { asResolvedSourceConfig, asRuntimeConfig } from "../../config/materialize.js";
import { GATEWAY_SERVICE_KIND, GATEWAY_SERVICE_MARKER } from "../../daemon/constants.js";
import { resolveGatewayInstallEntrypoint } from "../../daemon/gateway-entrypoint.js";
import { resolveGatewayRestartLogPath } from "../../daemon/restart-logs.js";
import { readGatewayServiceState, resolveGatewayService } from "../../daemon/service.js";
@@ -151,6 +152,16 @@ export function shouldUseLegacyProcessRestartAfterUpdate(params: {
return !isPackageManagerUpdateMode(params.updateMode);
}
function isRunningInsideGatewayService(
env: Record<string, string | undefined> = process.env,
): boolean {
if (env.OPENCLAW_SERVICE_MARKER?.trim() !== GATEWAY_SERVICE_MARKER) {
return false;
}
const serviceKind = env.OPENCLAW_SERVICE_KIND?.trim();
return !serviceKind || serviceKind === GATEWAY_SERVICE_KIND;
}
function formatCommandFailure(stdout: string, stderr: string): string {
const detail = (stderr || stdout).trim();
if (!detail) {
@@ -1309,6 +1320,18 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
return;
}
if (updateInstallKind === "package" && isRunningInsideGatewayService()) {
defaultRuntime.error(
[
"Package updates cannot run from inside the gateway service process.",
"That path replaces the active OpenClaw dist tree while the live gateway may still lazy-load old chunks.",
`Run \`${replaceCliName(formatCliCommand("openclaw update"), CLI_NAME)}\` from a shell outside the gateway service, or stop the gateway service first and then update.`,
].join("\n"),
);
defaultRuntime.exit(1);
return;
}
if (downgradeRisk && !opts.yes) {
if (!process.stdin.isTTY || opts.json) {
defaultRuntime.error(