kimi-plugin-cc 2.2.2 → 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 +8 -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 +13 -62
  15. package/dist/commands/ask.js +24 -29
  16. package/dist/commands/cancel.js +7 -19
  17. package/dist/commands/cli-helpers.js +7 -16
  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 +6 -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 +1 -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 +6 -174
  43. package/dist/version.js +1 -1
  44. package/docs/migration.md +2 -6
  45. package/docs/registry-releases.md +18 -0
  46. package/docs/safety.md +14 -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 +13 -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 +7 -16
  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 +6 -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 +1 -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 +6 -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
@@ -1,43 +1,21 @@
1
1
  import { SETUP_SUPPORTED_FLAGS } from "../parsing.js";
2
- // Managed-block installer for the kimi-code PreToolUse hook.
2
+ // Managed-block installer for the load-bearing kimi-code PreToolUse hook.
3
+ // Without it, `kimi -p` auto-approves tool calls, including from read-only
4
+ // commands.
3
5
  //
4
- // Replaces the v0.4 Wire-based setup probe. In v1.0 the load-bearing
5
- // safety control is the PreToolUse hook (see runtime/hooks/approval-policy.ts) —
6
- // without it, `kimi -p` auto-approves every tool call from review /
7
- // challenge / review_gate / ask. PR 3 made rescue REFUSE to run when the
8
- // hook is missing, so /kimi:setup is the only path that wires the
9
- // plugin's safety story into ~/.kimi-code/config.toml.
6
+ // Setup writes or refreshes a host-scoped BEGIN/END block containing the
7
+ // canonical `'<absolute-node>' '<absolute-hook-script>'` command from
8
+ // resolveNodeBinary/buildHookShellCommand. Both tokens are shell-quoted and the
9
+ // absolute Node path avoids PATH dependence under sanitized launches. Setup
10
+ // probes the hook directly with resolveNodeBinary and through `/bin/sh -c`
11
+ // using the exact installed command bytes. It also warns about broad permission
12
+ // denies and refuses duplicate, orphaned, or malformed managed blocks.
10
13
  //
11
- // What this command does:
14
+ // Subcommands: install (default), --check, --uninstall [--all], and
15
+ // --enable-review-gate/--disable-review-gate.
12
16
  //
13
- // - Writes (or refreshes, idempotently) a BEGIN/END marker block to
14
- // `~/.kimi-code/config.toml` containing a [[hooks]] entry that
15
- // invokes `<process.execPath> /abs/path/to/dist/hooks/approval-hook.js`
16
- // on PreToolUse. The absolute Node binary is used (not bare `node`)
17
- // so the hook keeps working when kimi-code is launched from a
18
- // GUI/LaunchAgent with a sanitized PATH (nvm/asdf/mise users).
19
- // - Probes the installed hook in TWO ways: (a) directly via the same
20
- // in-process Node binary, and (b) via `/bin/sh -c "<command>"` to
21
- // mirror kimi-code's actual hook-runner spawn shape (kimi-code
22
- // shells out via `/bin/sh -c` per agent-core hooks/runner.ts).
23
- // - Scans the user's existing `[[permission.rules]]` for broad denies
24
- // that would interfere with read-only commands; warns on stdout.
25
- // - Detects and refuses to install over duplicate managed blocks,
26
- // orphan markers, or malformed blocks (matcher present, wrong
27
- // event, missing command). Same parser shared with PR 2's
28
- // runtime/hooks/install.ts verifier.
29
- //
30
- // Subcommands:
31
- //
32
- // /kimi:setup — install (idempotent) + probe
33
- // /kimi:setup --check — probe only (no write)
34
- // /kimi:setup --uninstall — remove managed block (and orphan markers)
35
- // /kimi:setup --enable-review-gate / --disable-review-gate
36
- // — flip the plugin-side review-gate flag in
37
- // CLAUDE_PLUGIN_DATA/config.json
38
- //
39
- // TOML manipulation is line-based, not via a TOML parser. The marker
40
- // block is owned by us; the rest of the file is the user's, untouched.
17
+ // Managed-block splicing is line-based and preserves user bytes outside owned
18
+ // edits; whole-file validation and inline-hook normalization use the TOML parser.
41
19
  import { spawn } from "node:child_process";
42
20
  import { access, mkdir, open, readFile, rename, unlink, } from "node:fs/promises";
43
21
  import { constants as fsConstants, existsSync } from "node:fs";
@@ -59,13 +37,10 @@ const DEFAULT_HOOK_TIMEOUT_S = 15;
59
37
  // node startup on cold CI runners. The shell probe adds /bin/sh fork
60
38
  // overhead but should still complete well within the same budget.
61
39
  const PROBE_TIMEOUT_MS = 5_000;
62
- // Reject hook script paths that contain characters TOML basic strings
63
- // reserve as escape sequences (other than the ones we already escape)
64
- // or shell-special characters whose presence would force us into
65
- // fragile quoting. The realistic install location lives under
66
- // `${plugin_root}/dist/hooks/approval-hook.js` and never legitimately
67
- // contains these — anyone hitting this hard error has either a hostile
68
- // `KIMI_PLUGIN_CC_HOOK_SCRIPT` override or an unusual install layout.
40
+ // Reject the specific control characters, double quotes, and backslashes matched
41
+ // below, including newlines and tabs; this is not a general shell-metacharacter
42
+ // filter. Normal install paths do not contain them, so a match indicates an
43
+ // unsafe override or an unusual layout that needs an explicit path change.
69
44
  const PATH_FORBIDDEN_RE = /[\x00-\x1f"\\\n\r\t]/;
70
45
  export async function runSetup(argv, context) {
71
46
  const parsed = parseArgs(argv);
@@ -86,8 +61,8 @@ export async function runSetup(argv, context) {
86
61
  const configPath = resolveKimiCodeConfigPath(context.env);
87
62
  const hookScriptPath = resolveHookScriptPath(context.env);
88
63
  assertHookPathTomlSafe(hookScriptPath);
89
- // Host id keys the managed block so Claude Code and Codex share one
90
- // ~/.kimi-code/config.toml without clobbering each other (v1.7.0).
64
+ // Host id scopes each managed block so Claude Code and Codex can share one
65
+ // ~/.kimi-code/config.toml without clobbering each other.
91
66
  const hostId = resolveHostId(context.env, hookScriptPath);
92
67
  const warnings = [];
93
68
  switch (parsed.mode) {
@@ -154,12 +129,9 @@ async function runInstall(configPath, hookScriptPath, hostId, reviewGateEnabled,
154
129
  }
155
130
  warnings.push(`Converted the top-level inline hooks array (${inlineHooks.entryCount} entr${inlineHooks.entryCount === 1 ? "y" : "ies"}) to canonical [[hooks]] tables before installing. Surrounding config bytes were preserved; formatting and comments inside that hooks assignment were normalized.`);
156
131
  }
157
- // Prune THIS HOST's orphaned, marker-less approval-hook [[hooks]] entries
158
- // BEFORE resolving the managed block, so line numbers used for splicing are
159
- // computed against the cleaned text. Host-scoped (v1.8.2): after a
160
- // kimi-code config rewrite strips marker comments, ANOTHER host's
161
- // marker-less table is that host's LIVE hook — pruning it here was the
162
- // pre-v1.8.2 seesaw where each host's setup disarmed the other.
132
+ // Prune only this host's orphaned marker-less entries before computing
133
+ // splice line numbers. Another host's marker-less table may be its live hook
134
+ // after a config rewrite strips comments, so it must remain untouched.
163
135
  const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
164
136
  const { pruned, count: prunedCount, commands: prunedCommands } = pruneOrphanApprovalHooks(inlineHooks.contents, lineEnding, hostId, expectedCommand);
165
137
  const existing = pruned;
@@ -265,9 +237,8 @@ async function runCheck(configPath, hookScriptPath, hostId, reviewGateEnabled, w
265
237
  };
266
238
  }
267
239
  const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
268
- // nodeExists → a command mismatch is reported as a classified H4 drift
269
- // diagnosis (Node upgrade / version-manager switch vs. plugin path move).
270
- // hostId → check THIS host's block in the shared config (v1.7.0 scoping).
240
+ // nodeExists refines mismatch prose into drift classification; hostId checks
241
+ // only this host's block in the shared config.
271
242
  const installedCheck = evaluateInstalled(existing, expectedCommand, {
272
243
  hostId,
273
244
  nodeExists: (binPath) => existsSync(binPath),
@@ -280,20 +251,15 @@ async function runCheck(configPath, hookScriptPath, hostId, reviewGateEnabled, w
280
251
  if (otherHosts.length > 0) {
281
252
  warnings.push(`Other host(s) also manage a block in this shared config: ${otherHosts.join(", ")} (coexisting, untouched).`);
282
253
  }
283
- // Marker-less table triage (v1.8.2): after a kimi-code config rewrite strips
284
- // all comments, each host's [[hooks]] table survives unmarked and KEEPS
285
- // ENFORCING. Ours-with-the-exact-current-command is the live hook (covered
286
- // by the installedCheck note below, not "cruft"); ours-with-a-stale-command
287
- // is prunable by this host's setup; another host's is THEIR live hook —
288
- // report it informationally and leave it strictly alone.
254
+ // Config rewrites can strip markers while leaving each host's table enforcing.
255
+ // Our exact-command table is live; our stale table is prunable; another host's
256
+ // table is reported but left strictly alone.
289
257
  const bareTables = findUnmanagedApprovalHookBlocks(existing);
290
258
  const ownStale = bareTables.filter((t) => hostIdFromHookCommand(t.command) === hostId && t.command !== expectedCommand);
291
259
  const foreignHosts = [
292
260
  ...new Set(bareTables
293
- // Exclude any table whose command byte-equals what THIS host writes —
294
- // it is unambiguously ours even if a `KIMI_PLUGIN_CC_HOST_ID` override
295
- // makes its path-derived host differ from `hostId` (Opus review NIT:
296
- // otherwise runCheck would mislabel the host's own table as foreign).
261
+ // Exact current command bytes identify this host even when a host-id
262
+ // override disagrees with the path-derived host.
297
263
  .filter((t) => t.command !== expectedCommand)
298
264
  .map((t) => hostIdFromHookCommand(t.command))
299
265
  .filter((h) => h !== null && h !== hostId)),
@@ -383,10 +349,8 @@ async function runCheck(configPath, hookScriptPath, hostId, reviewGateEnabled, w
383
349
  nextStep: !probe.ok
384
350
  ? "Run /kimi:setup to repair the managed block, or /kimi:setup --uninstall if you want to remove the integration."
385
351
  : installedCheck.via === "bare-table"
386
- // Enforcement is active via the marker-less table, but the markers are
387
- // gone (kimi-code stripped them). Don't say "No action needed" while a
388
- // warning tells the user to re-adorn — that contradiction confused
389
- // operators (Kimi review F3). Recommend the harmless re-adorn.
352
+ // Enforcement remains active through the marker-less table, but recommend
353
+ // re-adorning rather than claiming no action is needed.
390
354
  ? "Enforcement is active. Run /kimi:setup to re-adorn this host's managed-block markers — kimi-code stripped them on its last config write (comments are not preserved). Your hook keeps working meanwhile."
391
355
  : "No action needed.",
392
356
  details: buildDetails({
@@ -425,21 +389,14 @@ async function runUninstallLocked(configPath, hookScriptPath, hostId, removeAllH
425
389
  }),
426
390
  };
427
391
  }
428
- // Default: remove THIS host's block(s) — its own suffixed block plus any
429
- // legacy block whose command path derives to this host — and THIS HOST's
430
- // orphaned marker-less hook entries, leaving OTHER hosts' blocks AND their
431
- // marker-less tables intact (a marker-less table is likely that host's LIVE
432
- // hook after a kimi-code comment strip — v1.8.2 host scoping). `--all`:
433
- // remove EVERY host's managed block and every one of our hook tables (the
434
- // full nuke).
392
+ // Scoped uninstall removes only this host's marked and marker-less blocks;
393
+ // other hosts' blocks and live marker-less tables remain intact. `--all` is
394
+ // the explicit every-host sweep for all of this plugin's hook tables.
435
395
  const { stripped: strippedMarkers, removedBlocks, orphansLeft } = stripManagedBlocks(existing, hostId, removeAllHosts);
436
396
  const lineEnding = detectLineEnding(existing);
437
- // Pass `expectedCommand` as `alsoMatchCommand` (both scoped and --all) so
438
- // uninstall removes THIS host's own current hook table by byte-exact command
439
- // even when a `KIMI_PLUGIN_CC_HOST_ID` override disagrees with the command's
440
- // path-derived host, or when the hook lives under a non-standard (dev) path
441
- // that `isOurApprovalHookCommand` wouldn't recognize — the install path
442
- // already does this; uninstall now matches it (Codex P2 / Opus A / Kimi F4).
397
+ // Exact current command bytes also identify this host's table when a host-id
398
+ // override disagrees with path derivation or a dev path is outside the strict
399
+ // ownership pattern.
443
400
  const expectedCommand = buildHookShellCommand(hookScriptPath, context.env);
444
401
  const { pruned: stripped, count: prunedCount } = pruneOrphanApprovalHooks(strippedMarkers, lineEnding, removeAllHosts ? undefined : hostId, expectedCommand);
445
402
  const changed = stripped !== existing;
@@ -498,21 +455,12 @@ async function readConfigSafe(configPath) {
498
455
  }
499
456
  }
500
457
  async function writeConfigAtomic(configPath, contents) {
501
- // Two-phase write: write to a sibling temp file, then rename. Each
502
- // call uses a unique temp path so two concurrent installs/uninstalls
503
- // can race without clobbering each other's intermediate file (PR 4
504
- // reviewer finding — fixed-path tmp file was a race surface).
458
+ // Write a unique sibling temp file and atomically rename it. O_EXCL prevents
459
+ // concurrent operations from sharing an intermediate path.
505
460
  //
506
- // Mode: the temp file is created at 0o600 (owner-only read/write) via
507
- // O_CREAT|O_EXCL with the mode passed directly to open(), mirroring
508
- // createPrivateLockFile in hooks/config-safety.ts. The kimi-code config
509
- // file holds API keys and tokens; a writeFile()-then-chmod() sequence
510
- // would let the temp file sit briefly at the default umask mode
511
- // (typically 0o644, group/world-readable) in between — audit report 28
512
- // (Codex M1) tracked the chmod-after-write half of this, but a second
513
- // pass found the create-then-tighten window itself. Passing the mode to
514
- // open() means the file never exists on disk at anything wider than
515
- // 0o600.
461
+ // Pass 0o600 at creation, not in a later chmod: the config can contain
462
+ // credentials, so the temp file must never exist with a wider umask-derived
463
+ // mode.
516
464
  await mkdir(path.dirname(configPath), { recursive: true });
517
465
  const suffix = randomBytes(8).toString("hex");
518
466
  const tmpPath = `${configPath}.kimi-plugin-cc.${process.pid}.${suffix}.tmp`;
@@ -589,13 +537,9 @@ function splitPreservingEnding(contents) {
589
537
  return contents.split("\n").map((line) => line.replace(/\r$/, ""));
590
538
  }
591
539
  /**
592
- * Remove managed BEGIN/END marker pairs that belong to `currentHost` (or every
593
- * host when `removeAll`). A block's host is its marker suffix, or — for a legacy
594
- * un-suffixed block — the host derived from its command path (so a Claude
595
- * uninstall never removes a `~/.codex/…` legacy block another host still relies
596
- * on; Kimi review). Orphan marker lines are removed individually (no
597
- * destructive sweep of trailing content) so `--uninstall` on a corrupted config
598
- * doesn't take user data with it.
540
+ * Remove managed blocks owned by `currentHost`, or every host with `removeAll`.
541
+ * Ownership comes from the marker suffix or a legacy command's derived host.
542
+ * Orphan markers are removed individually so surrounding user content survives.
599
543
  */
600
544
  function stripManagedBlocks(contents, currentHost, removeAll) {
601
545
  const lineEnding = detectLineEnding(contents);
@@ -603,13 +547,10 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
603
547
  const result = [];
604
548
  const orphansLeft = [];
605
549
  let removedBlocks = 0;
606
- // Decide whether a managed block/marker belongs to the host we're uninstalling
607
- // for. Suffixed blocks: only the matching host. Legacy (un-suffixed) COMPLETE
608
- // blocks: the host derived from the command path — but if the command can't be
609
- // attributed (not the canonical two-token shape), leave it (ambiguous
610
- // ownership; only `--all` removes it), so a scoped uninstall never destroys
611
- // another host's non-canonical block (Codex review). A bare/orphan marker line
612
- // has no command and is broken cruft, so a scoped uninstall does sweep it.
550
+ // Suffixed blocks belong only to the matching host. Complete legacy blocks
551
+ // use their command-derived host; an unattributable non-canonical command is
552
+ // ambiguous and only --all removes it. Orphan marker lines have no command
553
+ // and can be swept individually by a scoped uninstall.
613
554
  const shouldRemove = (markerHost, commandPath, isOrphanMarker) => {
614
555
  if (removeAll)
615
556
  return true;
@@ -685,7 +626,6 @@ function stripManagedBlocks(contents, currentHost, removeAll) {
685
626
  result.push(line);
686
627
  i += 1;
687
628
  }
688
- // Collapse runs of >= 3 blank lines created by removal back to 2.
689
629
  const collapsed = [];
690
630
  let blankRun = 0;
691
631
  for (const line of result) {
@@ -725,7 +665,6 @@ function pruneOrphanApprovalHooks(contents, lineEnding, ownedBy, alsoMatchComman
725
665
  }
726
666
  const lines = splitPreservingEnding(contents);
727
667
  const kept = lines.filter((_, idx) => !remove.has(idx));
728
- // Collapse runs of >= 3 blank lines created by removal back to 2.
729
668
  const collapsed = [];
730
669
  let blankRun = 0;
731
670
  for (const line of kept) {
@@ -753,15 +692,14 @@ function buildManagedBlock(hookScriptPath, env, lineEnding = "\n", hostId = "")
753
692
  return [
754
693
  `${BEGIN_MARKER_PREFIX}${suffix} (v${KIMI_PLUGIN_CC_VERSION}) ===`,
755
694
  `# DO NOT EDIT — managed by /kimi:setup. Run /kimi:setup --uninstall to remove.`,
756
- `# Host: ${hostId.length > 0 ? hostId : "(legacy)"} — Claude Code and Codex each own a`,
757
- `# separate block in this shared ~/.kimi-code/config.toml; setup in one host`,
758
- `# never touches the other's.`,
695
+ `# Host: ${hostId.length > 0 ? hostId : "(legacy)"} — each host owns a separate block in this shared`,
696
+ `# ~/.kimi-code/config.toml. Scoped setup/uninstall preserves other hosts;`,
697
+ `# /kimi:setup --uninstall --all removes every host's block.`,
759
698
  `# Purpose:`,
760
699
  `# kimi-code's \`kimi -p\` mode hard-codes permission='auto' and`,
761
- `# auto-approves every tool call. This hook enforces /kimi:review,`,
762
- `# /kimi:challenge, /kimi:review_gate, and /kimi:ask as read-only,`,
763
- `# and applies the workspace-bound rescue allowlist for /kimi:rescue.`,
764
- `# Without this block the plugin's safety contract collapses.`,
700
+ `# auto-approves every tool call. This hook enforces each managed command's`,
701
+ `# read-only or workspace-bound approval policy. Without this block,`,
702
+ `# the plugin's safety contract collapses.`,
765
703
  `# Matcher field is intentionally OMITTED — kimi-code compiles the`,
766
704
  `# matcher with \`new RegExp(...)\`. An empty/missing matcher means`,
767
705
  `# "fire for every tool". The string "*" would throw and silently`,
@@ -777,11 +715,9 @@ function buildManagedBlock(hookScriptPath, env, lineEnding = "\n", hostId = "")
777
715
  ].join(lineEnding);
778
716
  }
779
717
  /**
780
- * Encode a string for a TOML 1.0 basic string. Escapes the six required
781
- * escape sequences plus quotes/backslashes. Avoids the `\'` shell-quote
782
- * hazard that broke the v1.0-alpha.1 prototype: TOML basic strings
783
- * declare `\'` as a reserved escape, and a parser-compliant TOML
784
- * library (kimi-code uses smol-toml) raises an error on encounter.
718
+ * Encode a TOML basic string, including quotes, backslashes, and control
719
+ * escapes. Raw shell output must not be interpolated directly: TOML reserves
720
+ * `\'`, and a compliant parser rejects that escape.
785
721
  */
786
722
  function tomlBasicString(value) {
787
723
  const escaped = value
@@ -818,24 +754,18 @@ async function assertHookScriptExists(hookScriptPath) {
818
754
  }
819
755
  }
820
756
  /**
821
- * Two-layer probe:
757
+ * Two-layer probe; both layers require exit 2 and a non-empty denial reason.
822
758
  *
823
- * 1. **Direct probe.** Spawn the hook with `process.execPath` (the
824
- * same Node binary running the companion) and assert exit 2 +
825
- * non-empty stderr. Catches: missing script, broken hook code,
826
- * hook misrouting a deny as exit 0. This probe is reliable under
827
- * PATH-sanitized smoke tests because it uses an absolute Node
828
- * path.
759
+ * 1. Directly spawn the hook with the absolute path from resolveNodeBinary,
760
+ * catching missing/broken scripts and incorrect hook exit behavior without
761
+ * relying on PATH.
762
+ * 2. Execute the exact canonical command from buildHookShellCommand through
763
+ * `/bin/sh -c`: two shell-quoted absolute tokens for Node and the script.
764
+ * This catches pinned-interpreter execution and quoting failures. It does
765
+ * NOT test PATH discovery or compatibility of some other `node` on PATH.
829
766
  *
830
- * 2. **Shell probe.** Run `/bin/sh -c "<nodeBin> <hookScript>"` (the
831
- * exact shape kimi-code uses via agent-core hooks/runner.ts) with
832
- * synthetic stdin. Catches: kimi-code's shell can't find the
833
- * Node binary, shell-quoting in the managed block is wrong, the
834
- * Node binary on PATH isn't compatible.
835
- *
836
- * Both must pass for the install to be considered healthy. The shell
837
- * probe is skipped on platforms without `/bin/sh` (Windows pure, etc.) —
838
- * but the rest of the runtime already assumes POSIX.
767
+ * The shell layer is skipped where `/bin/sh` is unsupported; the runtime
768
+ * otherwise assumes POSIX.
839
769
  */
840
770
  async function probeHook(hookScriptPath, env) {
841
771
  const directResult = await probeHookDirect(hookScriptPath, env);
@@ -905,7 +835,6 @@ function spawnProbe(command, args, env, label) {
905
835
  child.kill("SIGKILL");
906
836
  }
907
837
  catch {
908
- // best effort
909
838
  }
910
839
  resolve({
911
840
  ok: false,
@@ -947,27 +876,11 @@ function truncate(value, max) {
947
876
  }
948
877
  // ----- Permission rules scan --------------------------------------------
949
878
  /**
950
- * Best-effort scan for `[[permission.rules]]` entries that would
951
- * interfere with read-only commands. We don't parse TOML; we look for
952
- * deny rules against common read tools. This recognizes both
953
- * double-quoted basic strings and single-quoted literal strings, but
954
- * still misses inline-table and multi-line forms — those are flagged
955
- * as a known limitation in PR 4 docs.
956
- */
957
- /**
958
- * Probe kimi-code's version and append a warning to `warnings` if the
959
- * installed kimi is outside the range kimi-plugin-cc was tested
960
- * against (H6, Codex post-hotfix audit Area 8).
961
- *
962
- * Setup soft-fail policy: if the probe itself fails (kimi not on PATH, spawn
963
- * error, unparseable output), we record nothing here — the hook probe
964
- * will surface a more direct error in that case. We only loud-warn when
965
- * the version is **demonstrably** out of range. Setup may still complete,
966
- * but the model execution-plan gate will refuse that binary before spawn.
967
- *
968
- * Override: `KIMI_PLUGIN_CC_SKIP_VERSION_PROBE=1` skips the probe
969
- * entirely for tests and CI environments without kimi installed. It is not a
970
- * production compatibility override; model jobs persist it as test-bypass.
879
+ * Warn only when the installed kimi is demonstrably outside the certified
880
+ * range. Probe failure records nothing here because the hook probe reports the
881
+ * more direct problem; setup may finish, but model spawn still refuses an
882
+ * uncertified binary. KIMI_PLUGIN_CC_SKIP_VERSION_PROBE is a test/CI bypass,
883
+ * not a production compatibility override.
971
884
  */
972
885
  async function collectKimiVersionWarnings(env, warnings) {
973
886
  if (env.KIMI_PLUGIN_CC_SKIP_VERSION_PROBE === "1")
@@ -991,16 +904,12 @@ async function collectKimiVersionWarnings(env, warnings) {
991
904
  warnings.push(formatVersionOutOfRangeWarning(probe, KIMI_PLUGIN_CC_VERSION));
992
905
  }
993
906
  /**
994
- * H8 — non-blocking notice listing kimi-code's OWN installed plugins (the
995
- * user-global plugin system kimi-code 0.4.0+ added; manifest at
996
- * `~/.kimi-code/plugins/installed.json`, shape verified through 0.12.0:
997
- * `{ plugins: [{ id, enabled, ... }] }`). kimi-code registers their tools
998
- * (incl. MCP) on every session. Under kimi-plugin-cc's read-only commands the
999
- * PreToolUse hook denies any tool outside Read/Grep/Glob, so calls to these
1000
- * plugins' tools are blocked — safe, but they silently burn model turns. We
1001
- * surface that expectation at setup time so a confused "kimi did nothing" report
1002
- * doesn't follow. Best-effort: a missing/unreadable/malformed manifest yields no
1003
- * notice. Never blocks setup; never mutates the plugin list.
907
+ * Non-blocking notice for kimi-code's own enabled plugins from
908
+ * ~/.kimi-code/plugins/installed.json. Their tools can register on every
909
+ * session, but read-only commands deny everything outside READ_ONLY_TOOLS
910
+ * (Read, Grep, Glob, ReadMediaFile, TaskList, TaskOutput), which can waste model
911
+ * turns. Missing, unreadable, or malformed manifests produce no notice; setup
912
+ * never blocks on or mutates the plugin list.
1004
913
  */
1005
914
  async function collectInstalledKimiPluginsNotice(env, warnings) {
1006
915
  const home = resolveKimiHome(env);
@@ -1041,6 +950,11 @@ async function collectInstalledKimiPluginsNotice(env, warnings) {
1041
950
  " kimi-code plugins you don't need for delegated work in your kimi-code config.",
1042
951
  ].join("\n"));
1043
952
  }
953
+ /**
954
+ * Best-effort line scan for permission rules that deny common read tools. This
955
+ * is not a TOML parse: it recognizes basic and literal quoted strings but misses
956
+ * inline-table and multiline forms.
957
+ */
1044
958
  function collectPermissionRuleWarnings(contents, warnings) {
1045
959
  const lines = contents.split("\n").map((line) => line.replace(/\r$/, ""));
1046
960
  let inRule = false;