



























@@ -58,6 +58,7 @@ import {
5858buildCodexPluginAppCacheKey,
5959resolveCodexPluginAppCacheEndpoint,
6060} from "./plugin-app-cache-key.js";
61+import { buildCodexPluginThreadConfig } from "./plugin-thread-config.js";
6162import type { CodexServerNotification } from "./protocol.js";
6263import {
6364readRecentCodexRateLimits,
@@ -569,6 +570,76 @@ function createCodexToolBridgeForTest(
569570});
570571}
571572573+async function startThreadWithDisabledNativeSurfaceForTest(
574+params: EmbeddedRunAttemptParams,
575+options: {
576+pluginConfig?: Record<string, unknown>;
577+developerInstructions?: string;
578+} = {},
579+) {
580+const workspaceDir = params.workspaceDir;
581+if (!workspaceDir) {
582+throw new Error("createParams must provide a workspaceDir for Codex thread tests.");
583+}
584+const sandboxSessionKey = params.sessionKey;
585+if (!sandboxSessionKey) {
586+throw new Error("createParams must provide a sessionKey for Codex dynamic tool tests.");
587+}
588+const nativeToolSurfaceEnabled = testing.shouldEnableCodexAppServerNativeToolSurface(params);
589+const dynamicTools = await testing.buildDynamicTools({
590+ params,
591+resolvedWorkspace: workspaceDir,
592+effectiveWorkspace: workspaceDir,
593+ sandboxSessionKey,
594+sandbox: { enabled: false, backendId: "docker" } as never,
595+ nativeToolSurfaceEnabled,
596+runAbortController: new AbortController(),
597+sessionAgentId: "main",
598+pluginConfig: options.pluginConfig ?? {},
599+onYieldDetected: () => undefined,
600+});
601+const request = vi.fn(async (method: string, _requestParams?: unknown) => {
602+if (method === "thread/start") {
603+return threadStartResult();
604+}
605+if (method === "app/list") {
606+throw new Error("app/list should not run when runtime toolsAllow is empty.");
607+}
608+throw new Error(`unexpected method: ${method}`);
609+});
610+const pluginConfig = {
611+ ...options.pluginConfig,
612+codexPlugins: {
613+ ...((options.pluginConfig?.codexPlugins as Record<string, unknown> | undefined) ?? {}),
614+enabled: false,
615+},
616+};
617+618+await startOrResumeThread({
619+client: { request } as never,
620+ params,
621+cwd: workspaceDir,
622+dynamicTools: dynamicTools as never,
623+appServer: createThreadLifecycleAppServerOptions(),
624+developerInstructions: options.developerInstructions,
625+nativeCodeModeEnabled: nativeToolSurfaceEnabled,
626+nativeCodeModeOnlyEnabled: false,
627+userMcpServersEnabled: false,
628+environmentSelection: [],
629+pluginThreadConfig: {
630+enabled: true,
631+build: () =>
632+buildCodexPluginThreadConfig({
633+ pluginConfig,
634+request: request as never,
635+appCacheKey: "test-app-cache-key",
636+}),
637+},
638+});
639+640+return { request, nativeToolSurfaceEnabled };
641+}
642+572643function filterAllowedRuntimeToolNamesForTest(
573644params: EmbeddedRunAttemptParams,
574645tools: RuntimeDynamicToolForTest[],
@@ -2489,12 +2560,6 @@ describe("runCodexAppServerAttempt", () => {
24892560createRuntimeDynamicTool("message"),
24902561createRuntimeDynamicTool("web_search"),
24912562]);
2492-const harness = createStartedThreadHarness(async (method) => {
2493-if (method === "app/list") {
2494-throw new Error("app/list should not run when runtime toolsAllow is empty.");
2495-}
2496-return undefined;
2497-});
24982563const params = createParams(
24992564path.join(tempDir, "session.jsonl"),
25002565path.join(tempDir, "workspace"),
@@ -2504,26 +2569,27 @@ describe("runCodexAppServerAttempt", () => {
25042569params.toolsAllow = [];
25052570params.extraSystemPrompt = "Tool and file actions are disabled for this sender by chat policy.";
250625712507-const run = runCodexAppServerAttempt(params, {
2508-pluginConfig: {
2509-appServer: { mode: "yolo" },
2510-codexPlugins: {
2511-enabled: true,
2512-plugins: {
2513-"google-calendar": {
2514-marketplaceName: "openai-curated",
2515-pluginName: "google-calendar",
2572+const { request, nativeToolSurfaceEnabled } = await startThreadWithDisabledNativeSurfaceForTest(
2573+params,
2574+{
2575+pluginConfig: {
2576+appServer: { mode: "yolo" },
2577+codexPlugins: {
2578+enabled: true,
2579+plugins: {
2580+"google-calendar": {
2581+marketplaceName: "openai-curated",
2582+pluginName: "google-calendar",
2583+},
25162584},
25172585},
25182586},
2587+developerInstructions: params.extraSystemPrompt,
25192588},
2520-});
2521-await harness.waitForMethod("turn/start", 120_000);
2522-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
2523-await run;
2589+);
252425902525-const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
2526-const startParams = startRequest?.params as
2591+const startRequest = request.mock.calls.find(([method]) => method === "thread/start");
2592+const startParams = startRequest?.[1] as
25272593| {
25282594dynamicTools?: Array<{ name?: string }>;
25292595environments?: unknown[];
@@ -2539,6 +2605,7 @@ describe("runCodexAppServerAttempt", () => {
25392605}
25402606| undefined;
254126072608+expect(nativeToolSurfaceEnabled).toBe(false);
25422609expect(startParams?.dynamicTools).toEqual([]);
25432610expect(startParams?.environments).toEqual([]);
25442611expect(startParams?.developerInstructions).toContain(
@@ -2552,17 +2619,11 @@ describe("runCodexAppServerAttempt", () => {
25522619open_world_enabled: false,
25532620});
25542621expect(startParams?.config?.apps?.["google-calendar-app"]?.enabled).toBeUndefined();
2555-expect(harness.requests.map((entry) => entry.method)).not.toContain("app/list");
2622+expect(request.mock.calls.map(([method]) => method)).not.toContain("app/list");
25562623});
2557262425582625it("fails closed for Codex app defaults when restricted native tools have no plugin config", async () => {
25592626testing.setOpenClawCodingToolsFactoryForTests(() => [createRuntimeDynamicTool("message")]);
2560-const harness = createStartedThreadHarness(async (method) => {
2561-if (method === "app/list") {
2562-throw new Error("app/list should not run when runtime toolsAllow is empty.");
2563-}
2564-return undefined;
2565-});
25662627const params = createParams(
25672628path.join(tempDir, "session.jsonl"),
25682629path.join(tempDir, "workspace"),
@@ -2571,15 +2632,12 @@ describe("runCodexAppServerAttempt", () => {
25712632params.runtimePlan = createCodexRuntimePlanFixture();
25722633params.toolsAllow = [];
257326342574-const run = runCodexAppServerAttempt(params, {
2635+const { request } = await startThreadWithDisabledNativeSurfaceForTest(params, {
25752636pluginConfig: { appServer: { mode: "yolo" } },
25762637});
2577-await harness.waitForMethod("turn/start", 120_000);
2578-await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
2579-await run;
258026382581-const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
2582-const startParams = startRequest?.params as
2639+const startRequest = request.mock.calls.find(([method]) => method === "thread/start");
2640+const startParams = startRequest?.[1] as
25832641| {
25842642config?: {
25852643apps?: Record<
@@ -2595,7 +2653,7 @@ describe("runCodexAppServerAttempt", () => {
25952653destructive_enabled: false,
25962654open_world_enabled: false,
25972655});
2598-expect(harness.requests.map((entry) => entry.method)).not.toContain("app/list");
2656+expect(request.mock.calls.map(([method]) => method)).not.toContain("app/list");
25992657});
2600265826012659it("returns a run context report without deferred Codex dynamic tool schemas", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。