From 8336a94ecbfd076d1af9958e9c1926706420a46c Mon Sep 17 00:00:00 2001 From: Vincent Koc <25068+vincentkoc@users.noreply.github.com> Date: Mon, 6 Jul 2026 09:50:55 -0700 Subject: [PATCH] test(live): accept yielded Codex bridge parent --- ...gateway-codex-harness.live-helpers.test.ts | 22 +++++++++++++++++++ .../gateway-codex-harness.live-helpers.ts | 21 ++++++++++++++++++ .../gateway-codex-harness.live.test.ts | 8 ++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/gateway/gateway-codex-harness.live-helpers.test.ts b/src/gateway/gateway-codex-harness.live-helpers.test.ts index d34fca2f1489..b2aa0136758f 100644 --- a/src/gateway/gateway-codex-harness.live-helpers.test.ts +++ b/src/gateway/gateway-codex-harness.live-helpers.test.ts @@ -7,6 +7,7 @@ import { EXPECTED_CODEX_STATUS_COMMAND_TEXT, isExpectedCodexModelsCommandText, isExpectedCodexStatusCommandText, + isExpectedYieldedAgentTimeout, isRetryableCodexHarnessLiveError, isStrictExpectedCodexModelsCommandText, } from "./gateway-codex-harness.live-helpers.js"; @@ -41,6 +42,27 @@ describe("gateway codex harness live helpers", () => { expect(isRetryableCodexHarnessLiveError(error)).toBe(false); }); + it("accepts only paused yielded agent timeouts for native subagent delivery", () => { + expect( + isExpectedYieldedAgentTimeout({ + status: "timeout", + result: { meta: { livenessState: "paused", yielded: true } }, + }), + ).toBe(true); + expect( + isExpectedYieldedAgentTimeout({ + status: "timeout", + result: { meta: { livenessState: "paused", yielded: false } }, + }), + ).toBe(false); + expect( + isExpectedYieldedAgentTimeout({ + status: "ok", + result: { meta: { livenessState: "paused", yielded: true } }, + }), + ).toBe(false); + }); + it("accepts the current codex status prose from the live harness", () => { 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:live-codex-harness`."; diff --git a/src/gateway/gateway-codex-harness.live-helpers.ts b/src/gateway/gateway-codex-harness.live-helpers.ts index 75f36420a051..9b8c83b40146 100644 --- a/src/gateway/gateway-codex-harness.live-helpers.ts +++ b/src/gateway/gateway-codex-harness.live-helpers.ts @@ -419,3 +419,24 @@ export function isRetryableCodexHarnessLiveError(error: unknown): boolean { } return error.message.includes("gateway request timeout for sessions.list"); } + +/** Matches the terminal snapshot emitted when a native subagent parent yields for delivery. */ +export function isExpectedYieldedAgentTimeout(payload: unknown): boolean { + if (!payload || typeof payload !== "object") { + return false; + } + const result = (payload as { result?: unknown; status?: unknown }).result; + if (!result || typeof result !== "object") { + return false; + } + const meta = (result as { meta?: unknown }).meta; + if (!meta || typeof meta !== "object") { + return false; + } + const record = meta as { livenessState?: unknown; yielded?: unknown }; + return ( + (payload as { status?: unknown }).status === "timeout" && + record.yielded === true && + record.livenessState === "paused" + ); +} diff --git a/src/gateway/gateway-codex-harness.live.test.ts b/src/gateway/gateway-codex-harness.live.test.ts index a05cf8fcc5e2..f0b6cd558a35 100644 --- a/src/gateway/gateway-codex-harness.live.test.ts +++ b/src/gateway/gateway-codex-harness.live.test.ts @@ -27,6 +27,7 @@ import { EXPECTED_CODEX_MODELS_COMMAND_TEXT, EXPECTED_CODEX_STATUS_COMMAND_TEXT, isExpectedCodexStatusCommandText, + isExpectedYieldedAgentTimeout, isRetryableCodexHarnessLiveError, isStrictExpectedCodexModelsCommandText, } from "./gateway-codex-harness.live-helpers.js"; @@ -257,6 +258,7 @@ async function writeLiveGatewayConfig(params: { } async function requestAgentTextWithEvents(params: { + acceptYieldedTimeout?: boolean; client: GatewayClient; eventPrefix?: string; includeAllSessions?: boolean; @@ -293,7 +295,9 @@ async function requestAgentTextWithEvents(params: { }, { expectFinal: true, timeoutMs: CODEX_HARNESS_REQUEST_TIMEOUT_MS }, ); - if (payload?.status !== "ok") { + const acceptedYieldedTimeout = + params.acceptYieldedTimeout === true && isExpectedYieldedAgentTimeout(payload); + if (payload?.status !== "ok" && !acceptedYieldedTimeout) { throw new Error(`agent status=${String(payload?.status)} payload=${JSON.stringify(payload)}`); } return { text: extractPayloadText(payload.result), events }; @@ -980,6 +984,8 @@ async function verifyCodexNativeSubagentBridgeProbe(params: { const parentToken = `CODEX-NATIVE-PARENT-${runId.slice(0, 6).toUpperCase()}`; const { listTaskRecords } = await import("../tasks/runtime-internal.js"); const { text, events } = await requestAgentTextWithEvents({ + // Native Codex waiting pauses this parent turn; task delivery resumes it separately. + acceptYieldedTimeout: true, client: params.client, eventPrefix: "codex_app_server.", includeAllSessions: true,