kimi-plugin-cc 2.4.4 → 2.5.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 (119) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +21 -0
  4. package/README.md +50 -34
  5. package/dist/commands/swarm.js +1 -1
  6. package/dist/dsh/companion.js +174 -0
  7. package/dist/dsh/index.js +156 -0
  8. package/dist/hooks/config-safety.js +3 -2
  9. package/dist/hooks/install-paths.js +2 -2
  10. package/dist/kimi-engine.js +1 -0
  11. package/dist/swarm-activation.js +1 -1
  12. package/dist/version.js +1 -1
  13. package/docs/migration.md +4 -4
  14. package/docs/models.md +5 -1
  15. package/docs/registry-releases.md +20 -17
  16. package/docs/safety.md +28 -15
  17. package/docs/swarm-recovery.md +1 -1
  18. package/docs/upstream-2.4.1-certification.md +54 -0
  19. package/package.json +41 -6
  20. package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
  21. package/plugins/kimi-codex/dist/commands/swarm.js +1 -1
  22. package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
  23. package/plugins/kimi-codex/dist/dsh/index.js +156 -0
  24. package/plugins/kimi-codex/dist/hooks/config-safety.js +3 -2
  25. package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
  26. package/plugins/kimi-codex/dist/kimi-engine.js +1 -0
  27. package/plugins/kimi-codex/dist/swarm-activation.js +1 -1
  28. package/plugins/kimi-codex/dist/version.js +1 -1
  29. package/plugins/kimi-dsh/LICENSE +201 -0
  30. package/plugins/kimi-dsh/README.md +80 -0
  31. package/plugins/kimi-dsh/cordis.patch.yml +45 -0
  32. package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
  33. package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
  34. package/plugins/kimi-dsh/dist/cli-client.js +876 -0
  35. package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
  36. package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
  37. package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
  38. package/plugins/kimi-dsh/dist/commands/models.js +140 -0
  39. package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
  40. package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
  41. package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
  42. package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
  43. package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
  44. package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
  45. package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
  46. package/plugins/kimi-dsh/dist/commands/result.js +66 -0
  47. package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
  48. package/plugins/kimi-dsh/dist/commands/review.js +220 -0
  49. package/plugins/kimi-dsh/dist/commands/setup.js +1047 -0
  50. package/plugins/kimi-dsh/dist/commands/status.js +27 -0
  51. package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
  52. package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
  53. package/plugins/kimi-dsh/dist/companion.js +97 -0
  54. package/plugins/kimi-dsh/dist/config.js +22 -0
  55. package/plugins/kimi-dsh/dist/dsh/companion.js +174 -0
  56. package/plugins/kimi-dsh/dist/dsh/index.js +156 -0
  57. package/plugins/kimi-dsh/dist/errors.js +40 -0
  58. package/plugins/kimi-dsh/dist/git.js +224 -0
  59. package/plugins/kimi-dsh/dist/help.js +43 -0
  60. package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
  61. package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
  62. package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
  63. package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
  64. package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
  65. package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
  66. package/plugins/kimi-dsh/dist/hooks/managed-block.js +619 -0
  67. package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
  68. package/plugins/kimi-dsh/dist/job-id.js +12 -0
  69. package/plugins/kimi-dsh/dist/job-store.js +659 -0
  70. package/plugins/kimi-dsh/dist/jobs.js +217 -0
  71. package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
  72. package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
  73. package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
  74. package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
  75. package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
  76. package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
  77. package/plugins/kimi-dsh/dist/logging.js +22 -0
  78. package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
  79. package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
  80. package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
  81. package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
  82. package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
  83. package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
  84. package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
  85. package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
  86. package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
  87. package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
  88. package/plugins/kimi-dsh/dist/model-command.js +32 -0
  89. package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
  90. package/plugins/kimi-dsh/dist/package.json +1 -0
  91. package/plugins/kimi-dsh/dist/parsing.js +620 -0
  92. package/plugins/kimi-dsh/dist/paths.js +27 -0
  93. package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
  94. package/plugins/kimi-dsh/dist/process-tree.js +479 -0
  95. package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
  96. package/plugins/kimi-dsh/dist/render.js +177 -0
  97. package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
  98. package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
  99. package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
  100. package/plugins/kimi-dsh/dist/session-title.js +293 -0
  101. package/plugins/kimi-dsh/dist/stream-json.js +346 -0
  102. package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
  103. package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
  104. package/plugins/kimi-dsh/dist/types.js +1 -0
  105. package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
  106. package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
  107. package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
  108. package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
  109. package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
  110. package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
  111. package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
  112. package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
  113. package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
  114. package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
  115. package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
  116. package/plugins/kimi-dsh/dist/version.js +7 -0
  117. package/plugins/kimi-dsh/package.json +48 -0
  118. package/plugins/kimi-dsh/scripts/companion.sh +37 -0
  119. package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,216 @@
1
+ import { spawn } from "node:child_process";
2
+ import { access, appendFile, constants as fsConstants } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { RuntimeError, formatError } from "./errors.js";
6
+ import { preferStableNodePath } from "./hooks/install-paths.js";
7
+ import { markJobFailed, waitForTerminalJob } from "./jobs.js";
8
+ import { JobStore } from "./job-store.js";
9
+ import { readArtifact } from "./render.js";
10
+ // Resolves to runtime/companion.ts in dev (via tsx) and dist/companion.js in production (compiled
11
+ // with tsc to the same relative layout). The extension is derived from the running module so the
12
+ // child-process spawn in startBackgroundJob targets the right entrypoint in both modes.
13
+ const isCompiledRuntime = import.meta.url.endsWith(".js");
14
+ const companionEntrypoint = fileURLToPath(new URL(isCompiledRuntime ? "./companion.js" : "./companion.ts", import.meta.url));
15
+ const companionProjectRoot = path.resolve(path.dirname(companionEntrypoint), "..");
16
+ const FAILURE_PERSIST_RETRY_DELAYS_MS = [100, 200, 400];
17
+ export async function startBackgroundJob(job, prompt, context, paths, options) {
18
+ // Background spawn and hook verification must resolve the same stable Node
19
+ // path: the worker re-verifies the byte-exact hook command from its own
20
+ // runtime path, so a different spelling than setup pinned fails closed. For
21
+ // example, spawn(process.execPath) can resolve …/Cellar/node/<ver>/bin/node
22
+ // while setup pinned /opt/homebrew/bin/node.
23
+ //
24
+ // An explicit spawn override need not be absolute because spawn() resolves
25
+ // bare names through PATH. The hook command separately requires an absolute
26
+ // override because /bin/sh -c may have a sanitized PATH. Only the no-override
27
+ // default uses preferStableNodePath to match the verifier.
28
+ const override = context.env.KIMI_PLUGIN_CC_NODE_BIN;
29
+ const nodeBinary = override !== undefined && override.length > 0
30
+ ? override
31
+ : preferStableNodePath(process.execPath);
32
+ // Only fast-path the `fs.access` check for absolute/relative paths. A bare
33
+ // name like "node" is resolved by spawn() via PATH; the spawn `error` listener
34
+ // will still surface ENOENT on nextTick for that path, classified as
35
+ // spawnFailedErrorCode via the listener below.
36
+ if (nodeBinary.includes("/") || nodeBinary.includes("\\")) {
37
+ try {
38
+ await access(nodeBinary, fsConstants.X_OK);
39
+ }
40
+ catch (accessError) {
41
+ const code = accessError.code;
42
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM") {
43
+ const classified = new RuntimeError(options.nodeBinInvalidErrorCode, `Configured Node binary is not executable: ${nodeBinary}. Set KIMI_PLUGIN_CC_NODE_BIN to a valid Node >=22.5 executable and retry.`, options.spawnStage, accessError instanceof Error ? { cause: accessError } : undefined);
44
+ await persistBackgroundFailure(paths, job.job_id, classified, options.failedSummary, "node-bin-invalid");
45
+ throw classified;
46
+ }
47
+ // Any other errno (EIO, etc.) — fall through to the spawn attempt and let the
48
+ // existing spawn error path surface it.
49
+ }
50
+ }
51
+ const b64 = Buffer.from(prompt, "utf8").toString("base64");
52
+ const spawnArgs = isCompiledRuntime
53
+ ? [companionEntrypoint, "worker", options.workerKind, job.job_id]
54
+ : ["--import", "tsx", companionEntrypoint, "worker", options.workerKind, job.job_id];
55
+ const child = spawn(nodeBinary, spawnArgs, {
56
+ cwd: companionProjectRoot,
57
+ detached: true,
58
+ stdio: "ignore",
59
+ env: {
60
+ ...context.env,
61
+ KIMI_PLUGIN_CC_WORKSPACE_CWD: context.cwd,
62
+ [options.promptEnvVar]: b64,
63
+ [options.reusedSessionEnvVar]: options.reusedSession ? "1" : "0",
64
+ ...(options.extraEnv ?? {}),
65
+ // A worker must reopen the exact store its parent selected, regardless of cwd.
66
+ KIMI_PLUGIN_CC_DATA: path.resolve(paths.claudePluginData),
67
+ },
68
+ });
69
+ let spawnReportedFailure = false;
70
+ const spawnErrorPromise = new Promise((resolve) => {
71
+ let settled = false;
72
+ const settle = (value) => {
73
+ if (settled) {
74
+ return;
75
+ }
76
+ settled = true;
77
+ resolve(value);
78
+ };
79
+ child.once("error", (spawnError) => {
80
+ spawnReportedFailure = true;
81
+ settle(new RuntimeError(options.spawnFailedErrorCode, `Background ${options.workerKind} worker failed to spawn: ${spawnError.message}`, options.spawnStage, { cause: spawnError }));
82
+ });
83
+ // Give the event loop one tick to deliver a synchronous spawn error
84
+ // (ENOENT fires on process.nextTick). If nothing fires by then, the
85
+ // child is live and we can proceed.
86
+ setImmediate(() => settle(null));
87
+ });
88
+ child.on("close", (exitCode, signal) => {
89
+ if (spawnReportedFailure) {
90
+ return;
91
+ }
92
+ if (exitCode === null || exitCode === 0) {
93
+ return;
94
+ }
95
+ // The worker exited non-zero before any state update. If the job is
96
+ // still in its spawn phase, the worker never reached executeJob.
97
+ const classified = new RuntimeError(options.earlyExitErrorCode, `Background ${options.workerKind} worker exited with code ${exitCode}${signal ? ` (signal ${signal})` : ""} before reporting a result.`, options.spawnStage);
98
+ void markEarlyExit(paths, job.job_id, classified, options.failedSummary);
99
+ });
100
+ child.unref();
101
+ const spawnError = await spawnErrorPromise;
102
+ if (spawnError) {
103
+ await markSpawnFailure(paths, job.job_id, spawnError, options.failedSummary);
104
+ throw spawnError;
105
+ }
106
+ const store = new JobStore(paths);
107
+ try {
108
+ store.updateRunningJob(job.job_id, {
109
+ pid: child.pid ?? null,
110
+ phase: "worker-spawned",
111
+ });
112
+ }
113
+ finally {
114
+ store.close();
115
+ }
116
+ if (options.wait) {
117
+ const completed = await waitForTerminalJob(() => new JobStore(paths), job.job_id);
118
+ if (!completed.final_output_path) {
119
+ throw new RuntimeError(options.missingResultErrorCode, describeMissingResult(completed, options.workerKind), options.waitStage);
120
+ }
121
+ return readArtifact(completed.final_output_path);
122
+ }
123
+ return `${JSON.stringify({
124
+ job_id: job.job_id,
125
+ command_type: job.command_type,
126
+ }, null, 2)}\n`;
127
+ }
128
+ async function markSpawnFailure(paths, jobId, classified, failedSummary) {
129
+ await persistBackgroundFailure(paths, jobId, classified, failedSummary, "spawn-failed");
130
+ }
131
+ async function markEarlyExit(paths, jobId, classified, failedSummary) {
132
+ await persistBackgroundFailure(paths, jobId, classified, failedSummary, "early-exit", (current) => {
133
+ // Only mark failure if the worker exited before advancing past the spawn phase.
134
+ // Once the worker reaches worker-running or turn-running, the child owns the
135
+ // job state and the parent's close listener must not race with it.
136
+ return current.phase === "worker-spawned" || current.phase === "queued";
137
+ });
138
+ }
139
+ async function persistBackgroundFailure(paths, jobId, classified, failedSummary, event, shouldMark = () => true) {
140
+ let lastWriteError;
141
+ for (let attempt = 0; attempt <= FAILURE_PERSIST_RETRY_DELAYS_MS.length; attempt += 1) {
142
+ let store;
143
+ try {
144
+ store = new JobStore(paths);
145
+ const current = store.getJob(jobId);
146
+ if (!current || current.status !== "running" || !shouldMark(current)) {
147
+ return;
148
+ }
149
+ await markJobFailed(store, paths, current, classified, failedSummary, { phase: "failed" });
150
+ return;
151
+ }
152
+ catch (writeError) {
153
+ lastWriteError = writeError;
154
+ if (attempt < FAILURE_PERSIST_RETRY_DELAYS_MS.length) {
155
+ await sleep(FAILURE_PERSIST_RETRY_DELAYS_MS[attempt]);
156
+ }
157
+ }
158
+ finally {
159
+ try {
160
+ store?.close();
161
+ }
162
+ catch {
163
+ // The persistence attempt outcome above is the signal that matters.
164
+ }
165
+ }
166
+ }
167
+ await appendStuckJobTelemetry(paths, {
168
+ event,
169
+ job_id: jobId,
170
+ failed_summary: failedSummary,
171
+ error: {
172
+ code: classified.code,
173
+ stage: classified.stage,
174
+ message: classified.message,
175
+ details: classified.details,
176
+ },
177
+ persistence_error: formatError(lastWriteError),
178
+ });
179
+ }
180
+ async function appendStuckJobTelemetry(paths, entry) {
181
+ const stuckPath = path.join(paths.pluginRoot, "stuck-jobs.jsonl");
182
+ try {
183
+ await appendFile(stuckPath, `${JSON.stringify({ ts: new Date().toISOString(), ...entry })}\n`, "utf8");
184
+ }
185
+ catch (appendError) {
186
+ process.stderr.write(`[kimi-plugin-cc] failed to append stuck job telemetry at ${stuckPath}: ${formatError(appendError)}\n`);
187
+ }
188
+ }
189
+ function sleep(ms) {
190
+ return new Promise((resolve) => {
191
+ setTimeout(resolve, ms);
192
+ });
193
+ }
194
+ /**
195
+ * Builds a descriptive message for the rare edge case where `waitForTerminalJob`
196
+ * returns a terminal job whose `final_output_path` is null (e.g. `markJobFailed`'s
197
+ * own artifact write failed). The normal background --wait failure path returns
198
+ * the rendered failure artifact via `readArtifact` — that artifact already
199
+ * carries the code, stage, and message via `renderTerminalJobArtifact`. This
200
+ * helper is belt-and-suspenders for the near-unreachable case where even the
201
+ * failure artifact could not be written. Intended for terminal job records only;
202
+ * a running record would render the generic "finished without a rendered result."
203
+ * sentence, which would be misleading.
204
+ */
205
+ export function describeMissingResult(job, workerKind = "rescue") {
206
+ const base = `Background ${workerKind} job ${job.job_id} finished without a rendered result`;
207
+ if (job.status === "failed" || job.status === "cancelled") {
208
+ const code = job.error?.code || "unknown";
209
+ const stage = job.error?.stage || "unknown";
210
+ // `||` (not `??`) intentionally: empty-string error.message or summary
211
+ // should fall through to the next fallback, not render as blank.
212
+ const detail = job.error?.message || job.summary || "no further detail";
213
+ return `${base} (${job.status}, ${code}, stage ${stage}): ${detail}`;
214
+ }
215
+ return `${base}.`;
216
+ }
@@ -0,0 +1,43 @@
1
+ import process from "node:process";
2
+ export function createCliCancellationHandlers() {
3
+ const controller = new AbortController();
4
+ let cancelling = false;
5
+ let disposed = false;
6
+ const onSignal = () => {
7
+ if (cancelling)
8
+ return;
9
+ cancelling = true;
10
+ // Abort BEFORE running through dispose — the cli-client may still be
11
+ // in its pre-spawn signal check or its post-spawn wait, both of which
12
+ // honor signal.aborted.
13
+ try {
14
+ controller.abort();
15
+ }
16
+ catch {
17
+ // AbortController.abort() can't throw in standard runtimes, but
18
+ // belt-and-suspenders for runtime quirks (e.g. test harnesses
19
+ // overriding global AbortController).
20
+ }
21
+ };
22
+ // Use `process.on` (not `once`) so dispose() can remove the listener
23
+ // via the function reference. `process.once` wraps the callback
24
+ // internally and depending on Node version removeListener may miss it.
25
+ // Idempotency comes from the `if (cancelling) return` guard.
26
+ process.on("SIGTERM", onSignal);
27
+ process.on("SIGINT", onSignal);
28
+ return {
29
+ get cancelling() {
30
+ return cancelling;
31
+ },
32
+ get signal() {
33
+ return controller.signal;
34
+ },
35
+ dispose() {
36
+ if (disposed)
37
+ return;
38
+ disposed = true;
39
+ process.off("SIGTERM", onSignal);
40
+ process.off("SIGINT", onSignal);
41
+ },
42
+ };
43
+ }