Skip to content

Commit 304ff68

Browse files
committed
fix(qa-lab): stabilize codex runtime parity fixtures
1 parent 6b52dff commit 304ff68

10 files changed

Lines changed: 783 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai
5252
- Debug proxy: record CONNECT client-socket errors and destroy the paired upstream socket so abrupt client disconnects no longer leak tunnel resources. (#82444) Thanks @SebTardif.
5353
- Diffs: continue hydrating later diff cards when one card fails so a single broken card no longer blanks the whole diff viewer. (#84775) Thanks @cosmopolitan033.
5454
- Mac app: use the native settings sidebar window chrome so the sidebar toggle stays on the left and content no longer clips under oversized titlebar padding.
55+
- QA-Lab/Codex: bundle auth/plugin fixture imports for flow scenarios and let terminal async media tools end Codex app-server turns without timing out. (#80397, refs #80323) Thanks @100yenadmin.
5556
- Gateway/agents: preserve fresh session overrides and metadata when stale cached agent-session entries race with store updates, so subagent model/provider overrides and routing policy survive concurrent writes. (#19328) Thanks @CodeReclaimers.
5657
- Control UI/chat: keep chat session search inline with the session selector so the header no longer shows a duplicate standalone search row.
5758
- Codex app-server: restart the native app-server and retry once when server-side compaction times out, so preflight compaction stalls recover instead of failing every dispatch. (#85500)

extensions/codex/src/app-server/dynamic-tools.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,28 @@ describe("createCodexDynamicToolBridge", () => {
802802
expectContextFields(callArg(handler, 0, 1, "middleware context"), { runtime: "codex" });
803803
});
804804

805+
it("preserves terminal async tool results without marking them as errors", async () => {
806+
const bridge = createBridgeWithToolResult("image_generate", {
807+
content: [{ type: "text", text: "Background task started." }],
808+
details: { async: true, status: "started", taskId: "task-1" },
809+
terminate: true,
810+
});
811+
812+
const result = await bridge.handleToolCall({
813+
threadId: "thread-1",
814+
turnId: "turn-1",
815+
callId: "call-1",
816+
namespace: null,
817+
tool: "image_generate",
818+
arguments: { prompt: "lighthouse" },
819+
});
820+
821+
expect(result).toEqual(expectInputText("Background task started."));
822+
expect(result.sideEffectEvidence).toBe(true);
823+
expect(result.terminate).toBe(true);
824+
expect(Object.keys(result)).not.toContain("terminate");
825+
});
826+
805827
it("marks executed dynamic tool results as side-effect evidence", async () => {
806828
const bridge = createBridgeWithToolResult("exec", textToolResult("done"));
807829

extensions/codex/src/app-server/dynamic-tools.ts

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,21 @@ export function createCodexDynamicToolBridge(params: {
192192
startedAt,
193193
});
194194
const terminalType = inferToolResultDiagnosticTerminalType(result, resultIsError);
195-
return withSideEffectEvidence(
196-
withDiagnosticTerminalType(
197-
{
198-
contentItems: convertToolContents(result.content, toolResultMaxChars),
199-
success: !resultIsError,
200-
},
201-
terminalType,
202-
),
203-
terminalType !== "blocked",
195+
const response = withDiagnosticTerminalType(
196+
{
197+
contentItems: convertToolContents(result.content, toolResultMaxChars),
198+
success: !resultIsError,
199+
},
200+
terminalType,
201+
);
202+
withDynamicToolTermination(
203+
response,
204+
rawResult.terminate === true ||
205+
result.terminate === true ||
206+
isToolResultYield(rawResult) ||
207+
isToolResultYield(result),
204208
);
209+
return withSideEffectEvidence(response, terminalType !== "blocked");
205210
} catch (error) {
206211
collectToolTelemetry({
207212
toolName: tool.name,
@@ -463,10 +468,21 @@ function isToolResultError(result: AgentToolResult<unknown>): boolean {
463468
status !== "success" &&
464469
status !== "completed" &&
465470
status !== "recorded" &&
466-
status !== "running"
471+
status !== "pending" &&
472+
status !== "started" &&
473+
status !== "running" &&
474+
status !== "yielded"
467475
);
468476
}
469477

478+
function isToolResultYield(result: AgentToolResult<unknown>): boolean {
479+
const details = result.details;
480+
if (!isRecord(details) || typeof details.status !== "string") {
481+
return false;
482+
}
483+
return details.status.trim().toLowerCase() === "yielded";
484+
}
485+
470486
function inferToolResultDiagnosticTerminalType(
471487
result: AgentToolResult<unknown>,
472488
isError: boolean,
@@ -508,6 +524,21 @@ function withSideEffectEvidence<T extends CodexDynamicToolCallResponse>(
508524
return response;
509525
}
510526

527+
function withDynamicToolTermination<T extends CodexDynamicToolCallResponse>(
528+
response: T,
529+
terminate: boolean,
530+
): T {
531+
if (!terminate) {
532+
return response;
533+
}
534+
Object.defineProperty(response, "terminate", {
535+
configurable: true,
536+
enumerable: false,
537+
value: true,
538+
});
539+
return response;
540+
}
541+
511542
function normalizeToolResultMaxChars(maxChars: number): number {
512543
return typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0
513544
? Math.floor(maxChars)

extensions/codex/src/app-server/protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ export type CodexDynamicToolCallResponse = {
272272
diagnosticTerminalType?: CodexDynamicToolDiagnosticTerminalType;
273273
sideEffectEvidence?: boolean;
274274
success: boolean;
275+
terminate?: boolean;
275276
};
276277

277278
export type CodexDynamicToolDiagnosticTerminalType = "blocked" | "completed" | "error";

0 commit comments

Comments
 (0)