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.
Files changed (88) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +27 -0
  4. package/README.md +5 -5
  5. package/agents/kimi-ask.md +5 -1
  6. package/agents/kimi-challenge.md +4 -2
  7. package/agents/kimi-pursue.md +3 -1
  8. package/agents/kimi-rescue.md +5 -1
  9. package/agents/kimi-review.md +4 -2
  10. package/agents/kimi-swarm-write.md +3 -1
  11. package/agents/kimi-swarm.md +3 -1
  12. package/commands/ask.md +5 -3
  13. package/commands/challenge.md +5 -3
  14. package/commands/pursue.md +5 -1
  15. package/commands/rescue.md +5 -3
  16. package/commands/review.md +5 -3
  17. package/commands/swarm.md +5 -1
  18. package/dist/cli-client.js +42 -10
  19. package/dist/commands/ask.js +14 -7
  20. package/dist/commands/cli-helpers.js +66 -4
  21. package/dist/commands/pursue.js +7 -3
  22. package/dist/commands/rescue.js +16 -10
  23. package/dist/commands/result.js +5 -1
  24. package/dist/commands/review-gate.js +21 -14
  25. package/dist/commands/review.js +9 -5
  26. package/dist/commands/status.js +2 -2
  27. package/dist/commands/swarm.js +10 -4
  28. package/dist/help.js +1 -1
  29. package/dist/hook-escape.js +71 -0
  30. package/dist/hooks/approval-policy.js +17 -27
  31. package/dist/hooks/config-safety.js +15 -7
  32. package/dist/job-store.js +52 -7
  33. package/dist/kimi-engine.js +2 -0
  34. package/dist/kimi-errors.js +1 -1
  35. package/dist/kimi-timeouts.js +6 -10
  36. package/dist/kimi-version-probe.js +2 -2
  37. package/dist/parsing.js +59 -53
  38. package/dist/session-recovery.js +203 -0
  39. package/dist/vendor/smol-toml/date.js +50 -21
  40. package/dist/vendor/smol-toml/error.js +6 -2
  41. package/dist/vendor/smol-toml/extract.js +259 -66
  42. package/dist/vendor/smol-toml/parse.js +64 -48
  43. package/dist/vendor/smol-toml/primitive.js +107 -135
  44. package/dist/vendor/smol-toml/struct.js +106 -134
  45. package/dist/vendor/smol-toml/util.js +22 -78
  46. package/dist/version.js +1 -1
  47. package/docs/models.md +10 -0
  48. package/docs/registry-releases.md +24 -1
  49. package/docs/safety.md +59 -2
  50. package/package.json +1 -1
  51. package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
  52. package/plugins/kimi-codex/dist/cli-client.js +42 -10
  53. package/plugins/kimi-codex/dist/commands/ask.js +14 -7
  54. package/plugins/kimi-codex/dist/commands/cli-helpers.js +66 -4
  55. package/plugins/kimi-codex/dist/commands/pursue.js +7 -3
  56. package/plugins/kimi-codex/dist/commands/rescue.js +16 -10
  57. package/plugins/kimi-codex/dist/commands/result.js +5 -1
  58. package/plugins/kimi-codex/dist/commands/review-gate.js +21 -14
  59. package/plugins/kimi-codex/dist/commands/review.js +9 -5
  60. package/plugins/kimi-codex/dist/commands/status.js +2 -2
  61. package/plugins/kimi-codex/dist/commands/swarm.js +10 -4
  62. package/plugins/kimi-codex/dist/help.js +1 -1
  63. package/plugins/kimi-codex/dist/hook-escape.js +71 -0
  64. package/plugins/kimi-codex/dist/hooks/approval-policy.js +17 -27
  65. package/plugins/kimi-codex/dist/hooks/config-safety.js +15 -7
  66. package/plugins/kimi-codex/dist/job-store.js +52 -7
  67. package/plugins/kimi-codex/dist/kimi-engine.js +2 -0
  68. package/plugins/kimi-codex/dist/kimi-errors.js +1 -1
  69. package/plugins/kimi-codex/dist/kimi-timeouts.js +6 -10
  70. package/plugins/kimi-codex/dist/kimi-version-probe.js +2 -2
  71. package/plugins/kimi-codex/dist/parsing.js +59 -53
  72. package/plugins/kimi-codex/dist/session-recovery.js +203 -0
  73. package/plugins/kimi-codex/dist/vendor/smol-toml/date.js +50 -21
  74. package/plugins/kimi-codex/dist/vendor/smol-toml/error.js +6 -2
  75. package/plugins/kimi-codex/dist/vendor/smol-toml/extract.js +259 -66
  76. package/plugins/kimi-codex/dist/vendor/smol-toml/parse.js +64 -48
  77. package/plugins/kimi-codex/dist/vendor/smol-toml/primitive.js +107 -135
  78. package/plugins/kimi-codex/dist/vendor/smol-toml/struct.js +106 -134
  79. package/plugins/kimi-codex/dist/vendor/smol-toml/util.js +22 -78
  80. package/plugins/kimi-codex/dist/version.js +1 -1
  81. package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +4 -2
  82. package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +4 -2
  83. package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +3 -1
  84. package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +4 -2
  85. package/plugins/kimi-codex/skills/kimi-review/SKILL.md +4 -2
  86. package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +2 -0
  87. package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +3 -1
  88. package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +3 -1
@@ -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 { 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,13 +77,17 @@ export async function runAsk(argv, context) {
77
77
  command_type: "ask",
78
78
  cwd: context.cwd,
79
79
  model: parsed.model ?? null,
80
- thinking: parsed.thinking ?? null,
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,
84
+ budget_source: parsed.budgetMs !== undefined ? "explicit"
85
+ : sessionResolution.sourceJob?.operation_kind === "ask" && sessionResolution.sourceJob.budget_ms !== null ? "inherited" : "default",
83
86
  pid: parsed.background ? null : process.pid,
84
87
  kimi_pid: null,
85
88
  status: "running",
86
89
  kimi_session_id: sessionResolution.kimiSessionId,
90
+ kimi_session_id_source: sessionResolution.sourceJob?.kimi_session_id_source ?? null,
87
91
  ...persistedExecutionPlanFields(executionPlan),
88
92
  agent_profile: ASK_AGENT_PROFILE_PLACEHOLDER,
89
93
  prompt_digest: digestPrompt(askPrompt),
@@ -105,6 +109,7 @@ export async function runAsk(argv, context) {
105
109
  await markJobFailed(store, paths, job, classified, askConfig.cancellation.failedSummary, { phase: "failed" });
106
110
  throw classified;
107
111
  }
112
+ warnInheritedBudget(job, context.stderr);
108
113
  if (parsed.background) {
109
114
  return startBackgroundJob(job, askPrompt, context, paths, {
110
115
  workerKind: "ask",
@@ -170,6 +175,7 @@ export async function executeAskJob(jobId, prompt, context, options) {
170
175
  prompt,
171
176
  commandLabel: "ask",
172
177
  model: job.model ?? undefined,
178
+ thinkingEffort: job.thinking_effort ?? undefined,
173
179
  resumeSessionId: job.kimi_session_id ?? undefined,
174
180
  logPath: job.stream_log_path,
175
181
  signal: handlers.signal,
@@ -179,17 +185,17 @@ export async function executeAskJob(jobId, prompt, context, options) {
179
185
  throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, "ask.runtime");
180
186
  }
181
187
  assertCliResultSuccess(result, "ask.runtime");
188
+ warnHookEscapes(result, "ask", context.stderr);
182
189
  // Persist whichever session id kimi actually announced. For fresh
183
190
  // sessions this is the only chance to record it. For resumed
184
191
  // sessions it should match the input, but capture anyway in case
185
192
  // kimi-code mints a new id on each turn.
186
193
  if (result.sessionId !== undefined &&
187
- result.sessionId.length > 0 &&
188
- result.sessionId !== job.kimi_session_id) {
194
+ result.sessionId.length > 0) {
189
195
  // length>0 guard: empty-string capture would poison the row. The
190
196
  // warning helper's null-ish check would also fire but only after
191
197
  // the bad write. (Kimi alpha.4 challenge finding #3.)
192
- store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
198
+ store.updateRunningJob(job.job_id, { kimi_session_id_source: "announced", kimi_session_id: result.sessionId });
193
199
  }
194
200
  await syncKimiSessionTitle({
195
201
  promptText: prompt,
@@ -219,6 +225,9 @@ export async function executeAskJob(jobId, prompt, context, options) {
219
225
  }) ?? job);
220
226
  }
221
227
  catch (error) {
228
+ const classified = error instanceof RuntimeError && error.stage === "ask.hook-check" ? error
229
+ : classifyManagedCommandFailure(error, "ask", job.job_id, { background: job.background, budgetMs: job.budget_ms ?? KIMI_ASK_PROMPT_TIMEOUT_MS });
230
+ const partialOutput = await captureTimeoutPartialOutput(classified, store, job, context.env, handlers?.signal);
222
231
  if (handlers?.cancelling) {
223
232
  const cancelledError = new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.default, "ask.runtime", error instanceof Error ? { cause: error } : undefined);
224
233
  return await markJobCancelled(store, paths, job, cancel.cancelledSummary, cancelledError, { phase: "cancelled" });
@@ -231,8 +240,6 @@ export async function executeAskJob(jobId, prompt, context, options) {
231
240
  await markJobFailed(store, paths, job, error, cancel.failedSummary, { phase: "failed" });
232
241
  throw error;
233
242
  }
234
- const classified = classifyManagedCommandFailure(error, "ask", job.job_id, { background: job.background, budgetMs: job.budget_ms ?? KIMI_ASK_PROMPT_TIMEOUT_MS });
235
- const partialOutput = captureTimeoutPartialOutput(classified, store, job);
236
243
  await markJobFailed(store, paths, job, classified, cancel.failedSummary, { phase: "failed", partialOutput });
237
244
  throw classified;
238
245
  }
@@ -1,6 +1,9 @@
1
1
  import { RuntimeError } from "../errors.js";
2
2
  import { ResponseTimeoutError } from "../cli-client.js";
3
3
  import { observedExecutionFields } from "../kimi-engine.js";
4
+ import { resolveKimiHome } from "../kimi-home.js";
5
+ import { recoverSessionIdFromStore } from "../session-recovery.js";
6
+ import { formatBudgetDuration } from "../render.js";
4
7
  /**
5
8
  * Reassemble prose final text from cli-client records.
6
9
  *
@@ -112,12 +115,42 @@ export function assertCliResultSuccess(result, stage) {
112
115
  });
113
116
  }
114
117
  }
115
- /** Timeout-only capture, called after cancellation has taken precedence. No title sync on failure. */
116
- export function captureTimeoutPartialOutput(classified, store, job) {
118
+ /** Timeout-only capture. Cancellation takes precedence, including while the
119
+ * asynchronous store scan is in flight. No title sync on failure. */
120
+ export async function captureTimeoutPartialOutput(classified, store, job, env, cancellationSignal) {
121
+ if (cancellationSignal?.aborted)
122
+ return undefined;
117
123
  const timeout = classified.cause;
118
124
  if (!(timeout instanceof ResponseTimeoutError) || !timeout.partialResult)
119
125
  return undefined;
120
126
  const result = timeout.partialResult;
127
+ let sessionId = result.sessionId;
128
+ let source = sessionId ? "announced" : null;
129
+ const details = classified instanceof RuntimeError ? classified.details : timeout.details;
130
+ details.kimi_session_id_source = job.kimi_session_id_source;
131
+ // The persisted plan is plugin-owned. Recovery never runs on cancellation,
132
+ // v1, successful turns, teardown rejection, or an already-known resume id.
133
+ if (!sessionId && !job.kimi_session_id && job.intended_engine === "native-v2") {
134
+ // Without the system.version marker the row has no proven engine, and
135
+ // resume would refuse the id as lineage-unknown; do not advertise one.
136
+ if (result.observedEngine === null) {
137
+ details.session_recovery = "engine_unobserved";
138
+ }
139
+ else {
140
+ const recovered = await recoverSessionIdFromStore({
141
+ kimiHome: resolveKimiHome(env, job.cwd), cwd: job.cwd,
142
+ spawnedAt: result.spawnedAt, settledAt: result.settledAt, promptDigest: job.prompt_digest,
143
+ store, jobId: job.job_id,
144
+ });
145
+ sessionId = recovered.sessionId;
146
+ if (sessionId)
147
+ source = "recovered_session_dir";
148
+ else
149
+ details.session_recovery = recovered.reason;
150
+ }
151
+ }
152
+ if (cancellationSignal?.aborted)
153
+ return undefined;
121
154
  // Best-effort metadata only. The row is still `running`, and a session id
122
155
  // written here can collide with `jobs_running_session_idx` when a resume of
123
156
  // the same session was started while this job ran. A store error must never
@@ -125,11 +158,22 @@ export function captureTimeoutPartialOutput(classified, store, job) {
125
158
  // than the metadata, so swallow and fall through.
126
159
  try {
127
160
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
128
- if (result.sessionId !== undefined && result.sessionId.length > 0 && result.sessionId !== job.kimi_session_id) {
129
- store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
161
+ if (sessionId) {
162
+ const updated = source === "recovered_session_dir"
163
+ ? store.claimRecoveredSessionId(job.job_id, sessionId)
164
+ : store.updateRunningJob(job.job_id, { kimi_session_id: sessionId, kimi_session_id_source: source });
165
+ if (updated?.kimi_session_id === sessionId && updated.kimi_session_id_source === source) {
166
+ details.kimi_session_id = sessionId;
167
+ details.kimi_session_id_source = source;
168
+ }
169
+ else
170
+ details[source === "recovered_session_dir" ? "session_recovery" : "session_id_persistence"] = "persistence_failed";
130
171
  }
131
172
  }
132
173
  catch {
174
+ // `session_recovery` describes only the recovery branch; an announced id or
175
+ // the provenance update failing is a different fact.
176
+ details[source === "recovered_session_dir" ? "session_recovery" : "session_id_persistence"] = "persistence_failed";
133
177
  // Diagnostics stay in the stream log; the failure transition proceeds.
134
178
  }
135
179
  return {
@@ -141,3 +185,21 @@ export function captureTimeoutPartialOutput(classified, store, job) {
141
185
  budgetMs: timeout.details.budget_ms,
142
186
  };
143
187
  }
188
+ /** One-line human summary of hook-escape findings, or undefined when none. */
189
+ export function describeHookEscapes(result) {
190
+ const escapes = result.hookEscapes ?? [];
191
+ if (escapes.length === 0)
192
+ return undefined;
193
+ const first = escapes[0];
194
+ 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).`;
195
+ }
196
+ export function warnHookEscapes(result, commandType, stderr) {
197
+ const note = describeHookEscapes(result);
198
+ if (note !== undefined)
199
+ stderr.write(`[${commandType}] ${note}\n`);
200
+ }
201
+ export function warnInheritedBudget(job, stderr) {
202
+ if (job.budget_source === "inherited" && job.budget_ms !== null) {
203
+ stderr.write(`[${job.command_type}] budget ${formatBudgetDuration(job.budget_ms)} inherited from job ${job.resumed_from_job_id}; pass --budget to override\n`);
204
+ }
205
+ }
@@ -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,9 +141,11 @@ export async function runPursue(argv, context) {
141
141
  command_type: "rescue",
142
142
  cwd: context.cwd,
143
143
  model: parsed.model ?? null,
144
- thinking: parsed.thinking ?? null,
144
+ thinking: null,
145
+ thinking_effort: parsed.thinkingEffort ?? null,
145
146
  background: false,
146
147
  budget_ms: parsed.budgetMs ?? KIMI_PURSUE_DEFAULT_BUDGET_MS,
148
+ budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
147
149
  pid: process.pid,
148
150
  kimi_pid: null,
149
151
  status: "running",
@@ -232,10 +234,12 @@ async function executePursueJob(jobId, prompt, objective, budgetMs, context) {
232
234
  commandLabel: "rescue",
233
235
  trustedWorkspaceRoot: job.cwd,
234
236
  model: job.model ?? undefined,
237
+ thinkingEffort: job.thinking_effort ?? undefined,
235
238
  logPath: job.stream_log_path,
236
239
  signal: handlers.signal,
237
240
  }, budgetMs, "pursue.prompt");
238
241
  store.updateRunningJob(job.job_id, observedExecutionFields(result));
242
+ warnHookEscapes(result, "pursue", context.stderr);
239
243
  if (handlers.cancelling) {
240
244
  throw new RuntimeError("PURSUE_CANCELLED", "Pursue cancelled by user request.", "pursue.runtime");
241
245
  }
@@ -250,7 +254,7 @@ async function executePursueJob(jobId, prompt, objective, budgetMs, context) {
250
254
  if (result.sessionId !== undefined &&
251
255
  result.sessionId.length > 0 &&
252
256
  result.sessionId !== job.kimi_session_id) {
253
- store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
257
+ store.updateRunningJob(job.job_id, { kimi_session_id_source: "announced", kimi_session_id: result.sessionId });
254
258
  }
255
259
  await syncKimiSessionTitle({
256
260
  promptText: objective,
@@ -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 { 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,13 +69,17 @@ export async function runRescue(argv, context) {
69
69
  command_type: "rescue",
70
70
  cwd: context.cwd,
71
71
  model: parsed.model ?? null,
72
- thinking: parsed.thinking ?? null,
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,
76
+ budget_source: parsed.budgetMs !== undefined ? "explicit"
77
+ : sessionResolution.sourceJob?.operation_kind === "rescue" && sessionResolution.sourceJob.budget_ms !== null ? "inherited" : "default",
75
78
  pid: parsed.background ? null : process.pid,
76
79
  kimi_pid: null,
77
80
  status: "running",
78
81
  kimi_session_id: sessionResolution.kimiSessionId,
82
+ kimi_session_id_source: sessionResolution.sourceJob?.kimi_session_id_source ?? null,
79
83
  ...persistedExecutionPlanFields(executionPlan),
80
84
  agent_profile: RESCUE_AGENT_PROFILE_PLACEHOLDER,
81
85
  prompt_digest: digestPrompt(prompt),
@@ -97,6 +101,7 @@ export async function runRescue(argv, context) {
97
101
  await markJobFailed(store, paths, job, classified, rescueConfig.cancellation.failedSummary, { phase: "failed" });
98
102
  throw classified;
99
103
  }
104
+ warnInheritedBudget(job, context.stderr);
100
105
  if (parsed.background) {
101
106
  return startBackgroundJob(job, prompt, context, paths, {
102
107
  workerKind: "rescue",
@@ -184,6 +189,7 @@ export async function executeRescueJob(jobId, prompt, context, options) {
184
189
  commandLabel: "rescue",
185
190
  trustedWorkspaceRoot: job.cwd,
186
191
  model: job.model ?? undefined,
192
+ thinkingEffort: job.thinking_effort ?? undefined,
187
193
  resumeSessionId: job.kimi_session_id ?? undefined,
188
194
  logPath: job.stream_log_path,
189
195
  signal: handlers.signal,
@@ -195,12 +201,12 @@ export async function executeRescueJob(jobId, prompt, context, options) {
195
201
  throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, "rescue.runtime");
196
202
  }
197
203
  assertCliResultSuccess(result, "rescue.runtime");
204
+ warnHookEscapes(result, "rescue", context.stderr);
198
205
  if (result.sessionId !== undefined &&
199
- result.sessionId.length > 0 &&
200
- result.sessionId !== job.kimi_session_id) {
206
+ result.sessionId.length > 0) {
201
207
  // length>0 guard: empty-string capture would poison the row. (Kimi
202
208
  // alpha.4 challenge finding #3.)
203
- store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
209
+ store.updateRunningJob(job.job_id, { kimi_session_id_source: "announced", kimi_session_id: result.sessionId });
204
210
  }
205
211
  await syncKimiSessionTitle({
206
212
  promptText: prompt,
@@ -246,16 +252,16 @@ export async function executeRescueJob(jobId, prompt, context, options) {
246
252
  }) ?? job);
247
253
  }
248
254
  catch (error) {
249
- if (handlers.cancelling) {
250
- const cancelledError = new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.default, "rescue.runtime", error instanceof Error ? { cause: error } : undefined);
251
- return await markJobCancelled(store, paths, job, cancel.cancelledSummary, cancelledError, { phase: "cancelled" });
252
- }
253
255
  const classified = classifyManagedCommandFailure(error, "rescue", job.job_id, {
254
256
  preserveStage: true,
255
257
  background: job.background,
256
258
  budgetMs: job.budget_ms ?? KIMI_RESCUE_PROMPT_TIMEOUT_MS,
257
259
  });
258
- const partialOutput = captureTimeoutPartialOutput(classified, store, job);
260
+ const partialOutput = await captureTimeoutPartialOutput(classified, store, job, context.env, handlers.signal);
261
+ if (handlers.cancelling) {
262
+ const cancelledError = new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.default, "rescue.runtime", error instanceof Error ? { cause: error } : undefined);
263
+ return await markJobCancelled(store, paths, job, cancel.cancelledSummary, cancelledError, { phase: "cancelled" });
264
+ }
259
265
  return await markJobFailed(store, paths, job, classified, cancel.failedSummary, { phase: "failed", partialOutput });
260
266
  }
261
267
  finally {
@@ -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 { 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";
@@ -43,6 +43,10 @@ function renderResultEnvelope(job, artifactPath, body) {
43
43
  kind: job.command_type,
44
44
  status: job.status,
45
45
  budget_ms: job.budget_ms,
46
+ budget_source: renderBudgetSource(job),
47
+ thinking_effort_requested: job.thinking_effort ?? null,
48
+ hook_escapes: parseHookEscapes(job.hook_escapes),
49
+ kimi_session_id_source: job.kimi_session_id_source,
46
50
  kimi_session_id: job.kimi_session_id,
47
51
  // True when a budget timeout preserved assistant prose in the artifact.
48
52
  partial_output: job.status === "failed" && Number(job.error?.details?.partial_output_chars ?? 0) > 0,
@@ -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
- return {
93
- systemMessage: `Kimi review gate noted concerns but allowed stop: ${output.summary}`,
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,11 +155,13 @@ 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,
154
162
  status: "running",
155
163
  kimi_session_id: null,
164
+ budget_source: "default",
156
165
  ...persistedExecutionPlanFields(executionPlan),
157
166
  agent_profile: REVIEW_GATE_AGENT_PROFILE_PLACEHOLDER,
158
167
  prompt_digest: digestPrompt(prompt),
@@ -177,17 +186,15 @@ async function executeReviewGate(payload, assistantMessage, context) {
177
186
  prompt,
178
187
  commandLabel: "review_gate",
179
188
  model,
180
- // Intent: thinking-off so the 8s Stop-hook budget is achievable.
181
- // Mechanism: currently advisory — kimi-code 0.1.1 has no CLI
182
- // flag for this; control is config-based (default_thinking /
183
- // [thinking].mode). The field carries intent through the
184
- // contract; when upstream adds a per-spawn flag, buildArgs will
185
- // translate it. (Round 2 Codex finding.)
186
- thinking: false,
189
+ // Requested via env; provider/model may ignore it. The 8s budget still applies.
190
+ thinkingEffort,
187
191
  logPath,
188
192
  }, KIMI_REVIEW_GATE_TIMEOUT_MS, "review_gate.runtime");
189
193
  activeStore.updateRunningJob(job.job_id, observedExecutionFields(result));
190
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);
191
198
  if (result.sessionId !== undefined && result.sessionId.length > 0) {
192
199
  // length>0 guard matches the other commands (Kimi alpha.4
193
200
  // challenge finding #3 — defense-in-depth, since
@@ -195,7 +202,7 @@ async function executeReviewGate(payload, assistantMessage, context) {
195
202
  // can't synthesize empty strings today, but the guard locks the
196
203
  // SQLite contract independent of upstream behavior).
197
204
  activeStore.updateRunningJob(job.job_id, {
198
- kimi_session_id: result.sessionId,
205
+ kimi_session_id_source: "announced", kimi_session_id: result.sessionId,
199
206
  });
200
207
  }
201
208
  const finalText = reassembleProseFromRecords(result.records);
@@ -216,7 +223,7 @@ async function executeReviewGate(payload, assistantMessage, context) {
216
223
  final_output_path: artifactPath,
217
224
  error: null,
218
225
  });
219
- return rendered.output;
226
+ return { ...rendered.output, hookEscapeNote };
220
227
  }
221
228
  catch (error) {
222
229
  const classified = classifyManagedCommandFailure(error, "review_gate", job.job_id);
@@ -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,9 +79,11 @@ 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: parsed.thinking ?? null,
82
+ thinking: null,
83
+ thinking_effort: parsed.thinkingEffort ?? null,
83
84
  background: false,
84
85
  budget_ms: parsed.budgetMs ?? null,
86
+ budget_source: parsed.budgetMs !== undefined ? "explicit" : "default",
85
87
  pid: process.pid,
86
88
  kimi_pid: null,
87
89
  status: "running",
@@ -112,6 +114,7 @@ export async function runReview(argv, context, commandType) {
112
114
  prompt: previewPrompt,
113
115
  commandLabel: commandType,
114
116
  model: parsed.model,
117
+ thinkingEffort: parsed.thinkingEffort,
115
118
  logPath,
116
119
  signal: handlers.signal,
117
120
  }, job.budget_ms ?? KIMI_REVIEW_PROMPT_TIMEOUT_MS, `${commandType}.prompt`);
@@ -120,6 +123,7 @@ export async function runReview(argv, context, commandType) {
120
123
  throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, `${commandType}.runtime`);
121
124
  }
122
125
  assertCliResultSuccess(result, `${commandType}.runtime`);
126
+ warnHookEscapes(result, commandType, context.stderr);
123
127
  // Persist whichever session id kimi announced. Review never resumes,
124
128
  // but the id is the only handle for post-hoc replay against
125
129
  // ~/.kimi-code/sessions/.
@@ -128,7 +132,7 @@ export async function runReview(argv, context, commandType) {
128
132
  // poison the row — consumers reading SQLite distinguish "missing"
129
133
  // (NULL) from "captured" (non-empty UUID) only via NULL-ness.
130
134
  // (Kimi alpha.4 challenge finding #3.)
131
- store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
135
+ store.updateRunningJob(job.job_id, { kimi_session_id_source: "announced", kimi_session_id: result.sessionId });
132
136
  }
133
137
  await syncKimiSessionTitle({
134
138
  promptText: previewPrompt,
@@ -159,13 +163,13 @@ export async function runReview(argv, context, commandType) {
159
163
  return rendered.output;
160
164
  }
161
165
  catch (error) {
166
+ const classified = classifyManagedCommandFailure(error, commandType, job.job_id, { background: job.background, budgetMs: job.budget_ms ?? KIMI_REVIEW_PROMPT_TIMEOUT_MS });
167
+ const partialOutput = await captureTimeoutPartialOutput(classified, store, job, context.env, handlers.signal);
162
168
  if (handlers.cancelling) {
163
169
  const cancelledError = new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.default, `${commandType}.runtime`, error instanceof Error ? { cause: error } : undefined);
164
170
  await markJobCancelled(store, paths, job, cancel.cancelledSummary, cancelledError);
165
171
  throw cancelledError;
166
172
  }
167
- const classified = classifyManagedCommandFailure(error, commandType, job.job_id, { background: job.background, budgetMs: job.budget_ms ?? KIMI_REVIEW_PROMPT_TIMEOUT_MS });
168
- const partialOutput = captureTimeoutPartialOutput(classified, store, job);
169
173
  await markJobFailed(store, paths, job, classified, cancel.failedSummary, { partialOutput });
170
174
  throw classified;
171
175
  }
@@ -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: parsed.thinking ?? null,
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: parsed.thinking ?? null,
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,
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":