From a854cea4f7c6f69e24115c6017edb3b01d3594de Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 27 Jul 2026 04:34:38 -0400 Subject: [PATCH] fix(codex): stop supervision flag from gating the whole plugin config (#114413) The plugin re-resolved its own enable state with supervision.enabled as enabledByDefault, so an operator who only declared plugins.entries.codex.config (auto-enabled by core) silently lost every codex setting, including appServer.homeScope=user, whenever supervision was absent or off. Use a live plugin config block as the plugin-side default, matching core's plugin-tool-configured auto-enable and the onepassword convention. Supervision stays gated in its own tool surface. --- extensions/codex/index.test.ts | 87 ++++++++++++++++++++++++++++++++++ extensions/codex/index.ts | 7 ++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/extensions/codex/index.test.ts b/extensions/codex/index.test.ts index 84845a76a0f2..e90367f5bddf 100644 --- a/extensions/codex/index.test.ts +++ b/extensions/codex/index.test.ts @@ -256,6 +256,93 @@ describe("codex plugin", () => { expect(registration?.[0]({})).toEqual([]); }); + it.each([ + ["supervision is absent", undefined], + ["supervision is disabled", { enabled: false }], + ["supervision is enabled", { enabled: true }], + ] as const)( + "keeps live user-home appServer config for an auto-enabled Codex entry when %s", + (_label, supervision) => { + const registerTool = vi.fn(); + plugin.register( + createTestPluginApi({ + id: "codex", + name: "Codex", + source: "test", + config: {}, + pluginConfig: {}, + // No explicit plugins.entries.codex.enabled: core auto-enables the + // plugin from this config block, so the harness must keep honoring it. + runtime: createCodexTestRuntime(() => ({ + plugins: { + entries: { + codex: { + config: { + appServer: { homeScope: "user" }, + ...(supervision ? { supervision } : {}), + }, + }, + }, + }, + })), + registerAgentHarness: vi.fn(), + registerCommand: vi.fn(), + registerMediaUnderstandingProvider: vi.fn(), + registerMigrationProvider: vi.fn(), + registerProvider: vi.fn(), + registerTool, + on: vi.fn(), + }), + ); + + const registration = registerTool.mock.calls.find( + ([, options]) => options?.name === "codex_threads", + ) as + | [(context: { senderIsOwner?: boolean }) => { name: string } | null, { name: string }] + | undefined; + // codex_threads exists only while user-home scope or supervision is live, + // so it proves the plugin config survived the enable-state resolution. + expect(registration?.[0]({ senderIsOwner: true })?.name).toBe("codex_threads"); + }, + ); + + it("drops live plugin config when the Codex entry is explicitly disabled", () => { + const registerTool = vi.fn(); + plugin.register( + createTestPluginApi({ + id: "codex", + name: "Codex", + source: "test", + config: {}, + pluginConfig: {}, + runtime: createCodexTestRuntime(() => ({ + plugins: { + entries: { + codex: { + enabled: false, + config: { appServer: { homeScope: "user" } }, + }, + }, + }, + })), + registerAgentHarness: vi.fn(), + registerCommand: vi.fn(), + registerMediaUnderstandingProvider: vi.fn(), + registerMigrationProvider: vi.fn(), + registerProvider: vi.fn(), + registerTool, + on: vi.fn(), + }), + ); + + const registration = registerTool.mock.calls.find( + ([, options]) => options?.name === "codex_threads", + ) as + | [(context: { senderIsOwner?: boolean }) => { name: string } | null, { name: string }] + | undefined; + expect(registration?.[0]({ senderIsOwner: true })).toBeNull(); + }); + it("activates from live supervision config through a normalized Codex entry id", () => { const registerTool = vi.fn(); plugin.register( diff --git a/extensions/codex/index.ts b/extensions/codex/index.ts index 32b92e4aba51..c6a37897c1ff 100644 --- a/extensions/codex/index.ts +++ b/extensions/codex/index.ts @@ -81,7 +81,12 @@ export default definePluginEntry({ origin: "bundled", config: normalizePluginsConfig(liveConfig.plugins), rootConfig: liveConfig, - enabledByDefault: readCodexPluginConfig(livePluginConfig).supervision?.enabled === true, + // Core auto-enables this bundled plugin whenever the operator declares a + // codex config block, so a live block is the plugin-side default. Gating + // on a feature flag (supervision) here would silently drop unrelated + // harness settings such as appServer.homeScope; feature gates belong in + // the feature's own surface (see requireSupervisionEnabled). + enabledByDefault: livePluginConfig !== undefined, }).enabled; if (!enabled) { return undefined;