kimi-plugin-cc 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +18 -0
- package/README.md +17 -9
- package/dist/cli-client.js +19 -9
- package/dist/commands/cancel.js +14 -3
- package/dist/commands/cli-helpers.js +5 -1
- package/dist/commands/recover-swarm.js +135 -0
- package/dist/commands/status.js +1 -1
- package/dist/commands/swarm.js +241 -87
- package/dist/companion.js +5 -1
- package/dist/git.js +5 -3
- package/dist/help.js +3 -2
- package/dist/hooks/config-safety.js +4 -3
- package/dist/job-store.js +13 -0
- package/dist/jobs.js +3 -0
- package/dist/kimi-engine.js +1 -0
- package/dist/kimi-errors.js +27 -21
- package/dist/parsing.js +1 -0
- package/dist/process-tree.js +48 -17
- package/dist/prompt-size.js +18 -0
- package/dist/swarm-activation.js +833 -0
- package/dist/swarm-recovery.js +134 -0
- package/dist/version.js +1 -1
- package/docs/migration.md +12 -2
- package/docs/registry-releases.md +13 -2
- package/docs/safety.md +20 -6
- package/docs/swarm-recovery.md +33 -0
- package/docs/windows-validation.md +39 -0
- package/package.json +7 -4
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/cli-client.js +19 -9
- package/plugins/kimi-codex/dist/commands/cancel.js +14 -3
- package/plugins/kimi-codex/dist/commands/cli-helpers.js +5 -1
- package/plugins/kimi-codex/dist/commands/recover-swarm.js +135 -0
- package/plugins/kimi-codex/dist/commands/status.js +1 -1
- package/plugins/kimi-codex/dist/commands/swarm.js +241 -87
- package/plugins/kimi-codex/dist/companion.js +5 -1
- package/plugins/kimi-codex/dist/git.js +5 -3
- package/plugins/kimi-codex/dist/help.js +3 -2
- package/plugins/kimi-codex/dist/hooks/config-safety.js +4 -3
- package/plugins/kimi-codex/dist/job-store.js +13 -0
- package/plugins/kimi-codex/dist/jobs.js +3 -0
- package/plugins/kimi-codex/dist/kimi-engine.js +1 -0
- package/plugins/kimi-codex/dist/kimi-errors.js +27 -21
- package/plugins/kimi-codex/dist/parsing.js +1 -0
- package/plugins/kimi-codex/dist/process-tree.js +48 -17
- package/plugins/kimi-codex/dist/prompt-size.js +18 -0
- package/plugins/kimi-codex/dist/swarm-activation.js +833 -0
- package/plugins/kimi-codex/dist/swarm-recovery.js +134 -0
- package/plugins/kimi-codex/dist/version.js +1 -1
package/dist/kimi-errors.js
CHANGED
|
@@ -44,6 +44,11 @@ export function classifyManagedCommandFailure(error, commandType, jobId, options
|
|
|
44
44
|
const details = { availability: classification.kind };
|
|
45
45
|
if (error instanceof RuntimeError) {
|
|
46
46
|
details.cause_code = error.code;
|
|
47
|
+
for (const key of ["spawn_code", "spawn_errno", "spawn_message"]) {
|
|
48
|
+
if (error.details[key] !== undefined)
|
|
49
|
+
details[key] = key === "spawn_message" && typeof error.details[key] === "string"
|
|
50
|
+
? truncateCauseMessageForDetails(error.details[key]) : error.details[key];
|
|
51
|
+
}
|
|
47
52
|
if (error.code === "RESPONSE_TIMEOUT") {
|
|
48
53
|
for (const key of ["budget_ms", "record_count", "partial_output_chars", "aborted", "settled_post_abort", "hook_escapes"]) {
|
|
49
54
|
if (error.details[key] !== undefined)
|
|
@@ -129,28 +134,29 @@ function classifyKimiAvailability(error) {
|
|
|
129
134
|
authProbe: "failed",
|
|
130
135
|
};
|
|
131
136
|
}
|
|
132
|
-
if (error instanceof RuntimeError && error.code === "CLI_SPAWN_FAILED") {
|
|
133
|
-
return {
|
|
134
|
-
kind: "binary_unavailable",
|
|
135
|
-
summary: "the Kimi CLI is missing from PATH or not executable in this environment.",
|
|
136
|
-
nextStep: "Run `/kimi:setup` to verify the install, then expose `kimi` on PATH and retry.",
|
|
137
|
-
runtimeProbe: "failed",
|
|
138
|
-
authProbe: "failed",
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
// v1.0 cli-client surfaces async ENOENT (Bun) as CLI_PROCESS_ERROR with
|
|
142
|
-
// "spawn ... ENOENT" in the message. Map that to binary_unavailable so
|
|
143
|
-
// the failure message points users at /kimi:setup.
|
|
144
137
|
if (error instanceof RuntimeError &&
|
|
145
|
-
error.code === "CLI_PROCESS_ERROR"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
138
|
+
(error.code === "CLI_SPAWN_FAILED" || error.code === "CLI_PROCESS_ERROR")) {
|
|
139
|
+
const spawnCode = error.details.spawn_code;
|
|
140
|
+
const binaryUnavailable = typeof spawnCode === "string" && ["ENOENT", "EACCES", "ENOEXEC"].includes(spawnCode);
|
|
141
|
+
if (typeof spawnCode === "string" && !binaryUnavailable) {
|
|
142
|
+
return {
|
|
143
|
+
kind: "startup_failed",
|
|
144
|
+
summary: "the Kimi subprocess could not be started or used.",
|
|
145
|
+
nextStep: "Check the underlying process error; shorten the prompt or environment if an argument-size limit was exceeded, then retry.",
|
|
146
|
+
runtimeProbe: "failed",
|
|
147
|
+
authProbe: "failed",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// Keep opaque synchronous failures and legacy async ENOENT diagnostics compatible.
|
|
151
|
+
if (binaryUnavailable || error.code === "CLI_SPAWN_FAILED" || /\bENOENT\b/.test(message)) {
|
|
152
|
+
return {
|
|
153
|
+
kind: "binary_unavailable",
|
|
154
|
+
summary: "the Kimi CLI is missing from PATH or not executable in this environment.",
|
|
155
|
+
nextStep: "Run `/kimi:setup` to verify the install, then expose `kimi` on PATH and retry.",
|
|
156
|
+
runtimeProbe: "failed",
|
|
157
|
+
authProbe: "failed",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
154
160
|
}
|
|
155
161
|
// v1.0 cli-client surfaces non-ENOENT process errors and non-zero
|
|
156
162
|
// exits as CLI_PROCESS_ERROR / CLI_NONZERO_EXIT. Treat both as
|
package/dist/parsing.js
CHANGED
|
@@ -18,6 +18,7 @@ export const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|me
|
|
|
18
18
|
export const REVIEW_SUPPORTED_FLAGS = "--base <ref>, -m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>";
|
|
19
19
|
export const JOB_LOOKUP_SUPPORTED_FLAGS = "--type <review|challenge|rescue|review_gate|ask>, --json";
|
|
20
20
|
export const SETUP_SUPPORTED_FLAGS = "--check, --uninstall, --all (with --uninstall), --enable-review-gate, --disable-review-gate, --models [--json] (without other setup flags)";
|
|
21
|
+
export const RECOVER_SWARM_SUPPORTED_FLAGS = "--apply";
|
|
21
22
|
export const REPAIR_SESSIONS_SUPPORTED_FLAGS = "--apply, --all";
|
|
22
23
|
export const REPLAY_SUPPORTED_FLAGS = "none (one job-id required)";
|
|
23
24
|
/** Validate syntax only; upstream per-model supportEfforts remains authoritative. */
|
package/dist/process-tree.js
CHANGED
|
@@ -6,7 +6,7 @@ const MAX_DESCENDANT_DEPTH = 8;
|
|
|
6
6
|
const MAX_DESCENDANT_PIDS = 512;
|
|
7
7
|
const PROCESS_EXIT_POLL_MS = 10;
|
|
8
8
|
const PROCESS_SNAPSHOT_TIMEOUT_MS = 1_000;
|
|
9
|
-
const PROCESS_IDENTITY_PROBE_TIMEOUT_MS =
|
|
9
|
+
const PROCESS_IDENTITY_PROBE_TIMEOUT_MS = 1_000;
|
|
10
10
|
const PROBE_TIMEOUT = Symbol("process-probe-timeout");
|
|
11
11
|
/**
|
|
12
12
|
* Capture descendant ancestry and reusable identities in the same bounded
|
|
@@ -23,6 +23,18 @@ export async function collectDescendantIdentities(rootPid) {
|
|
|
23
23
|
}
|
|
24
24
|
return snapshot;
|
|
25
25
|
}
|
|
26
|
+
/** Return current members of the selected process groups, or undefined when unreadable. */
|
|
27
|
+
export async function collectProcessGroupsIdentities(processGroupIds) {
|
|
28
|
+
if (process.platform === "win32")
|
|
29
|
+
return undefined;
|
|
30
|
+
const groups = new Set(processGroupIds.filter((id) => Number.isInteger(id) && id > 0));
|
|
31
|
+
if (groups.size === 0)
|
|
32
|
+
return [];
|
|
33
|
+
const rows = await raceValueWithTimeout(collectProcessSnapshotRows(), PROCESS_SNAPSHOT_TIMEOUT_MS);
|
|
34
|
+
if (rows === PROBE_TIMEOUT || rows === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
return rows.filter((row) => groups.has(row.processGroupId)).map(toProcessIdentity);
|
|
37
|
+
}
|
|
26
38
|
/**
|
|
27
39
|
* Compatibility surface for callers that only need numeric process listings.
|
|
28
40
|
* Cancellation must use collectDescendantIdentities so ancestry and identity
|
|
@@ -36,15 +48,21 @@ export async function collectDescendants(rootPid) {
|
|
|
36
48
|
return [];
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
|
-
/**
|
|
40
|
-
export async function
|
|
51
|
+
/** Distinguish a missing process from a failed or timed-out identity probe. */
|
|
52
|
+
export async function probeProcessIdentity(pid) {
|
|
41
53
|
if (!Number.isInteger(pid) || pid <= 0 || process.platform === "win32")
|
|
42
|
-
return
|
|
54
|
+
return { complete: true };
|
|
43
55
|
const read = process.platform === "linux"
|
|
44
56
|
? readProcessIdentityBatchFromProc([pid])
|
|
45
57
|
: readProcessIdentityBatchWithPs([pid]);
|
|
46
58
|
const batch = await raceValueWithTimeout(read, PROCESS_IDENTITY_PROBE_TIMEOUT_MS);
|
|
47
|
-
|
|
59
|
+
if (batch === PROBE_TIMEOUT)
|
|
60
|
+
return { complete: false };
|
|
61
|
+
return { identity: batch.identities.get(pid), complete: batch.complete };
|
|
62
|
+
}
|
|
63
|
+
/** Read one reusable process identity. PID alone is not stable across teardown. */
|
|
64
|
+
export async function readProcessIdentity(pid) {
|
|
65
|
+
return (await probeProcessIdentity(pid)).identity;
|
|
48
66
|
}
|
|
49
67
|
/**
|
|
50
68
|
* Revalidate all captured identities concurrently under one deadline. The
|
|
@@ -63,16 +81,26 @@ export async function revalidateProcessIdentities(expectedIdentities, options =
|
|
|
63
81
|
if (batch === PROBE_TIMEOUT)
|
|
64
82
|
return { identities: [], complete: false };
|
|
65
83
|
const identities = [];
|
|
84
|
+
const groupChanged = [];
|
|
66
85
|
for (const expectedIdentity of expected) {
|
|
67
86
|
const current = batch.identities.get(expectedIdentity.pid);
|
|
68
|
-
if (current
|
|
69
|
-
current.startToken
|
|
70
|
-
current.
|
|
71
|
-
|
|
87
|
+
if (current === undefined ||
|
|
88
|
+
current.startToken !== expectedIdentity.startToken ||
|
|
89
|
+
isQuiescentState(current.state)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (current.processGroupId !== expectedIdentity.processGroupId) {
|
|
93
|
+
groupChanged.push(current);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
72
96
|
identities.push(current);
|
|
73
97
|
}
|
|
74
98
|
}
|
|
75
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
identities,
|
|
101
|
+
complete: batch.complete,
|
|
102
|
+
...(groupChanged.length > 0 ? { groupChanged } : {}),
|
|
103
|
+
};
|
|
76
104
|
}
|
|
77
105
|
/** Return the current identity only when pid, start token, and PGID still match. */
|
|
78
106
|
export async function revalidateProcessIdentity(expected, identityReader) {
|
|
@@ -104,7 +132,7 @@ export async function waitForProcessTreeExit(identities, timeoutMs, options = {}
|
|
|
104
132
|
identityReader: options.identityReader,
|
|
105
133
|
timeoutMs: probeTimeoutMs,
|
|
106
134
|
});
|
|
107
|
-
active = !validation.complete || validation.identities.length > 0;
|
|
135
|
+
active = !validation.complete || validation.identities.length > 0 || (validation.groupChanged?.length ?? 0) > 0;
|
|
108
136
|
}
|
|
109
137
|
if (!active)
|
|
110
138
|
return true;
|
|
@@ -117,13 +145,13 @@ export async function waitForProcessTreeExit(identities, timeoutMs, options = {}
|
|
|
117
145
|
async function collectVerifiedDescendantIdentities(rootPid) {
|
|
118
146
|
const initialRows = await collectProcessSnapshotRows();
|
|
119
147
|
if (initialRows === undefined)
|
|
120
|
-
|
|
148
|
+
throw new Error("process snapshot unavailable");
|
|
121
149
|
const candidateRows = descendantRowsFromSnapshot(rootPid, initialRows);
|
|
122
150
|
const initialRoot = initialRows.find((row) => row.pid === rootPid);
|
|
123
151
|
if (initialRoot === undefined || candidateRows.length === 0)
|
|
124
152
|
return [];
|
|
125
|
-
// A second, targeted snapshot closes the cross-row /proc race:
|
|
126
|
-
//
|
|
153
|
+
// A second, targeted snapshot closes the cross-row /proc race: each child
|
|
154
|
+
// must retain its PID and start token. Reparenting and PGID changes are not exit.
|
|
127
155
|
const expectedRows = [initialRoot, ...candidateRows];
|
|
128
156
|
const expectedByPid = new Map(expectedRows.map((row) => [row.pid, row]));
|
|
129
157
|
const verificationRows = await readCurrentSnapshotRows(expectedRows);
|
|
@@ -131,11 +159,9 @@ async function collectVerifiedDescendantIdentities(rootPid) {
|
|
|
131
159
|
const expected = expectedByPid.get(current.pid);
|
|
132
160
|
return (expected !== undefined &&
|
|
133
161
|
current.startToken === expected.startToken &&
|
|
134
|
-
current.processGroupId === expected.processGroupId &&
|
|
135
|
-
current.parentPid === expected.parentPid &&
|
|
136
162
|
!isQuiescentState(current.state));
|
|
137
163
|
});
|
|
138
|
-
return
|
|
164
|
+
return stableRows.map(toProcessIdentity);
|
|
139
165
|
}
|
|
140
166
|
function descendantRowsFromSnapshot(rootPid, rows) {
|
|
141
167
|
const root = rows.find((row) => row.pid === rootPid);
|
|
@@ -186,6 +212,9 @@ async function readCurrentSnapshotRows(expectedRows) {
|
|
|
186
212
|
Promise.all(procPids.map((pid) => readProcRow(pid))),
|
|
187
213
|
collectProcessSnapshotRowsFromPs(psPids),
|
|
188
214
|
]);
|
|
215
|
+
if (procOutcomes.some((outcome) => outcome.status === "error") || (psPids.length > 0 && psRows === undefined)) {
|
|
216
|
+
throw new Error("process identity verification failed");
|
|
217
|
+
}
|
|
189
218
|
return [
|
|
190
219
|
...procOutcomes.flatMap((outcome) => outcome.status === "found" ? [outcome.row] : []),
|
|
191
220
|
...(psRows ?? []),
|
|
@@ -212,6 +241,8 @@ async function collectProcessSnapshotRowsFromProc() {
|
|
|
212
241
|
const outcomes = await Promise.all(entries
|
|
213
242
|
.filter((entry) => /^\d+$/.test(entry))
|
|
214
243
|
.map((entry) => readProcRow(Number.parseInt(entry, 10))));
|
|
244
|
+
if (outcomes.some((outcome) => outcome.status === "error"))
|
|
245
|
+
return undefined;
|
|
215
246
|
return outcomes.flatMap((outcome) => outcome.status === "found" ? [outcome.row] : []);
|
|
216
247
|
}
|
|
217
248
|
async function collectProcessSnapshotRowsFromPs(pids) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RuntimeError } from "./errors.js";
|
|
2
|
+
// Node/libuv's default CreateProcess argument quoting (UTF-16 code units).
|
|
3
|
+
export function windowsCommandLineLength(command, args) {
|
|
4
|
+
const quotedLength = (value) => {
|
|
5
|
+
if (value.length > 0 && !/[\s"]/u.test(value))
|
|
6
|
+
return value.length;
|
|
7
|
+
return 2 + value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1').length;
|
|
8
|
+
};
|
|
9
|
+
return [command, ...args].reduce((size, value) => size + quotedLength(value) + 1, 0);
|
|
10
|
+
}
|
|
11
|
+
export function assertPromptFitsCommandLine(command, args, platform = process.platform) {
|
|
12
|
+
if (platform !== "win32")
|
|
13
|
+
return;
|
|
14
|
+
const size = windowsCommandLineLength(command, args);
|
|
15
|
+
if (size <= 32_767)
|
|
16
|
+
return;
|
|
17
|
+
throw new RuntimeError("CLI_COMMAND_LINE_TOO_LONG", "The Kimi prompt exceeds the Windows command-line limit; the CLI was not started. Narrow the review or split it into smaller scopes. A short ask prompt can reference a saved diff inside the workspace. Reinstalling Kimi or running setup will not fix this. The current print transport does not support stdin prompts.", "cli-client.pre-spawn", { details: { command_line_utf16_units: size, limit_utf16_units: 32_767, platform } });
|
|
18
|
+
}
|