kimi-plugin-cc 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +20 -0
- package/README.md +5 -5
- package/agents/kimi-ask.md +4 -0
- package/agents/kimi-challenge.md +3 -1
- package/agents/kimi-pursue.md +3 -1
- package/agents/kimi-rescue.md +4 -0
- package/agents/kimi-review.md +3 -1
- package/agents/kimi-swarm-write.md +3 -1
- package/agents/kimi-swarm.md +3 -1
- package/commands/ask.md +4 -2
- package/commands/challenge.md +4 -2
- package/commands/pursue.md +5 -1
- package/commands/rescue.md +4 -2
- package/commands/review.md +4 -2
- package/commands/swarm.md +5 -1
- package/dist/cli-client.js +42 -7
- package/dist/commands/ask.js +5 -2
- package/dist/commands/cli-helpers.js +15 -1
- package/dist/commands/pursue.js +5 -2
- package/dist/commands/rescue.js +5 -2
- package/dist/commands/result.js +3 -1
- package/dist/commands/review-gate.js +19 -13
- package/dist/commands/review.js +5 -2
- package/dist/commands/status.js +2 -2
- package/dist/commands/swarm.js +7 -3
- package/dist/help.js +1 -1
- package/dist/hook-escape.js +71 -0
- package/dist/hooks/approval-policy.js +17 -27
- package/dist/hooks/config-safety.js +3 -2
- package/dist/job-store.js +17 -4
- package/dist/kimi-engine.js +3 -0
- package/dist/kimi-errors.js +1 -1
- package/dist/kimi-timeouts.js +6 -10
- package/dist/kimi-version-probe.js +2 -2
- package/dist/parsing.js +59 -53
- package/dist/stream-json.js +16 -0
- package/dist/version.js +1 -1
- package/docs/models.md +10 -0
- package/docs/registry-releases.md +8 -4
- package/docs/safety.md +14 -2
- package/package.json +1 -1
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/cli-client.js +42 -7
- package/plugins/kimi-codex/dist/commands/ask.js +5 -2
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +15 -1
- package/plugins/kimi-codex/dist/commands/pursue.js +5 -2
- package/plugins/kimi-codex/dist/commands/rescue.js +5 -2
- package/plugins/kimi-codex/dist/commands/result.js +3 -1
- package/plugins/kimi-codex/dist/commands/review-gate.js +19 -13
- package/plugins/kimi-codex/dist/commands/review.js +5 -2
- package/plugins/kimi-codex/dist/commands/status.js +2 -2
- package/plugins/kimi-codex/dist/commands/swarm.js +7 -3
- package/plugins/kimi-codex/dist/help.js +1 -1
- package/plugins/kimi-codex/dist/hook-escape.js +71 -0
- package/plugins/kimi-codex/dist/hooks/approval-policy.js +17 -27
- package/plugins/kimi-codex/dist/hooks/config-safety.js +3 -2
- package/plugins/kimi-codex/dist/job-store.js +17 -4
- package/plugins/kimi-codex/dist/kimi-engine.js +3 -0
- package/plugins/kimi-codex/dist/kimi-errors.js +1 -1
- package/plugins/kimi-codex/dist/kimi-timeouts.js +6 -10
- package/plugins/kimi-codex/dist/kimi-version-probe.js +2 -2
- package/plugins/kimi-codex/dist/parsing.js +59 -53
- package/plugins/kimi-codex/dist/stream-json.js +16 -0
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-review/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +2 -0
- package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +3 -1
package/dist/parsing.js
CHANGED
|
@@ -21,38 +21,30 @@ function looksLikeFlag(token) {
|
|
|
21
21
|
// Short flag: a single dash followed by exactly one letter (`-r`, `-m`).
|
|
22
22
|
return /^-[a-zA-Z]$/.test(token);
|
|
23
23
|
}
|
|
24
|
-
export const ASK_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
25
|
-
export const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
26
|
-
export const REVIEW_SUPPORTED_FLAGS = "--base <ref>, -m/--model <name>, --budget <30m|1h|90s>";
|
|
24
|
+
export const ASK_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
25
|
+
export const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
26
|
+
export const REVIEW_SUPPORTED_FLAGS = "--base <ref>, -m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>";
|
|
27
27
|
export const JOB_LOOKUP_SUPPORTED_FLAGS = "--type <review|challenge|rescue|review_gate|ask>, --json";
|
|
28
28
|
export const SETUP_SUPPORTED_FLAGS = "--check, --uninstall, --all (with --uninstall), --enable-review-gate, --disable-review-gate, --models [--json] (without other setup flags)";
|
|
29
29
|
export const REPAIR_SESSIONS_SUPPORTED_FLAGS = "--apply, --all";
|
|
30
30
|
export const REPLAY_SUPPORTED_FLAGS = "none (one job-id required)";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// and a wrapper agent reading the error would think the cure is to drop
|
|
39
|
-
// the `=value` rather than the flag itself.
|
|
40
|
-
const THINKING_FLAG_REMOVED_MESSAGE = "--thinking / --no-thinking were removed in v1.0. Thinking is always on for user-facing commands.";
|
|
41
|
-
function isRemovedThinkingFlag(token) {
|
|
42
|
-
return (token === "--thinking" ||
|
|
43
|
-
token === "--no-thinking" ||
|
|
44
|
-
token.startsWith("--thinking=") ||
|
|
45
|
-
token.startsWith("--no-thinking="));
|
|
31
|
+
/** Validate syntax only; upstream per-model supportEfforts remains authoritative. */
|
|
32
|
+
export function validateThinkingEffort(value, stage = "args.parse", lowercase = true) {
|
|
33
|
+
const effort = lowercase ? value?.toLowerCase() : value;
|
|
34
|
+
if (!effort || !/^[a-z][a-z0-9-]{0,31}$/.test(effort)) {
|
|
35
|
+
throw new RuntimeError("INVALID_ARGS", "--thinking requires an effort matching [a-z][a-z0-9-]{0,31} (e.g. off, low, medium, high).", stage);
|
|
36
|
+
}
|
|
37
|
+
return effort;
|
|
46
38
|
}
|
|
47
|
-
export const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --turns <N>";
|
|
48
|
-
export const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
|
|
39
|
+
export const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --turns <N>";
|
|
40
|
+
export const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
|
|
49
41
|
/**
|
|
50
42
|
* Parser for /kimi:swarm (read-only parallel fan-out). Foreground-only:
|
|
51
43
|
* no --background/--fresh/--resume. Trailing tokens are the objective.
|
|
52
44
|
*/
|
|
53
45
|
export function parseSwarmArgs(argv) {
|
|
54
46
|
let model;
|
|
55
|
-
|
|
47
|
+
let thinkingEffort;
|
|
56
48
|
let budgetMs;
|
|
57
49
|
let cap;
|
|
58
50
|
let maxConcurrency;
|
|
@@ -60,6 +52,13 @@ export function parseSwarmArgs(argv) {
|
|
|
60
52
|
let trailingTokens;
|
|
61
53
|
for (let index = 0; index < argv.length; index += 1) {
|
|
62
54
|
const token = argv[index];
|
|
55
|
+
if (token === "--thinking" || token.startsWith("--thinking=")) {
|
|
56
|
+
thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
|
|
60
|
+
throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
|
|
61
|
+
}
|
|
63
62
|
if (token === "--") {
|
|
64
63
|
trailingTokens = argv.slice(index + 1);
|
|
65
64
|
break;
|
|
@@ -105,9 +104,6 @@ export function parseSwarmArgs(argv) {
|
|
|
105
104
|
break;
|
|
106
105
|
}
|
|
107
106
|
default:
|
|
108
|
-
if (isRemovedThinkingFlag(token)) {
|
|
109
|
-
throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "swarm.parse");
|
|
110
|
-
}
|
|
111
107
|
if (looksLikeFlag(token)) {
|
|
112
108
|
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for swarm. Supported flags: ${SWARM_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped objective text to pass it through.`, "swarm.parse");
|
|
113
109
|
}
|
|
@@ -122,7 +118,7 @@ export function parseSwarmArgs(argv) {
|
|
|
122
118
|
maxConcurrency,
|
|
123
119
|
write,
|
|
124
120
|
model,
|
|
125
|
-
|
|
121
|
+
thinkingEffort,
|
|
126
122
|
objective: trailingTokens?.join(" "),
|
|
127
123
|
};
|
|
128
124
|
}
|
|
@@ -186,12 +182,19 @@ export function parseDurationMs(token, flag, stage, budgetKind) {
|
|
|
186
182
|
*/
|
|
187
183
|
export function parsePursueArgs(argv) {
|
|
188
184
|
let model;
|
|
189
|
-
|
|
185
|
+
let thinkingEffort;
|
|
190
186
|
let budgetMs;
|
|
191
187
|
let turns;
|
|
192
188
|
let trailingTokens;
|
|
193
189
|
for (let index = 0; index < argv.length; index += 1) {
|
|
194
190
|
const token = argv[index];
|
|
191
|
+
if (token === "--thinking" || token.startsWith("--thinking=")) {
|
|
192
|
+
thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
|
|
196
|
+
throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
|
|
197
|
+
}
|
|
195
198
|
if (token === "--") {
|
|
196
199
|
trailingTokens = argv.slice(index + 1);
|
|
197
200
|
break;
|
|
@@ -237,9 +240,6 @@ export function parsePursueArgs(argv) {
|
|
|
237
240
|
break;
|
|
238
241
|
}
|
|
239
242
|
default:
|
|
240
|
-
if (isRemovedThinkingFlag(token)) {
|
|
241
|
-
throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "pursue.parse");
|
|
242
|
-
}
|
|
243
243
|
if (looksLikeFlag(token)) {
|
|
244
244
|
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for pursue. Supported flags: ${PURSUE_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped objective text to pass it through.`, "pursue.parse");
|
|
245
245
|
}
|
|
@@ -252,12 +252,12 @@ export function parsePursueArgs(argv) {
|
|
|
252
252
|
budgetMs,
|
|
253
253
|
turns,
|
|
254
254
|
model,
|
|
255
|
-
|
|
255
|
+
thinkingEffort,
|
|
256
256
|
objective: trailingTokens?.join(" "),
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
259
|
/** Help before -- wins even over malformed flags or prose trailing-token mode. */
|
|
260
|
-
const VALUE_TAKING_FLAGS = new Set(["-m", "--model", "--budget", "--resume", "--base", "--cap", "--max-concurrency", "--turns", "--type"]);
|
|
260
|
+
const VALUE_TAKING_FLAGS = new Set(["--thinking", "-m", "--model", "--budget", "--resume", "--base", "--cap", "--max-concurrency", "--turns", "--type"]);
|
|
261
261
|
/**
|
|
262
262
|
* `--help` / `-h` counts only inside the leading flag region: before `--`
|
|
263
263
|
* and before the first prose token. The value of a value-taking flag is
|
|
@@ -289,10 +289,7 @@ export function parseAskArgs(argv) {
|
|
|
289
289
|
return { help: true, background: false, wait: false, fresh: false, resume: false };
|
|
290
290
|
let budgetMs;
|
|
291
291
|
let model;
|
|
292
|
-
|
|
293
|
-
// parser rejects --thinking / --no-thinking; review-gate's runtime
|
|
294
|
-
// caller pins thinking=false via CliClientOptions instead.
|
|
295
|
-
let thinking = true;
|
|
292
|
+
let thinkingEffort;
|
|
296
293
|
let background = false;
|
|
297
294
|
let wait = false;
|
|
298
295
|
let fresh = false;
|
|
@@ -302,6 +299,13 @@ export function parseAskArgs(argv) {
|
|
|
302
299
|
let trailingTokens;
|
|
303
300
|
for (let index = 0; index < argv.length; index += 1) {
|
|
304
301
|
const token = argv[index];
|
|
302
|
+
if (token === "--thinking" || token.startsWith("--thinking=")) {
|
|
303
|
+
thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
|
|
307
|
+
throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
|
|
308
|
+
}
|
|
305
309
|
if (token === "--") {
|
|
306
310
|
trailingTokens = argv.slice(index + 1);
|
|
307
311
|
break;
|
|
@@ -357,9 +361,6 @@ export function parseAskArgs(argv) {
|
|
|
357
361
|
break;
|
|
358
362
|
}
|
|
359
363
|
default:
|
|
360
|
-
if (isRemovedThinkingFlag(token)) {
|
|
361
|
-
throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "args.parse");
|
|
362
|
-
}
|
|
363
364
|
if (looksLikeFlag(token)) {
|
|
364
365
|
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ask. Supported flags: ${ASK_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped prompt text to pass it through.`, "args.parse");
|
|
365
366
|
}
|
|
@@ -382,7 +383,7 @@ export function parseAskArgs(argv) {
|
|
|
382
383
|
resume,
|
|
383
384
|
resumeTarget,
|
|
384
385
|
model,
|
|
385
|
-
|
|
386
|
+
thinkingEffort,
|
|
386
387
|
prompt: trailingTokens?.join(" "),
|
|
387
388
|
};
|
|
388
389
|
}
|
|
@@ -394,7 +395,7 @@ export function parseReviewArgs(argv, commandName = "review") {
|
|
|
394
395
|
background: parsed.background,
|
|
395
396
|
wait: parsed.wait,
|
|
396
397
|
model: parsed.model,
|
|
397
|
-
|
|
398
|
+
thinkingEffort: parsed.thinkingEffort,
|
|
398
399
|
focus: parsed.trailingText,
|
|
399
400
|
};
|
|
400
401
|
}
|
|
@@ -403,7 +404,7 @@ export function parseRescueArgs(argv) {
|
|
|
403
404
|
return { help: true, background: false, wait: false, fresh: false, resume: false };
|
|
404
405
|
let budgetMs;
|
|
405
406
|
let model;
|
|
406
|
-
let
|
|
407
|
+
let thinkingEffort;
|
|
407
408
|
let background = false;
|
|
408
409
|
let wait = false;
|
|
409
410
|
let fresh = false;
|
|
@@ -413,6 +414,13 @@ export function parseRescueArgs(argv) {
|
|
|
413
414
|
let trailingTokens;
|
|
414
415
|
for (let index = 0; index < argv.length; index += 1) {
|
|
415
416
|
const token = argv[index];
|
|
417
|
+
if (token === "--thinking" || token.startsWith("--thinking=")) {
|
|
418
|
+
thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
|
|
422
|
+
throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
|
|
423
|
+
}
|
|
416
424
|
if (token === "--") {
|
|
417
425
|
trailingTokens = argv.slice(index + 1);
|
|
418
426
|
break;
|
|
@@ -468,9 +476,6 @@ export function parseRescueArgs(argv) {
|
|
|
468
476
|
break;
|
|
469
477
|
}
|
|
470
478
|
default:
|
|
471
|
-
if (isRemovedThinkingFlag(token)) {
|
|
472
|
-
throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "args.parse");
|
|
473
|
-
}
|
|
474
479
|
if (looksLikeFlag(token)) {
|
|
475
480
|
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for rescue. Supported flags: ${RESCUE_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped prompt text to pass it through.`, "args.parse");
|
|
476
481
|
}
|
|
@@ -493,7 +498,7 @@ export function parseRescueArgs(argv) {
|
|
|
493
498
|
resume,
|
|
494
499
|
resumeTarget,
|
|
495
500
|
model,
|
|
496
|
-
|
|
501
|
+
thinkingEffort,
|
|
497
502
|
prompt: trailingTokens?.join(" "),
|
|
498
503
|
};
|
|
499
504
|
}
|
|
@@ -540,21 +545,25 @@ function isManagedCommandType(value) {
|
|
|
540
545
|
function parseKnownFlags(argv, knownFlags, commandName) {
|
|
541
546
|
let budgetMs;
|
|
542
547
|
let model;
|
|
543
|
-
let
|
|
548
|
+
let thinkingEffort;
|
|
544
549
|
let base;
|
|
545
550
|
let background = false;
|
|
546
551
|
let wait = false;
|
|
547
552
|
let trailingTokens;
|
|
548
553
|
for (let index = 0; index < argv.length; index += 1) {
|
|
549
554
|
const token = argv[index];
|
|
555
|
+
if (token === "--thinking" || token.startsWith("--thinking=")) {
|
|
556
|
+
thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
|
|
560
|
+
throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
|
|
561
|
+
}
|
|
550
562
|
if (token === "--") {
|
|
551
563
|
trailingTokens = argv.slice(index + 1);
|
|
552
564
|
break;
|
|
553
565
|
}
|
|
554
566
|
if (!knownFlags.has(token)) {
|
|
555
|
-
if (isRemovedThinkingFlag(token)) {
|
|
556
|
-
throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "args.parse");
|
|
557
|
-
}
|
|
558
567
|
if (looksLikeFlag(token)) {
|
|
559
568
|
// Wrapper agents routinely hallucinate flags like `--file` /
|
|
560
569
|
// `--context` because that shape is common in other CLIs. Silently
|
|
@@ -610,9 +619,6 @@ function parseKnownFlags(argv, knownFlags, commandName) {
|
|
|
610
619
|
// The early `if (!knownFlags.has(token))` gate above means we only
|
|
611
620
|
// reach this switch with tokens that ARE in knownFlags, and every
|
|
612
621
|
// member is handled above. A `default` arm would be unreachable.
|
|
613
|
-
// --thinking and --no-thinking are NOT in knownFlags any more (v1.0
|
|
614
|
-
// removed them); the early gate above hard-fails on them with
|
|
615
|
-
// THINKING_FLAG_REMOVED_MESSAGE.
|
|
616
622
|
}
|
|
617
623
|
}
|
|
618
624
|
return {
|
|
@@ -621,7 +627,7 @@ function parseKnownFlags(argv, knownFlags, commandName) {
|
|
|
621
627
|
background,
|
|
622
628
|
wait,
|
|
623
629
|
model,
|
|
624
|
-
|
|
630
|
+
thinkingEffort,
|
|
625
631
|
trailingText: trailingTokens?.join(" "),
|
|
626
632
|
};
|
|
627
633
|
}
|
package/dist/stream-json.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// {"role":"meta","type":"turn.step.retrying","failed_attempt":1,"next_attempt":2,"max_attempts":3,"delay_ms":300,"error_name":"APIProviderRateLimitError","error_message":"...","status_code":429}
|
|
13
13
|
// {"role":"meta","type":"system.version","version":"0.24.2"} // experimental v2 only; modeled since v1.8.5 (SystemVersionRecord), filtered like other meta
|
|
14
14
|
//
|
|
15
|
+
// {"type":"error","code":"session.occupied","message":"...","details":{...}} // 2.3.0; stdout-only refusal, exit 4
|
|
16
|
+
//
|
|
15
17
|
// Notes on what does and does not appear here:
|
|
16
18
|
// - The session.resume_hint meta record is NEW in kimi-code 0.2.0
|
|
17
19
|
// (introduced at run-prompt.ts:477-505 in 0.2.0; the writer region —
|
|
@@ -263,6 +265,20 @@ function parseLine(line) {
|
|
|
263
265
|
if (role === undefined && parsed["type"] === "goal.summary") {
|
|
264
266
|
return validateGoalSummary(parsed, line);
|
|
265
267
|
}
|
|
268
|
+
if (role === undefined && parsed["type"] === "error") {
|
|
269
|
+
if (typeof parsed["code"] !== "string" || parsed["code"].length === 0 ||
|
|
270
|
+
typeof parsed["message"] !== "string" || parsed["message"].length === 0 ||
|
|
271
|
+
(parsed["details"] !== undefined && !isRecord(parsed["details"]))) {
|
|
272
|
+
return { malformedLine: line, malformedReason: "error record field has unexpected type" };
|
|
273
|
+
}
|
|
274
|
+
// Keep bounded diagnostic fields only; holder metadata is not needed and
|
|
275
|
+
// must not become a session-id source or be copied into persisted errors.
|
|
276
|
+
return { cliError: {
|
|
277
|
+
type: "error",
|
|
278
|
+
code: parsed["code"].slice(0, 200),
|
|
279
|
+
message: parsed["message"].slice(0, 4000),
|
|
280
|
+
} };
|
|
281
|
+
}
|
|
266
282
|
// H3 forward-compat: a non-empty string role we don't model (a role a future
|
|
267
283
|
// kimi-code adds). Surface it out-of-band on the unknownRecord channel —
|
|
268
284
|
// tolerated, not malformed — so the parser keeps working and the diagnostic
|
package/dist/version.js
CHANGED
package/docs/models.md
CHANGED
|
@@ -80,3 +80,13 @@ validator. Every task still uses the existing exact-version, hook, workspace,
|
|
|
80
80
|
and no-plan checks. Source review is against kimi-code 2.0.0 (`1b89e4b0`),
|
|
81
81
|
including `kosongConfig/configSection.ts`, `envOverlay.ts`, and
|
|
82
82
|
`cli/v2/run-v2-print.ts`.
|
|
83
|
+
|
|
84
|
+
## Requested thinking effort
|
|
85
|
+
|
|
86
|
+
`--thinking <effort>` requests per-run effort through `KIMI_MODEL_THINKING_EFFORT`, never a CLI flag or saved-default change; omitted flags preserve the operator-owned ambient variable, and resume does not inherit a previous job's request. Providers that are not trait-driven silently ignore it, as does a base level already `off`; an `alwaysThinking` model receives `off` unnormalized. Stream-json carries no thinking deltas (`writeThinkingDelta() {}`), so the plugin cannot verify whether the request was honoured.
|
|
87
|
+
|
|
88
|
+
Use `--thinking off`, `--thinking high`, or `--thinking=<effort>` before the prompt. Values are lowercased and must match `/^[a-z][a-z0-9-]{0,31}$/` (a plugin-owned syntax bound; a future upstream token outside it is rejected before upstream sees it); upstream per-model `supportEfforts`, not a plugin enum, determines supported values. `--no-thinking` is an error: use `--thinking off`. In CLI 2.2.0 only the three Moonshot `kimi*` provider definitions are trait-driven. BYO providers ignore the request silently; an always-thinking model may remain thinking.
|
|
89
|
+
|
|
90
|
+
Jobs persist nullable `thinking_effort`; status and `result --json` expose `thinking_effort_requested`, not observed effort. Detached ask/rescue workers replay the request. A resume without `--thinking` exports no new override (any ambient operator value remains). No config.toml write occurs.
|
|
91
|
+
|
|
92
|
+
The review gate requests `off` by default. `KIMI_PLUGIN_CC_REVIEW_GATE_THINKING=inherit` exports no override; another non-empty value is lowercased and must match the same regex. Unset or empty means `off`; an invalid value makes the gate skip visibly. The gate remains fail-open on its 8 s timeout: a BYO provider or always-thinking model can ignore the request, and even an honoured request does not guarantee completion within the budget.
|
|
@@ -6,9 +6,13 @@ remain available. The package has no install scripts and no separate CLI.
|
|
|
6
6
|
|
|
7
7
|
## Publishing status
|
|
8
8
|
|
|
9
|
-
As of October 15, 2026, [2.
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
As of October 15, 2026, [2.2.1 is live on npm](https://www.npmjs.com/package/kimi-plugin-cc/v/2.2.1), published by
|
|
10
|
+
the release-event run of `publish.yml` (run 38720071707) through GitHub Actions OIDC with a signed
|
|
11
|
+
provenance statement (sigstore log index 3263398504; tarball shasum
|
|
12
|
+
`03907c66d440155763a860ab52b5107c0233938b`, registry propagation took a few minutes this time).
|
|
13
|
+
2.2.0 (same day, run 38658918855, log index 3256863983) was the first release where the tag-triggered
|
|
14
|
+
path worked end to end without a manual dispatch; 2.0.8 (same day, run 38650044033) was the first
|
|
15
|
+
OIDC publication; 2.0.7 (September 26) was the hand-published bootstrap.
|
|
12
16
|
The first upload used the validated GitHub Actions artifact and interactive npm
|
|
13
17
|
authentication. Its public tarball matched that artifact byte-for-byte, and a
|
|
14
18
|
fresh registry install passed isolated setup/check for both hosts.
|
|
@@ -17,7 +21,7 @@ The trusted publisher is configured for `linxule/kimi-plugin-cc`, workflow
|
|
|
17
21
|
`publish.yml`, environment `npm`, with direct publishing allowed.
|
|
18
22
|
`NPM_TRUSTED_PUBLISHING=true` is enabled in GitHub Actions. Future GitHub releases
|
|
19
23
|
use short-lived OIDC credentials, without a stored npm token or interactive 2FA.
|
|
20
|
-
|
|
24
|
+
OIDC publication is established; never republish an existing version.
|
|
21
25
|
|
|
22
26
|
**2026-10-15 status.** The `v2.0.8` release triggered the first OIDC attempt. It
|
|
23
27
|
failed twice on the publish step for reasons unrelated to the package: current
|
package/docs/safety.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Safety
|
|
2
2
|
|
|
3
|
-
This guide describes the
|
|
3
|
+
This guide describes the current checkout runtime; 2.3.0 certification is unreleased. Read-only tasks deny file writes and shell commands. Rescue and pursue can edit your workspace. Write-swarm returns a patch from a temporary Git worktree.
|
|
4
4
|
|
|
5
|
-
Native agent-core-v2 is certified at exact CLI versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`,
|
|
5
|
+
Native agent-core-v2 is certified at exact CLI versions `0.42.0`, `0.43.0`, `0.43.1`, `2.0.0`, `2.0.1`, `2.0.2`, `2.1.0`, `2.1.1`, `2.2.0`, and `2.3.0`, with plan mode disabled. The [runtime certification table](../runtime/kimi-engine.ts) controls routing. The hook is not an operating-system sandbox.
|
|
6
6
|
|
|
7
7
|
## Why the safety story is hook-based
|
|
8
8
|
|
|
@@ -156,6 +156,8 @@ Provenance: a native-v2 plan requires the `system.version` marker as the first s
|
|
|
156
156
|
|
|
157
157
|
Residuals, unchanged from v1 and stated plainly: `config.toml` and the session journals are trusted operator state (an actor who can write them could already delete the `[[hooks]]` entry); the upstream hook runner fails open on its own internal errors; the plan-path comparison does not realpath (a symlink there would need a prior hook-allowed write). Native plan mode, tower, subagent fork, Remote Control and `--add-dir` are out of scope.
|
|
158
158
|
|
|
159
|
+
In CLI 2.3.0, stale-session retry can reread those same journals. Do not concurrently operate on or edit a plugin-managed session from another client. The upstream session lock is not a security boundary; no parent-side rescan can make that reload atomic.
|
|
160
|
+
|
|
159
161
|
## The rescue allowlist
|
|
160
162
|
|
|
161
163
|
For `/kimi:rescue`, the hook delegates to [`evaluateRescueHookRequest`](../runtime/rescue-approval.ts). The allowlist has been extended since the original Wire-transport implementation. Highlights:
|
|
@@ -363,3 +365,13 @@ Agents use `retryable_after_setup`, not `drift_axis` alone. They run setup for t
|
|
|
363
365
|
## Reporting safety issues
|
|
364
366
|
|
|
365
367
|
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.
|
|
368
|
+
|
|
369
|
+
## Hook-escape monitor: detection evidence
|
|
370
|
+
|
|
371
|
+
**Hook-escape monitor** (§1): the cli-client replays the pure hook policy on every streamed `tool_calls` entry; a call the policy denies whose tool result is not the hook's denial (byte-exact, or with upstream's repeat-breaker `<system-reminder>` decoration / handoff text) is recorded as `mismatch`, and a denied call still without a result at settlement as `unresolved`. Findings are **detection evidence, never a failure**: logged (`hook_escape`), persisted on the job row (`hook_escapes`, JSON), exposed by `status`/`result --json`, printed as a stderr `safety hook notice`, and carried in timeout details; the run continues, the job's outcome is unchanged, and the review gate stays fail-open and only annotates. The stream has no outcome field, so a mismatch is equally an upstream pre-hook rejection (unknown tool, disabled tool, invalid args, sensitive path, per-tool resolve errors), a coexisting hook or permission policy blocking first, or hook drift — failing the job needs positive execution evidence this stream does not provide. Streamed calls under read-only labels are covered (swarm subagents are in-process, not streamed); write labels only for the pure denials (plan/goal tools).
|
|
372
|
+
|
|
373
|
+
A denied attempt alone is not a finding: the hook's denial text in the corresponding tool result (byte-exact, or carrying upstream's repeat-breaker decoration) proves enforcement and clears it. Anything else is recorded as `mismatch` with a 200-character excerpt; a denied call with no result at settlement as `unresolved`. **What a finding means:** the model received something other than the managed denial. On certified 2.2.0 that is produced just as readily by calls upstream rejects before the hook runs (`Tool "LS" not found`, `Invalid args for tool …`, the sensitive-path `PathSecurityError` for `.env`, per-tool resolve errors, `Tool skipped because a previous tool call stopped the turn.`), by a coexisting PreToolUse hook ahead of the managed block or an upstream permission policy blocking first, or by a cancel/budget abort landing before the result — none of which executed anything — as by a hook that failed open. Because the stream carries no outcome field, the plugin cannot tell these apart, so it reports and does not fail: the job completes or fails on its own merits, `status`/`result --json` carry `hook_escapes`, stderr prints a `safety hook notice`, and the review gate annotates `systemMessage` without changing its decision. Treat a finding as a prompt to run `/kimi:setup --check` and read the excerpt, not as proof of a breach; treat a *recurring* `mismatch` whose excerpt looks like tool output (`Wrote N bytes to …`) as a drifted hook. Coverage is streamed calls under read-only labels and pure denials under write labels — not filesystem-dependent rescue allowlist decisions, not swarm subagent calls (in-process, never streamed). Non-vacuous live control (CLI 2.2.0, 2026-10-15): with the hook command pointed at a nonexistent script upstream failed open, `Write` created the file, and the finding was `mismatch` with excerpt `Wrote 7 bytes to ESCAPE.txt`; with the hook intact the same prompt produced the byte-exact denial and no finding. Enforcement (stopping the run, failing the job) is deliberately not shipped: it needs positive execution evidence. The upstream ask, filed as MoonshotAI/kimi-code#4241, is an `outcome: executed|vetoed|rejected` field on print-mode tool records; a plugin-side alternative is a per-job trace the hook writes before it exits.
|
|
374
|
+
|
|
375
|
+
## Review-gate thinking request
|
|
376
|
+
|
|
377
|
+
The gate requests `off` through `KIMI_MODEL_THINKING_EFFORT` by default. `KIMI_PLUGIN_CC_REVIEW_GATE_THINKING=inherit` leaves the ambient environment alone; any other non-empty value matching `/^[a-z][a-z0-9-]{0,31}$/` is passed verbatim. This is best-effort, never an enforced latency guarantee: non-trait-driven/BYO providers ignore it silently, an already-off base remains off, and an always-thinking model receives `off` unnormalized and may continue reasoning. Stream-json has no thinking deltas, so honouring the request cannot be observed. The 8 s budget and fail-open-on-timeout contract are unchanged; a hook-escape finding only annotates the gate's `systemMessage`. `KIMI_PLUGIN_CC_REVIEW_GATE_THINKING` is lowercased like `--thinking`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
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
5
|
"author": {
|
|
6
6
|
"name": "linxule"
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { spawn } from "node:child_process";
|
|
20
20
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
21
21
|
import path from "node:path";
|
|
22
|
+
import { createHookEscapeMonitor } from "./hook-escape.js";
|
|
22
23
|
import { RuntimeError, formatError } from "./errors.js";
|
|
23
24
|
import { assertNoUnsafeExperimentalSelector, assertExecutionPlanMatchesSpawn, } from "./kimi-engine.js";
|
|
24
25
|
import { assertPrefixArgsSafe } from "./kimi-command.js";
|
|
@@ -125,7 +126,16 @@ export async function runCliPrompt(opts) {
|
|
|
125
126
|
let stderrTail = "";
|
|
126
127
|
let announcedSessionId;
|
|
127
128
|
let announcedGoalSummary;
|
|
129
|
+
let announcedCliError;
|
|
128
130
|
let announcedSystemVersion;
|
|
131
|
+
const hookMonitor = createHookEscapeMonitor({ commandLabel: opts.commandLabel,
|
|
132
|
+
operationKind: opts.executionPlan.operationKind, trustedWorkspaceRoot: opts.trustedWorkspaceRoot, cwd: opts.cwd });
|
|
133
|
+
const hookEscapes = [];
|
|
134
|
+
const noteHookEscape = (escape) => {
|
|
135
|
+
hookEscapes.push(escape);
|
|
136
|
+
appendLogLine({ event: "hook_escape", ...escape, command_label: opts.commandLabel,
|
|
137
|
+
operation_kind: opts.executionPlan.operationKind });
|
|
138
|
+
};
|
|
129
139
|
let engineMismatchDetected = false;
|
|
130
140
|
let handleEngineMismatch;
|
|
131
141
|
const plannedEngine = opts.executionPlan.intendedEngine;
|
|
@@ -181,6 +191,13 @@ export async function runCliPrompt(opts) {
|
|
|
181
191
|
for (const outcome of outcomes) {
|
|
182
192
|
if (engineMismatchDetected)
|
|
183
193
|
return;
|
|
194
|
+
if (outcome.cliError !== undefined) {
|
|
195
|
+
if (requireMarkerBeforeOutput("a CLI error record"))
|
|
196
|
+
return;
|
|
197
|
+
announcedCliError ??= outcome.cliError;
|
|
198
|
+
appendLogLine({ event: "cli_error", ...outcome.cliError });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
184
201
|
if (outcome.unknownRecord !== undefined) {
|
|
185
202
|
// H3 forward-compat: a stream-json line with a role we don't model
|
|
186
203
|
// (a future kimi-code role). Log it for diagnostics but keep it OUT of
|
|
@@ -283,6 +300,9 @@ export async function runCliPrompt(opts) {
|
|
|
283
300
|
}
|
|
284
301
|
if (requireMarkerBeforeOutput(`a ${outcome.record.role} record`))
|
|
285
302
|
return;
|
|
303
|
+
const escape = hookMonitor.observe(outcome.record);
|
|
304
|
+
if (escape)
|
|
305
|
+
noteHookEscape(escape);
|
|
286
306
|
records.push(outcome.record);
|
|
287
307
|
appendLogLine({ event: "record", record: outcome.record });
|
|
288
308
|
invokeOnRecord(outcome.record);
|
|
@@ -314,6 +334,13 @@ export async function runCliPrompt(opts) {
|
|
|
314
334
|
const settle = (kind, payload) => {
|
|
315
335
|
if (settlementStarted)
|
|
316
336
|
return;
|
|
337
|
+
// Denied calls still without a result (a cancel/budget race, an abnormal
|
|
338
|
+
// exit, or a turn that ended without the denial the hook owes) are reported
|
|
339
|
+
// alongside the real outcome, never instead of it.
|
|
340
|
+
for (const escape of hookMonitor.settle())
|
|
341
|
+
noteHookEscape(escape);
|
|
342
|
+
if (kind === "resolve" && hookEscapes.length > 0)
|
|
343
|
+
payload = { ...payload, hookEscapes: [...hookEscapes] };
|
|
317
344
|
settlementStarted = true;
|
|
318
345
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
319
346
|
// Abort teardown can outlive the direct child's close event while a
|
|
@@ -472,6 +499,11 @@ export async function runCliPrompt(opts) {
|
|
|
472
499
|
}
|
|
473
500
|
}
|
|
474
501
|
};
|
|
502
|
+
const stopForPolicyFailure = (error) => {
|
|
503
|
+
engineMismatchDetected = true; // Stops all further consumer output.
|
|
504
|
+
cancellationTeardown ??= teardownChildTree().then(() => ({ ok: true }), (error) => ({ ok: false, error }));
|
|
505
|
+
settle("reject", error);
|
|
506
|
+
};
|
|
475
507
|
let engineMismatchError;
|
|
476
508
|
handleEngineMismatch = (mismatch) => {
|
|
477
509
|
if (engineMismatchError !== undefined)
|
|
@@ -493,8 +525,7 @@ export async function runCliPrompt(opts) {
|
|
|
493
525
|
system_version: mismatch.systemVersion ?? null,
|
|
494
526
|
reason: mismatch.reason,
|
|
495
527
|
});
|
|
496
|
-
|
|
497
|
-
settle("reject", engineMismatchError);
|
|
528
|
+
stopForPolicyFailure(engineMismatchError);
|
|
498
529
|
};
|
|
499
530
|
const onAbort = () => {
|
|
500
531
|
if (aborted)
|
|
@@ -586,6 +617,7 @@ export async function runCliPrompt(opts) {
|
|
|
586
617
|
settledAt: 0, // Filled at settlement, after the teardown barrier.
|
|
587
618
|
sessionId,
|
|
588
619
|
goalSummary: announcedGoalSummary,
|
|
620
|
+
cliError: announcedCliError,
|
|
589
621
|
systemVersion: announcedSystemVersion,
|
|
590
622
|
observedEngine: announcedSystemVersion !== undefined
|
|
591
623
|
? "native-v2"
|
|
@@ -674,6 +706,7 @@ export class ResponseTimeoutError extends RuntimeError {
|
|
|
674
706
|
partial_output_chars: partialResult?.records.reduce((count, record) => count + (record.role === "assistant" && typeof record.content === "string" ? record.content.length : 0), 0) ?? 0,
|
|
675
707
|
aborted: partialResult?.aborted ?? true,
|
|
676
708
|
...(partialResult ? { settled_post_abort: true } : {}),
|
|
709
|
+
...(partialResult?.hookEscapes?.length ? { hook_escapes: partialResult.hookEscapes } : {}),
|
|
677
710
|
},
|
|
678
711
|
});
|
|
679
712
|
this.name = "ResponseTimeoutError";
|
|
@@ -778,11 +811,11 @@ function buildArgs(opts) {
|
|
|
778
811
|
args.push("--skills-dir", dir);
|
|
779
812
|
}
|
|
780
813
|
}
|
|
781
|
-
// NOTE:
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
814
|
+
// NOTE: thinking effort is requested through the env binding, never CLI argv.
|
|
815
|
+
// Non-trait-driven providers or a base level already off ignore it silently;
|
|
816
|
+
// an alwaysThinking model receives off unnormalized. stream-json carries no
|
|
817
|
+
// thinking deltas (writeThinkingDelta() {} in the JSON writer), so the plugin
|
|
818
|
+
// cannot observe whether the request was honoured.
|
|
786
819
|
// -p accepts the prompt as the option value. We pass it as a separate argv
|
|
787
820
|
// entry so prompts with leading dashes or whitespace are not interpreted as
|
|
788
821
|
// additional flags.
|
|
@@ -791,6 +824,8 @@ function buildArgs(opts) {
|
|
|
791
824
|
}
|
|
792
825
|
function buildEnv(opts) {
|
|
793
826
|
const env = { ...opts.env };
|
|
827
|
+
if (opts.thinkingEffort !== undefined)
|
|
828
|
+
env.KIMI_MODEL_THINKING_EFFORT = opts.thinkingEffort;
|
|
794
829
|
// 2.2.0 audit: ambient trust starts project MCP stdio servers outside PreToolUse.
|
|
795
830
|
delete env.KIMI_CODE_TRUST_WORKSPACE;
|
|
796
831
|
// kimi-code 0.33.0 inverted its CLI engine default: an unflagged `kimi -p`
|
|
@@ -17,7 +17,7 @@ import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.j
|
|
|
17
17
|
import { classifyManagedCommandFailure } from "../kimi-errors.js";
|
|
18
18
|
import { startBackgroundJob } from "../background-spawn.js";
|
|
19
19
|
import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
|
|
20
|
-
import { warnInheritedBudget, captureTimeoutPartialOutput, assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
|
+
import { warnInheritedBudget, captureTimeoutPartialOutput, assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing, warnHookEscapes } from "./cli-helpers.js";
|
|
21
21
|
import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
|
|
22
22
|
// v1.0 cutover note (PR 2):
|
|
23
23
|
//
|
|
@@ -77,7 +77,8 @@ export async function runAsk(argv, context) {
|
|
|
77
77
|
command_type: "ask",
|
|
78
78
|
cwd: context.cwd,
|
|
79
79
|
model: parsed.model ?? null,
|
|
80
|
-
thinking:
|
|
80
|
+
thinking: null,
|
|
81
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
81
82
|
background: parsed.background,
|
|
82
83
|
budget_ms: parsed.budgetMs ?? (sessionResolution.sourceJob?.operation_kind === "ask" ? sessionResolution.sourceJob.budget_ms : null) ?? null,
|
|
83
84
|
budget_source: parsed.budgetMs !== undefined ? "explicit"
|
|
@@ -174,6 +175,7 @@ export async function executeAskJob(jobId, prompt, context, options) {
|
|
|
174
175
|
prompt,
|
|
175
176
|
commandLabel: "ask",
|
|
176
177
|
model: job.model ?? undefined,
|
|
178
|
+
thinkingEffort: job.thinking_effort ?? undefined,
|
|
177
179
|
resumeSessionId: job.kimi_session_id ?? undefined,
|
|
178
180
|
logPath: job.stream_log_path,
|
|
179
181
|
signal: handlers.signal,
|
|
@@ -183,6 +185,7 @@ export async function executeAskJob(jobId, prompt, context, options) {
|
|
|
183
185
|
throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, "ask.runtime");
|
|
184
186
|
}
|
|
185
187
|
assertCliResultSuccess(result, "ask.runtime");
|
|
188
|
+
warnHookEscapes(result, "ask", context.stderr);
|
|
186
189
|
// Persist whichever session id kimi actually announced. For fresh
|
|
187
190
|
// sessions this is the only chance to record it. For resumed
|
|
188
191
|
// sessions it should match the input, but capture anyway in case
|
|
@@ -99,7 +99,7 @@ export function assertCliResultSuccess(result, stage) {
|
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
if (result.exitCode !== 0) {
|
|
102
|
-
const tail = result.stderrTail.trim();
|
|
102
|
+
const tail = result.cliError?.message ?? result.stderrTail.trim();
|
|
103
103
|
throw new RuntimeError("CLI_NONZERO_EXIT", [
|
|
104
104
|
`kimi subprocess exited with code ${result.exitCode}`,
|
|
105
105
|
result.signal ? ` (signal ${result.signal})` : "",
|
|
@@ -110,6 +110,7 @@ export function assertCliResultSuccess(result, stage) {
|
|
|
110
110
|
signal: result.signal,
|
|
111
111
|
record_count: result.records.length,
|
|
112
112
|
malformed_count: result.malformed.length,
|
|
113
|
+
...(result.cliError !== undefined && { cli_error: result.cliError }),
|
|
113
114
|
stderr_tail_len: result.stderrTail.length,
|
|
114
115
|
},
|
|
115
116
|
});
|
|
@@ -185,6 +186,19 @@ export async function captureTimeoutPartialOutput(classified, store, job, env, c
|
|
|
185
186
|
budgetMs: timeout.details.budget_ms,
|
|
186
187
|
};
|
|
187
188
|
}
|
|
189
|
+
/** One-line human summary of hook-escape findings, or undefined when none. */
|
|
190
|
+
export function describeHookEscapes(result) {
|
|
191
|
+
const escapes = result.hookEscapes ?? [];
|
|
192
|
+
if (escapes.length === 0)
|
|
193
|
+
return undefined;
|
|
194
|
+
const first = escapes[0];
|
|
195
|
+
return `safety hook notice: ${escapes.length} policy-denied tool call(s) did not come back as the managed hook's denial (first: ${first.tool_name} ${first.kind}). Detection evidence only — the call may have been rejected upstream before the hook ran, blocked by another hook, or executed because the hook drifted. Run /kimi:setup --check; details in \`result <jobId> --json\` (hook_escapes).`;
|
|
196
|
+
}
|
|
197
|
+
export function warnHookEscapes(result, commandType, stderr) {
|
|
198
|
+
const note = describeHookEscapes(result);
|
|
199
|
+
if (note !== undefined)
|
|
200
|
+
stderr.write(`[${commandType}] ${note}\n`);
|
|
201
|
+
}
|
|
188
202
|
export function warnInheritedBudget(job, stderr) {
|
|
189
203
|
if (job.budget_source === "inherited" && job.budget_ms !== null) {
|
|
190
204
|
stderr.write(`[${job.command_type}] budget ${formatBudgetDuration(job.budget_ms)} inherited from job ${job.resumed_from_job_id}; pass --budget to override\n`);
|
|
@@ -14,7 +14,7 @@ import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
|
14
14
|
import { parsePursueArgs } from "../parsing.js";
|
|
15
15
|
import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.js";
|
|
16
16
|
import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
|
|
17
|
-
import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
17
|
+
import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing, warnHookEscapes } from "./cli-helpers.js";
|
|
18
18
|
import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
|
|
19
19
|
// /kimi:pursue — autonomous goal mode (kimi-code 0.8.0+ headless `/goal`).
|
|
20
20
|
//
|
|
@@ -141,7 +141,8 @@ export async function runPursue(argv, context) {
|
|
|
141
141
|
command_type: "rescue",
|
|
142
142
|
cwd: context.cwd,
|
|
143
143
|
model: parsed.model ?? null,
|
|
144
|
-
thinking:
|
|
144
|
+
thinking: null,
|
|
145
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
145
146
|
background: false,
|
|
146
147
|
budget_ms: parsed.budgetMs ?? KIMI_PURSUE_DEFAULT_BUDGET_MS,
|
|
147
148
|
budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
|
|
@@ -233,10 +234,12 @@ async function executePursueJob(jobId, prompt, objective, budgetMs, context) {
|
|
|
233
234
|
commandLabel: "rescue",
|
|
234
235
|
trustedWorkspaceRoot: job.cwd,
|
|
235
236
|
model: job.model ?? undefined,
|
|
237
|
+
thinkingEffort: job.thinking_effort ?? undefined,
|
|
236
238
|
logPath: job.stream_log_path,
|
|
237
239
|
signal: handlers.signal,
|
|
238
240
|
}, budgetMs, "pursue.prompt");
|
|
239
241
|
store.updateRunningJob(job.job_id, observedExecutionFields(result));
|
|
242
|
+
warnHookEscapes(result, "pursue", context.stderr);
|
|
240
243
|
if (handlers.cancelling) {
|
|
241
244
|
throw new RuntimeError("PURSUE_CANCELLED", "Pursue cancelled by user request.", "pursue.runtime");
|
|
242
245
|
}
|