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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { constants as fsConstants, existsSync } from "node:fs";
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { validateKimiHookSetForEnvironment } from "./config-safety.js";
|
|
5
|
+
import { evaluateInstalled } from "./managed-block.js";
|
|
6
|
+
import { resolveHostId, tryBuildExpectedHookCommand } from "./install-paths.js";
|
|
7
|
+
import { resolveKimiHome } from "../kimi-home.js";
|
|
8
|
+
export async function verifyHookInstalled(env) {
|
|
9
|
+
const configPath = resolveKimiCodeConfigPath(env);
|
|
10
|
+
if (env.KIMI_PLUGIN_CC_SKIP_HOOK_CHECK === "1") {
|
|
11
|
+
return { installed: true, configPath };
|
|
12
|
+
}
|
|
13
|
+
// Canonical expected shell command for the current env. If this
|
|
14
|
+
// can't be resolved (KIMI_PLUGIN_CC_NODE_BIN not absolute,
|
|
15
|
+
// install-paths module can't infer plugin root, etc.) treat the hook
|
|
16
|
+
// as un-verifiable — installed=false with a structured reason. The
|
|
17
|
+
// caller's stderr warning surfaces the underlying error code.
|
|
18
|
+
const expected = tryBuildExpectedHookCommand(env);
|
|
19
|
+
if ("error" in expected) {
|
|
20
|
+
return {
|
|
21
|
+
installed: false,
|
|
22
|
+
reason: `unable to resolve canonical hook command for this companion: ${expected.error.message}`,
|
|
23
|
+
configPath,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
let raw;
|
|
27
|
+
try {
|
|
28
|
+
raw = await readFile(configPath, "utf8");
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const code = err.code;
|
|
32
|
+
if (code === "ENOENT") {
|
|
33
|
+
return {
|
|
34
|
+
installed: false,
|
|
35
|
+
reason: "kimi-code config file does not exist",
|
|
36
|
+
configPath,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
installed: false,
|
|
41
|
+
reason: `failed to read kimi-code config: ${err.message}`,
|
|
42
|
+
configPath,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const hookSet = await validateKimiHookSetForEnvironment(raw, env);
|
|
46
|
+
if (!hookSet.valid) {
|
|
47
|
+
return {
|
|
48
|
+
installed: false,
|
|
49
|
+
reason: hookSet.reason ?? "configured hooks failed whole-array validation",
|
|
50
|
+
configPath,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Pass `nodeExists` so a command MISMATCH is classified into an actionable
|
|
54
|
+
// H4 diagnosis (Node upgrade / version-manager switch vs. plugin path drift)
|
|
55
|
+
// instead of a raw expected-vs-got dump. Classification only refines the
|
|
56
|
+
// reason; it never changes the installed=false decision.
|
|
57
|
+
//
|
|
58
|
+
// `hostId` selects THIS host's block in the shared config so Claude Code and
|
|
59
|
+
// Codex verify their own managed block independently (v1.7.0 host scoping).
|
|
60
|
+
const check = evaluateInstalled(raw, expected.command, {
|
|
61
|
+
hostId: resolveHostId(env, expected.hookScriptPath),
|
|
62
|
+
nodeExists: (binPath) => existsSync(binPath),
|
|
63
|
+
});
|
|
64
|
+
if (!check.installed) {
|
|
65
|
+
return {
|
|
66
|
+
installed: false,
|
|
67
|
+
reason: check.reason,
|
|
68
|
+
configPath,
|
|
69
|
+
// Forwarded verbatim. Present only for a parseable path/binary mismatch;
|
|
70
|
+
// the earlier short-circuits above (unresolvable command, unreadable
|
|
71
|
+
// config, invalid hook set) return before this point and therefore never
|
|
72
|
+
// carry drift — those are not re-pin-recoverable.
|
|
73
|
+
...(check.drift !== undefined ? { drift: check.drift } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// The command matched byte-for-byte, but that alone doesn't prove the
|
|
77
|
+
// hook can run — confirm the script it points at still exists and is
|
|
78
|
+
// readable (see point 3 above). Fail closed on any stat/access error.
|
|
79
|
+
try {
|
|
80
|
+
await access(expected.hookScriptPath, fsConstants.R_OK);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return {
|
|
84
|
+
installed: false,
|
|
85
|
+
reason: `hook script ${expected.hookScriptPath} is missing or unreadable — run /kimi:setup to reinstall`,
|
|
86
|
+
configPath,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// ...and confirm the pinned INTERPRETER is executable.
|
|
90
|
+
//
|
|
91
|
+
// SCOPE — read this before widening the claim. This is a NARROW BACKSTOP, not
|
|
92
|
+
// the thing that catches a dead Node. Two adversarial reviews (2026-07-25)
|
|
93
|
+
// rejected the original, wider rationale, and it was verified empirically:
|
|
94
|
+
//
|
|
95
|
+
// - It does NOT catch a dangling Homebrew symlink. A dangling symlink cannot
|
|
96
|
+
// be exec'd at all, and `command -v node` skips it, so companion.sh (which
|
|
97
|
+
// `exec`s the binary it resolves, scripts/companion.sh:34,50) lands on a
|
|
98
|
+
// DIFFERENT node. The expected command is recomputed from the live process
|
|
99
|
+
// on every call, so it changes, and plain byte-exact equality above
|
|
100
|
+
// already refuses — with a RECOVERABLE `drift` that /kimi:setup converges.
|
|
101
|
+
// - It does NOT catch the 2026-07-25 dead-node zombie (a config pinning
|
|
102
|
+
// ~/.hermes/node). That is a byte mismatch, likewise caught above.
|
|
103
|
+
// - On the default path it is close to self-evident: `resolveNodeBinary`
|
|
104
|
+
// returns the path THIS process was launched through, which therefore
|
|
105
|
+
// existed and was executable moments ago.
|
|
106
|
+
//
|
|
107
|
+
// THE EXACT PREDICATE it covers: config and expected command still match
|
|
108
|
+
// byte-for-byte, yet the Node token embedded in that command fails X_OK AT
|
|
109
|
+
// VERIFICATION TIME. Three reachable ways to get there:
|
|
110
|
+
//
|
|
111
|
+
// 1. An absolute `KIMI_PLUGIN_CC_NODE_BIN` naming a binary that is NOT the
|
|
112
|
+
// running interpreter and is already dead — e.g. a pinned `nvm` version
|
|
113
|
+
// later `nvm uninstall`ed — reached by a direct `node dist/companion.js`
|
|
114
|
+
// that bypasses companion.sh's exec+version gate. The override is
|
|
115
|
+
// returned verbatim, so it is never proven by the running process.
|
|
116
|
+
// 2. POST-LAUNCH MUTATION on the DEFAULT path (this one is easy to miss):
|
|
117
|
+
// the interpreter is valid at launch, so the process starts, and is then
|
|
118
|
+
// `chmod -x`'d. `realpathSync` still resolves it, so `preferStableNodePath`
|
|
119
|
+
// keeps argv0 and the bytes are unchanged — equality passes, X_OK fails.
|
|
120
|
+
// 3. Same, but the pathname is REMOVED after launch. `realpathSync` throws
|
|
121
|
+
// and we fall back to `execPath`, which in argv0===execPath layouts
|
|
122
|
+
// (nvm/asdf) is the identical spelling — so equality still passes while
|
|
123
|
+
// the path is gone.
|
|
124
|
+
//
|
|
125
|
+
// In all three the old behavior was installed=true → `/bin/sh -c` exits 127 →
|
|
126
|
+
// kimi-code reads any non-2 exit as ALLOW. Converting that to a refusal is
|
|
127
|
+
// the whole value. Cases 2 and 3 are also why setup's stronger execution
|
|
128
|
+
// probe is not a substitute: it runs at install/--check, whereas this reruns
|
|
129
|
+
// before every model spawn and so catches degradation since the last probe.
|
|
130
|
+
//
|
|
131
|
+
// It proves the exec bit ONLY — not a working Node, not the right arch, not
|
|
132
|
+
// >=22.5. The real proof is setup's shell probe, which EXECUTES the byte-
|
|
133
|
+
// identical command (setup.ts::probeHook) and now fails install nonzero. The
|
|
134
|
+
// verifier runs on every spawn and cannot afford a fork, so it approximates.
|
|
135
|
+
//
|
|
136
|
+
// Fail-closed on ANY errno, deliberately diverging from background-spawn.ts,
|
|
137
|
+
// which ignores errnos outside ENOENT/EACCES/EPERM. Spawning can afford to try
|
|
138
|
+
// and see; a security verifier cannot. Do not "harmonize" these.
|
|
139
|
+
//
|
|
140
|
+
// Not re-pin-recoverable: no `drift` is attached (drift only exists when the
|
|
141
|
+
// equality check FAILS, so it can never displace a recoverable path), and
|
|
142
|
+
// `hookRefusalDetails` therefore omits `retryable_after_setup`, which agent
|
|
143
|
+
// callers map to "surface and stop." Setup cannot conjure a working Node.
|
|
144
|
+
try {
|
|
145
|
+
await access(expected.nodeBin, fsConstants.X_OK);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return {
|
|
149
|
+
installed: false,
|
|
150
|
+
reason: `hook interpreter ${expected.nodeBin} is missing or not executable, so the PreToolUse hook ` +
|
|
151
|
+
`cannot spawn (kimi-code reads a failed hook as ALLOW). Repair the Node install, or set ` +
|
|
152
|
+
`KIMI_PLUGIN_CC_NODE_BIN to a valid Node >=22.5 executable and run /kimi:setup.`,
|
|
153
|
+
configPath,
|
|
154
|
+
// Machine-readable discriminator: this is the ONE refusal whose remedy is
|
|
155
|
+
// NOT "run /kimi:setup" (LLM-caller discipline — an agent cannot read the
|
|
156
|
+
// prose above, and every other refusal emits an identical `{config_path}`).
|
|
157
|
+
refusalKind: "node-bin-not-executable",
|
|
158
|
+
nodeBin: expected.nodeBin,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return { installed: true, configPath };
|
|
162
|
+
}
|
|
163
|
+
function resolveKimiCodeConfigPath(env) {
|
|
164
|
+
return path.join(resolveKimiHome(env), "config.toml");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Format a stderr-suitable warning message for the missing-hook case.
|
|
168
|
+
* Single source of truth so review.ts / review-gate.ts / ask.ts emit
|
|
169
|
+
* identical language.
|
|
170
|
+
*/
|
|
171
|
+
/**
|
|
172
|
+
* Structured `details` payload for a hook-refusal `RuntimeError`. Single source
|
|
173
|
+
* of truth so all five model-spawning commands emit the same machine-readable
|
|
174
|
+
* shape — an LLM caller cannot see stderr, so anything it must act on has to
|
|
175
|
+
* ride the error's structured context (LLM-caller discipline, v0.3.6+).
|
|
176
|
+
*
|
|
177
|
+
* `drift_axis === "hook-script"` is the ONLY value a caller may treat as
|
|
178
|
+
* retryable (run /kimi:setup for this host, retry once). Its absence means the
|
|
179
|
+
* refusal is not a moved-path problem and re-running setup will not fix it.
|
|
180
|
+
*/
|
|
181
|
+
/**
|
|
182
|
+
* Caller-facing self-heal protocol, appended to every hook-refusal message.
|
|
183
|
+
*
|
|
184
|
+
* The slash-command wrappers are deliberately thin, so the retry protocol
|
|
185
|
+
* rides the refusal itself — the LLM caller reads it on stdout at exactly the
|
|
186
|
+
* moment it applies, and one source here serves both hosts and all surfaces
|
|
187
|
+
* (commands, agents, Codex skills) without duplicating it in each file.
|
|
188
|
+
* Mirrors the fuller contract in agents/*.md: re-pin via setup, retry ONCE,
|
|
189
|
+
* only on `retryable_after_setup: true` AND a zero setup exit; never the
|
|
190
|
+
* skip env. Embeds the resolved plugin root so the command is runnable as-is.
|
|
191
|
+
*/
|
|
192
|
+
export function hookRefusalRetryProtocol(env) {
|
|
193
|
+
const root = env.CLAUDE_PLUGIN_ROOT ?? env.PLUGIN_ROOT ?? "${CLAUDE_PLUGIN_ROOT}";
|
|
194
|
+
return (`If details shows retryable_after_setup:true (routine after a plugin update — the ` +
|
|
195
|
+
`version-stamped hook path moved), run \`${root}/scripts/companion.sh setup\` and, only ` +
|
|
196
|
+
`if it exits 0, retry the refused command once. Anything else — or a second refusal — ` +
|
|
197
|
+
`surface the refusal instead of retrying.`);
|
|
198
|
+
}
|
|
199
|
+
export function hookRefusalDetails(status) {
|
|
200
|
+
return {
|
|
201
|
+
config_path: status.configPath,
|
|
202
|
+
// Routes the caller to "tell the human to repair Node" instead of the
|
|
203
|
+
// default "run /kimi:setup and retry" reflex. Never co-occurs with `drift`:
|
|
204
|
+
// drift requires the equality check to FAIL, this requires it to PASS.
|
|
205
|
+
...(status.refusalKind !== undefined
|
|
206
|
+
? { refusal_kind: status.refusalKind, node_bin: status.nodeBin }
|
|
207
|
+
: {}),
|
|
208
|
+
...(status.drift !== undefined
|
|
209
|
+
? {
|
|
210
|
+
drift_axis: status.drift.axis,
|
|
211
|
+
drift_installed_command: status.drift.installedCommand,
|
|
212
|
+
drift_expected_command: status.drift.expectedCommand,
|
|
213
|
+
// Retryable exactly when EVERY differing token is benign:
|
|
214
|
+
// - the hook script moved → the install path is version-stamped, so
|
|
215
|
+
// setup re-pins it to the running install.
|
|
216
|
+
// - the node token changed spelling but names the SAME file → not
|
|
217
|
+
// an interpreter move (this is the v1.9.0 re-pin, which also
|
|
218
|
+
// moves the script, hence axis "both").
|
|
219
|
+
// A node token naming a DIFFERENT file is never retryable: the pinned
|
|
220
|
+
// interpreter may be gone, and a hook that cannot spawn exits 127,
|
|
221
|
+
// which kimi-code treats as ALLOW. Re-pinning would paper over a real
|
|
222
|
+
// enforcement gap instead of surfacing it.
|
|
223
|
+
retryable_after_setup: status.drift.axis === "hook-script" || status.drift.nodeInterpreterUnchanged === true,
|
|
224
|
+
}
|
|
225
|
+
: {}),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export function formatHookMissingWarning(status, commandLabel) {
|
|
229
|
+
return [
|
|
230
|
+
"",
|
|
231
|
+
"WARNING: kimi-plugin-cc safety hook is NOT installed (or is invalid).",
|
|
232
|
+
` Command: ${commandLabel}`,
|
|
233
|
+
` Config: ${status.configPath}`,
|
|
234
|
+
` Reason: ${status.reason ?? "unknown"}`,
|
|
235
|
+
"",
|
|
236
|
+
" Without a valid PreToolUse hook, kimi-code's `-p` mode auto-approves",
|
|
237
|
+
" every tool call — including Bash, Write, Edit — even from commands",
|
|
238
|
+
" documented as read-only.",
|
|
239
|
+
"",
|
|
240
|
+
" This command will not start a Kimi model run until enforcement is",
|
|
241
|
+
" repaired (the review gate skips instead of blocking stop).",
|
|
242
|
+
"",
|
|
243
|
+
" Fix: run Claude Code `/kimi:setup` or Codex `$kimi-setup` to install",
|
|
244
|
+
" or repair this host's managed block in",
|
|
245
|
+
" ~/.kimi-code/config.toml. If you use nvm, asdf, mise, or fnm, you",
|
|
246
|
+
" must re-run `/kimi:setup` after any Node version switch — the",
|
|
247
|
+
" verifier pins the absolute Node binary path and a switch invalidates",
|
|
248
|
+
" the previously-installed block by design. See docs/safety.md.",
|
|
249
|
+
"",
|
|
250
|
+
" KIMI_PLUGIN_CC_SKIP_HOOK_CHECK=1 explicitly bypasses hook-verification",
|
|
251
|
+
" refusals and restores un-enforced `permission: auto` execution. It does",
|
|
252
|
+
" NOT bypass the experimental-v2 safety refusal. Reserve it for tests or",
|
|
253
|
+
" diagnostics where the hook risk is intentional.",
|
|
254
|
+
"",
|
|
255
|
+
].join("\n");
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Process-lifetime latch so commands don't spam the warning on every
|
|
259
|
+
* call inside a single test run.
|
|
260
|
+
*/
|
|
261
|
+
let warnedThisProcess = false;
|
|
262
|
+
/**
|
|
263
|
+
* Emit the warning to stderr at most once per process.
|
|
264
|
+
*
|
|
265
|
+
* Why stderr rather than stdout: stdout is reserved for the command's
|
|
266
|
+
* load-bearing output (artifact prose, JSON envelopes). LLM-caller
|
|
267
|
+
* discipline says stderr is humans-only, and this warning is exactly
|
|
268
|
+
* that — a developer-facing nudge to run /kimi:setup before tagging.
|
|
269
|
+
*/
|
|
270
|
+
export function maybeWarnHookMissing(status, commandLabel, stderr = process.stderr) {
|
|
271
|
+
if (status.installed)
|
|
272
|
+
return;
|
|
273
|
+
if (warnedThisProcess)
|
|
274
|
+
return;
|
|
275
|
+
warnedThisProcess = true;
|
|
276
|
+
stderr.write(formatHookMissingWarning(status, commandLabel));
|
|
277
|
+
}
|
|
278
|
+
/** Test hook — resets the once-per-process latch. */
|
|
279
|
+
export function __resetHookMissingWarning() {
|
|
280
|
+
warnedThisProcess = false;
|
|
281
|
+
}
|