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.
This commit is contained in:
Peter Steinberger
2026-07-27 04:34:38 -04:00
committed by GitHub
parent da4ae86232
commit a854cea4f7
2 changed files with 93 additions and 1 deletions

View File

@@ -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(

View File

@@ -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;