kimi-plugin-cc 2.0.8 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +27 -0
- package/README.md +5 -5
- package/agents/kimi-ask.md +5 -1
- package/agents/kimi-challenge.md +4 -2
- package/agents/kimi-pursue.md +3 -1
- package/agents/kimi-rescue.md +5 -1
- package/agents/kimi-review.md +4 -2
- package/agents/kimi-swarm-write.md +3 -1
- package/agents/kimi-swarm.md +3 -1
- package/commands/ask.md +5 -3
- package/commands/challenge.md +5 -3
- package/commands/pursue.md +5 -1
- package/commands/rescue.md +5 -3
- package/commands/review.md +5 -3
- package/commands/swarm.md +5 -1
- package/dist/cli-client.js +42 -10
- package/dist/commands/ask.js +14 -7
- package/dist/commands/cli-helpers.js +66 -4
- package/dist/commands/pursue.js +7 -3
- package/dist/commands/rescue.js +16 -10
- package/dist/commands/result.js +5 -1
- package/dist/commands/review-gate.js +21 -14
- package/dist/commands/review.js +9 -5
- package/dist/commands/status.js +2 -2
- package/dist/commands/swarm.js +10 -4
- 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 +15 -7
- package/dist/job-store.js +52 -7
- package/dist/kimi-engine.js +2 -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/session-recovery.js +203 -0
- package/dist/vendor/smol-toml/date.js +50 -21
- package/dist/vendor/smol-toml/error.js +6 -2
- package/dist/vendor/smol-toml/extract.js +259 -66
- package/dist/vendor/smol-toml/parse.js +64 -48
- package/dist/vendor/smol-toml/primitive.js +107 -135
- package/dist/vendor/smol-toml/struct.js +106 -134
- package/dist/vendor/smol-toml/util.js +22 -78
- package/dist/version.js +1 -1
- package/docs/models.md +10 -0
- package/docs/registry-releases.md +24 -1
- package/docs/safety.md +59 -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 -10
- package/plugins/kimi-codex/dist/commands/ask.js +14 -7
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +66 -4
- package/plugins/kimi-codex/dist/commands/pursue.js +7 -3
- package/plugins/kimi-codex/dist/commands/rescue.js +16 -10
- package/plugins/kimi-codex/dist/commands/result.js +5 -1
- package/plugins/kimi-codex/dist/commands/review-gate.js +21 -14
- package/plugins/kimi-codex/dist/commands/review.js +9 -5
- package/plugins/kimi-codex/dist/commands/status.js +2 -2
- package/plugins/kimi-codex/dist/commands/swarm.js +10 -4
- 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 +15 -7
- package/plugins/kimi-codex/dist/job-store.js +52 -7
- package/plugins/kimi-codex/dist/kimi-engine.js +2 -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/session-recovery.js +203 -0
- package/plugins/kimi-codex/dist/vendor/smol-toml/date.js +50 -21
- package/plugins/kimi-codex/dist/vendor/smol-toml/error.js +6 -2
- package/plugins/kimi-codex/dist/vendor/smol-toml/extract.js +259 -66
- package/plugins/kimi-codex/dist/vendor/smol-toml/parse.js +64 -48
- package/plugins/kimi-codex/dist/vendor/smol-toml/primitive.js +107 -135
- package/plugins/kimi-codex/dist/vendor/smol-toml/struct.js +106 -134
- package/plugins/kimi-codex/dist/vendor/smol-toml/util.js +22 -78
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +4 -2
- package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +4 -2
- package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +4 -2
- package/plugins/kimi-codex/skills/kimi-review/SKILL.md +4 -2
- 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
|
@@ -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 { 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, 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
|
}
|
|
@@ -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,9 +208,11 @@ 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,
|
|
215
|
+
budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
|
|
214
216
|
pid: process.pid,
|
|
215
217
|
kimi_pid: null,
|
|
216
218
|
status: "running",
|
|
@@ -307,9 +309,11 @@ async function runWriteSwarm(parsed, objective, context) {
|
|
|
307
309
|
command_type: "rescue",
|
|
308
310
|
cwd: context.cwd,
|
|
309
311
|
model: parsed.model ?? null,
|
|
310
|
-
thinking:
|
|
312
|
+
thinking: null,
|
|
313
|
+
thinking_effort: parsed.thinkingEffort ?? null,
|
|
311
314
|
background: false,
|
|
312
315
|
budget_ms: parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS,
|
|
316
|
+
budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
|
|
313
317
|
pid: process.pid,
|
|
314
318
|
kimi_pid: null,
|
|
315
319
|
status: "running",
|
|
@@ -491,6 +495,7 @@ writeMode) {
|
|
|
491
495
|
// by other labels). Only set in write mode.
|
|
492
496
|
trustedWorkspaceRoot: writeMode?.workspaceRoot,
|
|
493
497
|
model: job.model ?? undefined,
|
|
498
|
+
thinkingEffort: job.thinking_effort ?? undefined,
|
|
494
499
|
logPath: job.stream_log_path,
|
|
495
500
|
signal: handlers.signal,
|
|
496
501
|
}, budgetMs, "swarm.prompt");
|
|
@@ -499,10 +504,11 @@ writeMode) {
|
|
|
499
504
|
throw new RuntimeError("SWARM_CANCELLED", "Swarm cancelled by user request.", "swarm.runtime");
|
|
500
505
|
}
|
|
501
506
|
assertCliResultSuccess(result, "swarm.runtime");
|
|
507
|
+
warnHookEscapes(result, "swarm", context.stderr);
|
|
502
508
|
if (result.sessionId !== undefined &&
|
|
503
509
|
result.sessionId.length > 0 &&
|
|
504
510
|
result.sessionId !== job.kimi_session_id) {
|
|
505
|
-
store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
|
|
511
|
+
store.updateRunningJob(job.job_id, { kimi_session_id_source: "announced", kimi_session_id: result.sessionId });
|
|
506
512
|
}
|
|
507
513
|
await syncKimiSessionTitle({
|
|
508
514
|
promptText: objective,
|
|
@@ -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)) {
|
|
@@ -417,15 +417,23 @@ export function normalizeTopLevelInlineHooks(contents, lineEnding) {
|
|
|
417
417
|
entryCount: hooks.length,
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
|
+
// Same defaults as smol-toml 1.9.1 parse(); this walker only locates assignments.
|
|
421
|
+
// tests/runtime/toml-walker-context.test.ts asserts these against the vendored parse().
|
|
422
|
+
export function assignmentParseContext(s) {
|
|
423
|
+
return { s, p: s.charCodeAt(0) === 0xfeff ? 1 : 0, d: 1_000, bi: false, ld: true, uk: 0 };
|
|
424
|
+
}
|
|
420
425
|
function findTopLevelHooksAssignment(contents) {
|
|
421
|
-
|
|
422
|
-
|
|
426
|
+
const ctx = assignmentParseContext(contents);
|
|
427
|
+
skipVoid(ctx);
|
|
428
|
+
while (ctx.p < contents.length) {
|
|
423
429
|
// A root-level direct assignment cannot resume after entering a table.
|
|
424
|
-
if (contents[
|
|
430
|
+
if (contents[ctx.p] === "[")
|
|
425
431
|
return null;
|
|
426
|
-
const declarationStart =
|
|
427
|
-
const
|
|
428
|
-
|
|
432
|
+
const declarationStart = ctx.p;
|
|
433
|
+
const key = parseKey(ctx);
|
|
434
|
+
skipVoid(ctx, true, true);
|
|
435
|
+
extractValue(ctx, undefined);
|
|
436
|
+
const valueEnd = ctx.p;
|
|
429
437
|
if (key.length === 1 && key[0] === "hooks") {
|
|
430
438
|
const lineStart = contents.lastIndexOf("\n", declarationStart - 1) + 1;
|
|
431
439
|
const start = /^[\t ]*$/.test(contents.slice(lineStart, declarationStart))
|
|
@@ -445,7 +453,7 @@ function findTopLevelHooksAssignment(contents) {
|
|
|
445
453
|
}
|
|
446
454
|
return { start, end, hadLineEnding };
|
|
447
455
|
}
|
|
448
|
-
|
|
456
|
+
skipVoid(ctx);
|
|
449
457
|
}
|
|
450
458
|
return null;
|
|
451
459
|
}
|
|
@@ -11,8 +11,11 @@ export class JobStore {
|
|
|
11
11
|
let db;
|
|
12
12
|
try {
|
|
13
13
|
db = createSqliteAdapter(paths.stateDbPath);
|
|
14
|
-
|
|
14
|
+
// busy_timeout FIRST: switching the journal mode takes a brief write lock,
|
|
15
|
+
// and a second opener (status/result racing a worker) otherwise fails
|
|
16
|
+
// immediately with SQLITE_BUSY instead of waiting (seen 2026-10-14).
|
|
15
17
|
db.pragma("busy_timeout = 5000");
|
|
18
|
+
db.pragma("journal_mode = WAL");
|
|
16
19
|
}
|
|
17
20
|
catch (error) {
|
|
18
21
|
try {
|
|
@@ -70,6 +73,11 @@ export class JobStore {
|
|
|
70
73
|
if (!tableHasColumn(this.db, "budget_ms")) {
|
|
71
74
|
this.db.exec(`ALTER TABLE jobs ADD COLUMN budget_ms INTEGER;`);
|
|
72
75
|
}
|
|
76
|
+
for (const column of ["kimi_session_id_source", "budget_source", "thinking_effort", "hook_escapes"]) {
|
|
77
|
+
if (!tableHasColumn(this.db, column)) {
|
|
78
|
+
this.db.exec("ALTER TABLE jobs ADD COLUMN " + column + " TEXT;");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
73
81
|
// Additive provenance migration. Existing rows deliberately remain NULL:
|
|
74
82
|
// v1.9.5 plus kimi-code 0.33/0.34 could have routed to native v2 before the
|
|
75
83
|
// legacy pin landed, so a blanket historical v1 backfill would invent fact.
|
|
@@ -98,6 +106,8 @@ export class JobStore {
|
|
|
98
106
|
CREATE INDEX IF NOT EXISTS jobs_repo_type_updated_idx
|
|
99
107
|
ON jobs (repo_id, command_type, updated_at DESC);
|
|
100
108
|
|
|
109
|
+
CREATE INDEX IF NOT EXISTS jobs_session_id_idx ON jobs (kimi_session_id);
|
|
110
|
+
|
|
101
111
|
CREATE INDEX IF NOT EXISTS jobs_session_idx
|
|
102
112
|
ON jobs (repo_id, kimi_session_id, updated_at DESC);
|
|
103
113
|
|
|
@@ -162,15 +172,15 @@ export class JobStore {
|
|
|
162
172
|
try {
|
|
163
173
|
this.db.run(`
|
|
164
174
|
INSERT INTO jobs (
|
|
165
|
-
job_id, repo_id, command_type, created_at, updated_at, cwd, model, thinking,
|
|
166
|
-
background, pid, kimi_pid, status, kimi_session_id,
|
|
175
|
+
job_id, repo_id, command_type, created_at, updated_at, cwd, model, thinking, thinking_effort,
|
|
176
|
+
background, pid, kimi_pid, status, kimi_session_id, kimi_session_id_source, budget_source,
|
|
167
177
|
operation_kind, intended_engine, observed_engine, kimi_version, system_version,
|
|
168
178
|
kimi_command, kimi_prefix_args, plan_certification, resumed_from_job_id, safety_profile,
|
|
169
179
|
agent_profile, prompt_digest, summary, phase, budget_ms, final_output_path, stream_log_path, error
|
|
170
180
|
)
|
|
171
181
|
VALUES (
|
|
172
|
-
@job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking,
|
|
173
|
-
@background, @pid, @kimi_pid, @status, @kimi_session_id,
|
|
182
|
+
@job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking, @thinking_effort,
|
|
183
|
+
@background, @pid, @kimi_pid, @status, @kimi_session_id, @kimi_session_id_source, @budget_source,
|
|
174
184
|
@operation_kind, @intended_engine, @observed_engine, @kimi_version, @system_version,
|
|
175
185
|
@kimi_command, @kimi_prefix_args, @plan_certification, @resumed_from_job_id, @safety_profile,
|
|
176
186
|
@agent_profile, @prompt_digest, @summary, @phase, @budget_ms, @final_output_path, @stream_log_path, @error
|
|
@@ -198,6 +208,10 @@ export class JobStore {
|
|
|
198
208
|
const row = this.db.get("SELECT * FROM jobs WHERE job_id = ?", jobId);
|
|
199
209
|
return row ? hydrateRow(row) : null;
|
|
200
210
|
}
|
|
211
|
+
/** Global across repos and terminal states; optionally exclude the current row. */
|
|
212
|
+
hasSessionId(sessionId, exceptJobId = "") {
|
|
213
|
+
return Boolean(this.db.get("SELECT 1 FROM jobs WHERE kimi_session_id = ? AND job_id != ? LIMIT 1", sessionId, exceptJobId));
|
|
214
|
+
}
|
|
201
215
|
findLatestJob(options) {
|
|
202
216
|
const conditions = ["repo_id = @repo_id"];
|
|
203
217
|
const params = {
|
|
@@ -247,6 +261,18 @@ export class JobStore {
|
|
|
247
261
|
updateRunningJob(jobId, patch) {
|
|
248
262
|
return this.updateWhere(jobId, patch, "status = 'running'");
|
|
249
263
|
}
|
|
264
|
+
/** Recheck uniqueness atomically: another worker can claim an ID during the
|
|
265
|
+
* asynchronous directory scan. Ordinary announced/resumed IDs may be shared. */
|
|
266
|
+
claimRecoveredSessionId(jobId, sessionId) {
|
|
267
|
+
this.db.run(`UPDATE jobs
|
|
268
|
+
SET kimi_session_id = @session_id, kimi_session_id_source = 'recovered_session_dir',
|
|
269
|
+
updated_at = @updated_at
|
|
270
|
+
WHERE job_id = @job_id AND status = 'running' AND kimi_session_id IS NULL
|
|
271
|
+
AND NOT EXISTS (SELECT 1 FROM jobs WHERE kimi_session_id = @session_id AND job_id != @job_id)`, { job_id: jobId, session_id: sessionId, updated_at: new Date().toISOString() });
|
|
272
|
+
const job = this.getJob(jobId);
|
|
273
|
+
return job?.status === "running" && job.kimi_session_id === sessionId &&
|
|
274
|
+
job.kimi_session_id_source === "recovered_session_dir" ? job : null;
|
|
275
|
+
}
|
|
250
276
|
/**
|
|
251
277
|
* Evidence-only backfill for pre-provenance rows. COALESCE prevents forensic
|
|
252
278
|
* inspection from overwriting a field already recorded at execution time.
|
|
@@ -280,10 +306,10 @@ export class JobStore {
|
|
|
280
306
|
return this.updateWhere(jobId, { ...patch, status: "cancelled", pid: null, kimi_pid: null }, "status = 'running'");
|
|
281
307
|
}
|
|
282
308
|
static JOBS_COLUMNS = new Set([
|
|
283
|
-
"status", "summary", "phase", "budget_ms", "pid", "kimi_pid",
|
|
309
|
+
"status", "summary", "phase", "budget_ms", "budget_source", "kimi_session_id_source", "pid", "kimi_pid",
|
|
284
310
|
"final_output_path", "stream_log_path", "kimi_session_id",
|
|
285
311
|
"error", "command_type", "prompt_digest", "repo_id",
|
|
286
|
-
"cwd", "model", "thinking", "background", "agent_profile",
|
|
312
|
+
"cwd", "model", "thinking", "thinking_effort", "hook_escapes", "background", "agent_profile",
|
|
287
313
|
"operation_kind", "intended_engine", "observed_engine", "kimi_version",
|
|
288
314
|
"system_version", "kimi_command", "kimi_prefix_args", "plan_certification",
|
|
289
315
|
"resumed_from_job_id", "safety_profile",
|
|
@@ -323,8 +349,11 @@ function hydrateRow(row) {
|
|
|
323
349
|
function serializeRecord(record) {
|
|
324
350
|
return {
|
|
325
351
|
...record,
|
|
352
|
+
thinking_effort: record.thinking_effort ?? null,
|
|
326
353
|
phase: record.phase ?? null,
|
|
327
354
|
budget_ms: record.budget_ms ?? null,
|
|
355
|
+
budget_source: record.budget_source ?? null,
|
|
356
|
+
kimi_session_id_source: record.kimi_session_id_source ?? null,
|
|
328
357
|
operation_kind: record.operation_kind ?? null,
|
|
329
358
|
intended_engine: record.intended_engine ?? null,
|
|
330
359
|
observed_engine: record.observed_engine ?? null,
|
|
@@ -562,3 +591,19 @@ export function withJobStoreSync(paths, fn) {
|
|
|
562
591
|
store?.close();
|
|
563
592
|
}
|
|
564
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
|
+
}
|
|
607
|
+
export function renderBudgetSource(job) {
|
|
608
|
+
return job.budget_source ?? (job.budget_ms === null ? "default" : "unknown");
|
|
609
|
+
}
|
|
@@ -206,6 +206,8 @@ export function observedExecutionFields(result) {
|
|
|
206
206
|
return {
|
|
207
207
|
observed_engine: result.observedEngine,
|
|
208
208
|
system_version: result.systemVersion ?? null,
|
|
209
|
+
// Detection evidence from runtime/hook-escape.ts, JSON array or NULL.
|
|
210
|
+
hook_escapes: result.hookEscapes && result.hookEscapes.length > 0 ? JSON.stringify(result.hookEscapes) : null,
|
|
209
211
|
};
|
|
210
212
|
}
|
|
211
213
|
export function executionPlanFromPersisted(fields) {
|
|
@@ -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
|
}
|
|
@@ -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.
|