Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/default-v2-engine-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bump the changeset for the removed command

When this ships, anyone still invoking the previously exposed kimi acp-v2 command will get an unknown-command failure, but the changeset only bumps @moonshot-ai/kimi-code as minor while line 5 explicitly says the command is being dropped. The repository release rule treats renamed or removed commands/arguments as major-change criteria, so either keep a deprecated acp-v2 alias or make this a confirmed major changeset before release.

AGENTS.md reference: AGENTS.md:L83-L84

Useful? React with 👍 / 👎.

---

Run the CLI surfaces (interactive TUI, `kimi -p`, `kimi doctor`, `kimi acp`, `kimi export`, `kimi provider`) on the agent-core-v2 engine by default, and drop the experimental `kimi acp-v2` command now that `kimi acp` uses the new engine directly. Set `KIMI_CODE_LEGACY_FLAG=1` to fall back to the legacy engine.
5 changes: 0 additions & 5 deletions apps/kimi-code/src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { CLI_COMMAND_NAME } from '#/constant/app';
import { registerMigrateCommand } from '#/migration/index';
import { Command, InvalidArgumentError, Option } from 'commander';

import { isAcpV2Enabled } from './experimental-v2';
import type { CLIOptions } from './options';
import { registerAcpCommand } from './sub/acp';
import { registerAcpV2Command } from './sub/acp-v2';
import { registerDoctorCommand } from './sub/doctor';
import { registerExportCommand } from './sub/export';
import { registerLoginCommand } from './sub/login';
Expand Down Expand Up @@ -119,9 +117,6 @@ export function createProgram(
registerProviderCommand(program);
registerAcpCommand(program);
registerWebCommand(program);
if (isAcpV2Enabled()) {
registerAcpV2Command(program);
}
registerLoginCommand(program);
registerDoctorCommand(program);
registerVisCommand(program);
Expand Down
27 changes: 11 additions & 16 deletions apps/kimi-code/src/cli/experimental-v2.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
/**
* Experimental agent-core-v2 engine gate for the CLI surfaces.
* Agent engine routing gates for the CLI surfaces.
*
* When the master switch `KIMI_CODE_EXPERIMENTAL_FLAG` is truthy, `kimi -p`
* (print mode) routes to the native agent-core-v2 runner (see
* `run-prompt.ts`), the interactive TUI builds its harness through the
* SDK's v2-backed client (see `run-shell.ts`), and `kimi doctor` validates
* config.toml against the v2 section registry (see `sub/doctor.ts` /
* `v2/validate-config.ts`), all instead of the default v1 engine. The
* master switch also enables every experimental feature flag in the engine. Read directly from the env (matching
* `cli/update/rollout.ts`) because the CLI must not depend on the core flag
* registry. Unset / any non-truthy value keeps the v1 path.
* `kimi -p`, the interactive TUI, and `kimi doctor` use the native
* agent-core-v2 path by default. A truthy `KIMI_CODE_LEGACY_FLAG` selects the
* legacy agent-core-backed path instead. `KIMI_CODE_EXPERIMENTAL_FLAG` remains
* the master switch for experimental features within either engine; it does
* not select the engine.
*
* Note: `kimi web` always boots kap-server (the agent-core-v2 engine
* server) — it does not consult this switch.
*/

export const KIMI_V2_ENV = 'KIMI_CODE_EXPERIMENTAL_FLAG';
export const KIMI_ACP_V2_ENV = 'KIMI_CODE_EXPERIMENTAL_ACP_V2';
export const KIMI_LEGACY_ENV = 'KIMI_CODE_LEGACY_FLAG';

const TRUTHY_VALUES = new Set(['1', 'true', 'yes', 'on']);

Expand All @@ -27,14 +22,14 @@ function isTruthyEnv(
return TRUTHY_VALUES.has((env[key] ?? '').trim().toLowerCase());
}

export function isKimiV2Enabled(
export function isLegacyEnabled(
env: Readonly<Record<string, string | undefined>> = process.env,
): boolean {
return isTruthyEnv(KIMI_V2_ENV, env);
return isTruthyEnv(KIMI_LEGACY_ENV, env);
}

export function isAcpV2Enabled(
export function isKimiV2Enabled(
env: Readonly<Record<string, string | undefined>> = process.env,
): boolean {
return isTruthyEnv(KIMI_ACP_V2_ENV, env) || isKimiV2Enabled(env);
return !isLegacyEnabled(env);
}
10 changes: 5 additions & 5 deletions apps/kimi-code/src/cli/prompt-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*
* `run-prompt.ts` only needs a small subset of the SDK `KimiHarness` / `Session`
* API. Coding the print-mode driver against these narrow interfaces — instead of
* the concrete SDK classes — lets the same driver run on either the v1 engine
* (`createKimiHarness`, the default) or the experimental agent-core-v2 engine
* (`createPromptHarnessV2`, gated by `KIMI_CODE_EXPERIMENTAL_FLAG`). Both the
* v1 `KimiHarness` / `Session` and the v2 harness structurally satisfy these
* interfaces, so no adapter wrappers are needed on the v1 path.
* the concrete SDK classes — lets the same driver run on either the legacy
* engine (`createKimiHarness`) or the default agent-core-v2 engine
* (`createPromptHarnessV2`, selected unless `KIMI_CODE_LEGACY_FLAG` is truthy).
* Both the legacy `KimiHarness` / `Session` and the v2 harness structurally
* satisfy these interfaces, so no adapter wrappers are needed on the legacy path.
*/

import type {
Expand Down
7 changes: 3 additions & 4 deletions apps/kimi-code/src/cli/run-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,9 @@ export async function runPrompt(
io: PromptRunIO = {},
): Promise<void> {
if (isKimiV2Enabled()) {
// The experimental agent-core-v2 engine runs on its own native DI service
// runtime (see v2/run-v2-print.ts); it does not share the v1 PromptHarness
// path below. Loaded lazily so the v2 module graph stays off the default
// (v1) path.
// The agent-core-v2 engine runs on its own native DI service runtime (see
// v2/run-v2-print.ts); it does not share the v1 PromptHarness path below.
// Loaded lazily so the v2 module graph stays off the legacy path.
const { runV2Print } = await import('./v2/run-v2-print');
await runV2Print(opts, version, io);
return;
Expand Down
6 changes: 3 additions & 3 deletions apps/kimi-code/src/cli/run-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export async function runShell(
},
sessionStartedProperties: { yolo: opts.yolo, auto: opts.auto, plan: opts.plan, afk: false },
};
// Experimental agent-core-v2 route (same master switch as `kimi -p`): the
// harness is the SDK's v2-backed client, so the whole TUI runs on the
// agent-core-v2 engine.
// The agent-core-v2 route is the default (same engine gate as `kimi -p`):
// the harness is the SDK's v2-backed client, so the whole TUI runs on the
// agent-core-v2 engine unless the legacy flag is set.
const engineV2 = isKimiV2Enabled();
const harness = engineV2
? createKimiHarnessV2(harnessOptions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
/**
* `kimi acp-v2` sub-command.
* Native `kimi acp` implementation.
*
* Starts the Agent Client Protocol (ACP) server backed directly by the
* DI × Scope agent engine (`agent-core-v2`) over stdio, so ACP-compatible
* clients can drive a kimi-code session on the new engine. This is the v2
* counterpart to `kimi acp` (which runs the legacy `@moonshot-ai/acp-adapter`
* over the SDK harness).
* clients can drive a kimi-code session on the default engine.
*
* Wire-up mirrors `kimi acp` for the parts that are host-independent:
* - `--login` pivots into the shared device-code login flow (the entry point
Expand All @@ -17,9 +15,8 @@
* `_meta['terminal-auth'].command` fallback.
*
* `@moonshot-ai/acp-server` (and its `agent-core-v2` engine) is loaded via a
* lazy dynamic import so the default CLI / `kimi acp` module graph stays free
* of the experimental v2 engine — mirroring the `kimi server run` v2 routing
* in `#/cli/sub/server/run.ts`.
* lazy dynamic import so parsing the CLI does not initialize the ACP engine —
* mirroring the `kimi server run` v2 routing in `#/cli/sub/server/run.ts`.
*/

import type { Command } from 'commander';
Expand All @@ -30,12 +27,10 @@ import { getDataDir } from '#/utils/paths';

import { runLoginFlow } from './login-flow';

export function registerAcpV2Command(parent: Command): void {
export function registerNativeAcpCommand(parent: Command): void {
parent
.command('acp-v2')
.description(
'Run kimi-code as an Agent Client Protocol (ACP) server over stdio (experimental agent-core-v2 engine).',
)
.command('acp')
.description('Run kimi-code as an Agent Client Protocol (ACP) server over stdio.')
.option(
'--login',
'Run the device-code login flow then exit (entry point for ACP terminal-auth).',
Expand Down Expand Up @@ -70,7 +65,7 @@ export function registerAcpV2Command(parent: Command): void {
});
process.exit(0);
} catch (error) {
process.stderr.write(`acp-v2 server: fatal error: ${String(error)}\n`);
process.stderr.write(`acp server: fatal error: ${String(error)}\n`);
process.exit(1);
}
});
Expand Down
15 changes: 11 additions & 4 deletions apps/kimi-code/src/cli/sub/acp.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/**
* `kimi acp` sub-command.
* `kimi acp` sub-command routing and legacy implementation.
*
* Starts the Agent Client Protocol (ACP) server over stdio so that
* ACP-compatible clients (editors, IDEs, custom front-ends) can drive
* a kimi-code session.
* By default the command delegates to the agent-core-v2 ACP server. A truthy
* `KIMI_CODE_LEGACY_FLAG` uses the SDK harness and `@moonshot-ai/acp-adapter`
* implementation below instead.
*
* Wire-up:
* - A {@link KimiHarness} is constructed with the kimi-code host identity
Expand Down Expand Up @@ -33,9 +33,16 @@ import { KIMI_CODE_HOME_ENV } from '#/constant/app';
import { createKimiCodeHostIdentity, getVersion } from '#/cli/version';
import { buildSkillSlashCommands } from '#/tui/commands/skills';

import { isLegacyEnabled } from '../experimental-v2';
import { registerNativeAcpCommand } from './acp-native';
import { runLoginFlow } from './login-flow';

export function registerAcpCommand(parent: Command): void {
if (!isLegacyEnabled()) {
registerNativeAcpCommand(parent);
return;
}

parent
.command('acp')
.description('Run kimi-code as an Agent Client Protocol (ACP) server over stdio.')
Expand Down
6 changes: 3 additions & 3 deletions apps/kimi-code/src/cli/sub/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ function resolveDeps(deps: Partial<DoctorDeps> | DoctorDeps | undefined): Resolv
deps?.validateConfigToml ??
(async (text, filePath) => {
if (isKimiV2Enabled()) {
// Experimental v2 route (same master switch as `kimi -p`): validate
// with the agent-core-v2 section registry instead of the v1 schema.
// Loaded lazily so the v2 module graph stays off the default path.
// Default v2 route (same engine gate as `kimi -p`): validate with
// the agent-core-v2 section registry instead of the legacy schema.
// Loaded lazily so the v2 module graph stays off the legacy path.
const { validateConfigTomlV2 } = await import('../v2/validate-config');
return validateConfigTomlV2(text, filePath);
}
Expand Down
30 changes: 24 additions & 6 deletions apps/kimi-code/src/cli/sub/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@moonshot-ai/kimi-telemetry';
import {
createKimiHarness,
createKimiHarnessV2,
type ExportSessionInput,
type ExportSessionResult,
type KimiHarness,
Expand All @@ -30,6 +31,8 @@ import { detectInstallSource } from '#/cli/update/source';
import { createKimiCodeHostIdentity } from '#/cli/version';
import { detectShellEnvironment } from '#/utils/process/shell-env';

import { isKimiV2Enabled } from '../experimental-v2';

interface WritableLike {
write(chunk: string): boolean;
}
Expand Down Expand Up @@ -120,15 +123,22 @@ export function registerExportCommand(parent: Command, deps?: Partial<ExportDeps
sessionId: string | undefined,
options: { output?: string; yes?: boolean; includeGlobalLog?: boolean },
) => {
await handleExport(createDefaultExportDeps(deps), sessionId, options.output, {
yes: options.yes === true,
includeGlobalLog: options.includeGlobalLog !== false,
});
const resolved = createDefaultExportDeps(deps);
try {
await handleExport(resolved, sessionId, options.output, {
yes: options.yes === true,
includeGlobalLog: options.includeGlobalLog !== false,
});
} finally {
await resolved.close();
}
},
);
}

function createDefaultExportDeps(overrides: Partial<ExportDeps> = {}): ExportDeps {
function createDefaultExportDeps(overrides: Partial<ExportDeps> = {}): ExportDeps & {
readonly close: () => Promise<void>;
} {
let harness: KimiHarness | undefined;
let telemetryBootstrap: ReturnType<typeof createCliTelemetryBootstrap> | undefined;
let telemetryInitialized = false;
Expand All @@ -145,7 +155,9 @@ function createDefaultExportDeps(overrides: Partial<ExportDeps> = {}): ExportDep
};
const getHarness = (): KimiHarness => {
const currentTelemetryBootstrap = getTelemetryBootstrap();
harness ??= createKimiHarness({
// Same engine gate as `kimi -p` / the TUI: the SDK's v2-backed harness by
// default, the legacy agent-core harness when KIMI_CODE_LEGACY_FLAG is set.
harness ??= (isKimiV2Enabled() ? createKimiHarnessV2 : createKimiHarness)({
homeDir: currentTelemetryBootstrap.homeDir,
identity,
telemetry: telemetryClient,
Expand Down Expand Up @@ -197,6 +209,12 @@ function createDefaultExportDeps(overrides: Partial<ExportDeps> = {}): ExportDep
stdout: overrides.stdout ?? process.stdout,
stderr: overrides.stderr ?? process.stderr,
exit: overrides.exit ?? ((code: number) => process.exit(code)),
// The v2 harness boots an engine whose watchers hold the event loop open;
// close it so a one-shot command can exit. No-op when the run never needed
// the harness.
close: async () => {
await harness?.close();
},
};
}

Expand Down
24 changes: 21 additions & 3 deletions apps/kimi-code/src/cli/sub/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
catalogProviderModels,
CatalogFetchError,
createKimiHarness,
createKimiHarnessV2,
DEFAULT_CATALOG_URL,
resolveCatalogImport,
type Catalog,
Expand All @@ -36,6 +37,8 @@ import type { Command } from 'commander';
import { createKimiCodeHostIdentity, createKimiCodeUserAgent } from '#/cli/version';
import { fetchCatalogOrBuiltIn } from '#/utils/catalog-fetch';

import { isKimiV2Enabled } from '../experimental-v2';

interface WritableLike {
write(chunk: string): boolean;
}
Expand Down Expand Up @@ -457,12 +460,17 @@ export function registerProviderCommand(parent: Command, deps?: Partial<Provider
// anything that escapes (e.g. a config write rejected because config.toml
// is invalid) must end as a one-line error + exit 1, not an unhandled
// rejection dumping a stack trace.
const runAction = async (resolved: ProviderDeps, run: () => Promise<void>): Promise<void> => {
const runAction = async (
resolved: ResolvedProviderDeps,
run: () => Promise<void>,
): Promise<void> => {
try {
await run();
} catch (error) {
resolved.stderr.write(`${errorMessage(error)}\n`);
resolved.exit(1);
} finally {
await resolved.close();
}
};

Expand Down Expand Up @@ -546,20 +554,30 @@ export function registerProviderCommand(parent: Command, deps?: Partial<Provider
);
}

function resolveDeps(overrides: Partial<ProviderDeps> = {}): ProviderDeps {
type ResolvedProviderDeps = ProviderDeps & { readonly close: () => Promise<void> };

function resolveDeps(overrides: Partial<ProviderDeps> = {}): ResolvedProviderDeps {
let harness: KimiHarness | undefined;
const identity = createKimiCodeHostIdentity();
return {
getHarness:
overrides.getHarness ??
(() => {
harness ??= createKimiHarness({ identity });
// Same engine gate as the TUI's `/provider` flow: the SDK's v2-backed
// harness by default, the legacy agent-core harness when
// KIMI_CODE_LEGACY_FLAG is set.
harness ??= (isKimiV2Enabled() ? createKimiHarnessV2 : createKimiHarness)({ identity });
return harness;
}),
stdout: overrides.stdout ?? process.stdout,
stderr: overrides.stderr ?? process.stderr,
env: overrides.env ?? process.env,
exit: overrides.exit ?? ((code: number) => process.exit(code)),
// The v2 harness boots an engine whose watchers hold the event loop open;
// close it so a one-shot command can exit. No-op for injected harnesses.
close: async () => {
await harness?.close();
},
};
}

Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/cli/v2/run-v2-print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* - applies the print-mode background policy (config-driven, v1-aligned:
* `exit` / `drain` / `steer`) before exiting.
*
* Selected by `runPrompt` when `KIMI_CODE_EXPERIMENTAL_FLAG` is set.
* Selected by `runPrompt` unless `KIMI_CODE_LEGACY_FLAG` is truthy.
*/

import { readFile } from 'node:fs/promises';
Expand Down
10 changes: 5 additions & 5 deletions apps/kimi-code/src/cli/v2/validate-config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* Experimental v2 config.toml validation for `kimi doctor`.
* V2 config.toml validation for `kimi doctor`.
*
* Loaded lazily (dynamic import) by the doctor command only when the
* agent-core-v2 master switch (`KIMI_CODE_EXPERIMENTAL_FLAG`) is on, so the
* v2 module graph stays off the default (v1) doctor path. Validation uses the
* engine's own section registry instead of v1's whole-document strict schema:
* Loaded lazily (dynamic import) by the doctor command on the default
* agent-core-v2 path, so the v2 module graph stays off the legacy doctor path.
* Validation uses the engine's own section registry instead of the legacy
* whole-document strict schema:
* importing the package root runs every built-in section's side-effect
* registration ("import = register"), and `ConfigRegistry` is then
* constructed directly — no DI container, no `ConfigService`, no file IO.
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/tui/commands/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export interface SlashCommandHost {
state: TUIState;
session: Session | undefined;
readonly harness: KimiHarness;
/** agent-core-v2 engine (KIMI_CODE_EXPERIMENTAL_FLAG); enables lazy session creation. */
/** agent-core-v2 engine; enables lazy session creation. */
readonly engineV2: boolean;
cancelInFlight: (() => void) | undefined;
deferUserMessages: boolean;
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/tui/kimi-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export interface KimiTUIStartupInput {
readonly migrationPlan?: MigrationPlan | null;
/** When true, run only the migration screen, then exit (the `kimi migrate` command). */
readonly migrateOnly?: boolean;
/** agent-core-v2 engine (KIMI_CODE_EXPERIMENTAL_FLAG); enables the startup workspace-trust prompt. */
/** agent-core-v2 engine; enables the startup workspace-trust prompt. */
readonly engineV2?: boolean;
}

Expand Down
Loading
Loading