diff --git a/.changeset/agile-quails-glide.md b/.changeset/agile-quails-glide.md new file mode 100644 index 000000000..02217ab72 --- /dev/null +++ b/.changeset/agile-quails-glide.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2687 +--- +**The host-integration capability matrix now documents the `kimi-code` runtime** — kimi-code shipped as a distinct runtime but its section was never added, so its `hostIntegration` axes had no cited source. Sourcing each axis against Kimi Code CLI's own docs also corrected three values that had been inherited from the unrelated Python `kimi` CLI: `embeddingMode` is `declarative` (plugins are a manifest plus markdown Skills, with no in-process API), `dispatch.nested` is `true` (the `coder` built-in dispatches nested sub-agents), and `dispatch.maxDepth` is `undocumented` (no depth bound is published). (#2603) diff --git a/capabilities/kimi-code/capability.json b/capabilities/kimi-code/capability.json index 95e16acfd..798811874 100644 --- a/capabilities/kimi-code/capability.json +++ b/capabilities/kimi-code/capability.json @@ -51,12 +51,12 @@ "SubagentStart" ], "hostIntegration": { - "embeddingMode": "imperative", + "embeddingMode": "declarative", "commandSurface": "slash-file", "dispatch": { "namedDispatch": false, - "nested": false, - "maxDepth": 1, + "nested": true, + "maxDepth": "undocumented", "background": true, "subagentToolkit": "built-in-only", "backgroundDispatch": true, diff --git a/docs/reference/host-integration-capability-matrix.md b/docs/reference/host-integration-capability-matrix.md index 794cd3691..2a3d96201 100644 --- a/docs/reference/host-integration-capability-matrix.md +++ b/docs/reference/host-integration-capability-matrix.md @@ -33,7 +33,7 @@ consumed verbatim by `gen:capability-registry` and validated by `capability-vali | `nested` | Whether subagents can themselves spawn subagents (true/false/`undocumented`). | | `maxDepth` | Maximum nesting depth (integer; -1 = unbounded; `undocumented`). | | `background` | Whether subagents can run asynchronously in the background (true/false/`undocumented`). | -| `subagentToolkit` | Tool surface available to subagents: `full`, `read-only`, or `undocumented`. | +| `subagentToolkit` | Tool surface available to subagents: `full`, `read-only`, `built-in-only`, or `undocumented`. `built-in-only` means the host ships a fixed set of built-in subagent types whose tool surfaces differ from one another, so no single `full`/`read-only` value describes them; it degrades closed to `read-only` in the negotiated axes. | | `backgroundDispatch` | Whether a BACKGROUND-dispatched sub-agent can itself spawn further named sub-agents — the #853 discriminator (true/false/`undocumented`). | ### Interface points @@ -630,6 +630,48 @@ Documentation gaps: --- +## kimi-code + +> **`kimi-code` is a different product from `kimi` above — every axis below is sourced independently.** `kimi` is Moonshot's Python `kimi-cli` (`from kimi_cli.app import KimiCLI`, `~/.kimi/config.toml`, `--work-dir`); `kimi-code` is the TypeScript/Node **Kimi Code CLI** (`~/.kimi-code/config.toml`, `$KIMI_CODE_HOME`, process-cwd, `AgentSwarm`). None of the values here are inherited from the `kimi` section. See `docs/migration/kimi-to-kimi-code.md` for the user-facing split. + +| Axis | Value | Source | Evidence | +|---|---|---|---| +| embeddingMode | declarative | https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/plugins.md | "Plugins package reusable Kimi Code CLI capabilities into installable units — they can add Agent Skills, automatically load a specified Skill at session start, and declare MCP servers to provide real tool capabilities." A plugin is a `kimi.plugin.json` manifest plus markdown Skills; real tool capability arrives via external MCP processes. No in-process programmatic extension API is documented — the same shape as `codex` above. | +| commandSurface | slash-file | https://github.com/moonshotai/kimi-code/blob/main/docs/en/reference/slash-commands.md | "External skills are automatically registered as slash commands using the skill: namespace prefix. Users can invoke these by typing /skill:\ followed by optional text, which is then appended to the skill prompt." Skills are `SKILL.md` markdown files with YAML frontmatter (`name`, `description`, `type`, `whenToUse`, `arguments`). | +| modelMode | passive | https://github.com/moonshotai/kimi-code/blob/main/apps/kimi-code/src/cli/commands.ts ; /docs/en/guides/getting-started.md | "`-m, --model ` — LLM model alias to use for this invocation. Defaults to `default_model` in config.toml." Model selection is config alias + `--model` flag + the interactive `/model` command; no API lets an extension programmatically supply a model. | +| hookBus | host | https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/hooks.md | "All hook rules are written in the `[[hooks]]` array in `~/.kimi-code/config.toml`, where each entry is one rule." The CLI fires the lifecycle events (`UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `PermissionResult`, `SessionStart`, `SessionEnd`, `SubagentStart`, `SubagentStop`, `PreCompact`, `PostCompact`, `Stop`, `Notification`, `Interrupt`); each rule is `event` + optional `matcher` + `command` + optional `timeout` (1–600s, default 30). | +| stateIO | filesystem | https://github.com/MoonshotAI/kimi-code | "Kimi Code CLI is an AI coding agent that runs in your terminal — it can read and edit code, run shell commands, search files, fetch web pages, and choose the next step based on the feedback it receives." Full local filesystem; no sandboxed-storage tier is documented. | +| transport | mcp | https://github.com/MoonshotAI/kimi-code ; /docs/en/customization/plugins.md | "AI-native MCP configuration. Add, edit, and authenticate Model Context Protocol servers conversationally with `/mcp-config`, without hand-editing JSON." Plugin manifests additionally carry an `mcpServers` field. | +| runtime | node | https://github.com/MoonshotAI/kimi-code | "Requirements: Node.js ≥ 24.15.0, pnpm 10.33.0." The CLI is a TypeScript pnpm monorepo (`apps/kimi-code`, `packages/agent-core-v2`); hook commands are shell, and MCP servers are external processes. | +| dispatch.namedDispatch | false | https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/agents.md ; `capabilities/kimi-code/capability.json` (`artifactLayout` — skills only) | "The system includes three built-in sub-agents: 'coder' for general software engineering tasks like file modification, 'explore' for read-only codebase navigation and summarization, and 'plan' for architecture design without file or shell access." GSD's kimi-code artifact layout installs Agent **Skills** only (no `agents` kind), so no named GSD subagent is ever registered with the host and every GSD role resolves to one of the three built-ins (`resolveDispatchType`, `src/host-integration.cts`). **This is a GSD-integration-scoped `false`, not a claim that the host lacks named agents — see Documentation gaps.** | +| dispatch.nested | true | https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/agents.md | The `coder` sub-agent "can dispatch its own nested sub-agents when a task decomposes naturally." (Agent files also expose a `subagents` delegation allowlist, where `subagents: []` is what *prevents* further delegation — nesting is the default.) | +| dispatch.maxDepth | undocumented | searched: https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/agents.md | Nesting is documented, but no maximum nesting depth is stated anywhere in the agents or tools reference. | +| dispatch.background | true | https://github.com/moonshotai/kimi-code/blob/main/docs/en/reference/tools.md | "**`Agent`** delegates a subtask to a sub-Agent. Supports foreground execution (waiting for completion) or background execution (returning a task ID). … `run_in_background` (boolean) - Optional - Defaults to false." | +| dispatch.subagentToolkit | built-in-only | https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/agents.md | The three built-ins carry deliberately different tool surfaces — coder "Shares most of the main Agent's toolset; can run shell commands, maintain todo lists, enter Plan mode, and invoke Agent Skills"; explore "Performs read-only operations only and does not modify any files"; plan "Even shell commands are not available". No single `full`/`read-only` value covers the set, so the `built-in-only` member applies (degrades closed to `read-only` when negotiated). | +| dispatch.backgroundDispatch | true | https://github.com/moonshotai/kimi-code/blob/main/docs/en/reference/tools.md ; /docs/en/customization/agents.md | The `coder` built-in "can dispatch its own nested sub-agents" and the `Agent` tool's `run_in_background` is a call-time parameter available to it, so a background-dispatched sub-agent may itself dispatch further. `AgentSwarm` additionally fans out concurrently: "By default the tool ramps up concurrency without an upper limit (5 subagents start immediately, then 1 more every 700 ms); set `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY` to a positive integer to cap how many subagents run at the same time during that ramp, or leave it unset for no cap." | +| dispatch.isolation | orchestrator-worktree | https://github.com/moonshotai/kimi-code/blob/main/docs/en/reference/tools.md ; ADR-1239 §Codex-binding amendment | Neither `Agent` nor `AgentSwarm` exposes a working-directory/cwd parameter — sub-agents inherit the CLI's process cwd — and the CLI has no `--work-dir`-style flag (unlike `kimi`). GSD therefore creates, validates and merges the worktree itself and spawns the executor with that cwd (#2584). | + +**Negotiation note.** Because `dispatch.namedDispatch` is `false`, `negotiateHostCapabilities` caps `nested`, `maxDepth`, `background` and `backgroundDispatch` to `false`/`0` in the **effective** axes for structural consistency (`src/host-integration.cts`). The declared values above are still the host-capability record the matrix exists to hold; they are what a future named-dispatch upgrade would negotiate against. + +Sources consulted: +- https://github.com/MoonshotAI/kimi-code +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/agents.md +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/hooks.md +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/plugins.md +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/customization/skills.md +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/reference/tools.md +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/reference/slash-commands.md +- https://github.com/moonshotai/kimi-code/blob/main/docs/en/guides/getting-started.md +- https://github.com/moonshotai/kimi-code/blob/main/apps/kimi-code/src/cli/commands.ts +- /moonshotai/kimi-code (Context7) + +Documentation gaps: +- **dispatch.namedDispatch — host capability vs. GSD surface.** Kimi Code *does* support user-authored named agents: "Beyond the three built-in sub-agents, you can define your own agents as Markdown files", discovered across five scopes (`--agent-file` > `.kimi-code/agents/`, `.agents/agents/` > extra dirs > `$KIMI_CODE_HOME/agents/` > built-in). The axis is `false` because GSD installs **no** agent files for this host, so its reachable dispatch surface is the three built-ins — flipping the axis without also shipping agent artifacts would reintroduce the dispatch failure recorded in `docs/migration/kimi-to-kimi-code.md` ("Every workflow that called a named GSD subagent … **failed at dispatch**"). Shipping GSD agent files to kimi-code is an unbuilt capability upgrade, not a gap in the host's documentation. +- **dispatch.maxDepth** — nesting is documented but no depth bound is published, so the axis carries the `undocumented` sentinel rather than a guessed integer. +- **runtime** — the published artifact is a single bundled binary; `Node.js ≥ 24.15.0` is stated as a *development* requirement. The sources are a TypeScript/Node monorepo, so `node` is the best-supported classification, but the docs do not name a canonical plugin-execution runtime (the same ambiguity noted for `codex` above). + +--- + ## zcode > ZCode (Z.ai) is a desktop Agentic Development Environment for the GLM-5.2 model, distributed as an Electron app. It exposes a Claude-Code-shaped extensibility surface (per-user `~/.zcode/skills//SKILL.md`, slash commands, named subagents, native MCP, and a plugin system). All values below are sourced verbatim from the official ZCode docs. diff --git a/gsd-core/bin/lib/capability-registry.cjs b/gsd-core/bin/lib/capability-registry.cjs index 90983aacb..698c7b325 100644 --- a/gsd-core/bin/lib/capability-registry.cjs +++ b/gsd-core/bin/lib/capability-registry.cjs @@ -1834,12 +1834,12 @@ const capabilities = { "SubagentStart" ], "hostIntegration": { - "embeddingMode": "imperative", + "embeddingMode": "declarative", "commandSurface": "slash-file", "dispatch": { "namedDispatch": false, - "nested": false, - "maxDepth": 1, + "nested": true, + "maxDepth": "undocumented", "background": true, "subagentToolkit": "built-in-only", "backgroundDispatch": true, @@ -5295,12 +5295,12 @@ const runtimes = { "SubagentStart" ], "hostIntegration": { - "embeddingMode": "imperative", + "embeddingMode": "declarative", "commandSurface": "slash-file", "dispatch": { "namedDispatch": false, - "nested": false, - "maxDepth": 1, + "nested": true, + "maxDepth": "undocumented", "background": true, "subagentToolkit": "built-in-only", "backgroundDispatch": true, diff --git a/src/host-integration.cts b/src/host-integration.cts index d715a7aa5..d80249f0b 100644 --- a/src/host-integration.cts +++ b/src/host-integration.cts @@ -454,6 +454,13 @@ function negotiateHostCapabilities( if (hostDispatch.isolation === 'undocumented') { warnings.push(`dispatch.isolation is undocumented — degraded closed (none)`); } + if (hostDispatch.maxDepth === UNDOCUMENTED) { + // #2603: maxDepth was the one dispatch sub-axis with no sentinel-specific + // warning, so a descriptor carrying the documented fail-closed sentinel was + // reported as `missing or not a number` — indistinguishable from a genuinely + // malformed descriptor. Six shipped runtimes use the sentinel here. + warnings.push(`dispatch.maxDepth is undocumented — degraded closed (0)`); + } effectiveNamedDispatch = (hostDispatch.namedDispatch === true) && engineDispatch.namedDispatch; effectiveNested = (hostDispatch.nested === true) && engineDispatch.nested; @@ -475,10 +482,14 @@ function negotiateHostCapabilities( ? hostIso as DispatchIsolation : 'none'; - // maxDepth: missing/non-number/non-finite → 0 + warning + // maxDepth: missing/non-number/non-finite → 0 + warning. The documented + // 'undocumented' sentinel also degrades to 0, but is reported by the + // sentinel-specific warning above rather than as a malformed value (#2603). let hostMaxDepth: number; if (typeof hostDispatch.maxDepth !== 'number' || !Number.isFinite(hostDispatch.maxDepth)) { - warnings.push(`host dispatch.maxDepth is missing or not a number — treating as 0`); + if (hostDispatch.maxDepth !== UNDOCUMENTED) { + warnings.push(`host dispatch.maxDepth is missing or not a number — treating as 0`); + } hostMaxDepth = 0; } else { hostMaxDepth = hostDispatch.maxDepth; diff --git a/tests/fix-2603-kimi-code-host-matrix.test.cjs b/tests/fix-2603-kimi-code-host-matrix.test.cjs new file mode 100644 index 000000000..d7b53f26b --- /dev/null +++ b/tests/fix-2603-kimi-code-host-matrix.test.cjs @@ -0,0 +1,224 @@ +/** + * #2603 — `docs/reference/host-integration-capability-matrix.md` documented 18 of the + * 19 installed runtimes but had no `## kimi-code` section, so kimi-code's + * `runtime.hostIntegration` axes shipped with no cited source and no evidence quote. + * + * Sourcing each axis independently (the issue's explicit requirement — "Do not copy + * `kimi`'s section", they are distinct products) showed three axis values had been + * inherited from the Python `kimi` descriptor rather than sourced for Kimi Code CLI: + * + * - `embeddingMode: imperative` → `declarative`. Kimi Code plugins are a + * `kimi.plugin.json` manifest plus markdown Skills; "Plugins are configuration and + * markdown only" with no in-process programmatic API (docs/en/customization/plugins.md). + * Same shape as codex, which is `declarative`. + * - `dispatch.nested: false` → `true`. The `coder` built-in "can dispatch its own + * nested sub-agents when a task decomposes naturally" (docs/en/customization/agents.md). + * The Python `kimi` CLI genuinely prohibits nesting; Kimi Code does not. + * - `dispatch.maxDepth: 1` → `'undocumented'`. Nesting is documented but no depth + * bound is published, so the fail-closed sentinel applies rather than a guessed 1. + * + * `dispatch.namedDispatch` deliberately stays `false`: GSD's kimi-code artifact layout + * installs Agent Skills only (no `agents` kind), so no named GSD subagent is registered + * with the host and `resolveDispatchType` maps every role onto coder/explore/plan. + * Flipping it without also shipping agent files would reintroduce the dispatch failure + * recorded in docs/migration/kimi-to-kimi-code.md. + * + * This is the same defect class as #2598 (a descriptor axis asserting something the + * host docs contradict), and takes the same countermeasure: pin the corrected values + * AND require the matrix to agree with the descriptor, because a descriptor/matrix + * disagreement is how the gap survived. + */ + +// allow-test-rule: runtime-contract-is-the-product #2603 — the descriptor JSON and the +// host-integration matrix ARE the negotiated contract; asserting their values is behavioral. + +'use strict'; + +process.env.GSD_TEST_MODE = '1'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const ROOT = path.join(__dirname, '..'); +const DESCRIPTOR = path.join(ROOT, 'capabilities', 'kimi-code', 'capability.json'); +const MATRIX = path.join(ROOT, 'docs', 'reference', 'host-integration-capability-matrix.md'); + +const { + profileOf, + negotiateHostCapabilities, +} = require(path.join(ROOT, 'gsd-core/bin/lib/host-integration.cjs')); + +function kimiCodeAxes() { + return JSON.parse(fs.readFileSync(DESCRIPTOR, 'utf8')).runtime.hostIntegration; +} + +/** Extract the `## ` section body, stopping at the next top-level host heading. */ +function matrixSection(host) { + const matrix = fs.readFileSync(MATRIX, 'utf8'); + const start = matrix.indexOf(`\n## ${host}\n`); + if (start === -1) return null; + const rest = matrix.slice(start + 1); + const end = rest.indexOf('\n## '); + return end === -1 ? rest : rest.slice(0, end); +} + +/** Read the `| | | …` cell out of a matrix section. */ +function matrixValue(section, axis) { + const row = section.split('\n').find((l) => l.startsWith(`| ${axis} |`)); + return row ? row.split('|')[2].trim() : null; +} + +describe('#2603: the host-integration matrix documents kimi-code', () => { + test('a `## kimi-code` section exists', () => { + assert.ok( + matrixSection('kimi-code'), + 'the matrix is the deployment source-of-truth for every installed runtime; kimi-code must have a section', + ); + }); + + test('every hostIntegration axis kimi-code declares is documented in the matrix', () => { + const section = matrixSection('kimi-code'); + const axes = kimiCodeAxes(); + + const scalarAxes = Object.keys(axes).filter((k) => k !== 'dispatch'); + for (const axis of scalarAxes) { + assert.ok( + matrixValue(section, axis), + `matrix must document the "${axis}" axis for kimi-code`, + ); + } + + // `builtInSubagents` is a GSD-side list, not a negotiated axis — the matrix + // documents it in prose, not as its own row. + const dispatchAxes = Object.keys(axes.dispatch).filter((k) => k !== 'builtInSubagents'); + for (const axis of dispatchAxes) { + assert.ok( + matrixValue(section, `dispatch.${axis}`), + `matrix must document the "dispatch.${axis}" sub-axis for kimi-code`, + ); + } + }); + + test('the matrix values agree with the shipped descriptor', () => { + const section = matrixSection('kimi-code'); + const axes = kimiCodeAxes(); + + for (const axis of Object.keys(axes).filter((k) => k !== 'dispatch')) { + assert.equal( + matrixValue(section, axis), + String(axes[axis]), + `matrix "${axis}" must match the descriptor`, + ); + } + for (const axis of Object.keys(axes.dispatch).filter((k) => k !== 'builtInSubagents')) { + assert.equal( + matrixValue(section, `dispatch.${axis}`), + String(axes.dispatch[axis]), + `matrix "dispatch.${axis}" must match the descriptor`, + ); + } + }); + + test('the kimi-code section is sourced independently of the kimi section', () => { + // The two are distinct products (Python kimi-cli vs TypeScript Kimi Code CLI); + // the issue's central requirement is that kimi's section was NOT copied. The + // check is scoped to the axis ROWS — the section's prose intro deliberately + // names kimi's Python API to draw the contrast, which is the opposite of a copy. + const rows = matrixSection('kimi-code') + .split('\n') + .filter((l) => l.startsWith('| ') && !l.startsWith('| Axis |') && !l.startsWith('|---')); + + assert.ok(rows.length >= 11, 'expected a row per hostIntegration axis'); + for (const row of rows) { + assert.ok( + !row.includes('kimi_cli'), + `kimi-code axis row must not cite the Python kimi-cli: ${row.slice(0, 60)}`, + ); + assert.ok( + !row.includes('moonshotai.github.io/kimi-cli'), + `kimi-code axis row must not cite kimi-cli docs: ${row.slice(0, 60)}`, + ); + } + assert.ok( + rows.some((r) => r.includes('kimi-code/blob/main/docs')), + 'kimi-code axes must cite the Kimi Code CLI docs', + ); + }); +}); + +describe('#2603: axis values inherited from the Python kimi descriptor are corrected', () => { + test('embeddingMode is declarative — plugins expose no in-process API', () => { + assert.equal(kimiCodeAxes().embeddingMode, 'declarative'); + }); + + test('kimi-code therefore classifies as the declarative-cli profile', () => { + assert.equal(profileOf(kimiCodeAxes()), 'declarative-cli'); + }); + + test('dispatch.nested is true — the coder built-in dispatches nested sub-agents', () => { + assert.equal(kimiCodeAxes().dispatch.nested, true); + }); + + test('dispatch.maxDepth is the undocumented sentinel, not a guessed integer', () => { + assert.equal(kimiCodeAxes().dispatch.maxDepth, 'undocumented'); + }); + + test('namedDispatch stays false — GSD installs no agent files for this host', () => { + // Guard against a well-meaning "the docs say custom agents exist" edit: flipping + // this makes resolveDispatchType return `gsd-planner` unchanged, which kimi-code + // cannot dispatch (docs/migration/kimi-to-kimi-code.md). + assert.equal(kimiCodeAxes().dispatch.namedDispatch, false); + }); + + test('the undocumented maxDepth sentinel is reported as a sentinel, not as malformed', () => { + // Surfaced by this change: maxDepth was the ONE dispatch sub-axis with no + // sentinel-specific warning, so the documented fail-closed value was reported + // as "missing or not a number" — indistinguishable from a genuinely broken + // descriptor. kimi-code would have been the sixth runtime to hit that path. + const { warnings } = negotiateHostCapabilities(kimiCodeAxes()); + + assert.ok( + warnings.some((w) => w.includes('dispatch.maxDepth is undocumented')), + `expected a maxDepth sentinel warning, got: ${JSON.stringify(warnings)}`, + ); + assert.ok( + !warnings.some((w) => w.includes('maxDepth is missing or not a number')), + 'the documented sentinel must not be reported as a malformed value', + ); + }); + + test('a genuinely malformed maxDepth is still reported as malformed', () => { + // Boundary: the sentinel carve-out must not swallow the real error case. + const axes = kimiCodeAxes(); + const malformed = { ...axes, dispatch: { ...axes.dispatch, maxDepth: 'not-a-number' } }; + const { warnings } = negotiateHostCapabilities(malformed); + + assert.ok( + warnings.some((w) => w.includes('maxDepth is missing or not a number')), + `expected the malformed-value warning, got: ${JSON.stringify(warnings)}`, + ); + }); + + test('both maxDepth paths still degrade the effective value closed to 0', () => { + const axes = kimiCodeAxes(); + assert.equal(negotiateHostCapabilities(axes).effective.dispatch.maxDepth, 0); + const malformed = { ...axes, dispatch: { ...axes.dispatch, maxDepth: 'not-a-number' } }; + assert.equal(negotiateHostCapabilities(malformed).effective.dispatch.maxDepth, 0); + }); + + test('the axes that were already correct are left intact', () => { + const axes = kimiCodeAxes(); + assert.equal(axes.commandSurface, 'slash-file'); + assert.equal(axes.modelMode, 'passive'); + assert.equal(axes.hookBus, 'host'); + assert.equal(axes.stateIO, 'filesystem'); + assert.equal(axes.transport, 'mcp'); + assert.equal(axes.runtime, 'node'); + assert.equal(axes.dispatch.background, true); + assert.equal(axes.dispatch.backgroundDispatch, true); + assert.equal(axes.dispatch.subagentToolkit, 'built-in-only'); + assert.equal(axes.dispatch.isolation, 'orchestrator-worktree'); + }); +}); diff --git a/tests/host-integration-descriptors.test.cjs b/tests/host-integration-descriptors.test.cjs index 88ddbaad7..93af42da6 100644 --- a/tests/host-integration-descriptors.test.cjs +++ b/tests/host-integration-descriptors.test.cjs @@ -38,8 +38,12 @@ const DISPATCH_KEYS = ['namedDispatch', 'nested', 'maxDepth', 'background', 'sub const RUNTIME_IDS = Object.keys(registry.runtimes); // Contract-pinned profile split (derived from .host-cli-final.json): -// programmatic-cli: claude, cline, cursor, hermes, kilo, kimi, kimi-code, opencode, pi, qwen, trae (11) -// declarative-cli: antigravity, augment, codebuddy, codex, copilot, windsurf, zcode (7) +// programmatic-cli: claude, cline, cursor, hermes, kilo, kimi, opencode, pi, qwen, trae (10) +// declarative-cli: antigravity, augment, codebuddy, codex, copilot, kimi-code, windsurf, zcode (8) +// kimi-code moved programmatic-cli → declarative-cli in #2603: its plugin surface is a +// `kimi.plugin.json` manifest plus markdown Skills with no in-process programmatic API +// (docs/en/customization/plugins.md), the same shape as codex. The value had been inherited +// from the Python `kimi` descriptor rather than sourced for Kimi Code CLI. // ide: vscode (1) — #2103, the first installed ide-profile host. const EXPECTED_PROFILES = { claude: 'programmatic-cli', @@ -48,7 +52,6 @@ const EXPECTED_PROFILES = { hermes: 'programmatic-cli', kilo: 'programmatic-cli', kimi: 'programmatic-cli', - 'kimi-code': 'programmatic-cli', opencode: 'programmatic-cli', pi: 'programmatic-cli', qwen: 'programmatic-cli', @@ -58,6 +61,7 @@ const EXPECTED_PROFILES = { codebuddy: 'declarative-cli', codex: 'declarative-cli', copilot: 'declarative-cli', + 'kimi-code': 'declarative-cli', windsurf: 'declarative-cli', zcode: 'declarative-cli', vscode: 'ide',