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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
- `apps/vis`, `apps/vis/server`, `apps/vis/web`: visual debugging tools for sessions and replays.
- `apps/kimi-inspect`: web inspector for the kap-server `/api/v1/debug` RPC surface — workspace/session browser, per-session chat, and Service panels (data + trigger buttons) for the Session and Agent scopes. A left icon rail (`src/components/NavRail.tsx`) switches top-level views: the Chat workspace, the global message search (`src/components/SearchView.tsx` — cross-session full-text search over `POST /api/v1/search`, cursor-paged via a manual Load more; an exact-match checkbox maps to the API's `mode: 'literal'` substring search, which ignores sort and orders newest-first; a `live`/`index` badge on the results shows which server route served them (in-memory session transcript vs the persisted index)), the Model Catalog (`src/components/ModelCatalogView.tsx` — every Provider with its Models and the default marker, via `IModelCatalog` / `IModelService` channel proxies), and App Services (`src/components/AppServicesView.tsx` — the app-scope Service reflection, full width, joined by the Workspace Services view (`src/components/WorkspaceServicesView.tsx`) — the workspace-scope counterpart with a left sidebar directory browser (`src/components/WorkspaceDirBrowser.tsx` — server-side fs browsing over the App-scope `IHostFolderBrowser`, marking entries that are registered workspaces with their `IWorkspaceTrust` trust state, and registering a picked folder on demand via `IWorkspaceService.createOrTouch`), its proxies riding the `/workspace/:id` route, which materializes the handler on demand via `IWorkspaceLifecycleService.handlerFor`; the Agent scope stays in the Chat view's right dock (`src/components/RightPanel.tsx`) across two tabs: the `Agent` tab (`Inspector`: agent switcher + a Plan lookup card — `PlanCard` in `src/components/Inspector.tsx` — querying `GET /sessions/{id}/transcript/plan` (one tool_call_id, or every plan of the agent) via `src/transcript/api.ts`'s `fetchTranscriptPlan` — plus the agent Service panels) and the `State` tab (every key an Agent Service registered into the agent-state container, polled live via `IAgentStateService.snapshot()` — the same live diff-tree view as the session State tab, sharing `StateCard` from `src/components/StateCard.tsx`), while the Session scope has its own column right next to the session-list sidebar (`src/components/SessionPane.tsx`) with two tabs: Services (the pending-interactions card — `src/components/InteractionsCard.tsx` — plus the session Service panels) and State (every key a Session Service registered into the session-state container, read on demand via `ISessionStateService.snapshot()`)). Expanding a Model opens the model inspector inside that view: provider/model config layers plus the resolved runtime view with per-value provenance (config / override / builtin / env / synthesized), served on demand by `IModelCatalog.inspect` — the same resolution pass the runtime's `get` serves, traced via `ResolutionTraceCollector` and assembled by `kosong/model/inspection.ts`. Built on its own old-klient-style channel layer (`src/channel/`: the VS Code `ProxyChannel` model — service-bound `IChannel`, HTTP `ProxyChannel` for calls routed to `/api/v1/debug`), typed by `agent-core-v2` Service interfaces; `GET /api/v1/debug/channels` loads the whole wire protocol 1:1 (every scoped Service, no whitelist). There is no Service-event push channel: panels fetch/refresh on demand (`Sidebar` polls react-query on a 15 s interval), and a connection failure shows a blocking "Debug surface unavailable" screen instead of falling back anywhere. Session-level coarse status is the one exception: `src/activity/` holds a second `/api/v1/ws` client (`GlobalEventsWs`) that subscribes to nothing and consumes the server-pushed global facts — `event.session.work_changed` updates a per-session activity map (`SessionActivityHub` + subscribe/version store, seeded on connect/reconnect from `GET /api/v1/sessions`), while `event.session.created` / `session.meta.updated` invalidate the `['sessions']` query; the `Sidebar` session rows render `running` / `approval` / `question` / `failed` badges from it via `useSessionActivities`. The Vite dev server proxies `/api` to a running kap-server (`KIMI_SERVER_URL`, default `http://127.0.0.1:58627`) and exposes `GET /__inspect/servers` (`vite/serverDiscovery.ts`), which scans the local kap-server instance registry (`~/.kimi-code/server/instances` + legacy `lock`) and the home token so the app can zero-config auto-connect and switch servers from the header dropdown at runtime. The per-session chat (`src/components/ChatView.tsx`) renders turn-granularly from the **transcript** surface instead of context memory and carries an in-chat search bar (`src/components/ChatSearchBar.tsx`): it searches the current session via `POST /api/v1/search` with `container: { session_id }` (usually served by the live route, since selecting a session resumes it), and a result click funnels through the app shell's `openSearchHit` — the same agent-switch + `ChatJump` (page-back, scroll, flash) path the global search view uses; full state is read from `GET /api/v1/sessions/{id}/transcript` (initial load = newest page, refreshes re-read from the tail backwards), older history auto-pages with `before_turn` via an IntersectionObserver sentinel at the top of the scroll view, and each timeline item is wrapped in `content-visibility: auto` + `contain-intrinsic-size` so the browser virtualizes off-screen rendering natively (no windowing library); `/api/v1/ws` is an incremental channel (`transcript.ops`, grade `block` — the cheapest grade that still carries whole-state frame upserts, dropping per-token `append` frames; `transcript.reset` is ignored by the store, surfaced only to the audit recorder via the optional `onReset` handler). The channel tracks the op-batch watermark: a dedicated `subscribe_v2` control frame carries the per-agent grades and the `transcript_since` cursor, a seq gap / reconnect / `resync_required` / append gap triggers a point-to-point catch-up (`fetchTranscriptOps` → `GET .../transcript/ops?since_seq=`), and any legacy/incomplete answer falls back to the full REST refresh. Convergence reuses `@moonshot-ai/transcript`'s L2 reducer (`src/transcript/`: REST/WS clients + store; the data model and reducer come from the package, nothing is re-implemented locally). The Transcript audit panel (`src/components/audit/`, the `Audit` tab of the chat view's right dock — `src/components/RightPanel.tsx`, fed the trail by `ChatView`'s `onTrailChange`) replays how the visible store was built: an `AuditTrail` (`src/audit/`) records every step — each REST page (request + replace/prepend), every WS frame (`transcript.ops` live/buffered/flushed/catchup, `transcript.reset`), loss signals, and prompt/cancel actions — with the resulting immutable `AgentState` per entry; the panel offers a draggable timeline plus a Diff tab (structural diff vs the previous entry: added/modified/removed colored, long strings tail-truncated, all fields kept), a full State view, and the raw Event payload.
- `packages/agent-core`: the unified agent engine, including Agent, Session, profile, skills, tools, plan, permission, background, records, the in-process DI service layer (`src/services/`), and other core capabilities.
- `packages/agent-core-v2`: the DI × Scope agent engine (the v2 port behind kap-server). Four `LifecycleScope` tiers — `App` / `Workspace` / `Session` / `Agent` (`_base/di/scope.ts`). The `workspace/` domain owns one Workspace scope per materialized workspace handler: the App-scope `IWorkspaceLifecycleService` keeps the live handler registry (create-or-get + join, handlers never closed), and each handler's `ISessionLifecycleService` owns session create/resume/fork/close as its child scopes — there is no App-level session lifecycle facade, callers compose `ISessionIndex` → `handlerFor` → the handler. Workspace-scope services hold the handler-shared resources loaded once per handler and refreshed by fs watch: skills / AGENTS.md (`workspaceSkillCatalog` / `workspaceInstructions`), the workspace agent-profile loader (`workspaceAgentProfileLoader` — agent profiles follow the Contribution / Registry / Catalog extension point: the domain owns agent-file discovery end to end (parse / roots / SYSTEM.md / explicit files) and its Workspace-scope loaders (`workspace` / `user` / `plugin` / `extra` / `explicit`) register `AgentProfileContribution`s into the App-scope `IAgentProfileRegistry`, tagged with the handler's `workspaceId`; the App-scope `builtinAgentProfileLoader` contributes the code-defined profiles, and each Session-scope `sessionAgentProfileCatalog` projects the registry directly (name-level dedup + builtin-override rule in the projection), seeded with only the workspace key), one shared MCP connection set (`workspaceMcp`, pure connection orchestration over the scope-agnostic `mcpCore` layer; the effective server set is owned by `workspaceMcpConfig` — mcp.json files + plugin contributions, fs-watch refreshed — and MCP persistence, the `[mcp]` config section plus OAuth credentials, lives in `app/mcpConfig`, the same wrapper shape as `kosongConfig` over kosong), fs / fs-watch / process runner / git (`workspaceFs` / `workspaceFsWatch` / `workspaceProcess` / `workspaceGit`), the additional-directory set (`workspaceDirs`, backed by `.kimi-code/local.toml`), the os-level tool veto (`workspaceToolPolicy`), and the trust marker (`workspaceTrust` — persisted under the home, keyed by `encodeWorkDirKey(root)`; while a workspace is untrusted, `workspaceMcpConfig` skips the project-level `.mcp.json` / `.kimi-code/mcp.json` files, and the state flips through kap-server's `GET|POST /workspaces/{id}/trust` + `POST /workspaces/{id}/untrust` routes). Session/Agent scopes consume these through session-domain seed contracts with change events (`session/mcp`, `session/workspaceInfo`, `session/sessionSkillCatalog` data, …). See `packages/agent-core-v2/AGENTS.md` and use the `agent-core-dev` skill (`.agents/skills/agent-core-dev/SKILL.md`) when developing here.
- `packages/agent-core-v2`: the DI × Scope agent engine (the v2 port behind kap-server). Four `LifecycleScope` tiers — `App` / `Workspace` / `Session` / `Agent` (`_base/di/scope.ts`). The `workspace/` domain owns one Workspace scope per materialized workspace handler: the App-scope `IWorkspaceLifecycleService` keeps the live handler registry (create-or-get + join, handlers never closed), and each handler's `ISessionLifecycleService` owns session create/resume/fork/close/delete as its child scopes — there is no App-level session lifecycle facade, callers compose `ISessionIndex` → `handlerFor` → the handler. Workspace-scope services hold the handler-shared resources loaded once per handler and refreshed by fs watch: skills / AGENTS.md (`workspaceSkillCatalog` / `workspaceInstructions`), the workspace agent-profile loader (`workspaceAgentProfileLoader` — agent profiles follow the Contribution / Registry / Catalog extension point: the domain owns agent-file discovery end to end (parse / roots / SYSTEM.md / explicit files) and its Workspace-scope loaders (`workspace` / `user` / `plugin` / `extra` / `explicit`) register `AgentProfileContribution`s into the App-scope `IAgentProfileRegistry`, tagged with the handler's `workspaceId`; the App-scope `builtinAgentProfileLoader` contributes the code-defined profiles, and each Session-scope `sessionAgentProfileCatalog` projects the registry directly (name-level dedup + builtin-override rule in the projection), seeded with only the workspace key), one shared MCP connection set (`workspaceMcp`, pure connection orchestration over the scope-agnostic `mcpCore` layer; the effective server set is owned by `workspaceMcpConfig` — mcp.json files + plugin contributions, fs-watch refreshed — and MCP persistence, the `[mcp]` config section plus OAuth credentials, lives in `app/mcpConfig`, the same wrapper shape as `kosongConfig` over kosong; a session created with `CreateSessionOptions.mcpServers` additionally gets ephemeral per-session servers on a session-owned overlay manager from `workspaceMcp.sessionOverlay` — merged into the session's MCP seed, released on session close, never persisted, not gated by `workspaceTrust`), fs / fs-watch / process runner / git (`workspaceFs` / `workspaceFsWatch` / `workspaceProcess` / `workspaceGit`), the additional-directory set (`workspaceDirs`, backed by `.kimi-code/local.toml`), the os-level tool veto (`workspaceToolPolicy`), and the trust marker (`workspaceTrust` — persisted under the home, keyed by `encodeWorkDirKey(root)`; while a workspace is untrusted, `workspaceMcpConfig` skips the project-level `.mcp.json` / `.kimi-code/mcp.json` files, and the state flips through kap-server's `GET|POST /workspaces/{id}/trust` + `POST /workspaces/{id}/untrust` routes). Session/Agent scopes consume these through session-domain seed contracts with change events (`session/mcp`, `session/workspaceInfo`, `session/sessionSkillCatalog` data, …). See `packages/agent-core-v2/AGENTS.md` and use the `agent-core-dev` skill (`.agents/skills/agent-core-dev/SKILL.md`) when developing here.
- `packages/node-sdk`: the public TypeScript SDK and harness.
- `packages/kosong`: the LLM / provider abstraction layer.
- `packages/kaos`: the execution environment and file/process abstractions.
Expand Down
1 change: 1 addition & 0 deletions apps/kimi-code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
},
"devDependencies": {
"@moonshot-ai/acp-adapter": "workspace:^",
"@moonshot-ai/acp-server": "workspace:^",
"@moonshot-ai/agent-core-v2": "workspace:^",
"@moonshot-ai/kap-server": "workspace:^",
"@moonshot-ai/kimi-code-oauth": "workspace:^",
Expand Down
5 changes: 5 additions & 0 deletions apps/kimi-code/src/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ 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 @@ -117,6 +119,9 @@ export function createProgram(
registerProviderCommand(program);
registerAcpCommand(program);
registerWebCommand(program);
if (isAcpV2Enabled()) {
registerAcpV2Command(program);
}
registerLoginCommand(program);
registerDoctorCommand(program);
registerVisCommand(program);
Expand Down
7 changes: 7 additions & 0 deletions apps/kimi-code/src/cli/experimental-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

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

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

Expand All @@ -30,3 +31,9 @@ export function isKimiV2Enabled(
): boolean {
return isTruthyEnv(KIMI_V2_ENV, env);
}

export function isAcpV2Enabled(
env: Readonly<Record<string, string | undefined>> = process.env,
): boolean {
return isTruthyEnv(KIMI_ACP_V2_ENV, env) || isKimiV2Enabled(env);
}
77 changes: 77 additions & 0 deletions apps/kimi-code/src/cli/sub/acp-v2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* `kimi acp-v2` sub-command.
*
* 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).
*
* Wire-up mirrors `kimi acp` for the parts that are host-independent:
* - `--login` pivots into the shared device-code login flow (the entry point
* ACP clients hit via the first-class `AuthMethodTerminal` path, re-invoking
* the agent binary with the advertised `args:['--login']`).
* - `KIMI_CODE_HOME` (if set) is forwarded into `authMethods[0].env` so the
* login subprocess writes its token under the same data root the server
* reads from, and `process.argv[1]` is advertised as the legacy
* `_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`.
*/

import type { Command } from 'commander';

import { getVersion } from '#/cli/version';
import { KIMI_CODE_HOME_ENV } from '#/constant/app';
import { getDataDir } from '#/utils/paths';

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

export function registerAcpV2Command(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).',
)
.option(
'--login',
'Run the device-code login flow then exit (entry point for ACP terminal-auth).',
false,
)
.action(async (opts: { login?: boolean }) => {
if (opts.login === true) {
await runLoginFlow();
return;
}
// Forward `KIMI_CODE_HOME` (if set) into `authMethods[0].env` so the
// login subprocess clients spawn for terminal-auth writes its token
// under the same data root the ACP server reads from.
const sandboxHome = process.env[KIMI_CODE_HOME_ENV];
const terminalAuthEnv =
sandboxHome !== undefined && sandboxHome.length > 0
? { [KIMI_CODE_HOME_ENV]: sandboxHome }
: undefined;
// Legacy `_meta.terminal-auth` fallback for clients that don't yet
// honor the first-class `type:'terminal'`. `command` is the absolute
// path to this very binary so the client can spawn it for login.
const legacyCommand = process.argv[1];
try {
const { runAcpServer } = await import('@moonshot-ai/acp-server');
await runAcpServer({
homeDir: getDataDir(),
agentInfo: { name: 'Kimi Code CLI', version: getVersion() },
...(terminalAuthEnv ? { terminalAuthEnv } : {}),
...(legacyCommand !== undefined && legacyCommand.length > 0
? { terminalAuthLegacyCommand: legacyCommand }
: {}),
});
process.exit(0);
} catch (error) {
process.stderr.write(`acp-v2 server: fatal error: ${String(error)}\n`);
process.exit(1);
}
});
}
Loading
Loading