diff --git a/src/config/config.tools-alsoAllow.test.ts b/src/config/config.tools-alsoAllow.test.ts new file mode 100644 index 000000000..aea4f02d9 --- /dev/null +++ b/src/config/config.tools-alsoAllow.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; + +import { validateConfigObject } from "./validation.js"; + +// NOTE: These tests ensure allow + alsoAllow cannot be set in the same scope. + +describe("config: tools.alsoAllow", () => { + it("rejects tools.allow + tools.alsoAllow together", () => { + const res = validateConfigObject({ + tools: { + allow: ["group:fs"], + alsoAllow: ["lobster"], + }, + }); + + expect(res.ok).toBe(false); + if (!res.ok) { + expect(res.issues.some((i) => i.path === "tools")).toBe(true); + } + }); + + it("rejects agents.list[].tools.allow + alsoAllow together", () => { + const res = validateConfigObject({ + agents: { + list: [ + { + id: "main", + tools: { + allow: ["group:fs"], + alsoAllow: ["lobster"], + }, + }, + ], + }, + }); + + expect(res.ok).toBe(false); + if (!res.ok) { + expect(res.issues.some((i) => i.path.includes("agents.list"))).toBe(true); + } + }); + + it("allows profile + alsoAllow", () => { + const res = validateConfigObject({ + tools: { + profile: "coding", + alsoAllow: ["lobster"], + }, + }); + + expect(res.ok).toBe(true); + }); +}); diff --git a/src/config/legacy.migrations.part-3.ts b/src/config/legacy.migrations.part-3.ts index d4b75e871..21589e4fa 100644 --- a/src/config/legacy.migrations.part-3.ts +++ b/src/config/legacy.migrations.part-3.ts @@ -9,83 +9,9 @@ import { resolveDefaultAgentIdFromRaw, } from "./legacy.shared.js"; -function mergeAlsoAllowIntoAllow(node: unknown): boolean { - if (!isRecord(node)) return false; - const allow = node.allow; - const alsoAllow = node.alsoAllow; - if (!Array.isArray(allow) || allow.length === 0) return false; - if (!Array.isArray(alsoAllow) || alsoAllow.length === 0) return false; - const merged = Array.from(new Set([...(allow as unknown[]), ...(alsoAllow as unknown[])])); - node.allow = merged; - delete node.alsoAllow; - return true; -} +// NOTE: tools.alsoAllow was introduced after legacy migrations; no legacy migration needed. -function migrateAlsoAllowInToolConfig(raw: Record, changes: string[]) { - let mutated = false; - - // Global tools - const tools = getRecord(raw.tools); - if (mergeAlsoAllowIntoAllow(tools)) { - mutated = true; - changes.push("Merged tools.alsoAllow into tools.allow (and removed tools.alsoAllow)."); - } - - // tools.byProvider.* - const byProvider = getRecord(tools?.byProvider); - if (byProvider) { - for (const [key, value] of Object.entries(byProvider)) { - if (mergeAlsoAllowIntoAllow(value)) { - mutated = true; - changes.push(`Merged tools.byProvider.${key}.alsoAllow into allow (and removed alsoAllow).`); - } - } - } - - // agents.list[].tools - const agentsList = getAgentsList(raw); - for (const agent of agentsList) { - const agentTools = getRecord(agent.tools); - if (mergeAlsoAllowIntoAllow(agentTools)) { - mutated = true; - const id = typeof agent.id === "string" ? agent.id : ""; - changes.push(`Merged agents.list[${id}].tools.alsoAllow into allow (and removed alsoAllow).`); - } - - const agentByProvider = getRecord(agentTools?.byProvider); - if (agentByProvider) { - for (const [key, value] of Object.entries(agentByProvider)) { - if (mergeAlsoAllowIntoAllow(value)) { - mutated = true; - const id = typeof agent.id === "string" ? agent.id : ""; - changes.push( - `Merged agents.list[${id}].tools.byProvider.${key}.alsoAllow into allow (and removed alsoAllow).`, - ); - } - } - } - } - - // Provider group tool policies: channels..groups.*.tools and similar nested tool policy objects. - const channels = getRecord(raw.channels); - if (channels) { - for (const [provider, providerCfg] of Object.entries(channels)) { - const groups = getRecord(getRecord(providerCfg)?.groups); - if (!groups) continue; - for (const [groupKey, groupCfg] of Object.entries(groups)) { - const toolsCfg = getRecord(getRecord(groupCfg)?.tools); - if (mergeAlsoAllowIntoAllow(toolsCfg)) { - mutated = true; - changes.push( - `Merged channels.${provider}.groups.${groupKey}.tools.alsoAllow into allow (and removed alsoAllow).`, - ); - } - } - } - } - - return mutated; -} +// tools.alsoAllow legacy migration intentionally omitted (field not shipped in prod). export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ { @@ -102,13 +28,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [ changes.push('Updated auth.profiles["anthropic:claude-cli"].mode → "oauth".'); }, }, - { - id: "tools.alsoAllow-merge", - describe: "Merge tools.alsoAllow into allow when allow is present", - apply: (raw, changes) => { - migrateAlsoAllowInToolConfig(raw, changes); - }, - }, + // tools.alsoAllow migration removed (field not shipped in prod; enforce via schema instead). { id: "tools.bash->tools.exec", describe: "Move tools.bash to tools.exec",