From e4c228d36e5dd19ac50837a1fc38a71bae8969e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Wed, 27 May 2026 01:50:50 +0000 Subject: [PATCH] feat(cli): add agentOverrides and modelOverrides to config key validation (#532) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add agentOverrides (minDepth 3) and modelOverrides (minDepth 2) to VALID_CONFIG_KEYS - Support per-key minDepth instead of hardcoded 3 - No knownFields for either key (sub-keys are user-defined) - Add 5 new tests covering valid/invalid paths for both keys 小橘 --- .../cli-workflow/src/__tests__/config.test.ts | 60 +++++++++++++++++++ packages/cli-workflow/src/commands/config.ts | 21 ++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/packages/cli-workflow/src/__tests__/config.test.ts b/packages/cli-workflow/src/__tests__/config.test.ts index 2c08777..078c2f2 100644 --- a/packages/cli-workflow/src/__tests__/config.test.ts +++ b/packages/cli-workflow/src/__tests__/config.test.ts @@ -618,5 +618,65 @@ defaultModel: default rmSync(tempDir, { recursive: true, force: true }); } }); + + test("agentOverrides — accepts valid 3-segment path", async () => { + const tempDir = mkdtempSync(join(tmpdir(), "test-config-")); + try { + createTestConfig(tempDir, sampleConfig); + await cmdConfigSet(tempDir, "agentOverrides.solve-issue.planner", "claude-code"); + const value = await cmdConfigGet(tempDir, "agentOverrides.solve-issue.planner"); + expect(value).toBe("claude-code"); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + test("agentOverrides — rejects incomplete path (2 segments)", async () => { + const tempDir = mkdtempSync(join(tmpdir(), "test-config-")); + try { + createTestConfig(tempDir, sampleConfig); + await expect(cmdConfigSet(tempDir, "agentOverrides.solve-issue", "hermes")).rejects.toThrow( + /incomplete path|must specify a field/i, + ); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + test("modelOverrides — accepts valid 2-segment path", async () => { + const tempDir = mkdtempSync(join(tmpdir(), "test-config-")); + try { + createTestConfig(tempDir, sampleConfig); + await cmdConfigSet(tempDir, "modelOverrides.extract", "gpt4"); + const value = await cmdConfigGet(tempDir, "modelOverrides.extract"); + expect(value).toBe("gpt4"); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + test("modelOverrides — rejects incomplete path (1 segment only)", async () => { + const tempDir = mkdtempSync(join(tmpdir(), "test-config-")); + try { + createTestConfig(tempDir, sampleConfig); + await expect(cmdConfigSet(tempDir, "modelOverrides", "gpt4")).rejects.toThrow( + /incomplete path|must specify a field/i, + ); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); + + test("rejects unknown top-level key (regression)", async () => { + const tempDir = mkdtempSync(join(tmpdir(), "test-config-")); + try { + createTestConfig(tempDir, sampleConfig); + await expect(cmdConfigSet(tempDir, "randomKey", "value")).rejects.toThrow( + /Unknown config key/, + ); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } + }); }); }); diff --git a/packages/cli-workflow/src/commands/config.ts b/packages/cli-workflow/src/commands/config.ts index 63d3a9b..a791fe1 100644 --- a/packages/cli-workflow/src/commands/config.ts +++ b/packages/cli-workflow/src/commands/config.ts @@ -5,7 +5,10 @@ import { parse, stringify } from "yaml"; /** * Valid configuration key schema */ -const VALID_CONFIG_KEYS: Record = { +const VALID_CONFIG_KEYS: Record< + string, + { nested: boolean; knownFields?: string[]; minDepth?: number } +> = { providers: { nested: true, knownFields: ["baseUrl", "apiKey"], @@ -18,6 +21,17 @@ const VALID_CONFIG_KEYS: Record. = agentAlias (string value) + // No knownFields — workflow/role names are user-defined + }, + modelOverrides: { + nested: true, + minDepth: 2, + // modelOverrides. = modelAlias (string value) + // No knownFields — scenarios are user-defined + }, defaultAgent: { nested: false }, defaultModel: { nested: false }, }; @@ -43,8 +57,9 @@ function validateConfigKey(path: string[]): void { throw new Error(`${topLevel} is a scalar key and cannot have nested properties`); } - // Nested keys must have at least 3 segments (e.g., providers.myProvider.baseUrl) - if (schema.nested && path.length < 3) { + // Nested keys must have at least minDepth segments (default 3) + const minDepth = schema.minDepth ?? 3; + if (schema.nested && path.length < minDepth) { const fields = schema.knownFields?.join(", ") ?? ""; throw new Error( `Incomplete path for ${topLevel}. Must specify a field (e.g., ${topLevel}..). Valid fields: ${fields}`,