



























@@ -300,6 +300,7 @@ describe("startOrResumeThread — user mcp.servers projection (regression: #8081
300300cwd: workspaceDir,
301301model: "gpt-5.4-codex",
302302modelProvider: "openai",
303+dynamicToolsFingerprint: "[]",
303304});
304305const request = vi.fn(async (method: string, _params: unknown) => {
305306if (method === "thread/start") {
@@ -338,6 +339,87 @@ describe("startOrResumeThread — user mcp.servers projection (regression: #8081
338339expect(preservedBinding?.threadId).toBe("thread-native");
339340});
340341342+it("preserves MCP-mismatched bindings across transient native-tool-disabled turns", async () => {
343+const sessionFile = path.join(tempDir, "session.jsonl");
344+const workspaceDir = path.join(tempDir, "workspace");
345+await writeCodexAppServerBinding(sessionFile, {
346+threadId: "thread-native",
347+cwd: workspaceDir,
348+model: "gpt-5.4-codex",
349+modelProvider: "openai",
350+dynamicToolsFingerprint: "[]",
351+mcpServersFingerprint: "mcp-v1",
352+});
353+const request = vi.fn(async (method: string, _params: unknown) => {
354+if (method === "thread/start") {
355+return threadStartResult("thread-restricted");
356+}
357+throw new Error(`unexpected method: ${method}`);
358+});
359+360+await startOrResumeThread({
361+client: { request } as never,
362+params: createParams(sessionFile, workspaceDir),
363+cwd: workspaceDir,
364+dynamicTools: [],
365+appServer: createAppServerOptions(),
366+mcpServersFingerprint: undefined,
367+mcpServersFingerprintEvaluated: true,
368+nativeCodeModeEnabled: false,
369+userMcpServersEnabled: false,
370+});
371+372+expect(request.mock.calls.map(([method]) => method)).toEqual(["thread/start"]);
373+const startParams = request.mock.calls[0]?.[1] as {
374+config?: {
375+"features.code_mode"?: boolean;
376+mcp_servers?: Record<string, unknown>;
377+};
378+};
379+expect(startParams?.config?.["features.code_mode"]).toBe(false);
380+expect(startParams?.config?.mcp_servers).toBeUndefined();
381+const preservedBinding = await readCodexAppServerBinding(sessionFile);
382+expect(preservedBinding?.threadId).toBe("thread-native");
383+expect(preservedBinding?.mcpServersFingerprint).toBe("mcp-v1");
384+});
385+386+it("preserves MCP-mismatched bindings when provider web-search support is unknown", async () => {
387+const sessionFile = path.join(tempDir, "session.jsonl");
388+const workspaceDir = path.join(tempDir, "workspace");
389+await writeCodexAppServerBinding(sessionFile, {
390+threadId: "thread-native",
391+cwd: workspaceDir,
392+model: "gpt-5.4-codex",
393+modelProvider: "openai",
394+dynamicToolsFingerprint: "[]",
395+webSearchThreadConfigFingerprint: "web-search-v1",
396+mcpServersFingerprint: "mcp-v1",
397+});
398+const request = vi.fn(async (method: string, _params: unknown) => {
399+if (method === "thread/start") {
400+return threadStartResult("thread-fallback");
401+}
402+throw new Error(`unexpected method: ${method}`);
403+});
404+405+await startOrResumeThread({
406+client: { request } as never,
407+params: createParams(sessionFile, workspaceDir),
408+cwd: workspaceDir,
409+dynamicTools: [],
410+appServer: createAppServerOptions(),
411+mcpServersFingerprint: undefined,
412+mcpServersFingerprintEvaluated: true,
413+nativeProviderWebSearchSupport: "unknown",
414+userMcpServersEnabled: false,
415+});
416+417+expect(request.mock.calls.map(([method]) => method)).toEqual(["thread/start"]);
418+const preservedBinding = await readCodexAppServerBinding(sessionFile);
419+expect(preservedBinding?.threadId).toBe("thread-native");
420+expect(preservedBinding?.mcpServersFingerprint).toBe("mcp-v1");
421+});
422+341423it("starts a new thread without user MCP servers when runtime policy disables them", async () => {
342424const sessionFile = path.join(tempDir, "session.jsonl");
343425const workspaceDir = path.join(tempDir, "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。