kimi-plugin-cc 2.0.7 → 2.2.0
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 +50 -0
- package/README.md +22 -6
- package/agents/kimi-ask.md +3 -0
- package/agents/kimi-challenge.md +3 -1
- package/agents/kimi-rescue.md +3 -0
- package/agents/kimi-review.md +3 -1
- package/commands/ask.md +6 -2
- package/commands/challenge.md +6 -2
- package/commands/rescue.md +6 -2
- package/commands/review.md +6 -2
- package/dist/cli-client.js +36 -13
- package/dist/commands/ask.js +16 -7
- package/dist/commands/cli-helpers.js +80 -0
- package/dist/commands/pursue.js +4 -2
- package/dist/commands/repair-sessions.js +2 -1
- package/dist/commands/replay.js +3 -2
- package/dist/commands/rescue.js +19 -9
- package/dist/commands/result.js +7 -1
- package/dist/commands/review-gate.js +2 -1
- package/dist/commands/review.js +8 -5
- package/dist/commands/setup.js +2 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/swarm.js +7 -3
- package/dist/companion.js +6 -0
- package/dist/help.js +40 -0
- package/dist/hooks/config-safety.js +18 -9
- package/dist/job-store.js +43 -6
- package/dist/jobs.js +7 -3
- package/dist/kimi-engine.js +1 -0
- package/dist/kimi-errors.js +35 -1
- package/dist/kimi-timeouts.js +7 -7
- package/dist/parsing.js +87 -13
- package/dist/render.js +19 -1
- 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/migration.md +3 -3
- package/docs/registry-releases.md +35 -1
- package/docs/safety.md +49 -2
- package/package.json +2 -2
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/cli-client.js +36 -13
- package/plugins/kimi-codex/dist/commands/ask.js +16 -7
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +80 -0
- package/plugins/kimi-codex/dist/commands/pursue.js +4 -2
- package/plugins/kimi-codex/dist/commands/repair-sessions.js +2 -1
- package/plugins/kimi-codex/dist/commands/replay.js +3 -2
- package/plugins/kimi-codex/dist/commands/rescue.js +19 -9
- package/plugins/kimi-codex/dist/commands/result.js +7 -1
- package/plugins/kimi-codex/dist/commands/review-gate.js +2 -1
- package/plugins/kimi-codex/dist/commands/review.js +8 -5
- package/plugins/kimi-codex/dist/commands/setup.js +2 -1
- 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/companion.js +6 -0
- package/plugins/kimi-codex/dist/help.js +40 -0
- package/plugins/kimi-codex/dist/hooks/config-safety.js +18 -9
- package/plugins/kimi-codex/dist/job-store.js +43 -6
- package/plugins/kimi-codex/dist/jobs.js +7 -3
- package/plugins/kimi-codex/dist/kimi-engine.js +1 -0
- package/plugins/kimi-codex/dist/kimi-errors.js +35 -1
- package/plugins/kimi-codex/dist/kimi-timeouts.js +7 -7
- package/plugins/kimi-codex/dist/parsing.js +87 -13
- package/plugins/kimi-codex/dist/render.js +19 -1
- 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 +3 -1
- package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +2 -1
- package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +3 -1
- package/plugins/kimi-codex/skills/kimi-review/SKILL.md +3 -2
package/dist/job-store.js
CHANGED
|
@@ -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 {
|
|
@@ -58,6 +61,7 @@ export class JobStore {
|
|
|
58
61
|
prompt_digest TEXT NOT NULL,
|
|
59
62
|
summary TEXT NOT NULL,
|
|
60
63
|
phase TEXT,
|
|
64
|
+
budget_ms INTEGER,
|
|
61
65
|
final_output_path TEXT,
|
|
62
66
|
stream_log_path TEXT NOT NULL,
|
|
63
67
|
error TEXT
|
|
@@ -66,6 +70,14 @@ export class JobStore {
|
|
|
66
70
|
if (!tableHasColumn(this.db, "phase")) {
|
|
67
71
|
this.db.exec(`ALTER TABLE jobs ADD COLUMN phase TEXT;`);
|
|
68
72
|
}
|
|
73
|
+
if (!tableHasColumn(this.db, "budget_ms")) {
|
|
74
|
+
this.db.exec(`ALTER TABLE jobs ADD COLUMN budget_ms INTEGER;`);
|
|
75
|
+
}
|
|
76
|
+
for (const column of ["kimi_session_id_source", "budget_source"]) {
|
|
77
|
+
if (!tableHasColumn(this.db, column)) {
|
|
78
|
+
this.db.exec("ALTER TABLE jobs ADD COLUMN " + column + " TEXT;");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
69
81
|
// Additive provenance migration. Existing rows deliberately remain NULL:
|
|
70
82
|
// v1.9.5 plus kimi-code 0.33/0.34 could have routed to native v2 before the
|
|
71
83
|
// legacy pin landed, so a blanket historical v1 backfill would invent fact.
|
|
@@ -94,6 +106,8 @@ export class JobStore {
|
|
|
94
106
|
CREATE INDEX IF NOT EXISTS jobs_repo_type_updated_idx
|
|
95
107
|
ON jobs (repo_id, command_type, updated_at DESC);
|
|
96
108
|
|
|
109
|
+
CREATE INDEX IF NOT EXISTS jobs_session_id_idx ON jobs (kimi_session_id);
|
|
110
|
+
|
|
97
111
|
CREATE INDEX IF NOT EXISTS jobs_session_idx
|
|
98
112
|
ON jobs (repo_id, kimi_session_id, updated_at DESC);
|
|
99
113
|
|
|
@@ -159,17 +173,17 @@ export class JobStore {
|
|
|
159
173
|
this.db.run(`
|
|
160
174
|
INSERT INTO jobs (
|
|
161
175
|
job_id, repo_id, command_type, created_at, updated_at, cwd, model, thinking,
|
|
162
|
-
background, pid, kimi_pid, status, kimi_session_id,
|
|
176
|
+
background, pid, kimi_pid, status, kimi_session_id, kimi_session_id_source, budget_source,
|
|
163
177
|
operation_kind, intended_engine, observed_engine, kimi_version, system_version,
|
|
164
178
|
kimi_command, kimi_prefix_args, plan_certification, resumed_from_job_id, safety_profile,
|
|
165
|
-
agent_profile, prompt_digest, summary, phase, final_output_path, stream_log_path, error
|
|
179
|
+
agent_profile, prompt_digest, summary, phase, budget_ms, final_output_path, stream_log_path, error
|
|
166
180
|
)
|
|
167
181
|
VALUES (
|
|
168
182
|
@job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking,
|
|
169
|
-
@background, @pid, @kimi_pid, @status, @kimi_session_id,
|
|
183
|
+
@background, @pid, @kimi_pid, @status, @kimi_session_id, @kimi_session_id_source, @budget_source,
|
|
170
184
|
@operation_kind, @intended_engine, @observed_engine, @kimi_version, @system_version,
|
|
171
185
|
@kimi_command, @kimi_prefix_args, @plan_certification, @resumed_from_job_id, @safety_profile,
|
|
172
|
-
@agent_profile, @prompt_digest, @summary, @phase, @final_output_path, @stream_log_path, @error
|
|
186
|
+
@agent_profile, @prompt_digest, @summary, @phase, @budget_ms, @final_output_path, @stream_log_path, @error
|
|
173
187
|
)
|
|
174
188
|
`, {
|
|
175
189
|
...serializeRecord(input),
|
|
@@ -194,6 +208,10 @@ export class JobStore {
|
|
|
194
208
|
const row = this.db.get("SELECT * FROM jobs WHERE job_id = ?", jobId);
|
|
195
209
|
return row ? hydrateRow(row) : null;
|
|
196
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
|
+
}
|
|
197
215
|
findLatestJob(options) {
|
|
198
216
|
const conditions = ["repo_id = @repo_id"];
|
|
199
217
|
const params = {
|
|
@@ -243,6 +261,18 @@ export class JobStore {
|
|
|
243
261
|
updateRunningJob(jobId, patch) {
|
|
244
262
|
return this.updateWhere(jobId, patch, "status = 'running'");
|
|
245
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
|
+
}
|
|
246
276
|
/**
|
|
247
277
|
* Evidence-only backfill for pre-provenance rows. COALESCE prevents forensic
|
|
248
278
|
* inspection from overwriting a field already recorded at execution time.
|
|
@@ -276,7 +306,7 @@ export class JobStore {
|
|
|
276
306
|
return this.updateWhere(jobId, { ...patch, status: "cancelled", pid: null, kimi_pid: null }, "status = 'running'");
|
|
277
307
|
}
|
|
278
308
|
static JOBS_COLUMNS = new Set([
|
|
279
|
-
"status", "summary", "phase", "pid", "kimi_pid",
|
|
309
|
+
"status", "summary", "phase", "budget_ms", "budget_source", "kimi_session_id_source", "pid", "kimi_pid",
|
|
280
310
|
"final_output_path", "stream_log_path", "kimi_session_id",
|
|
281
311
|
"error", "command_type", "prompt_digest", "repo_id",
|
|
282
312
|
"cwd", "model", "thinking", "background", "agent_profile",
|
|
@@ -320,6 +350,9 @@ function serializeRecord(record) {
|
|
|
320
350
|
return {
|
|
321
351
|
...record,
|
|
322
352
|
phase: record.phase ?? null,
|
|
353
|
+
budget_ms: record.budget_ms ?? null,
|
|
354
|
+
budget_source: record.budget_source ?? null,
|
|
355
|
+
kimi_session_id_source: record.kimi_session_id_source ?? null,
|
|
323
356
|
operation_kind: record.operation_kind ?? null,
|
|
324
357
|
intended_engine: record.intended_engine ?? null,
|
|
325
358
|
observed_engine: record.observed_engine ?? null,
|
|
@@ -557,3 +590,7 @@ export function withJobStoreSync(paths, fn) {
|
|
|
557
590
|
store?.close();
|
|
558
591
|
}
|
|
559
592
|
}
|
|
593
|
+
/** Old non-null budgets have unknown provenance; never invent explicit intent. */
|
|
594
|
+
export function renderBudgetSource(job) {
|
|
595
|
+
return job.budget_source ?? (job.budget_ms === null ? "default" : "unknown");
|
|
596
|
+
}
|
package/dist/jobs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { RuntimeError } from "./errors.js";
|
|
3
|
-
import { renderTerminalJobArtifact, writeArtifact } from "./render.js";
|
|
3
|
+
import { formatBudgetDuration, renderTerminalJobArtifact, writeArtifact } from "./render.js";
|
|
4
4
|
export function digestPrompt(prompt) {
|
|
5
5
|
return createHash("sha256").update(prompt).digest("hex");
|
|
6
6
|
}
|
|
@@ -30,10 +30,14 @@ export function normalizeJobError(error) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
export async function markJobFailed(store, paths, job, error, summary = "Job failed.", options) {
|
|
33
|
+
if (options?.partialOutput) {
|
|
34
|
+
const label = job.command_type[0].toUpperCase() + job.command_type.slice(1);
|
|
35
|
+
summary = `${label} timed out after ${formatBudgetDuration(options.partialOutput.budgetMs)}; partial output captured (${options.partialOutput.text.trim().length} chars).`;
|
|
36
|
+
}
|
|
33
37
|
const normalized = normalizeJobError(error);
|
|
34
38
|
const phase = options?.phase;
|
|
35
39
|
const artifactJob = {
|
|
36
|
-
...job,
|
|
40
|
+
...(store.getJob(job.job_id) ?? job),
|
|
37
41
|
status: "failed",
|
|
38
42
|
summary,
|
|
39
43
|
error: normalized,
|
|
@@ -42,7 +46,7 @@ export async function markJobFailed(store, paths, job, error, summary = "Job fai
|
|
|
42
46
|
kimi_pid: null,
|
|
43
47
|
...(phase !== undefined ? { phase } : {}),
|
|
44
48
|
};
|
|
45
|
-
const artifactPath = await writeArtifact(paths, artifactJob, renderTerminalJobArtifact(artifactJob));
|
|
49
|
+
const artifactPath = await writeArtifact(paths, artifactJob, renderTerminalJobArtifact(artifactJob, options?.partialOutput));
|
|
46
50
|
return (store.markFailed(job.job_id, {
|
|
47
51
|
summary,
|
|
48
52
|
error: normalized,
|
package/dist/kimi-engine.js
CHANGED
package/dist/kimi-errors.js
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
import { RuntimeError, formatError } from "./errors.js";
|
|
2
|
+
const BUDGET_CEILING_MS = 24 * 60 * 60 * 1_000;
|
|
3
|
+
function formatBudgetMs(ms) {
|
|
4
|
+
if (ms % 3_600_000 === 0)
|
|
5
|
+
return `${ms / 3_600_000}h`;
|
|
6
|
+
if (ms % 60_000 === 0)
|
|
7
|
+
return `${ms / 60_000}m`;
|
|
8
|
+
return `${Math.ceil(ms / 1_000)}s`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Suggest a concrete next budget from the one that just expired: double it,
|
|
12
|
+
* capped at the 24h ceiling. At the ceiling there is nothing left to raise,
|
|
13
|
+
* so the only honest advice is to narrow the prompt.
|
|
14
|
+
*/
|
|
15
|
+
function budgetAdvice(budgetMs) {
|
|
16
|
+
if (budgetMs === undefined || !Number.isFinite(budgetMs) || budgetMs <= 0)
|
|
17
|
+
return "raise the budget (`--budget <dur>`, at most 24h)";
|
|
18
|
+
if (budgetMs >= BUDGET_CEILING_MS)
|
|
19
|
+
return "note the budget is already at the 24h ceiling, so narrowing the prompt is the only remaining lever";
|
|
20
|
+
return `raise the budget (e.g. \`--budget ${formatBudgetMs(Math.min(budgetMs * 2, BUDGET_CEILING_MS))}\`)`;
|
|
21
|
+
}
|
|
2
22
|
export function classifyManagedCommandFailure(error, commandType, jobId, options) {
|
|
3
23
|
const classification = classifyKimiAvailability(error);
|
|
4
24
|
if (!classification) {
|
|
5
25
|
return error instanceof Error ? error : new Error(String(error));
|
|
6
26
|
}
|
|
27
|
+
if (classification.kind === "response_timeout" && commandType !== "review_gate" && options?.background !== undefined) {
|
|
28
|
+
const foregroundAdvice = !options?.background && (commandType === "ask" || commandType === "rescue")
|
|
29
|
+
? " Or run with --background so the caller's shell timeout is not the limit."
|
|
30
|
+
: "";
|
|
31
|
+
classification.nextStep = `Narrow the prompt or ${budgetAdvice(options.budgetMs)}. Partial output, if any, is in the artifact (\`result ${jobId}\`).${foregroundAdvice}`;
|
|
32
|
+
}
|
|
7
33
|
const label = formatCommandLabel(commandType);
|
|
8
34
|
const stage = options?.preserveStage && error instanceof RuntimeError
|
|
9
35
|
? error.stage
|
|
@@ -18,6 +44,14 @@ export function classifyManagedCommandFailure(error, commandType, jobId, options
|
|
|
18
44
|
const details = { availability: classification.kind };
|
|
19
45
|
if (error instanceof RuntimeError) {
|
|
20
46
|
details.cause_code = error.code;
|
|
47
|
+
if (error.code === "RESPONSE_TIMEOUT") {
|
|
48
|
+
for (const key of ["budget_ms", "record_count", "partial_output_chars", "aborted", "settled_post_abort"]) {
|
|
49
|
+
if (error.details[key] !== undefined)
|
|
50
|
+
details[key] = error.details[key];
|
|
51
|
+
}
|
|
52
|
+
if (details.budget_ms === undefined && options?.budgetMs !== undefined)
|
|
53
|
+
details.budget_ms = options.budgetMs;
|
|
54
|
+
}
|
|
21
55
|
}
|
|
22
56
|
if (error instanceof Error && error.message.length > 0) {
|
|
23
57
|
details.cause_message = truncateCauseMessageForDetails(error.message);
|
|
@@ -170,7 +204,7 @@ function classifyKimiAvailability(error) {
|
|
|
170
204
|
return {
|
|
171
205
|
kind: "response_timeout",
|
|
172
206
|
summary: "Kimi started and accepted the prompt but never returned a final response.",
|
|
173
|
-
nextStep: "
|
|
207
|
+
nextStep: "Narrow the prompt or raise the budget (`--budget <dur>`, at most 24h). Check the job artifact (`result <jobId>`) for any captured partial output. Backgrounding does not extend the runtime budget.",
|
|
174
208
|
runtimeProbe: "ok",
|
|
175
209
|
authProbe: "ok",
|
|
176
210
|
};
|
package/dist/kimi-timeouts.js
CHANGED
|
@@ -12,23 +12,23 @@
|
|
|
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
|
|
15
|
-
// testing under default flags.
|
|
16
|
-
//
|
|
15
|
+
// testing under default flags. Defaults are now 1800/1800/2700 seconds
|
|
16
|
+
// for ask/review/rescue to allow repo-wide work; review-gate stays at
|
|
17
17
|
// 8s because it fires inside Claude Code's Stop hook and is forced to
|
|
18
18
|
// thinking-off in its own caller.
|
|
19
19
|
/** Ask budget. Conversational; thinking-on free-form reasoning. */
|
|
20
|
-
export const KIMI_ASK_PROMPT_TIMEOUT_MS =
|
|
20
|
+
export const KIMI_ASK_PROMPT_TIMEOUT_MS = 1_800_000; // 30 minutes
|
|
21
21
|
/** Review/challenge budget. Thinking-on single-turn analysis over the workspace. */
|
|
22
22
|
export const KIMI_REVIEW_PROMPT_TIMEOUT_MS = 1_800_000;
|
|
23
23
|
/** Rescue budget. Thinking-on multi-step apply/test/verify loops. */
|
|
24
|
-
export const KIMI_RESCUE_PROMPT_TIMEOUT_MS =
|
|
24
|
+
export const KIMI_RESCUE_PROMPT_TIMEOUT_MS = 2_700_000; // 45 minutes
|
|
25
25
|
/**
|
|
26
26
|
* Pursue (autonomous goal mode) DEFAULT wall-clock ceiling when the user
|
|
27
27
|
* passes no `--budget`. This is the ONLY hard bound on an autonomous goal —
|
|
28
28
|
* headless goal create sets no token/turn budget (only the model, via
|
|
29
|
-
* SetGoalBudget, or this AbortController stops the loop).
|
|
30
|
-
* single-turn rescue
|
|
31
|
-
*
|
|
29
|
+
* SetGoalBudget, or this AbortController stops the loop). The same duration as
|
|
30
|
+
* the single-turn rescue default, but a safety ceiling rather than a patience
|
|
31
|
+
* knob. Deliberately finite. Overridable per-job via `--budget` (parsed to ms in
|
|
32
32
|
* parsePursueArgs). See runtime/commands/pursue.ts and docs/safety.md.
|
|
33
33
|
*/
|
|
34
34
|
export const KIMI_PURSUE_DEFAULT_BUDGET_MS = 2_700_000; // 45 minutes
|
package/dist/parsing.js
CHANGED
|
@@ -21,8 +21,13 @@ function looksLikeFlag(token) {
|
|
|
21
21
|
// Short flag: a single dash followed by exactly one letter (`-r`, `-m`).
|
|
22
22
|
return /^-[a-zA-Z]$/.test(token);
|
|
23
23
|
}
|
|
24
|
-
const ASK_SUPPORTED_FLAGS = "-m/--model <name>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
25
|
-
const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
24
|
+
export const ASK_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
25
|
+
export const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
|
|
26
|
+
export const REVIEW_SUPPORTED_FLAGS = "--base <ref>, -m/--model <name>, --budget <30m|1h|90s>";
|
|
27
|
+
export const JOB_LOOKUP_SUPPORTED_FLAGS = "--type <review|challenge|rescue|review_gate|ask>, --json";
|
|
28
|
+
export const SETUP_SUPPORTED_FLAGS = "--check, --uninstall, --all (with --uninstall), --enable-review-gate, --disable-review-gate, --models [--json] (without other setup flags)";
|
|
29
|
+
export const REPAIR_SESSIONS_SUPPORTED_FLAGS = "--apply, --all";
|
|
30
|
+
export const REPLAY_SUPPORTED_FLAGS = "none (one job-id required)";
|
|
26
31
|
// Thinking-on is always-on for user-facing commands in v1.0. review-gate
|
|
27
32
|
// pins thinking=false via the cli-client options bag instead of an argv
|
|
28
33
|
// flag (see runtime/cli-client.ts::CliClientOptions). The parser rejects
|
|
@@ -39,8 +44,8 @@ function isRemovedThinkingFlag(token) {
|
|
|
39
44
|
token.startsWith("--thinking=") ||
|
|
40
45
|
token.startsWith("--no-thinking="));
|
|
41
46
|
}
|
|
42
|
-
const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --turns <N>";
|
|
43
|
-
const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
|
|
47
|
+
export const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --turns <N>";
|
|
48
|
+
export const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
|
|
44
49
|
/**
|
|
45
50
|
* Parser for /kimi:swarm (read-only parallel fan-out). Foreground-only:
|
|
46
51
|
* no --background/--fresh/--resume. Trailing tokens are the objective.
|
|
@@ -85,7 +90,7 @@ export function parseSwarmArgs(argv) {
|
|
|
85
90
|
if (!value || value.startsWith("-")) {
|
|
86
91
|
throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "swarm.parse");
|
|
87
92
|
}
|
|
88
|
-
budgetMs = parseDurationMs(value, "--budget");
|
|
93
|
+
budgetMs = parseDurationMs(value, "--budget", "swarm.parse", "safety");
|
|
89
94
|
index += 1;
|
|
90
95
|
break;
|
|
91
96
|
}
|
|
@@ -154,20 +159,23 @@ export const MAX_DURATION_MS = 24 * 60 * 60 * 1_000;
|
|
|
154
159
|
* into milliseconds. Throws INVALID_ARGS on a malformed, non-positive, or
|
|
155
160
|
* absurdly large value (> {@link MAX_DURATION_MS}).
|
|
156
161
|
*/
|
|
157
|
-
export function parseDurationMs(token, flag) {
|
|
162
|
+
export function parseDurationMs(token, flag, stage, budgetKind) {
|
|
163
|
+
const limitReason = budgetKind === "safety"
|
|
164
|
+
? "the budget is a safety ceiling, not a scheduler"
|
|
165
|
+
: "the budget is a response-time limit; a timed-out job is still a safe failure";
|
|
158
166
|
const match = /^(\d+)(s|m|h)?$/.exec(token.trim());
|
|
159
167
|
if (!match) {
|
|
160
|
-
throw new RuntimeError("INVALID_ARGS", `${flag} expects a duration like 30m, 1h, or 90s (bare number = minutes); got "${token}".`,
|
|
168
|
+
throw new RuntimeError("INVALID_ARGS", `${flag} expects a duration like 30m, 1h, or 90s (bare number = minutes); got "${token}".`, stage);
|
|
161
169
|
}
|
|
162
170
|
const value = Number(match[1]);
|
|
163
171
|
const unit = match[2] ?? "m";
|
|
164
172
|
if (!Number.isFinite(value) || value <= 0) {
|
|
165
|
-
throw new RuntimeError("INVALID_ARGS", `${flag} must be a positive duration.`,
|
|
173
|
+
throw new RuntimeError("INVALID_ARGS", `${flag} must be a positive duration.`, stage);
|
|
166
174
|
}
|
|
167
175
|
const unitMs = unit === "s" ? 1_000 : unit === "h" ? 3_600_000 : 60_000;
|
|
168
176
|
const ms = value * unitMs;
|
|
169
177
|
if (ms > MAX_DURATION_MS) {
|
|
170
|
-
throw new RuntimeError("INVALID_ARGS", `${flag} must be at most 24h (got "${token}");
|
|
178
|
+
throw new RuntimeError("INVALID_ARGS", `${flag} must be at most 24h (got "${token}"); ${limitReason}.`, stage);
|
|
171
179
|
}
|
|
172
180
|
return ms;
|
|
173
181
|
}
|
|
@@ -211,7 +219,7 @@ export function parsePursueArgs(argv) {
|
|
|
211
219
|
if (!value || value.startsWith("-")) {
|
|
212
220
|
throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "pursue.parse");
|
|
213
221
|
}
|
|
214
|
-
budgetMs = parseDurationMs(value, "--budget");
|
|
222
|
+
budgetMs = parseDurationMs(value, "--budget", "pursue.parse", "safety");
|
|
215
223
|
index += 1;
|
|
216
224
|
break;
|
|
217
225
|
}
|
|
@@ -248,7 +256,38 @@ export function parsePursueArgs(argv) {
|
|
|
248
256
|
objective: trailingTokens?.join(" "),
|
|
249
257
|
};
|
|
250
258
|
}
|
|
259
|
+
/** Help before -- wins even over malformed flags or prose trailing-token mode. */
|
|
260
|
+
const VALUE_TAKING_FLAGS = new Set(["-m", "--model", "--budget", "--resume", "--base", "--cap", "--max-concurrency", "--turns", "--type"]);
|
|
261
|
+
/**
|
|
262
|
+
* `--help` / `-h` counts only inside the leading flag region: before `--`
|
|
263
|
+
* and before the first prose token. The value of a value-taking flag is
|
|
264
|
+
* skipped, so `-m kimi -h` is still a help request (`kimi` is a value, not
|
|
265
|
+
* prose) and `--budget --help` asks for help rather than failing on a bad
|
|
266
|
+
* duration. Once prose starts (`ask what does du -h print`), nothing after it
|
|
267
|
+
* is a flag — the whole remainder is the prompt.
|
|
268
|
+
*/
|
|
269
|
+
export function hasHelpFlag(argv) {
|
|
270
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
271
|
+
const token = argv[index];
|
|
272
|
+
if (token === "--")
|
|
273
|
+
return false;
|
|
274
|
+
if (token === "--help" || token === "-h")
|
|
275
|
+
return true;
|
|
276
|
+
if (!token.startsWith("-"))
|
|
277
|
+
return false;
|
|
278
|
+
if (VALUE_TAKING_FLAGS.has(token)) {
|
|
279
|
+
const value = argv[index + 1];
|
|
280
|
+
if (value === "--help" || value === "-h")
|
|
281
|
+
return true;
|
|
282
|
+
index += 1;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
251
287
|
export function parseAskArgs(argv) {
|
|
288
|
+
if (hasHelpFlag(argv))
|
|
289
|
+
return { help: true, background: false, wait: false, fresh: false, resume: false };
|
|
290
|
+
let budgetMs;
|
|
252
291
|
let model;
|
|
253
292
|
// Thinking is on by default across ask/review/challenge/rescue. The
|
|
254
293
|
// parser rejects --thinking / --no-thinking; review-gate's runtime
|
|
@@ -272,6 +311,15 @@ export function parseAskArgs(argv) {
|
|
|
272
311
|
break;
|
|
273
312
|
}
|
|
274
313
|
switch (token) {
|
|
314
|
+
case "--budget": {
|
|
315
|
+
const value = argv[index + 1];
|
|
316
|
+
if (!value || value.startsWith("-")) {
|
|
317
|
+
throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "ask.parse");
|
|
318
|
+
}
|
|
319
|
+
budgetMs = parseDurationMs(value, "--budget", "ask.parse", "response");
|
|
320
|
+
index += 1;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
275
323
|
case "-m":
|
|
276
324
|
case "--model": {
|
|
277
325
|
const value = argv[index + 1];
|
|
@@ -327,6 +375,7 @@ export function parseAskArgs(argv) {
|
|
|
327
375
|
throw new RuntimeError("INVALID_ARGS", "--resume only accepts a job-id or session-id. Use -r to resume the latest ask session with a prompt.", "ask.parse");
|
|
328
376
|
}
|
|
329
377
|
return {
|
|
378
|
+
budgetMs,
|
|
330
379
|
background,
|
|
331
380
|
wait,
|
|
332
381
|
fresh,
|
|
@@ -338,8 +387,9 @@ export function parseAskArgs(argv) {
|
|
|
338
387
|
};
|
|
339
388
|
}
|
|
340
389
|
export function parseReviewArgs(argv, commandName = "review") {
|
|
341
|
-
const parsed = parseKnownFlags(argv, new Set(["--base", "--background", "--wait", "-m", "--model"]), commandName);
|
|
390
|
+
const parsed = parseKnownFlags(argv, new Set(["--budget", "--base", "--background", "--wait", "-m", "--model"]), commandName);
|
|
342
391
|
return {
|
|
392
|
+
budgetMs: parsed.budgetMs,
|
|
343
393
|
base: parsed.base,
|
|
344
394
|
background: parsed.background,
|
|
345
395
|
wait: parsed.wait,
|
|
@@ -349,6 +399,9 @@ export function parseReviewArgs(argv, commandName = "review") {
|
|
|
349
399
|
};
|
|
350
400
|
}
|
|
351
401
|
export function parseRescueArgs(argv) {
|
|
402
|
+
if (hasHelpFlag(argv))
|
|
403
|
+
return { help: true, background: false, wait: false, fresh: false, resume: false };
|
|
404
|
+
let budgetMs;
|
|
352
405
|
let model;
|
|
353
406
|
let thinking = true;
|
|
354
407
|
let background = false;
|
|
@@ -369,6 +422,15 @@ export function parseRescueArgs(argv) {
|
|
|
369
422
|
break;
|
|
370
423
|
}
|
|
371
424
|
switch (token) {
|
|
425
|
+
case "--budget": {
|
|
426
|
+
const value = argv[index + 1];
|
|
427
|
+
if (!value || value.startsWith("-")) {
|
|
428
|
+
throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "rescue.parse");
|
|
429
|
+
}
|
|
430
|
+
budgetMs = parseDurationMs(value, "--budget", "rescue.parse", "response");
|
|
431
|
+
index += 1;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
372
434
|
case "-m":
|
|
373
435
|
case "--model": {
|
|
374
436
|
const value = argv[index + 1];
|
|
@@ -424,6 +486,7 @@ export function parseRescueArgs(argv) {
|
|
|
424
486
|
throw new RuntimeError("INVALID_ARGS", "--resume only accepts a job-id or session-id. Use -r to resume the latest rescue session with a prompt.", "rescue.parse");
|
|
425
487
|
}
|
|
426
488
|
return {
|
|
489
|
+
budgetMs,
|
|
427
490
|
background,
|
|
428
491
|
wait,
|
|
429
492
|
fresh,
|
|
@@ -454,7 +517,7 @@ export function parseJobLookupArgs(argv) {
|
|
|
454
517
|
continue;
|
|
455
518
|
}
|
|
456
519
|
if (token.startsWith("-")) {
|
|
457
|
-
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token}. Supported flags:
|
|
520
|
+
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token}. Supported flags: ${JOB_LOOKUP_SUPPORTED_FLAGS}.`, "args.parse");
|
|
458
521
|
}
|
|
459
522
|
if (jobId) {
|
|
460
523
|
throw new RuntimeError("INVALID_ARGS", "Only one optional job id may be supplied.", "args.parse");
|
|
@@ -475,6 +538,7 @@ function isManagedCommandType(value) {
|
|
|
475
538
|
value === "ask");
|
|
476
539
|
}
|
|
477
540
|
function parseKnownFlags(argv, knownFlags, commandName) {
|
|
541
|
+
let budgetMs;
|
|
478
542
|
let model;
|
|
479
543
|
let thinking = true;
|
|
480
544
|
let base;
|
|
@@ -497,12 +561,21 @@ function parseKnownFlags(argv, knownFlags, commandName) {
|
|
|
497
561
|
// slurping them into focus text produced a bloated prompt that
|
|
498
562
|
// looked like a hang. Hard-fail with the supported list inline so
|
|
499
563
|
// the error message itself is the correction.
|
|
500
|
-
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ${commandName}. Supported flags:
|
|
564
|
+
throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ${commandName}. Supported flags: ${REVIEW_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped focus text to pass it through.`, "args.parse");
|
|
501
565
|
}
|
|
502
566
|
trailingTokens = argv.slice(index);
|
|
503
567
|
break;
|
|
504
568
|
}
|
|
505
569
|
switch (token) {
|
|
570
|
+
case "--budget": {
|
|
571
|
+
const value = argv[index + 1];
|
|
572
|
+
if (!value || value.startsWith("-")) {
|
|
573
|
+
throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", `${commandName}.parse`);
|
|
574
|
+
}
|
|
575
|
+
budgetMs = parseDurationMs(value, "--budget", `${commandName}.parse`, "response");
|
|
576
|
+
index += 1;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
506
579
|
case "-m":
|
|
507
580
|
case "--model": {
|
|
508
581
|
const value = argv[index + 1];
|
|
@@ -543,6 +616,7 @@ function parseKnownFlags(argv, knownFlags, commandName) {
|
|
|
543
616
|
}
|
|
544
617
|
}
|
|
545
618
|
return {
|
|
619
|
+
budgetMs,
|
|
546
620
|
base,
|
|
547
621
|
background,
|
|
548
622
|
wait,
|
package/dist/render.js
CHANGED
|
@@ -126,7 +126,16 @@ export function renderReviewGateArtifact(job, output) {
|
|
|
126
126
|
}
|
|
127
127
|
return `${lines.join("\n")}\n`;
|
|
128
128
|
}
|
|
129
|
-
export function
|
|
129
|
+
export function formatBudgetDuration(ms) {
|
|
130
|
+
if (ms % 3_600_000 === 0)
|
|
131
|
+
return `${ms / 3_600_000}h`;
|
|
132
|
+
if (ms % 60_000 === 0)
|
|
133
|
+
return `${ms / 60_000}m`;
|
|
134
|
+
if (ms % 1_000 === 0)
|
|
135
|
+
return `${ms / 1_000}s`;
|
|
136
|
+
return `${ms}ms`;
|
|
137
|
+
}
|
|
138
|
+
export function renderTerminalJobArtifact(job, partialOutput) {
|
|
130
139
|
const lines = [
|
|
131
140
|
`# ${capitalize(job.status)} Job`,
|
|
132
141
|
"",
|
|
@@ -149,6 +158,15 @@ export function renderTerminalJobArtifact(job) {
|
|
|
149
158
|
}
|
|
150
159
|
}
|
|
151
160
|
}
|
|
161
|
+
if (partialOutput) {
|
|
162
|
+
lines.push("", `## Partial output (budget exhausted after ${formatBudgetDuration(partialOutput.budgetMs)} — NOT a final answer)`, "", partialOutput.text.trim()
|
|
163
|
+
? partialOutput.text
|
|
164
|
+
: partialOutput.recordCount === 0
|
|
165
|
+
? "No stream records arrived before the budget expired (the model had not produced any output yet)."
|
|
166
|
+
: partialOutput.toolCalls
|
|
167
|
+
? "No assistant prose was emitted before the budget (tool calls only)."
|
|
168
|
+
: "No assistant prose was emitted before the budget.");
|
|
169
|
+
}
|
|
152
170
|
return lines.join("\n");
|
|
153
171
|
}
|
|
154
172
|
function capitalize(value) {
|