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/commands/rescue.js
CHANGED
|
@@ -17,7 +17,7 @@ import { parseRescueArgs } from "../parsing.js";
|
|
|
17
17
|
import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.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
|
export { describeMissingResult } from "../background-spawn.js";
|
|
23
23
|
// v1.0 cutover note (PR 3):
|
|
@@ -69,7 +69,8 @@ export async function runRescue(argv, context) {
|
|
|
69
69
|
command_type: "rescue",
|
|
70
70
|
cwd: context.cwd,
|
|
71
71
|
model: parsed.model ?? null,
|
|
72
|
-
thinking:
|
|
72
|
+
thinking: null,
|
|
73
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
73
74
|
background: parsed.background,
|
|
74
75
|
budget_ms: parsed.budgetMs ?? (sessionResolution.sourceJob?.operation_kind === "rescue" ? sessionResolution.sourceJob.budget_ms : null) ?? null,
|
|
75
76
|
budget_source: parsed.budgetMs !== undefined ? "explicit"
|
|
@@ -188,6 +189,7 @@ export async function executeRescueJob(jobId, prompt, context, options) {
|
|
|
188
189
|
commandLabel: "rescue",
|
|
189
190
|
trustedWorkspaceRoot: job.cwd,
|
|
190
191
|
model: job.model ?? undefined,
|
|
192
|
+
thinkingEffort: job.thinking_effort ?? undefined,
|
|
191
193
|
resumeSessionId: job.kimi_session_id ?? undefined,
|
|
192
194
|
logPath: job.stream_log_path,
|
|
193
195
|
signal: handlers.signal,
|
|
@@ -199,6 +201,7 @@ export async function executeRescueJob(jobId, prompt, context, options) {
|
|
|
199
201
|
throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, "rescue.runtime");
|
|
200
202
|
}
|
|
201
203
|
assertCliResultSuccess(result, "rescue.runtime");
|
|
204
|
+
warnHookEscapes(result, "rescue", context.stderr);
|
|
202
205
|
if (result.sessionId !== undefined &&
|
|
203
206
|
result.sessionId.length > 0) {
|
|
204
207
|
// length>0 guard: empty-string capture would poison the row. (Kimi
|
package/dist/commands/result.js
CHANGED
|
@@ -2,7 +2,7 @@ import { RuntimeError } from "../errors.js";
|
|
|
2
2
|
import { resolveRepoIdentity } from "../git.js";
|
|
3
3
|
import { sweepStaleJobs } from "../jobs.js";
|
|
4
4
|
import { reconcileHistoricalJobProvenance } from "../kimi-engine.js";
|
|
5
|
-
import { renderBudgetSource, withJobStore } from "../job-store.js";
|
|
5
|
+
import { renderBudgetSource, withJobStore, parseHookEscapes } from "../job-store.js";
|
|
6
6
|
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
7
7
|
import { parseJobLookupArgs } from "../parsing.js";
|
|
8
8
|
import { readArtifact, renderTerminalJobArtifact } from "../render.js";
|
|
@@ -44,6 +44,8 @@ function renderResultEnvelope(job, artifactPath, body) {
|
|
|
44
44
|
status: job.status,
|
|
45
45
|
budget_ms: job.budget_ms,
|
|
46
46
|
budget_source: renderBudgetSource(job),
|
|
47
|
+
thinking_effort_requested: job.thinking_effort ?? null,
|
|
48
|
+
hook_escapes: parseHookEscapes(job.hook_escapes),
|
|
47
49
|
kimi_session_id_source: job.kimi_session_id_source,
|
|
48
50
|
kimi_session_id: job.kimi_session_id,
|
|
49
51
|
// True when a budget timeout preserved assistant prose in the artifact.
|
|
@@ -15,9 +15,10 @@ import { KIMI_REVIEW_GATE_TIMEOUT_MS, isTimeoutCode } from "../kimi-timeouts.js"
|
|
|
15
15
|
import { writeInvocationLogHeader } from "../logging.js";
|
|
16
16
|
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
17
17
|
import { renderManagedJobOutput, writeArtifact, } from "../render.js";
|
|
18
|
+
import { validateThinkingEffort } from "../parsing.js";
|
|
18
19
|
import { maybeWarnHookMissing, verifyHookInstalled } from "../hooks/install.js";
|
|
19
20
|
import { resolveKimiHome } from "../kimi-home.js";
|
|
20
|
-
import { assertCliResultSuccess, reassembleProseFromRecords } from "./cli-helpers.js";
|
|
21
|
+
import { assertCliResultSuccess, describeHookEscapes, reassembleProseFromRecords } from "./cli-helpers.js";
|
|
21
22
|
const REVIEW_GATE_AGENT_PROFILE_PLACEHOLDER = "<cli-client>";
|
|
22
23
|
// v1.0 cutover note (PR 2):
|
|
23
24
|
//
|
|
@@ -82,18 +83,19 @@ export async function runReviewGateStopHook(payload, context) {
|
|
|
82
83
|
...payload,
|
|
83
84
|
cwd,
|
|
84
85
|
}, assistantMessage, context);
|
|
86
|
+
const note = output.hookEscapeNote !== undefined ? `Kimi review gate ${output.hookEscapeNote}` : undefined;
|
|
85
87
|
if (output.decision === "BLOCK" && output.confidence === "high") {
|
|
86
88
|
return {
|
|
87
89
|
decision: "block",
|
|
88
90
|
reason: buildBlockReason(output),
|
|
91
|
+
...(note !== undefined ? { systemMessage: note } : {}),
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
if (output.decision === "BLOCK") {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
+
const concern = `Kimi review gate noted concerns but allowed stop: ${output.summary}`;
|
|
96
|
+
return { systemMessage: note !== undefined ? `${concern}\n${note}` : concern };
|
|
95
97
|
}
|
|
96
|
-
return {};
|
|
98
|
+
return note !== undefined ? { systemMessage: note } : {};
|
|
97
99
|
}
|
|
98
100
|
catch (error) {
|
|
99
101
|
return {
|
|
@@ -105,6 +107,7 @@ function reviewGateSkipped(reason) {
|
|
|
105
107
|
return { systemMessage: `review-gate skipped: ${reason}` };
|
|
106
108
|
}
|
|
107
109
|
async function executeReviewGate(payload, assistantMessage, context) {
|
|
110
|
+
let hookEscapeNote;
|
|
108
111
|
const paths = resolvePluginPaths(context.env);
|
|
109
112
|
await ensurePluginPaths(paths);
|
|
110
113
|
const repoIdentity = await resolveRepoIdentity(payload.cwd);
|
|
@@ -121,6 +124,10 @@ async function executeReviewGate(payload, assistantMessage, context) {
|
|
|
121
124
|
cwd: payload.cwd,
|
|
122
125
|
repoRoot: repoIdentity.repoRoot,
|
|
123
126
|
});
|
|
127
|
+
// Unset or empty → request "off" (the 8 s budget is why); "inherit" → export
|
|
128
|
+
// nothing; anything else is lowercased and syntax-checked like --thinking.
|
|
129
|
+
const gateThinking = context.env.KIMI_PLUGIN_CC_REVIEW_GATE_THINKING?.trim() || "off";
|
|
130
|
+
const thinkingEffort = gateThinking.toLowerCase() === "inherit" ? undefined : validateThinkingEffort(gateThinking, "review_gate.parse");
|
|
124
131
|
const configuredModel = context.env.KIMI_PLUGIN_CC_REVIEW_GATE_MODEL;
|
|
125
132
|
const model = configuredModel?.trim() ? configuredModel : undefined;
|
|
126
133
|
const executionPlan = await prepareKimiExecutionPlan({
|
|
@@ -148,6 +155,7 @@ async function executeReviewGate(payload, assistantMessage, context) {
|
|
|
148
155
|
cwd: payload.cwd,
|
|
149
156
|
model: model ?? null,
|
|
150
157
|
thinking: false,
|
|
158
|
+
thinking_effort: thinkingEffort ?? null,
|
|
151
159
|
background: false,
|
|
152
160
|
pid: null,
|
|
153
161
|
kimi_pid: null,
|
|
@@ -178,17 +186,15 @@ async function executeReviewGate(payload, assistantMessage, context) {
|
|
|
178
186
|
prompt,
|
|
179
187
|
commandLabel: "review_gate",
|
|
180
188
|
model,
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
// flag for this; control is config-based (default_thinking /
|
|
184
|
-
// [thinking].mode). The field carries intent through the
|
|
185
|
-
// contract; when upstream adds a per-spawn flag, buildArgs will
|
|
186
|
-
// translate it. (Round 2 Codex finding.)
|
|
187
|
-
thinking: false,
|
|
189
|
+
// Requested via env; provider/model may ignore it. The 8s budget still applies.
|
|
190
|
+
thinkingEffort,
|
|
188
191
|
logPath,
|
|
189
192
|
}, KIMI_REVIEW_GATE_TIMEOUT_MS, "review_gate.runtime");
|
|
190
193
|
activeStore.updateRunningJob(job.job_id, observedExecutionFields(result));
|
|
191
194
|
assertCliResultSuccess(result, "review_gate.runtime");
|
|
195
|
+
// Detection evidence only (runtime/hook-escape.ts): the gate stays fail-open
|
|
196
|
+
// and annotates its decision; the finding is also on the job row.
|
|
197
|
+
hookEscapeNote = describeHookEscapes(result);
|
|
192
198
|
if (result.sessionId !== undefined && result.sessionId.length > 0) {
|
|
193
199
|
// length>0 guard matches the other commands (Kimi alpha.4
|
|
194
200
|
// challenge finding #3 — defense-in-depth, since
|
|
@@ -217,7 +223,7 @@ async function executeReviewGate(payload, assistantMessage, context) {
|
|
|
217
223
|
final_output_path: artifactPath,
|
|
218
224
|
error: null,
|
|
219
225
|
});
|
|
220
|
-
return rendered.output;
|
|
226
|
+
return { ...rendered.output, hookEscapeNote };
|
|
221
227
|
}
|
|
222
228
|
catch (error) {
|
|
223
229
|
const classified = classifyManagedCommandFailure(error, "review_gate", job.job_id);
|
package/dist/commands/review.js
CHANGED
|
@@ -17,7 +17,7 @@ import { renderManagedJobOutput, writeArtifact } from "../render.js";
|
|
|
17
17
|
import { RuntimeError } from "../errors.js";
|
|
18
18
|
import { resolveRepoIdentity } from "../git.js";
|
|
19
19
|
import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
|
|
20
|
-
import { captureTimeoutPartialOutput, assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
|
+
import { 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
|
//
|
|
@@ -79,7 +79,8 @@ export async function runReview(argv, context, commandType) {
|
|
|
79
79
|
command_type: commandType,
|
|
80
80
|
cwd: context.cwd,
|
|
81
81
|
model: parsed.model ?? null,
|
|
82
|
-
thinking:
|
|
82
|
+
thinking: null,
|
|
83
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
83
84
|
background: false,
|
|
84
85
|
budget_ms: parsed.budgetMs ?? null,
|
|
85
86
|
budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
|
|
@@ -113,6 +114,7 @@ export async function runReview(argv, context, commandType) {
|
|
|
113
114
|
prompt: previewPrompt,
|
|
114
115
|
commandLabel: commandType,
|
|
115
116
|
model: parsed.model,
|
|
117
|
+
thinkingEffort: parsed.thinkingEffort,
|
|
116
118
|
logPath,
|
|
117
119
|
signal: handlers.signal,
|
|
118
120
|
}, job.budget_ms ?? KIMI_REVIEW_PROMPT_TIMEOUT_MS, `${commandType}.prompt`);
|
|
@@ -121,6 +123,7 @@ export async function runReview(argv, context, commandType) {
|
|
|
121
123
|
throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, `${commandType}.runtime`);
|
|
122
124
|
}
|
|
123
125
|
assertCliResultSuccess(result, `${commandType}.runtime`);
|
|
126
|
+
warnHookEscapes(result, commandType, context.stderr);
|
|
124
127
|
// Persist whichever session id kimi announced. Review never resumes,
|
|
125
128
|
// but the id is the only handle for post-hoc replay against
|
|
126
129
|
// ~/.kimi-code/sessions/.
|
package/dist/commands/status.js
CHANGED
|
@@ -2,7 +2,7 @@ import { RuntimeError } from "../errors.js";
|
|
|
2
2
|
import { resolveRepoIdentity } from "../git.js";
|
|
3
3
|
import { sweepStaleJobs } from "../jobs.js";
|
|
4
4
|
import { reconcileHistoricalJobProvenance } from "../kimi-engine.js";
|
|
5
|
-
import { renderBudgetSource, withJobStore } from "../job-store.js";
|
|
5
|
+
import { parseHookEscapes, renderBudgetSource, withJobStore } from "../job-store.js";
|
|
6
6
|
import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
7
7
|
import { parseJobLookupArgs } from "../parsing.js";
|
|
8
8
|
export async function runStatus(argv, context) {
|
|
@@ -22,6 +22,6 @@ export async function runStatus(argv, context) {
|
|
|
22
22
|
throw new RuntimeError("JOB_NOT_FOUND", "No matching job was found for status.", "status.lookup");
|
|
23
23
|
}
|
|
24
24
|
const job = await reconcileHistoricalJobProvenance(store, found);
|
|
25
|
-
return `${JSON.stringify({ ...job, budget_source: renderBudgetSource(job) }, null, 2)}\n`;
|
|
25
|
+
return `${JSON.stringify({ ...job, thinking_effort_requested: job.thinking_effort ?? null, hook_escapes: parseHookEscapes(job.hook_escapes), budget_source: renderBudgetSource(job) }, null, 2)}\n`;
|
|
26
26
|
});
|
|
27
27
|
}
|
package/dist/commands/swarm.js
CHANGED
|
@@ -15,7 +15,7 @@ import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
|
|
|
15
15
|
import { MAX_DURATION_MS, parseSwarmArgs } from "../parsing.js";
|
|
16
16
|
import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.js";
|
|
17
17
|
import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
|
|
18
|
-
import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
18
|
+
import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing, warnHookEscapes } from "./cli-helpers.js";
|
|
19
19
|
import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
|
|
20
20
|
// /kimi:swarm — READ-ONLY parallel fan-out (kimi-code 0.12.0 AgentSwarm tool).
|
|
21
21
|
//
|
|
@@ -208,7 +208,8 @@ async function runReadSwarm(parsed, objective, context) {
|
|
|
208
208
|
command_type: "review",
|
|
209
209
|
cwd: context.cwd,
|
|
210
210
|
model: parsed.model ?? null,
|
|
211
|
-
thinking:
|
|
211
|
+
thinking: null,
|
|
212
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
212
213
|
background: false,
|
|
213
214
|
budget_ms: parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS,
|
|
214
215
|
budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
|
|
@@ -308,7 +309,8 @@ async function runWriteSwarm(parsed, objective, context) {
|
|
|
308
309
|
command_type: "rescue",
|
|
309
310
|
cwd: context.cwd,
|
|
310
311
|
model: parsed.model ?? null,
|
|
311
|
-
thinking:
|
|
312
|
+
thinking: null,
|
|
313
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
312
314
|
background: false,
|
|
313
315
|
budget_ms: parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS,
|
|
314
316
|
budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
|
|
@@ -493,6 +495,7 @@ writeMode) {
|
|
|
493
495
|
// by other labels). Only set in write mode.
|
|
494
496
|
trustedWorkspaceRoot: writeMode?.workspaceRoot,
|
|
495
497
|
model: job.model ?? undefined,
|
|
498
|
+
thinkingEffort: job.thinking_effort ?? undefined,
|
|
496
499
|
logPath: job.stream_log_path,
|
|
497
500
|
signal: handlers.signal,
|
|
498
501
|
}, budgetMs, "swarm.prompt");
|
|
@@ -501,6 +504,7 @@ writeMode) {
|
|
|
501
504
|
throw new RuntimeError("SWARM_CANCELLED", "Swarm cancelled by user request.", "swarm.runtime");
|
|
502
505
|
}
|
|
503
506
|
assertCliResultSuccess(result, "swarm.runtime");
|
|
507
|
+
warnHookEscapes(result, "swarm", context.stderr);
|
|
504
508
|
if (result.sessionId !== undefined &&
|
|
505
509
|
result.sessionId.length > 0 &&
|
|
506
510
|
result.sessionId !== job.kimi_session_id) {
|
package/dist/help.js
CHANGED
|
@@ -11,7 +11,7 @@ export function companionHelp(argv) {
|
|
|
11
11
|
: 1;
|
|
12
12
|
if (!hasHelpFlag(argv.slice(argumentStart)))
|
|
13
13
|
return undefined;
|
|
14
|
-
const suffix = " Use --help or -h among the leading flags (before any prompt text or --) to show this usage.";
|
|
14
|
+
const suffix = " Thinking effort is a per-run request, not a guarantee. Use --help or -h among the leading flags (before any prompt text or --) to show this usage.";
|
|
15
15
|
const usage = (name, positionals, flags, description) => `Usage: companion.sh ${name}${positionals ? ` ${positionals}` : ""}. ${description} Supported flags: ${flags}.${suffix}`;
|
|
16
16
|
switch (command) {
|
|
17
17
|
case "--help":
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { decideHookOutcomePure } from "./hooks/approval-policy.js";
|
|
2
|
+
// Upstream decorates results it still delivers verbatim otherwise (2.2.0,
|
|
3
|
+
// `agent/toolDedupe/toolDedupeService.ts`): the repeat breaker appends one
|
|
4
|
+
// `\n\n<system-reminder>…</system-reminder>` block from the 3rd identical call
|
|
5
|
+
// (`appendReminder`, also for vetoed results — `finalizeToolResult` runs the
|
|
6
|
+
// `onDidExecuteTool` hooks on every settled result), and at the 12th call it
|
|
7
|
+
// replaces the result with a fixed "not executed" text (`HANDOFF_VETO_TEXT`).
|
|
8
|
+
// Both still mean the hook's veto stood. Re-verify each certification.
|
|
9
|
+
const REMINDER_SUFFIX = /^(?:\n\n<system-reminder>[\s\S]*?<\/system-reminder>)+$/;
|
|
10
|
+
const HANDOFF_VETO_PREFIX = "This turn was ended by the repeat breaker after the same tool call was issued ";
|
|
11
|
+
/** True when `content` is the hook's denial, possibly carrying upstream's repeat-breaker decoration. */
|
|
12
|
+
export function carriesDenial(content, reason) {
|
|
13
|
+
if (content === reason)
|
|
14
|
+
return true;
|
|
15
|
+
if (content.startsWith(reason) && REMINDER_SUFFIX.test(content.slice(reason.length)))
|
|
16
|
+
return true;
|
|
17
|
+
return content.startsWith(HANDOFF_VETO_PREFIX);
|
|
18
|
+
}
|
|
19
|
+
/** Detection evidence, never enforcement. Results arrive after a tool may have
|
|
20
|
+
* executed, and the stream carries no outcome field: a result other than the
|
|
21
|
+
* managed denial is also what upstream emits for calls it rejected BEFORE the
|
|
22
|
+
* hook ran (unknown tool, disabled tool, invalid args, sensitive path, per-tool
|
|
23
|
+
* resolve errors) and what a coexisting hook or permission policy emits when it
|
|
24
|
+
* blocks first. So an escape is reported — logged, persisted, shown — and the
|
|
25
|
+
* run continues; failing the job needs positive execution evidence this stream
|
|
26
|
+
* does not provide. Filesystem policy is never replayed here. */
|
|
27
|
+
export function createHookEscapeMonitor(ctx) {
|
|
28
|
+
const pending = new Map();
|
|
29
|
+
return {
|
|
30
|
+
observe(record) {
|
|
31
|
+
if (!ctx.commandLabel)
|
|
32
|
+
return;
|
|
33
|
+
if (record.role === "assistant") {
|
|
34
|
+
for (const call of record.tool_calls ?? []) {
|
|
35
|
+
let args;
|
|
36
|
+
try {
|
|
37
|
+
args = JSON.parse(call.function.arguments);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
args = {};
|
|
41
|
+
}
|
|
42
|
+
if (typeof args !== "object" || args === null || Array.isArray(args))
|
|
43
|
+
args = {};
|
|
44
|
+
const result = decideHookOutcomePure({
|
|
45
|
+
hook_event_name: "PreToolUse", tool_name: call.function.name,
|
|
46
|
+
tool_input: args, tool_call_id: call.id, cwd: ctx.cwd,
|
|
47
|
+
}, ctx);
|
|
48
|
+
if (!("evaluator" in result) && result.decision === "deny") {
|
|
49
|
+
pending.set(call.id, { reason: result.reason, tool_name: call.function.name });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (record.role === "tool") {
|
|
54
|
+
const denied = pending.get(record.tool_call_id);
|
|
55
|
+
if (denied) {
|
|
56
|
+
pending.delete(record.tool_call_id);
|
|
57
|
+
if (!carriesDenial(record.content, denied.reason)) {
|
|
58
|
+
return { tool_call_id: record.tool_call_id, tool_name: denied.tool_name,
|
|
59
|
+
kind: "mismatch", content_excerpt: record.content.slice(0, 200) };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
/** Every denied call still without a result; call once at settlement. */
|
|
65
|
+
settle() {
|
|
66
|
+
const unresolved = [...pending.entries()].map(([tool_call_id, { tool_name }]) => ({ tool_call_id, tool_name, kind: "unresolved" }));
|
|
67
|
+
pending.clear();
|
|
68
|
+
return unresolved;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -81,6 +81,21 @@ const KNOWN_LABELS = new Set([
|
|
|
81
81
|
"swarm-write",
|
|
82
82
|
]);
|
|
83
83
|
export async function decideHookOutcome(input, ctx) {
|
|
84
|
+
const result = decideHookOutcomePure(input, ctx);
|
|
85
|
+
if (!("evaluator" in result))
|
|
86
|
+
return result;
|
|
87
|
+
const toolName = typeof input.tool_name === "string" ? input.tool_name : "";
|
|
88
|
+
if (ctx.rescueEvaluator !== undefined) {
|
|
89
|
+
return await ctx.rescueEvaluator(result.workspaceRoot, toolName, input.tool_input);
|
|
90
|
+
}
|
|
91
|
+
if (READ_ONLY_TOOLS.has(toolName))
|
|
92
|
+
return { decision: "allow" };
|
|
93
|
+
return {
|
|
94
|
+
decision: "deny",
|
|
95
|
+
reason: `kimi-plugin-cc safety hook: ${ctx.commandLabel} evaluator not configured; tool "${toolName}" denied as a safety default.`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function decideHookOutcomePure(input, ctx) {
|
|
84
99
|
const label = typeof ctx.commandLabel === "string" ? ctx.commandLabel : undefined;
|
|
85
100
|
// Out-of-plugin context: any kimi process spawned without our env var
|
|
86
101
|
// is direct user usage. The hook MUST NOT restrict it.
|
|
@@ -176,18 +191,7 @@ export async function decideHookOutcome(input, ctx) {
|
|
|
176
191
|
"This is a plugin misconfiguration — writes are refused rather than risk the user's working tree.",
|
|
177
192
|
};
|
|
178
193
|
}
|
|
179
|
-
|
|
180
|
-
return await ctx.rescueEvaluator(workspaceRoot, toolName, input.tool_input);
|
|
181
|
-
}
|
|
182
|
-
// Stub for callers that didn't inject the evaluator (tests of the policy
|
|
183
|
-
// function alone). The production entry script always injects it.
|
|
184
|
-
if (READ_ONLY_TOOLS.has(toolName)) {
|
|
185
|
-
return { decision: "allow" };
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
decision: "deny",
|
|
189
|
-
reason: `kimi-plugin-cc safety hook: swarm-write evaluator not configured; tool "${toolName}" denied as a safety default.`,
|
|
190
|
-
};
|
|
194
|
+
return { evaluator: "rescue", workspaceRoot };
|
|
191
195
|
}
|
|
192
196
|
case "rescue": {
|
|
193
197
|
const workspaceRoot = typeof ctx.trustedWorkspaceRoot === "string" ? ctx.trustedWorkspaceRoot : "";
|
|
@@ -200,21 +204,7 @@ export async function decideHookOutcome(input, ctx) {
|
|
|
200
204
|
"This is a plugin misconfiguration — writes are refused rather than trusting hook payload cwd.",
|
|
201
205
|
};
|
|
202
206
|
}
|
|
203
|
-
|
|
204
|
-
return await ctx.rescueEvaluator(workspaceRoot, toolName, input.tool_input);
|
|
205
|
-
}
|
|
206
|
-
// Stub for callers that didn't inject the evaluator (e.g.,
|
|
207
|
-
// tests that exercise only the policy function). Allow
|
|
208
|
-
// Read/Grep/Glob and friends; deny everything else. The
|
|
209
|
-
// production entry script wires the real evaluator from
|
|
210
|
-
// runtime/rescue-approval.ts.
|
|
211
|
-
if (READ_ONLY_TOOLS.has(toolName)) {
|
|
212
|
-
return { decision: "allow" };
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
decision: "deny",
|
|
216
|
-
reason: `kimi-plugin-cc safety hook: rescue evaluator not configured; tool "${toolName}" denied as a safety default.`,
|
|
217
|
-
};
|
|
207
|
+
return { evaluator: "rescue", workspaceRoot };
|
|
218
208
|
}
|
|
219
209
|
default:
|
|
220
210
|
if (KNOWN_LABELS.has(label)) {
|
|
@@ -593,7 +593,7 @@ function validateVersionSensitiveEvents(events, version) {
|
|
|
593
593
|
return invalidHookSet(`version-sensitive event ${JSON.stringify(first.event)} requires a verified kimi-code version`, first.entry, first.line);
|
|
594
594
|
}
|
|
595
595
|
// Exact schemas reviewed at 2.0.0 (1b89e4b0), 2.0.1 (caf7d4e2),
|
|
596
|
-
// 2.0.2 (9d07f634), 2.1.0 (52437299), 2.1.1 (f67e6398)
|
|
596
|
+
// 2.0.2 (9d07f634), 2.1.0 (52437299), 2.1.1 (f67e6398), 2.2.0 (472192c3) and 2.3.0 (b5e28fd6):
|
|
597
597
|
// the additive events and HookDefSchema are unchanged. Require the full
|
|
598
598
|
// version string AND matching components; no inferred 2.x compatibility.
|
|
599
599
|
if (version.major === 2 && ((version.minor === 0 && ((version.version === "2.0.0" && version.patch === 0) ||
|
|
@@ -601,7 +601,8 @@ function validateVersionSensitiveEvents(events, version) {
|
|
|
601
601
|
(version.version === "2.0.2" && version.patch === 2))) ||
|
|
602
602
|
(version.minor === 1 && ((version.version === "2.1.0" && version.patch === 0) ||
|
|
603
603
|
(version.version === "2.1.1" && version.patch === 1))) ||
|
|
604
|
-
(version.minor === 2 && ((version.version === "2.2.0" && version.patch === 0)))
|
|
604
|
+
(version.minor === 2 && ((version.version === "2.2.0" && version.patch === 0))) ||
|
|
605
|
+
(version.minor === 3 && version.version === "2.3.0" && version.patch === 0))) {
|
|
605
606
|
return { valid: true };
|
|
606
607
|
}
|
|
607
608
|
if (version.major !== 0) {
|
package/dist/job-store.js
CHANGED
|
@@ -73,7 +73,7 @@ export class JobStore {
|
|
|
73
73
|
if (!tableHasColumn(this.db, "budget_ms")) {
|
|
74
74
|
this.db.exec(`ALTER TABLE jobs ADD COLUMN budget_ms INTEGER;`);
|
|
75
75
|
}
|
|
76
|
-
for (const column of ["kimi_session_id_source", "budget_source"]) {
|
|
76
|
+
for (const column of ["kimi_session_id_source", "budget_source", "thinking_effort", "hook_escapes"]) {
|
|
77
77
|
if (!tableHasColumn(this.db, column)) {
|
|
78
78
|
this.db.exec("ALTER TABLE jobs ADD COLUMN " + column + " TEXT;");
|
|
79
79
|
}
|
|
@@ -172,14 +172,14 @@ export class JobStore {
|
|
|
172
172
|
try {
|
|
173
173
|
this.db.run(`
|
|
174
174
|
INSERT INTO jobs (
|
|
175
|
-
job_id, repo_id, command_type, created_at, updated_at, cwd, model, thinking,
|
|
175
|
+
job_id, repo_id, command_type, created_at, updated_at, cwd, model, thinking, thinking_effort,
|
|
176
176
|
background, pid, kimi_pid, status, kimi_session_id, kimi_session_id_source, budget_source,
|
|
177
177
|
operation_kind, intended_engine, observed_engine, kimi_version, system_version,
|
|
178
178
|
kimi_command, kimi_prefix_args, plan_certification, resumed_from_job_id, safety_profile,
|
|
179
179
|
agent_profile, prompt_digest, summary, phase, budget_ms, final_output_path, stream_log_path, error
|
|
180
180
|
)
|
|
181
181
|
VALUES (
|
|
182
|
-
@job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking,
|
|
182
|
+
@job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking, @thinking_effort,
|
|
183
183
|
@background, @pid, @kimi_pid, @status, @kimi_session_id, @kimi_session_id_source, @budget_source,
|
|
184
184
|
@operation_kind, @intended_engine, @observed_engine, @kimi_version, @system_version,
|
|
185
185
|
@kimi_command, @kimi_prefix_args, @plan_certification, @resumed_from_job_id, @safety_profile,
|
|
@@ -309,7 +309,7 @@ export class JobStore {
|
|
|
309
309
|
"status", "summary", "phase", "budget_ms", "budget_source", "kimi_session_id_source", "pid", "kimi_pid",
|
|
310
310
|
"final_output_path", "stream_log_path", "kimi_session_id",
|
|
311
311
|
"error", "command_type", "prompt_digest", "repo_id",
|
|
312
|
-
"cwd", "model", "thinking", "background", "agent_profile",
|
|
312
|
+
"cwd", "model", "thinking", "thinking_effort", "hook_escapes", "background", "agent_profile",
|
|
313
313
|
"operation_kind", "intended_engine", "observed_engine", "kimi_version",
|
|
314
314
|
"system_version", "kimi_command", "kimi_prefix_args", "plan_certification",
|
|
315
315
|
"resumed_from_job_id", "safety_profile",
|
|
@@ -349,6 +349,7 @@ function hydrateRow(row) {
|
|
|
349
349
|
function serializeRecord(record) {
|
|
350
350
|
return {
|
|
351
351
|
...record,
|
|
352
|
+
thinking_effort: record.thinking_effort ?? null,
|
|
352
353
|
phase: record.phase ?? null,
|
|
353
354
|
budget_ms: record.budget_ms ?? null,
|
|
354
355
|
budget_source: record.budget_source ?? null,
|
|
@@ -591,6 +592,18 @@ export function withJobStoreSync(paths, fn) {
|
|
|
591
592
|
}
|
|
592
593
|
}
|
|
593
594
|
/** Old non-null budgets have unknown provenance; never invent explicit intent. */
|
|
595
|
+
/** Parsed `hook_escapes` column for JSON envelopes: [] when NULL or unreadable. */
|
|
596
|
+
export function parseHookEscapes(raw) {
|
|
597
|
+
if (!raw)
|
|
598
|
+
return [];
|
|
599
|
+
try {
|
|
600
|
+
const parsed = JSON.parse(raw);
|
|
601
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
594
607
|
export function renderBudgetSource(job) {
|
|
595
608
|
return job.budget_source ?? (job.budget_ms === null ? "default" : "unknown");
|
|
596
609
|
}
|
package/dist/kimi-engine.js
CHANGED
|
@@ -39,6 +39,7 @@ export const NATIVE_V2_CERTIFIED_VERSIONS = Object.freeze([
|
|
|
39
39
|
"2.1.0",
|
|
40
40
|
"2.1.1",
|
|
41
41
|
"2.2.0",
|
|
42
|
+
"2.3.0",
|
|
42
43
|
]);
|
|
43
44
|
/** Per-operation native-v2 certification. One green operation never certifies another. */
|
|
44
45
|
export const NATIVE_V2_CERTIFIED = new Map([
|
|
@@ -206,6 +207,8 @@ export function observedExecutionFields(result) {
|
|
|
206
207
|
return {
|
|
207
208
|
observed_engine: result.observedEngine,
|
|
208
209
|
system_version: result.systemVersion ?? null,
|
|
210
|
+
// Detection evidence from runtime/hook-escape.ts, JSON array or NULL.
|
|
211
|
+
hook_escapes: result.hookEscapes && result.hookEscapes.length > 0 ? JSON.stringify(result.hookEscapes) : null,
|
|
209
212
|
};
|
|
210
213
|
}
|
|
211
214
|
export function executionPlanFromPersisted(fields) {
|
package/dist/kimi-errors.js
CHANGED
|
@@ -45,7 +45,7 @@ export function classifyManagedCommandFailure(error, commandType, jobId, options
|
|
|
45
45
|
if (error instanceof RuntimeError) {
|
|
46
46
|
details.cause_code = error.code;
|
|
47
47
|
if (error.code === "RESPONSE_TIMEOUT") {
|
|
48
|
-
for (const key of ["budget_ms", "record_count", "partial_output_chars", "aborted", "settled_post_abort"]) {
|
|
48
|
+
for (const key of ["budget_ms", "record_count", "partial_output_chars", "aborted", "settled_post_abort", "hook_escapes"]) {
|
|
49
49
|
if (error.details[key] !== undefined)
|
|
50
50
|
details[key] = error.details[key];
|
|
51
51
|
}
|
package/dist/kimi-timeouts.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// startup failures surface as CLI_SPAWN_FAILED / CLI_PROCESS_ERROR —
|
|
9
9
|
// not as separate timeouts. So only the per-command response budgets
|
|
10
10
|
// remain.
|
|
11
|
-
// Budgets
|
|
11
|
+
// Budgets accommodate thinking-capable workflows; per-run effort is only a request.
|
|
12
12
|
// kimi-code spends real wallclock on extended reasoning before emitting
|
|
13
13
|
// the first assistant record; pre-alpha.4 budgets (300s/600s) were sized
|
|
14
14
|
// against the non-thinking path and timed out in production smoke
|
|
@@ -49,15 +49,11 @@ export const KIMI_SWARM_DEFAULT_BUDGET_MS = 1_800_000; // 30 minutes
|
|
|
49
49
|
* Review-gate budget. Fires inside Claude Code's Stop hook so any value
|
|
50
50
|
* above the user's perceptible wait makes the gate feel broken.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* selected for review-gate (via KIMI_PLUGIN_CC_REVIEW_GATE_MODEL).
|
|
58
|
-
* Under thinking-on the gate will time out — the gate is fail-open on
|
|
59
|
-
* timeout by design, so the worst case is the gate becomes
|
|
60
|
-
* always-allow. See docs/safety.md.
|
|
52
|
+
* The gate requests off through KIMI_MODEL_THINKING_EFFORT by default;
|
|
53
|
+
* KIMI_PLUGIN_CC_REVIEW_GATE_THINKING can inherit or request another effort.
|
|
54
|
+
* BYO/non-trait-driven providers and always-thinking models can ignore it.
|
|
55
|
+
* The 8s timeout remains fail-open; a request never guarantees completion.
|
|
56
|
+
* Hook-enforcement escape is a separate blocking failure. See docs/safety.md.
|
|
61
57
|
*/
|
|
62
58
|
export const KIMI_REVIEW_GATE_TIMEOUT_MS = 8_000;
|
|
63
59
|
export function isTimeoutCode(code) {
|
|
@@ -198,8 +198,8 @@ export const KIMI_TESTED_MINORS = [
|
|
|
198
198
|
// import it; `-p` stdout stays the direct PromptJsonWriter), session-lifecycle
|
|
199
199
|
// changes that only HELP our cancellation story (active-turn cancel on close +
|
|
200
200
|
// BACKGROUND_KEEP_ALIVE_ON_EXIT default flipped true→false), an `alwaysThinking`
|
|
201
|
-
// model-capability flag (
|
|
202
|
-
//
|
|
201
|
+
// model-capability flag (the H5 env request ships in plugin 2.2.1,
|
|
202
|
+
// but always-thinking models need not honour it), a SIGHUP cleanup handler (exit 129; SIGTERM still
|
|
203
203
|
// exits 143 and runs cleanup — our SIGTERM→SIGKILL reaping is unaffected), and
|
|
204
204
|
// a new builtin `import-from-cc-codex` skill (not a plugin surface).
|
|
205
205
|
// See .claude/kimi-code-research/reports/72-76 for the audit reports.
|