diff --git a/.changeset/skip-legacy-max-steps.md b/.changeset/skip-legacy-max-steps.md new file mode 100644 index 000000000..eb3bdfa1d --- /dev/null +++ b/.changeset/skip-legacy-max-steps.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/kimi-code": patch +"@moonshot-ai/migration-legacy": patch +--- + +Do not carry obsolete legacy loop, background, plan, yolo, or unknown experimental flags into migrated config files. diff --git a/packages/migration-legacy/src/steps/config.ts b/packages/migration-legacy/src/steps/config.ts index 72dd8e168..a685025d7 100644 --- a/packages/migration-legacy/src/steps/config.ts +++ b/packages/migration-legacy/src/steps/config.ts @@ -7,6 +7,7 @@ import { ProviderConfigSchema, transformTomlData, } from '@moonshot-ai/agent-core'; +import { FLAG_DEFINITIONS } from '@moonshot-ai/agent-core/flags/registry'; import { atomicWrite } from '../atomic-write.js'; import { DEFAULT_CONFIG_FILE_TEXT, isTuiStubOrMissing } from '../stub-detect.js'; import { @@ -19,6 +20,18 @@ import { // `theme` / `default_editor` belong in tui.toml, not config.toml. const TUI_TOP_LEVEL_KEYS = new Set(['theme', 'default_editor']); +const TOP_LEVEL_KEYS_TO_DROP = new Set(['plan_mode', 'yolo']); +const LOOP_CONTROL_FIELDS_TO_KEEP = new Set([ + 'max_retries_per_step', + 'reserved_context_size', +]); +const BACKGROUND_FIELDS_TO_KEEP = new Set([ + 'max_running_tasks', + 'keep_alive_on_exit', +]); +const REGISTERED_EXPERIMENTAL_FLAGS: ReadonlySet = new Set( + FLAG_DEFINITIONS.map((definition) => definition.id), +); // kimi-code's tui.toml `theme` enum (mirrors apps/kimi-code TuiThemeSchema). // A legacy theme outside this set would fail loadTuiConfig()'s whole-file @@ -97,6 +110,23 @@ function emptyResult(): ConfigStepResult { }; } +function filterFields( + value: Record, + fieldsToKeep: ReadonlySet, +): Record | undefined { + const keptEntries = Object.entries(value).filter(([field]) => fieldsToKeep.has(field)); + return keptEntries.length > 0 ? Object.fromEntries(keptEntries) : undefined; +} + +function filterRegisteredExperimentalFlags( + value: Record, +): Record | undefined { + const keptEntries = Object.entries(value).filter( + ([field, flag]) => REGISTERED_EXPERIMENTAL_FLAGS.has(field) && typeof flag === 'boolean', + ); + return keptEntries.length > 0 ? Object.fromEntries(keptEntries) : undefined; +} + /** True when the kimi-cli provider entry validates against kimi-code's schema. */ function providerIsSupported(prov: Record): boolean { const transformed = transformTomlData({ providers: { x: prov } }); @@ -309,6 +339,7 @@ export async function migrateConfigStep(input: ConfigStepInput): Promise 0) migratedTop['providers'] = keptProviders; diff --git a/packages/migration-legacy/test/steps/config.test.ts b/packages/migration-legacy/test/steps/config.test.ts index 983e200eb..ba420e725 100644 --- a/packages/migration-legacy/test/steps/config.test.ts +++ b/packages/migration-legacy/test/steps/config.test.ts @@ -430,6 +430,54 @@ base_url = "https://target.example/v1" expect(r.siblingContents.hooks).toBe(2); }); + it('drops legacy migration fields but keeps supported loop and background fields', async () => { + await writeFile( + join(src, 'config.toml'), + 'default_thinking = true\n' + + 'plan_mode = true\n' + + 'yolo = true\n' + + '[experimental]\n' + + 'micro_compaction = false\n' + + 'unknown_flag = true\n' + + '[loop_control]\n' + + 'max_steps_per_turn = 1000\n' + + 'max_steps_per_run = 42\n' + + 'max_retries_per_step = 2\n' + + 'max_ralph_iterations = 3\n' + + 'reserved_context_size = 60000\n' + + 'compaction_trigger_ratio = 0.7\n' + + '[background]\n' + + 'max_running_tasks = 8\n' + + 'keep_alive_on_exit = true\n' + + 'kill_grace_period_ms = 2000\n' + + 'print_wait_ceiling_s = 3600\n' + + 'read_max_bytes = 30000\n', + ); + + const r = await migrateConfigStep({ sourceHome: src, targetHome: tgt }); + + expect(r.migrated).toBe(true); + const cfg = await readFile(join(tgt, 'config.toml'), 'utf-8'); + expect(cfg).toContain('[experimental]'); + expect(cfg).toContain('micro_compaction = false'); + expect(cfg).not.toContain('unknown_flag'); + expect(cfg).toContain('[loop_control]'); + expect(cfg).toContain('max_retries_per_step = 2'); + expect(cfg).toContain('reserved_context_size = 60000'); + expect(cfg).not.toContain('max_steps_per_turn'); + expect(cfg).not.toContain('max_steps_per_run'); + expect(cfg).not.toContain('max_ralph_iterations'); + expect(cfg).not.toContain('compaction_trigger_ratio'); + expect(cfg).toContain('[background]'); + expect(cfg).toContain('max_running_tasks = 8'); + expect(cfg).toContain('keep_alive_on_exit = true'); + expect(cfg).not.toContain('kill_grace_period_ms'); + expect(cfg).not.toContain('print_wait_ceiling_s'); + expect(cfg).not.toContain('read_max_bytes'); + expect(cfg).not.toContain('plan_mode = true'); + expect(cfg).not.toContain('yolo = true'); + }); + it('maps default_yolo to default_permission_mode = "yolo"', async () => { await writeFile( join(src, 'config.toml'),