kimi-plugin-cc 2.2.1 → 2.3.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.
Files changed (91) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/CHANGELOG.md +22 -0
  4. package/README.md +5 -5
  5. package/agents/kimi-challenge.md +3 -3
  6. package/agents/kimi-pursue.md +1 -1
  7. package/agents/kimi-rescue.md +2 -2
  8. package/agents/kimi-review.md +3 -3
  9. package/commands/README.md +2 -2
  10. package/commands/challenge.md +1 -1
  11. package/commands/pursue.md +1 -1
  12. package/commands/review.md +1 -1
  13. package/dist/background-spawn.js +9 -17
  14. package/dist/cli-client.js +22 -62
  15. package/dist/commands/ask.js +24 -29
  16. package/dist/commands/cancel.js +7 -19
  17. package/dist/commands/cli-helpers.js +9 -17
  18. package/dist/commands/pursue.js +21 -31
  19. package/dist/commands/registry.js +6 -19
  20. package/dist/commands/rescue.js +20 -20
  21. package/dist/commands/review-gate.js +16 -24
  22. package/dist/commands/review.js +37 -43
  23. package/dist/commands/setup.js +85 -171
  24. package/dist/commands/swarm.js +103 -88
  25. package/dist/commands/worker.js +0 -6
  26. package/dist/git.js +14 -7
  27. package/dist/hooks/approval-hook.js +9 -20
  28. package/dist/hooks/approval-policy.js +36 -69
  29. package/dist/hooks/config-safety.js +7 -4
  30. package/dist/hooks/install-paths.js +62 -138
  31. package/dist/hooks/install.js +37 -108
  32. package/dist/hooks/managed-block.js +88 -214
  33. package/dist/job-store.js +6 -28
  34. package/dist/kimi-command.js +1 -3
  35. package/dist/kimi-engine.js +2 -1
  36. package/dist/kimi-timeouts.js +9 -21
  37. package/dist/kimi-version-probe.js +16 -1210
  38. package/dist/native-v2-preflight.js +1 -1
  39. package/dist/parsing.js +4 -18
  40. package/dist/rescue-approval.js +78 -164
  41. package/dist/session-recovery.js +7 -10
  42. package/dist/stream-json.js +22 -174
  43. package/dist/version.js +1 -1
  44. package/docs/migration.md +2 -6
  45. package/docs/registry-releases.md +25 -5
  46. package/docs/safety.md +16 -12
  47. package/package.json +2 -2
  48. package/plugins/kimi-codex/.codex-plugin/plugin.json +4 -4
  49. package/plugins/kimi-codex/dist/background-spawn.js +9 -17
  50. package/plugins/kimi-codex/dist/cli-client.js +22 -62
  51. package/plugins/kimi-codex/dist/commands/ask.js +24 -29
  52. package/plugins/kimi-codex/dist/commands/cancel.js +7 -19
  53. package/plugins/kimi-codex/dist/commands/cli-helpers.js +9 -17
  54. package/plugins/kimi-codex/dist/commands/pursue.js +21 -31
  55. package/plugins/kimi-codex/dist/commands/registry.js +6 -19
  56. package/plugins/kimi-codex/dist/commands/rescue.js +20 -20
  57. package/plugins/kimi-codex/dist/commands/review-gate.js +16 -24
  58. package/plugins/kimi-codex/dist/commands/review.js +37 -43
  59. package/plugins/kimi-codex/dist/commands/setup.js +85 -171
  60. package/plugins/kimi-codex/dist/commands/swarm.js +103 -88
  61. package/plugins/kimi-codex/dist/commands/worker.js +0 -6
  62. package/plugins/kimi-codex/dist/git.js +14 -7
  63. package/plugins/kimi-codex/dist/hooks/approval-hook.js +9 -20
  64. package/plugins/kimi-codex/dist/hooks/approval-policy.js +36 -69
  65. package/plugins/kimi-codex/dist/hooks/config-safety.js +7 -4
  66. package/plugins/kimi-codex/dist/hooks/install-paths.js +62 -138
  67. package/plugins/kimi-codex/dist/hooks/install.js +37 -108
  68. package/plugins/kimi-codex/dist/hooks/managed-block.js +88 -214
  69. package/plugins/kimi-codex/dist/job-store.js +6 -28
  70. package/plugins/kimi-codex/dist/kimi-command.js +1 -3
  71. package/plugins/kimi-codex/dist/kimi-engine.js +2 -1
  72. package/plugins/kimi-codex/dist/kimi-timeouts.js +9 -21
  73. package/plugins/kimi-codex/dist/kimi-version-probe.js +16 -1210
  74. package/plugins/kimi-codex/dist/native-v2-preflight.js +1 -1
  75. package/plugins/kimi-codex/dist/parsing.js +4 -18
  76. package/plugins/kimi-codex/dist/rescue-approval.js +78 -164
  77. package/plugins/kimi-codex/dist/session-recovery.js +7 -10
  78. package/plugins/kimi-codex/dist/stream-json.js +22 -174
  79. package/plugins/kimi-codex/dist/version.js +1 -1
  80. package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +2 -2
  81. package/plugins/kimi-codex/skills/kimi-cancel/SKILL.md +1 -1
  82. package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +1 -1
  83. package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +1 -1
  84. package/plugins/kimi-codex/skills/kimi-replay/SKILL.md +1 -1
  85. package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +1 -1
  86. package/plugins/kimi-codex/skills/kimi-result/SKILL.md +1 -1
  87. package/plugins/kimi-codex/skills/kimi-review/SKILL.md +3 -3
  88. package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +1 -1
  89. package/plugins/kimi-codex/skills/kimi-status/SKILL.md +1 -1
  90. package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +1 -1
  91. package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +1 -1
@@ -10,162 +10,13 @@
10
10
  // {"role":"tool","tool_call_id":"...","content":"..."}
11
11
  // {"role":"meta","type":"session.resume_hint","session_id":"session_<uuid>","command":"kimi -r session_<uuid>","content":"To resume this session: kimi -r session_<uuid>"}
12
12
  // {"role":"meta","type":"turn.step.retrying","failed_attempt":1,"next_attempt":2,"max_attempts":3,"delay_ms":300,"error_name":"APIProviderRateLimitError","error_message":"...","status_code":429}
13
- // {"role":"meta","type":"system.version","version":"0.24.2"} // experimental v2 only; modeled since v1.8.5 (SystemVersionRecord), filtered like other meta
13
+ // {"role":"meta","type":"system.version","version":"0.24.2"} // native-v2 marker; required first on native-v2 plans and filtered after capture
14
+ //
15
+ // {"type":"error","code":"session.occupied","message":"...","details":{...}} // 2.3.0; stdout-only refusal, exit 4
14
16
  //
15
17
  // Notes on what does and does not appear here:
16
- // - The session.resume_hint meta record is NEW in kimi-code 0.2.0
17
- // (introduced at run-prompt.ts:477-505 in 0.2.0; the writer region —
18
- // `PromptJsonResumeMetaMessage` / `writeResumeHint` / `PromptJsonWriter` —
19
- // sits at apps/kimi-code/src/cli/run-prompt.ts:567-696 as of 0.9.0 and is
20
- // verified compatible through 0.24.2. 2026-05-27 audit covered 0.4.0,
21
- // 2026-05-28 audit covered 0.5.0 (run-prompt.ts zero-byte diff across
22
- // both), 2026-05-31 audit covered 0.6.0, 2026-06-03 audit covered
23
- // 0.7.0/0.8.0/0.9.0 in one cumulative pass, 2026-06-09 audit covered
24
- // 0.10.0/0.11.0/0.12.0 in one cumulative 0.9.0→0.12.0 pass, 2026-06-12 audit
25
- // covered 0.12.1/0.13.0/0.13.1/0.14.0/0.14.1 in one cumulative 0.12.0→0.14.1
26
- // pass (records/ dir empty diff; the 0.13/0.14 packages/protocol/ REST+WS
27
- // control API is a separate transport — run-prompt.ts imports none of it,
28
- // `-p` stdout stays the direct PromptJsonWriter), 2026-06-13 audit covered
29
- // the 0.14.2 patch (records/ + the PromptJsonWriter/writeResumeHint region
30
- // both 0-byte; the patch is a repo-wide `.md`→`.md?raw` bundler-import
31
- // migration plus a Bash-tool stdout/stderr streaming callback, neither
32
- // touching the writer), and 2026-06-14 covered the 0.14.3 patch (all four
33
- // scoped diffs 0-byte; the entire patch is a TUI model-picker
34
- // provider-refresh change — PR #713 — off the `-p` path), and 2026-06-16
35
- // covered the 0.15.0 minor (permission/hooks/run-prompt/cli all 0-byte by
36
- // independent `git diff` byte-count; the only scoped change is records/ +
37
- // session/ persistence churn — PR #786 drops app_version/resumed from the
38
- // .records/ metadata artifact, a SessionSkillRegistry rename, a static
39
- // model-capability lookup — none of it on the `-p` stdout stream; plus an
40
- // additive `transport:'sse'` MCP config variant the plugin never writes),
41
- // and 2026-06-17 covered the 0.16.0 minor (permission/hooks/run-prompt all
42
- // 0-byte; the only argv change registers a new `kimi vis` subcommand off
43
- // the `-p` path, and the records/replay/compaction/logging churn is
44
- // internal — not on the stdout stream; GREEN smoke 7/0 on the 0.16.0
45
- // binary), 2026-06-19 covered the 0.16.0→0.18.0 jump (0.17.0/0.17.1/0.18.0;
46
- // 03-hooks.diff 0-byte, run-prompt.ts only a telemetry refactor outside the
47
- // writer, the sole permission change a NON-auto-mode GoalStartReviewAsk that
48
- // is dead on `-p`), and 2026-06-23 covered the 0.18.0→0.19.1 minor (the
49
- // writer + records/ 0-byte; the bumping feature #812 `--add-dir` touches
50
- // only an approve policy at the queue tail, structurally dead below the
51
- // `-p` auto-mode approve and still pre-empted by our index-0 hook even when
52
- // project-local `.kimi-code/local.toml` makes additionalDirs non-empty;
53
- // #963's new `turn.ended` reason:'filtered' is a failure REASON not a new
54
- // stdout record SHAPE;
55
- // GREEN smoke 9/0 on the operator's 0.19.1 binary —
56
- // .claude/kimi-code-research/reports/85-upstream-0191-surface.md), and
57
- // 2026-06-26 covered the 0.19.1→0.20.0 minor (run-prompt.ts + options.ts +
58
- // records/ all 0-byte; #1040's new `warning` agent event is swallowed on `-p`
59
- // at run-prompt.ts:495 `case 'warning': return;`, so it never reaches this
60
- // parser; #1062's tool-result budget adds a `truncated` flag to tool-result
61
- // CONTENT, not the record SHAPE; the new runShellCommand RPC + kimi server/web
62
- // stack are off the `-p` path — report 88. Smoke NOT run this cert: the 0.20.0
63
- // `bun run smoke:real` was quota-blocked, 403 usage-limit, records:[] — an
64
- // operator-billing false alarm, not a compat break; certified on source audit;
65
- // the later 0.20.2 smoke ran GREEN 9/0 on 2026-06-29), and 2026-07-01
66
- // covered the 0.20.3→0.21.1 minor+patch (report 93; agent/records/ 0-byte,
67
- // run-prompt.ts writer/resume_hint/goal.summary unchanged, #1204 plugin
68
- // slash commands off the `-p` path, #1233 cleanup timeout after the writer
69
- // flush; GREEN smoke 9/0 on the operator's 0.21.1 binary), and 2026-07-05
70
- // covered the 0.21.1→0.22.3 minor+patch (reports 94/95; hook engine
71
- // unchanged, policy order still hook-first, writer shape compatible; 0.22.3's
72
- // prompt-mode background-drain happens after assistant-output flush and is
73
- // bounded by plugin budgets; shell-output caps and session-owned
74
- // media-originals do not alter the NDJSON record schema; GREEN smoke 9/0 on
75
- // a temp-installed 0.22.3 binary), and 2026-07-07 covered the
76
- // 0.22.3→0.23.1 minor (report 96; hook engine unchanged, policy order still
77
- // hook-first, PromptJsonWriter-compatible scoped `run-prompt.ts` delta only
78
- // adds telemetry sessionId; `select_tools` is denied by our hook unless
79
- // deliberately allowlisted; observability records are persisted agent-log
80
- // records, not new stdout NDJSON shapes; GREEN smoke 9/0 on a temp-installed
81
- // 0.23.1 binary), and 2026-07-08 covered the 0.23.2 patch (daily checkup;
82
- // no writer/resume_hint/goal.summary drift, `02-permission.diff` and
83
- // `05-session-bootstrap.diff` 0-byte; the only scoped `run-prompt.ts`
84
- // change keeps the cleanup timeout ref'd so failed headless `-p` turns exit
85
- // non-zero reliably, and the only hook-engine-adjacent change adds
86
- // `windowsHide:true` to hook child-process spawn options on Windows; GREEN
87
- // smoke 9/0 on a temp-installed 0.23.2 binary), and 2026-07-09 covered the
88
- // 0.23.3/0.23.4 patches (daily checkup; permission + both hook-tree diffs
89
- // 0-byte; #1516 keeps prompt-mode goals alive across continuation turns
90
- // until terminal without changing PromptJsonWriter, session.resume_hint,
91
- // or goal.summary shapes; the wire/session delta is internal provider-auth
92
- // and media-projection plumbing, while the bootstrap delta adds `[image]`
93
- // limits without changing hook or permission construction; GREEN smoke 9/0
94
- // on a temp-installed 0.23.4 binary, including two-minute multi-turn goal
95
- // confinement and both write-swarm worktree-boundary assertions), and
96
- // 2026-07-11 covered the 0.23.5 patch at tag commit `352a4492`
97
- // (permission, both hook trees, and session bootstrap all 0-byte; the wire
98
- // diff only adds the internal `media-stripped` projection type). The one
99
- // live stdout change is this file's `turn.step.retrying` meta record: it is
100
- // modeled exactly and filtered from consumer records while malformed shapes
101
- // remain diagnostic. The post-change exact-binary smoke was GREEN 9/0 in
102
- // 229.56s, including the full goal budget and both write-swarm boundaries),
103
- // and 2026-07-12 covered the 0.23.6 patch at tag commit `b5c236d0`
104
- // (permission and both hook trees 0-byte; no new PromptJsonWriter record;
105
- // `run-prompt.ts` now keeps print mode alive across active goals, pending
106
- // cron fires, and opt-in background steering, all still bounded externally
107
- // by plugin budgets; subagent timeout config and the capability rename to
108
- // dynamically_loaded_tools do not alter stdout shape). Exact-binary smoke
109
- // was GREEN 9/0 in 274.55s, including the full goal budget and both
110
- // write-swarm boundaries), and 2026-07-15 covered the 0.23.6→0.24.2
111
- // minor (Phase-1 reports 97-100; permission and the live session/hooks
112
- // surfaces 0-byte; default-v1 writer/resume/retry/goal shapes compatible;
113
- // GREEN exact-binary v1 smoke 9/0, 39 assertions in 261.33s). A truthy
114
- // native v2 shares the writer shapes and terminal session.resume_hint but
115
- // prepends one new
116
- // role:"meta", type:"system.version" record. Unknown meta is intentionally
117
- // non-fatal: it becomes diagnostic-only, never enters records[]/onRecord,
118
- // and cannot affect prose or session pinning. A targeted exact-v2 review
119
- // smoke denied the forced write (1 pass / 8 skip / 0 fail).
120
- // 2026-07-16 covered 0.25.0 + 0.26.0 (the 0.25.0 step added only 943 B
121
- // of permission telemetry; all three primary safety surfaces were 0-byte
122
- // on the 0.26.0 step; no new PromptJsonWriter record on either),
123
- // 2026-07-18 covered 0.27.0, 2026-07-21 covered 0.28.1,
124
- // 2026-07-23/24 covered 0.29.0 + 0.29.1, and 2026-07-27 covered 0.29.2
125
- // (permission/hooks stayed 0-byte across every step; the 0.29.x
126
- // wire/bootstrap changes did not alter PromptJsonWriter output; GREEN
127
- // exact-binary smoke 10/0, 43 assertions, on each of exact 0.27.0,
128
- // 0.28.1, 0.29.0, 0.29.1, and 0.29.2; the 0.29.1 veto-event refactor
129
- // and 0.29.2 automatic scope-activation refactor preserved the modeled
130
- // system.version signal and terminal session pinning), 2026-07-29 covered
131
- // 0.30.0, 2026-07-31 covered 0.31.0, 2026-08-01 covered 0.31.1, and
132
- // 2026-08-08 covered 0.32.0 through 0.34.0, 2026-08-12 covered 0.35.0,
133
- // 2026-08-13 covered 0.36.0, 2026-08-24 covered 0.37.0 through
134
- // 0.38.0, 2026-08-27 covered 0.39.0 (prompt-render.ts byte-identical;
135
- // the MCP structured-content and error-code changes shift only prose
136
- // inside opaque tool content strings), and 2026-09-02 covered 0.40.0
137
- // (wire/session scope 0 bytes; run-prompt.ts absent from the diff). The
138
- // 2026-09-06 covered 0.41.0 (v1 core, prompt-render.ts, goal-prompt.ts,
139
- // and prompt-session.ts unchanged; exact-binary smoke 12/0, 55 assertions).
140
- // The legacy-v1 writer remains compatible through 0.41.0;
141
- // custom profiles can change content/tools but
142
- // not the NDJSON envelope. In 0.33.0 native v2 became the unflagged default,
143
- // so v1.9.6 forces KIMI_CODE_LEGACY_FLAG=1 in every accepted child while
144
- // retaining the old experimental-selector refusal. This parser continues
145
- // to model v2's system.version record for diagnostics and a future safe
146
- // re-enable; absence of that record is now a legacy-pin smoke assertion.
147
- // NB: from 0.6.0 run-prompt.ts
148
- // is no longer a whole-file zero-byte diff — at 0.6.0 it gained a
149
- // resume-session workDir guard, and at 0.8.0 it gained headless goal
150
- // mode (`runHeadlessGoal`) — but both changes are OUTSIDE the
151
- // stream-json writer, which every audit confirmed byte-identical by
152
- // content/blob SHA. Goal mode also writes a `{"type":"goal.summary",...}`
153
- // line (no `role` field) before the resume hint when the prompt is
154
- // `/goal`-prefixed (on 0.8–0.11 the `goal-command` experimental flag must
155
- // also be on; 0.12.0 removed that gate — PR #569 — so the `/goal` prefix
156
- // alone triggers it). Read-only commands never trigger it (their trimmed
157
- // prompt never starts with `/goal`); the v1.1 /kimi:pursue command is
158
- // the intentional consumer — it is recognized as a first-class record on
159
- // the dedicated `StreamJsonOutcome.goalSummary` channel (see
160
- // GoalSummaryRecord below), NOT routed to the malformed channel. See
161
- // reports 47-65, 72-76).
162
- // The hint is emitted once per prompt run at session END
163
- // (after runPromptTurn settles), not at session start. In 0.1.x the
164
- // resume hint went to stderr only; 0.2.0+ emits a structured
165
- // stream-json record on stdout when `--output-format stream-json` is
166
- // selected (text-output mode still writes the stderr line). Our
167
- // cli-client consumes the meta record's `session_id` for resume/replay
168
- // routing.
18
+ // - session.resume_hint is emitted once at session end. Legacy 0.1.x sent
19
+ // the resume hint to stderr; stream-json uses this stdout meta record.
169
20
  // - hook.result events arrive as role:"assistant" with the block rendered as
170
21
  // plain text (see formatHookResultPlain in run-prompt.ts). Our
171
22
  // PreToolUse hook's deny reason therefore surfaces in the assistant
@@ -183,10 +34,8 @@ export const MAX_STREAM_JSON_LINE_BYTES = 1_048_576;
183
34
  * Lifecycle:
184
35
  * - `push(chunk)` — consume bytes; returns outcomes for any complete lines
185
36
  * that finished within this chunk.
186
- * - `flush()` — at end of stream, force-parse the trailing buffer if any.
187
- * Stream-json emits a newline after every record (run-prompt.ts:573), so
188
- * a non-empty flush remainder indicates an unfinished record (likely an
189
- * interrupted spawn).
37
+ * - `flush()` — at end of stream, parse a valid trailing JSON record even
38
+ * without a final newline; malformed trailing input becomes diagnostic.
190
39
  *
191
40
  * Outcomes are non-throwing — malformed lines surface via `malformedLine` /
192
41
  * `malformedReason` so callers can route them to a diagnostics log without
@@ -223,8 +72,6 @@ export class StreamJsonParser {
223
72
  while (newlineIndex !== -1) {
224
73
  const line = this.buffer.slice(0, newlineIndex);
225
74
  this.buffer = this.buffer.slice(newlineIndex + 1);
226
- // Skip blank lines (the writer doesn't emit them, but tolerate
227
- // CRLF-doubled or trailing whitespace defensively).
228
75
  if (line.replace(/\r/g, "").length > 0) {
229
76
  out.push(parseLine(line));
230
77
  }
@@ -258,21 +105,26 @@ function parseLine(line) {
258
105
  if (role === "meta") {
259
106
  return validateMeta(parsed, line);
260
107
  }
261
- // Role-less goal-mode summary (kimi-code 0.8.0+). Keyed by `type`, not
262
- // `role`, so it's surfaced on the dedicated goalSummary outcome channel.
263
108
  if (role === undefined && parsed["type"] === "goal.summary") {
264
109
  return validateGoalSummary(parsed, line);
265
110
  }
266
- // H3 forward-compat: a non-empty string role we don't model (a role a future
267
- // kimi-code adds). Surface it out-of-band on the unknownRecord channel —
268
- // tolerated, not malformed — so the parser keeps working and the diagnostic
269
- // log distinguishes "new upstream role" from a genuinely broken line. It
270
- // never enters records[] (see UnknownRoleRecord + the consumer sweep).
111
+ if (role === undefined && parsed["type"] === "error") {
112
+ if (typeof parsed["code"] !== "string" || parsed["code"].length === 0 ||
113
+ typeof parsed["message"] !== "string" || parsed["message"].length === 0 ||
114
+ (parsed["details"] !== undefined && !isRecord(parsed["details"]))) {
115
+ return { malformedLine: line, malformedReason: "error record field has unexpected type" };
116
+ }
117
+ // Keep bounded diagnostic fields only; holder metadata is not needed and
118
+ // must not become a session-id source or be copied into persisted errors.
119
+ return { cliError: {
120
+ type: "error",
121
+ code: parsed["code"].slice(0, 200),
122
+ message: parsed["message"].slice(0, 4000),
123
+ } };
124
+ }
271
125
  if (typeof role === "string" && role.length > 0) {
272
126
  return { unknownRecord: { role, raw: parsed } };
273
127
  }
274
- // Genuinely malformed: no `role` at all (and not a recognized role-less typed
275
- // record like goal.summary), or a non-string role. Surface as malformed.
276
128
  return {
277
129
  malformedLine: line,
278
130
  malformedReason: `unknown role: ${JSON.stringify(role)}`,
@@ -384,9 +236,7 @@ function validateMeta(parsed, line) {
384
236
  };
385
237
  return { record };
386
238
  }
387
- // Unknown meta types are forward-compatible — surface them via the
388
- // malformed channel for diagnostics, but don't crash. kimi-code may add
389
- // new meta types in 0.3.x and our wrapper should keep working.
239
+ // Unknown meta types are diagnostic-only and nonfatal.
390
240
  return {
391
241
  malformedLine: line,
392
242
  malformedReason: `unknown meta.type: ${JSON.stringify(type)}`,
@@ -487,8 +337,6 @@ export function extractSessionIdFromStderr(stderr) {
487
337
  // hostile stderr line could pin `session_--------` (8 dashes) or any
488
338
  // 8+ char hex-dash token as the captured session id, weakening the
489
339
  // "anchored full-UUID regex" safety invariant documented in AGENTS.md.
490
- // Review-smoke (kimi 0.2.0 on the alpha.5 candidate) flagged the loose
491
- // form; this is the post-review tightening.
492
340
  const uuid = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
493
341
  const pattern = new RegExp(`^To resume this session:\\s+kimi\\s+-r\\s+(session_${uuid}|${uuid})\\s*$`, "im");
494
342
  return stderr.match(pattern)?.[1];
package/dist/version.js CHANGED
@@ -4,4 +4,4 @@
4
4
  // and AGENTS.md on every release. A future improvement would read this from
5
5
  // package.json at build time, but that adds a build-step dependency we
6
6
  // don't want yet.
7
- export const KIMI_PLUGIN_CC_VERSION = "2.2.1";
7
+ export const KIMI_PLUGIN_CC_VERSION = "2.3.1";
package/docs/migration.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Compatibility and upgrades
2
2
 
3
- For plugin 2.0.7, use Kimi Code CLI 2.1.1. The plugin certifies 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`, and `2.1.1` for all eight operations. It does not automatically accept later patches or minor releases. The npm distribution adds an installation channel; it does not change this compatibility table.
3
+ For plugin 2.3.1, use Kimi Code CLI 2.3.1. The plugin certifies 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` for all eight operations. It does not automatically accept later patches or minor releases. 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,11 +8,7 @@ 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. Plugin 2.0.7 retains
12
- the exact CLI certifications from 2.0.6; the runtime table records that mapping. Each compatible
13
- maintenance or certification update takes the next unused plugin patch. Published
14
- versions and tags are never reused. Every upstream patch requires its own certification.
15
- The native-v2 no-plan profile, plugin/marketplace IDs and existing job store are preserved.
11
+ Plugin versions advance independently using ordinary SemVer and may deliberately align with a certified CLI release. Plugin 2.3.1 certifies CLI 2.3.1; the runtime table records that mapping. 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.
16
12
 
17
13
  After updating the plugin, run Claude Code `/kimi:setup` or Codex `$kimi-setup`
18
14
  from that host's active install to re-pin its version-stamped hook path, then
@@ -6,10 +6,30 @@ remain available. The package has no install scripts and no separate CLI.
6
6
 
7
7
  ## Publishing status
8
8
 
9
- As of October 15, 2026, [2.2.0 is live on npm](https://www.npmjs.com/package/kimi-plugin-cc/v/2.2.0), published by
10
- the release-event run of `publish.yml` (run 38658918855) through GitHub Actions OIDC with a signed
11
- provenance statement (sigstore log index 3256863983) — the first release where the tag-triggered
12
- path worked end to end without a manual dispatch. 2.0.8 (same day, run 38650044033) was the first
9
+ The current release candidate is plugin **2.3.1**, certifying exact CLI **2.3.1**; publication is pending final repository and CI gates. See the [certification evidence](./upstream-2.3.1-certification.md).
10
+ The previous verified publication was **2.2.2**:
11
+ [Release v2.2.2](https://github.com/linxule/kimi-plugin-cc/releases/tag/v2.2.2)
12
+ is anchored at `256e78ff7cbf302fb19e8e523d8e3f943f7cb366`; its
13
+ [CI check](https://github.com/linxule/kimi-plugin-cc/actions/runs/39755724024)
14
+ and [release-event publishing run](https://github.com/linxule/kimi-plugin-cc/actions/runs/39755982507)
15
+ passed. npm accepted the OIDC publication with signed provenance (sigstore log
16
+ index `3383319298`) and tarball shasum
17
+ `31aa9752bf05dc4e9c818d5eef5f99e604968613`. The registry tarball matches the
18
+ validated Actions artifact byte-for-byte. A fresh npm install passed isolated
19
+ setup/check for both hosts, and both runtime mirrors match the release checkout.
20
+ Metadata and tarball availability propagated separately; the tarball endpoint
21
+ briefly served a cached 404 (300-second TTL). No publication retry was needed.
22
+ Publication does not update installed
23
+ hosts, re-pin their hooks, or change the operator's CLI binary.
24
+
25
+ ### Previous publications
26
+
27
+ As of October 15, 2026, [2.2.1 is live on npm](https://www.npmjs.com/package/kimi-plugin-cc/v/2.2.1), published by
28
+ the release-event run of `publish.yml` (run 38720071707) through GitHub Actions OIDC with a signed
29
+ provenance statement (sigstore log index 3263398504; tarball shasum
30
+ `03907c66d440155763a860ab52b5107c0233938b`, registry propagation took a few minutes this time).
31
+ 2.2.0 (same day, run 38658918855, log index 3256863983) was the first release where the tag-triggered
32
+ path worked end to end without a manual dispatch; 2.0.8 (same day, run 38650044033) was the first
13
33
  OIDC publication; 2.0.7 (September 26) was the hand-published bootstrap.
14
34
  The first upload used the validated GitHub Actions artifact and interactive npm
15
35
  authentication. Its public tarball matched that artifact byte-for-byte, and a
@@ -19,7 +39,7 @@ The trusted publisher is configured for `linxule/kimi-plugin-cc`, workflow
19
39
  `publish.yml`, environment `npm`, with direct publishing allowed.
20
40
  `NPM_TRUSTED_PUBLISHING=true` is enabled in GitHub Actions. Future GitHub releases
21
41
  use short-lived OIDC credentials, without a stored npm token or interactive 2FA.
22
- The first npm OIDC publication will be the next release; do not republish 2.0.7.
42
+ OIDC publication is established; never republish an existing version.
23
43
 
24
44
  **2026-10-15 status.** The `v2.0.8` release triggered the first OIDC attempt. It
25
45
  failed twice on the publish step for reasons unrelated to the package: current
package/docs/safety.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Safety
2
2
 
3
- This guide describes the shipped 2.2.0 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.
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`, and `2.1.1`, with plan mode disabled. The [runtime certification table](../runtime/kimi-engine.ts) controls routing. The hook is not an operating-system sandbox.
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.1`, 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
 
@@ -156,6 +156,10 @@ Provenance: a native-v2 plan requires the `system.version` marker as the first s
156
156
 
157
157
  Residuals, unchanged from v1 and stated plainly: `config.toml` and the session journals are trusted operator state (an actor who can write them could already delete the `[[hooks]]` entry); the upstream hook runner fails open on its own internal errors; the plan-path comparison does not realpath (a symlink there would need a prior hook-allowed write). Native plan mode, tower, subagent fork, Remote Control and `--add-dir` are out of scope.
158
158
 
159
+ In CLI 2.3.0 and later, stale-session retry can reread those same journals. Do not concurrently operate on or edit a plugin-managed session from another client. The upstream session lock is not a security boundary; no parent-side rescan can make that reload atomic.
160
+
161
+ CLI 2.3.1 adds `KIMI_CODE_SESSION_LOCK=0` and explicit TUI/SDK/REST lock-takeover paths, which can disable or break mutual exclusion. Plugin children inherit an ambient lock-disable setting; print mode itself reports occupancy and does not take over. The prohibition on concurrent clients still applies. During POSIX lock contention, upstream can also execute bare `ps` with the child's inherited PATH and cwd outside PreToolUse; a PATH containing the workspace is therefore part of the internal-helper trust boundary. Legacy workspace-trust migration recognizes more old path spellings and can enable project MCP for those already trusted roots. Trust-store read failure can suppress the initial MCP load, an availability/diagnostics change rather than a trust fail-open.
162
+
159
163
  ## The rescue allowlist
160
164
 
161
165
  For `/kimi:rescue`, the hook delegates to [`evaluateRescueHookRequest`](../runtime/rescue-approval.ts). The allowlist has been extended since the original Wire-transport implementation. Highlights:
@@ -167,7 +171,7 @@ For `/kimi:rescue`, the hook delegates to [`evaluateRescueHookRequest`](../runti
167
171
  - **Exec-delegating flags**: every shell command is rejected if its argv contains a flag whose *value* is run as a command or external tool — `--open-files-in-pager` (`git grep --open-files-in-pager=touch /tmp/x needle` makes git execute `touch`; same class as the pre-subcommand `git -c core.pager=` smuggle, just via a subcommand flag — also the `-O` short form under `git`), `-vettool` / `-toolexec` / `-exec` (`go vet -vettool=<bin>`, `go test -exec <bin>`). These were the CRITICAL/HIGH findings of the 2026-05-28 allowlist audit (report 43).
168
172
  - **Report-writing flags on type/test tools**: `mypy --junit-xml`/`--*-report` and `pytest --junitxml`/`--result-log`/`--report-log` write to arbitrary paths and are rejected.
169
173
  - **Read-safe commands** (`ls`, `cat`, `head`, `tail`, `rg`, `pwd`, `wc`, `git status`, `git diff`, ...): allowed.
170
- - **`.git/`**: every file-edit path check excludes anything inside `.git/`. Shell commands cannot include `git commit`, `git push`, `git reset --hard`, etc. Branch and commit ownership stays with the main Claude thread.
174
+ - **`.git/`**: every file-edit path check excludes anything inside `.git/`. Shell commands cannot include `git commit`, `git push`, `git reset --hard`, etc. Branch and commit ownership stays with the main thread.
171
175
 
172
176
  ### Effective working directory (`Bash.cwd`, v1.10.0)
173
177
 
@@ -187,7 +191,7 @@ Treat these files as **trusted model instructions** before using write-capable r
187
191
 
188
192
  ## Autonomous goal mode (`/kimi:pursue`, experimental)
189
193
 
190
- `/kimi:pursue` (v1.1 prototype) hands Kimi an objective to pursue across multiple continuation turns via kimi-code's headless goal mode (`kimi -p "/goal ..."`, kimi-code 0.8.0+). It is **write-capable and reuses the rescue trust boundary**: it runs with `KIMI_PLUGIN_CC_CMD=rescue`, so the PreToolUse hook applies the exact same workspace allowlist above, and it cannot mutate git state.
194
+ `/kimi:pursue` (v1.1 prototype) hands Kimi an objective to pursue across multiple continuation turns via kimi-code's headless goal mode (`kimi -p "/goal -- <objective>"`, kimi-code 0.8.0+). It is **write-capable and reuses the rescue trust boundary**: it runs with `KIMI_PLUGIN_CC_CMD=rescue`, so the PreToolUse hook applies the exact same workspace allowlist above, and it cannot mutate git state.
191
195
 
192
196
  Pursue also needs to settle its current goal. The child receives `KIMI_PLUGIN_CC_OPERATION` from its immutable execution plan, overwriting any inherited value. With the rescue label, operation `pursue`, and a trusted workspace root, the hook allows only `GetGoal {}` and `UpdateGoal {status: "complete" | "blocked"}`. These calls read or terminally update session goal metadata. Extra fields, goal creation, reactivation and budget changes are denied. `--turns` remains a soft instruction; the plugin's wall-clock budget is the hard bound.
193
197
 
@@ -196,10 +200,10 @@ If another installed host still points to an older hook, that hook may veto thes
196
200
  > **Goal-mode entry gate (changed in 0.12.0).** Headless goal mode is reached by a `/goal`-prefixed prompt. On kimi-code 0.8–0.11 it is *additionally* gated by the `goal-command` experimental flag, which `pursue.ts` sets per-spawn (`KIMI_CODE_EXPERIMENTAL_GOAL_COMMAND=1`). kimi-code 0.12.0 (PR #569) **removed that experimental-flag gate**, so the `/goal` prefix alone triggers goal mode. This does not weaken plugin safety: read-only commands (review/challenge/ask/review_gate) hard-prefix an English instruction line, so their trimmed prompt never starts with `/goal` and they cannot enter goal mode on any version; and the managed PreToolUse hook denies every write under a read-only label regardless (see § Native agent-core-v2 for why the hook cannot be pre-empted). The env var `pursue.ts` sets is now redundant on 0.12.0 but harmless, and is kept for 0.8–0.11 compat.
197
201
 
198
202
  - **The hook fires on every tool call in every continuation turn.** On native v2 every tool execution passes `prepareToolCall` → `fireBeforeExecute`, where the external-hooks listener vetoes ahead of any terminal allow (the auto-approve gate uses non-terminal `pass()`; the engine's only `allow()` is the plan-file guard, never armed in a plugin-managed session — see § Native agent-core-v2). (On legacy-v1 ≤ 0.41 the same guarantee came from `PreToolCallHookPermissionPolicy` sitting at policy index 0.) Goal continuation turns are ordinary turns — there is no autonomous-continuation path that bypasses per-tool approval (verified against kimi-code 0.9.0, audit report 58; re-verified through 0.12.0 by the goal-mode real-binary smoke, reports 61-65). A goal-mode run is exactly as write-gated as a single-turn rescue. This is locked by a real-binary smoke (`tests/runtime/real-binary-smoke.test.ts`, run via `KIMI_PLUGIN_CC_SMOKE=1 bun test ... -t "goal mode"`): it runs real headless goal mode under a read-only label and asserts that **no file lands across the entire multi-turn run** — a hook miss on any continuation turn would land one.
199
- - **The only new risk is unboundedness, and the plugin bounds it.** Headless goal create sets no token/turn budget; only the model (via `SetGoalBudget`) or an external signal stops the loop. `/kimi:pursue` therefore enforces a **mandatory finite wall-clock ceiling** via its AbortController (`--budget`, default 45m) — the SIGTERM→SIGKILL descendant-reaping cancellation reaps the whole goal process tree. `--turns` is a *soft* hint injected into the objective (the model may call `SetGoalBudget`); it is not enforceable headless. Treat `--budget` as the real bound.
203
+ - **The only new risk is unboundedness, and the plugin bounds it.** Headless goal create sets no token/turn budget, and the managed hook denies `SetGoalBudget`; the plugin's external wall-clock stop is therefore the guaranteed bound. `/kimi:pursue` enforces a **mandatory finite wall-clock ceiling** via its AbortController (`--budget`, default 45m) — the SIGTERM→SIGKILL descendant-reaping cancellation reaps the whole goal process tree. `--turns` is a *soft* instruction injected into the objective, never a request to call the denied `SetGoalBudget` tool; it is not enforceable headless, and stopping at that soft hint is not proof of completion. Treat `--budget` as the real bound.
200
204
  - **Loop runaway is now also capped upstream.** kimi-code 0.8.0 (#364) detects repeated identical tool calls and force-stops a turn at a repeat streak. This benefits `/kimi:rescue`, `/kimi:ask`, and `/kimi:pursue` alike: a force-stopped turn ends *normally* (exit 0, flows through the plugin's success path — not a failure), reducing the chance an autonomous run spins against the wall-clock budget. It is a complement to, not a replacement for, the wall-clock ceiling.
201
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.
202
- - **A model-invocable `kimi-pursue` agent (v1.3) wraps this command.** The main Claude 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`.
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`.
203
207
 
204
208
  ## Read-only swarm (`/kimi:swarm`)
205
209
 
@@ -207,10 +211,10 @@ If another installed host still points to an older hook, that hook may veto thes
207
211
 
208
212
  - **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.
209
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.) **This is locked by a real-binary smoke** (`tests/runtime/real-binary-smoke.test.ts`, run via `bun run smoke:real`): it spawns the real binary under the `swarm` label with an adversarial prompt that forces a spawned subagent to attempt a write, and asserts that **no file lands** and the hook deny marker appears — converting the subagent-denial claim from source-read-verified to runtime-test-verified.
210
- - **Defense-in-depth: read-only subagent profile.** The coordinator prompt steers the swarm to `subagent_type: "explore"` — kimi-code's read-only exploration profile, whose tool loadout has *no* `Write`/`Edit` tools at all. That is a second layer beneath the hook: even setting the hook aside, an explore subagent has no file-editing tool to call. (The hook remains the load-bearing enforcement; the profile is belt-and-suspenders.)
214
+ - **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.
211
215
  - **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.
212
216
  - **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.
213
- - **A model-invocable `kimi-swarm` agent (v1.3) wraps this command.** The `/kimi:swarm` slash command is human-only (`disable-model-invocation: true`, the blanket convention for *all* commands), but the `kimi-swarm` subagent lets the main Claude thread *dispatch* a read-only fan-out on its own judgement — the same human-command + model-invocable-agent split that `kimi-review`/`kimi-challenge`/`kimi-ask`/`kimi-rescue` already use. Swarm is a safe candidate for an agent precisely because it is **read-only (zero write surface)**: the only thing auto-dispatch adds is COST exposure, already bounded by the hard `--budget` and a hard `--max-concurrency` that the runtime now defaults to 4 for every swarm run (enforced on 0.18.0+) — so an auto-dispatched fan-out is bounded by construction, not by ignorable agent prose (the agent additionally restricts backgrounding to explicit user requests). `/kimi:pursue` ALSO gains a model-invocable agent (`kimi-pursue`, v1.3 — see the Autonomous goal mode section above): it is write-capable, but auto-dispatch widens no write surface (the rescue workspace allowlist + the managed PreToolUse hook gate every tool call on every continuation turn, and it cannot mutate git state) and removes no bound (the mandatory `--budget` ceiling holds), so its agent path is exactly as confined as its command path. Because it is the plugin's highest-autonomy surface, its agent description demands an explicit hands-off-autonomy request and steers single bounded tasks to `kimi-rescue`.
217
+ - **A model-invocable `kimi-swarm` agent (v1.3) wraps this command.** The `/kimi:swarm` slash command is human-only (`disable-model-invocation: true`, the blanket convention for *all* commands), but the `kimi-swarm` subagent lets the main thread *dispatch* a read-only fan-out on its own judgement — the same human-command + model-invocable-agent split that `kimi-review`/`kimi-challenge`/`kimi-ask`/`kimi-rescue` already use. Swarm is a safe candidate for an agent precisely because it is **read-only (zero write surface)**: the only thing auto-dispatch adds is COST exposure, already bounded by the hard `--budget` and a hard `--max-concurrency` that the runtime now defaults to 4 for every swarm run (enforced on 0.18.0+) — so an auto-dispatched fan-out is bounded by construction, not by ignorable agent prose (the agent additionally restricts backgrounding to explicit user requests). `/kimi:pursue` ALSO gains a model-invocable agent (`kimi-pursue`, v1.3 — see the Autonomous goal mode section above): it is write-capable, but auto-dispatch widens no write surface (the rescue workspace allowlist + the managed PreToolUse hook gate every tool call on every continuation turn, and it cannot mutate git state) and removes no bound (the mandatory `--budget` ceiling holds), so its agent path is exactly as confined as its command path. Because it is the plugin's highest-autonomy surface, its agent description demands an explicit hands-off-autonomy request and steers single bounded tasks to `kimi-rescue`.
214
218
 
215
219
  ## Write-capable swarm (`/kimi:swarm --write`)
216
220
 
@@ -218,12 +222,12 @@ If another installed host still points to an older hook, that hook may veto thes
218
222
 
219
223
  - **One ephemeral worktree for the whole swarm (Design A).** kimi-code runs `AgentSwarm` subagents **in-process**, all sharing the coordinator's session workspace (at 0.42.0 a child DI scope under `features/swarm/`; pre-0.42 `subagent-host.ts:362`, `cwd: parent.config.cwd`) — so "a worktree per subagent" is impossible without abandoning `AgentSwarm`. Instead the plugin creates ONE detached worktree off HEAD (under its own data dir, never inside the user repo) and spawns the coordinator `kimi -p` with cwd = that worktree. Every subagent inherits it. The worktree is disposable: a botched run is thrown away.
220
224
  - **Writes are confined by the hook to a forge-proof trusted root.** The `swarm-write` hook label routes write/edit/shell through the **rescue allowlist verbatim** (workspace check, symlink/realpath reject, mutating-flag + exec-delegating-flag detector, git-mutation deny), but scoped to `KIMI_PLUGIN_CC_WORKSPACE_ROOT` — the worktree path the plugin exports on the spawn — **NOT** the hook payload `cwd`. The model running inside kimi cannot set an env var on its already-spawned process, so confinement does not depend on how upstream derives the payload cwd for an in-process subagent. A missing trusted root **fails closed** (all writes denied). An out-of-worktree write (absolute path into the user's real tree) is rejected by the allowlist's `isWithin` check; a write to the worktree's `.git` pointer is rejected by the `.git`-segment check.
221
- - **`coder` subagents really can write, and the hook is the sole boundary.** Verified at kimi-code 0.42.0: swarm subagents get their own eager external-hooks service under the standard before-execute channel — there is no deny-all path on the swarm route (the `btw` side-question path is separate). So unlike read-only swarm (which has the `explore` profile's no-edit-tools as a second layer), write-swarm's `coder` subagents DO have edit tools, and the managed PreToolUse hook is the **only** write boundary — which is why the trusted-root confinement above is load-bearing and gated tightly.
225
+ - **`coder` subagents really can write, and the hook is the sole boundary.** Verified at kimi-code 0.42.0: swarm subagents get their own eager external-hooks service under the standard before-execute channel — there is no deny-all path on the swarm route (the `btw` side-question path is separate). Write-swarm's `coder` subagents DO have edit tools, and the managed PreToolUse hook is the **only** write boundary — which is why the trusted-root confinement above is load-bearing and gated tightly.
222
226
  - **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.
223
- - **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. 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.
227
+ - **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.
224
228
  - **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`.
225
229
  - **Field-proven by a real-binary smoke.** `tests/runtime/real-binary-smoke.test.ts` runs the real `runSwarm --write` and asserts the POSITIVE end-to-end (a `coder` subagent's edits land in the worktree, captured as a patch; the user's real tree is byte-identical + git-clean; the worktree is cleaned up) plus the NEGATIVE (an absolute-path write outside the trusted root is hook-denied). 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.
226
- - **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 Claude 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.
230
+ - **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.
227
231
 
228
232
  ## Response budgets and partial output (2.2.0)
229
233
 
@@ -368,7 +372,7 @@ If you find a way to coax /kimi:review, /kimi:challenge, /kimi:review_gate, or /
368
372
 
369
373
  **Hook-escape monitor** (§1): the cli-client replays the pure hook policy on every streamed `tool_calls` entry; a call the policy denies whose tool result is not the hook's denial (byte-exact, or with upstream's repeat-breaker `<system-reminder>` decoration / handoff text) is recorded as `mismatch`, and a denied call still without a result at settlement as `unresolved`. Findings are **detection evidence, never a failure**: logged (`hook_escape`), persisted on the job row (`hook_escapes`, JSON), exposed by `status`/`result --json`, printed as a stderr `safety hook notice`, and carried in timeout details; the run continues, the job's outcome is unchanged, and the review gate stays fail-open and only annotates. The stream has no outcome field, so a mismatch is equally an upstream pre-hook rejection (unknown tool, disabled tool, invalid args, sensitive path, per-tool resolve errors), a coexisting hook or permission policy blocking first, or hook drift — failing the job needs positive execution evidence this stream does not provide. Streamed calls under read-only labels are covered (swarm subagents are in-process, not streamed); write labels only for the pure denials (plan/goal tools).
370
374
 
371
- A denied attempt alone is not a finding: the hook's denial text in the corresponding tool result (byte-exact, or carrying upstream's repeat-breaker decoration) proves enforcement and clears it. Anything else is recorded as `mismatch` with a 200-character excerpt; a denied call with no result at settlement as `unresolved`. **What a finding means:** the model received something other than the managed denial. On certified 2.2.0 that is produced just as readily by calls upstream rejects before the hook runs (`Tool "LS" not found`, `Invalid args for tool …`, the sensitive-path `PathSecurityError` for `.env`, per-tool resolve errors, `Tool skipped because a previous tool call stopped the turn.`), by a coexisting PreToolUse hook ahead of the managed block or an upstream permission policy blocking first, or by a cancel/budget abort landing before the result — none of which executed anything — as by a hook that failed open. Because the stream carries no outcome field, the plugin cannot tell these apart, so it reports and does not fail: the job completes or fails on its own merits, `status`/`result --json` carry `hook_escapes`, stderr prints a `safety hook notice`, and the review gate annotates `systemMessage` without changing its decision. Treat a finding as a prompt to run `/kimi:setup --check` and read the excerpt, not as proof of a breach; treat a *recurring* `mismatch` whose excerpt looks like tool output (`Wrote N bytes to …`) as a drifted hook. Coverage is streamed calls under read-only labels and pure denials under write labels — not filesystem-dependent rescue allowlist decisions, not swarm subagent calls (in-process, never streamed). Non-vacuous live control (CLI 2.2.0, 2026-10-15): with the hook command pointed at a nonexistent script upstream failed open, `Write` created the file, and the finding was `mismatch` with excerpt `Wrote 7 bytes to ESCAPE.txt`; with the hook intact the same prompt produced the byte-exact denial and no finding. Enforcement (stopping the run, failing the job) is deliberately not shipped: it needs positive execution evidence. The parked upstream ask is an `outcome: executed|vetoed|rejected` field on print-mode tool records; a plugin-side alternative is a per-job trace the hook writes before it exits.
375
+ A denied attempt alone is not a finding: the hook's denial text in the corresponding tool result (byte-exact, or carrying upstream's repeat-breaker decoration) proves enforcement and clears it. Anything else is recorded as `mismatch` with a 200-character excerpt; a denied call with no result at settlement as `unresolved`. **What a finding means:** the model received something other than the managed denial. On certified 2.2.0 that is produced just as readily by calls upstream rejects before the hook runs (`Tool "LS" not found`, `Invalid args for tool …`, the sensitive-path `PathSecurityError` for `.env`, per-tool resolve errors, `Tool skipped because a previous tool call stopped the turn.`), by a coexisting PreToolUse hook ahead of the managed block or an upstream permission policy blocking first, or by a cancel/budget abort landing before the result — none of which executed anything — as by a hook that failed open. Because the stream carries no outcome field, the plugin cannot tell these apart, so it reports and does not fail: the job completes or fails on its own merits, `status`/`result --json` carry `hook_escapes`, stderr prints a `safety hook notice`, and the review gate annotates `systemMessage` without changing its decision. Treat a finding as a prompt to run `/kimi:setup --check` and read the excerpt, not as proof of a breach; treat a *recurring* `mismatch` whose excerpt looks like tool output (`Wrote N bytes to …`) as a drifted hook. Coverage is streamed calls under read-only labels and pure denials under write labels — not filesystem-dependent rescue allowlist decisions, not swarm subagent calls (in-process, never streamed). Non-vacuous live control (CLI 2.2.0, 2026-10-15): with the hook command pointed at a nonexistent script upstream failed open, `Write` created the file, and the finding was `mismatch` with excerpt `Wrote 7 bytes to ESCAPE.txt`; with the hook intact the same prompt produced the byte-exact denial and no finding. Enforcement (stopping the run, failing the job) is deliberately not shipped: it needs positive execution evidence. The upstream ask, filed as MoonshotAI/kimi-code#4241, is an `outcome: executed|vetoed|rejected` field on print-mode tool records; a plugin-side alternative is a per-job trace the hook writes before it exits.
372
376
 
373
377
  ## Review-gate thinking request
374
378
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kimi-plugin-cc",
3
- "version": "2.2.1",
4
- "description": "Kimi for Claude Code and Codex: code questions, reviews, fixes, and parallel tasks through the local Kimi Code CLI.",
3
+ "version": "2.3.1",
4
+ "description": "Kimi for Claude Code and Codex: code questions, independent reviews, bounded fixes, autonomous goals, and parallel tasks through the local Kimi Code CLI.",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.5.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kimi",
3
- "version": "2.2.1",
4
- "description": "Codex plugin that delegates review, challenge, ask, rescue, pursue, and swarm workflows to the local kimi-code CLI through the kimi-plugin-cc companion runtime.",
3
+ "version": "2.3.1",
4
+ "description": "Kimi for Codex: code questions, independent reviews, bounded fixes, autonomous goals, and parallel tasks through the local Kimi Code CLI.",
5
5
  "author": {
6
6
  "name": "linxule"
7
7
  },
@@ -19,8 +19,8 @@
19
19
  "skills": "./skills/",
20
20
  "interface": {
21
21
  "displayName": "Kimi",
22
- "shortDescription": "Delegate repo work to local kimi-code",
23
- "longDescription": "Shell-only Codex packaging for kimi-plugin-cc. It exposes Codex skills that call the existing companion runtime and local kimi-code subprocess, while preserving the Claude Code plugin surface.",
22
+ "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; no MCP server is required.",
24
24
  "developerName": "linxule",
25
25
  "category": "Developer Tools",
26
26
  "capabilities": [
@@ -15,24 +15,16 @@ const companionEntrypoint = fileURLToPath(new URL(isCompiledRuntime ? "./compani
15
15
  const companionProjectRoot = path.resolve(path.dirname(companionEntrypoint), "..");
16
16
  const FAILURE_PERSIST_RETRY_DELAYS_MS = [100, 200, 400];
17
17
  export async function startBackgroundJob(job, prompt, context, paths, options) {
18
- // MUST be the same resolution the hook verifier uses. The worker re-verifies
19
- // the hook (rescue.ts / ask.ts, deliberately, so a background job cannot
20
- // outlive or bypass the check), and the verifier rebuilds the canonical
21
- // command from ITS OWN `process.argv0`. Spawning with a different spelling of
22
- // node than setup pinned makes the worker compute a different expected
23
- // command than the config holds → byte-exact mismatch → every background
24
- // rescue/ask refuses forever, with `/kimi:setup` unable to converge the two
25
- // sides (setup runs via companion.sh, whose argv0 is the stable symlink).
18
+ // Background spawn and hook verification must resolve the same stable Node
19
+ // path: the worker re-verifies the byte-exact hook command from its own
20
+ // runtime path, so a different spelling than setup pinned fails closed. For
21
+ // example, spawn(process.execPath) can resolve …/Cellar/node/<ver>/bin/node
22
+ // while setup pinned /opt/homebrew/bin/node.
26
23
  //
27
- // Concretely: `spawn(process.execPath)` gives the child an argv0 equal to the
28
- // fully symlink-resolved path (…/Cellar/node/<ver>/bin/node), whereas setup
29
- // pins /opt/homebrew/bin/node. Caught by Kimi's pre-release review of v1.9.0.
30
- // NOTE: this is deliberately NOT `resolveNodeBinary`, which additionally
31
- // REQUIRES an absolute override because the hook command runs under
32
- // `/bin/sh -c` with a possibly sanitized PATH. Spawning has no such
33
- // constraint — a bare name is resolved by spawn() via PATH — so the override
34
- // is honored verbatim here. Only the no-override default needs to change:
35
- // it must match what the verifier computes, hence `preferStableNodePath`.
24
+ // An explicit spawn override need not be absolute because spawn() resolves
25
+ // bare names through PATH. The hook command separately requires an absolute
26
+ // override because /bin/sh -c may have a sanitized PATH. Only the no-override
27
+ // default uses preferStableNodePath to match the verifier.
36
28
  const override = context.env.KIMI_PLUGIN_CC_NODE_BIN;
37
29
  const nodeBinary = override !== undefined && override.length > 0
38
30
  ? override