kimi-plugin-cc 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +5 -3
  3. package/.mcp.json +13 -0
  4. package/CHANGELOG.md +10 -0
  5. package/README.md +57 -5
  6. package/dist/cli-client.js +11 -1
  7. package/dist/commands/ask.js +4 -2
  8. package/dist/commands/cancel.js +43 -4
  9. package/dist/commands/pursue.js +3 -2
  10. package/dist/commands/rescue.js +3 -2
  11. package/dist/commands/review-gate.js +2 -4
  12. package/dist/commands/review.js +4 -2
  13. package/dist/commands/swarm.js +4 -3
  14. package/dist/companion.js +13 -40
  15. package/dist/help.js +2 -0
  16. package/dist/job-id.js +12 -0
  17. package/dist/job-store.js +59 -0
  18. package/dist/jobs.js +9 -1
  19. package/dist/mcp/binding.js +58 -0
  20. package/dist/mcp/job-control.js +345 -0
  21. package/dist/mcp/launcher.js +257 -0
  22. package/dist/mcp/operations.js +301 -0
  23. package/dist/mcp/protocol.js +275 -0
  24. package/dist/mcp/server.js +8 -0
  25. package/dist/mcp/stdio.js +78 -0
  26. package/dist/mcp/supervised-child.js +15 -0
  27. package/dist/mcp/tool-result.js +7 -0
  28. package/dist/mcp/workspace.js +34 -0
  29. package/dist/model-command.js +32 -0
  30. package/dist/version.js +1 -1
  31. package/docs/migration.md +2 -2
  32. package/docs/registry-releases.md +6 -8
  33. package/docs/safety.md +16 -2
  34. package/package.json +7 -3
  35. package/plugins/kimi-codex/.codex-plugin/plugin.json +7 -4
  36. package/plugins/kimi-codex/.mcp.json +28 -0
  37. package/plugins/kimi-codex/dist/cli-client.js +11 -1
  38. package/plugins/kimi-codex/dist/commands/ask.js +4 -2
  39. package/plugins/kimi-codex/dist/commands/cancel.js +43 -4
  40. package/plugins/kimi-codex/dist/commands/pursue.js +3 -2
  41. package/plugins/kimi-codex/dist/commands/rescue.js +3 -2
  42. package/plugins/kimi-codex/dist/commands/review-gate.js +2 -4
  43. package/plugins/kimi-codex/dist/commands/review.js +4 -2
  44. package/plugins/kimi-codex/dist/commands/swarm.js +4 -3
  45. package/plugins/kimi-codex/dist/companion.js +13 -40
  46. package/plugins/kimi-codex/dist/help.js +2 -0
  47. package/plugins/kimi-codex/dist/job-id.js +12 -0
  48. package/plugins/kimi-codex/dist/job-store.js +59 -0
  49. package/plugins/kimi-codex/dist/jobs.js +9 -1
  50. package/plugins/kimi-codex/dist/mcp/binding.js +58 -0
  51. package/plugins/kimi-codex/dist/mcp/job-control.js +345 -0
  52. package/plugins/kimi-codex/dist/mcp/launcher.js +257 -0
  53. package/plugins/kimi-codex/dist/mcp/operations.js +301 -0
  54. package/plugins/kimi-codex/dist/mcp/protocol.js +275 -0
  55. package/plugins/kimi-codex/dist/mcp/server.js +8 -0
  56. package/plugins/kimi-codex/dist/mcp/stdio.js +78 -0
  57. package/plugins/kimi-codex/dist/mcp/supervised-child.js +15 -0
  58. package/plugins/kimi-codex/dist/mcp/tool-result.js +7 -0
  59. package/plugins/kimi-codex/dist/mcp/workspace.js +34 -0
  60. package/plugins/kimi-codex/dist/model-command.js +32 -0
  61. package/plugins/kimi-codex/dist/version.js +1 -1
@@ -4,8 +4,8 @@
4
4
  "name": "linxule"
5
5
  },
6
6
  "metadata": {
7
- "description": "Kimi for Claude Code: code questions, reviews, fixes, and parallel tasks through the local Kimi Code CLI.",
8
- "version": "2.3.1"
7
+ "description": "Kimi for Claude Code: code questions, reviews, fixes, parallel tasks, and MCP tasks through the local Kimi Code CLI.",
8
+ "version": "2.4.0"
9
9
  },
10
10
  "plugins": [
11
11
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kimi",
3
- "description": "Kimi for Claude Code: ask, review, challenge, rescue, pursue, and swarm through the local Kimi Code CLI, with per-command PreToolUse safety hooks and an optional Stop-hook review gate.",
4
- "version": "2.3.1",
3
+ "description": "Kimi for Claude Code: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP tasks through the local Kimi Code CLI, with PreToolUse safety hooks.",
4
+ "version": "2.4.0",
5
5
  "homepage": "https://github.com/linxule/kimi-plugin-cc",
6
6
  "repository": "https://github.com/linxule/kimi-plugin-cc",
7
7
  "license": "Apache-2.0",
@@ -11,7 +11,9 @@
11
11
  "review",
12
12
  "code-review",
13
13
  "delegation",
14
- "multi-model"
14
+ "multi-model",
15
+ "mcp",
16
+ "model-context-protocol"
15
17
  ],
16
18
  "author": {
17
19
  "name": "linxule"
package/.mcp.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "mcpServers": {
3
+ "kimi": {
4
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/companion.sh",
5
+ "args": [
6
+ "mcp"
7
+ ],
8
+ "env": {
9
+ "KIMI_PLUGIN_CC_MCP_WORKSPACE": "${CLAUDE_PROJECT_DIR}"
10
+ }
11
+ }
12
+ }
13
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  > **Post-1.0 release history (v1.0.1 -> present) lives in [ROADMAP-TO-GA.md § Post-GA audit log](./ROADMAP-TO-GA.md#post-ga-audit-log)** and the "Version" / "Upstream compat" lines of [AGENTS.md](./AGENTS.md). Docs-only kimi-code compat checkups that don't bump the plugin version (e.g. the 0.14.2 / 0.14.3 patches) are recorded there, not here. Notable releases are summarized below; the GA entry and full pre-GA detail follow.
4
4
 
5
+ ## 2.4.0 — 2026-10-24
6
+
7
+ - Add one dependency-free MCP stdio server for Claude Code and Codex, with typed schemas for seven model operations plus job status, result, and cancellation. The existing slash commands, skills, safety hooks, and review gate remain intact.
8
+ - Return native MCP task handles for Claude calls that negotiate the tasks extension per request. Codex receives ordinary launch handles and uses explicit job-control tools; this does not claim native task-extension support for Codex.
9
+ - Supervise each operation through a fixed task/job UUID, bounded attachment, durable reconnects, and PID/PGID/start-token identity checks. Preserve recoverable launches and their public IDs after inconclusive probes, reconcile stale dead companions, and keep cancellation from signalling a changed identity.
10
+ - Preserve exact CLI 2.3.1 certification, workspace containment, data-root isolation, hook verification, resume lineage, and trusted write roots. Terminal jobs retain their artifact semantics: failed jobs complete tasks with `isError:true`, and cancelled jobs map to `cancelled`.
11
+ - Document the transport, lifecycle, ownership, safety, comment-hygiene, and contributor rules. Align package and marketplace descriptions and keywords with the MCP surface.
12
+
13
+ Contributors: **Kimi** (design, implementation, documentation, and verification), **Claude** and **Codex** (adversarial review and re-review).
14
+
5
15
  ## 2.3.1 — 2026-10-23
6
16
 
7
17
  - Certify exact kimi-code 2.3.1 for all eight native-v2 operations after the source scan, plan-ON/OFF controls and full 15-test candidate smoke. Preserve the no-plan construction and document the new session-lock, internal-`ps`/PATH and legacy-trust-migration residuals. See [evidence](docs/upstream-2.3.1-certification.md).
package/README.md CHANGED
@@ -40,7 +40,7 @@ You need:
40
40
  - macOS or Linux
41
41
  - Claude Code or Codex with plugin support
42
42
  - Node.js 22.5 or newer
43
- - Kimi Code CLI 2.3.1 (2.3.0 also certified), the recommended version for plugin 2.3.1
43
+ - Kimi Code CLI 2.3.1 (2.3.0 also certified), the recommended version for plugin 2.4.0
44
44
 
45
45
  Windows is not currently supported. Check your versions in a terminal:
46
46
 
@@ -108,7 +108,7 @@ needed to use the package. Kimi Code CLI and Node are still prerequisites.
108
108
  For an explicitly versioned local Claude Code plugin:
109
109
 
110
110
  ```sh
111
- npm install --prefix ~/.local/share/kimi-plugin-cc kimi-plugin-cc@2.3.1
111
+ npm install --prefix ~/.local/share/kimi-plugin-cc kimi-plugin-cc@2.4.0
112
112
  claude --plugin-dir ~/.local/share/kimi-plugin-cc/node_modules/kimi-plugin-cc
113
113
  ```
114
114
 
@@ -180,6 +180,19 @@ Canceling a task does not undo changes it has already made.
180
180
 
181
181
  Ask and rescue support resuming a session. For example, `/kimi:ask -r Explain the next step.` continues your latest ask session. In Codex, ask `$kimi-ask` to resume it. See the [ask options](./commands/ask.md) and [rescue options](./commands/rescue.md).
182
182
 
183
+ ### Use the MCP server
184
+
185
+ The plugin also bundles one MCP stdio server for Claude Code and Codex, alongside the existing slash commands and skills. Those interfaces remain available, and the existing safety-hook setup is still mandatory.
186
+
187
+ The seven model tools are `kimi_ask`, `kimi_review`, `kimi_challenge`, `kimi_rescue`, `kimi_pursue`, `kimi_swarm`, and `kimi_swarm_write`. Job control uses `kimi_job_status`, `kimi_job_result`, and `kimi_job_cancel`.
188
+
189
+ - **Claude Code:** task-capable calls return durable MCP tasks. Poll them with `tasks/get` and cancel them with `tasks/cancel`.
190
+ - **Codex:** calls currently use ordinary MCP results, not native MCP tasks. After attachment, a running job returns a launch handle; an already settled job returns its terminal result. Use the three job-control tools to check, read, or cancel it.
191
+
192
+ Claude supplies the project directory as the default workspace. When the host has no default, as is normal for Codex, the initial operation requires an absolute `workspace` path. Later job-control calls can rely on the job's persisted workspace binding. An MCP server disconnect does not cancel jobs. The jobs table owns status and outcomes; the terminal artifact is the result authority. Failed jobs return `isError: true` with their artifacts.
193
+
194
+ For example: “Use `kimi_review` to check this diff. In Claude Code, poll the returned task with `tasks/get`; in Codex, use the returned handle with `kimi_job_status` and then `kimi_job_result`.”
195
+
183
196
  ## Update or remove the plugin
184
197
 
185
198
  ### Update in Claude Code
@@ -249,7 +262,7 @@ Contributors need Bun to run `bun run check`. The detailed engineering documents
249
262
  - macOS 或 Linux
250
263
  - 支持插件的 Claude Code 或 Codex
251
264
  - Node.js 22.5 或更高版本
252
- - Kimi Code CLI 2.3.1(2.3.0 亦已认证),这是插件 2.3.1 的推荐版本
265
+ - Kimi Code CLI 2.3.1(2.3.0 亦已认证),这是插件 2.4.0 的推荐版本
253
266
 
254
267
  目前不支持 Windows。在终端检查版本:
255
268
 
@@ -364,6 +377,19 @@ Claude Code 还提供可选的[自动审查功能](./commands/setup.md),在 Cl
364
377
 
365
378
  Ask 和 rescue 支持恢复会话。例如,`/kimi:ask -r 解释下一步。` 会继续最近一次 ask 会话。在 Codex 中,请 `$kimi-ask` 恢复该会话。参见 [ask 参数](./commands/ask.md)和 [rescue 参数](./commands/rescue.md)。
366
379
 
380
+ ### 使用 MCP 服务器
381
+
382
+ 插件现在还内置了一个同时供 Claude Code 和 Codex 使用的 MCP stdio 服务器,与现有斜杠命令和技能并存。这些界面仍然可用,现有的安全钩子配置也仍是必需步骤。
383
+
384
+ 七个模型工具是 `kimi_ask`、`kimi_review`、`kimi_challenge`、`kimi_rescue`、`kimi_pursue`、`kimi_swarm` 和 `kimi_swarm_write`。任务控制使用 `kimi_job_status`、`kimi_job_result` 和 `kimi_job_cancel`。
385
+
386
+ - **Claude Code:** 支持任务式调用的工具会返回持久 MCP 任务,通过 `tasks/get` 轮询,通过 `tasks/cancel` 取消。
387
+ - **Codex:** 目前使用普通 MCP 结果,而不是原生 MCP 任务。附加完成后,仍在运行的任务返回启动句柄;已结束的任务直接返回最终结果。使用上述三个任务控制工具查看进度、读取结果或取消任务。
388
+
389
+ Claude 默认提供项目目录作为工作区。如果宿主没有默认工作区(Codex 通常如此),首次操作必须提供绝对的 `workspace` 路径。之后的任务控制调用可以使用为该任务保存的工作区绑定关系。MCP 服务器断开连接不会取消任务。`jobs` 表是任务状态与结局的依据,最终产物(artifact)是结果内容的依据。任务失败时会返回 `isError: true` 以及相应产物。
390
+
391
+ 例如:“使用 `kimi_review` 检查这次差异。在 Claude Code 中,用 `tasks/get` 轮询返回的任务;在 Codex 中,用返回的句柄调用 `kimi_job_status`,然后调用 `kimi_job_result`。”
392
+
367
393
  ## 更新或移除插件
368
394
 
369
395
  ### 在 Claude Code 中更新
@@ -433,7 +459,7 @@ Il vous faut :
433
459
  - macOS ou Linux
434
460
  - une version de Claude Code ou de Codex prenant en charge les plugins
435
461
  - Node.js 22.5 ou une version ultérieure
436
- - Kimi Code CLI 2.3.1 (2.3.0 également certifiée), la version recommandée pour le plugin 2.3.1
462
+ - Kimi Code CLI 2.3.1 (2.3.0 également certifiée), la version recommandée pour le plugin 2.4.0
437
463
 
438
464
  Windows n'est pas pris en charge actuellement. Vérifiez les versions dans un terminal :
439
465
 
@@ -548,6 +574,19 @@ Annuler une tâche n'annule pas les modifications déjà effectuées.
548
574
 
549
575
  Ask et rescue permettent de reprendre une session. Par exemple, `/kimi:ask -r Explique l'étape suivante.` reprend votre dernière session ask. Dans Codex, demandez à `$kimi-ask` de la reprendre. Consultez les [options d'ask](./commands/ask.md) et les [options de rescue](./commands/rescue.md).
550
576
 
577
+ ### Utiliser le serveur MCP
578
+
579
+ Le plugin intègre aussi un serveur MCP stdio unique pour Claude Code et Codex, en plus des commandes slash et skills existants. Ces interfaces restent disponibles et la configuration du hook de sécurité reste obligatoire.
580
+
581
+ Les sept outils de modèle sont `kimi_ask`, `kimi_review`, `kimi_challenge`, `kimi_rescue`, `kimi_pursue`, `kimi_swarm` et `kimi_swarm_write`. Le contrôle des tâches utilise `kimi_job_status`, `kimi_job_result` et `kimi_job_cancel`.
582
+
583
+ - **Claude Code :** les appels compatibles avec les tâches renvoient des tâches MCP durables. Interrogez-les avec `tasks/get` et annulez-les avec `tasks/cancel`.
584
+ - **Codex :** les appels utilisent actuellement des résultats MCP ordinaires, et non des tâches MCP natives. Après l'attachement, un job encore actif renvoie un handle de lancement ; un job déjà terminé renvoie directement son résultat final. Utilisez les trois outils de contrôle pour suivre, lire ou annuler le job.
585
+
586
+ Claude fournit le répertoire du projet comme espace de travail par défaut. Sans espace de travail par défaut côté hôte, ce qui est normal pour Codex, l'opération initiale exige un chemin `workspace` absolu. Les contrôles ultérieurs peuvent utiliser l'association enregistrée entre la tâche et l'espace de travail. Une déconnexion du serveur MCP n'annule pas les tâches. La table jobs fait autorité pour l'état et l'issue des jobs ; l'artefact final fait autorité pour le contenu du résultat. Une tâche en échec renvoie `isError: true` avec ses artefacts.
587
+
588
+ Par exemple : « Utilise `kimi_review` pour vérifier ce diff. Dans Claude Code, interroge la tâche renvoyée avec `tasks/get` ; dans Codex, utilise le handle renvoyé avec `kimi_job_status`, puis `kimi_job_result`. »
589
+
551
590
  ## Mettre à jour ou supprimer le plugin
552
591
 
553
592
  ### Mettre à jour dans Claude Code
@@ -617,7 +656,7 @@ Les contributeurs ont besoin de Bun pour exécuter `bun run check`. La documenta
617
656
  - macOS または Linux
618
657
  - プラグインに対応した Claude Code または Codex
619
658
  - Node.js 22.5 以降
620
- - Kimi Code CLI 2.3.1(2.3.0 も認証済み。プラグイン 2.3.1 の推奨バージョン)
659
+ - Kimi Code CLI 2.3.1(2.3.0 も認証済み。プラグイン 2.4.0 の推奨バージョン)
621
660
 
622
661
  現在、Windows には対応していません。ターミナルでバージョンを確認します。
623
662
 
@@ -732,6 +771,19 @@ Codex では「$kimi-review を使い、設定済みの『……』というモ
732
771
 
733
772
  Ask と rescue はセッションの再開に対応しています。たとえば `/kimi:ask -r 次の手順を説明してください。` は、直近の ask セッションを続けます。Codex では `$kimi-ask` に再開を依頼してください。[ask のオプション](./commands/ask.md)と [rescue のオプション](./commands/rescue.md)を参照できます。
734
773
 
774
+ ### MCP サーバーを使う
775
+
776
+ プラグインには、既存のスラッシュコマンドとスキルに加え、Claude Code と Codex の両方で使える MCP stdio サーバーが 1 つ同梱されています。既存のコマンドとスキルも引き続き使え、既存の安全フックの設定も必須です。
777
+
778
+ 7 つのモデルツールは `kimi_ask`、`kimi_review`、`kimi_challenge`、`kimi_rescue`、`kimi_pursue`、`kimi_swarm`、`kimi_swarm_write` です。ジョブ制御には `kimi_job_status`、`kimi_job_result`、`kimi_job_cancel` を使います。
779
+
780
+ - **Claude Code:** タスク対応の呼び出しは永続的な MCP タスクを返します。`tasks/get` でポーリングし、`tasks/cancel` でキャンセルします。
781
+ - **Codex:** 現在はネイティブの MCP タスクではなく、通常の MCP 結果を使います。アタッチ後、実行中のジョブには起動ハンドルを返し、すでに終了したジョブには最終結果を直接返します。進捗確認・結果取得・キャンセルには 3 つのジョブ制御ツールを使います。
782
+
783
+ Claude はプロジェクトディレクトリをデフォルトのワークスペースとして提供します。ホスト側にデフォルトがない場合(Codex ではこれが通常です)、最初の操作では絶対パスの `workspace` が必要です。後続のジョブ制御では、そのジョブに保存されたワークスペースの関連付けを利用できます。MCP サーバーとの接続が切れても、ジョブはキャンセルされません。`jobs` テーブルがジョブの状態と結果を管理し、最終成果物(artifact)が結果内容の正規の情報源となります。失敗したジョブも成果物とともに `isError: true` を返します。
784
+
785
+ 例:「`kimi_review` でこの差分を確認してください。Claude Code では返されたタスクを `tasks/get` でポーリングし、Codex では返されたハンドルを使って `kimi_job_status`、`kimi_job_result` の順に呼び出してください。」
786
+
735
787
  ## プラグインを更新または削除する
736
788
 
737
789
  ### Claude Code で更新する
@@ -305,7 +305,7 @@ export async function runCliPrompt(opts) {
305
305
  args,
306
306
  command_label: opts.commandLabel ?? null,
307
307
  });
308
- return await new Promise((resolve, reject) => {
308
+ return await new Promise(async (resolve, reject) => {
309
309
  let aborted = false;
310
310
  let cancellationTeardown;
311
311
  let settlementStarted = false;
@@ -536,6 +536,16 @@ export async function runCliPrompt(opts) {
536
536
  if (opts.signal?.aborted === true) {
537
537
  onAbort();
538
538
  }
539
+ if (child.pid !== undefined && opts.onSpawn) {
540
+ try {
541
+ await opts.onSpawn(child.pid);
542
+ }
543
+ catch (error) {
544
+ onAbort();
545
+ settle("reject", new RuntimeError("CLI_SPAWN_CALLBACK_FAILED", `Failed to record the spawned kimi process: ${formatError(error)}`, "cli-client.spawn", error instanceof Error ? { cause: error, details: errorDetails() } : { details: errorDetails() }));
546
+ return;
547
+ }
548
+ }
539
549
  earlyErrorHandled = true;
540
550
  child.on("error", (err) => {
541
551
  appendLogLine({ event: "process_error", message: formatError(err) });
@@ -1,5 +1,5 @@
1
1
  import { companionHelp } from "../help.js";
2
- import { randomUUID } from "node:crypto";
2
+ import { allocateJobId } from "../job-id.js";
3
3
  import path from "node:path";
4
4
  import { createCliCancellationHandlers } from "../cli-cancellation.js";
5
5
  import { runCliPromptWithBudget } from "../cli-client.js";
@@ -55,7 +55,7 @@ export async function runAsk(argv, context) {
55
55
  const store = new JobStore(paths);
56
56
  try {
57
57
  await sweepStaleJobs(store, paths);
58
- const jobId = randomUUID();
58
+ const jobId = allocateJobId(context);
59
59
  const sessionResolution = await resolveAskSession(store, repoIdentity.repoId, parsed.fresh, parsed.resume, parsed.resumeTarget);
60
60
  const askPrompt = buildAskPrompt(parsed.prompt, sessionResolution.reusedSession);
61
61
  const executionPlan = await prepareKimiExecutionPlan({
@@ -152,6 +152,7 @@ export async function executeAskJob(jobId, prompt, context, options) {
152
152
  }
153
153
  handlers = createCliCancellationHandlers();
154
154
  const executionPlan = executionPlanFromPersisted(job);
155
+ store.updateRunningJob(job.job_id, { phase: "turn-running" });
155
156
  // Re-validate resume lineage from the store before spawning: a detached
156
157
  // worker (or a forged queued row) reloads the persisted plan and would
157
158
  // otherwise hand kimi_session_id to the spawn without the dispatch-time
@@ -174,6 +175,7 @@ export async function executeAskJob(jobId, prompt, context, options) {
174
175
  thinkingEffort: job.thinking_effort ?? undefined,
175
176
  resumeSessionId: job.kimi_session_id ?? undefined,
176
177
  logPath: job.stream_log_path,
178
+ onSpawn: (pid) => { store.updateRunningJob(job.job_id, { kimi_pid: pid }); },
177
179
  signal: handlers.signal,
178
180
  }, job.budget_ms ?? KIMI_ASK_PROMPT_TIMEOUT_MS, "ask.prompt");
179
181
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
@@ -5,6 +5,7 @@ import { markJobCancelled, sweepStaleJobs } from "../jobs.js";
5
5
  import { JobStore, withJobStore } from "../job-store.js";
6
6
  import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
7
7
  import { parseJobLookupArgs } from "../parsing.js";
8
+ import { revalidateProcessIdentities } from "../process-tree.js";
8
9
  import { getManagedCommandConfig } from "./registry.js";
9
10
  export async function runCancel(argv, context) {
10
11
  const parsed = parseJobLookupArgs(argv);
@@ -31,6 +32,10 @@ export async function runCancel(argv, context) {
31
32
  message: "cancel is a no-op for terminal jobs.",
32
33
  }, null, 2)}\n`;
33
34
  }
35
+ if (store.getMcpTask(job.job_id) &&
36
+ (context.mcpCompanionIdentity === undefined || context.mcpKimiIdentity === undefined)) {
37
+ throw new RuntimeError("MCP_CANCEL_REQUIRES_BINDING", `MCP job ${job.job_id} requires MCP job-control cancellation with recorded identities; use tasks/cancel or kimi_job_cancel.`, "mcp.cancel");
38
+ }
34
39
  if (job.pid === null && job.kimi_pid === null) {
35
40
  throw new RuntimeError("CANCEL_NOT_SUPPORTED", `Job ${job.job_id} does not have a recorded process id to cancel.`, "cancel.runtime");
36
41
  }
@@ -53,6 +58,7 @@ export async function runCancel(argv, context) {
53
58
  catch (error) {
54
59
  preMarkError = error;
55
60
  }
61
+ await assertMcpCompanionIdentity(job, context.mcpCompanionIdentity);
56
62
  signalJobProcesses(job, "SIGTERM");
57
63
  await sleep(1_000);
58
64
  if (hasLiveRecordedProcess(job)) {
@@ -67,6 +73,8 @@ export async function runCancel(argv, context) {
67
73
  message: "review_gate cancelled; SIGTERM→SIGKILL escalation completed.",
68
74
  }, null, 2)}\n`;
69
75
  }
76
+ await assertMcpCompanionIdentity(job, context.mcpCompanionIdentity);
77
+ await assertMcpKimiIdentity(job, context.mcpKimiIdentity);
70
78
  signalJobProcesses(job, "SIGTERM");
71
79
  const cancelled = await waitForCancellation(paths, job.job_id);
72
80
  if (cancelled) {
@@ -76,7 +84,11 @@ export async function runCancel(argv, context) {
76
84
  message: "Cancellation completed.",
77
85
  }, null, 2)}\n`;
78
86
  }
79
- signalJobProcesses(job, "SIGKILL");
87
+ const companionMatches = await assertMcpCompanionIdentity(job, context.mcpCompanionIdentity, true);
88
+ const kimiMatches = await assertMcpKimiIdentity(job, context.mcpKimiIdentity, true);
89
+ if (companionMatches || kimiMatches) {
90
+ signalJobProcesses(job, "SIGKILL", { companion: companionMatches, kimi: kimiMatches });
91
+ }
80
92
  await withJobStore(paths, async (forcedStore) => {
81
93
  const current = forcedStore.getJob(job.job_id);
82
94
  if (current?.status === "running") {
@@ -93,11 +105,38 @@ export async function runCancel(argv, context) {
93
105
  store.close();
94
106
  }
95
107
  }
96
- function signalJobProcesses(job, signal) {
108
+ async function assertMcpCompanionIdentity(job, expected, allowMissing = false) {
109
+ return assertMcpProcessIdentity(job.pid, expected, "companion", allowMissing);
110
+ }
111
+ async function assertMcpKimiIdentity(job, expected, allowMissing = false) {
112
+ return assertMcpProcessIdentity(job.kimi_pid, expected, "Kimi", allowMissing);
113
+ }
114
+ async function assertMcpProcessIdentity(actualPid, expected, label, allowMissing) {
115
+ if (expected === undefined)
116
+ return true;
117
+ if (expected === null) {
118
+ if (actualPid === null)
119
+ return false;
120
+ throw new RuntimeError("CANCEL_NOT_SUPPORTED", `MCP job ${actualPid} has no verified ${label} identity; refusing PID-only cancellation.`, "mcp.cancel");
121
+ }
122
+ if (actualPid !== expected.pid) {
123
+ throw new RuntimeError("MCP_PROCESS_IDENTITY_MISMATCH", `The recorded ${label} process identity no longer matches; no signal was sent.`, "mcp.cancel");
124
+ }
125
+ const validation = await revalidateProcessIdentities([expected]);
126
+ if (!validation.complete) {
127
+ throw new RuntimeError("MCP_PROCESS_IDENTITY_UNAVAILABLE", `The ${label} identity probe was inconclusive; no further signal was sent and the job was not changed.`, "mcp.cancel");
128
+ }
129
+ const matches = validation.identities.length > 0;
130
+ if (!matches && !allowMissing) {
131
+ throw new RuntimeError("MCP_PROCESS_IDENTITY_MISMATCH", `The recorded ${label} process identity no longer matches; no signal was sent.`, "mcp.cancel");
132
+ }
133
+ return matches;
134
+ }
135
+ function signalJobProcesses(job, signal, include = { companion: true, kimi: true }) {
97
136
  const pids = new Set();
98
- if (job.pid !== null)
137
+ if (include.companion && job.pid !== null)
99
138
  pids.add(job.pid);
100
- if (job.kimi_pid !== null)
139
+ if (include.kimi && job.kimi_pid !== null)
101
140
  pids.add(job.kimi_pid);
102
141
  for (const pid of pids) {
103
142
  try {
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { allocateJobId } from "../job-id.js";
2
2
  import path from "node:path";
3
3
  import { createCliCancellationHandlers } from "../cli-cancellation.js";
4
4
  import { runCliPromptWithBudget } from "../cli-client.js";
@@ -122,7 +122,7 @@ export async function runPursue(argv, context) {
122
122
  env: context.env,
123
123
  });
124
124
  const prompt = buildGoalPrompt(objective, parsed.turns);
125
- const jobId = randomUUID();
125
+ const jobId = allocateJobId(context);
126
126
  const logPath = path.join(paths.logsDir, `pursue-${jobId}.jsonl`);
127
127
  const job = store.createJob({
128
128
  job_id: jobId,
@@ -226,6 +226,7 @@ async function executePursueJob(jobId, prompt, objective, budgetMs, context) {
226
226
  model: job.model ?? undefined,
227
227
  thinkingEffort: job.thinking_effort ?? undefined,
228
228
  logPath: job.stream_log_path,
229
+ onSpawn: (pid) => { store.updateRunningJob(job.job_id, { kimi_pid: pid }); },
229
230
  signal: handlers.signal,
230
231
  }, budgetMs, "pursue.prompt");
231
232
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
@@ -1,5 +1,5 @@
1
1
  import { companionHelp } from "../help.js";
2
- import { randomUUID } from "node:crypto";
2
+ import { allocateJobId } from "../job-id.js";
3
3
  import path from "node:path";
4
4
  import { createCliCancellationHandlers } from "../cli-cancellation.js";
5
5
  import { runCliPromptWithBudget } from "../cli-client.js";
@@ -61,7 +61,7 @@ export async function runRescue(argv, context) {
61
61
  resumeSessionId: sessionResolution.kimiSessionId ?? undefined,
62
62
  resumeSource: sessionResolution.sourceJob,
63
63
  });
64
- const jobId = randomUUID();
64
+ const jobId = allocateJobId(context);
65
65
  const logPath = path.join(paths.logsDir, `rescue-${jobId}.jsonl`);
66
66
  const job = store.createJob({
67
67
  job_id: jobId,
@@ -192,6 +192,7 @@ export async function executeRescueJob(jobId, prompt, context, options) {
192
192
  thinkingEffort: job.thinking_effort ?? undefined,
193
193
  resumeSessionId: job.kimi_session_id ?? undefined,
194
194
  logPath: job.stream_log_path,
195
+ onSpawn: (pid) => { store.updateRunningJob(job.job_id, { kimi_pid: pid }); },
195
196
  signal: handlers.signal,
196
197
  }, job.budget_ms ?? KIMI_RESCUE_PROMPT_TIMEOUT_MS, "rescue.prompt");
197
198
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
@@ -166,10 +166,7 @@ async function executeReviewGate(payload, assistantMessage, context) {
166
166
  try {
167
167
  const activeStore = store;
168
168
  // runCliPromptWithBudget ties the 8 s timeout to an AbortController
169
- // that kills the kimi child on expiry — review_gate runs inside
170
- // Claude Code's Stop hook, so a runaway kimi after timeout would
171
- // hold model tokens with no way for /kimi:cancel to reach it
172
- // (the SQLite row's kimi_pid is null). See reports/17 and 18.
169
+ // that kills the kimi child on expiry and records its PID at spawn.
173
170
  const result = await runCliPromptWithBudget({
174
171
  cwd: payload.cwd,
175
172
  env: context.env,
@@ -182,6 +179,7 @@ async function executeReviewGate(payload, assistantMessage, context) {
182
179
  // Requested via env; provider/model may ignore it. The 8s budget still applies.
183
180
  thinkingEffort,
184
181
  logPath,
182
+ onSpawn: (pid) => { activeStore.updateRunningJob(job.job_id, { kimi_pid: pid }); },
185
183
  }, KIMI_REVIEW_GATE_TIMEOUT_MS, "review_gate.runtime");
186
184
  activeStore.updateRunningJob(job.job_id, observedExecutionFields(result));
187
185
  assertCliResultSuccess(result, "review_gate.runtime");
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { allocateJobId } from "../job-id.js";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { createCliCancellationHandlers } from "../cli-cancellation.js";
@@ -38,7 +38,7 @@ export async function runReview(argv, context, commandType) {
38
38
  env: context.env,
39
39
  });
40
40
  const store = new JobStore(paths);
41
- const jobId = randomUUID();
41
+ const jobId = allocateJobId(context);
42
42
  // Leave kimi_session_id NULL until kimi announces an actual ID; never invent
43
43
  // one that no on-disk session matches.
44
44
  const reviewSessionId = null;
@@ -75,6 +75,7 @@ export async function runReview(argv, context, commandType) {
75
75
  const reviewConfig = getManagedCommandConfig(commandType);
76
76
  const cancel = reviewConfig.cancellation;
77
77
  const handlers = createCliCancellationHandlers();
78
+ store.updateRunningJob(job.job_id, { phase: "turn-running" });
78
79
  try {
79
80
  const result = await runCliPromptWithBudget({
80
81
  cwd: context.cwd,
@@ -87,6 +88,7 @@ export async function runReview(argv, context, commandType) {
87
88
  model: parsed.model,
88
89
  thinkingEffort: parsed.thinkingEffort,
89
90
  logPath,
91
+ onSpawn: (pid) => { store.updateRunningJob(job.job_id, { kimi_pid: pid }); },
90
92
  signal: handlers.signal,
91
93
  }, job.budget_ms ?? KIMI_REVIEW_PROMPT_TIMEOUT_MS, `${commandType}.prompt`);
92
94
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
@@ -1,4 +1,4 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { allocateJobId } from "../job-id.js";
2
2
  import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { createCliCancellationHandlers } from "../cli-cancellation.js";
@@ -180,7 +180,7 @@ async function runReadSwarm(parsed, objective, context) {
180
180
  env: context.env,
181
181
  });
182
182
  const prompt = buildSwarmPrompt(objective, parsed.cap);
183
- const jobId = randomUUID();
183
+ const jobId = allocateJobId(context);
184
184
  const logPath = path.join(paths.logsDir, `swarm-${jobId}.jsonl`);
185
185
  const job = store.createJob({
186
186
  job_id: jobId,
@@ -257,7 +257,7 @@ async function runWriteSwarm(parsed, objective, context) {
257
257
  context.stderr.write("[kimi-plugin-cc] /kimi:swarm --write bases its worktree on HEAD — your uncommitted changes are NOT visible to the swarm. Commit or stash them first if the swarm needs them.\n");
258
258
  }
259
259
  const prompt = buildSwarmWritePrompt(objective, parsed.cap);
260
- const jobId = randomUUID();
260
+ const jobId = allocateJobId(context);
261
261
  const logPath = path.join(paths.logsDir, `swarm-write-${jobId}.jsonl`);
262
262
  // Namespace worktrees per repo so the cross-repo sweep below only ever
263
263
  // touches THIS repo's subtree (the worktreesDir is a single global dir shared
@@ -493,6 +493,7 @@ writeMode) {
493
493
  model: job.model ?? undefined,
494
494
  thinkingEffort: job.thinking_effort ?? undefined,
495
495
  logPath: job.stream_log_path,
496
+ onSpawn: (pid) => { store.updateRunningJob(job.job_id, { kimi_pid: pid }); },
496
497
  signal: handlers.signal,
497
498
  }, budgetMs, "swarm.prompt");
498
499
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
package/dist/companion.js CHANGED
@@ -1,20 +1,17 @@
1
1
  import { companionHelp } from "./help.js";
2
- import { runAsk } from "./commands/ask.js";
3
2
  import { runModelInventory } from "./commands/models.js";
4
3
  import { runCancel } from "./commands/cancel.js";
5
- import { notImplementedCompanionCommand } from "./commands/not-implemented.js";
6
4
  import { runReplay } from "./commands/replay.js";
7
5
  import { runRepairSessions } from "./commands/repair-sessions.js";
8
6
  import { runResult } from "./commands/result.js";
9
- import { runReview } from "./commands/review.js";
10
- import { runRescue } from "./commands/rescue.js";
11
- import { runPursue } from "./commands/pursue.js";
12
- import { runSwarm } from "./commands/swarm.js";
13
7
  import { renderSetupResult, runSetup } from "./commands/setup.js";
14
8
  import { runStatus } from "./commands/status.js";
15
9
  import { runWorker } from "./commands/worker.js";
16
10
  import { formatError, RuntimeError } from "./errors.js";
17
11
  import { resolveKimiHome } from "./kimi-home.js";
12
+ import { runModelCommand } from "./model-command.js";
13
+ import { runMcpChild } from "./mcp/supervised-child.js";
14
+ import { runMcpServer } from "./mcp/server.js";
18
15
  async function main(argv) {
19
16
  const help = companionHelp(argv);
20
17
  if (help !== undefined) {
@@ -27,11 +24,16 @@ async function main(argv) {
27
24
  cwd,
28
25
  env: {
29
26
  ...process.env,
30
- KIMI_CODE_HOME: resolveKimiHome(process.env, cwd),
27
+ KIMI_CODE_HOME: command === "mcp" ? process.env.KIMI_CODE_HOME : resolveKimiHome(process.env, cwd),
31
28
  },
32
29
  stdout: process.stdout,
33
30
  stderr: process.stderr,
34
31
  };
32
+ const modelResult = await runModelCommand(argv, context);
33
+ if (modelResult !== undefined) {
34
+ context.stdout.write(modelResult);
35
+ return;
36
+ }
35
37
  switch (command) {
36
38
  case "setup": {
37
39
  if (rest.includes("--models")) {
@@ -57,41 +59,12 @@ async function main(argv) {
57
59
  }
58
60
  return;
59
61
  }
60
- case "review": {
61
- const result = await runReview(rest, context, "review");
62
- context.stdout.write(`${result}\n`);
63
- return;
64
- }
65
- case "task": {
66
- const [taskType, ...taskArgs] = rest;
67
- if (taskType === "rescue") {
68
- const result = await runRescue(taskArgs, context);
69
- context.stdout.write(result);
70
- return;
71
- }
72
- if (taskType === "challenge") {
73
- const result = await runReview(taskArgs, context, "challenge");
74
- context.stdout.write(`${result}\n`);
75
- return;
76
- }
77
- if (taskType === "pursue") {
78
- const result = await runPursue(taskArgs, context);
79
- context.stdout.write(result);
80
- return;
81
- }
82
- if (taskType === "swarm") {
83
- const result = await runSwarm(taskArgs, context);
84
- context.stdout.write(result);
85
- return;
86
- }
87
- notImplementedCompanionCommand(taskType ? `task ${taskType}` : "task");
62
+ case "mcp":
63
+ await runMcpServer(context);
88
64
  return;
89
- }
90
- case "ask": {
91
- const result = await runAsk(rest, context);
92
- context.stdout.write(`${result}\n`);
65
+ case "mcp-child":
66
+ context.stdout.write(await runMcpChild(rest, context));
93
67
  return;
94
- }
95
68
  case "status":
96
69
  context.stdout.write(await runStatus(rest, context));
97
70
  return;
package/dist/help.js CHANGED
@@ -35,6 +35,8 @@ export function companionHelp(argv) {
35
35
  case "replay": return usage(command, "<job-id>", REPLAY_SUPPORTED_FLAGS, "Replay a completed job's persisted stream log.");
36
36
  case "repair-sessions": return usage(command, "[options]", REPAIR_SESSIONS_SUPPORTED_FLAGS, "Preview session metadata repair; --apply writes, --all selects all host-owned repositories.");
37
37
  case "worker": return "Usage: companion.sh worker <ask|rescue> <job-id>. Internal detached-worker entry point; launched by ask/rescue, not manually.";
38
+ case "mcp": return "Usage: companion.sh mcp. Run the Kimi MCP stdio server; normally launched by Claude Code or Codex plugin configuration.";
39
+ case "mcp-child": return "Usage: companion.sh mcp-child <job-uuid> <model-operation> [arguments]. Internal supervised MCP child; launched by the MCP server, not manually.";
38
40
  default: return undefined;
39
41
  }
40
42
  }
package/dist/job-id.js ADDED
@@ -0,0 +1,12 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { RuntimeError } from "./errors.js";
3
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
+ export function allocateJobId(context) {
5
+ const fixed = context.mcpJobId;
6
+ if (fixed === undefined)
7
+ return randomUUID();
8
+ if (!UUID_PATTERN.test(fixed)) {
9
+ throw new RuntimeError("MCP_INVALID_JOB_ID", "The supervised job ID must be a valid UUID.", "mcp.child");
10
+ }
11
+ return fixed;
12
+ }