diff --git a/scripts/openclaw-cross-os-release-checks.ts b/scripts/openclaw-cross-os-release-checks.ts index 92cb785cabc..a3efeea12a1 100644 --- a/scripts/openclaw-cross-os-release-checks.ts +++ b/scripts/openclaw-cross-os-release-checks.ts @@ -61,6 +61,8 @@ const OMITTED_QA_EXTENSION_PREFIXES = [ export const CROSS_OS_DASHBOARD_SMOKE_TIMEOUT_MS = 120_000; export const CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS = 10_000; export const CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS = 30_000; +export const CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS = + CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS + 45_000; export const CROSS_OS_GATEWAY_READY_TIMEOUT_MS = 3 * 60_000; export const CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS = 5 * 60_000; @@ -1635,13 +1637,12 @@ async function resolveInstalledGatewayStatusArgs(params) { return [ "gateway", "status", - "--deep", "--require-rpc", "--timeout", String(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS), ]; } - return ["gateway", "status", "--deep"]; + return ["gateway", "status"]; } export async function canConnectToLoopbackPort(port, timeoutMs = 1_000) { @@ -1684,7 +1685,7 @@ async function waitForInstalledGateway(params) { cwd: params.lane.homeDir, env: params.env, logPath: params.logPath, - timeoutMs: 20_000, + timeoutMs: CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS, check: false, }); if (result.exitCode === 0) { @@ -1711,7 +1712,7 @@ async function waitForInstalledGatewayToStop(params) { cwd: params.lane.homeDir, env: params.env, logPath: params.logPath, - timeoutMs: 20_000, + timeoutMs: CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS, check: false, }); const portReachable = await canConnectToLoopbackPort(params.lane.gatewayPort); @@ -2364,7 +2365,7 @@ async function waitForGateway(params) { env: params.env, args: statusArgs, logPath: params.logPath, - timeoutMs: 20_000, + timeoutMs: CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS, check: false, }); } catch { @@ -2398,13 +2399,12 @@ async function resolveGatewayStatusArgs(lane, env, logPath) { return [ "gateway", "status", - "--deep", "--require-rpc", "--timeout", String(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS), ]; } - return ["gateway", "status", "--deep"]; + return ["gateway", "status"]; } async function runModelsSet(params) { diff --git a/src/agents/harness/lifecycle-hook-helpers.test.ts b/src/agents/harness/lifecycle-hook-helpers.test.ts new file mode 100644 index 00000000000..1c2e4cdb24c --- /dev/null +++ b/src/agents/harness/lifecycle-hook-helpers.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; +import { + runAgentHarnessAgentEndHook, + runAgentHarnessBeforeAgentFinalizeHook, + runAgentHarnessLlmInputHook, + runAgentHarnessLlmOutputHook, +} from "./lifecycle-hook-helpers.js"; + +const legacyHookRunner = { + hasHooks: () => true, +}; + +describe("agent harness lifecycle hook helpers", () => { + it("ignores legacy hook runners that advertise llm_input without a runner method", () => { + expect(() => + runAgentHarnessLlmInputHook({ + ctx: {}, + event: {}, + hookRunner: legacyHookRunner, + } as never), + ).not.toThrow(); + }); + + it("ignores legacy hook runners that advertise llm_output without a runner method", () => { + expect(() => + runAgentHarnessLlmOutputHook({ + ctx: {}, + event: {}, + hookRunner: legacyHookRunner, + } as never), + ).not.toThrow(); + }); + + it("ignores legacy hook runners that advertise agent_end without a runner method", () => { + expect(() => + runAgentHarnessAgentEndHook({ + ctx: {}, + event: {}, + hookRunner: legacyHookRunner, + } as never), + ).not.toThrow(); + }); + + it("continues when legacy hook runners advertise before_agent_finalize without a runner method", async () => { + await expect( + runAgentHarnessBeforeAgentFinalizeHook({ + ctx: {}, + event: {}, + hookRunner: legacyHookRunner, + } as never), + ).resolves.toEqual({ action: "continue" }); + }); +}); diff --git a/src/agents/harness/lifecycle-hook-helpers.ts b/src/agents/harness/lifecycle-hook-helpers.ts index 15802293d2f..db35af577ef 100644 --- a/src/agents/harness/lifecycle-hook-helpers.ts +++ b/src/agents/harness/lifecycle-hook-helpers.ts @@ -19,7 +19,7 @@ export function runAgentHarnessLlmInputHook(params: { hookRunner?: AgentHarnessHookRunner; }): void { const hookRunner = params.hookRunner ?? getGlobalHookRunner(); - if (!hookRunner?.hasHooks("llm_input")) { + if (!hookRunner?.hasHooks("llm_input") || typeof hookRunner.runLlmInput !== "function") { return; } void hookRunner.runLlmInput(params.event, buildAgentHookContext(params.ctx)).catch((error) => { @@ -33,7 +33,7 @@ export function runAgentHarnessLlmOutputHook(params: { hookRunner?: AgentHarnessHookRunner; }): void { const hookRunner = params.hookRunner ?? getGlobalHookRunner(); - if (!hookRunner?.hasHooks("llm_output")) { + if (!hookRunner?.hasHooks("llm_output") || typeof hookRunner.runLlmOutput !== "function") { return; } void hookRunner.runLlmOutput(params.event, buildAgentHookContext(params.ctx)).catch((error) => { @@ -47,7 +47,7 @@ export function runAgentHarnessAgentEndHook(params: { hookRunner?: AgentHarnessHookRunner; }): void { const hookRunner = params.hookRunner ?? getGlobalHookRunner(); - if (!hookRunner?.hasHooks("agent_end")) { + if (!hookRunner?.hasHooks("agent_end") || typeof hookRunner.runAgentEnd !== "function") { return; } void hookRunner.runAgentEnd(params.event, buildAgentHookContext(params.ctx)).catch((error) => { @@ -66,7 +66,10 @@ export async function runAgentHarnessBeforeAgentFinalizeHook(params: { hookRunner?: AgentHarnessHookRunner; }): Promise { const hookRunner = params.hookRunner ?? getGlobalHookRunner(); - if (!hookRunner?.hasHooks("before_agent_finalize")) { + if ( + !hookRunner?.hasHooks("before_agent_finalize") || + typeof hookRunner.runBeforeAgentFinalize !== "function" + ) { return { action: "continue" }; } try { diff --git a/src/gateway/gateway-codex-harness.live-helpers.test.ts b/src/gateway/gateway-codex-harness.live-helpers.test.ts index 265ad217cf4..e93a776f9a6 100644 --- a/src/gateway/gateway-codex-harness.live-helpers.test.ts +++ b/src/gateway/gateway-codex-harness.live-helpers.test.ts @@ -17,6 +17,16 @@ describe("gateway codex harness live helpers", () => { expect(isExpectedCodexStatusCommandText(text)).toBe(true); }); + it("accepts current status prose that reports session context without the session id", () => { + const text = [ + "OpenClaw is running on `openai/gpt-5.5` with low reasoning/text settings.", + "", + "Session context is light: `22k/272k` tokens used, `8%`, no compactions. There is 1 active task: `/codex status`.", + ].join("\n"); + + expect(isExpectedCodexStatusCommandText(text)).toBe(true); + }); + it("rejects status prose for a different codex session", () => { const text = "OpenClaw is running on `openai/gpt-5.5` with low reasoning/text settings. Context is at `22k/272k` tokens, no compactions, and the current session is `agent:dev:other`."; diff --git a/src/gateway/gateway-codex-harness.live-helpers.ts b/src/gateway/gateway-codex-harness.live-helpers.ts index eb2600fbc26..19cbfb52808 100644 --- a/src/gateway/gateway-codex-harness.live-helpers.ts +++ b/src/gateway/gateway-codex-harness.live-helpers.ts @@ -94,7 +94,8 @@ export function isExpectedCodexStatusCommandText(text: string): boolean { normalized.includes("session: agent:dev:live-codex-harness") || normalized.includes("session `agent:dev:live-codex-harness`") || normalized.includes("current session is `agent:dev:live-codex-harness`") || - normalized.includes("current session is agent:dev:live-codex-harness"); + normalized.includes("current session is agent:dev:live-codex-harness") || + (normalized.includes("session context") && normalized.includes("active task: `/codex status`")); const mentionsModel = normalized.includes("`openai/") || normalized.includes(" openai/") || diff --git a/test/scripts/openclaw-cross-os-release-checks.test.ts b/test/scripts/openclaw-cross-os-release-checks.test.ts index ce8d802d89a..5ca2474e1e2 100644 --- a/test/scripts/openclaw-cross-os-release-checks.test.ts +++ b/test/scripts/openclaw-cross-os-release-checks.test.ts @@ -13,6 +13,7 @@ import { buildDiscordSmokeGuildsConfig, buildRealUpdateEnv, CROSS_OS_GATEWAY_READY_TIMEOUT_MS, + CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS, CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS, CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS, CROSS_OS_DASHBOARD_FETCH_TIMEOUT_MS, @@ -51,6 +52,9 @@ describe("scripts/openclaw-cross-os-release-checks", () => { it("keeps gateway RPC status probes patient enough for live release startup", () => { expect(CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000); + expect(CROSS_OS_GATEWAY_STATUS_COMMAND_TIMEOUT_MS).toBeGreaterThan( + CROSS_OS_GATEWAY_STATUS_RPC_TIMEOUT_MS, + ); expect(CROSS_OS_GATEWAY_READY_TIMEOUT_MS).toBeGreaterThanOrEqual(180_000); expect(CROSS_OS_WINDOWS_GATEWAY_READY_TIMEOUT_MS).toBeGreaterThanOrEqual(300_000); });