

























@@ -252,76 +252,96 @@ describe("runCodexAppServerSideQuestion", () => {
252252const result = await runCodexAppServerSideQuestion(sideParams());
253253254254expect(result).toEqual({ text: "Side answer." });
255-expect(client.request).toHaveBeenNthCalledWith(
256-1,
257-"thread/fork",
258-expect.objectContaining({
259-threadId: "parent-thread",
260-model: "gpt-5.5",
261-approvalPolicy: "on-request",
262-sandbox: "workspace-write",
263-ephemeral: true,
264-threadSource: "user",
265-}),
266-expect.any(Object),
267-);
268-expect(client.request.mock.calls[0]?.[1]).not.toHaveProperty("dynamicTools");
269-expect(client.request.mock.calls[0]?.[1]).not.toHaveProperty("modelProvider");
270-expect(client.request).toHaveBeenNthCalledWith(
271-2,
272-"thread/inject_items",
273-expect.objectContaining({
274-threadId: "side-thread",
275-items: [expect.objectContaining({ type: "message", role: "user" })],
276-}),
277-expect.any(Object),
255+const forkCall = client.request.mock.calls[0];
256+expect(forkCall?.[0]).toBe("thread/fork");
257+const forkParams = forkCall?.[1] as Record<string, unknown> | undefined;
258+expect(Object.keys(forkParams ?? {}).sort()).toEqual([
259+"approvalPolicy",
260+"approvalsReviewer",
261+"config",
262+"cwd",
263+"developerInstructions",
264+"ephemeral",
265+"model",
266+"sandbox",
267+"threadId",
268+"threadSource",
269+]);
270+expect(forkParams?.threadId).toBe("parent-thread");
271+expect(forkParams?.model).toBe("gpt-5.5");
272+expect(forkParams?.approvalPolicy).toBe("on-request");
273+expect(forkParams?.sandbox).toBe("workspace-write");
274+expect(forkParams?.ephemeral).toBe(true);
275+expect(forkParams?.threadSource).toBe("user");
276+expect(forkParams?.approvalsReviewer).toBe("user");
277+expect(forkParams?.cwd).toBe("/tmp/workspace");
278+expect(forkParams?.config).toEqual({
279+"features.code_mode": true,
280+"features.code_mode_only": true,
281+});
282+expect(forkParams?.developerInstructions).toContain("You are in a side conversation");
283+expect(forkParams?.developerInstructions).toContain(
284+"Only instructions submitted after the side-conversation boundary are active.",
278285);
279-const injectedItem = (
280-client.request.mock.calls.find(([method]) => method === "thread/inject_items")?.[1] as {
281-items?: Array<{ content?: Array<{ text?: string }> }>;
282-}
283-)?.items?.[0];
286+expect(forkCall?.[2]).toEqual({ timeoutMs: 60_000, signal: undefined });
287+288+const injectCall = client.request.mock.calls[1];
289+expect(injectCall?.[0]).toBe("thread/inject_items");
290+const injectParams = injectCall?.[1] as
291+| { threadId?: string; items?: Array<{ type?: string; role?: string; content?: unknown }> }
292+| undefined;
293+expect(injectParams?.threadId).toBe("side-thread");
294+expect(injectParams?.items).toHaveLength(1);
295+expect(injectParams?.items?.[0]?.type).toBe("message");
296+expect(injectParams?.items?.[0]?.role).toBe("user");
297+expect(injectCall?.[2]).toEqual({ timeoutMs: 60_000, signal: undefined });
298+const injectedItem = injectParams?.items?.[0] as
299+| { content?: Array<{ text?: string }> }
300+| undefined;
284301const injectedText = injectedItem?.content?.[0]?.text;
285302expect(injectedText).toContain(
286303"External tools may be available according to this thread's current permissions",
287304);
288305expect(injectedText).toContain(
289306"unless the user explicitly asks for that mutation after this boundary",
290307);
291-expect(client.request).toHaveBeenCalledWith(
308+const turnStartCall = client.request.mock.calls.find(([method]) => method === "turn/start");
309+expect(turnStartCall).toEqual([
292310"turn/start",
293-expect.objectContaining({
311+{
294312threadId: "side-thread",
295313input: [{ type: "text", text: "What changed?", text_elements: [] }],
314+cwd: "/tmp/workspace",
296315model: "gpt-5.5",
297-}),
298-expect.any(Object),
299-);
300-const turnStartParams = client.request.mock.calls.find(
301-([method]) => method === "turn/start",
302-)?.[1] as Record<string, unknown> | undefined;
316+effort: null,
317+collaborationMode: {
318+mode: "default",
319+settings: {
320+model: "gpt-5.5",
321+reasoning_effort: null,
322+developer_instructions: null,
323+},
324+},
325+},
326+{ timeoutMs: 60_000, signal: undefined },
327+]);
328+const turnStartParams = turnStartCall?.[1] as Record<string, unknown> | undefined;
303329expect(turnStartParams).not.toHaveProperty("approvalPolicy");
304330expect(turnStartParams).not.toHaveProperty("sandboxPolicy");
305-expect(client.request).toHaveBeenLastCalledWith(
331+expect(client.request.mock.calls.at(-1)).toEqual([
306332"thread/unsubscribe",
307333{ threadId: "side-thread" },
308-expect.any(Object),
309-);
310-expect(client.request).not.toHaveBeenCalledWith(
311-"turn/interrupt",
312-expect.anything(),
313-expect.anything(),
314-);
315-expect(createOpenClawCodingToolsMock).toHaveBeenCalledWith(
316-expect.objectContaining({
317-agentDir: "/tmp/agent",
318-workspaceDir: "/tmp/workspace",
319-sessionId: "session-1",
320-modelProvider: "openai",
321-modelId: "gpt-5.5",
322-requireExplicitMessageTarget: true,
323-}),
324-);
334+{ timeoutMs: 60_000 },
335+]);
336+expect(client.request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
337+338+const [toolOptions] = createOpenClawCodingToolsMock.mock.calls[0] ?? [];
339+expect(toolOptions).toHaveProperty("agentDir", "/tmp/agent");
340+expect(toolOptions).toHaveProperty("workspaceDir", "/tmp/workspace");
341+expect(toolOptions).toHaveProperty("sessionId", "session-1");
342+expect(toolOptions).toHaveProperty("modelProvider", "openai");
343+expect(toolOptions).toHaveProperty("modelId", "gpt-5.5");
344+expect(toolOptions).toHaveProperty("requireExplicitMessageTarget", true);
325345});
326346327347it("bridges side-thread dynamic tool requests to OpenClaw tools", async () => {
@@ -362,12 +382,13 @@ describe("runCodexAppServerSideQuestion", () => {
362382const result = await runCodexAppServerSideQuestion(sideParams());
363383364384expect(result).toEqual({ text: "Tool answer." });
365-expect(toolExecuteMock).toHaveBeenCalledWith(
366-"tool-1",
367-{ topic: "AGENTS.md" },
368-expect.any(AbortSignal),
369-undefined,
370-);
385+const [toolCallId, toolArguments, toolSignal, toolOptions] =
386+toolExecuteMock.mock.calls[0] ?? [];
387+expect(toolExecuteMock).toHaveBeenCalledTimes(1);
388+expect(toolCallId).toBe("tool-1");
389+expect(toolArguments).toEqual({ topic: "AGENTS.md" });
390+expect(toolSignal).toBeInstanceOf(AbortSignal);
391+expect(toolOptions).toBeUndefined();
371392expect(toolResponse).toEqual({
372393success: true,
373394contentItems: [{ type: "inputText", text: "tool output" }],
@@ -549,15 +570,11 @@ describe("runCodexAppServerSideQuestion", () => {
549570}),
550571),
551572).rejects.toThrow("Codex /btw was aborted.");
552-expect(client.request).toHaveBeenCalledWith(
553-"turn/interrupt",
554-{ threadId: "side-thread", turnId: "turn-1" },
555-expect.any(Object),
556-);
557-expect(client.request).toHaveBeenCalledWith(
558-"thread/unsubscribe",
559-{ threadId: "side-thread" },
560-expect.any(Object),
573+expect(client.request.mock.calls.filter(([method]) => method === "turn/interrupt")).toEqual([
574+["turn/interrupt", { threadId: "side-thread", turnId: "turn-1" }, { timeoutMs: 60_000 }],
575+]);
576+expect(client.request.mock.calls.filter(([method]) => method === "thread/unsubscribe")).toEqual(
577+[["thread/unsubscribe", { threadId: "side-thread" }, { timeoutMs: 60_000 }]],
561578);
562579});
563580});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。