fix: allow obsolete experimental config entries (#584)

* fix: allow obsolete experimental config entries

* docs: remove config docs update from PR
This commit is contained in:
liruifengv 2026-06-09 17:00:48 +08:00 committed by GitHub
parent aa3471f5d3
commit 11bb62c12f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 46 additions and 26 deletions

View file

@ -0,0 +1,6 @@
---
"@moonshot-ai/agent-core": patch
"@moonshot-ai/kimi-code": patch
---
Allow obsolete experimental config entries to remain without blocking startup.

View file

@ -1,7 +1,6 @@
import { HOOK_EVENT_TYPES } from '../session/hooks/types';
import { parsePattern } from '#/agent/permission/matches-rule';
import { ErrorCodes, KimiError } from '#/errors';
import { FLAG_DEFINITIONS, type FlagId } from '#/flags/registry';
import { z } from 'zod';
export const ProviderTypeSchema = z.enum([
@ -105,20 +104,7 @@ export const BackgroundConfigSchema = z.object({
export type BackgroundConfig = z.infer<typeof BackgroundConfigSchema>;
const ExperimentalFlagIdSet = new Set<string>(FLAG_DEFINITIONS.map((def) => def.id));
export const ExperimentalConfigSchema = z
.record(z.string(), z.boolean())
.superRefine((config, ctx) => {
for (const key of Object.keys(config)) {
if (ExperimentalFlagIdSet.has(key)) continue;
ctx.addIssue({
code: 'custom',
path: [key],
message: `Unknown experimental feature "${key}".`,
});
}
}) as z.ZodType<Partial<Record<FlagId, boolean>>>;
export const ExperimentalConfigSchema = z.record(z.string(), z.boolean());
export type ExperimentalConfig = z.infer<typeof ExperimentalConfigSchema>;

View file

@ -279,18 +279,27 @@ micro_compaction = false
expect(parseConfigString(text, configPath).experimental).toEqual(config.experimental);
});
it('rejects unknown experimental feature keys', () => {
expectKimiErrorCode(
() =>
parseConfigString(
`
it('accepts obsolete experimental feature keys as inert config', async () => {
const dir = makeTempDir();
const configPath = join(dir, 'obsolete-experimental.toml');
const toml = `
[experimental]
not_registered = true
`,
'unknown-experimental.toml',
),
ErrorCodes.CONFIG_INVALID,
);
legacy_feature = true
obsolete_feature = false
removed_flag = true
`;
const config = parseConfigString(toml, configPath);
expect(config.experimental).toEqual({
'legacy_feature': true,
'obsolete_feature': false,
'removed_flag': true,
});
await writeConfigFile(configPath, config);
const text = await readFile(configPath, 'utf-8');
expect(parseConfigString(text, configPath).experimental).toEqual(config.experimental);
});
it('loads defaults for absent files and writes typed fields without dropping raw sections', async () => {

View file

@ -106,6 +106,25 @@ describe('FlagResolver', () => {
expect(resolver.enabled('b-off-default' as FlagId)).toBe(true);
});
it('ignores obsolete config override ids outside the registry', () => {
const resolver = new FlagResolver({}, DEFS, {
'a-on-default': false,
'legacy_feature': true,
} as never);
expect(resolver.enabled('a-on-default' as FlagId)).toBe(false);
expect(resolver.enabled('legacy_feature' as FlagId)).toBe(false);
expect(resolver.snapshot()).toEqual({
'a-on-default': false,
'b-off-default': false,
});
expect(resolver.enabledIds()).toEqual([]);
expect(resolver.explainAll().map((feature) => feature.id)).toEqual([
'a-on-default',
'b-off-default',
]);
});
it('keeps env precedence above config overrides', () => {
const resolver = new FlagResolver(
{