kimi-plugin-cc 2.4.5 → 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 (109) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +14 -0
  4. package/README.md +42 -26
  5. package/dist/dsh/companion.js +174 -0
  6. package/dist/dsh/index.js +156 -0
  7. package/dist/hooks/install-paths.js +2 -2
  8. package/dist/version.js +1 -1
  9. package/docs/models.md +5 -1
  10. package/docs/registry-releases.md +20 -17
  11. package/docs/safety.md +22 -9
  12. package/docs/upstream-2.4.1-certification.md +6 -0
  13. package/package.json +40 -6
  14. package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
  15. package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
  16. package/plugins/kimi-codex/dist/dsh/index.js +156 -0
  17. package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
  18. package/plugins/kimi-codex/dist/version.js +1 -1
  19. package/plugins/kimi-dsh/LICENSE +201 -0
  20. package/plugins/kimi-dsh/README.md +80 -0
  21. package/plugins/kimi-dsh/cordis.patch.yml +45 -0
  22. package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
  23. package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
  24. package/plugins/kimi-dsh/dist/cli-client.js +876 -0
  25. package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
  26. package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
  27. package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
  28. package/plugins/kimi-dsh/dist/commands/models.js +140 -0
  29. package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
  30. package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
  31. package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
  32. package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
  33. package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
  34. package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
  35. package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
  36. package/plugins/kimi-dsh/dist/commands/result.js +66 -0
  37. package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
  38. package/plugins/kimi-dsh/dist/commands/review.js +220 -0
  39. package/plugins/kimi-dsh/dist/commands/setup.js +1047 -0
  40. package/plugins/kimi-dsh/dist/commands/status.js +27 -0
  41. package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
  42. package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
  43. package/plugins/kimi-dsh/dist/companion.js +97 -0
  44. package/plugins/kimi-dsh/dist/config.js +22 -0
  45. package/plugins/kimi-dsh/dist/dsh/companion.js +174 -0
  46. package/plugins/kimi-dsh/dist/dsh/index.js +156 -0
  47. package/plugins/kimi-dsh/dist/errors.js +40 -0
  48. package/plugins/kimi-dsh/dist/git.js +224 -0
  49. package/plugins/kimi-dsh/dist/help.js +43 -0
  50. package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
  51. package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
  52. package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
  53. package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
  54. package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
  55. package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
  56. package/plugins/kimi-dsh/dist/hooks/managed-block.js +619 -0
  57. package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
  58. package/plugins/kimi-dsh/dist/job-id.js +12 -0
  59. package/plugins/kimi-dsh/dist/job-store.js +659 -0
  60. package/plugins/kimi-dsh/dist/jobs.js +217 -0
  61. package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
  62. package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
  63. package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
  64. package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
  65. package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
  66. package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
  67. package/plugins/kimi-dsh/dist/logging.js +22 -0
  68. package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
  69. package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
  70. package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
  71. package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
  72. package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
  73. package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
  74. package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
  75. package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
  76. package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
  77. package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
  78. package/plugins/kimi-dsh/dist/model-command.js +32 -0
  79. package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
  80. package/plugins/kimi-dsh/dist/package.json +1 -0
  81. package/plugins/kimi-dsh/dist/parsing.js +620 -0
  82. package/plugins/kimi-dsh/dist/paths.js +27 -0
  83. package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
  84. package/plugins/kimi-dsh/dist/process-tree.js +479 -0
  85. package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
  86. package/plugins/kimi-dsh/dist/render.js +177 -0
  87. package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
  88. package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
  89. package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
  90. package/plugins/kimi-dsh/dist/session-title.js +293 -0
  91. package/plugins/kimi-dsh/dist/stream-json.js +346 -0
  92. package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
  93. package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
  94. package/plugins/kimi-dsh/dist/types.js +1 -0
  95. package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
  96. package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
  97. package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
  98. package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
  99. package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
  100. package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
  101. package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
  102. package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
  103. package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
  104. package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
  105. package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
  106. package/plugins/kimi-dsh/dist/version.js +7 -0
  107. package/plugins/kimi-dsh/package.json +48 -0
  108. package/plugins/kimi-dsh/scripts/companion.sh +37 -0
  109. package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,217 @@
1
+ import { createHash } from "node:crypto";
2
+ import { RuntimeError } from "./errors.js";
3
+ import { formatBudgetDuration, renderTerminalJobArtifact, writeArtifact } from "./render.js";
4
+ export function digestPrompt(prompt) {
5
+ return createHash("sha256").update(prompt).digest("hex");
6
+ }
7
+ export function normalizeJobError(error) {
8
+ if (error instanceof RuntimeError) {
9
+ const normalized = {
10
+ code: error.code,
11
+ message: error.message,
12
+ stage: error.stage,
13
+ };
14
+ if (Object.keys(error.details).length > 0) {
15
+ normalized.details = error.details;
16
+ }
17
+ return normalized;
18
+ }
19
+ if (error instanceof Error) {
20
+ return {
21
+ code: "UNEXPECTED_ERROR",
22
+ message: error.message,
23
+ stage: "runtime",
24
+ };
25
+ }
26
+ return {
27
+ code: "UNEXPECTED_ERROR",
28
+ message: String(error),
29
+ stage: "runtime",
30
+ };
31
+ }
32
+ export async function markJobFailed(store, paths, job, error, summary = "Job failed.", options) {
33
+ const current = store.getJob(job.job_id) ?? job;
34
+ if (current.status !== "running")
35
+ return current;
36
+ if (options?.partialOutput) {
37
+ const label = job.command_type[0].toUpperCase() + job.command_type.slice(1);
38
+ summary = `${label} timed out after ${formatBudgetDuration(options.partialOutput.budgetMs)}; partial output captured (${options.partialOutput.text.trim().length} chars).`;
39
+ }
40
+ const normalized = normalizeJobError(error);
41
+ const phase = options?.phase;
42
+ const artifactJob = {
43
+ ...(store.getJob(job.job_id) ?? job),
44
+ status: "failed",
45
+ summary,
46
+ error: normalized,
47
+ final_output_path: null,
48
+ pid: null,
49
+ kimi_pid: null,
50
+ ...(phase !== undefined ? { phase } : {}),
51
+ };
52
+ const artifactPath = await writeArtifact(paths, artifactJob, renderTerminalJobArtifact(artifactJob, options?.partialOutput));
53
+ return (store.markFailed(job.job_id, {
54
+ summary,
55
+ error: normalized,
56
+ final_output_path: artifactPath,
57
+ ...(phase !== undefined ? { phase } : {}),
58
+ }) ?? artifactJob);
59
+ }
60
+ export async function markJobCancelled(store, paths, job, summary, error, options) {
61
+ const current = store.getJob(job.job_id) ?? job;
62
+ if (current.status !== "running")
63
+ return current;
64
+ const normalized = error ? normalizeJobError(error) : null;
65
+ const phase = options?.phase;
66
+ const artifactJob = {
67
+ ...job,
68
+ status: "cancelled",
69
+ summary,
70
+ error: normalized,
71
+ final_output_path: null,
72
+ pid: null,
73
+ kimi_pid: null,
74
+ ...(phase !== undefined ? { phase } : {}),
75
+ };
76
+ const artifactPath = await writeArtifact(paths, artifactJob, renderTerminalJobArtifact(artifactJob));
77
+ return (store.markCancelled(job.job_id, {
78
+ summary,
79
+ error: normalized,
80
+ final_output_path: artifactPath,
81
+ ...(phase !== undefined ? { phase } : {}),
82
+ }) ?? artifactJob);
83
+ }
84
+ export async function sweepStaleJobs(store, paths) {
85
+ for (const job of store.listRunningJobsWithProcessHints()) {
86
+ if (store.getMcpTask(job.job_id))
87
+ continue;
88
+ // Recovery owns orphaned swarm resources; PID-only sweeping must not signal replacements.
89
+ if (store.getSwarmRecovery(job.job_id))
90
+ continue;
91
+ if (!isStaleEnoughToSweep(job)) {
92
+ continue;
93
+ }
94
+ const companionMissing = job.pid !== null && !isPidAlive(job.pid);
95
+ const kimiMissing = job.kimi_pid !== null && !isPidAlive(job.kimi_pid);
96
+ if (job.background) {
97
+ if (!companionMissing && !kimiMissing) {
98
+ continue;
99
+ }
100
+ terminateRecordedProcesses(job);
101
+ await markJobFailed(store, paths, job, new RuntimeError(companionMissing ? "WORKER_DISAPPEARED" : "KIMI_PROCESS_DISAPPEARED", companionMissing
102
+ ? `Background worker pid ${job.pid} is no longer running.`
103
+ : `Kimi pid ${job.kimi_pid} is no longer running while background worker pid ${job.pid} is still recorded.`, "jobs.sweep"), companionMissing
104
+ ? "Background worker disappeared before reporting a terminal state."
105
+ : "Kimi process disappeared before reporting a terminal state.", job.command_type === "ask" || job.command_type === "rescue" ? { phase: "failed" } : undefined);
106
+ continue;
107
+ }
108
+ if (!companionMissing && !kimiMissing) {
109
+ continue;
110
+ }
111
+ // Sweep-vs-live-companion race fix: if the foreground companion pid is
112
+ // still alive, the companion process is microseconds-to-seconds away
113
+ // from writing its own terminal state. SIGTERMing it here would kill
114
+ // its render path mid-flight (and from a sibling shell's read-only
115
+ // /kimi:status, no less). Skip — let the companion finish on its own.
116
+ if (!companionMissing && job.pid !== null) {
117
+ continue;
118
+ }
119
+ terminateRecordedProcesses(job);
120
+ await markJobFailed(store, paths, job, new RuntimeError("FOREGROUND_PROCESS_DISAPPEARED", describeMissingForegroundProcess(job, companionMissing, kimiMissing), "jobs.sweep"), "Foreground Kimi job disappeared before reporting a terminal state.", job.command_type === "ask" || job.command_type === "rescue" ? { phase: "failed" } : undefined);
121
+ }
122
+ }
123
+ export const STALE_SWEEP_GRACE_MS = 15_000;
124
+ function isStaleEnoughToSweep(job) {
125
+ const updatedAt = Date.parse(job.updated_at);
126
+ return Number.isFinite(updatedAt) && Date.now() - updatedAt >= STALE_SWEEP_GRACE_MS;
127
+ }
128
+ function describeMissingForegroundProcess(job, companionMissing, kimiMissing) {
129
+ if (companionMissing && kimiMissing) {
130
+ return `Foreground companion pid ${job.pid} and Kimi pid ${job.kimi_pid} are no longer running.`;
131
+ }
132
+ if (companionMissing) {
133
+ return `Foreground companion pid ${job.pid} is no longer running.`;
134
+ }
135
+ return `Kimi pid ${job.kimi_pid} is no longer running.`;
136
+ }
137
+ function terminateRecordedProcesses(job) {
138
+ const pids = new Set();
139
+ if (job.pid !== null)
140
+ pids.add(job.pid);
141
+ if (job.kimi_pid !== null)
142
+ pids.add(job.kimi_pid);
143
+ for (const pid of pids) {
144
+ if (!isPidAlive(pid)) {
145
+ continue;
146
+ }
147
+ killPid(pid, "SIGTERM");
148
+ // SIGKILL escalation: if the process ignores SIGTERM (stuck Kimi child,
149
+ // uninterruptible sleep, signal mask), force-kill after 2s. The job is
150
+ // already determined stale at this point, so leaving a survivor running
151
+ // would just orphan resources. Timer is unref'd so it doesn't keep the
152
+ // process alive.
153
+ const timer = setTimeout(() => {
154
+ if (isPidAlive(pid)) {
155
+ killPid(pid, "SIGKILL");
156
+ }
157
+ }, 2_000);
158
+ timer.unref();
159
+ }
160
+ }
161
+ function killPid(pid, signal) {
162
+ try {
163
+ process.kill(pid, signal);
164
+ }
165
+ catch (error) {
166
+ const code = error.code;
167
+ // ESRCH: process already gone. EPERM: pid was likely reused by an
168
+ // unrelated process we don't own — silently skip.
169
+ if (code !== "ESRCH" && code !== "EPERM") {
170
+ throw error;
171
+ }
172
+ }
173
+ }
174
+ function isPidAlive(pid) {
175
+ try {
176
+ process.kill(pid, 0);
177
+ return true;
178
+ }
179
+ catch (error) {
180
+ const code = error.code;
181
+ // ESRCH: no such process — definitively dead.
182
+ // EPERM: process exists but we can't signal it. We treat this as "dead
183
+ // for our purposes" because it most likely means the pid was reused by
184
+ // an unrelated higher-privilege process. The companion stores its own
185
+ // pid in v0.2.2+; under normal usage we should always have permission
186
+ // to signal our own descendants. Returning true here lets stale jobs
187
+ // hide forever (the sweeper relies on isPidAlive=false to flip them to
188
+ // failed). signalJobProcesses and killPid already swallow EPERM
189
+ // consistently — this brings the read path in line with the write path.
190
+ if (code === "ESRCH" || code === "EPERM") {
191
+ return false;
192
+ }
193
+ return true;
194
+ }
195
+ }
196
+ export async function waitForTerminalJob(storeFactory, jobId, timeoutMs = 60_000) {
197
+ const deadline = Date.now() + timeoutMs;
198
+ while (Date.now() < deadline) {
199
+ const store = storeFactory();
200
+ try {
201
+ const job = store.getJob(jobId);
202
+ if (job && job.status !== "running") {
203
+ return job;
204
+ }
205
+ }
206
+ finally {
207
+ store.close();
208
+ }
209
+ await sleep(200);
210
+ }
211
+ throw new RuntimeError("JOB_WAIT_TIMEOUT", `Timed out after ${timeoutMs}ms while waiting for job ${jobId} to finish. The worker may still be running; use /kimi:status ${jobId} to check progress or /kimi:result ${jobId} once it completes.`, "jobs.wait");
212
+ }
213
+ function sleep(ms) {
214
+ return new Promise((resolve) => {
215
+ setTimeout(resolve, ms);
216
+ });
217
+ }
@@ -0,0 +1,70 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ export function resolveKimiCliCommand(env) {
3
+ const command = env.KIMI_PLUGIN_CC_KIMI_BIN || "kimi";
4
+ const raw = env.KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS;
5
+ if (!raw) {
6
+ return { command, prefixArgs: [] };
7
+ }
8
+ let parsed;
9
+ try {
10
+ parsed = JSON.parse(raw);
11
+ }
12
+ catch {
13
+ // Plain-text values are space-split for launcher-style overrides.
14
+ const plainArgs = raw.split(" ").filter(Boolean);
15
+ assertPrefixArgsSafe(plainArgs, raw);
16
+ return { command, prefixArgs: plainArgs };
17
+ }
18
+ if (!Array.isArray(parsed)) {
19
+ throw new RuntimeError("INVALID_ENV", "KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS must be a JSON array of strings.", "kimi-command.env", { details: { env_var: "KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS", value: raw, retryable_after_setup: false } });
20
+ }
21
+ for (const entry of parsed) {
22
+ if (typeof entry !== "string") {
23
+ throw new RuntimeError("INVALID_ENV", "KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS entries must be strings.", "kimi-command.env", { details: { env_var: "KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS", value: raw, retryable_after_setup: false } });
24
+ }
25
+ }
26
+ const prefixArgs = parsed;
27
+ assertPrefixArgsSafe(prefixArgs, raw);
28
+ return { command, prefixArgs };
29
+ }
30
+ /**
31
+ * Flags the plugin owns on the `kimi -p` argv. The prefix is only a launcher
32
+ * shim (e.g. `["run", "<companion-dist>"]`); it must never carry a kimi flag
33
+ * that changes the session, engine, plan, prompt, or output format. In
34
+ * particular a smuggled `-r <session>` would resume a session the plugin never
35
+ * scanned for plan-mode taint (the journal scan keys only on the plugin-chosen
36
+ * resumeSessionId), so it is refused here — the single resolution point reached
37
+ * by both foreground and detached-worker spawns.
38
+ */
39
+ const RESERVED_PREFIX_FLAGS = new Set([
40
+ // Session selection — incl. the HIDDEN `-C` alias of `--continue`
41
+ // (apps/kimi-code/src/cli/commands.ts), which resumes the latest session for
42
+ // the cwd without any plugin-chosen resumeSessionId, so no journal scan runs.
43
+ "-r", "--resume", "-S", "--session", "-c", "-C", "--continue",
44
+ // Prompt / output.
45
+ "-p", "--prompt", "--output-format",
46
+ // Permission / plan mode.
47
+ "--plan", "--auto", "-y", "--yolo", "--yes", "--auto-approve", "--manual",
48
+ // Model / profile / roots.
49
+ "-m", "--model", "--skills-dir", "--agent", "--agent-file", "--add-dir",
50
+ // Terminal informational flags (would replace the run with a banner).
51
+ "-V", "--version", "-h", "--help",
52
+ ]);
53
+ export function assertPrefixArgsSafe(prefixArgs, raw) {
54
+ for (const token of prefixArgs) {
55
+ // Compare the flag name only, so `--session=x` is caught as well as `-S x`.
56
+ const flag = token.startsWith("-") ? token.split("=", 1)[0] : token;
57
+ if (RESERVED_PREFIX_FLAGS.has(flag)) {
58
+ throw new RuntimeError("INVALID_ENV", `KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS must not contain the reserved kimi flag "${flag}". ` +
59
+ "The prefix is a launcher shim only; session, engine, plan, prompt, model, and " +
60
+ "output-format flags are owned by the plugin and would bypass its safety checks.", "kimi-command.env", {
61
+ details: {
62
+ env_var: "KIMI_PLUGIN_CC_KIMI_PREFIX_ARGS",
63
+ reserved_flag: flag,
64
+ value: raw,
65
+ retryable_after_setup: false,
66
+ },
67
+ });
68
+ }
69
+ }
70
+ }