kimi-plugin-cc 2.3.1 → 2.4.1
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +5 -3
- package/.mcp.json +13 -0
- package/CHANGELOG.md +19 -0
- package/README.md +67 -7
- package/dist/cli-client.js +13 -3
- package/dist/commands/ask.js +4 -2
- package/dist/commands/cancel.js +43 -4
- package/dist/commands/cli-helpers.js +5 -1
- package/dist/commands/pursue.js +3 -2
- package/dist/commands/rescue.js +3 -2
- package/dist/commands/review-gate.js +2 -4
- package/dist/commands/review.js +4 -2
- package/dist/commands/swarm.js +195 -37
- package/dist/companion.js +13 -40
- package/dist/help.js +2 -0
- package/dist/hooks/config-safety.js +4 -3
- package/dist/job-id.js +12 -0
- package/dist/job-store.js +59 -0
- package/dist/jobs.js +9 -1
- package/dist/kimi-engine.js +1 -0
- package/dist/mcp/binding.js +58 -0
- package/dist/mcp/job-control.js +345 -0
- package/dist/mcp/launcher.js +257 -0
- package/dist/mcp/operations.js +301 -0
- package/dist/mcp/protocol.js +275 -0
- package/dist/mcp/server.js +8 -0
- package/dist/mcp/stdio.js +78 -0
- package/dist/mcp/supervised-child.js +15 -0
- package/dist/mcp/tool-result.js +7 -0
- package/dist/mcp/workspace.js +34 -0
- package/dist/model-command.js +32 -0
- package/dist/process-tree.js +48 -17
- package/dist/swarm-activation.js +812 -0
- package/dist/version.js +1 -1
- package/docs/migration.md +12 -2
- package/docs/registry-releases.md +11 -9
- package/docs/safety.md +31 -5
- package/package.json +8 -4
- package/plugins/kimi-codex/.codex-plugin/plugin.json +7 -4
- package/plugins/kimi-codex/.mcp.json +28 -0
- package/plugins/kimi-codex/dist/cli-client.js +13 -3
- package/plugins/kimi-codex/dist/commands/ask.js +4 -2
- package/plugins/kimi-codex/dist/commands/cancel.js +43 -4
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +5 -1
- package/plugins/kimi-codex/dist/commands/pursue.js +3 -2
- package/plugins/kimi-codex/dist/commands/rescue.js +3 -2
- package/plugins/kimi-codex/dist/commands/review-gate.js +2 -4
- package/plugins/kimi-codex/dist/commands/review.js +4 -2
- package/plugins/kimi-codex/dist/commands/swarm.js +195 -37
- package/plugins/kimi-codex/dist/companion.js +13 -40
- package/plugins/kimi-codex/dist/help.js +2 -0
- package/plugins/kimi-codex/dist/hooks/config-safety.js +4 -3
- package/plugins/kimi-codex/dist/job-id.js +12 -0
- package/plugins/kimi-codex/dist/job-store.js +59 -0
- package/plugins/kimi-codex/dist/jobs.js +9 -1
- package/plugins/kimi-codex/dist/kimi-engine.js +1 -0
- package/plugins/kimi-codex/dist/mcp/binding.js +58 -0
- package/plugins/kimi-codex/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-codex/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-codex/dist/mcp/operations.js +301 -0
- package/plugins/kimi-codex/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-codex/dist/mcp/server.js +8 -0
- package/plugins/kimi-codex/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-codex/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-codex/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-codex/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-codex/dist/model-command.js +32 -0
- package/plugins/kimi-codex/dist/process-tree.js +48 -17
- package/plugins/kimi-codex/dist/swarm-activation.js +812 -0
- package/plugins/kimi-codex/dist/version.js +1 -1
package/dist/version.js
CHANGED
package/docs/migration.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Compatibility and upgrades
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plugin 2.4.1 certifies Kimi Code CLI 2.3.2 for all eight operations, alongside exact native-v2 versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, and `2.3.1`. Later patches and minor releases are not accepted automatically. The npm distribution adds an installation channel; it does not change this compatibility table.
|
|
4
4
|
|
|
5
5
|
Explicitly pinned legacy-v1 binaries remain supported within `KIMI_TESTED_MINORS`, through 0.41.x, subject to each operation's minimum version. The [runtime table](../runtime/kimi-engine.ts) is the authority.
|
|
6
6
|
|
|
@@ -8,7 +8,17 @@ Use the [README installation and update steps](../README.md) for Claude Code or
|
|
|
8
8
|
|
|
9
9
|
## Plugin versions and upgrades
|
|
10
10
|
|
|
11
|
-
Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.
|
|
11
|
+
Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.4.1 certifies exact CLI 2.3.2; the nearby numbers do not imply version alignment. Published versions and tags are never reused. Every upstream patch requires its own certification. The native-v2 no-plan profile, plugin/marketplace IDs and existing job store are preserved.
|
|
12
|
+
|
|
13
|
+
## CLI 2.3.2 swarm activation
|
|
14
|
+
|
|
15
|
+
Upstream 2.3.2 removes model self-activation of `AgentSwarm` in fresh print sessions. On that exact version only, swarm commands and MCP calls use the same `runSwarm` lifecycle:
|
|
16
|
+
|
|
17
|
+
1. A short-lived `web --no-open --host 127.0.0.1 --port 0 --log-level error` process uses authenticated loopback routes to verify the exact v2 server, choose the explicit or configured default model, create a session, bind the model and auto permission, enable and verify `swarm_mode`, and shut down.
|
|
18
|
+
2. One ordinary `kimi -p -r` prompt runner resumes that session. Its recorded session-id source is `preactivated`, and the returned print session must match it.
|
|
19
|
+
3. A second short-lived web lifecycle disables swarm mode, verifies status and the `swarm_mode.exit` journal record, and shuts down.
|
|
20
|
+
|
|
21
|
+
Activation is inside the job's finite budget. `runSwarm` solely owns deactivation: normal cleanup uses the remaining budget, while failure recovery has a separate two-second cancellation-aware bound and is never retried. Print/session failures stay primary. The ready token stays in memory and out of logs, and success cannot complete if cleanup fails. Post-mutation identity or process/group uncertainty preserves diagnostics and, for write-swarm, the recovery worktree. Process groups are rechecked after teardown; unknown survivors are unverified and unproven groups are not signalled. Older certified versions still launch swarm directly with `kimi -p`. This narrow lifecycle is not a full RPC transport or a model-driven activation workaround; the managed hook and workspace boundaries still govern every tool call.
|
|
12
22
|
|
|
13
23
|
After updating the plugin, run Claude Code `/kimi:setup` or Codex `$kimi-setup`
|
|
14
24
|
from that host's active install to re-pin its version-stamped hook path, then
|
|
@@ -6,8 +6,12 @@ remain available. The package has no install scripts and no separate CLI.
|
|
|
6
6
|
|
|
7
7
|
## Publishing status
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Plugin **2.4.1** certifies exact CLI **2.3.2** for all eight operations with host-preactivated swarm sessions. Its [certification evidence](./upstream-2.3.2-certification.md) includes the source scan, plan controls, hardened 15-test/115-assertion candidate matrix, and final 19-test/125-assertion production smoke in 296.67 seconds. Independent review is complete: Claude reports no blockers and Codex returned RESOLVED. The final repository gate passed 1,268 tests with zero failures and 16,055 assertions across 78 files, and package validation passed 224 files. CI, tagging, and registry verification are pending; the currently published release remains 2.4.0 until that closeout completes.
|
|
10
|
+
|
|
11
|
+
Plugin **2.4.0** preserves exact CLI **2.3.1** certification and adds the shared MCP transport. [Release v2.4.0](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.4.0) is anchored at `9232d1ec98465454d8d9328e439d3f31bfc53e04`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/40040084944) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/40040442610) passed. The registry tarball has SHA-1 `8243616529aa9f03f26bf4fef68c36d90e2304ae`, contains 222 files, and matches the CI artifact byte-for-byte. A fresh npm install passed isolated setup/check for both hosts.
|
|
12
|
+
The previous verified publication was **2.3.1**:
|
|
13
|
+
[Release v2.3.1](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.3.1) is anchored at `20011ff0554c611afe0a426b30a68c2473175568`; its [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/39977037533) and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/39977818482) passed. The registry tarball has SHA-1 `e309448480a2ce841b49840cdaeeed37acc6c50b`, contains 196 files, and matches the CI artifact byte-for-byte. See the [certification evidence](./upstream-2.3.1-certification.md).
|
|
14
|
+
Earlier verified publication was **2.2.2**:
|
|
11
15
|
[Release v2.2.2](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.2.2)
|
|
12
16
|
is anchored at `256e78ff7cbf302fb19e8e523d8e3f943f7cb366`; its
|
|
13
17
|
[CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/39755724024)
|
|
@@ -78,8 +82,9 @@ bun run check:package .tmp/npm
|
|
|
78
82
|
|
|
79
83
|
The package check inspects the actual tarball, verifies runtime copies and
|
|
80
84
|
vendored licenses, and installs/checks the safety hook in isolated homes for
|
|
81
|
-
both hosts. It also exercises unsupported-version refusal using a fake CLI
|
|
82
|
-
|
|
85
|
+
both hosts. It also exercises unsupported-version refusal using a fake CLI and
|
|
86
|
+
modern MCP discovery plus legacy initialization on both extracted compiled
|
|
87
|
+
distributions. It does not invoke a model or read the maintainer's Kimi configuration.
|
|
83
88
|
|
|
84
89
|
## First publication (completed bootstrap reference)
|
|
85
90
|
|
|
@@ -119,8 +124,5 @@ release. `.github/workflows/publish.yml` runs on `release.published`; it checks
|
|
|
119
124
|
the tag against `package.json`, runs the full gate and package smoke, then
|
|
120
125
|
publishes the validated tarball through OIDC with provenance. The publish job
|
|
121
126
|
has no source checkout or build scripts. A manual dispatch with an existing
|
|
122
|
-
release tag is available for
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
A locally bootstrapped first version is already published: do not dispatch it
|
|
126
|
-
again. The next release is the first OIDC publication.
|
|
127
|
+
release tag is available for recovery after a verified publication failure.
|
|
128
|
+
Check npm before retrying; released versions are immutable.
|
package/docs/safety.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This guide describes the current checkout runtime. Read-only tasks deny file writes and shell commands. Rescue and pursue can edit your workspace. Write-swarm returns a patch from a temporary Git worktree.
|
|
4
4
|
|
|
5
|
-
Native agent-core-v2 is certified at exact CLI versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, and `2.3.
|
|
5
|
+
Native agent-core-v2 is certified at exact CLI versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, `2.3.0`, `2.3.1`, and `2.3.2`, with plan mode disabled. The [runtime certification table](../runtime/kimi-engine.ts) controls routing. The hook is not an operating-system sandbox.
|
|
6
6
|
|
|
7
7
|
## Why the safety story is hook-based
|
|
8
8
|
|
|
@@ -205,12 +205,24 @@ If another installed host still points to an older hook, that hook may veto thes
|
|
|
205
205
|
- **Resume is intentionally not exposed yet.** In goal mode `goal.summary` emits a *goalId* distinct from the resume-hint *sessionId*; `kimi -r <sessionId>` re-enters the session but not necessarily the goal continuation. The plugin captures and surfaces the goalId but does not offer `--resume` for pursue until that split is reconciled upstream — exposing it now would be a silent-failure trap. **Re-verified against kimi-code 0.26.0 (2026-07-16):** the headless CLI still has no argv, prompt syntax, or session-resume path that calls `resumeGoal()`. `parseHeadlessGoalCreate` (`apps/kimi-code/src/cli/goal-prompt.ts`) accepts only the `/goal <objective>` *create* shape on both the v1 and experimental-v2 drivers, and upstream's own docs (`docs/en/guides/goals.md`, § "Use goal mode carefully") state that in non-interactive prompt mode *only goal creation is supported* — goal management is a TUI/kimi-web control. PR #552 makes a plain `-r <sessionId>` correctly *restore* the paused goal snapshot, but nothing in the headless path *acts* on it; the real `resumeGoal()` is wired only to the TUI (`/goal resume`) and kimi-web RPC (PR #1693, web-only). Implementing `--resume` would therefore require either prompt-injected model compliance (rejected — the plugin bounds autonomy with hard limits, not model goodwill) or abandoning the subprocess model for kimi-web's RPC. Re-check if upstream ever ships a headless `goal resume` verb.
|
|
206
206
|
- **A model-invocable `kimi-pursue` agent (v1.3) wraps this command.** The main thread can dispatch autonomous goal mode, not just the human-typed `/kimi:pursue` — the same command↔agent mirror as rescue/review/swarm. Auto-dispatch does NOT widen the blast radius: pursue runs under `commandLabel: "rescue"`, so the managed PreToolUse hook + workspace allowlist gate every tool call on every continuation turn (no git mutation), and the mandatory `--budget` wall-clock ceiling bounds the autonomy regardless of who launched it. Because it is write-capable AND autonomous — the plugin's highest blast radius — the agent's description requires an explicit user request for hands-off multi-turn pursuit, steers single bounded tasks to `kimi-rescue`, and never auto-promotes a fix or a question into a goal loop. Like the command, it refuses without the hook and offers no `--resume`.
|
|
207
207
|
|
|
208
|
+
## Swarm activation on CLI 2.3.2
|
|
209
|
+
|
|
210
|
+
Upstream 2.3.2 removes model self-activation of `AgentSwarm` in fresh print sessions. A rejected call can therefore coexist with zero child journals and a successful exit; a tool-call record, aggregate denial, or coordinator-produced patch does not prove fan-out. The plugin does not use prompt workarounds or replace its print transport with general RPC.
|
|
211
|
+
|
|
212
|
+
For versions requiring activation (currently exact 2.3.2), both slash-command and MCP swarm operations use the same `runSwarm` implementation and one ordinary `kimi -p -r` prompt runner per job, bracketed by short-lived host-managed web lifecycles:
|
|
213
|
+
|
|
214
|
+
1. The exact planned CLI starts `web --no-open --host 127.0.0.1 --port 0 --log-level error`. The plugin accepts only the expected loopback ready URL, uses a supplied token for authenticated REST requests, and keeps that token in memory, never writing it to logs. `/api/v1/meta` must confirm backend v2, the exact planned version, and no dangerous authentication bypass. The plugin uses the explicit model or environment-model alias, otherwise reading the default alias from `/api/v1/config`; creates a session in the job cwd; binds that model with `permission_mode:auto` and `swarm_mode:true`; verifies those settings and `plan_mode:false` through `/status`; verifies clean shutdown; and requires the main journal's manual `swarm_mode.enter` record.
|
|
215
|
+
2. The ordinary stream-json print runner resumes that session with `kimi -p -r`. The job records `kimi_session_id_source:"preactivated"`, and the session returned by print must exactly match the preactivated id. Every coordinator and child tool call remains governed by the same managed PreToolUse hook and operation allowlist.
|
|
216
|
+
3. After the print run, a second web lifecycle sets `swarm_mode:false`, verifies `/status`, shuts down cleanly, and requires `swarm_mode.exit` in the main journal. Activation consumes part of the same finite job budget rather than extending it. `runSwarm` solely owns session deactivation: normal cleanup uses the remaining budget, while failure recovery has a separate two-second cancellation-aware bound and is never retried. Print/session failures stay primary, with exit codes and cleanup diagnostics preserved. Cancellation aborts the owned lifecycle and never starts a fresh cleanup server. Every post-mutation failure retains the session identity; process/group uncertainty is marked without inventing an ID; process groups are rechecked after teardown, unknown survivors are unverified, unproven groups are never signalled, and a same-PID/start-token process that changes PGID remains active but is not a validated signal target. Root loss requires a previously observed matching identity; first-missing roots and actual probe errors/timeouts remain unverified. Reparenting and PGID moves preserve the current verified child identity for revalidation rather than treating it as exited. Unverified cleanup prevents success and preserves raw output, structured diagnostics, and the write worktree for recovery.
|
|
217
|
+
|
|
218
|
+
Older certified versions keep the direct `kimi -p` path. The web processes are narrow, planned lifecycle helpers, not a second model runner and not a general RPC transport.
|
|
219
|
+
|
|
208
220
|
## Read-only swarm (`/kimi:swarm`)
|
|
209
221
|
|
|
210
222
|
`/kimi:swarm` (v1.2) fans a **read-only** review out across subagents using kimi-code's `AgentSwarm` tool (kimi-code 0.12.0+). The coordinator launches one subagent per target (file/module/question); each inspects the workspace with read tools and reports findings, which the coordinator consolidates into one markdown report. It opens **zero new write surface**.
|
|
211
223
|
|
|
212
224
|
- **The hook label is `swarm`, and it allows the read-only tool set PLUS `AgentSwarm`.** The coordinator must be allowed to call `AgentSwarm` (else the swarm never launches), but no write/edit/shell tool is allowed. The exact allowed tool name is `AgentSwarm` (verified at 0.42.0 against `packages/agent-core-v2/src/features/swarm/tools/agent-swarm/agentSwarmTool.ts`: `'AgentSwarm'`, strict schema). The singular `Agent` tool is deliberately *not* allowed — swarm is the fan-out surface, not arbitrary delegation.
|
|
213
|
-
- **Every subagent inherits the `swarm` label and fires the same hook.** On native v2 (0.42.0) `AgentSwarm` children run in-process in a child DI scope with their OWN eager external-hooks service, so every child tool call passes the same before-execute channel and the same managed hook — there is no sub-vs-main branch and no deny-all shortcut on the swarm route. So a subagent's `Write`/`Edit`/`Bash` is denied exactly like a single-turn review's. The swarm listener in that channel is non-terminal and nothing auto-approves `AgentSwarm` ahead of the hook. (Legacy-v1 ≤ 0.41 reached the same result by placing the hook policy at index 0 of every agent's permission stack.) **
|
|
225
|
+
- **Every subagent inherits the `swarm` label and fires the same hook.** On native v2 (0.42.0) `AgentSwarm` children run in-process in a child DI scope with their OWN eager external-hooks service, so every child tool call passes the same before-execute channel and the same managed hook — there is no sub-vs-main branch and no deny-all shortcut on the swarm route. So a subagent's `Write`/`Edit`/`Bash` is denied exactly like a single-turn review's. The swarm listener in that channel is non-terminal and nothing auto-approves `AgentSwarm` ahead of the hook. (Legacy-v1 ≤ 0.41 reached the same result by placing the hook policy at index 0 of every agent's permission stack.) **Required for final certification evidence** (`tests/runtime/real-binary-smoke.test.ts`, run via `bun run smoke:real`): distinct child journals must exist, and each assigned Write/Edit `path` must be correlated through its `toolCallId` with the managed denial in that exact tool result; unrelated denials and coordinator-only reports are insufficient. On 2.3.2, targeted controls also prove that direct OFF produces no children while the preactivated ON path produces the real child journals and denials.
|
|
214
226
|
- **The managed hook is the read-only boundary; no `explore`-profile second layer is assumed.** Production deliberately omits `subagent_type` for read-only swarm rather than selecting kimi-code's `explore` profile. Every coordinator and subagent tool call is enforced by the same managed hook.
|
|
215
227
|
- **Swarm refuses without the hook, like every model-spawning command.** A swarm makes the reason especially acute: it asks for up to `--cap` subagents, but this total-count hint is advisory, so a missing hook would remove enforcement from all of them at once. `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` remains an explicit tests/diagnostics escape hatch, not a recovery path.
|
|
216
228
|
- **The only new risk is COST, and the plugin bounds it.** Read-only swarm writes nothing, so the new risk is runaway cost from N parallel model runs (and possible nested `AgentSwarm`). `/kimi:swarm` enforces a **mandatory finite wall-clock ceiling** via its AbortController (`--budget`, default 30m); the SIGTERM→SIGKILL descendant-reaping cancellation reaps the whole subagent process tree. Two **distinct** subagent bounds: `--cap N` is a *soft* prompt-injected hint on TOTAL subagent count (the hook is stateless and cannot enforce a count, so the model may exceed it), and `--max-concurrency N` is a *hard* ceiling on how many subagents kimi-code's `SubagentBatch` runs concurrently — exported as `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY` (PR #888) on **kimi-code 0.18.0+** (older binaries ignore the unknown env var, so on < 0.18 only the soft hint applies). It **defaults to 4** when the user passes none (`resolveSwarmMaxConcurrency` in swarm.ts), so every swarm run — agent-dispatched or human-typed — carries an enforced peak-parallelism ceiling on 0.18.0+ rather than falling back to kimi-code's internal ramp; pass an explicit value to widen or throttle. **Never merge them into one flag:** concurrency ≠ total count — a hard concurrency cap of N still permits more than N subagents over the life of the run, just never more than N at once. The `--budget` bounds elapsed time, not a dollar amount or total token count; `--max-concurrency` bounds *peak* parallelism (the spike in simultaneous model spend), and `--cap` is only an advisory total-count nudge. Folding both onto one flag would create the "cap-as-total-count illusion" — a user reading `--cap N` would expect a total bound but receive only a peak-parallelism one.
|
|
@@ -226,9 +238,23 @@ If another installed host still points to an older hook, that hook may veto thes
|
|
|
226
238
|
- **The plugin never touches git; the main thread owns the merge.** Subagents cannot run git (allowlist denies mutation). The plugin captures the change set with `git add -N` (intent-to-add, so new files appear in the diff WITHOUT writing loose objects into the shared object DB) + `git diff --binary` (untruncated, applyable), writes it to a `swarm-write-<jobId>.patch` artifact, and prints the path. It does **not** `git apply`, commit, or move any ref — the user/main thread reviews the patch and applies it (`git apply <path>`). The worktree shares the main repo's object DB and refs, but no ref/HEAD is ever moved, so the user's repository state is untouched.
|
|
227
239
|
- **The patch is captured on every terminal path, before cleanup.** Success, cancel, budget-expiry, and failure all capture the (possibly partial) patch BEFORE `git worktree remove --force` discards the tree, so a cancelled run never silently loses the work the subagents already did. If capture itself fails, the job fails instead of completing and the worktree is retained with its path in the error details for recovery. That guarantee covers the plugin's own terminal paths, including `companion.sh cancel`. It does **not** cover a harness interrupt: Esc/TaskStop delivers SIGTERM then SIGKILL after ~1.35s (measured, process-group-wide), which is shorter than the 1500ms child escalation plus quiescence plus patch capture — so a hard interrupt can kill the companion mid-teardown and lose the patch. `companion.sh cancel` signals from a separate process not racing that deadline and is the work-preserving stop. A startup sweep reaps worktrees orphaned by a hard kill (SIGKILL of the plugin process); its reap TTL is derived as `MAX_DURATION_MS + 2h` so it always exceeds the longest a run can live (the 24h `--budget` cap) and can never force-remove a still-live worktree out from under a concurrent run — the sweep's only liveness signal is the worktree dir mtime, which a run editing only existing files leaves frozen at `git worktree add` time.
|
|
228
240
|
- **Gated tightly.** Requires kimi-code **≥ 0.18.0** (below it `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY` is ignored, so a write fan-out would have no hard peak bound — unacceptable for concurrent writers); because that cap cannot be guaranteed without a confirmed version, the write-mode version gate **fails closed** on an unconfirmable `kimi --version` probe (the shared execution-plan gate also refuses unverified or uncertified binaries for read-only swarm; older certified legacy versions may lack the hard concurrency setting). Also requires a git repo with a **committed HEAD**, and the PreToolUse hook (**refuses** without it, like read swarm). Bases the worktree on HEAD, so a **loud warning** fires when the working tree is dirty (uncommitted work is not visible to the swarm). Default `--max-concurrency` is **1** (serialize) since disjoint-target partitioning is prompt-only and unenforceable by the stateless hook; the coordinator prompt also forbids git mutation and nested `AgentSwarm`.
|
|
229
|
-
- **
|
|
241
|
+
- **Required for final certification evidence.** `tests/runtime/real-binary-smoke.test.ts` runs the real `runSwarm --write` and requires the POSITIVE end-to-end: distinct child journals contain their assigned edits with `Write`/`Replaced` results on the same `toolCallId`, those child-authored changes land in the worktree and captured patch, the user's real tree is byte-identical + git-clean, and the worktree is cleaned up. A patch without per-call child provenance is not accepted as fan-out evidence. The NEGATIVE lane correlates each absolute out-of-root `path` and `toolCallId` with its managed denial. It is the check that catches `tool_input` field-name drift — `Write`/`Edit` carry `path`, not `file_path`, and reading the wrong key denies every real write silently.
|
|
230
242
|
- **Model-invocable agent (v1.5), patch-only by construction.** Following the field-proof above, `--write` now has a `kimi-swarm-write` subagent so the main thread can dispatch a write fan-out on its own judgement — the same human-command + model-invocable-agent split the rest of the suite uses. Auto-dispatch is a safe extension here because it widens **no** write surface and removes **no** bound: the agent path runs the identical `runSwarm --write` code, so edits still land only in the throwaway worktree, the managed hook still gates every `coder` subagent, the patch is still handed back for the user to apply (**the plugin never applies or commits**), and `--budget` + default `--max-concurrency` 1 + the hook requirement all still hold. In fact write-swarm is a *less* dangerous agent than the `kimi-pursue` agent already shipped: pursue writes directly into the workspace, whereas write-swarm's output is a patch the human reviews before anything reaches a real tree. Because it is still a write surface, the agent description demands BOTH signals — many disjoint write targets AND explicit fan-out intent — and steers single edits to `kimi-rescue`, read-only fan-outs to `kimi-swarm`, and autonomous loops to `kimi-pursue`. Default `--max-concurrency` stays **1**; raising it (concurrent writers) remains a deliberate, human-or-explicitly-requested choice.
|
|
231
243
|
|
|
244
|
+
## MCP transport and long-running jobs
|
|
245
|
+
|
|
246
|
+
The plugin also bundles an MCP stdio server for Claude Code and Codex. This is an additive transport, not a second execution engine or a way around the managed hook. The seven model tools run the same certified companion operations, exact-version checks, native-v2 preflight, workspace allowlists, finite budgets, and artifact rendering as the slash commands and skills. The review gate remains a separate Stop-hook feature.
|
|
247
|
+
|
|
248
|
+
Claude Code task-capable calls return durable MCP task IDs and use `tasks/get` and `tasks/cancel`. Codex currently consumes ordinary MCP tool results, not this task extension: after attachment, model tools return a job handle when the job is still running or its terminal result when already settled. `kimi_job_status`, `kimi_job_result`, and `kimi_job_cancel` provide polling and cancellation. Do not describe the Codex path as native MCP tasks.
|
|
249
|
+
|
|
250
|
+
Claude supplies its project directory as the default workspace. A configured host workspace is a containment boundary: an explicit workspace must be that directory or a descendant, not an arbitrary override. Codex calls normally supply an absolute `workspace` on the initial operation because its packaged stdio server runs from the plugin root; that explicit path is the operation's trust choice. Subsequent job control can use the persisted binding. Job controls manage only MCP-launched jobs, require the same canonical workspace and repository, and cannot inspect or cancel an arbitrary CLI job by ID.
|
|
251
|
+
|
|
252
|
+
Attachment waits for the operation to install cancellation handlers, with a 60-second bound. Verified teardown can close a stalled launch; missing identity or inconclusive termination evidence keeps it starting and recoverable by ID. `kimi_pid` is recorded at actual spawn rather than inferred from a later process-list match.
|
|
253
|
+
|
|
254
|
+
The MCP task/job ID is preassigned and bound to the job's real `operation_kind`, not merely its lineage. `mcp_tasks` stores launch and ownership metadata; the normal `jobs` table remains the outcome authority. Failed jobs, including budget timeouts, produce completed tasks with `isError:true` and the failed-job artifact, so partial output is not mistaken for a final answer. Supervisor and protocol failures are distinct from ordinary Kimi failures.
|
|
255
|
+
|
|
256
|
+
Closing an MCP connection does not cancel work. A later server reopens the same store and resumes status/result handling by ID. Generic stale sweeps skip MCP-owned rows, and direct CLI cancellation refuses them; use MCP job controls for those jobs. Companion and observed Kimi-descendant identities include start tokens and process groups; MCP cancellation and stale cleanup revalidate them rather than signalling a recycled PID. Cancellation remains cooperative and does not roll back edits. Pursue teardown and write-swarm patch capture still run through the existing companion path; write-swarm captures its patch on every terminal path before removing the worktree.
|
|
257
|
+
|
|
232
258
|
## Response budgets and partial output (2.2.0)
|
|
233
259
|
|
|
234
260
|
Every single-turn command runs under a wall-clock budget: ask, review and
|
|
@@ -265,9 +291,9 @@ at it (the runtime resolves that path instead of `kimi` on `PATH`; every spawn
|
|
|
265
291
|
still exports `KIMI_CODE_NO_AUTO_UPDATE=1`, so the pinned copy never drifts).
|
|
266
292
|
|
|
267
293
|
```sh
|
|
268
|
-
PIN=~/.local/share/kimi-plugin-cc/kimi-bin/2.2
|
|
294
|
+
PIN=~/.local/share/kimi-plugin-cc/kimi-bin/2.3.2
|
|
269
295
|
mkdir -p "$PIN" && cd "$PIN" && printf '{"name":"kimi-plugin-cc-pin","private":true}\n' > package.json
|
|
270
|
-
bun add @moonshot-ai/kimi-code@2.2
|
|
296
|
+
bun add @moonshot-ai/kimi-code@2.3.2
|
|
271
297
|
export KIMI_PLUGIN_CC_KIMI_BIN="$PIN/node_modules/.bin/kimi" # e.g. in ~/.zshenv
|
|
272
298
|
```
|
|
273
299
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi-plugin-cc",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Kimi for Claude Code and Codex: code questions, independent reviews, bounded fixes, autonomous goals,
|
|
3
|
+
"version": "2.4.1",
|
|
4
|
+
"description": "Kimi for Claude Code and Codex: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP tools through the local Kimi Code CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.5.0"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"build": "rm -rf dist && bunx tsc -p tsconfig.build.json && mkdir -p dist/vendor/shell-quote dist/vendor/smol-toml && cp runtime/vendor/shell-quote/LICENSE dist/vendor/shell-quote/LICENSE && cp runtime/vendor/smol-toml/*.js runtime/vendor/smol-toml/LICENSE dist/vendor/smol-toml/ && echo '{\"type\":\"module\"}' > dist/package.json",
|
|
14
14
|
"companion": "node --import tsx runtime/companion.ts",
|
|
15
15
|
"test": "bun test",
|
|
16
|
-
"smoke:real": "KIMI_PLUGIN_CC_SMOKE=1 bun test tests/runtime/real-binary-smoke.test.ts",
|
|
16
|
+
"smoke:real": "KIMI_PLUGIN_CC_SMOKE=1 bun test tests/runtime/real-binary-smoke.test.ts --path-ignore-patterns '**/worktrees/**'",
|
|
17
17
|
"check:package": "node scripts/check-package.mjs"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
@@ -38,14 +38,18 @@
|
|
|
38
38
|
"claude-code",
|
|
39
39
|
"codex",
|
|
40
40
|
"kimi",
|
|
41
|
+
"kimi-code",
|
|
41
42
|
"plugin",
|
|
42
|
-
"code-review"
|
|
43
|
+
"code-review",
|
|
44
|
+
"mcp",
|
|
45
|
+
"model-context-protocol"
|
|
43
46
|
],
|
|
44
47
|
"publishConfig": {
|
|
45
48
|
"access": "public",
|
|
46
49
|
"registry": "https://registry.npmjs.org/"
|
|
47
50
|
},
|
|
48
51
|
"files": [
|
|
52
|
+
".mcp.json",
|
|
49
53
|
".claude-plugin/",
|
|
50
54
|
".agents/plugins/marketplace.json",
|
|
51
55
|
"agents/",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals,
|
|
3
|
+
"version": "2.4.1",
|
|
4
|
+
"description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals, parallel tasks, and MCP job controls through the local Kimi Code CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "linxule"
|
|
7
7
|
},
|
|
@@ -14,13 +14,16 @@
|
|
|
14
14
|
"review",
|
|
15
15
|
"code-review",
|
|
16
16
|
"delegation",
|
|
17
|
-
"multi-model"
|
|
17
|
+
"multi-model",
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol"
|
|
18
20
|
],
|
|
19
21
|
"skills": "./skills/",
|
|
22
|
+
"mcpServers": "./.mcp.json",
|
|
20
23
|
"interface": {
|
|
21
24
|
"displayName": "Kimi",
|
|
22
25
|
"shortDescription": "Ask, review, and build with Kimi",
|
|
23
|
-
"longDescription": "Kimi Code workflows for Codex: read-only explanations and reviews, bounded workspace fixes, autonomous goals, and parallel tasks. Codex skills call a self-contained shell runtime
|
|
26
|
+
"longDescription": "Kimi Code workflows for Codex: read-only explanations and reviews, bounded workspace fixes, autonomous goals, and parallel tasks. Codex skills call a self-contained shell runtime, with MCP tools provided by the bundled server.",
|
|
24
27
|
"developerName": "linxule",
|
|
25
28
|
"category": "Developer Tools",
|
|
26
29
|
"capabilities": [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"kimi": {
|
|
4
|
+
"type": "stdio",
|
|
5
|
+
"command": "./scripts/companion.sh",
|
|
6
|
+
"args": [
|
|
7
|
+
"mcp"
|
|
8
|
+
],
|
|
9
|
+
"cwd": ".",
|
|
10
|
+
"env_vars": [
|
|
11
|
+
"KIMI_PLUGIN_CC_DATA",
|
|
12
|
+
"CLAUDE_PLUGIN_DATA",
|
|
13
|
+
"PLUGIN_DATA",
|
|
14
|
+
"KIMI_CODE_HOME",
|
|
15
|
+
"KIMI_PLUGIN_CC_KIMI_BIN",
|
|
16
|
+
"KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS",
|
|
17
|
+
"KIMI_PLUGIN_CC_NODE_BIN",
|
|
18
|
+
"KIMI_PLUGIN_CC_HOOK_SCRIPT",
|
|
19
|
+
"KIMI_PLUGIN_CC_HOST_ID",
|
|
20
|
+
"KIMI_MODEL_NAME",
|
|
21
|
+
"KIMI_MODEL_API_KEY",
|
|
22
|
+
"KIMI_MODEL_PROVIDER_TYPE",
|
|
23
|
+
"KIMI_MODEL_BASE_URL",
|
|
24
|
+
"KIMI_MODEL_THINKING_EFFORT"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -60,7 +60,7 @@ export async function runCliPrompt(opts) {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
const args = buildArgs(opts);
|
|
63
|
-
const env =
|
|
63
|
+
const env = buildKimiCliEnvironment(opts);
|
|
64
64
|
if (opts.logPath !== undefined) {
|
|
65
65
|
await mkdir(path.dirname(opts.logPath), { recursive: true }).catch(() => {
|
|
66
66
|
// Best-effort; if mkdir fails the appendFile below will error and be
|
|
@@ -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) });
|
|
@@ -778,7 +788,7 @@ function buildArgs(opts) {
|
|
|
778
788
|
args.push("-p", opts.prompt);
|
|
779
789
|
return args;
|
|
780
790
|
}
|
|
781
|
-
function
|
|
791
|
+
export function buildKimiCliEnvironment(opts) {
|
|
782
792
|
const env = { ...opts.env };
|
|
783
793
|
if (opts.thinkingEffort !== undefined)
|
|
784
794
|
env.KIMI_MODEL_THINKING_EFFORT = opts.thinkingEffort;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { companionHelp } from "../help.js";
|
|
2
|
-
import {
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 {
|
|
@@ -117,7 +117,11 @@ export async function captureTimeoutPartialOutput(classified, store, job, env, c
|
|
|
117
117
|
return undefined;
|
|
118
118
|
const result = timeout.partialResult;
|
|
119
119
|
let sessionId = result.sessionId;
|
|
120
|
-
let source = sessionId
|
|
120
|
+
let source = sessionId
|
|
121
|
+
? sessionId === job.kimi_session_id
|
|
122
|
+
? job.kimi_session_id_source
|
|
123
|
+
: "announced"
|
|
124
|
+
: null;
|
|
121
125
|
const details = classified instanceof RuntimeError ? classified.details : timeout.details;
|
|
122
126
|
details.kimi_session_id_source = job.kimi_session_id_source;
|
|
123
127
|
// The persisted plan is plugin-owned. Recovery never runs on cancellation,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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
|
|
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 {
|
|
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 =
|
|
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));
|