From 14149ffa96cf77c51a67775a2be2ef2c1ffbd074 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 3 Jun 2026 11:11:39 +0800 Subject: [PATCH] feat: log enabled experimental flags --- .changeset/log-enabled-flags.md | 6 +++ packages/agent-core/src/flags/resolver.ts | 18 ++++++-- packages/agent-core/src/rpc/core-impl.ts | 7 +--- .../agent-core/test/flags/resolver.test.ts | 11 ++++- .../agent-core/test/harness/runtime.test.ts | 41 ++++++++++++++++++- 5 files changed, 72 insertions(+), 11 deletions(-) create mode 100644 .changeset/log-enabled-flags.md diff --git a/.changeset/log-enabled-flags.md b/.changeset/log-enabled-flags.md new file mode 100644 index 00000000..1da7b4ec --- /dev/null +++ b/.changeset/log-enabled-flags.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/agent-core": minor +"@moonshot-ai/kimi-code": minor +--- + +Log enabled experimental flags at startup. diff --git a/packages/agent-core/src/flags/resolver.ts b/packages/agent-core/src/flags/resolver.ts index db24e377..3675b7a7 100644 --- a/packages/agent-core/src/flags/resolver.ts +++ b/packages/agent-core/src/flags/resolver.ts @@ -17,14 +17,12 @@ export const MASTER_ENV = 'KIMI_CODE_EXPERIMENTAL_FLAG'; * L3 registry default */ export class FlagResolver { - private readonly env: Readonly>; private readonly byId: ReadonlyMap; constructor( - env: Readonly> = process.env, - definitions: readonly FlagDefinitionInput[] = FLAG_DEFINITIONS, + private readonly env: Readonly> = process.env, + private readonly definitions: readonly FlagDefinitionInput[] = FLAG_DEFINITIONS, ) { - this.env = env; this.byId = new Map(definitions.map((def) => [def.id, def])); } @@ -36,6 +34,18 @@ export class FlagResolver { if (override !== undefined) return override; return def.default; // L3 default } + + snapshot(): Record { + return Object.fromEntries( + this.definitions.map((def) => [def.id, this.enabled(def.id as FlagId)]), + ); + } + + enabledIds(): readonly FlagId[] { + return this.definitions + .filter((def) => this.enabled(def.id as FlagId)) + .map((def) => def.id as FlagId); + } } /** diff --git a/packages/agent-core/src/rpc/core-impl.ts b/packages/agent-core/src/rpc/core-impl.ts index a6ede1ac..750af181 100644 --- a/packages/agent-core/src/rpc/core-impl.ts +++ b/packages/agent-core/src/rpc/core-impl.ts @@ -22,11 +22,8 @@ import { type MoonshotServiceConfig, } from '../config'; import { - FLAG_DEFINITIONS, flags, type ExperimentalFlagMap, - type FlagDefinitionInput, - type FlagId, } from '../flags'; import type { Logger } from '../logging/types'; import { resolveSessionMcpConfig, type SessionMcpConfig } from '../mcp'; @@ -169,6 +166,7 @@ export class KimiCore implements PromisableMethods { this.pluginsReady = this.plugins.load().catch((error: unknown) => { this.pluginsLoadError = error instanceof Error ? error : new Error(String(error)); }); + log.info('experimental flags enabled', { flags: flags.enabledIds() }); this.sdk = rpcClient(this); } @@ -259,8 +257,7 @@ export class KimiCore implements PromisableMethods { } getExperimentalFlags(): ExperimentalFlagMap { - const defs: readonly FlagDefinitionInput[] = FLAG_DEFINITIONS; - return Object.fromEntries(defs.map((def) => [def.id, flags.enabled(def.id as FlagId)])); + return flags.snapshot(); } async closeSession({ sessionId }: CloseSessionPayload): Promise { diff --git a/packages/agent-core/test/flags/resolver.test.ts b/packages/agent-core/test/flags/resolver.test.ts index a143b8c5..f484a3eb 100644 --- a/packages/agent-core/test/flags/resolver.test.ts +++ b/packages/agent-core/test/flags/resolver.test.ts @@ -63,7 +63,7 @@ describe('FlagResolver', () => { expect(enabled('b-off-default')).toBe(true); }); - it('L1 master switch beats an L2 per-feature off (D2)', () => { + it('L1 master switch beats an L2 per-feature off', () => { const enabled = make({ [MASTER_ENV]: '1', KIMI_CODE_EXPERIMENTAL_A: '0' }); expect(enabled('a-on-default')).toBe(true); }); @@ -73,6 +73,15 @@ describe('FlagResolver', () => { expect(enabled('b-off-default')).toBe(false); }); + it('returns a full snapshot and enabled ids in registry order', () => { + const resolver = new FlagResolver({ KIMI_CODE_EXPERIMENTAL_B: '1' }, DEFS); + expect(resolver.snapshot()).toEqual({ + 'a-on-default': true, + 'b-off-default': true, + }); + expect(resolver.enabledIds()).toEqual(['a-on-default', 'b-off-default']); + }); + it('reads the env name declared in the registry (the declared name works, others do not)', () => { expect(make({ KIMI_CODE_EXPERIMENTAL_B: '1' })('b-off-default')).toBe(true); // The name mechanically derived from the id must not take effect (env is explicitly ..._B). diff --git a/packages/agent-core/test/harness/runtime.test.ts b/packages/agent-core/test/harness/runtime.test.ts index a79600c3..068540bd 100644 --- a/packages/agent-core/test/harness/runtime.test.ts +++ b/packages/agent-core/test/harness/runtime.test.ts @@ -1,18 +1,32 @@ -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'pathe'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { + FLAG_DEFINITIONS, + MASTER_ENV, createRPC, KimiCore, type ApprovalResponse, type CoreAPI, type SDKAPI, } from '../../src'; +import { + __resetRootLoggerForTest, + getRootLogger, + resolveGlobalLogPath, +} from '../../src/logging/logger'; +import { resolveLoggingConfig } from '../../src/logging/resolve-config'; import type { OAuthTokenProviderResolver } from '../../src/session/provider-manager'; +function requiredFlagEnv(id: string): string { + const def = FLAG_DEFINITIONS.find((item) => item.id === id); + if (def === undefined) throw new Error(`Missing flag definition: ${id}`); + return def.env; +} + describe('KimiCore runtime config', () => { let tmp: string; @@ -20,9 +34,34 @@ describe('KimiCore runtime config', () => { if (tmp !== undefined) { await rm(tmp, { recursive: true, force: true }); } + await __resetRootLoggerForTest(); + vi.unstubAllEnvs(); vi.unstubAllGlobals(); }); + it('logs all enabled experimental flags once on core startup', async () => { + tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); + const homeDir = join(tmp, 'home'); + await mkdir(homeDir, { recursive: true }); + await getRootLogger().configure(resolveLoggingConfig({ homeDir })); + + vi.stubEnv(MASTER_ENV, '0'); + for (const def of FLAG_DEFINITIONS) { + vi.stubEnv(def.env, '0'); + } + vi.stubEnv(requiredFlagEnv('goal-command'), '1'); + vi.stubEnv(requiredFlagEnv('background-ask'), '1'); + + void new KimiCore(async () => ({}) as never, { homeDir }); + await getRootLogger().flushGlobal(); + + const text = await readFile(resolveGlobalLogPath(homeDir), 'utf-8'); + expect(text).toContain('experimental flags enabled'); + expect(text).toContain('goal-command'); + expect(text).toContain('background-ask'); + expect(text.match(/experimental flags enabled/g)).toHaveLength(1); + }); + it('uses the shared OAuth resolver for Moonshot service tokens', async () => { tmp = await mkdtemp(join(tmpdir(), 'kimi-core-runtime-')); const homeDir = join(tmp, 'home');