kimi-plugin-cc 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +32 -0
- package/CHANGELOG.md +1096 -0
- package/LICENSE +201 -0
- package/README.md +772 -0
- package/agents/kimi-ask.md +82 -0
- package/agents/kimi-challenge.md +82 -0
- package/agents/kimi-pursue.md +91 -0
- package/agents/kimi-rescue.md +85 -0
- package/agents/kimi-review.md +82 -0
- package/agents/kimi-swarm-write.md +92 -0
- package/agents/kimi-swarm.md +83 -0
- package/commands/README.md +16 -0
- package/commands/ask.md +32 -0
- package/commands/cancel.md +17 -0
- package/commands/challenge.md +29 -0
- package/commands/pursue.md +37 -0
- package/commands/replay.md +13 -0
- package/commands/rescue.md +32 -0
- package/commands/result.md +13 -0
- package/commands/review.md +29 -0
- package/commands/setup.md +23 -0
- package/commands/status.md +13 -0
- package/commands/swarm.md +38 -0
- package/dist/background-spawn.js +224 -0
- package/dist/cli-cancellation.js +43 -0
- package/dist/cli-client.js +845 -0
- package/dist/commands/ask.js +327 -0
- package/dist/commands/cancel.js +178 -0
- package/dist/commands/cli-helpers.js +112 -0
- package/dist/commands/models.js +140 -0
- package/dist/commands/not-implemented.js +4 -0
- package/dist/commands/pursue.js +311 -0
- package/dist/commands/registry.js +87 -0
- package/dist/commands/repair-sessions.js +159 -0
- package/dist/commands/replay.js +123 -0
- package/dist/commands/rescue.js +333 -0
- package/dist/commands/result.js +58 -0
- package/dist/commands/review-gate.js +393 -0
- package/dist/commands/review.js +218 -0
- package/dist/commands/setup.js +1132 -0
- package/dist/commands/status.js +27 -0
- package/dist/commands/swarm.js +576 -0
- package/dist/commands/worker.js +31 -0
- package/dist/companion.js +114 -0
- package/dist/config.js +22 -0
- package/dist/errors.js +40 -0
- package/dist/git.js +215 -0
- package/dist/hooks/approval-hook.js +112 -0
- package/dist/hooks/approval-policy.js +254 -0
- package/dist/hooks/config-safety.js +637 -0
- package/dist/hooks/install-paths.js +504 -0
- package/dist/hooks/install.js +281 -0
- package/dist/hooks/managed-block.js +745 -0
- package/dist/hooks/review-gate-stop.js +19 -0
- package/dist/job-store.js +559 -0
- package/dist/jobs.js +202 -0
- package/dist/kimi-command.js +72 -0
- package/dist/kimi-engine.js +624 -0
- package/dist/kimi-errors.js +220 -0
- package/dist/kimi-home.js +9 -0
- package/dist/kimi-timeouts.js +68 -0
- package/dist/kimi-version-probe.js +1410 -0
- package/dist/logging.js +22 -0
- package/dist/native-v2-preflight.js +417 -0
- package/dist/package.json +1 -0
- package/dist/parsing.js +553 -0
- package/dist/paths.js +27 -0
- package/dist/plugin-data.js +98 -0
- package/dist/process-tree.js +448 -0
- package/dist/render.js +159 -0
- package/dist/rescue-approval.js +951 -0
- package/dist/schemas/review-gate-output.js +43 -0
- package/dist/session-title.js +293 -0
- package/dist/stream-json.js +498 -0
- package/dist/types.js +1 -0
- package/dist/vendor/shell-quote/LICENSE +24 -0
- package/dist/vendor/shell-quote/index.js +1 -0
- package/dist/vendor/shell-quote/parse.js +229 -0
- package/dist/vendor/smol-toml/LICENSE +24 -0
- package/dist/vendor/smol-toml/date.js +127 -0
- package/dist/vendor/smol-toml/error.js +63 -0
- package/dist/vendor/smol-toml/extract.js +100 -0
- package/dist/vendor/smol-toml/parse.js +148 -0
- package/dist/vendor/smol-toml/primitive.js +179 -0
- package/dist/vendor/smol-toml/struct.js +184 -0
- package/dist/vendor/smol-toml/util.js +113 -0
- package/dist/version.js +7 -0
- package/docs/migration.md +215 -0
- package/docs/models.md +82 -0
- package/docs/registry-releases.md +70 -0
- package/docs/safety.md +318 -0
- package/docs/session-visibility.md +32 -0
- package/hooks/README.md +9 -0
- package/hooks/hooks.json +16 -0
- package/package.json +67 -0
- package/plugins/kimi-codex/.codex-plugin/plugin.json +39 -0
- package/plugins/kimi-codex/dist/background-spawn.js +224 -0
- package/plugins/kimi-codex/dist/cli-cancellation.js +43 -0
- package/plugins/kimi-codex/dist/cli-client.js +845 -0
- package/plugins/kimi-codex/dist/commands/ask.js +327 -0
- package/plugins/kimi-codex/dist/commands/cancel.js +178 -0
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +112 -0
- package/plugins/kimi-codex/dist/commands/models.js +140 -0
- package/plugins/kimi-codex/dist/commands/not-implemented.js +4 -0
- package/plugins/kimi-codex/dist/commands/pursue.js +311 -0
- package/plugins/kimi-codex/dist/commands/registry.js +87 -0
- package/plugins/kimi-codex/dist/commands/repair-sessions.js +159 -0
- package/plugins/kimi-codex/dist/commands/replay.js +123 -0
- package/plugins/kimi-codex/dist/commands/rescue.js +333 -0
- package/plugins/kimi-codex/dist/commands/result.js +58 -0
- package/plugins/kimi-codex/dist/commands/review-gate.js +393 -0
- package/plugins/kimi-codex/dist/commands/review.js +218 -0
- package/plugins/kimi-codex/dist/commands/setup.js +1132 -0
- package/plugins/kimi-codex/dist/commands/status.js +27 -0
- package/plugins/kimi-codex/dist/commands/swarm.js +576 -0
- package/plugins/kimi-codex/dist/commands/worker.js +31 -0
- package/plugins/kimi-codex/dist/companion.js +114 -0
- package/plugins/kimi-codex/dist/config.js +22 -0
- package/plugins/kimi-codex/dist/errors.js +40 -0
- package/plugins/kimi-codex/dist/git.js +215 -0
- package/plugins/kimi-codex/dist/hooks/approval-hook.js +112 -0
- package/plugins/kimi-codex/dist/hooks/approval-policy.js +254 -0
- package/plugins/kimi-codex/dist/hooks/config-safety.js +637 -0
- package/plugins/kimi-codex/dist/hooks/install-paths.js +504 -0
- package/plugins/kimi-codex/dist/hooks/install.js +281 -0
- package/plugins/kimi-codex/dist/hooks/managed-block.js +745 -0
- package/plugins/kimi-codex/dist/hooks/review-gate-stop.js +19 -0
- package/plugins/kimi-codex/dist/job-store.js +559 -0
- package/plugins/kimi-codex/dist/jobs.js +202 -0
- package/plugins/kimi-codex/dist/kimi-command.js +72 -0
- package/plugins/kimi-codex/dist/kimi-engine.js +624 -0
- package/plugins/kimi-codex/dist/kimi-errors.js +220 -0
- package/plugins/kimi-codex/dist/kimi-home.js +9 -0
- package/plugins/kimi-codex/dist/kimi-timeouts.js +68 -0
- package/plugins/kimi-codex/dist/kimi-version-probe.js +1410 -0
- package/plugins/kimi-codex/dist/logging.js +22 -0
- package/plugins/kimi-codex/dist/native-v2-preflight.js +417 -0
- package/plugins/kimi-codex/dist/package.json +1 -0
- package/plugins/kimi-codex/dist/parsing.js +553 -0
- package/plugins/kimi-codex/dist/paths.js +27 -0
- package/plugins/kimi-codex/dist/plugin-data.js +98 -0
- package/plugins/kimi-codex/dist/process-tree.js +448 -0
- package/plugins/kimi-codex/dist/render.js +159 -0
- package/plugins/kimi-codex/dist/rescue-approval.js +951 -0
- package/plugins/kimi-codex/dist/schemas/review-gate-output.js +43 -0
- package/plugins/kimi-codex/dist/session-title.js +293 -0
- package/plugins/kimi-codex/dist/stream-json.js +498 -0
- package/plugins/kimi-codex/dist/types.js +1 -0
- package/plugins/kimi-codex/dist/vendor/shell-quote/LICENSE +24 -0
- package/plugins/kimi-codex/dist/vendor/shell-quote/index.js +1 -0
- package/plugins/kimi-codex/dist/vendor/shell-quote/parse.js +229 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/LICENSE +24 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/date.js +127 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/error.js +63 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/extract.js +100 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/parse.js +148 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/primitive.js +179 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/struct.js +184 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/util.js +113 -0
- package/plugins/kimi-codex/dist/version.js +7 -0
- package/plugins/kimi-codex/scripts/companion.sh +37 -0
- package/plugins/kimi-codex/scripts/review-gate-hook.sh +32 -0
- package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +31 -0
- package/plugins/kimi-codex/skills/kimi-ask/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-cancel/SKILL.md +25 -0
- package/plugins/kimi-codex/skills/kimi-cancel/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +31 -0
- package/plugins/kimi-codex/skills/kimi-challenge/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +30 -0
- package/plugins/kimi-codex/skills/kimi-pursue/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-replay/SKILL.md +25 -0
- package/plugins/kimi-codex/skills/kimi-replay/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +30 -0
- package/plugins/kimi-codex/skills/kimi-rescue/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-result/SKILL.md +25 -0
- package/plugins/kimi-codex/skills/kimi-result/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-review/SKILL.md +31 -0
- package/plugins/kimi-codex/skills/kimi-review/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +32 -0
- package/plugins/kimi-codex/skills/kimi-setup/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-status/SKILL.md +25 -0
- package/plugins/kimi-codex/skills/kimi-status/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +31 -0
- package/plugins/kimi-codex/skills/kimi-swarm/agents/openai.yaml +6 -0
- package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +32 -0
- package/plugins/kimi-codex/skills/kimi-swarm-write/agents/openai.yaml +6 -0
- package/scripts/companion.sh +37 -0
- package/scripts/review-gate-hook.sh +32 -0
package/docs/safety.md
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Safety
|
|
2
|
+
|
|
3
|
+
This guide describes the shipped 2.0.6 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
|
+
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Why the safety story is hook-based
|
|
8
|
+
|
|
9
|
+
kimi-code's `kimi -p` mode hard-codes `permission: 'auto'` and registers an auto-approve handler for every tool call. There is no argv flag to disable this — `-p` is a non-interactive batch mode and assumes the caller wants tools to run unattended. That's a sensible default for a coding agent invoked directly from a terminal. It's the wrong default when a plugin is forwarding a read-only review request and the user expects no shell or file mutations to happen.
|
|
10
|
+
|
|
11
|
+
The plugin installs a `PreToolUse` hook in Kimi's `config.toml`. Each host has its own hook entry. The plugin sets `KIMI_PLUGIN_CC_CMD` for each child process so the hook can select the operation's policy.
|
|
12
|
+
|
|
13
|
+
On certified native-v2 versions, every tool call reaches the hook because plugin-managed sessions never activate plan mode. The preflight, tool policy, and resume checks enforce that condition. See the no-plan construction below. An explicitly pinned legacy-v1 binary uses the earlier permission-policy chain.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌──────────────────────┐ KIMI_PLUGIN_CC_CMD=review ┌──────────────────────┐
|
|
17
|
+
│ /kimi:review │ ───────────────────────────────► │ kimi -p │
|
|
18
|
+
│ (companion.sh) │ │ (permission:auto) │
|
|
19
|
+
└──────────────────────┘ └─────────┬────────────┘
|
|
20
|
+
│
|
|
21
|
+
│ PreToolUse:
|
|
22
|
+
│ Bash
|
|
23
|
+
▼
|
|
24
|
+
┌──────────────────────┐
|
|
25
|
+
│ approval-hook.js │
|
|
26
|
+
│ reads env var, │
|
|
27
|
+
│ applies policy, │
|
|
28
|
+
│ exits 2 + stderr │
|
|
29
|
+
│ (deny) │
|
|
30
|
+
└──────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If the hook is missing or invalid, **every model-spawning command fails closed before Kimi starts**: rescue/pursue/swarm already refused, and v1.8 extends the same rule to ask/review/challenge. The review gate cannot safely block a host Stop event without enforcement, so it skips visibly instead of spawning an un-enforced Kimi turn. `/kimi:setup` also runs a two-layer probe before reporting success so missing-Node-on-PATH and broken-quoting failures surface up front instead of at first tool call. `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` remains an explicit tests/diagnostics escape hatch for hook verification; it accepts kimi-code's unsafe `permission: auto` posture but **does not** bypass `CLI_V2_HOOK_ORDER_UNSAFE` and is never a repair path.
|
|
34
|
+
|
|
35
|
+
## The managed block
|
|
36
|
+
|
|
37
|
+
`/kimi:setup` writes a marker-delimited block to `~/.kimi-code/config.toml`:
|
|
38
|
+
|
|
39
|
+
```toml
|
|
40
|
+
# === BEGIN kimi-plugin-cc-managed:claude-code (vX.Y.Z) ===
|
|
41
|
+
# DO NOT EDIT — managed by /kimi:setup. Run /kimi:setup --uninstall to remove.
|
|
42
|
+
# Host: claude-code — Claude Code and Codex each own a separate block in this
|
|
43
|
+
# shared ~/.kimi-code/config.toml; setup in one host never touches the other's.
|
|
44
|
+
# Purpose:
|
|
45
|
+
# kimi-code's `kimi -p` mode hard-codes permission='auto' and
|
|
46
|
+
# auto-approves every tool call. This hook enforces /kimi:review,
|
|
47
|
+
# /kimi:challenge, /kimi:review_gate, and /kimi:ask as read-only,
|
|
48
|
+
# and applies the workspace-bound rescue allowlist for /kimi:rescue.
|
|
49
|
+
# Without this block the plugin's safety contract collapses.
|
|
50
|
+
# Matcher field is intentionally OMITTED — kimi-code compiles the
|
|
51
|
+
# matcher with `new RegExp(...)`. An empty/missing matcher means
|
|
52
|
+
# "fire for every tool". The string "*" would throw and silently
|
|
53
|
+
# disable the hook. Do not "fix" this.
|
|
54
|
+
# The Node binary path is absolute so kimi-code's `/bin/sh -c`
|
|
55
|
+
# hook spawn doesn't need `node` on its PATH (GUI launches,
|
|
56
|
+
# LaunchAgents, etc.).
|
|
57
|
+
[[hooks]]
|
|
58
|
+
event = "PreToolUse"
|
|
59
|
+
command = "'/abs/path/to/node' '/abs/path/to/dist/hooks/approval-hook.js'"
|
|
60
|
+
timeout = 15
|
|
61
|
+
# === END kimi-plugin-cc-managed:claude-code ===
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `vX.Y.Z` marker is replaced with the live plugin version by
|
|
65
|
+
`runtime/commands/setup.ts`. Both the node binary path and the hook
|
|
66
|
+
script path are shell-quoted via `runtime/hooks/install-paths.ts`
|
|
67
|
+
before they reach the config (so a path containing spaces or quotes
|
|
68
|
+
can't bypass the safety contract through argv splitting).
|
|
69
|
+
|
|
70
|
+
Key constraints (verified by [`runtime/hooks/managed-block.ts`](../runtime/hooks/managed-block.ts) on both install and verify):
|
|
71
|
+
|
|
72
|
+
1. **No `matcher` line.** kimi-code compiles the matcher field via `new RegExp(matcher)`. An empty matcher means "fire for every tool." `new RegExp("*")` throws, which kimi-code catches and treats as "no matcher" → silently disables the hook. The installer omits the field intentionally; the verifier rejects any block that contains a `matcher = ...` line.
|
|
73
|
+
2. **Absolute Node path.** kimi-code spawns hook commands via `/bin/sh -c "<command>"`. If we wrote bare `node`, the shell would resolve it against kimi-code's PATH at execution time. On a system where kimi-code launches from a GUI/LaunchAgent with a sanitized PATH (nvm/asdf/mise users), bare `node` would exit 127 → hook protocol treats non-{0,2} as **allow** → safety contract collapses with no user-visible signal.
|
|
74
|
+
3. **TOML basic-string escaping.** The command field is a TOML basic string. Quotes, backslashes, newlines, and control characters in the resolved hook path are escaped during install; paths containing characters that cannot be safely escaped (literal `"`, raw control chars) are rejected up front with `SETUP_HOOK_PATH_UNSAFE`.
|
|
75
|
+
4. **Exact marker grammar.** The verifier rejects stray comments containing the marker tag, partial references, duplicate blocks (from a setup race), and orphan markers (from a manual edit). The same parser is used by both the installer and the per-call verifier so the two cannot disagree.
|
|
76
|
+
5. **Exact command equality, per host.** The verifier reconstructs the canonical command byte string from the current env and equality-checks the installed `command` (never a substring — a crafted `true # /path/to/approval-hook.js` runs only `true` and would auto-allow). This holds *per host id* (below): each host verifies its own block.
|
|
77
|
+
6. **Full-config parse and whole-hook-array validation.** The verifier parses the complete file with vendored `smol-toml@1.6.1` with the upstream GHSA-7w5x-hrqm-74c2 fix backported (see `runtime/vendor/smol-toml/README.md`), before inspecting the managed block. It then applies upstream's strict config schema to every configured hook (`event`, optional string `matcher`, non-empty `command`, optional integer `timeout` 1–600, no extra fields). A malformed unrelated TOML value or one invalid foreign hook can make kimi-code discard enforcement; both therefore fail verification.
|
|
78
|
+
7. **Serialized, identity-bound setup mutations.** Install and uninstall hold a private `0o600` sibling lock across the complete read-modify-write transaction. The candidate lock is published atomically; lock reads are no-follow, nonblocking, regular-file-only, and capped at 4 KiB. Acquisition is bounded, live owners are never stolen, stale recovery is guarded by a nonrecursive recovery lease, and inode/token ownership is rechecked before config mutation or lock removal. Simultaneous Claude Code/Codex setup runs therefore cannot lose either host's block, and a replaced lock is never treated as ours. A newly created Kimi home is `0o700`; atomic config writes remain `0o600`.
|
|
79
|
+
8. **Inline hook arrays are normalized before insertion.** kimi-code accepts both `hooks = [...]` and `[[hooks]]`, but appending a managed array-of-tables block after an inline assignment can produce a conflicting document. Setup converts a valid inline hook array to canonical `[[hooks]]` tables before inserting the host block and emits an explicit warning that formatting and comments inside that assignment are normalized. Surrounding config bytes are preserved.
|
|
80
|
+
|
|
81
|
+
### Host scoping (Claude Code ↔ Codex share one config)
|
|
82
|
+
|
|
83
|
+
Claude Code and Codex install this plugin to **different, version-stamped
|
|
84
|
+
paths** (`~/.claude/plugins/cache/…` vs `~/.codex/plugins/cache/…`) but read
|
|
85
|
+
the **same** `~/.kimi-code/config.toml`. So the managed block is **host-scoped**:
|
|
86
|
+
its marker carries a `:<host-id>` suffix, and each host owns, verifies, and
|
|
87
|
+
uninstalls only its **own** block.
|
|
88
|
+
|
|
89
|
+
- **Host id** (`runtime/hooks/install-paths.ts::resolveHostId`) is derived
|
|
90
|
+
version-independently from the hook path: `~/.claude/…` → `claude-code`,
|
|
91
|
+
`~/.codex/…` → `codex`, else a stable `host-<sha1>` for dev checkouts. A
|
|
92
|
+
plugin upgrade *refreshes* the same host's block rather than adding a new one.
|
|
93
|
+
- **No clobbering.** `/kimi:setup` (Claude) and `$kimi-setup` (Codex) each write
|
|
94
|
+
their own block and leave the other's byte-identical. Before v1.7.0 there was
|
|
95
|
+
a single block exact-matched to the running host, so the two hosts overwrote
|
|
96
|
+
each other and the loser's write-commands refused with "hook path drift."
|
|
97
|
+
- **Enforcement is redundant-safe.** kimi-code fires *every* `[[hooks]]` entry
|
|
98
|
+
and aggregates **any-block-wins** (first `block` wins). Both hosts' hooks run
|
|
99
|
+
the same allowlist logic, so the current host's live hook always enforces —
|
|
100
|
+
even if another host's path is stale (a stale path fails open, but the live
|
|
101
|
+
one still denies). A drifted *own* block is still caught at the per-command
|
|
102
|
+
verify gate, which prompts a re-setup for that host only.
|
|
103
|
+
- **Migration + cleanup.** A pre-v1.7.0 un-suffixed block is adopted and
|
|
104
|
+
converted in place by the current host on its next `/kimi:setup`. Orphaned,
|
|
105
|
+
marker-less `[[hooks]]` entries that are unambiguously our approval hook
|
|
106
|
+
(canonical command, `approval-hook.js` under a kimi-marketplace/kimi-plugin-cc
|
|
107
|
+
tree) are pruned **host-scoped** (v1.8.2): install and scoped uninstall touch
|
|
108
|
+
only tables whose command path derives to the *current* host. `/kimi:setup
|
|
109
|
+
--uninstall` removes only the current host's block (plus legacy + this host's
|
|
110
|
+
orphans); `/kimi:setup --uninstall --all` removes every host's block and every
|
|
111
|
+
plugin-owned table. Hand-rolled user hooks are never touched.
|
|
112
|
+
- **Markers are not durable — verification does not depend on them (v1.8.2).**
|
|
113
|
+
kimi-code persists its config via smol-toml `stringify(parse(...))` on every
|
|
114
|
+
login/settings write, which deletes ALL comments — the managed-block BEGIN/END
|
|
115
|
+
markers included — while the `[[hooks]]` table (data) survives and keeps
|
|
116
|
+
enforcing. When this host's marked block is absent, the verifier falls back to
|
|
117
|
+
a marker-less table whose decoded `command` is **byte-identical** to the
|
|
118
|
+
canonical expected command, under the same strict grammar (PreToolUse event,
|
|
119
|
+
no matcher, no keys beyond event/command/timeout). Anything weaker — drifted
|
|
120
|
+
path, matcher-bearing table, foreign keys — still refuses. The fallback never
|
|
121
|
+
applies over duplicate/orphan/invalid marked states. The next `/kimi:setup`
|
|
122
|
+
re-adorns the markers; the other host's marker-less table is that host's LIVE
|
|
123
|
+
hook and is left byte-untouched (pre-v1.8.2, the host-blind prune here caused
|
|
124
|
+
each host's setup to disarm the other after any kimi login — the "seesaw").
|
|
125
|
+
|
|
126
|
+
## The hook's per-command policy
|
|
127
|
+
|
|
128
|
+
[`runtime/hooks/approval-policy.ts`](../runtime/hooks/approval-policy.ts) is the single source of truth:
|
|
129
|
+
|
|
130
|
+
| `KIMI_PLUGIN_CC_CMD` | Allowed tools | Denied tools |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| unset / empty | everything | nothing (kimi was invoked outside the plugin) |
|
|
133
|
+
| `ask` | `Read`, `Grep`, `Glob`, `ReadMediaFile`, `TaskList`, `TaskOutput` | everything else, including `Bash`, `Write`, `Edit` |
|
|
134
|
+
| `review`, `challenge`, `review_gate` | same as `ask` | same as `ask` |
|
|
135
|
+
| `rescue` | governed by [`evaluateRescueHookRequest`](../runtime/rescue-approval.ts) against the plugin-owned `KIMI_PLUGIN_CC_WORKSPACE_ROOT` — workspace-bound shell allowlist, symlink-aware path containment, mutating-flag detection on `git`/`find`/`sed`, etc. | missing-root writes and every shell command, file edit, or write that the allowlist rejects |
|
|
136
|
+
| `swarm` | same read-only set as `ask`, plus exact `AgentSwarm` | everything else, including singular `Agent`, shell, and writes |
|
|
137
|
+
| `swarm-write` | read-only set + `AgentSwarm`; `Write`/`Edit`/`Bash` only through the rescue evaluator against the trusted ephemeral-worktree root | singular `Agent`, missing-root writes, and everything the rescue evaluator rejects |
|
|
138
|
+
| unknown label | `Read`, `Grep`, `Glob`, `ReadMediaFile`, `TaskList`, `TaskOutput` | everything else (conservative-deny for stale/misconfigured callers) |
|
|
139
|
+
|
|
140
|
+
`EnterPlanMode` and `ExitPlanMode` are explicitly denied before any per-label write evaluator for every managed label. Denied tool calls exit the hook with code 2 and write a reason to stderr. kimi-code surfaces the reason to the model, which can adapt and try a different approach (e.g., use `Read` instead of `Bash cat`).
|
|
141
|
+
|
|
142
|
+
### Native agent-core-v2: the no-plan construction (v1.10.0)
|
|
143
|
+
|
|
144
|
+
agent-core-v2 awaits external PreToolUse hooks on ordinary tool calls, but it registers `AgentPlanService` before `AgentExternalHooksService`, and the plan guard final-allows a `Write`/`Edit` whose only write access is the exact current plan file — ending listener iteration before the managed hook runs. Upstream documents no ordering contract; kimi-code 0.42.0 deleted the legacy-v1 engine, so the pin the plugin used since v1.9.6 is inert there.
|
|
145
|
+
|
|
146
|
+
The plugin therefore certifies native v2 on a different, explicitly amended basis (see [`native-v2-certification-provenance.md` §2](native-v2-certification-provenance.md#2-native-v2-entry-gate)): the plan guard is the engine's **only** chain-breaking final allow, it fires **only while plan mode is active**, and plugin-managed sessions never arm plan mode:
|
|
147
|
+
|
|
148
|
+
- The default-plan-mode setting — under EVERY spelling upstream's config loader normalizes to the `defaultPlanMode` domain (`default_plan_mode`, `defaultPlanMode`, mixed; the preflight mirrors upstream's `snakeToCamel` verbatim) — is refused before spawn by `runtime/native-v2-preflight.ts` (`CLI_V2_PLAN_MODE_CONFIGURED`, remedy: set it to `false` or remove it — never `/kimi:setup`). The key lives only in `<KIMI_CODE_HOME>/config.toml` and is read once inside session creation; the plugin parses that same file just before spawn and again at the spawn boundary.
|
|
149
|
+
- `EnterPlanMode` and `ExitPlanMode` are denied by the managed hook for every label. Nothing final-allows them, and the auto-approve gate uses a non-terminal `pass()`, so the deny wins. Verified live on 0.42.0.
|
|
150
|
+
- A session is resumed only when it is proven native-v2 plugin lineage AND a raw scan of every agent `wire.jsonl` finds no `plan_mode.*`/`plan.*` record (`KIMI_SESSION_PLAN_TAINTED`); unknown-provenance rows refuse (`KIMI_SESSION_LINEAGE_UNKNOWN`). Nothing saved is modified.
|
|
151
|
+
- v2-only experimental features the plugin does not certify (`tower`, `subagent_fork`, whether from `[experimental]` config or per-flag env) and the master `KIMI_CODE_EXPERIMENTAL_FLAG` are refused before spawn.
|
|
152
|
+
|
|
153
|
+
What the headline claim is: **every executed tool call in a plugin-managed session passes the managed hook, and the engine's sole final allow is never armed.** What it is not: a claim that the hook precedes every final allow. Because plan mode is never armed, the property does not depend on listener order at all. It is re-proven per certified tag by a mechanized source scan (`tests/audit/v2-tag-scan.test.ts`) and a live plan-mode-ON control that must still show the bypass (proving the refusal is load-bearing rather than vacuous).
|
|
154
|
+
|
|
155
|
+
Provenance: a native-v2 plan requires the `system.version` marker as the first stream-json line and requires it to equal the probed version; a legacy plan still refuses that marker. Either mismatch tears down the owned process tree before any record reaches the caller (`CLI_ENGINE_PROVENANCE_MISMATCH`). Certification is per exact version and per operation. See the current version list at the start of this guide.
|
|
156
|
+
|
|
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
|
+
|
|
159
|
+
## The rescue allowlist
|
|
160
|
+
|
|
161
|
+
For `/kimi:rescue`, the hook delegates to [`evaluateRescueHookRequest`](../runtime/rescue-approval.ts). The allowlist has been extended since the original Wire-transport implementation. Highlights:
|
|
162
|
+
|
|
163
|
+
- **File edits** (`Write`, `Edit`): the file path must resolve inside the workspace (`realpath`-based check), must not be inside `.git/`, and must not be a symlink that escapes the workspace.
|
|
164
|
+
- **Shell commands** (`Bash`): the command is parsed with `shell-quote`. Pipelines are split and each stage validated independently. Mutating flags on `git` (`commit`, `push`, `reset --hard`, ...), `find` (`-delete`, `-exec`), and `sed` (`-i`) are rejected.
|
|
165
|
+
- **Package-manager scripts**: `npm/pnpm/yarn/bun/uv run <script>` is rejected because `package.json` scripts can run anything. The `<pm> test` shorthand IS allowed (npm/pnpm/yarn test, bun test, uv test) — under the assumption that test runs are intentional and the developer trusts their own workspace's test config. If you don't, use direct test invocations (`pytest`, `tsc --noEmit`, `eslint`, …) instead and have your tooling refuse the `<pm> test` form.
|
|
166
|
+
- **Output-to-file flags**: every shell command — regardless of which binary — is rejected if its argv contains `--output`, `--output-file`, `--output-directory`, `--output-dir`, or any `<flag>=<path>` prefix form. These flags let a command write to an arbitrary path through its own report-output mechanism, bypassing the file-edit path containment check (e.g. `git diff --output=/etc/passwd`, `curl --output /tmp/payload`, `eslint . --output-file=/tmp/exfil.json`). The bare `-o` short form is NOT blanket-banned (too many read-only tools use it, e.g. `rg -o` for `--only-matching`), but the per-tool validator rejects it where the binary's semantics make it a write flag (`eslint -o`, `go -o`, `ruff check -o`, `sort -o`). `uniq IN OUT` (which writes its second operand) is rejected in pipelines.
|
|
167
|
+
- **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
|
+
- **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
|
+
- **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.
|
|
171
|
+
|
|
172
|
+
### Effective working directory (`Bash.cwd`, v1.10.0)
|
|
173
|
+
|
|
174
|
+
agent-core-v2's `Bash` tool accepts a separate `cwd` argument and executes there without asserting workspace membership; the hook payload's top-level `cwd` is the kimi process cwd, not the Bash cwd. The allowlist therefore validates `tool_input.cwd` before the command string: absent → the session work dir (the trusted root); present → must be an existing, non-symlinked directory whose realpath is inside the trusted root and not under `.git`. Everything else is denied, and the command-string policy still applies afterwards.
|
|
175
|
+
|
|
176
|
+
### Trust boundary: test runners execute workspace code
|
|
177
|
+
|
|
178
|
+
`/kimi:rescue` intentionally allows test/build runners (`go test`, `cargo test`, `pytest`, `python -m pytest`, `<pm> test`). **Running tests runs the workspace's own code** — test functions, `build.rs`, `conftest.py`, fixtures. The allowlist rejects known output and report-writing flags. It cannot confine arbitrary code executed by a test runner; that code may write outside the workspace or access the network. This is the same trust the developer already extends to their own repo. If you run `/kimi:rescue` in a workspace you do not trust, assume any allowed test runner can execute arbitrary code from that workspace.
|
|
179
|
+
|
|
180
|
+
The full table lives in `runtime/rescue-approval.ts`. Test coverage is in `tests/runtime/rescue-approval.test.ts` — every accept-shape and every reject-shape has at least one regression test.
|
|
181
|
+
|
|
182
|
+
### Trust boundary: kimi-code 0.31 custom agents and system prompts
|
|
183
|
+
|
|
184
|
+
kimi-code 0.31.0 brings custom-agent discovery and plugin system-prompt contributions to the default v1 print engine. It discovers project profiles under `<git-root>/.kimi-code/agents` and `<git-root>/.agents/agents` as well as user and enabled-plugin sources. A profile declaring `override: true` can replace a builtin name such as `agent` or `coder`; `~/.kimi-code/SYSTEM.md` can replace the default main-agent prompt, and enabled plugins can append system instructions. The bound catalog/profile is snapshotted into the upstream session, so resume preserves the selected instructions rather than proving the current repository files are unchanged.
|
|
185
|
+
|
|
186
|
+
Treat these files as **trusted model instructions** before using write-capable rescue, pursue, or swarm-write in an unfamiliar repository. They can redirect the task, remove expected tools, or replace the builtin `coder` prompt; the exact-0.31 smoke proves confinement, not task fidelity. Agent-profile fields and system-prompt text cannot themselves set hook definitions, permission mode, or cwd, cannot enlarge `runtime/hooks/approval-policy.ts`, and cannot escape swarm-write's env-pinned ephemeral root. Enabled plugins may separately contribute hooks; kimi-code's any-block-wins aggregation lets those hooks add denials but not override this plugin's managed denial. Read-only labels remain write-denying even under a hostile profile. This is an intent/prompt-injection boundary, not authority to skip the managed hook or widen the write allowlist.
|
|
187
|
+
|
|
188
|
+
## Autonomous goal mode (`/kimi:pursue`, experimental)
|
|
189
|
+
|
|
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.
|
|
191
|
+
|
|
192
|
+
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
|
+
|
|
194
|
+
If another installed host still points to an older hook, that hook may veto these goal calls: Kimi runs all matching hooks and any denial wins. Each host must be updated separately under the operator's authorization. Running setup repairs a stale path; it does not add missing permissions to old code. Never skip or remove an enforcing hook to work around this.
|
|
195
|
+
|
|
196
|
+
> **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
|
+
|
|
198
|
+
- **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.
|
|
200
|
+
- **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
|
+
- **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`.
|
|
203
|
+
|
|
204
|
+
## Read-only swarm (`/kimi:swarm`)
|
|
205
|
+
|
|
206
|
+
`/kimi:swarm` (v1.2) fans a **read-only** review out across subagents using kimi-code's `AgentSwarm` tool (kimi-code 0.12.0+). The coordinator launches one subagent per target (file/module/question); each inspects the workspace with read tools and reports findings, which the coordinator consolidates into one markdown report. It opens **zero new write surface**.
|
|
207
|
+
|
|
208
|
+
- **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
|
+
- **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.)
|
|
211
|
+
- **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
|
+
- **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`.
|
|
214
|
+
|
|
215
|
+
## Write-capable swarm (`/kimi:swarm --write`)
|
|
216
|
+
|
|
217
|
+
`/kimi:swarm --write` (v1.4) is the first WRITE mode for swarm: the coordinator and `coder` (write-capable) subagents EDIT files in parallel. It is **designed to keep direct edits in a throwaway Git worktree, subject to the test-runner trust boundary above**, and the result is handed back as a patch the user reviews before anything touches their branch.
|
|
218
|
+
|
|
219
|
+
- **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
|
+
- **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.
|
|
222
|
+
- **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.
|
|
224
|
+
- **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
|
+
- **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.
|
|
227
|
+
|
|
228
|
+
## The setup probe
|
|
229
|
+
|
|
230
|
+
`/kimi:setup` runs a two-layer probe before reporting success:
|
|
231
|
+
|
|
232
|
+
1. **Direct probe.** Spawns the hook with the same Node binary (`process.execPath`) the companion is running under. Sends synthetic PreToolUse stdin with `KIMI_PLUGIN_CC_CMD=review` + a Bash tool request. Asserts exit 2 + non-empty stderr.
|
|
233
|
+
2. **Shell probe.** Re-runs the same payload via `/bin/sh -c "<command>"` — the exact spawn shape kimi-code's hook runner uses. Same assertion.
|
|
234
|
+
|
|
235
|
+
Both must pass. The shell probe catches the case where `process.execPath` works but `/bin/sh` can't resolve the path through quoting (a `KIMI_PLUGIN_CC_NODE_BIN` override pointing at a binary in a shell-unsafe path, for example). If either probe fails, setup reports failure with the captured stderr and the user can act on a clear signal rather than discover the gap by accident.
|
|
236
|
+
|
|
237
|
+
## How to verify the install yourself
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
/kimi:setup --check
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Reports the current state without writing. The output line you want to see is `Probe: ok`. If the probe is failing, the `Details:` section shows which layer failed and what stderr was captured.
|
|
244
|
+
|
|
245
|
+
`/kimi:setup --check` is strict and exits nonzero on failure. It rejects an unloadable TOML file, any invalid configured hook, and any managed block that doesn't pass the [`parseManagedBlock`](../runtime/hooks/managed-block.ts) grammar (missing `[[hooks]]` table, wrong event, present `matcher = ...` line, duplicate blocks, orphan markers, or a stale hook-script path). `/kimi:setup` (without `--check`) can replace a malformed managed block, but it will not overwrite or silently work around invalid foreign config: if the complete candidate file would not load, setup leaves the existing file unchanged and identifies the bad hook or TOML line.
|
|
246
|
+
|
|
247
|
+
In Codex, ask `$kimi-setup` to check the active installation. Use its sanitized output for routine diagnosis. Do not print raw `config.toml` or OAuth files: they can contain credentials. Missing comment markers alone do not mean the hook is missing; the verifier also accepts a valid marker-less entry.
|
|
248
|
+
|
|
249
|
+
Follow the reported remedy. Only run setup and retry automatically when `retryable_after_setup` is literally `true` and setup succeeds.
|
|
250
|
+
|
|
251
|
+
## How to opt out
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
/kimi:setup --uninstall
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Removes the managed block from `~/.kimi-code/config.toml`. Subsequent kimi-plugin-cc command invocations:
|
|
258
|
+
|
|
259
|
+
- Every model-spawning command will refuse before Kimi starts with its command-specific `*_HOOK_NOT_INSTALLED` error.
|
|
260
|
+
- The review gate will skip visibly and allow the host to stop; it will not run an un-enforced model turn.
|
|
261
|
+
|
|
262
|
+
For tests or deliberate diagnostics only, `KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1` bypasses verification.
|
|
263
|
+
|
|
264
|
+
This diagnostic option bypasses hook verification only. It does not disable an installed hook or bypass engine certification, provenance checks, or the native-v2 preflight. Never use it to repair a failed setup.
|
|
265
|
+
|
|
266
|
+
## Refusal codes of the native-v2 contract (v1.10.0)
|
|
267
|
+
|
|
268
|
+
Every refusal below is raised BEFORE a kimi process is created (or, for the provenance mismatch, before any record reaches the caller) and is **not** repaired by `/kimi:setup` — the remedy is the named setting, binary, or a fresh session. Agents must not retry through setup on these. Every row says so explicitly with `retryable_after_setup: false` in `details` (since 1.10.1; in 1.10.0 `CLI_V2_HOOK_ORDER_UNSAFE`, `INVALID_ENV` and the legacy-v1 form of `KIMI_CAPABILITY_NOT_CERTIFIED` omitted it). The retry gate keys on the field being literally `true`, so an ABSENT field is treated as non-retryable — wrappers must do the same and never assert the field's presence.
|
|
269
|
+
|
|
270
|
+
| Code | Raised when | Remedy |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `KIMI_CAPABILITY_NOT_CERTIFIED` | the probed `kimi --version` is not in the operation's exact certified set (for example, an interactive CLI update installed an uncertified version) | update kimi-plugin-cc to a release certifying that version, or point `KIMI_PLUGIN_CC_KIMI_BIN` at a certified binary |
|
|
273
|
+
| `CLI_V2_PLAN_MODE_CONFIGURED` | `<KIMI_CODE_HOME>/config.toml` sets the default-plan-mode key (any spelling normalizing to `defaultPlanMode`) to anything but literal `false` | set it to `false` or remove it |
|
|
274
|
+
| `CLI_V2_CONFIG_UNREADABLE` | that config cannot be read safely (symlink, oversized, unparseable TOML, I/O error) — plan mode cannot be verified off | fix or remove the unreadable file |
|
|
275
|
+
| `CLI_V2_EXPERIMENTAL_UNSAFE` | `[experimental] tower` / `subagent_fork` (config) or `KIMI_CODE_EXPERIMENTAL_TOWER` / `…_SUBAGENT_FORK` (env) is truthy | unset the listed selector(s) |
|
|
276
|
+
| `CLI_V2_HOOK_ORDER_UNSAFE` | the master `KIMI_CODE_EXPERIMENTAL_FLAG` is truthy (`1|true|yes|on`) | unset it |
|
|
277
|
+
| `KIMI_SESSION_PLAN_TAINTED` | the session to resume has a `plan_mode.*`/`plan.*` record in any agent `wire.jsonl` | start a fresh session (nothing saved is modified) |
|
|
278
|
+
| `KIMI_SESSION_JOURNAL_UNAVAILABLE` | the session's journals cannot be found or read safely (missing dir, symlinked entry, oversized, not a regular file) | start a fresh session |
|
|
279
|
+
| `KIMI_SESSION_LINEAGE_UNKNOWN` | the job to resume has no proven engine provenance, no recorded source job, or a source that owns a different session | start a fresh session; the old job's status/result/replay remain available |
|
|
280
|
+
| `KIMI_SESSION_ENGINE_MISMATCH` | the source session ran on a different engine than the selected one | start a fresh session |
|
|
281
|
+
| `CLI_ENGINE_PROVENANCE_MISMATCH` | the started engine contradicts the plan: no leading `system.version` marker on a v2 plan, a marker on a v1 plan, a marker whose version is malformed or ≠ the probed version, or any line (meta, unknown-role, malformed) ahead of the marker | the process tree is torn down before any record is delivered; check for a binary swap (`KIMI_PLUGIN_CC_KIMI_BIN`, auto-update) |
|
|
282
|
+
| `INVALID_ENV` (`reserved_flag`) | `KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS` carries a kimi root flag (session, prompt, permission/plan, model/profile/roots, or `-V`/`-h`) — the prefix is a launcher shim only | remove the flag from the prefix |
|
|
283
|
+
|
|
284
|
+
## What this safety story does NOT cover
|
|
285
|
+
|
|
286
|
+
- **Repository Git configuration and internal subprocesses.** The managed hook gates tool calls, not every internal subprocess. Upstream's explorer profile builds Git context with `git status` and `git log` outside the child tool hook. On 2.0.2 and 2.1.1, repository settings such as `core.fsmonitor` can execute a helper there, including during read-only swarm startup. Version 2.1.0 hardens these Git invocations; 2.1.1 rolls that hardening back to the previously certified behavior. Use trusted repositories and Git configuration. Certification does not establish a sandbox for hostile repositories or arbitrary Git helpers.
|
|
287
|
+
- **Malicious kimi-code binaries.** The threat model assumes the user trusts the kimi-code binary they installed. A hostile binary can ignore the hook entry in its own config.
|
|
288
|
+
- **TOCTOU between path check and edit.** The rescue allowlist resolves and checks paths at allowlist time. Between that check and the actual file write, an attacker with workspace write access could swap a symlink. The mitigation is workspace-write-trust — if untrusted code can edit your workspace, you have bigger problems than this plugin.
|
|
289
|
+
- **Custom kimi-code skill libraries that bypass the hook.** Hooks fire on tool calls, not on skill activations. A skill that calls `Bash` will be hooked; a skill that performs file ops via a Node binding that doesn't surface as a tool call won't be. The default kimi-code skill catalog uses the standard tool surface.
|
|
290
|
+
- **The calling host.** The plugin constrains its Kimi subprocesses. Claude Code and Codex have their own tool permissions; this hook does not govern the host's other tools.
|
|
291
|
+
- **Compute exhaustion (no per-turn step cap on kimi-code ≥ 0.6.0).** kimi-code 0.6.0 (PR #186) removed the default 1000-step-per-turn ceiling; absent an explicit `max_steps_per_turn` in config, a turn can loop tool calls without an upstream bound. This is **not a write bypass** — every tool call still passes the PreToolUse hook and (for rescue) the workspace allowlist. The plugin bounds wall-clock and reaps the descendant process tree via its AbortController budget + SIGTERM→SIGKILL cancellation, so a runaway `/kimi:rescue` is cancellable; but if you want a hard step ceiling, set `max_steps_per_turn` in your kimi-code config.
|
|
292
|
+
- **A poisoned `KIMI_MODEL_*` child environment.** kimi-code 0.6.0 (PR #212) added an opt-in env channel: when `KIMI_MODEL_NAME` is set, kimi synthesizes a provider (base URL + API key) as the default model. `kimi -p` inherits the plugin's parent environment, so a pre-poisoned `KIMI_MODEL_BASE_URL`/`KIMI_MODEL_API_KEY` could reroute model traffic (which may carry workspace file contents from review/ask). The plugin does not sanitize the child environment — this is the same parent-env trust the host already extends. It does **not** touch hook or permission config: kimi-code only writes the synthesized provider/model/thinking entries (never `hooks`), and strips them on any config write-back, so `/kimi:setup`'s managed hook block can neither clobber nor be clobbered by it.
|
|
293
|
+
- **Out-of-band binary drift (kimi-code self-upgrades by default on ≥ 0.8.0).** kimi-code 0.8.0 (PR #334) added background automatic upgrades, enabled by default (`autoInstall: true`). The plugin's own `kimi -p` spawns are unaffected — in print mode the install source is forced `unsupported`, so a plugin spawn never swaps the binary mid-flight — but a user who *also* runs the interactive `kimi` TUI can be silently upgraded to a kimi-code the plugin has not compat-audited. This is a *version-assurance* boundary, not permission to guess. Setup emits an "outside tested range" warning and can complete, while every model-spawning command independently probes the exact command tuple and refuses an uncertified version before Kimi starts (`KIMI_CAPABILITY_NOT_CERTIFIED`; certification is per EXACT version on native v2 and per minor on legacy-v1). Update the plugin to a release that certifies the new version or point `KIMI_PLUGIN_CC_KIMI_BIN` at a certified binary; `KIMI_PLUGIN_CC_SKIP_VERSION_PROBE` is tests/smoke only. The current native-v2 versions are listed at the start of this guide. Legacy-v1 uses the tested minors through 0.41.x, with per-operation minimums, for an explicitly pinned binary. Every spawn probes the exact tuple and refuses an uncertified version (`KIMI_CAPABILITY_NOT_CERTIFIED`, see the refusal-code table above).
|
|
294
|
+
- **New permission approval hook events are observability, not a decision surface.** kimi-code 0.8.0 (PR #336) added `PermissionRequest`/`PermissionResult` hook events. A user *can* register `[[hooks]]` blocks for them in `config.toml`, but they fire fire-and-forget (the result is discarded) and only inside the interactive approval/ask path — which `kimi -p` auto mode shadows. They **cannot** approve a tool the PreToolUse hook denied, cannot run on the plugin's `-p` path, and live in a disjoint event bucket from the managed `PreToolUse` block (no impersonation). Setup validates every configured hook against the reviewed schema for the probed CLI version. Version 2.0.0 has an explicitly reviewed schema; unknown major-version schemas fail closed.
|
|
295
|
+
|
|
296
|
+
## Known limitation: Node version manager switches
|
|
297
|
+
|
|
298
|
+
The verifier does exact equality on the installed `command = "..."` against the canonical command built by `runtime/hooks/install-paths.ts`. It prefers a stable absolute Node path when that path resolves to the running interpreter. If you use `nvm`, `asdf`, `mise`, or `fnm` and switch the active Node version after `/kimi:setup`, the canonical command's Node binary path changes and the verifier rejects the previously-installed block. Every model-spawning command will refuse; the review gate will skip.
|
|
299
|
+
|
|
300
|
+
The workaround is to re-run `/kimi:setup` after any Node version switch. The strict behavior is deliberate — loosening the exact-equality check to "survive" Node moves would reopen the crafted-command bypass the audit closed (reports 27/28), so it stays strict by design.
|
|
301
|
+
|
|
302
|
+
**Soft-recovery (H4):** to make that strict failure *legible* instead of cryptic, the verifier classifies a command mismatch when the block is otherwise valid. Rather than dumping the raw expected-vs-got command, it reports the drift class — e.g. *"Node binary drift: the installed hook pins `/opt/homebrew/Cellar/node/26.0.0/bin/node`, which no longer exists on disk (this companion runs `…/26.3.0/…`) — the classic Node-upgrade / version-manager drift … Run /kimi:setup to re-pin"*, or *"Hook script path drift … likely a plugin update or move"* when the version-stamped plugin path moved instead. A gone interpreter is the unambiguous signal. The same classified reason surfaces from `/kimi:setup --check`. This changes only the message; the strict installed/not-installed decision is unchanged.
|
|
303
|
+
|
|
304
|
+
**Stable Node path (v1.9.0).** `process.execPath` is fully symlink-resolved, so under Homebrew it names `…/Cellar/node/<version>/bin/node` — a path that changes on every `brew upgrade node` and therefore invalidated **both** hosts' blocks at once, with no user-visible cause. `resolveNodeBinary` now prefers `process.argv0` (the name the interpreter was actually invoked with; `companion.sh` resolves `node` through the stable `/opt/homebrew/bin/node` symlink), **but only when that path is absolute AND `realpath`-identical to `process.execPath`**. The guard is the safety argument: the substitution can pick a more stable *name* for the interpreter already running, and provably never a *different* binary. Any doubt falls back to `execPath`. Exact-equality verification, absolute-path enforcement, and the shell probe are unchanged. It also keeps hook and companion on one interpreter by construction — previously the companion could run a freshly upgraded Node while the hook stayed pinned to a Cellar path `brew` had deleted, which fails to spawn and (exit 127) is read as **allow**. Version-manager users (nvm/asdf/mise/fnm) expose version-stamped paths directly, so `argv0 === execPath` and the limitation above still applies to them unchanged.
|
|
305
|
+
|
|
306
|
+
## Hook-path drift is repaired by the caller, never by the verifier (v1.9.0)
|
|
307
|
+
|
|
308
|
+
Plugin install paths are version-stamped (`…/kimi-marketplace/kimi/<version>/dist/hooks/approval-hook.js`), so a plugin update moves the hook script and the recorded command stops matching. That refusal is correct and fail-closed, but it is also routine, so refusals now carry a **machine-readable** classification alongside the prose reason:
|
|
309
|
+
|
|
310
|
+
`HookInstallStatus.drift` = `{axis: "hook-script" | "node-bin" | "both", installedCommand, expectedCommand}`, surfaced in every refusal's `RuntimeError.details` as `drift_axis` / `retryable_after_setup`. It is populated **only** when a structurally sound block's command names different paths, and is derived **after** byte-exact equality has already failed — it can never influence the installed decision, exactly like the H4 prose classification. Refusals that re-pinning cannot fix (unresolvable command, unreadable config, invalid hook set, duplicate/orphan markers, missing script) carry no drift at all.
|
|
311
|
+
|
|
312
|
+
Agents use `retryable_after_setup`, not `drift_axis` alone. They run setup for the affected host and retry once only when that field is literally `true` and setup exits successfully. This includes a moved hook script and a Node path spelling change proven to name the same interpreter. A different interpreter is not automatically retryable. `node-bin-not-executable` requires fixing the reported Node path; setup alone is not its remedy.
|
|
313
|
+
|
|
314
|
+
**The verifier never repairs the config itself.** An in-verifier auto-repin was specified and rejected after two independent adversarial reviews found: a lock self-deadlock (the repin path and the install transaction both take a non-reentrant lock); a false "target cannot be steered" premise (`KIMI_PLUGIN_CC_HOOK_SCRIPT`/`NODE_BIN`/`HOST_ID` override resolution, and every proposed gate reads the same env, so they corroborate nothing); no version-monotonicity bound, letting a stale session pin the *global* block down to a pre-1.0.3 hook carrying the CRITICAL exec-delegation bypass; and a write→spawn TOCTOU in which one session repins to a deleted path inside another's spawn window, producing exit 127 → allow → an unenforced `rescue` run. Keeping repair in the caller makes it a **visible, permission-gated action in the transcript** instead of a silent mutation of a security surface. Full analysis: `.claude/hook-pin-durability-spec-2026-07-25.md`.
|
|
315
|
+
|
|
316
|
+
## Reporting safety issues
|
|
317
|
+
|
|
318
|
+
If you find a way to coax /kimi:review, /kimi:challenge, /kimi:review_gate, or /kimi:ask into running a non-read tool — or to make /kimi:rescue mutate `.git/` or escape the workspace — please open a GitHub security advisory rather than a public issue.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Session visibility and native titles
|
|
2
|
+
|
|
3
|
+
Plugin-created sessions live in Kimi Code's local session store and can appear in both Kimi Code Desktop and Node's `kimi web`. Keep Web available: the migration away from the old Python CLI does not retire Node Web.
|
|
4
|
+
|
|
5
|
+
After a user-command run settles, the plugin fills missing prompt-preview metadata and notifies the shared index. This addresses the native-v2 headless path leaving `lastPrompt` absent, which causes Desktop/Web to hide a completed session as empty. The preview comes from the submitted prompt or task objective, is redacted using Kimi's credential-pattern rules, and is limited to 4,000 characters. It is a local display preview, not a new model prompt.
|
|
6
|
+
|
|
7
|
+
## Native generated titles
|
|
8
|
+
|
|
9
|
+
New native-v2 sessions receive a readable fallback such as `Kimi Review: ...`. The fallback is marked **replaceable**, so native title generation can replace it. Existing manual titles and already-generated titles remain unchanged. Older plugin titles marked custom are also preserved because they cannot always be distinguished from a human rename.
|
|
10
|
+
|
|
11
|
+
In Kimi Code Desktop 1.0.1, open the session's **Rename** control and select **Gen Title** to generate a title from its conversation. Native automatic generation also runs after an eligible turn finishes in Desktop. Merely opening an already-completed plugin session does not trigger it. Kimi's native generator uses its managed login; unavailable authentication or backend errors can leave the fallback in place.
|
|
12
|
+
|
|
13
|
+
The plugin does not silently call the native title-generation endpoint. That endpoint resumes the session, activates agent services and fires session-start hooks; it is not a metadata-only API. Selecting Gen Title in Desktop is a deliberate native-app action. The plugin makes no extra title-generation provider calls and does not read OAuth credentials.
|
|
14
|
+
|
|
15
|
+
## Repair existing sessions
|
|
16
|
+
|
|
17
|
+
Use the companion bundled with the updated plugin (or a freshly built checkout). Run while the target Kimi sessions are idle:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
scripts/companion.sh repair-sessions
|
|
21
|
+
scripts/companion.sh repair-sessions --apply
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The first command previews changes in the current repository; the second applies them. Add `--all` to cover repositories in that host's plugin job store. Claude Code and Codex have separate stores, so run against each relevant host. For a checkout repairing an existing host, select its data parent explicitly with `KIMI_PLUGIN_CC_DATA`; do not point it at the inner `kimi-plugin-cc` directory.
|
|
25
|
+
|
|
26
|
+
The command reports JSON status counts and job/session IDs, without prompt bodies. It only considers completed, proven native-v2 user-command sessions and excludes sessions with a running job in that store. It recovers the original prompt from a bounded invocation-log prefix and verifies its digest against the job record. It leaves job records, logs, journals, titles and existing nonempty previews unchanged. No model is invoked.
|
|
27
|
+
|
|
28
|
+
`unavailable-verified-prompt` means the source log is missing, unsafe, too large at its start, or does not match the recorded invocation. It is skipped rather than guessed from an answer summary. `state-changed` means a concurrent metadata change was detected: retry when idle. `index-failed` means metadata was saved but index notification failed: fix the directory problem and retry; an unchanged session is still re-indexed on apply.
|
|
29
|
+
|
|
30
|
+
The index may need its next reconciliation and the UI may need a list refresh before repaired sessions appear. A native session already loaded in another process may hold older in-memory metadata. Repair is not coordinated by a cross-process native lock; keep target sessions idle and reload their view afterward.
|
|
31
|
+
|
|
32
|
+
Write-swarm sessions belong to their temporary worktree, which is removed after patch capture. Their global history can be visible while an original-project filter omits them. The repair does not rewrite their cwd or change their execution provenance.
|
package/hooks/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# hooks
|
|
2
|
+
|
|
3
|
+
Claude Code hook definitions. Currently one opt-in hook is shipped:
|
|
4
|
+
|
|
5
|
+
- `hooks.json` — registers a `Stop` hook that routes through
|
|
6
|
+
`${CLAUDE_PLUGIN_ROOT}/scripts/review-gate-hook.sh` and launches the compiled review-gate
|
|
7
|
+
runtime (`dist/hooks/review-gate-stop.js`). Disabled by default; toggle with
|
|
8
|
+
`/kimi:setup --enable-review-gate` / `--disable-review-gate`. Fails open: malformed or
|
|
9
|
+
timed-out gate output becomes a warning, never a silent block.
|
package/hooks/hooks.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kimi-plugin-cc",
|
|
3
|
+
"version": "2.0.7",
|
|
4
|
+
"description": "Kimi for Claude Code and Codex: code questions, reviews, fixes, and parallel tasks through the local Kimi Code CLI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.5.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"check": "bun run build && bun run check:surfaces && bunx tsc --noEmit && bun test && git diff --exit-code -- dist plugins/kimi-codex && UNTRACKED=\"$(git ls-files --others --exclude-standard -- dist plugins/kimi-codex)\" && { [ -z \"$UNTRACKED\" ] || { echo \"drift gate: untracked dist/codex files (stage them and re-run check):\"; echo \"$UNTRACKED\"; exit 1; }; }",
|
|
11
|
+
"check:surfaces": "bun run scripts/generate-surfaces.ts --check",
|
|
12
|
+
"generate:surfaces": "bun run scripts/generate-surfaces.ts",
|
|
13
|
+
"build": "rm -rf dist && bunx tsc -p tsconfig.build.json && mkdir -p dist/vendor/shell-quote dist/vendor/smol-toml && cp runtime/vendor/shell-quote/LICENSE dist/vendor/shell-quote/LICENSE && cp runtime/vendor/smol-toml/*.js runtime/vendor/smol-toml/LICENSE dist/vendor/smol-toml/ && echo '{\"type\":\"module\"}' > dist/package.json",
|
|
14
|
+
"companion": "node --import tsx runtime/companion.ts",
|
|
15
|
+
"test": "bun test",
|
|
16
|
+
"smoke:real": "KIMI_PLUGIN_CC_SMOKE=1 bun test tests/runtime/real-binary-smoke.test.ts",
|
|
17
|
+
"check:package": "node scripts/check-package.mjs"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/bun": "^1.4.2",
|
|
21
|
+
"@types/node": "^26.5.1",
|
|
22
|
+
"smol-toml": "1.8.0",
|
|
23
|
+
"tsx": "^4.23.13",
|
|
24
|
+
"typescript": "^7.0.2"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {},
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"author": "Xule Lin",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/linxule/kimi-plugin-cc.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/linxule/kimi-plugin-cc#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/linxule/kimi-plugin-cc/issues"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"claude-code",
|
|
39
|
+
"codex",
|
|
40
|
+
"kimi",
|
|
41
|
+
"plugin",
|
|
42
|
+
"code-review"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"registry": "https://registry.npmjs.org/"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
".claude-plugin/",
|
|
50
|
+
".agents/plugins/marketplace.json",
|
|
51
|
+
"agents/",
|
|
52
|
+
"commands/",
|
|
53
|
+
"hooks/",
|
|
54
|
+
"dist/",
|
|
55
|
+
"plugins/kimi-codex/",
|
|
56
|
+
"scripts/companion.sh",
|
|
57
|
+
"scripts/review-gate-hook.sh",
|
|
58
|
+
"docs/safety.md",
|
|
59
|
+
"docs/migration.md",
|
|
60
|
+
"docs/models.md",
|
|
61
|
+
"docs/session-visibility.md",
|
|
62
|
+
"README.md",
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"CHANGELOG.md",
|
|
65
|
+
"docs/registry-releases.md"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kimi",
|
|
3
|
+
"version": "2.0.7",
|
|
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.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "linxule"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/linxule/kimi-plugin-cc",
|
|
9
|
+
"repository": "https://github.com/linxule/kimi-plugin-cc",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"kimi",
|
|
13
|
+
"kimi-code",
|
|
14
|
+
"review",
|
|
15
|
+
"code-review",
|
|
16
|
+
"delegation",
|
|
17
|
+
"multi-model"
|
|
18
|
+
],
|
|
19
|
+
"skills": "./skills/",
|
|
20
|
+
"interface": {
|
|
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.",
|
|
24
|
+
"developerName": "linxule",
|
|
25
|
+
"category": "Developer Tools",
|
|
26
|
+
"capabilities": [
|
|
27
|
+
"Code Review",
|
|
28
|
+
"Local Shell",
|
|
29
|
+
"Write"
|
|
30
|
+
],
|
|
31
|
+
"websiteURL": "https://github.com/linxule/kimi-plugin-cc",
|
|
32
|
+
"defaultPrompt": [
|
|
33
|
+
"Use $kimi-review to review my current diff.",
|
|
34
|
+
"Use $kimi-ask to explain this repository flow.",
|
|
35
|
+
"Use $kimi-rescue to delegate a bounded fix."
|
|
36
|
+
],
|
|
37
|
+
"brandColor": "#0F766E"
|
|
38
|
+
}
|
|
39
|
+
}
|