From a65011b2f45c7ac1e7877905bf76bd217fde64be Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 2 May 2026 01:53:28 +0700 Subject: [PATCH] fix: keep codex tool result context config-free --- .../src/app-server/dynamic-tools.test.ts | 86 +++++++++++++++++++ .../codex/src/app-server/dynamic-tools.ts | 54 ++++++++---- 2 files changed, 121 insertions(+), 19 deletions(-) diff --git a/extensions/codex/src/app-server/dynamic-tools.test.ts b/extensions/codex/src/app-server/dynamic-tools.test.ts index 7519d981932..14460326d06 100644 --- a/extensions/codex/src/app-server/dynamic-tools.test.ts +++ b/extensions/codex/src/app-server/dynamic-tools.test.ts @@ -414,6 +414,92 @@ describe("createCodexDynamicToolBridge", () => { expect(result).toEqual(expectInputText("legacy compacted")); }); + it("keeps config out of Codex tool-result contexts", async () => { + const config = { session: { store: "/tmp/openclaw-session-store.json" } }; + const registry = createEmptyPluginRegistry(); + const middlewareContexts: Record[] = []; + const legacyContexts: Record[] = []; + const middleware = vi.fn(async (_event: unknown, ctx: Record) => { + middlewareContexts.push(ctx); + return undefined; + }); + const factory = async (codex: { + on: ( + event: "tool_result", + handler: ( + event: unknown, + ctx: Record, + ) => Promise<{ result: AgentToolResult } | void>, + ) => void; + }) => { + codex.on("tool_result", async (_event, ctx) => { + legacyContexts.push(ctx); + }); + }; + registry.agentToolResultMiddlewares.push({ + pluginId: "tokenjuice", + pluginName: "Tokenjuice", + rawHandler: middleware, + handler: middleware, + runtimes: ["codex"], + source: "test", + }); + registry.codexAppServerExtensionFactories.push({ + pluginId: "legacy", + pluginName: "Legacy", + rawFactory: factory, + factory, + source: "test", + }); + setActivePluginRegistry(registry); + + const execute = vi.fn(async () => textToolResult("done")); + const bridge = createCodexDynamicToolBridge({ + tools: [createTool({ name: "exec", execute })], + signal: new AbortController().signal, + hookContext: { + agentId: "agent-1", + config: config as never, + sessionId: "session-1", + sessionKey: "agent:agent-1:session-1", + runId: "run-1", + }, + }); + + await bridge.handleToolCall({ + threadId: "thread-1", + turnId: "turn-1", + callId: "call-1", + namespace: null, + tool: "exec", + arguments: { command: "pwd" }, + }); + + expect(execute).toHaveBeenCalledWith( + "call-1", + { command: "pwd" }, + expect.any(AbortSignal), + undefined, + ); + expect(middlewareContexts).toHaveLength(1); + expect(middlewareContexts[0]).toMatchObject({ + runtime: "codex", + agentId: "agent-1", + sessionId: "session-1", + sessionKey: "agent:agent-1:session-1", + runId: "run-1", + }); + expect(middlewareContexts[0]).not.toHaveProperty("config"); + expect(legacyContexts).toHaveLength(1); + expect(legacyContexts[0]).toMatchObject({ + agentId: "agent-1", + sessionId: "session-1", + sessionKey: "agent:agent-1:session-1", + runId: "run-1", + }); + expect(legacyContexts[0]).not.toHaveProperty("config"); + }); + it("fires after_tool_call for successful codex tool executions", async () => { const afterToolCall = vi.fn(); initializeGlobalHookRunner( diff --git a/extensions/codex/src/app-server/dynamic-tools.ts b/extensions/codex/src/app-server/dynamic-tools.ts index 2ea8df69cba..285fe2979e2 100644 --- a/extensions/codex/src/app-server/dynamic-tools.ts +++ b/extensions/codex/src/app-server/dynamic-tools.ts @@ -25,6 +25,16 @@ import { type JsonValue, } from "./protocol.js"; +type CodexDynamicToolHookContext = { + agentId?: string; + config?: EmbeddedRunAttemptParams["config"]; + sessionId?: string; + sessionKey?: string; + runId?: string; +}; + +type CodexToolResultHookContext = Omit; + export type CodexDynamicToolBridge = { specs: CodexDynamicToolSpec[]; handleToolCall: ( @@ -46,14 +56,9 @@ export type CodexDynamicToolBridge = { export function createCodexDynamicToolBridge(params: { tools: AnyAgentTool[]; signal: AbortSignal; - hookContext?: { - agentId?: string; - config?: EmbeddedRunAttemptParams["config"]; - sessionId?: string; - sessionKey?: string; - runId?: string; - }; + hookContext?: CodexDynamicToolHookContext; }): CodexDynamicToolBridge { + const toolResultHookContext = toToolResultHookContext(params.hookContext); const tools = params.tools.map((tool) => isToolWrappedWithBeforeToolCallHook(tool) ? tool @@ -70,11 +75,10 @@ export function createCodexDynamicToolBridge(params: { }; const middlewareRunner = createAgentToolResultMiddlewareRunner({ runtime: "codex", - ...params.hookContext, + ...toolResultHookContext, }); - const legacyExtensionRunner = createCodexAppServerToolResultExtensionRunner( - params.hookContext ?? {}, - ); + const legacyExtensionRunner = + createCodexAppServerToolResultExtensionRunner(toolResultHookContext); return { specs: tools.map((tool) => ({ @@ -126,10 +130,10 @@ export function createCodexDynamicToolBridge(params: { void runAgentHarnessAfterToolCallHook({ toolName: tool.name, toolCallId: call.callId, - runId: params.hookContext?.runId, - agentId: params.hookContext?.agentId, - sessionId: params.hookContext?.sessionId, - sessionKey: params.hookContext?.sessionKey, + runId: toolResultHookContext.runId, + agentId: toolResultHookContext.agentId, + sessionId: toolResultHookContext.sessionId, + sessionKey: toolResultHookContext.sessionKey, startArgs: args, result, startedAt, @@ -149,10 +153,10 @@ export function createCodexDynamicToolBridge(params: { void runAgentHarnessAfterToolCallHook({ toolName: tool.name, toolCallId: call.callId, - runId: params.hookContext?.runId, - agentId: params.hookContext?.agentId, - sessionId: params.hookContext?.sessionId, - sessionKey: params.hookContext?.sessionKey, + runId: toolResultHookContext.runId, + agentId: toolResultHookContext.agentId, + sessionId: toolResultHookContext.sessionId, + sessionKey: toolResultHookContext.sessionKey, startArgs: args, error: error instanceof Error ? error.message : String(error), startedAt, @@ -171,6 +175,18 @@ export function createCodexDynamicToolBridge(params: { }; } +function toToolResultHookContext( + ctx: CodexDynamicToolHookContext | undefined, +): CodexToolResultHookContext { + const { agentId, sessionId, sessionKey, runId } = ctx ?? {}; + return { + ...(agentId && { agentId }), + ...(sessionId && { sessionId }), + ...(sessionKey && { sessionKey }), + ...(runId && { runId }), + }; +} + function composeAbortSignals(...signals: Array): AbortSignal { const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal)); if (activeSignals.length === 0) {