kimi-plugin-cc 2.0.7 → 2.0.8
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 +29 -0
- package/README.md +21 -5
- 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 +29 -12
- package/dist/commands/ask.js +9 -4
- package/dist/commands/cli-helpers.js +31 -0
- package/dist/commands/pursue.js +2 -1
- package/dist/commands/repair-sessions.js +2 -1
- package/dist/commands/replay.js +3 -2
- package/dist/commands/rescue.js +10 -3
- package/dist/commands/result.js +4 -0
- package/dist/commands/review.js +6 -4
- package/dist/commands/setup.js +2 -1
- package/dist/commands/swarm.js +4 -2
- package/dist/companion.js +6 -0
- package/dist/help.js +40 -0
- package/dist/hooks/config-safety.js +3 -2
- package/dist/job-store.js +8 -3
- 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/version.js +1 -1
- package/docs/migration.md +3 -3
- package/docs/registry-releases.md +14 -1
- package/package.json +2 -2
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/cli-client.js +29 -12
- package/plugins/kimi-codex/dist/commands/ask.js +9 -4
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +31 -0
- package/plugins/kimi-codex/dist/commands/pursue.js +2 -1
- 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 +10 -3
- package/plugins/kimi-codex/dist/commands/result.js +4 -0
- package/plugins/kimi-codex/dist/commands/review.js +6 -4
- package/plugins/kimi-codex/dist/commands/setup.js +2 -1
- package/plugins/kimi-codex/dist/commands/swarm.js +4 -2
- 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 +3 -2
- package/plugins/kimi-codex/dist/job-store.js +8 -3
- 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/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
|
@@ -4,6 +4,19 @@ The npm package `kimi-plugin-cc` ships the Claude plugin at its root and the
|
|
|
4
4
|
self-contained Codex plugin under `plugins/kimi-codex`. GitHub marketplaces
|
|
5
5
|
remain available. The package has no install scripts and no separate CLI.
|
|
6
6
|
|
|
7
|
+
## Publishing status
|
|
8
|
+
|
|
9
|
+
As of September 26, 2026, [2.0.7 is live on npm](https://www.npmjs.com/package/kimi-plugin-cc/v/2.0.7).
|
|
10
|
+
The first upload used the validated GitHub Actions artifact and interactive npm
|
|
11
|
+
authentication. Its public tarball matched that artifact byte-for-byte, and a
|
|
12
|
+
fresh registry install passed isolated setup/check for both hosts.
|
|
13
|
+
|
|
14
|
+
The trusted publisher is configured for `linxule/kimi-plugin-cc`, workflow
|
|
15
|
+
`publish.yml`, environment `npm`, with direct publishing allowed.
|
|
16
|
+
`NPM_TRUSTED_PUBLISHING=true` is enabled in GitHub Actions. Future GitHub releases
|
|
17
|
+
use short-lived OIDC credentials, without a stored npm token or interactive 2FA.
|
|
18
|
+
The first npm OIDC publication will be the next release; do not republish 2.0.7.
|
|
19
|
+
|
|
7
20
|
## Validation
|
|
8
21
|
|
|
9
22
|
Use the repository's normal version bump and generated-surface procedure in
|
|
@@ -25,7 +38,7 @@ vendored licenses, and installs/checks the safety hook in isolated homes for
|
|
|
25
38
|
both hosts. It also exercises unsupported-version refusal using a fake CLI.
|
|
26
39
|
It does not invoke a model or read the maintainer's Kimi configuration.
|
|
27
40
|
|
|
28
|
-
## First publication
|
|
41
|
+
## First publication (completed bootstrap reference)
|
|
29
42
|
|
|
30
43
|
The package must first exist on npm before its package settings can be configured.
|
|
31
44
|
After the release commit passes CI, create its immutable version tag and GitHub
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi-plugin-cc",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Kimi for Claude Code and Codex: code questions, reviews, fixes, and parallel tasks through the local Kimi Code CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/bun": "^1.4.2",
|
|
21
21
|
"@types/node": "^26.5.1",
|
|
22
|
-
"smol-toml": "1.
|
|
22
|
+
"smol-toml": "1.9.1",
|
|
23
23
|
"tsx": "^4.23.13",
|
|
24
24
|
"typescript": "^7.0.2"
|
|
25
25
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimi",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Codex plugin that delegates review, challenge, ask, rescue, pursue, and swarm workflows to the local kimi-code CLI through the kimi-plugin-cc companion runtime.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "linxule"
|
|
@@ -656,6 +656,25 @@ export function requireSessionId(result, context) {
|
|
|
656
656
|
},
|
|
657
657
|
});
|
|
658
658
|
}
|
|
659
|
+
/** Settled timeout data; stream records are deliberately non-serializable. */
|
|
660
|
+
export class ResponseTimeoutError extends RuntimeError {
|
|
661
|
+
constructor(budgetMs, stage, commandLabel, partialResult, cause) {
|
|
662
|
+
super("RESPONSE_TIMEOUT", `${stage} timed out after ${budgetMs}ms.`, stage, {
|
|
663
|
+
...(cause instanceof Error ? { cause } : {}),
|
|
664
|
+
details: {
|
|
665
|
+
budget_ms: budgetMs,
|
|
666
|
+
command_label: commandLabel ?? null,
|
|
667
|
+
record_count: partialResult?.records.length ?? 0,
|
|
668
|
+
partial_output_chars: partialResult?.records.reduce((count, record) => count + (record.role === "assistant" && typeof record.content === "string" ? record.content.length : 0), 0) ?? 0,
|
|
669
|
+
aborted: true,
|
|
670
|
+
...(partialResult ? { settled_post_abort: true } : {}),
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
this.name = "ResponseTimeoutError";
|
|
674
|
+
// Never serialize stream records into job error details or error envelopes.
|
|
675
|
+
Object.defineProperty(this, "partialResult", { value: partialResult, enumerable: false });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
659
678
|
/**
|
|
660
679
|
* Run `kimi -p` under a budget that also tells the subprocess to die
|
|
661
680
|
* when the budget expires.
|
|
@@ -682,7 +701,8 @@ export function requireSessionId(result, context) {
|
|
|
682
701
|
* subprocess/tree teardown, then reject with
|
|
683
702
|
* `RuntimeError("RESPONSE_TIMEOUT", …)` matching the legacy
|
|
684
703
|
* `withTimeout` code shape.
|
|
685
|
-
* -
|
|
704
|
+
* - A clean non-aborted exit-0 result wins same-tick budget expiry.
|
|
705
|
+
* Otherwise a settled result on timeout is carried as partial output.
|
|
686
706
|
*/
|
|
687
707
|
export async function runCliPromptWithBudget(opts, budgetMs, stage) {
|
|
688
708
|
const controller = new AbortController();
|
|
@@ -698,12 +718,7 @@ export async function runCliPromptWithBudget(opts, budgetMs, stage) {
|
|
|
698
718
|
controller.abort();
|
|
699
719
|
}, budgetMs);
|
|
700
720
|
timer.unref();
|
|
701
|
-
const timeoutError = (
|
|
702
|
-
details: {
|
|
703
|
-
budget_ms: budgetMs,
|
|
704
|
-
command_label: opts.commandLabel ?? null,
|
|
705
|
-
},
|
|
706
|
-
});
|
|
721
|
+
const timeoutError = (partialResult, cause) => new ResponseTimeoutError(budgetMs, stage, opts.commandLabel, partialResult, cause);
|
|
707
722
|
try {
|
|
708
723
|
let result;
|
|
709
724
|
try {
|
|
@@ -714,13 +729,13 @@ export async function runCliPromptWithBudget(opts, budgetMs, stage) {
|
|
|
714
729
|
}
|
|
715
730
|
catch (error) {
|
|
716
731
|
if (timeoutFired)
|
|
717
|
-
throw timeoutError();
|
|
732
|
+
throw timeoutError(undefined, error);
|
|
718
733
|
throw error;
|
|
719
734
|
}
|
|
720
|
-
if (timeoutFired) {
|
|
721
|
-
//
|
|
722
|
-
//
|
|
723
|
-
throw timeoutError(
|
|
735
|
+
if (timeoutFired && (result.aborted || result.exitCode !== 0)) {
|
|
736
|
+
// A clean, non-aborted result won the settle/timeout boundary and is success.
|
|
737
|
+
// Budget aborts normally resolve with records, after owned teardown settles.
|
|
738
|
+
throw timeoutError(result);
|
|
724
739
|
}
|
|
725
740
|
return result;
|
|
726
741
|
}
|
|
@@ -770,6 +785,8 @@ function buildArgs(opts) {
|
|
|
770
785
|
}
|
|
771
786
|
function buildEnv(opts) {
|
|
772
787
|
const env = { ...opts.env };
|
|
788
|
+
// 2.2.0 audit: ambient trust starts project MCP stdio servers outside PreToolUse.
|
|
789
|
+
delete env.KIMI_CODE_TRUST_WORKSPACE;
|
|
773
790
|
// kimi-code 0.33.0 inverted its CLI engine default: an unflagged `kimi -p`
|
|
774
791
|
// now selects native agent-core-v2, while this upstream-documented flag pins
|
|
775
792
|
// the legacy-v1 path. Native v2 still has a final-allow plan-file path that
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { companionHelp } from "../help.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createCliCancellationHandlers } from "../cli-cancellation.js";
|
|
@@ -16,7 +17,7 @@ import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.j
|
|
|
16
17
|
import { classifyManagedCommandFailure } from "../kimi-errors.js";
|
|
17
18
|
import { startBackgroundJob } from "../background-spawn.js";
|
|
18
19
|
import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
|
|
19
|
-
import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
|
+
import { captureTimeoutPartialOutput, assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
21
|
import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
|
|
21
22
|
// v1.0 cutover note (PR 2):
|
|
22
23
|
//
|
|
@@ -46,6 +47,8 @@ const ASK_PROMPT_PREFIX = [
|
|
|
46
47
|
].join("\n");
|
|
47
48
|
export async function runAsk(argv, context) {
|
|
48
49
|
const parsed = parseAskArgs(argv);
|
|
50
|
+
if (parsed.help)
|
|
51
|
+
return companionHelp(["ask", ...argv]);
|
|
49
52
|
if (context.env.KIMI_PLUGIN_CC_SKIP_HOOK_CHECK !== "1") {
|
|
50
53
|
await requireAskHookInstalled(context);
|
|
51
54
|
}
|
|
@@ -76,6 +79,7 @@ export async function runAsk(argv, context) {
|
|
|
76
79
|
model: parsed.model ?? null,
|
|
77
80
|
thinking: parsed.thinking ?? null,
|
|
78
81
|
background: parsed.background,
|
|
82
|
+
budget_ms: parsed.budgetMs ?? (sessionResolution.sourceJob?.operation_kind === "ask" ? sessionResolution.sourceJob.budget_ms : null) ?? null,
|
|
79
83
|
pid: parsed.background ? null : process.pid,
|
|
80
84
|
kimi_pid: null,
|
|
81
85
|
status: "running",
|
|
@@ -169,7 +173,7 @@ export async function executeAskJob(jobId, prompt, context, options) {
|
|
|
169
173
|
resumeSessionId: job.kimi_session_id ?? undefined,
|
|
170
174
|
logPath: job.stream_log_path,
|
|
171
175
|
signal: handlers.signal,
|
|
172
|
-
}, KIMI_ASK_PROMPT_TIMEOUT_MS, "ask.prompt");
|
|
176
|
+
}, job.budget_ms ?? KIMI_ASK_PROMPT_TIMEOUT_MS, "ask.prompt");
|
|
173
177
|
store.updateRunningJob(job.job_id, observedExecutionFields(result));
|
|
174
178
|
if (handlers.cancelling) {
|
|
175
179
|
throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, "ask.runtime");
|
|
@@ -227,8 +231,9 @@ export async function executeAskJob(jobId, prompt, context, options) {
|
|
|
227
231
|
await markJobFailed(store, paths, job, error, cancel.failedSummary, { phase: "failed" });
|
|
228
232
|
throw error;
|
|
229
233
|
}
|
|
230
|
-
const classified = classifyManagedCommandFailure(error, "ask", job.job_id);
|
|
231
|
-
|
|
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
|
+
await markJobFailed(store, paths, job, classified, cancel.failedSummary, { phase: "failed", partialOutput });
|
|
232
237
|
throw classified;
|
|
233
238
|
}
|
|
234
239
|
finally {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RuntimeError } from "../errors.js";
|
|
2
|
+
import { ResponseTimeoutError } from "../cli-client.js";
|
|
3
|
+
import { observedExecutionFields } from "../kimi-engine.js";
|
|
2
4
|
/**
|
|
3
5
|
* Reassemble prose final text from cli-client records.
|
|
4
6
|
*
|
|
@@ -110,3 +112,32 @@ export function assertCliResultSuccess(result, stage) {
|
|
|
110
112
|
});
|
|
111
113
|
}
|
|
112
114
|
}
|
|
115
|
+
/** Timeout-only capture, called after cancellation has taken precedence. No title sync on failure. */
|
|
116
|
+
export function captureTimeoutPartialOutput(classified, store, job) {
|
|
117
|
+
const timeout = classified.cause;
|
|
118
|
+
if (!(timeout instanceof ResponseTimeoutError) || !timeout.partialResult)
|
|
119
|
+
return undefined;
|
|
120
|
+
const result = timeout.partialResult;
|
|
121
|
+
// Best-effort metadata only. The row is still `running`, and a session id
|
|
122
|
+
// written here can collide with `jobs_running_session_idx` when a resume of
|
|
123
|
+
// the same session was started while this job ran. A store error must never
|
|
124
|
+
// prevent markJobFailed from running — the partial artifact matters more
|
|
125
|
+
// than the metadata, so swallow and fall through.
|
|
126
|
+
try {
|
|
127
|
+
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 });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Diagnostics stay in the stream log; the failure transition proceeds.
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
text: reassembleProseFromRecords(result.records),
|
|
137
|
+
toolCalls: result.records.some((record) => record.role === "tool" ||
|
|
138
|
+
(record.role === "assistant" && Array.isArray(record.tool_calls) &&
|
|
139
|
+
record.tool_calls.length > 0)),
|
|
140
|
+
recordCount: result.records.length,
|
|
141
|
+
budgetMs: timeout.details.budget_ms,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -143,6 +143,7 @@ export async function runPursue(argv, context) {
|
|
|
143
143
|
model: parsed.model ?? null,
|
|
144
144
|
thinking: parsed.thinking ?? null,
|
|
145
145
|
background: false,
|
|
146
|
+
budget_ms: parsed.budgetMs ?? KIMI_PURSUE_DEFAULT_BUDGET_MS,
|
|
146
147
|
pid: process.pid,
|
|
147
148
|
kimi_pid: null,
|
|
148
149
|
status: "running",
|
|
@@ -168,7 +169,7 @@ export async function runPursue(argv, context) {
|
|
|
168
169
|
await markJobFailed(store, paths, job, classified, "Pursue failed.", { phase: "failed" });
|
|
169
170
|
throw classified;
|
|
170
171
|
}
|
|
171
|
-
const completed = await executePursueJob(job.job_id, prompt, objective,
|
|
172
|
+
const completed = await executePursueJob(job.job_id, prompt, objective, job.budget_ms, context);
|
|
172
173
|
if (!completed.final_output_path) {
|
|
173
174
|
throw new RuntimeError("PURSUE_RESULT_MISSING", "Pursue finished without a rendered result.", "pursue.result");
|
|
174
175
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { REPAIR_SESSIONS_SUPPORTED_FLAGS } from "../parsing.js";
|
|
1
2
|
import { constants } from "node:fs";
|
|
2
3
|
import { lstat, open } from "node:fs/promises";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
@@ -15,7 +16,7 @@ export async function runRepairSessions(argv, context) {
|
|
|
15
16
|
const seen = new Set();
|
|
16
17
|
for (const arg of argv) {
|
|
17
18
|
if ((arg !== "--apply" && arg !== "--all") || seen.has(arg)) {
|
|
18
|
-
throw new RuntimeError("INVALID_ARGS",
|
|
19
|
+
throw new RuntimeError("INVALID_ARGS", `Usage: repair-sessions. Supported flags: ${REPAIR_SESSIONS_SUPPORTED_FLAGS}.`, "repair-sessions.args");
|
|
19
20
|
}
|
|
20
21
|
seen.add(arg);
|
|
21
22
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { REPLAY_SUPPORTED_FLAGS } from "../parsing.js";
|
|
1
2
|
import { access, stat, readFile } from "node:fs/promises";
|
|
2
3
|
import { constants as fsConstants } from "node:fs";
|
|
3
4
|
import { RuntimeError } from "../errors.js";
|
|
@@ -9,8 +10,8 @@ import { sweepStaleJobs } from "../jobs.js";
|
|
|
9
10
|
const MAX_REPLAY_LOG_BYTES = 32 * 1024 * 1024;
|
|
10
11
|
export async function runReplay(argv, context) {
|
|
11
12
|
const [jobId, ...rest] = argv;
|
|
12
|
-
if (!jobId || rest.length > 0) {
|
|
13
|
-
throw new RuntimeError("INVALID_ARGS",
|
|
13
|
+
if (!jobId || jobId.startsWith("-") || rest.length > 0) {
|
|
14
|
+
throw new RuntimeError("INVALID_ARGS", `replay expects exactly one job id: replay <job-id>. Supported flags: ${REPLAY_SUPPORTED_FLAGS}.`, "replay.parse");
|
|
14
15
|
}
|
|
15
16
|
const paths = resolvePluginPaths(context.env);
|
|
16
17
|
await ensurePluginPaths(paths);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { companionHelp } from "../help.js";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createCliCancellationHandlers } from "../cli-cancellation.js";
|
|
@@ -16,7 +17,7 @@ import { parseRescueArgs } from "../parsing.js";
|
|
|
16
17
|
import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.js";
|
|
17
18
|
import { startBackgroundJob } from "../background-spawn.js";
|
|
18
19
|
import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
|
|
19
|
-
import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
|
+
import { captureTimeoutPartialOutput, assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
21
|
import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
|
|
21
22
|
export { describeMissingResult } from "../background-spawn.js";
|
|
22
23
|
// v1.0 cutover note (PR 3):
|
|
@@ -41,6 +42,8 @@ const RESCUE_SUMMARY_MAX = 120;
|
|
|
41
42
|
const RESCUE_AGENT_PROFILE_PLACEHOLDER = "<cli-client>";
|
|
42
43
|
export async function runRescue(argv, context) {
|
|
43
44
|
const parsed = parseRescueArgs(argv);
|
|
45
|
+
if (parsed.help)
|
|
46
|
+
return companionHelp(["task", "rescue", ...argv]);
|
|
44
47
|
const paths = resolvePluginPaths(context.env);
|
|
45
48
|
await ensurePluginPaths(paths);
|
|
46
49
|
const repoIdentity = await resolveRepoIdentity(context.cwd);
|
|
@@ -68,6 +71,7 @@ export async function runRescue(argv, context) {
|
|
|
68
71
|
model: parsed.model ?? null,
|
|
69
72
|
thinking: parsed.thinking ?? null,
|
|
70
73
|
background: parsed.background,
|
|
74
|
+
budget_ms: parsed.budgetMs ?? (sessionResolution.sourceJob?.operation_kind === "rescue" ? sessionResolution.sourceJob.budget_ms : null) ?? null,
|
|
71
75
|
pid: parsed.background ? null : process.pid,
|
|
72
76
|
kimi_pid: null,
|
|
73
77
|
status: "running",
|
|
@@ -185,7 +189,7 @@ export async function executeRescueJob(jobId, prompt, context, options) {
|
|
|
185
189
|
signal: handlers.signal,
|
|
186
190
|
// SIGKILL escalation defaults to 1500ms inside cli-client —
|
|
187
191
|
// matches v0.4's cancellation.ts behavior for the wire client.
|
|
188
|
-
}, KIMI_RESCUE_PROMPT_TIMEOUT_MS, "rescue.prompt");
|
|
192
|
+
}, job.budget_ms ?? KIMI_RESCUE_PROMPT_TIMEOUT_MS, "rescue.prompt");
|
|
189
193
|
store.updateRunningJob(job.job_id, observedExecutionFields(result));
|
|
190
194
|
if (handlers.cancelling) {
|
|
191
195
|
throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, "rescue.runtime");
|
|
@@ -248,8 +252,11 @@ export async function executeRescueJob(jobId, prompt, context, options) {
|
|
|
248
252
|
}
|
|
249
253
|
const classified = classifyManagedCommandFailure(error, "rescue", job.job_id, {
|
|
250
254
|
preserveStage: true,
|
|
255
|
+
background: job.background,
|
|
256
|
+
budgetMs: job.budget_ms ?? KIMI_RESCUE_PROMPT_TIMEOUT_MS,
|
|
251
257
|
});
|
|
252
|
-
|
|
258
|
+
const partialOutput = captureTimeoutPartialOutput(classified, store, job);
|
|
259
|
+
return await markJobFailed(store, paths, job, classified, cancel.failedSummary, { phase: "failed", partialOutput });
|
|
253
260
|
}
|
|
254
261
|
finally {
|
|
255
262
|
handlers.dispose();
|
|
@@ -42,6 +42,10 @@ function renderResultEnvelope(job, artifactPath, body) {
|
|
|
42
42
|
job_id: job.job_id,
|
|
43
43
|
kind: job.command_type,
|
|
44
44
|
status: job.status,
|
|
45
|
+
budget_ms: job.budget_ms,
|
|
46
|
+
kimi_session_id: job.kimi_session_id,
|
|
47
|
+
// True when a budget timeout preserved assistant prose in the artifact.
|
|
48
|
+
partial_output: job.status === "failed" && Number(job.error?.details?.partial_output_chars ?? 0) > 0,
|
|
45
49
|
summary: job.summary,
|
|
46
50
|
error: job.error,
|
|
47
51
|
artifact_path: artifactPath,
|
|
@@ -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 { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
20
|
+
import { captureTimeoutPartialOutput, assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
|
|
21
21
|
import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
|
|
22
22
|
// v1.0 cutover note (PR 2):
|
|
23
23
|
//
|
|
@@ -81,6 +81,7 @@ export async function runReview(argv, context, commandType) {
|
|
|
81
81
|
model: parsed.model ?? null,
|
|
82
82
|
thinking: parsed.thinking ?? null,
|
|
83
83
|
background: false,
|
|
84
|
+
budget_ms: parsed.budgetMs ?? null,
|
|
84
85
|
pid: process.pid,
|
|
85
86
|
kimi_pid: null,
|
|
86
87
|
status: "running",
|
|
@@ -113,7 +114,7 @@ export async function runReview(argv, context, commandType) {
|
|
|
113
114
|
model: parsed.model,
|
|
114
115
|
logPath,
|
|
115
116
|
signal: handlers.signal,
|
|
116
|
-
}, KIMI_REVIEW_PROMPT_TIMEOUT_MS, `${commandType}.prompt`);
|
|
117
|
+
}, job.budget_ms ?? KIMI_REVIEW_PROMPT_TIMEOUT_MS, `${commandType}.prompt`);
|
|
117
118
|
store.updateRunningJob(job.job_id, observedExecutionFields(result));
|
|
118
119
|
if (handlers.cancelling) {
|
|
119
120
|
throw new RuntimeError(cancel.errorCodes.cancelled, cancel.cancelMessages.afterPrompt, `${commandType}.runtime`);
|
|
@@ -163,8 +164,9 @@ export async function runReview(argv, context, commandType) {
|
|
|
163
164
|
await markJobCancelled(store, paths, job, cancel.cancelledSummary, cancelledError);
|
|
164
165
|
throw cancelledError;
|
|
165
166
|
}
|
|
166
|
-
const classified = classifyManagedCommandFailure(error, commandType, job.job_id);
|
|
167
|
-
|
|
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
|
+
await markJobFailed(store, paths, job, classified, cancel.failedSummary, { partialOutput });
|
|
168
170
|
throw classified;
|
|
169
171
|
}
|
|
170
172
|
finally {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SETUP_SUPPORTED_FLAGS } from "../parsing.js";
|
|
1
2
|
// Managed-block installer for the kimi-code PreToolUse hook.
|
|
2
3
|
//
|
|
3
4
|
// Replaces the v0.4 Wire-based setup probe. In v1.0 the load-bearing
|
|
@@ -127,7 +128,7 @@ function parseArgs(argv) {
|
|
|
127
128
|
disableReviewGate = true;
|
|
128
129
|
break;
|
|
129
130
|
default:
|
|
130
|
-
throw new RuntimeError("INVALID_ARGS", `Unknown setup flag ${token}. Supported flags:
|
|
131
|
+
throw new RuntimeError("INVALID_ARGS", `Unknown setup flag ${token}. Supported flags: ${SETUP_SUPPORTED_FLAGS}.`, "setup.parse");
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
if (enableReviewGate && disableReviewGate) {
|
|
@@ -210,6 +210,7 @@ async function runReadSwarm(parsed, objective, context) {
|
|
|
210
210
|
model: parsed.model ?? null,
|
|
211
211
|
thinking: parsed.thinking ?? null,
|
|
212
212
|
background: false,
|
|
213
|
+
budget_ms: parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS,
|
|
213
214
|
pid: process.pid,
|
|
214
215
|
kimi_pid: null,
|
|
215
216
|
status: "running",
|
|
@@ -235,7 +236,7 @@ async function runReadSwarm(parsed, objective, context) {
|
|
|
235
236
|
await markJobFailed(store, paths, job, classified, "Swarm failed.", { phase: "failed" });
|
|
236
237
|
throw classified;
|
|
237
238
|
}
|
|
238
|
-
const completed = await executeSwarmJob(job.job_id, prompt, objective,
|
|
239
|
+
const completed = await executeSwarmJob(job.job_id, prompt, objective, job.budget_ms, resolveSwarmMaxConcurrency(parsed.maxConcurrency), context);
|
|
239
240
|
if (!completed.final_output_path) {
|
|
240
241
|
throw new RuntimeError("SWARM_RESULT_MISSING", "Swarm finished without a rendered result.", "swarm.result");
|
|
241
242
|
}
|
|
@@ -308,6 +309,7 @@ async function runWriteSwarm(parsed, objective, context) {
|
|
|
308
309
|
model: parsed.model ?? null,
|
|
309
310
|
thinking: parsed.thinking ?? null,
|
|
310
311
|
background: false,
|
|
312
|
+
budget_ms: parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS,
|
|
311
313
|
pid: process.pid,
|
|
312
314
|
kimi_pid: null,
|
|
313
315
|
status: "running",
|
|
@@ -335,7 +337,7 @@ async function runWriteSwarm(parsed, objective, context) {
|
|
|
335
337
|
});
|
|
336
338
|
throw classified;
|
|
337
339
|
}
|
|
338
|
-
const completed = await executeSwarmJob(job.job_id, prompt, objective,
|
|
340
|
+
const completed = await executeSwarmJob(job.job_id, prompt, objective, job.budget_ms, resolveSwarmWriteMaxConcurrency(parsed.maxConcurrency), context, { spawnCwd: worktreePath, workspaceRoot: worktreePath });
|
|
339
341
|
if (!completed.final_output_path) {
|
|
340
342
|
throw new RuntimeError("SWARM_RESULT_MISSING", "Swarm (write) finished without a rendered result.", "swarm.result");
|
|
341
343
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { companionHelp } from "./help.js";
|
|
1
2
|
import { runAsk } from "./commands/ask.js";
|
|
2
3
|
import { runModelInventory } from "./commands/models.js";
|
|
3
4
|
import { runCancel } from "./commands/cancel.js";
|
|
@@ -15,6 +16,11 @@ import { runWorker } from "./commands/worker.js";
|
|
|
15
16
|
import { formatError, RuntimeError } from "./errors.js";
|
|
16
17
|
import { resolveKimiHome } from "./kimi-home.js";
|
|
17
18
|
async function main(argv) {
|
|
19
|
+
const help = companionHelp(argv);
|
|
20
|
+
if (help !== undefined) {
|
|
21
|
+
process.stdout.write(`${help}\n`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
18
24
|
const [command, ...rest] = argv;
|
|
19
25
|
const cwd = process.env.KIMI_PLUGIN_CC_WORKSPACE_CWD || process.cwd();
|
|
20
26
|
const context = {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { hasHelpFlag, ASK_SUPPORTED_FLAGS, RESCUE_SUPPORTED_FLAGS, REVIEW_SUPPORTED_FLAGS, PURSUE_SUPPORTED_FLAGS, SWARM_SUPPORTED_FLAGS, JOB_LOOKUP_SUPPORTED_FLAGS, SETUP_SUPPORTED_FLAGS, REPAIR_SESSIONS_SUPPORTED_FLAGS, REPLAY_SUPPORTED_FLAGS, } from "./parsing.js";
|
|
2
|
+
const SUBCOMMANDS = "setup, review, task, ask, status, result, cancel, replay, repair-sessions";
|
|
3
|
+
const TASK_KINDS = "rescue, challenge, pursue, swarm";
|
|
4
|
+
/** Help is a dispatch-only, side-effect-free exception to normal argument parsing. */
|
|
5
|
+
export function companionHelp(argv) {
|
|
6
|
+
const [command, kind] = argv;
|
|
7
|
+
// Scan only the argument region: the subcommand (and a task kind) are
|
|
8
|
+
// positional names, not prose, so they must not end the flag region.
|
|
9
|
+
const argumentStart = command === "--help" || command === "-h" ? 0
|
|
10
|
+
: command === "task" && kind !== undefined && !kind.startsWith("-") ? 2
|
|
11
|
+
: 1;
|
|
12
|
+
if (!hasHelpFlag(argv.slice(argumentStart)))
|
|
13
|
+
return undefined;
|
|
14
|
+
const suffix = " Use --help or -h among the leading flags (before any prompt text or --) to show this usage.";
|
|
15
|
+
const usage = (name, positionals, flags, description) => `Usage: companion.sh ${name}${positionals ? ` ${positionals}` : ""}. ${description} Supported flags: ${flags}.${suffix}`;
|
|
16
|
+
switch (command) {
|
|
17
|
+
case "--help":
|
|
18
|
+
case "-h":
|
|
19
|
+
return `Usage: companion.sh <subcommand> [arguments]. Subcommands: ${SUBCOMMANDS}. Use <subcommand> --help for usage.`;
|
|
20
|
+
case "task":
|
|
21
|
+
switch (kind) {
|
|
22
|
+
case "rescue": return usage("task rescue", "[options] [--] <task>", RESCUE_SUPPORTED_FLAGS, "Write-capable task; default budget 45m. Backgrounding does not extend the budget.");
|
|
23
|
+
case "challenge": return usage("task challenge", "[options] [--] [focus]", REVIEW_SUPPORTED_FLAGS, "Read-only challenge; default budget 30m. Foreground-only (no --background/--wait).");
|
|
24
|
+
case "pursue": return usage("task pursue", "[options] [--] <objective>", PURSUE_SUPPORTED_FLAGS, "Autonomous goal; default budget 45m. Runtime foreground-only.");
|
|
25
|
+
case "swarm": return usage("task swarm", "[options] [--] <objective>", SWARM_SUPPORTED_FLAGS, "Parallel task; default budget 30m. Runtime foreground-only; --write produces a patch.");
|
|
26
|
+
default: return `Usage: companion.sh task <kind> [arguments]. Task kinds: ${TASK_KINDS}. Use task <kind> --help for usage.`;
|
|
27
|
+
}
|
|
28
|
+
case "ask": return usage(command, "[options] [--] [prompt]", ASK_SUPPORTED_FLAGS, "Read-only question; default budget 30m. Backgrounding does not extend the budget.");
|
|
29
|
+
case "review": return usage(command, "[options] [--] [focus]", REVIEW_SUPPORTED_FLAGS, "Read-only review; default budget 30m. Foreground-only (no --background/--wait).");
|
|
30
|
+
case "setup": return usage(command, "[options]", SETUP_SUPPORTED_FLAGS, "Install, check, or remove this host's managed hook, or list configured models.");
|
|
31
|
+
case "status":
|
|
32
|
+
case "result":
|
|
33
|
+
case "cancel":
|
|
34
|
+
return usage(command, "[job-id] [options]", JOB_LOOKUP_SUPPORTED_FLAGS, command === "status" ? "Show job state as JSON." : command === "result" ? "Read a terminal artifact, optionally in a JSON envelope." : "Cancel a running job; without an id, target the repository's latest running job.");
|
|
35
|
+
case "replay": return usage(command, "<job-id>", REPLAY_SUPPORTED_FLAGS, "Replay a completed job's persisted stream log.");
|
|
36
|
+
case "repair-sessions": return usage(command, "[options]", REPAIR_SESSIONS_SUPPORTED_FLAGS, "Preview session metadata repair; --apply writes, --all selects all host-owned repositories.");
|
|
37
|
+
case "worker": return "Usage: companion.sh worker <ask|rescue> <job-id>. Internal detached-worker entry point; launched by ask/rescue, not manually.";
|
|
38
|
+
default: return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -585,14 +585,15 @@ function validateVersionSensitiveEvents(events, version) {
|
|
|
585
585
|
return invalidHookSet(`version-sensitive event ${JSON.stringify(first.event)} requires a verified kimi-code version`, first.entry, first.line);
|
|
586
586
|
}
|
|
587
587
|
// Exact schemas reviewed at 2.0.0 (1b89e4b0), 2.0.1 (caf7d4e2),
|
|
588
|
-
// 2.0.2 (9d07f634), 2.1.0 (52437299),
|
|
588
|
+
// 2.0.2 (9d07f634), 2.1.0 (52437299), 2.1.1 (f67e6398) and 2.2.0 (472192c3):
|
|
589
589
|
// the additive events and HookDefSchema are unchanged. Require the full
|
|
590
590
|
// version string AND matching components; no inferred 2.x compatibility.
|
|
591
591
|
if (version.major === 2 && ((version.minor === 0 && ((version.version === "2.0.0" && version.patch === 0) ||
|
|
592
592
|
(version.version === "2.0.1" && version.patch === 1) ||
|
|
593
593
|
(version.version === "2.0.2" && version.patch === 2))) ||
|
|
594
594
|
(version.minor === 1 && ((version.version === "2.1.0" && version.patch === 0) ||
|
|
595
|
-
(version.version === "2.1.1" && version.patch === 1)))
|
|
595
|
+
(version.version === "2.1.1" && version.patch === 1))) ||
|
|
596
|
+
(version.minor === 2 && ((version.version === "2.2.0" && version.patch === 0))))) {
|
|
596
597
|
return { valid: true };
|
|
597
598
|
}
|
|
598
599
|
if (version.major !== 0) {
|
|
@@ -58,6 +58,7 @@ export class JobStore {
|
|
|
58
58
|
prompt_digest TEXT NOT NULL,
|
|
59
59
|
summary TEXT NOT NULL,
|
|
60
60
|
phase TEXT,
|
|
61
|
+
budget_ms INTEGER,
|
|
61
62
|
final_output_path TEXT,
|
|
62
63
|
stream_log_path TEXT NOT NULL,
|
|
63
64
|
error TEXT
|
|
@@ -66,6 +67,9 @@ export class JobStore {
|
|
|
66
67
|
if (!tableHasColumn(this.db, "phase")) {
|
|
67
68
|
this.db.exec(`ALTER TABLE jobs ADD COLUMN phase TEXT;`);
|
|
68
69
|
}
|
|
70
|
+
if (!tableHasColumn(this.db, "budget_ms")) {
|
|
71
|
+
this.db.exec(`ALTER TABLE jobs ADD COLUMN budget_ms INTEGER;`);
|
|
72
|
+
}
|
|
69
73
|
// Additive provenance migration. Existing rows deliberately remain NULL:
|
|
70
74
|
// v1.9.5 plus kimi-code 0.33/0.34 could have routed to native v2 before the
|
|
71
75
|
// legacy pin landed, so a blanket historical v1 backfill would invent fact.
|
|
@@ -162,14 +166,14 @@ export class JobStore {
|
|
|
162
166
|
background, pid, kimi_pid, status, kimi_session_id,
|
|
163
167
|
operation_kind, intended_engine, observed_engine, kimi_version, system_version,
|
|
164
168
|
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
|
|
169
|
+
agent_profile, prompt_digest, summary, phase, budget_ms, final_output_path, stream_log_path, error
|
|
166
170
|
)
|
|
167
171
|
VALUES (
|
|
168
172
|
@job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking,
|
|
169
173
|
@background, @pid, @kimi_pid, @status, @kimi_session_id,
|
|
170
174
|
@operation_kind, @intended_engine, @observed_engine, @kimi_version, @system_version,
|
|
171
175
|
@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
|
|
176
|
+
@agent_profile, @prompt_digest, @summary, @phase, @budget_ms, @final_output_path, @stream_log_path, @error
|
|
173
177
|
)
|
|
174
178
|
`, {
|
|
175
179
|
...serializeRecord(input),
|
|
@@ -276,7 +280,7 @@ export class JobStore {
|
|
|
276
280
|
return this.updateWhere(jobId, { ...patch, status: "cancelled", pid: null, kimi_pid: null }, "status = 'running'");
|
|
277
281
|
}
|
|
278
282
|
static JOBS_COLUMNS = new Set([
|
|
279
|
-
"status", "summary", "phase", "pid", "kimi_pid",
|
|
283
|
+
"status", "summary", "phase", "budget_ms", "pid", "kimi_pid",
|
|
280
284
|
"final_output_path", "stream_log_path", "kimi_session_id",
|
|
281
285
|
"error", "command_type", "prompt_digest", "repo_id",
|
|
282
286
|
"cwd", "model", "thinking", "background", "agent_profile",
|
|
@@ -320,6 +324,7 @@ function serializeRecord(record) {
|
|
|
320
324
|
return {
|
|
321
325
|
...record,
|
|
322
326
|
phase: record.phase ?? null,
|
|
327
|
+
budget_ms: record.budget_ms ?? null,
|
|
323
328
|
operation_kind: record.operation_kind ?? null,
|
|
324
329
|
intended_engine: record.intended_engine ?? null,
|
|
325
330
|
observed_engine: record.observed_engine ?? null,
|
|
@@ -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,
|