Skip to content
Open
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/database-config-section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add a `[database]` config section with `base`/`search` kill switches (both default `true`) and rename their environment variables to `KIMI_CODE_PERSISTENCE_MINIDB_READMODEL` and `KIMI_CODE_SEARCH_WORKER`.
5 changes: 5 additions & 0 deletions .changeset/remote-control-always-available.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Kimi Remote Control is no longer experimental: `kimi rc`, `kimi web --remote-control`, and `/remote-control` work out of the box, without setting `KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL` or `KIMI_CODE_EXPERIMENTAL_FLAG`.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo

- Gate a not-yet-public feature behind an experimental flag. Flags are env-driven and default off: `KIMI_CODE_EXPERIMENTAL_<NAME>` toggles one, `KIMI_CODE_EXPERIMENTAL_FLAG` enables all. Precedence is per-flag env > `[experimental]` config > master env > the flag's `default`. Release by flipping the entry's `default` to `true`.
- `packages/agent-core` (v1): add the flag to the central registry at `packages/agent-core/src/flags/registry.ts`, then check it with `flags.enabled('my-feature')`.
- `packages/agent-core-v2` and kap-server modules: there is no central catalog — declare the flag in the owning domain via `registerFlagDefinition` at import time, then check it with `IFlagService.enabled(id)`. Current search-index-separation flags: `persistence_minidb_readmodel` (session read model, default on) and `search_worker` (global search worker host, default on).
- `packages/agent-core-v2` and kap-server modules: there is no central catalog — declare the flag in the owning domain via `registerFlagDefinition` at import time, then check it with `IFlagService.enabled(id)`.

## Where to Update Instructions

Expand Down
5 changes: 2 additions & 3 deletions apps/kimi-code/src/cli/sub/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import type { Command } from 'commander';

import { registerDeprecatedServerCommand } from './deprecated-server';
import { isRemoteControlEnabled } from './remote-control';
import { registerRotateTokenCommand } from './rotate-token';
import { buildWebCommand } from './run';

Expand All @@ -27,9 +26,9 @@ export function registerWebCommand(program: Command): void {
registerDeprecatedServerCommand(program);
buildWebCommand(
program
.command('rc', { hidden: !isRemoteControlEnabled() })
.command('rc')
.alias('remote')
.description('Run the local Kimi server and open the web UI through Remote Control (experimental).'),
.description('Run the local Kimi server and open the web UI through Remote Control.'),
{ forceRemoteControl: true },
);
}
16 changes: 2 additions & 14 deletions apps/kimi-code/src/cli/sub/web/remote-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,6 @@ export const REMOTE_CONTROL_RELAY_ORIGIN = 'https://code-rc.kimi.com';

export const REMOTE_CONTROL_RELAY_URL_ENV = 'KIMI_CODE_REMOTE_CONTROL_RELAY_URL';

export const REMOTE_CONTROL_FLAG_ENV = 'KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL';

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

export function isRemoteControlEnabled(
env: Readonly<Record<string, string | undefined>> = process.env,
): boolean {
const truthy = (key: string): boolean =>
TRUTHY_ENV_VALUES.has((env[key] ?? '').trim().toLowerCase());
return truthy('KIMI_CODE_EXPERIMENTAL_FLAG') || truthy(REMOTE_CONTROL_FLAG_ENV);
}

export function resolveRemoteControlRelayOrigin(
env: Readonly<Record<string, string | undefined>> = process.env,
): string {
Expand Down Expand Up @@ -144,7 +132,7 @@ export function formatRemoteControlOutput(options: RemoteControlOutputOptions):
const feedback = toTerminalHyperlink('feedback', 'https://kimi.com/code/feedback');
return [
'',
` ${title('Kimi Remote Control ready')} ${muted(`${getVersion()} (experimental)`)}`,
` ${title('Kimi Remote Control ready')} ${muted(getVersion())}`,
` ${muted('Use Kimi Code on this machine from your phone or another computer.')}`,
'',
` ${label('1.')} Scan the QR code, or open ${link(options.url)}`,
Expand All @@ -159,7 +147,7 @@ export function formatRemoteControlOutput(options: RemoteControlOutputOptions):
` ${label('QR code PNG: ')}${options.pngPath} ${muted('(open this if the QR above does not scan)')}`,
` ${label('Local UI: ')}${muted(options.localOrigin)} ${muted('(LAN: --host)')}`,
'',
` ${muted('Experimental —')} ${docs} ${muted('·')} ${feedback}`,
` ${docs} ${muted('·')} ${feedback}`,
` ${label('Logs: ')}${muted('off (--log-level info)')} ${muted('·')} ${label('Stop: ')}${muted('Ctrl+C')}`,
'',
].join('\n');
Expand Down
13 changes: 2 additions & 11 deletions apps/kimi-code/src/cli/sub/web/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ import { type NetworkAddress } from './networks';
import {
formatRemoteControlOutput,
formatRemoteControlStatus,
isRemoteControlEnabled,
REMOTE_CONTROL_FLAG_ENV,
startRemoteControl,
type RemoteControlHandle,
type RemoteControlOptions,
Expand Down Expand Up @@ -170,10 +168,8 @@ export function buildWebCommand(
withServerOptions.addOption(
new Option(
'--rc, --remote-control',
'Expose the web UI through Kimi Remote Control (experimental).',
)
.default(false)
.hideHelp(!isRemoteControlEnabled()),
'Expose the web UI through Kimi Remote Control.',
).default(false),
);
}
return withServerOptions
Expand All @@ -195,11 +191,6 @@ export async function handleWebCommand(
deps: WebCommandDeps = DEFAULT_WEB_COMMAND_DEPS,
): Promise<void> {
const parsed = parseServerOptions(opts);
if (opts.remoteControl === true && !isRemoteControlEnabled()) {
throw new Error(
`--remote-control is experimental: set ${REMOTE_CONTROL_FLAG_ENV}=1 (or KIMI_CODE_EXPERIMENTAL_FLAG=1) to enable it.`,
);
}
if (opts.remoteControl === true && parsed.dangerousBypassAuth) {
throw new Error('--remote-control cannot be combined with --dangerous-bypass-auth.');
}
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function bootstrap(): void {
);
// Same pattern for the global-search worker: extracted from the SEA blob so
// the search index runs off the main thread; a failure leaves the search
// surface degraded (the `search_worker` flag restores the inline host).
// surface degraded ([database] search = false restores the inline host).
const searchWorkerInstall = installKapSearchWorker();
startupTrace(
searchWorkerInstall.status === 'installed'
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-code/src/native/search-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ function errorCode(error: unknown): string {
/**
* Install the SEA-bundled global-search worker without making optional
* extraction fatal. Without it the search service resolves no worker entry
* inside the single-file binary and reports the index as degraded; the
* `search_worker` experimental flag restores the in-process host.
* inside the single-file binary and reports the index as degraded;
* `[database] search = false` restores the in-process host.
*/
export function installKapSearchWorker(
options: NativeAssetOptions = {},
Expand Down
3 changes: 1 addition & 2 deletions apps/kimi-code/src/tui/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,9 @@ export const BUILTIN_SLASH_COMMANDS = [
{
name: 'remote-control',
aliases: ['rc'],
description: 'Open the current session through Kimi Remote Control (experimental)',
description: 'Open the current session through Kimi Remote Control',
priority: 40,
availability: 'always',
experimentalFlag: 'remote-control',
},
{
name: 'exit',
Expand Down
6 changes: 2 additions & 4 deletions apps/kimi-code/test/cli/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Run: pnpm -C apps/kimi-code exec vitest run test/cli/options.test.ts
*/

import { describe, expect, it, onTestFinished, vi } from 'vitest';
import { describe, expect, it, vi } from 'vitest';

import { createProgram } from '#/cli/commands';
import type { CLIOptions } from '#/cli/options';
Expand Down Expand Up @@ -572,9 +572,6 @@ describe('CLI options parsing', () => {
});

it('registers the visible sub-commands', () => {
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '0');
onTestFinished(() => { vi.unstubAllEnvs(); });
const program = createProgram(
'0.0.0',
() => {},
Expand All @@ -591,6 +588,7 @@ describe('CLI options parsing', () => {
'acp',
'web',
'server',
'rc',
'login',
'doctor',
'vis',
Expand Down
16 changes: 0 additions & 16 deletions apps/kimi-code/test/cli/web/remote-control.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
filterForwardRequestHeaders,
formatRemoteControlOutput,
formatRemoteControlStatus,
isRemoteControlEnabled,
parseRawHttpRequest,
resolveRemoteControlRelayOrigin,
rewriteRemoteControlResponse,
Expand All @@ -44,21 +43,6 @@ afterEach(async () => {
while (cleanups.length > 0) await cleanups.pop()!();
});

describe('Remote Control experimental flag', () => {
it('is off unless the per-feature env or the master switch is truthy', () => {
expect(isRemoteControlEnabled({})).toBe(false);
expect(isRemoteControlEnabled({ KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL: '0' })).toBe(false);
expect(isRemoteControlEnabled({ KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL: '1' })).toBe(true);
expect(isRemoteControlEnabled({ KIMI_CODE_EXPERIMENTAL_FLAG: 'true' })).toBe(true);
expect(
isRemoteControlEnabled({
KIMI_CODE_EXPERIMENTAL_FLAG: '0',
KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL: 'yes',
}),
).toBe(true);
});
});

describe('Remote Control URLs', () => {
it('builds the public device entry without a local token', () => {
const url = buildRemoteControlUrl('device/one');
Expand Down
47 changes: 9 additions & 38 deletions apps/kimi-code/test/cli/web/web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ describe('`kimi web` opens the browser', () => {
});

it('rejects Remote Control on a non-loopback host', async () => {
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '1');
const { handleWebCommand } = await import('#/cli/sub/web/run');
const { runner } = makeRunner();
const { stdout, stderr } = makeIo();
Expand All @@ -425,32 +424,11 @@ describe('`kimi web` opens the browser', () => {
).rejects.toThrow('--remote-control requires a loopback host.');
});

it('rejects --remote-control while the experimental flag is off', async () => {
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '0');
const { handleWebCommand } = await import('#/cli/sub/web/run');
const { runner } = makeRunner();
const { stdout, stderr } = makeIo();

await expect(
handleWebCommand(
{ remoteControl: true, open: false },
{ startServerForeground: runner, openUrl: vi.fn(), stdout, stderr },
),
).rejects.toThrow('--remote-control is experimental:');
});

it('hides --remote-control from help unless the experimental flag is on', () => {
const remoteControlOption = () =>
makeProgram()
.commands.find((command) => command.name() === 'web')!
.options.find((option) => option.long === '--remote-control');

vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '0');
expect(remoteControlOption()?.hidden).toBe(true);
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '1');
expect(remoteControlOption()?.hidden).toBe(false);
it('shows --remote-control in help', () => {
const remoteControlOption = makeProgram()
.commands.find((command) => command.name() === 'web')!
.options.find((option) => option.long === '--remote-control');
expect(remoteControlOption?.hidden).toBeFalsy();
});
});

Expand All @@ -471,17 +449,11 @@ describe('kimi rc', () => {
expect(longs).not.toContain('--remote-control');
});

it('hides `rc` from help unless the experimental flag is on', () => {
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '0');
expect(makeProgram().helpInformation()).not.toContain('rc|remote');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '1');
it('shows `rc` in help', () => {
expect(makeProgram().helpInformation()).toContain('rc|remote');
});

it('forces Remote Control for both `rc` and `remote`', async () => {
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_FLAG', '0');
vi.stubEnv('KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL', '0');
for (const name of ['rc', 'remote']) {
const program = makeProgram();
let stderr = '';
Expand All @@ -493,14 +465,13 @@ describe('kimi rc', () => {
.spyOn(process, 'exit')
.mockImplementation(() => undefined as never);
try {
await program.parseAsync(['node', 'kimi', name]);
await program.parseAsync(['node', 'kimi', name, '--host', '0.0.0.0']);
} finally {
errSpy.mockRestore();
exitSpy.mockRestore();
}
// The flag-off experimental error proves remoteControl was forced before
// the runner could start.
expect(stderr).toContain('--remote-control is experimental:');
// The loopback check only runs when remoteControl was forced on.
expect(stderr).toContain('--remote-control requires a loopback host.');
}
});
});
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-code/test/tui/commands/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,10 @@ describe('built-in slash command registry', () => {
expect(resolveSlashCommandAvailability(command!, 'Ship feature X')).toBe('always');
});

it('gates remote-control behind the remote-control experiment, always available', () => {
it('registers remote-control as always available', () => {
const command = findBuiltInSlashCommand('remote-control');
expect(command).toBeDefined();
expect((command as KimiSlashCommand).experimentalFlag).toBe('remote-control');
expect((command as KimiSlashCommand).experimentalFlag).toBeUndefined();
expect(resolveSlashCommandAvailability(command!, '')).toBe('always');
});

Expand Down
4 changes: 1 addition & 3 deletions apps/kimi-code/test/tui/commands/resolve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ describe('resolveSlashCommandInput', () => {
});
});

it('gates /remote-control behind the remote-control experimental flag', () => {
expect(resolve('/rc')).toEqual({ kind: 'message', input: '/rc' });
setExperimentalFeatures([{ id: 'remote-control', enabled: true }]);
it('resolves /remote-control and /rc as built-ins', () => {
expect(resolve('/rc')).toMatchObject({ kind: 'builtin', name: 'remote-control' });
expect(resolve('/remote-control')).toMatchObject({ kind: 'builtin', name: 'remote-control' });
});
Expand Down
11 changes: 11 additions & 0 deletions docs/en/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,17 @@ Like the `tools` / `disallowedTools` fields of an agent file, this section shape

`max_edge_px` can be overridden by the `KIMI_IMAGE_MAX_EDGE_PX` environment variable and `read_byte_budget` by `KIMI_IMAGE_READ_BYTE_BUDGET`; both take higher priority than `config.toml`.

## `database`

`database` controls the embedded storage engines behind session indexing and global search. Both keys default to `true` and act as kill switches that fall back to the legacy behavior when set to `false`.

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `base` | `boolean` | `true` | Use the minidb-backed read model for session indexing; `false` falls back to reading session metadata directly |
| `search` | `boolean` | `true` | Run the global search index in a dedicated worker thread; `false` runs it in the server process |

`base` can be overridden by the `KIMI_CODE_PERSISTENCE_MINIDB_READMODEL` environment variable and `search` by `KIMI_CODE_SEARCH_WORKER`; both take higher priority than `config.toml`.

<!--
## `experimental`

Expand Down
2 changes: 2 additions & 0 deletions docs/en/configuration/env-vars.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ Switches that control the behavior of subsystems such as telemetry, background t
| `KIMI_CODE_TUI_FULL_SCREEN` | Experimental fullscreen UI: scrollable transcript, mouse selection, clickable links, Ctrl-Shift-F search | `1` enables it; anything else keeps the regular inline UI |
| `KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL` | The [subagent model pool](./config-files.md#subagent-model-pool) is enabled by default in all launch modes; set a falsy value to disable it; `KIMI_CODE_EXPERIMENTAL_FLAG=1` also enables it | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
| `KIMI_CODE_EXPERIMENTAL_SUBAGENT_FORK` | Experimental `fork` parameter on `Agent`/`AgentSwarm`: start the subagent from a snapshot of the caller's history instead of an empty context; `KIMI_CODE_EXPERIMENTAL_FLAG=1` also enables it | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
| `KIMI_CODE_SEARCH_WORKER` | Run the global search index in a dedicated worker thread; higher priority than `[database] search` (default `true`) | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
| `KIMI_CODE_PERSISTENCE_MINIDB_READMODEL` | Use the minidb-backed read model for session indexing; higher priority than `[database] base` (default `true`) | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
| `KIMI_MCP_STARTUP_TIMEOUT_MS` | Global default connection timeout (ms) for MCP servers; overrides the config file, but `mcp.json` `startupTimeoutMs` still wins | Integer from `1` to `2147483647`; invalid values are ignored |
| `KIMI_MCP_TOOL_TIMEOUT_MS` | Global default single tool-call timeout (ms) for MCP servers; overrides the config file, but `mcp.json` `toolTimeoutMs` still wins | Integer from `1` to `2147483647`; invalid values are ignored |
| `KIMI_LOOP_MAX_STEPS_PER_TURN` | Max Agent steps per turn; higher priority than `[loop_control] max_steps_per_turn` (`0` = unlimited) | Non-negative integer; invalid values are ignored |
Expand Down
15 changes: 0 additions & 15 deletions docs/en/guides/remote-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

Start Kimi Code CLI with remote control enabled by running `kimi rc` in a terminal — it generates a link that can remotely control this machine. Scan the QR code with your phone to open the link, or visit it directly on another device. After opening the link, log in with the same Kimi account as in your local Kimi Code CLI to check on task progress, handle approvals, continue conversations, or start new sessions. Tasks always run on your machine — the web page is just a remote window.

> Remote Control is currently an experimental feature and requires an environment variable to enable; the interface and behavior are subject to the final release.

## Getting started

### Prerequisites
Expand All @@ -13,19 +11,6 @@ Before turning on Remote Control, make sure your machine meets the following con
- **Kimi Code CLI installed**: see [Getting started](../guides/getting-started.md)
- **Logged in to your Kimi account with a paid membership**: Remote Control requires a paid membership and is not available to free users
- **Machine stays awake and online**: Remote Control depends on a persistent connection between your machine and the Kimi service; remote sessions are unavailable after shutdown, sleep, or network loss
- **Experimental flag enabled**: set the environment variable with `export` (or use `KIMI_CODE_EXPERIMENTAL_FLAG=1` to enable all experimental features):

```sh
export KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL=1
# or enable all experimental features
export KIMI_CODE_EXPERIMENTAL_FLAG=1
```

The `export` above only applies to the current terminal session. If you use Remote Control regularly, persist it in your shell configuration file (e.g. `~/.zshrc`):

```sh
echo 'export KIMI_CODE_EXPERIMENTAL_REMOTE_CONTROL=1' >> ~/.zshrc
```

### Step 1: Start Remote Control

Expand Down
11 changes: 11 additions & 0 deletions docs/zh/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,17 @@ disabled = ["EnterPlanMode", "ExitPlanMode", "mcp__github__*"]

`max_edge_px` 可被环境变量 `KIMI_IMAGE_MAX_EDGE_PX` 覆盖,`read_byte_budget` 可被 `KIMI_IMAGE_READ_BYTE_BUDGET` 覆盖,优先级均高于配置文件。

## `database`

`database` 控制会话索引和全局搜索背后的嵌入式存储引擎。两个字段默认值都是 `true`,设为 `false` 时回退到旧有行为。

| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `base` | `boolean` | `true` | 会话索引使用基于 minidb 的读模型;`false` 回退为直接读取会话元数据 |
| `search` | `boolean` | `true` | 在独立 worker 线程中运行全局搜索索引;`false` 在服务器进程内运行 |

`base` 可被环境变量 `KIMI_CODE_PERSISTENCE_MINIDB_READMODEL` 覆盖,`search` 可被 `KIMI_CODE_SEARCH_WORKER` 覆盖,优先级均高于配置文件。

<!--
## `experimental`

Expand Down
Loading
Loading