




















@@ -2,13 +2,8 @@
22import path from "node:path";
33import {
44onAgentEvent,
5-wrapToolWithBeforeToolCallHook,
65type AgentEventPayload,
76} from "openclaw/plugin-sdk/agent-harness-runtime";
8-import {
9-createTerminalPresentationContractTool,
10-textToolResult,
11-} from "openclaw/plugin-sdk/agent-runtime-test-contracts";
127import {
138emitTrustedDiagnosticEvent,
149onInternalDiagnosticEvent,
@@ -23,14 +18,9 @@ import {
2318emitDynamicToolTerminalDiagnostic,
2419} from "./dynamic-tool-diagnostics.js";
2520import { createCodexDynamicToolBridge } from "./dynamic-tools.js";
26-import {
27-flattenCodexDynamicToolFunctions,
28-type CodexDynamicToolCallParams,
29-type CodexDynamicToolSpec,
30-} from "./protocol.js";
21+import type { CodexDynamicToolCallParams } from "./protocol.js";
3122import {
3223createParams,
33-createCodexRuntimePlanFixture,
3424createRuntimeDynamicTool,
3525createStartedThreadHarness,
3626runCodexAppServerAttempt,
@@ -43,10 +33,6 @@ function flushDiagnosticEvents() {
4333return waitForDiagnosticEventsDrained();
4434}
453546-function specNames(specs: readonly CodexDynamicToolSpec[]): string[] {
47-return flattenCodexDynamicToolFunctions(specs).map((tool) => tool.name);
48-}
49-5036function activeDiagnosticToolKeys(events: DiagnosticEventPayload[]): Set<string> {
5137const active = new Set<string>();
5238for (const event of events) {
@@ -70,246 +56,6 @@ function activeDiagnosticToolKeys(events: DiagnosticEventPayload[]): Set<string>
7056setupRunAttemptTestHooks();
71577258describe("runCodexAppServerAttempt dynamic tools", () => {
73-it("preserves model order across queued native and dynamic tools", async () => {
74-let rejectSlowTool!: (error: Error) => void;
75-const slowToolResult = new Promise<never>((_resolve, reject) => {
76-rejectSlowTool = reject;
77-});
78-const slowTool = createRuntimeDynamicTool("slow_failure");
79-slowTool.execute = vi.fn(() => slowToolResult);
80-const laterTool = createTerminalPresentationContractTool({
81-name: "fast_summary",
82-result: textToolResult("fast result"),
83-format: () => "later dynamic summary",
84-});
85-const harness = createStartedThreadHarness();
86-const params = createParams(
87-path.join(tempDir, "session.jsonl"),
88-path.join(tempDir, "workspace"),
89-);
90-let terminalPresentation: string | undefined;
91-let latestOrdinal = -1;
92-let nextOrdinal = 0;
93-const onExecutionPhase = vi.fn();
94-params.disableTools = false;
95-params.runtimePlan = createCodexRuntimePlanFixture();
96-params.allocateToolOutcomeOrdinal = () => nextOrdinal++;
97-params.onExecutionPhase = onExecutionPhase;
98-params.onToolOutcome = (observation) => {
99-const ordinal = observation.toolCallOrdinal ?? latestOrdinal + 1;
100-if (ordinal >= latestOrdinal) {
101-latestOrdinal = ordinal;
102-terminalPresentation = observation.terminalPresentation;
103-}
104-};
105-testing.setOpenClawCodingToolsFactoryForTests((options) =>
106-[slowTool, laterTool].map((tool) =>
107-wrapToolWithBeforeToolCallHook(tool, {
108-runId: options?.runId,
109-sessionId: options?.sessionId,
110-sessionKey: options?.sessionKey,
111-onToolOutcome: options?.onToolOutcome,
112-allocateToolOutcomeOrdinal: options?.allocateToolOutcomeOrdinal,
113-}),
114-),
115-);
116-117-const run = runCodexAppServerAttempt(params);
118-await harness.waitForMethod("thread/start");
119-await vi.waitFor(() =>
120-expect(onExecutionPhase).toHaveBeenCalledWith(
121-expect.objectContaining({ phase: "turn_accepted" }),
122-),
123-);
124-for (const item of [
125-{
126-type: "function_call",
127-name: "slow_failure",
128-arguments: "{}",
129-call_id: "call-slow",
130-},
131-{
132-type: "function_call",
133-name: "shell_command",
134-arguments: '{"command":"git status --short"}',
135-call_id: "command-before-dynamic",
136-},
137-]) {
138-await harness.notify({
139-method: "rawResponseItem/completed",
140-params: { threadId: "thread-1", turnId: "turn-1", item },
141-});
142-}
143-const webSearchItem = {
144-type: "webSearch",
145-id: "web-search-before-dynamic",
146-query: "OpenClaw",
147-status: "completed",
148-durationMs: 1,
149-};
150-const webSearchStarted = harness.notify({
151-method: "item/started",
152-params: { threadId: "thread-1", turnId: "turn-1", item: webSearchItem },
153-});
154-const rawWebSearch = harness.notify({
155-method: "rawResponseItem/completed",
156-params: {
157-threadId: "thread-1",
158-turnId: "turn-1",
159-item: {
160-type: "web_search_call",
161-status: "completed",
162-action: { type: "search", query: "OpenClaw" },
163-},
164-},
165-});
166-await harness.notify({
167-method: "rawResponseItem/completed",
168-params: {
169-threadId: "thread-1",
170-turnId: "turn-1",
171-item: {
172-type: "function_call",
173-name: "fast_summary",
174-arguments: "{}",
175-call_id: "call-later",
176-},
177-},
178-});
179-await rawWebSearch;
180-const slowCall = harness.handleServerRequest({
181-id: "request-slow",
182-method: "item/tool/call",
183-params: {
184-threadId: "thread-1",
185-turnId: "turn-1",
186-callId: "call-slow",
187-namespace: null,
188-tool: "slow_failure",
189-arguments: {},
190-},
191-});
192-const nativeItem = {
193-type: "commandExecution",
194-id: "command-before-dynamic",
195-command: "git status --short",
196-cwd: "/workspace",
197-processId: null,
198-source: "agent",
199-status: "completed",
200-commandActions: [{ type: "unknown", command: "git status --short" }],
201-aggregatedOutput: "",
202-exitCode: 0,
203-durationMs: 1,
204-};
205-const nativeStarted = harness.notify({
206-method: "item/started",
207-params: { threadId: "thread-1", turnId: "turn-1", item: nativeItem },
208-});
209-await harness.handleServerRequest({
210-id: "request-later",
211-method: "item/tool/call",
212-params: {
213-threadId: "thread-1",
214-turnId: "turn-1",
215-callId: "call-later",
216-namespace: null,
217-tool: "fast_summary",
218-arguments: {},
219-},
220-});
221-await nativeStarted;
222-await webSearchStarted;
223-await harness.notify({
224-method: "item/completed",
225-params: { threadId: "thread-1", turnId: "turn-1", item: nativeItem },
226-});
227-await harness.notify({
228-method: "item/completed",
229-params: { threadId: "thread-1", turnId: "turn-1", item: webSearchItem },
230-});
231-rejectSlowTool(new Error("slow failure"));
232-await slowCall;
233-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
234-await run;
235-236-expect(terminalPresentation).toBe("later dynamic summary");
237-});
238-239-it("suppresses a late dynamic tool presentation after its timeout response", async () => {
240-let resolveSlowTool!: (result: ReturnType<typeof textToolResult>) => void;
241-const slowToolResult = new Promise<ReturnType<typeof textToolResult>>((resolve) => {
242-resolveSlowTool = resolve;
243-});
244-const formatTerminalPresentation = vi.fn(() => "late success summary");
245-const slowTool = createTerminalPresentationContractTool({
246-name: "slow_summary",
247-result: textToolResult("unused"),
248-format: formatTerminalPresentation,
249-});
250-slowTool.execute = vi.fn(() => slowToolResult);
251-const harness = createStartedThreadHarness();
252-const params = createParams(
253-path.join(tempDir, "session.jsonl"),
254-path.join(tempDir, "workspace"),
255-);
256-let terminalPresentation: string | undefined = "previous summary";
257-let latestOrdinal = -1;
258-let nextOrdinal = 0;
259-const onExecutionPhase = vi.fn();
260-params.disableTools = false;
261-params.runtimePlan = createCodexRuntimePlanFixture();
262-params.allocateToolOutcomeOrdinal = () => nextOrdinal++;
263-params.onExecutionPhase = onExecutionPhase;
264-params.onToolOutcome = (observation) => {
265-const ordinal = observation.toolCallOrdinal ?? latestOrdinal + 1;
266-if (ordinal >= latestOrdinal) {
267-latestOrdinal = ordinal;
268-terminalPresentation = observation.terminalPresentation;
269-}
270-};
271-testing.setOpenClawCodingToolsFactoryForTests((options) => [
272-wrapToolWithBeforeToolCallHook(slowTool, {
273-runId: options?.runId,
274-sessionId: options?.sessionId,
275-sessionKey: options?.sessionKey,
276-onToolOutcome: options?.onToolOutcome,
277-allocateToolOutcomeOrdinal: options?.allocateToolOutcomeOrdinal,
278-}),
279-]);
280-281-const run = runCodexAppServerAttempt(params);
282-await harness.waitForMethod("thread/start");
283-await vi.waitFor(() =>
284-expect(onExecutionPhase).toHaveBeenCalledWith(
285-expect.objectContaining({ phase: "turn_accepted" }),
286-),
287-);
288-const response = await harness.handleServerRequest({
289-id: "request-timeout",
290-method: "item/tool/call",
291-params: {
292-threadId: "thread-1",
293-turnId: "turn-1",
294-callId: "call-timeout",
295-namespace: null,
296-tool: "slow_summary",
297-arguments: { timeoutMs: 1 },
298-},
299-});
300-expect(response).toMatchObject({ success: false });
301-expect(terminalPresentation).toBeUndefined();
302-303-resolveSlowTool(textToolResult("late result"));
304-await vi.waitFor(() => {
305-expect(formatTerminalPresentation).toHaveBeenCalled();
306-});
307-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
308-await run;
309-310-expect(terminalPresentation).toBeUndefined();
311-});
312-31359it("passes the live run session key to Codex dynamic tools when sandbox policy uses another key", () => {
31460const workspaceDir = path.join(tempDir, "workspace");
31561const params = createParams(path.join(tempDir, "session.jsonl"), workspaceDir);
@@ -331,75 +77,6 @@ describe("runCodexAppServerAttempt dynamic tools", () => {
33177});
33278});
33379334-it("keeps Codex native code mode while registering node shell tools for node sessions", async () => {
335-const harness = createStartedThreadHarness();
336-const params = createParams(
337-path.join(tempDir, "session.jsonl"),
338-path.join(tempDir, "workspace"),
339-);
340-params.disableTools = false;
341-params.runtimePlan = createCodexRuntimePlanFixture();
342-params.execOverrides = {
343-host: "node",
344-node: "mac-mini",
345-security: "full",
346-ask: "off",
347-};
348-params.config = {
349-mcp: {
350-servers: {
351-local_docs: {
352-transport: "stdio",
353-command: "node",
354-args: ["/opt/local-docs-mcp/dist/index.js"],
355-},
356-},
357-},
358-} as never;
359-testing.setOpenClawCodingToolsFactoryForTests(() => [
360-createRuntimeDynamicTool("exec"),
361-createRuntimeDynamicTool("process"),
362-createRuntimeDynamicTool("message"),
363-]);
364-365-const run = runCodexAppServerAttempt(params);
366-await harness.waitForMethod("thread/start");
367-await harness.waitForMethod("turn/start");
368-369-const startParams = harness.requests.find((request) => request.method === "thread/start")
370-?.params as
371-| {
372-config?: {
373-"features.code_mode"?: boolean;
374-"features.code_mode_only"?: boolean;
375-mcp_servers?: Record<string, unknown>;
376-};
377-dynamicTools?: CodexDynamicToolSpec[];
378-environments?: unknown[];
379-}
380-| undefined;
381-382-expect(startParams?.config).toMatchObject({
383-"features.code_mode": true,
384-"features.code_mode_only": false,
385-mcp_servers: {
386-local_docs: {
387-command: "node",
388-args: ["/opt/local-docs-mcp/dist/index.js"],
389-},
390-},
391-});
392-expect(startParams?.environments).toBeUndefined();
393-expect(specNames(startParams?.dynamicTools ?? [])).toEqual([
394-"message",
395-"node_exec",
396-"node_process",
397-]);
398-399-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
400-await run;
401-});
402-40380it("emits normalized tool progress around app-server dynamic tool requests", async () => {
40481const harness = createStartedThreadHarness();
40582const onRunAgentEvent = vi.fn();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。