fix(#2603): document kimi-code in the host-integration capability matrix (#2687)

* fix(#2603): document kimi-code in the host-integration matrix; correct 3 inherited axes

The matrix — ADR-1239's deployment source-of-truth — had a section for 18 of 19
installed runtimes but none for `kimi-code`, so its `hostIntegration` axes shipped
with no citation and no evidence quote.

Sourcing every axis independently against Kimi Code CLI's own docs (the issue's
explicit requirement — `kimi` and `kimi-code` are distinct products) showed three
values had been inherited from the Python `kimi` descriptor rather than sourced:

- `embeddingMode` imperative -> declarative. Kimi Code plugins are a
  `kimi.plugin.json` manifest plus markdown Skills with no in-process programmatic
  API (docs/en/customization/plugins.md) — the same shape as `codex`.
- `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 over a guessed integer.

`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 would reintroduce the dispatch failure recorded in
docs/migration/kimi-to-kimi-code.md. The matrix records the host-capability nuance
under Documentation gaps instead.

Behaviourally inert: `namedDispatch:false` already caps nested/maxDepth/background/
backgroundDispatch to false/0 in the effective axes (host-integration.cts:493-499),
and the install adapter is not selected by `embeddingMode` (install.js:543 always
uses the imperative adapter). The one visible effect is the curated profile pin,
which moves programmatic-cli -> declarative-cli.

Also fixes the axes legend, which omitted the `built-in-only` subagentToolkit
member that has been in the closed vocabulary since kimi-code shipped.

Same defect class and countermeasure as #2598: pin the corrected values and require
the matrix to agree with the descriptor, because a descriptor/matrix disagreement is
how the gap survived.

Closes #2603

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QT3ibz5qJuDuGqpTGRYVGf

* fix(#2603): report the maxDepth `undocumented` sentinel as a sentinel, not as malformed

Surfaced by the orthogonal review of this change. `negotiateHostCapabilities`
emits a sentinel-specific warning for every dispatch sub-axis carrying the
documented `undocumented` value — namedDispatch, nested, background,
subagentToolkit, backgroundDispatch, isolation — except `maxDepth`, which fell
through to the numeric guard and reported `host dispatch.maxDepth is missing or
not a number — treating as 0`.

That message is indistinguishable from a genuinely malformed descriptor, so a
correctly fail-closed descriptor reads as broken. Six shipped runtimes carry the
sentinel here (antigravity, augment, opencode, trae, windsurf, zcode) and this
PR's kimi-code correction adds a seventh, which is why it is fixed here rather
than left in place.

The numeric guard keeps firing for genuinely malformed values; both paths still
degrade `effective.dispatch.maxDepth` closed to 0. Covered by three tests,
including the boundary case that the sentinel carve-out must not swallow a real
malformed value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QT3ibz5qJuDuGqpTGRYVGf

* chore(#2603): backfill changeset PR number (#2687)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-26 22:52:57 -04:00
committed by GitHub
parent a3853472de
commit c07734216f
7 changed files with 301 additions and 15 deletions

View File

@@ -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)

View File

@@ -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,

View File

@@ -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:\<name\> 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 <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/<name>/SKILL.md`, slash commands, named subagents, native MCP, and a plugin system). All values below are sourced verbatim from the official ZCode docs.

View File

@@ -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,

View File

@@ -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;

View File

@@ -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 `## <host>` 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 `| <axis> | <value> | …` 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');
});
});

View File

@@ -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',