

























@@ -1119,6 +1119,7 @@ describe("runCodexAppServerAttempt", () => {
11191119params.disableTools = false;
11201120params.runtimePlan = createCodexRuntimePlanFixture();
11211121params.toolsAllow = [];
1122+params.extraSystemPrompt = "Tool and file actions are disabled for this sender by chat policy.";
1122112311231124const run = runCodexAppServerAttempt(params, {
11241125pluginConfig: {
@@ -1142,21 +1143,78 @@ describe("runCodexAppServerAttempt", () => {
11421143const startParams = startRequest?.params as
11431144| {
11441145dynamicTools?: Array<{ name?: string }>;
1146+environments?: unknown[];
1147+developerInstructions?: string;
11451148config?: {
11461149"features.code_mode"?: boolean;
11471150"features.code_mode_only"?: boolean;
1148-apps?: Record<string, { enabled?: boolean }>;
1151+apps?: Record<
1152+string,
1153+{ enabled?: boolean; destructive_enabled?: boolean; open_world_enabled?: boolean }
1154+>;
11491155};
11501156}
11511157| undefined;
1152115811531159expect(startParams?.dynamicTools).toEqual([]);
1160+expect(startParams?.environments).toEqual([]);
1161+expect(startParams?.developerInstructions).toContain(
1162+"Tool and file actions are disabled for this sender by chat policy.",
1163+);
11541164expect(startParams?.config?.["features.code_mode"]).toBe(false);
11551165expect(startParams?.config?.["features.code_mode_only"]).toBe(false);
1166+expect(startParams?.config?.apps?._default).toEqual({
1167+enabled: false,
1168+destructive_enabled: false,
1169+open_world_enabled: false,
1170+});
11561171expect(startParams?.config?.apps?.["google-calendar-app"]?.enabled).toBeUndefined();
11571172expect(harness.requests.map((entry) => entry.method)).not.toContain("app/list");
11581173});
115911741175+it("fails closed for Codex app defaults when restricted native tools have no plugin config", async () => {
1176+__testing.setOpenClawCodingToolsFactoryForTests(() => [createRuntimeDynamicTool("message")]);
1177+const harness = createStartedThreadHarness(async (method) => {
1178+if (method === "app/list") {
1179+throw new Error("app/list should not run when runtime toolsAllow is empty.");
1180+}
1181+return undefined;
1182+});
1183+const params = createParams(
1184+path.join(tempDir, "session.jsonl"),
1185+path.join(tempDir, "workspace"),
1186+);
1187+params.disableTools = false;
1188+params.runtimePlan = createCodexRuntimePlanFixture();
1189+params.toolsAllow = [];
1190+1191+const run = runCodexAppServerAttempt(params, {
1192+pluginConfig: { appServer: { mode: "yolo" } },
1193+});
1194+await harness.waitForMethod("turn/start", 120_000);
1195+await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
1196+await run;
1197+1198+const startRequest = harness.requests.find((entry) => entry.method === "thread/start");
1199+const startParams = startRequest?.params as
1200+| {
1201+config?: {
1202+apps?: Record<
1203+string,
1204+{ enabled?: boolean; destructive_enabled?: boolean; open_world_enabled?: boolean }
1205+>;
1206+};
1207+}
1208+| undefined;
1209+1210+expect(startParams?.config?.apps?._default).toEqual({
1211+enabled: false,
1212+destructive_enabled: false,
1213+open_world_enabled: false,
1214+});
1215+expect(harness.requests.map((entry) => entry.method)).not.toContain("app/list");
1216+});
1217+11601218it("returns a run context report without deferred Codex dynamic tool schemas", async () => {
11611219__testing.setOpenClawCodingToolsFactoryForTests(() => [
11621220createRuntimeDynamicTool("message"),
@@ -7056,6 +7114,105 @@ describe("runCodexAppServerAttempt", () => {
70567114]);
70577115});
705871167117+it("keeps plugin app bindings across transient native-tool-disabled turns", async () => {
7118+const sessionFile = path.join(tempDir, "session.jsonl");
7119+const workspaceDir = path.join(tempDir, "workspace");
7120+const pluginAppPolicyContext = createPluginAppPolicyContext();
7121+await writeExistingBinding(sessionFile, workspaceDir, {
7122+pluginAppsFingerprint: "plugin-apps-config-1",
7123+pluginAppsInputFingerprint: "plugin-apps-input-1",
7124+ pluginAppPolicyContext,
7125+});
7126+const params = createParams(sessionFile, workspaceDir);
7127+const appServer = createThreadLifecycleAppServerOptions();
7128+const request = vi.fn(async (method: string) => {
7129+if (method === "thread/start") {
7130+return threadStartResult("thread-transient");
7131+}
7132+if (method === "thread/resume") {
7133+return threadStartResult("thread-existing");
7134+}
7135+throw new Error(`unexpected method: ${method}`);
7136+});
7137+const buildDenyAllPluginThreadConfig = vi.fn(async () => ({
7138+enabled: true,
7139+configPatch: {
7140+apps: {
7141+_default: {
7142+enabled: false,
7143+destructive_enabled: false,
7144+open_world_enabled: false,
7145+},
7146+},
7147+},
7148+fingerprint: "plugin-apps-deny-all",
7149+inputFingerprint: "plugin-apps-input-deny-all",
7150+policyContext: { fingerprint: "plugin-policy-deny-all", apps: {}, pluginAppIds: {} },
7151+diagnostics: [],
7152+}));
7153+const buildEnabledPluginThreadConfig = vi.fn(async () => ({
7154+enabled: true,
7155+configPatch: createPluginAppConfigPatch(),
7156+fingerprint: "plugin-apps-config-1",
7157+inputFingerprint: "plugin-apps-input-1",
7158+policyContext: pluginAppPolicyContext,
7159+diagnostics: [],
7160+}));
7161+7162+await startOrResumeThread({
7163+client: { request } as never,
7164+ params,
7165+cwd: workspaceDir,
7166+dynamicTools: [],
7167+ appServer,
7168+nativeCodeModeEnabled: false,
7169+pluginThreadConfig: {
7170+enabled: true,
7171+inputFingerprint: "plugin-apps-input-deny-all",
7172+enabledPluginConfigKeys: [],
7173+build: buildDenyAllPluginThreadConfig,
7174+},
7175+});
7176+const savedAfterDeny = await readCodexAppServerBinding(sessionFile);
7177+7178+expect(savedAfterDeny?.threadId).toBe("thread-existing");
7179+expect(savedAfterDeny?.pluginAppsFingerprint).toBe("plugin-apps-config-1");
7180+expect(savedAfterDeny?.pluginAppsInputFingerprint).toBe("plugin-apps-input-1");
7181+7182+await startOrResumeThread({
7183+client: { request } as never,
7184+ params,
7185+cwd: workspaceDir,
7186+dynamicTools: [],
7187+ appServer,
7188+pluginThreadConfig: {
7189+enabled: true,
7190+inputFingerprint: "plugin-apps-input-1",
7191+enabledPluginConfigKeys: ["google-calendar"],
7192+build: buildEnabledPluginThreadConfig,
7193+},
7194+});
7195+7196+expect(buildDenyAllPluginThreadConfig).toHaveBeenCalledTimes(1);
7197+expect(buildEnabledPluginThreadConfig).toHaveBeenCalledTimes(1);
7198+const requestCalls = request.mock.calls as unknown as Array<[string, { config?: unknown }]>;
7199+expect(requestCalls.map(([method]) => method)).toEqual(["thread/start", "thread/resume"]);
7200+expect(requestCalls[0]?.[1].config).toMatchObject({
7201+apps: {
7202+_default: {
7203+enabled: false,
7204+destructive_enabled: false,
7205+open_world_enabled: false,
7206+},
7207+},
7208+});
7209+const savedAfterAllowed = await readCodexAppServerBinding(sessionFile);
7210+expect(savedAfterAllowed?.threadId).toBe("thread-existing");
7211+expect(savedAfterAllowed?.pluginAppsFingerprint).toBe("plugin-apps-config-1");
7212+expect(savedAfterAllowed?.pluginAppsInputFingerprint).toBe("plugin-apps-input-1");
7213+expect(savedAfterAllowed?.pluginAppPolicyContext).toEqual(pluginAppPolicyContext);
7214+});
7215+70597216it("preserves the binding when the app-server closes during thread resume", async () => {
70607217const sessionFile = path.join(tempDir, "session.jsonl");
70617218const workspaceDir = path.join(tempDir, "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。