diff --git a/CHANGELOG.md b/CHANGELOG.md index e8d5e9fd81c..ccdd223d256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Docs: https://docs.openclaw.ai - Agents/subagents: enforce `subagents.allowAgents` for explicit same-agent `sessions_spawn(agentId=...)` calls instead of auto-allowing requester self-targets. Fixes #72827. Thanks @oiGaDio. - ACP/sessions_spawn: let explicit `sessions_spawn(runtime="acp")` bootstrap turns run while `acp.dispatch.enabled=false` still blocks automatic ACP thread dispatch. Fixes #63591. Thanks @moeedahmed. +- Gateway: skip CLI startup self-respawn for foreground gateway runs so low-memory Linux/Node 24 hosts start through the same path as direct `dist/index.js` without hanging before logs. Fixes #72720. Thanks @sign-2025. - Google Meet: grant Meet media permissions through browser control and pin local Chrome audio defaults to `BlackHole 2ch`, so joined agents no longer show `Permission needed` or use macOS default audio devices. Thanks @DougButdorf. - Google Meet: route local Chrome joins through OpenClaw browser control instead of raw default Chrome, so agents use the configured OpenClaw browser profile when opening Meet. Thanks @oromeis. - Plugins/discovery: follow symlinked plugin directories in global and workspace plugin roots while keeping broken links ignored and existing package safety checks in place. Fixes #36754; carries forward #72695 and #63206. Thanks @Quackstro, @ming1523, and @xsfX20. diff --git a/src/cli/cli-utils.test.ts b/src/cli/cli-utils.test.ts index 2f49382a00a..5936fb16187 100644 --- a/src/cli/cli-utils.test.ts +++ b/src/cli/cli-utils.test.ts @@ -21,12 +21,22 @@ describe("shouldSkipRespawnForArgv", () => { it.each([ { argv: ["node", "openclaw", "--help"] }, { argv: ["node", "openclaw", "-V"] }, + { argv: ["node", "openclaw", "gateway"] }, + { argv: ["node", "openclaw", "gateway", "--port", "14720", "--bind", "loopback"] }, + { argv: ["node", "openclaw", "gateway", "run", "--port=14720", "--bind", "loopback"] }, + { + argv: ["node", "openclaw", "--profile", "server", "gateway", "run", "--allow-unconfigured"], + }, ] as const)("skips respawn for argv %j", ({ argv }) => { expect(shouldSkipRespawnForArgv([...argv]), argv.join(" ")).toBe(true); }); - it("keeps respawn path for normal commands", () => { - expect(shouldSkipRespawnForArgv(["node", "openclaw", "status"])).toBe(false); + it.each([ + { argv: ["node", "openclaw", "status"] }, + { argv: ["node", "openclaw", "gateway", "status"] }, + { argv: ["node", "openclaw", "gateway", "call", "health"] }, + ] as const)("keeps respawn path for argv %j", ({ argv }) => { + expect(shouldSkipRespawnForArgv([...argv]), argv.join(" ")).toBe(false); }); }); diff --git a/src/cli/respawn-policy.ts b/src/cli/respawn-policy.ts index 761c4d47eea..f5d2fbf020c 100644 --- a/src/cli/respawn-policy.ts +++ b/src/cli/respawn-policy.ts @@ -1,5 +1,47 @@ import { resolveCliArgvInvocation } from "./argv-invocation.js"; +import { getCommandPositionalsWithRootOptions } from "./argv.js"; + +const GATEWAY_RUN_BOOLEAN_FLAGS = [ + "--allow-unconfigured", + "--claude-cli-logs", + "--cli-backend-logs", + "--compact", + "--dev", + "--force", + "--raw-stream", + "--reset", + "--tailscale-reset-on-exit", + "--verbose", +] as const; + +const GATEWAY_RUN_VALUE_FLAGS = [ + "--auth", + "--bind", + "--password", + "--password-file", + "--port", + "--raw-stream-path", + "--tailscale", + "--token", + "--ws-log", +] as const; + +function isForegroundGatewayRunArgv(argv: string[]): boolean { + const positionals = getCommandPositionalsWithRootOptions(argv, { + commandPath: ["gateway"], + booleanFlags: GATEWAY_RUN_BOOLEAN_FLAGS, + valueFlags: GATEWAY_RUN_VALUE_FLAGS, + }); + if (!positionals) { + return false; + } + return positionals.length === 0 || (positionals.length === 1 && positionals[0] === "run"); +} export function shouldSkipRespawnForArgv(argv: string[]): boolean { - return resolveCliArgvInvocation(argv).hasHelpOrVersion; + const invocation = resolveCliArgvInvocation(argv); + return ( + invocation.hasHelpOrVersion || + (invocation.primary === "gateway" && isForegroundGatewayRunArgv(argv)) + ); } diff --git a/src/entry.respawn.test.ts b/src/entry.respawn.test.ts index afe34c09722..b4edc118da9 100644 --- a/src/entry.respawn.test.ts +++ b/src/entry.respawn.test.ts @@ -36,7 +36,7 @@ describe("buildCliRespawnPlan", () => { it("adds NODE_EXTRA_CA_CERTS and warning suppression in one respawn", () => { const plan = buildCliRespawnPlan({ - argv: ["node", "openclaw", "gateway", "run"], + argv: ["node", "openclaw", "status"], env: {}, execArgv: [], autoNodeExtraCaCerts: "/etc/ssl/certs/ca-certificates.crt", @@ -52,7 +52,7 @@ describe("buildCliRespawnPlan", () => { it("does not overwrite an existing NODE_EXTRA_CA_CERTS value", () => { const plan = buildCliRespawnPlan({ - argv: ["node", "openclaw", "gateway", "run"], + argv: ["node", "openclaw", "status"], env: { NODE_EXTRA_CA_CERTS: "/custom/ca.pem" }, execArgv: [], autoNodeExtraCaCerts: "/etc/ssl/certs/ca-certificates.crt", @@ -64,7 +64,7 @@ describe("buildCliRespawnPlan", () => { it("returns null when both respawn guards are already satisfied", () => { expect( buildCliRespawnPlan({ - argv: ["node", "openclaw", "gateway", "run"], + argv: ["node", "openclaw", "status"], env: { [OPENCLAW_NODE_EXTRA_CA_CERTS_READY]: "1", [OPENCLAW_NODE_OPTIONS_READY]: "1",