kimi-plugin-cc 2.0.7

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 (190) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +16 -0
  3. package/.claude-plugin/plugin.json +32 -0
  4. package/CHANGELOG.md +1096 -0
  5. package/LICENSE +201 -0
  6. package/README.md +772 -0
  7. package/agents/kimi-ask.md +82 -0
  8. package/agents/kimi-challenge.md +82 -0
  9. package/agents/kimi-pursue.md +91 -0
  10. package/agents/kimi-rescue.md +85 -0
  11. package/agents/kimi-review.md +82 -0
  12. package/agents/kimi-swarm-write.md +92 -0
  13. package/agents/kimi-swarm.md +83 -0
  14. package/commands/README.md +16 -0
  15. package/commands/ask.md +32 -0
  16. package/commands/cancel.md +17 -0
  17. package/commands/challenge.md +29 -0
  18. package/commands/pursue.md +37 -0
  19. package/commands/replay.md +13 -0
  20. package/commands/rescue.md +32 -0
  21. package/commands/result.md +13 -0
  22. package/commands/review.md +29 -0
  23. package/commands/setup.md +23 -0
  24. package/commands/status.md +13 -0
  25. package/commands/swarm.md +38 -0
  26. package/dist/background-spawn.js +224 -0
  27. package/dist/cli-cancellation.js +43 -0
  28. package/dist/cli-client.js +845 -0
  29. package/dist/commands/ask.js +327 -0
  30. package/dist/commands/cancel.js +178 -0
  31. package/dist/commands/cli-helpers.js +112 -0
  32. package/dist/commands/models.js +140 -0
  33. package/dist/commands/not-implemented.js +4 -0
  34. package/dist/commands/pursue.js +311 -0
  35. package/dist/commands/registry.js +87 -0
  36. package/dist/commands/repair-sessions.js +159 -0
  37. package/dist/commands/replay.js +123 -0
  38. package/dist/commands/rescue.js +333 -0
  39. package/dist/commands/result.js +58 -0
  40. package/dist/commands/review-gate.js +393 -0
  41. package/dist/commands/review.js +218 -0
  42. package/dist/commands/setup.js +1132 -0
  43. package/dist/commands/status.js +27 -0
  44. package/dist/commands/swarm.js +576 -0
  45. package/dist/commands/worker.js +31 -0
  46. package/dist/companion.js +114 -0
  47. package/dist/config.js +22 -0
  48. package/dist/errors.js +40 -0
  49. package/dist/git.js +215 -0
  50. package/dist/hooks/approval-hook.js +112 -0
  51. package/dist/hooks/approval-policy.js +254 -0
  52. package/dist/hooks/config-safety.js +637 -0
  53. package/dist/hooks/install-paths.js +504 -0
  54. package/dist/hooks/install.js +281 -0
  55. package/dist/hooks/managed-block.js +745 -0
  56. package/dist/hooks/review-gate-stop.js +19 -0
  57. package/dist/job-store.js +559 -0
  58. package/dist/jobs.js +202 -0
  59. package/dist/kimi-command.js +72 -0
  60. package/dist/kimi-engine.js +624 -0
  61. package/dist/kimi-errors.js +220 -0
  62. package/dist/kimi-home.js +9 -0
  63. package/dist/kimi-timeouts.js +68 -0
  64. package/dist/kimi-version-probe.js +1410 -0
  65. package/dist/logging.js +22 -0
  66. package/dist/native-v2-preflight.js +417 -0
  67. package/dist/package.json +1 -0
  68. package/dist/parsing.js +553 -0
  69. package/dist/paths.js +27 -0
  70. package/dist/plugin-data.js +98 -0
  71. package/dist/process-tree.js +448 -0
  72. package/dist/render.js +159 -0
  73. package/dist/rescue-approval.js +951 -0
  74. package/dist/schemas/review-gate-output.js +43 -0
  75. package/dist/session-title.js +293 -0
  76. package/dist/stream-json.js +498 -0
  77. package/dist/types.js +1 -0
  78. package/dist/vendor/shell-quote/LICENSE +24 -0
  79. package/dist/vendor/shell-quote/index.js +1 -0
  80. package/dist/vendor/shell-quote/parse.js +229 -0
  81. package/dist/vendor/smol-toml/LICENSE +24 -0
  82. package/dist/vendor/smol-toml/date.js +127 -0
  83. package/dist/vendor/smol-toml/error.js +63 -0
  84. package/dist/vendor/smol-toml/extract.js +100 -0
  85. package/dist/vendor/smol-toml/parse.js +148 -0
  86. package/dist/vendor/smol-toml/primitive.js +179 -0
  87. package/dist/vendor/smol-toml/struct.js +184 -0
  88. package/dist/vendor/smol-toml/util.js +113 -0
  89. package/dist/version.js +7 -0
  90. package/docs/migration.md +215 -0
  91. package/docs/models.md +82 -0
  92. package/docs/registry-releases.md +70 -0
  93. package/docs/safety.md +318 -0
  94. package/docs/session-visibility.md +32 -0
  95. package/hooks/README.md +9 -0
  96. package/hooks/hooks.json +16 -0
  97. package/package.json +67 -0
  98. package/plugins/kimi-codex/.codex-plugin/plugin.json +39 -0
  99. package/plugins/kimi-codex/dist/background-spawn.js +224 -0
  100. package/plugins/kimi-codex/dist/cli-cancellation.js +43 -0
  101. package/plugins/kimi-codex/dist/cli-client.js +845 -0
  102. package/plugins/kimi-codex/dist/commands/ask.js +327 -0
  103. package/plugins/kimi-codex/dist/commands/cancel.js +178 -0
  104. package/plugins/kimi-codex/dist/commands/cli-helpers.js +112 -0
  105. package/plugins/kimi-codex/dist/commands/models.js +140 -0
  106. package/plugins/kimi-codex/dist/commands/not-implemented.js +4 -0
  107. package/plugins/kimi-codex/dist/commands/pursue.js +311 -0
  108. package/plugins/kimi-codex/dist/commands/registry.js +87 -0
  109. package/plugins/kimi-codex/dist/commands/repair-sessions.js +159 -0
  110. package/plugins/kimi-codex/dist/commands/replay.js +123 -0
  111. package/plugins/kimi-codex/dist/commands/rescue.js +333 -0
  112. package/plugins/kimi-codex/dist/commands/result.js +58 -0
  113. package/plugins/kimi-codex/dist/commands/review-gate.js +393 -0
  114. package/plugins/kimi-codex/dist/commands/review.js +218 -0
  115. package/plugins/kimi-codex/dist/commands/setup.js +1132 -0
  116. package/plugins/kimi-codex/dist/commands/status.js +27 -0
  117. package/plugins/kimi-codex/dist/commands/swarm.js +576 -0
  118. package/plugins/kimi-codex/dist/commands/worker.js +31 -0
  119. package/plugins/kimi-codex/dist/companion.js +114 -0
  120. package/plugins/kimi-codex/dist/config.js +22 -0
  121. package/plugins/kimi-codex/dist/errors.js +40 -0
  122. package/plugins/kimi-codex/dist/git.js +215 -0
  123. package/plugins/kimi-codex/dist/hooks/approval-hook.js +112 -0
  124. package/plugins/kimi-codex/dist/hooks/approval-policy.js +254 -0
  125. package/plugins/kimi-codex/dist/hooks/config-safety.js +637 -0
  126. package/plugins/kimi-codex/dist/hooks/install-paths.js +504 -0
  127. package/plugins/kimi-codex/dist/hooks/install.js +281 -0
  128. package/plugins/kimi-codex/dist/hooks/managed-block.js +745 -0
  129. package/plugins/kimi-codex/dist/hooks/review-gate-stop.js +19 -0
  130. package/plugins/kimi-codex/dist/job-store.js +559 -0
  131. package/plugins/kimi-codex/dist/jobs.js +202 -0
  132. package/plugins/kimi-codex/dist/kimi-command.js +72 -0
  133. package/plugins/kimi-codex/dist/kimi-engine.js +624 -0
  134. package/plugins/kimi-codex/dist/kimi-errors.js +220 -0
  135. package/plugins/kimi-codex/dist/kimi-home.js +9 -0
  136. package/plugins/kimi-codex/dist/kimi-timeouts.js +68 -0
  137. package/plugins/kimi-codex/dist/kimi-version-probe.js +1410 -0
  138. package/plugins/kimi-codex/dist/logging.js +22 -0
  139. package/plugins/kimi-codex/dist/native-v2-preflight.js +417 -0
  140. package/plugins/kimi-codex/dist/package.json +1 -0
  141. package/plugins/kimi-codex/dist/parsing.js +553 -0
  142. package/plugins/kimi-codex/dist/paths.js +27 -0
  143. package/plugins/kimi-codex/dist/plugin-data.js +98 -0
  144. package/plugins/kimi-codex/dist/process-tree.js +448 -0
  145. package/plugins/kimi-codex/dist/render.js +159 -0
  146. package/plugins/kimi-codex/dist/rescue-approval.js +951 -0
  147. package/plugins/kimi-codex/dist/schemas/review-gate-output.js +43 -0
  148. package/plugins/kimi-codex/dist/session-title.js +293 -0
  149. package/plugins/kimi-codex/dist/stream-json.js +498 -0
  150. package/plugins/kimi-codex/dist/types.js +1 -0
  151. package/plugins/kimi-codex/dist/vendor/shell-quote/LICENSE +24 -0
  152. package/plugins/kimi-codex/dist/vendor/shell-quote/index.js +1 -0
  153. package/plugins/kimi-codex/dist/vendor/shell-quote/parse.js +229 -0
  154. package/plugins/kimi-codex/dist/vendor/smol-toml/LICENSE +24 -0
  155. package/plugins/kimi-codex/dist/vendor/smol-toml/date.js +127 -0
  156. package/plugins/kimi-codex/dist/vendor/smol-toml/error.js +63 -0
  157. package/plugins/kimi-codex/dist/vendor/smol-toml/extract.js +100 -0
  158. package/plugins/kimi-codex/dist/vendor/smol-toml/parse.js +148 -0
  159. package/plugins/kimi-codex/dist/vendor/smol-toml/primitive.js +179 -0
  160. package/plugins/kimi-codex/dist/vendor/smol-toml/struct.js +184 -0
  161. package/plugins/kimi-codex/dist/vendor/smol-toml/util.js +113 -0
  162. package/plugins/kimi-codex/dist/version.js +7 -0
  163. package/plugins/kimi-codex/scripts/companion.sh +37 -0
  164. package/plugins/kimi-codex/scripts/review-gate-hook.sh +32 -0
  165. package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +31 -0
  166. package/plugins/kimi-codex/skills/kimi-ask/agents/openai.yaml +6 -0
  167. package/plugins/kimi-codex/skills/kimi-cancel/SKILL.md +25 -0
  168. package/plugins/kimi-codex/skills/kimi-cancel/agents/openai.yaml +6 -0
  169. package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +31 -0
  170. package/plugins/kimi-codex/skills/kimi-challenge/agents/openai.yaml +6 -0
  171. package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +30 -0
  172. package/plugins/kimi-codex/skills/kimi-pursue/agents/openai.yaml +6 -0
  173. package/plugins/kimi-codex/skills/kimi-replay/SKILL.md +25 -0
  174. package/plugins/kimi-codex/skills/kimi-replay/agents/openai.yaml +6 -0
  175. package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +30 -0
  176. package/plugins/kimi-codex/skills/kimi-rescue/agents/openai.yaml +6 -0
  177. package/plugins/kimi-codex/skills/kimi-result/SKILL.md +25 -0
  178. package/plugins/kimi-codex/skills/kimi-result/agents/openai.yaml +6 -0
  179. package/plugins/kimi-codex/skills/kimi-review/SKILL.md +31 -0
  180. package/plugins/kimi-codex/skills/kimi-review/agents/openai.yaml +6 -0
  181. package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +32 -0
  182. package/plugins/kimi-codex/skills/kimi-setup/agents/openai.yaml +6 -0
  183. package/plugins/kimi-codex/skills/kimi-status/SKILL.md +25 -0
  184. package/plugins/kimi-codex/skills/kimi-status/agents/openai.yaml +6 -0
  185. package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +31 -0
  186. package/plugins/kimi-codex/skills/kimi-swarm/agents/openai.yaml +6 -0
  187. package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +32 -0
  188. package/plugins/kimi-codex/skills/kimi-swarm-write/agents/openai.yaml +6 -0
  189. package/scripts/companion.sh +37 -0
  190. package/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,27 @@
1
+ import { RuntimeError } from "../errors.js";
2
+ import { resolveRepoIdentity } from "../git.js";
3
+ import { sweepStaleJobs } from "../jobs.js";
4
+ import { reconcileHistoricalJobProvenance } from "../kimi-engine.js";
5
+ import { withJobStore } from "../job-store.js";
6
+ import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
7
+ import { parseJobLookupArgs } from "../parsing.js";
8
+ export async function runStatus(argv, context) {
9
+ const parsed = parseJobLookupArgs(argv);
10
+ const paths = resolvePluginPaths(context.env);
11
+ await ensurePluginPaths(paths);
12
+ const repoIdentity = await resolveRepoIdentity(context.cwd);
13
+ return withJobStore(paths, async (store) => {
14
+ await sweepStaleJobs(store, paths);
15
+ const found = parsed.jobId
16
+ ? store.getJob(parsed.jobId)
17
+ : store.findLatestJob({
18
+ repoId: repoIdentity.repoId,
19
+ commandType: parsed.type,
20
+ });
21
+ if (!found) {
22
+ throw new RuntimeError("JOB_NOT_FOUND", "No matching job was found for status.", "status.lookup");
23
+ }
24
+ const job = await reconcileHistoricalJobProvenance(store, found);
25
+ return `${JSON.stringify(job, null, 2)}\n`;
26
+ });
27
+ }
@@ -0,0 +1,576 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createCliCancellationHandlers } from "../cli-cancellation.js";
5
+ import { runCliPromptWithBudget } from "../cli-client.js";
6
+ import { executionPlanFromPersisted, observedExecutionFields, persistedExecutionPlanFields, prepareKimiExecutionPlan, } from "../kimi-engine.js";
7
+ import { RuntimeError } from "../errors.js";
8
+ import { captureWorktreePatch, createEphemeralWorktree, hasBornHead, isWorkingTreeDirty, pruneWorktrees, removeWorktree, resolveRepoIdentity, } from "../git.js";
9
+ import { digestPrompt, markJobCancelled, markJobFailed, sweepStaleJobs } from "../jobs.js";
10
+ import { JobStore } from "../job-store.js";
11
+ import { classifyManagedCommandFailure } from "../kimi-errors.js";
12
+ import { KIMI_SWARM_DEFAULT_BUDGET_MS } from "../kimi-timeouts.js";
13
+ import { writeInvocationLogHeader } from "../logging.js";
14
+ import { ensurePluginPaths, resolvePluginPaths } from "../paths.js";
15
+ import { MAX_DURATION_MS, parseSwarmArgs } from "../parsing.js";
16
+ import { readArtifact, renderManagedJobOutput, writeArtifact } from "../render.js";
17
+ import { hookRefusalDetails, hookRefusalRetryProtocol, maybeWarnHookMissing, verifyHookInstalled, } from "../hooks/install.js";
18
+ import { assertCliResultSuccess, reassembleProseFromRecords, warnIfSessionIdMissing } from "./cli-helpers.js";
19
+ import { buildKimiSessionTitle, syncKimiSessionTitle } from "../session-title.js";
20
+ // /kimi:swarm — READ-ONLY parallel fan-out (kimi-code 0.12.0 AgentSwarm tool).
21
+ //
22
+ // PROTOTYPE SCOPE (v1.2). The lowest-risk swarm shape: parallel review/analysis
23
+ // over N targets, with NO write surface. Deliberately narrow:
24
+ // - FOREGROUND ONLY. No --background/--detach. A hard wall-clock budget
25
+ // (--budget) bounds the whole run. Two distinct subagent bounds: --cap is a
26
+ // SOFT total-count hint injected into the prompt, and --max-concurrency is a
27
+ // HARD ceiling on how many subagents run AT ONCE via
28
+ // KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY on kimi-code 0.18.0+ (PR #888; older
29
+ // binaries ignore the unknown env var).
30
+ // - READ-ONLY. The PreToolUse hook runs under the "swarm" label, which
31
+ // allowlists the read-only tool set PLUS the AgentSwarm tool. Every
32
+ // subagent inherits that label and fires the SAME hook at policy index 0
33
+ // (kimi-code createPermissionDecisionPolicies puts the hook at index 0 for
34
+ // ALL agents — verified against 0.12.0), so a subagent's Write/Edit/Bash is
35
+ // denied exactly like a single-turn review's.
36
+ // - Reuses the REVIEW job lineage (command_type "review") so /kimi:status /
37
+ // /kimi:result / /kimi:cancel work unchanged. The hook label ("swarm") is
38
+ // independent of the job lineage — mirrors how /kimi:pursue reuses the
39
+ // rescue lineage with its own label. Promoting swarm to a first-class
40
+ // command_type is a follow-up.
41
+ //
42
+ // SAFETY — swarm REFUSES without the hook, like every model-spawning command.
43
+ // A swarm fans out up to `cap` subagents, each capable of attempting writes;
44
+ // without the index-0 hook there is ZERO enforcement on ALL of them — an
45
+ // N-fold blast radius. KIMI_PLUGIN_CC_SKIP_HOOK_CHECK remains an explicit,
46
+ // tests/diagnostics-only bypass of that refusal.
47
+ const SWARM_SUMMARY_MAX = 120;
48
+ const SWARM_AGENT_PROFILE = "<swarm>";
49
+ const SWARM_WRITE_AGENT_PROFILE = "<swarm-write>";
50
+ /**
51
+ * Orphaned write-swarm worktrees older than this are reaped by the startup sweep.
52
+ * Every normal run removes its own worktree in a finally; an orphan only survives
53
+ * a hard kill (SIGKILL of the plugin process).
54
+ *
55
+ * The sweep's only liveness signal is the worktree dir's mtime, which is frozen
56
+ * at `git worktree add` time when a run edits only EXISTING files (in-place edits
57
+ * never bump the containing dir's mtime). A live run can therefore look "idle" for
58
+ * its entire wall-clock life. So the TTL MUST exceed the longest a run can live —
59
+ * the hard `--budget` ceiling, `MAX_DURATION_MS` (24h) — or a concurrent run's
60
+ * sweep could `git worktree remove --force` a still-live worktree out from under
61
+ * its owner (ENOENT writes, lost patch). The +2h margin covers budget-expiry
62
+ * teardown (AbortController fires → the finally removes the worktree well within
63
+ * it). Orphans are still reaped, just after this longer, provably-safe delay.
64
+ */
65
+ export const WORKTREE_ORPHAN_TTL_MS = MAX_DURATION_MS + 2 * 60 * 60 * 1000;
66
+ /**
67
+ * Default HARD concurrency ceiling applied when the user passes no
68
+ * `--max-concurrency`. Since v1.3 `/kimi:swarm` is ALSO reachable via the
69
+ * model-invocable `kimi-swarm` agent (Claude can auto-dispatch a fan-out), so an
70
+ * unbounded peak-parallelism default is no longer acceptable: we always export
71
+ * `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY` (effective on kimi-code 0.18.0+; older
72
+ * binaries ignore it) so simultaneous model spend is capped BY CONSTRUCTION for
73
+ * every swarm run — agent-dispatched or human-typed. Conservative by design;
74
+ * override with an explicit `--max-concurrency N`. The `--budget` wall-clock
75
+ * ceiling remains the always-on hard bound on TOTAL cost; this bounds the peak.
76
+ */
77
+ export const SWARM_DEFAULT_MAX_CONCURRENCY = 4;
78
+ /**
79
+ * Resolve the effective hard concurrency ceiling: an explicit `--max-concurrency`
80
+ * wins; otherwise fall back to SWARM_DEFAULT_MAX_CONCURRENCY so the ceiling is
81
+ * never unset. Pure + exported for unit testing (the parser leaves the value
82
+ * undefined; swarm.ts owns the default, mirroring `budgetMs`).
83
+ */
84
+ export function resolveSwarmMaxConcurrency(requested) {
85
+ return requested ?? SWARM_DEFAULT_MAX_CONCURRENCY;
86
+ }
87
+ /**
88
+ * Default HARD concurrency ceiling for WRITE mode — 1 (serialized) vs read's 4.
89
+ * Writes are riskier than reads: disjoint-target partitioning is prompt-only and
90
+ * unenforceable (the hook is stateless), so two concurrent `coder` subagents
91
+ * racing the same file in the shared worktree would corrupt the patch. Serialize
92
+ * by default until a real-binary smoke proves clean concurrent disjoint writes;
93
+ * override with an explicit `--max-concurrency N`. Effective only on kimi-code
94
+ * 0.18.0+ (the write-mode version gate), so the ceiling is always enforced.
95
+ */
96
+ export const SWARM_WRITE_DEFAULT_MAX_CONCURRENCY = 1;
97
+ /** Resolve the write-mode concurrency ceiling: explicit wins, else default 1. */
98
+ export function resolveSwarmWriteMaxConcurrency(requested) {
99
+ return requested ?? SWARM_WRITE_DEFAULT_MAX_CONCURRENCY;
100
+ }
101
+ /**
102
+ * Build the swarm coordination prompt. States the READ-ONLY review GOAL and the
103
+ * desired report shape, then trusts the model to fan the work out with its own
104
+ * subagent capability — we deliberately do NOT prescribe the AgentSwarm tool call,
105
+ * subagent_type, or prompt_template/items mechanics. Over-prescribing those made
106
+ * the kimi-code 0.26.0 (k3) coordinator emit a non-sole-tool-call response that
107
+ * failed to launch (0 records, hang to budget); a goal-only prompt lets it launch.
108
+ * Read-only is NOT enforced by this prose — the PreToolUse hook (swarm label) at
109
+ * policy index 0 denies every write/edit/shell for the coordinator AND every
110
+ * subagent, so slimming this text does not move the safety boundary. The optional
111
+ * `cap` is a SOFT subagent-count hint (the hook cannot enforce a count). The HARD
112
+ * concurrency ceiling is separate: --max-concurrency →
113
+ * KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY (see executeSwarmJob / cli-client buildEnv).
114
+ * The --budget wall-clock ceiling is the always-on hard bound on the whole run.
115
+ */
116
+ export function buildSwarmPrompt(objective, cap) {
117
+ const trimmed = objective.trim();
118
+ const capClause = cap !== undefined ? `Use at most ${cap} parallel subagents.` : undefined;
119
+ return [
120
+ "Review the following read-only: inspect and report; do not modify anything.",
121
+ "",
122
+ `Objective: ${trimmed}`,
123
+ "",
124
+ "Only Read, Grep, and Glob are available in this mode — shell/Bash and every write/edit tool",
125
+ "are denied. Launch the parallel fan-out as your FIRST action; do NOT run preliminary shell",
126
+ "commands to size or list files first (the subagents inspect the workspace directly with",
127
+ "Read/Grep/Glob).",
128
+ "Fan the work out across the relevant targets in parallel using your own subagent capability,",
129
+ "as you judge best — split or group the work however fits the objective.",
130
+ ...(capClause ? [capClause] : []),
131
+ "",
132
+ "Consolidate everything into a single markdown report: a short verdict line, a brief summary,",
133
+ "then findings as `file:line — issue — severity`. Return plain markdown — no JSON wrapper, no",
134
+ "outer code fences.",
135
+ ].join("\n");
136
+ }
137
+ /**
138
+ * Build the WRITE-mode coordination prompt. Unlike the read-only prompt, this
139
+ * sets subagent_type="coder" (write-capable) and instructs DISJOINT one-target-
140
+ * per-subagent editing with NO git and NO nested swarm. The coordinator and all
141
+ * subagents run in an ephemeral throwaway worktree; the PreToolUse hook
142
+ * (swarm-write label) confines writes there and the plugin captures a patch the
143
+ * user reviews. The `cap` is the same SOFT total-count hint as read mode.
144
+ */
145
+ export function buildSwarmWritePrompt(objective, cap) {
146
+ const trimmed = objective.trim();
147
+ const capClause = cap !== undefined
148
+ ? `Launch at most ${cap} subagents (soft cap — split or group targets to stay within it).`
149
+ : "Launch one subagent per distinct target; group related targets if there are many.";
150
+ return [
151
+ "Coordinate a parallel CODE-EDITING task using the AgentSwarm tool.",
152
+ "",
153
+ `Objective: ${trimmed}`,
154
+ "",
155
+ "You are running inside a throwaway git worktree dedicated to this task — edit freely;",
156
+ "your changes will be captured as a patch for the human to review and apply. Use the",
157
+ 'AgentSwarm tool to fan the work out across subagents. Set subagent_type to "coder" (the',
158
+ "write-capable profile). Give each subagent ONE distinct, DISJOINT target via prompt_template",
159
+ "+ items: a single file or module per subagent, with NO overlap between subagents (two",
160
+ "subagents editing the same file will corrupt the result).",
161
+ capClause,
162
+ "",
163
+ "Hard rules for every subagent (the safety hook enforces these — violations are denied):",
164
+ "- Edit ONLY your assigned target. Writes are confined to this worktree; an out-of-worktree",
165
+ " write is denied.",
166
+ "- Do NOT run any git command that mutates state (no add/commit/checkout/etc.) — git mutation",
167
+ " is denied. The human owns all git operations.",
168
+ "- Do NOT call AgentSwarm yourself (no nested fan-out). One flat generation only.",
169
+ "- Report exactly what you changed and why.",
170
+ "",
171
+ "After the subagents return, consolidate into a single markdown report: a short verdict line,",
172
+ "a summary of what changed across all targets, then one section per target listing the edited",
173
+ "files. Do NOT paste full diffs (the patch is captured separately). Return plain markdown —",
174
+ "no JSON wrapper, no outer code fences.",
175
+ ].join("\n");
176
+ }
177
+ export async function runSwarm(argv, context) {
178
+ const parsed = parseSwarmArgs(argv);
179
+ const objective = parsed.objective?.trim();
180
+ if (!objective) {
181
+ throw new RuntimeError("INVALID_ARGS", "/kimi:swarm requires an objective. Usage: /kimi:swarm [--write] [--budget 30m] [--cap N] [--max-concurrency N] [-m model] <objective>", "swarm.parse");
182
+ }
183
+ return parsed.write
184
+ ? runWriteSwarm(parsed, objective, context)
185
+ : runReadSwarm(parsed, objective, context);
186
+ }
187
+ /** READ-ONLY swarm (default). Unchanged from v1.2/v1.3. */
188
+ async function runReadSwarm(parsed, objective, context) {
189
+ const paths = resolvePluginPaths(context.env);
190
+ await ensurePluginPaths(paths);
191
+ const repoIdentity = await resolveRepoIdentity(context.cwd);
192
+ const store = new JobStore(paths);
193
+ try {
194
+ await sweepStaleJobs(store, paths);
195
+ const executionPlan = await prepareKimiExecutionPlan({
196
+ operationKind: "swarm",
197
+ cwd: context.cwd,
198
+ env: context.env,
199
+ });
200
+ const prompt = buildSwarmPrompt(objective, parsed.cap);
201
+ const jobId = randomUUID();
202
+ const logPath = path.join(paths.logsDir, `swarm-${jobId}.jsonl`);
203
+ const job = store.createJob({
204
+ job_id: jobId,
205
+ repo_id: repoIdentity.repoId,
206
+ // Reuse the read-only review lineage; the hook label below ("swarm") is
207
+ // what actually drives the allowlist.
208
+ command_type: "review",
209
+ cwd: context.cwd,
210
+ model: parsed.model ?? null,
211
+ thinking: parsed.thinking ?? null,
212
+ background: false,
213
+ pid: process.pid,
214
+ kimi_pid: null,
215
+ status: "running",
216
+ kimi_session_id: null,
217
+ ...persistedExecutionPlanFields(executionPlan),
218
+ agent_profile: SWARM_AGENT_PROFILE,
219
+ prompt_digest: digestPrompt(prompt),
220
+ summary: `[swarm] ${shorten(objective, SWARM_SUMMARY_MAX)}`,
221
+ phase: "starting",
222
+ final_output_path: null,
223
+ stream_log_path: logPath,
224
+ error: null,
225
+ });
226
+ try {
227
+ await writeInvocationLogHeader(logPath, {
228
+ commandType: "swarm",
229
+ kimiSessionId: "(pending)",
230
+ cwd: context.cwd,
231
+ });
232
+ }
233
+ catch (error) {
234
+ const classified = new RuntimeError("SWARM_LOG_HEADER_FAILED", `Failed to write swarm invocation log header: ${error.message ?? String(error)}`, "swarm.log-header", error instanceof Error ? { cause: error } : undefined);
235
+ await markJobFailed(store, paths, job, classified, "Swarm failed.", { phase: "failed" });
236
+ throw classified;
237
+ }
238
+ const completed = await executeSwarmJob(job.job_id, prompt, objective, parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS, resolveSwarmMaxConcurrency(parsed.maxConcurrency), context);
239
+ if (!completed.final_output_path) {
240
+ throw new RuntimeError("SWARM_RESULT_MISSING", "Swarm finished without a rendered result.", "swarm.result");
241
+ }
242
+ return readArtifact(completed.final_output_path);
243
+ }
244
+ finally {
245
+ store.close();
246
+ }
247
+ }
248
+ /**
249
+ * WRITE-capable swarm (--write, v1.4). Spawns the coordinator in an ephemeral
250
+ * throwaway worktree off HEAD; `coder` subagents edit there; writes are confined
251
+ * by the swarm-write hook label; the result is captured as a patch the main
252
+ * thread reviews + applies. The worktree is created here and removed on EVERY
253
+ * terminal path (the patch is captured inside executeSwarmJob, before this
254
+ * finally removes the tree). Reuses the rescue (write-capable) job lineage.
255
+ */
256
+ async function runWriteSwarm(parsed, objective, context) {
257
+ const paths = resolvePluginPaths(context.env);
258
+ await ensurePluginPaths(paths);
259
+ const repoIdentity = await resolveRepoIdentity(context.cwd);
260
+ const store = new JobStore(paths);
261
+ try {
262
+ await sweepStaleJobs(store, paths);
263
+ // Write mode needs a real git repo and a born HEAD before any job state is
264
+ // created. Its execution plan is certified below from the actual throwaway
265
+ // worktree cwd, not from the user's tree.
266
+ assertWriteSwarmPreconditions(repoIdentity);
267
+ if (!(await hasBornHead(context.cwd))) {
268
+ throw new RuntimeError("WRITE_SWARM_NO_HEAD", "/kimi:swarm --write needs a git repository with at least one commit (HEAD). This repo has an unborn HEAD; make an initial commit first.", "swarm.precondition");
269
+ }
270
+ await sweepStaleWorktrees(paths, repoIdentity);
271
+ if (await isWorkingTreeDirty(context.cwd)) {
272
+ context.stderr.write("[kimi-plugin-cc] /kimi:swarm --write bases its worktree on HEAD — your uncommitted changes are NOT visible to the swarm. Commit or stash them first if the swarm needs them.\n");
273
+ }
274
+ const prompt = buildSwarmWritePrompt(objective, parsed.cap);
275
+ const jobId = randomUUID();
276
+ const logPath = path.join(paths.logsDir, `swarm-write-${jobId}.jsonl`);
277
+ // Namespace worktrees per repo so the cross-repo sweep below only ever
278
+ // touches THIS repo's subtree (the worktreesDir is a single global dir shared
279
+ // across every repo the user runs --write in).
280
+ const worktreePath = path.join(paths.worktreesDir, repoIdentity.repoId, `swarm-write-${jobId}`);
281
+ await mkdir(path.dirname(worktreePath), { recursive: true });
282
+ try {
283
+ await createEphemeralWorktree(repoIdentity.repoRoot, "HEAD", worktreePath);
284
+ }
285
+ catch (error) {
286
+ throw (error instanceof RuntimeError
287
+ ? error
288
+ : new RuntimeError("GIT_WORKTREE_ADD_FAILED", String(error), "swarm.worktree"));
289
+ }
290
+ try {
291
+ // Prefix args can contain relative wrapper/script paths. Probe from the
292
+ // exact spawn cwd so the certified command tuple and its effective target
293
+ // cannot diverge merely because --write runs in a separate worktree.
294
+ const executionPlan = await prepareKimiExecutionPlan({
295
+ operationKind: "swarm-write",
296
+ cwd: worktreePath,
297
+ env: context.env,
298
+ });
299
+ const job = store.createJob({
300
+ job_id: jobId,
301
+ repo_id: repoIdentity.repoId,
302
+ // Reuse the rescue (write-capable) lineage, like pursue; the hook label
303
+ // below ("swarm-write") drives the allowlist. cwd stays the USER's real
304
+ // cwd for lineage — the kimi spawn cwd is the worktree (threaded via the
305
+ // writeMode arg to executeSwarmJob).
306
+ command_type: "rescue",
307
+ cwd: context.cwd,
308
+ model: parsed.model ?? null,
309
+ thinking: parsed.thinking ?? null,
310
+ background: false,
311
+ pid: process.pid,
312
+ kimi_pid: null,
313
+ status: "running",
314
+ kimi_session_id: null,
315
+ ...persistedExecutionPlanFields(executionPlan),
316
+ agent_profile: SWARM_WRITE_AGENT_PROFILE,
317
+ prompt_digest: digestPrompt(prompt),
318
+ summary: `[swarm-write] ${shorten(objective, SWARM_SUMMARY_MAX)}`,
319
+ phase: "starting",
320
+ final_output_path: null,
321
+ stream_log_path: logPath,
322
+ error: null,
323
+ });
324
+ try {
325
+ await writeInvocationLogHeader(logPath, {
326
+ commandType: "swarm-write",
327
+ kimiSessionId: "(pending)",
328
+ cwd: context.cwd,
329
+ });
330
+ }
331
+ catch (error) {
332
+ const classified = new RuntimeError("SWARM_LOG_HEADER_FAILED", `Failed to write swarm-write invocation log header: ${error.message ?? String(error)}`, "swarm.log-header", error instanceof Error ? { cause: error } : undefined);
333
+ await markJobFailed(store, paths, job, classified, "Swarm (write) failed.", {
334
+ phase: "failed",
335
+ });
336
+ throw classified;
337
+ }
338
+ const completed = await executeSwarmJob(job.job_id, prompt, objective, parsed.budgetMs ?? KIMI_SWARM_DEFAULT_BUDGET_MS, resolveSwarmWriteMaxConcurrency(parsed.maxConcurrency), context, { spawnCwd: worktreePath, workspaceRoot: worktreePath });
339
+ if (!completed.final_output_path) {
340
+ throw new RuntimeError("SWARM_RESULT_MISSING", "Swarm (write) finished without a rendered result.", "swarm.result");
341
+ }
342
+ return readArtifact(completed.final_output_path);
343
+ }
344
+ finally {
345
+ // Patch was captured inside executeSwarmJob (before this runs) on every
346
+ // terminal path; --force discards the now-disposable worktree.
347
+ await removeWorktree(repoIdentity.repoRoot, worktreePath);
348
+ }
349
+ }
350
+ finally {
351
+ store.close();
352
+ }
353
+ }
354
+ /** Refuse --write outside a git repo (git worktree add has nothing to fork). */
355
+ function assertWriteSwarmPreconditions(repoIdentity) {
356
+ if (!repoIdentity.isGitRepo) {
357
+ throw new RuntimeError("WRITE_SWARM_NOT_A_REPO", "/kimi:swarm --write requires a git repository (it works in a throwaway worktree off HEAD). The current directory is not inside a git repo.", "swarm.precondition");
358
+ }
359
+ }
360
+ /**
361
+ * Reap orphaned write-swarm worktrees from prior crashed runs. Normal runs remove
362
+ * their own worktree in a finally; an orphan only survives a hard kill. Prunes
363
+ * stale admin entries (current repo) then force-removes on-disk worktree dirs
364
+ * older than the TTL. Best-effort — never blocks the run.
365
+ */
366
+ async function sweepStaleWorktrees(paths, repoIdentity) {
367
+ await pruneWorktrees(repoIdentity.repoRoot);
368
+ // Only sweep THIS repo's namespaced subdir, so we never `rm` a worktree that a
369
+ // different repo still considers live (and never leave that repo a dangling
370
+ // admin entry). repoId is the realpath-stable hash from resolveRepoIdentity.
371
+ const repoWorktreesDir = path.join(paths.worktreesDir, repoIdentity.repoId);
372
+ const entries = await readdir(repoWorktreesDir, { withFileTypes: true }).catch(() => null);
373
+ if (entries === null)
374
+ return; // not created yet — nothing to sweep.
375
+ const now = Date.now();
376
+ for (const entry of entries) {
377
+ if (!entry.isDirectory() || !entry.name.startsWith("swarm-write-"))
378
+ continue;
379
+ const full = path.join(repoWorktreesDir, entry.name);
380
+ try {
381
+ const st = await stat(full);
382
+ if (now - st.mtimeMs > WORKTREE_ORPHAN_TTL_MS) {
383
+ await removeWorktree(repoIdentity.repoRoot, full);
384
+ }
385
+ }
386
+ catch {
387
+ // best-effort per entry
388
+ }
389
+ }
390
+ }
391
+ /**
392
+ * Capture the worktree change set as a `.patch` artifact (write mode). Returns
393
+ * the artifact path + size, or null on capture failure. Called on EVERY terminal
394
+ * path so partial work survives a cancel/budget-expiry.
395
+ */
396
+ async function capturePatchArtifact(paths, jobId, worktreePath, stderr) {
397
+ try {
398
+ const patch = await captureWorktreePatch(worktreePath);
399
+ const patchPath = path.join(paths.artifactsDir, `swarm-write-${jobId}.patch`);
400
+ await writeFile(patchPath, patch, "utf8");
401
+ return { path: patchPath, bytes: Buffer.byteLength(patch, "utf8"), empty: patch.trim().length === 0 };
402
+ }
403
+ catch (error) {
404
+ stderr.write(`[kimi-plugin-cc] swarm-write patch capture failed for job ${jobId}: ${error.message ?? String(error)}\n`);
405
+ return null;
406
+ }
407
+ }
408
+ /** Render the patch-handoff header prepended to a write-swarm report. */
409
+ function renderWriteSwarmHeader(captured) {
410
+ if (captured === null) {
411
+ return "**Patch:** capture FAILED — no patch was written; inspect the run logs.";
412
+ }
413
+ if (captured.empty) {
414
+ return "**Patch:** the swarm made NO changes (empty patch).";
415
+ }
416
+ return [
417
+ `**Patch written to:** \`${captured.path}\` (${captured.bytes} bytes).`,
418
+ `Review it, then apply from the repo root with: \`git apply --3way ${captured.path}\` ` +
419
+ "(the worktree was based on HEAD, so `--3way` reconciles against your current tree; " +
420
+ "plain `git apply` works only if your tree hasn't moved since).",
421
+ "The plugin did NOT apply or commit anything — you own the merge.",
422
+ ].join("\n");
423
+ }
424
+ async function executeSwarmJob(jobId, prompt, objective, budgetMs, maxConcurrency, context,
425
+ /**
426
+ * Write mode (v1.4): when set, the kimi spawn cwd is the throwaway worktree
427
+ * (`spawnCwd`), the hook label is "swarm-write", and `workspaceRoot` (the same
428
+ * worktree) is exported as the trusted allowlist root + used to capture the
429
+ * patch. Undefined ⇒ read-only swarm (label "swarm", spawn cwd = job.cwd).
430
+ */
431
+ writeMode) {
432
+ const paths = resolvePluginPaths(context.env);
433
+ await ensurePluginPaths(paths);
434
+ const store = new JobStore(paths);
435
+ const job = store.getJob(jobId);
436
+ if (!job) {
437
+ store.close();
438
+ throw new RuntimeError("JOB_NOT_FOUND", `Swarm job ${jobId} was not found.`, "swarm.worker");
439
+ }
440
+ // Swarm is read-only BY POLICY, but it fans out N subagents — without the
441
+ // PreToolUse hook there is no enforcement on ANY of them. Fail closed before
442
+ // spawning, matching the single-turn read-only commands.
443
+ if (context.env.KIMI_PLUGIN_CC_SKIP_HOOK_CHECK !== "1") {
444
+ const installStatus = await verifyHookInstalled(context.env);
445
+ if (!installStatus.installed) {
446
+ maybeWarnHookMissing(installStatus, "swarm", context.stderr);
447
+ const classified = new RuntimeError("SWARM_HOOK_NOT_INSTALLED", [
448
+ "/kimi:swarm refuses to run without the kimi-plugin-cc PreToolUse hook.",
449
+ "Swarm fans out multiple subagents; the hook is the ONLY thing keeping every",
450
+ "one of them read-only, so a missing hook means no enforcement across the fan-out.",
451
+ `Hook check failed: ${installStatus.reason ?? "unknown"}.`,
452
+ "Repair by running Claude Code /kimi:setup or Codex $kimi-setup, then retry.",
453
+ hookRefusalRetryProtocol(context.env),
454
+ ].join(" "), "swarm.hook-check", { details: hookRefusalDetails(installStatus) });
455
+ try {
456
+ return await markJobFailed(store, paths, job, classified, "Swarm failed.", { phase: "failed" });
457
+ }
458
+ finally {
459
+ store.close();
460
+ }
461
+ }
462
+ }
463
+ const handlers = createCliCancellationHandlers();
464
+ try {
465
+ const executionPlan = executionPlanFromPersisted(job);
466
+ store.updateRunningJob(job.job_id, { phase: "turn-running" });
467
+ const result = await runCliPromptWithBudget({
468
+ // Read mode spawns in the user's cwd; WRITE mode spawns in the throwaway
469
+ // worktree so coder subagents edit there (job.cwd stays the user's real
470
+ // cwd for lineage). This is the load-bearing isolation seam.
471
+ cwd: writeMode?.spawnCwd ?? job.cwd,
472
+ env: context.env,
473
+ command: executionPlan.command,
474
+ prefixArgs: [...executionPlan.prefixArgs],
475
+ executionPlan,
476
+ prompt,
477
+ // "swarm" → read-only-plus-AgentSwarm allowlist; "swarm-write" → the same
478
+ // PLUS rescue-grade write confinement to the trusted worktree root. The
479
+ // label drives the hook for the coordinator AND every spawned subagent.
480
+ commandLabel: writeMode ? "swarm-write" : "swarm",
481
+ // The HARD concurrency ceiling on kimi-code 0.18.0+ (exported as
482
+ // KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY; ignored by older binaries).
483
+ // ALWAYS set: defaults to SWARM_DEFAULT_MAX_CONCURRENCY (read) /
484
+ // SWARM_WRITE_DEFAULT_MAX_CONCURRENCY (write) when the user passes no
485
+ // --max-concurrency, so a fan-out is never unbounded. Distinct from --cap
486
+ // (the soft total-count prompt hint).
487
+ swarmMaxConcurrency: maxConcurrency,
488
+ // Trusted allowlist root for the swarm-write hook (forge-proof; ignored
489
+ // by other labels). Only set in write mode.
490
+ trustedWorkspaceRoot: writeMode?.workspaceRoot,
491
+ model: job.model ?? undefined,
492
+ logPath: job.stream_log_path,
493
+ signal: handlers.signal,
494
+ }, budgetMs, "swarm.prompt");
495
+ store.updateRunningJob(job.job_id, observedExecutionFields(result));
496
+ if (handlers.cancelling) {
497
+ throw new RuntimeError("SWARM_CANCELLED", "Swarm cancelled by user request.", "swarm.runtime");
498
+ }
499
+ assertCliResultSuccess(result, "swarm.runtime");
500
+ if (result.sessionId !== undefined &&
501
+ result.sessionId.length > 0 &&
502
+ result.sessionId !== job.kimi_session_id) {
503
+ store.updateRunningJob(job.job_id, { kimi_session_id: result.sessionId });
504
+ }
505
+ await syncKimiSessionTitle({
506
+ promptText: objective,
507
+ env: context.env,
508
+ cwd: job.cwd,
509
+ sessionId: result.sessionId ?? job.kimi_session_id,
510
+ title: buildKimiSessionTitle(writeMode ? "swarm-write" : "swarm", objective),
511
+ stderr: context.stderr,
512
+ });
513
+ warnIfSessionIdMissing(result, "swarm", job.job_id, context.stderr);
514
+ const prose = reassembleProseFromRecords(result.records);
515
+ // WRITE mode: capture the worktree change set as a .patch artifact (before
516
+ // runWriteSwarm's finally removes the worktree) and prepend the patch-handoff
517
+ // header so the consolidated report tells the main thread where the patch is.
518
+ const finalText = writeMode
519
+ ? `${renderWriteSwarmHeader(await capturePatchArtifact(paths, job.job_id, writeMode.workspaceRoot, context.stderr))}\n\n---\n\n${prose}`.trimEnd()
520
+ : prose;
521
+ const rendered = renderManagedJobOutput(job, finalText);
522
+ let artifactPath;
523
+ try {
524
+ artifactPath = await writeArtifact(paths, job, rendered.rendered);
525
+ }
526
+ catch (writeError) {
527
+ context.stderr.write(`[kimi-plugin-cc] swarm artifact write failed for job ${job.job_id}; raw output preserved in error details.\n`);
528
+ const classified = new RuntimeError("SWARM_ARTIFACT_WRITE_FAILED", `Failed to write swarm artifact: ${writeError.message ?? String(writeError)}`, "swarm.artifact", {
529
+ ...(writeError instanceof Error ? { cause: writeError } : {}),
530
+ details: { rawOutput: finalText },
531
+ });
532
+ return await markJobFailed(store, paths, job, classified, "Swarm failed.", { phase: "failed" });
533
+ }
534
+ if (handlers.cancelling) {
535
+ throw new RuntimeError("SWARM_CANCELLED", "Swarm cancelled after artifact write.", "swarm.runtime");
536
+ }
537
+ return (store.markCompleted(job.job_id, {
538
+ summary: `[swarm] ${shorten(objective, SWARM_SUMMARY_MAX)}`,
539
+ phase: "done",
540
+ final_output_path: artifactPath,
541
+ error: null,
542
+ }) ?? job);
543
+ }
544
+ catch (error) {
545
+ // WRITE mode: best-effort capture of any PARTIAL work before we mark the job
546
+ // and before runWriteSwarm's finally removes the worktree, so a cancel /
547
+ // budget-expiry doesn't silently discard edits the subagents already made.
548
+ if (writeMode) {
549
+ const captured = await capturePatchArtifact(paths, job.job_id, writeMode.workspaceRoot, context.stderr);
550
+ if (captured && !captured.empty) {
551
+ context.stderr.write(`[kimi-plugin-cc] swarm-write run ended early; partial patch captured at ${captured.path}.\n`);
552
+ }
553
+ }
554
+ if (handlers.cancelling) {
555
+ const cancelledError = new RuntimeError("SWARM_CANCELLED", "Swarm cancelled by user request.", "swarm.runtime", error instanceof Error ? { cause: error } : undefined);
556
+ return await markJobCancelled(store, paths, job, "Swarm cancelled by user request.", cancelledError, {
557
+ phase: "cancelled",
558
+ });
559
+ }
560
+ const classified = classifyManagedCommandFailure(error, writeMode ? "rescue" : "review", job.job_id, {
561
+ preserveStage: true,
562
+ });
563
+ return await markJobFailed(store, paths, job, classified, "Swarm failed.", { phase: "failed" });
564
+ }
565
+ finally {
566
+ handlers.dispose();
567
+ store.close();
568
+ }
569
+ }
570
+ function shorten(text, max) {
571
+ const normalized = text.replace(/\s+/g, " ").trim();
572
+ if (normalized.length <= max) {
573
+ return normalized;
574
+ }
575
+ return `${normalized.slice(0, Math.max(0, max - 1)).trimEnd()}…`;
576
+ }
@@ -0,0 +1,31 @@
1
+ import { RuntimeError } from "../errors.js";
2
+ import { executeRescueJob } from "./rescue.js";
3
+ import { executeAskJob } from "./ask.js";
4
+ export async function runWorker(argv, context) {
5
+ const [kind, jobId] = argv;
6
+ if (!jobId || (kind !== "rescue" && kind !== "ask")) {
7
+ throw new RuntimeError("INVALID_COMMAND", "worker expects the form: worker rescue <job-id> or worker ask <job-id>.", "worker");
8
+ }
9
+ if (kind === "rescue") {
10
+ const encodedPrompt = context.env.KIMI_PLUGIN_CC_RESCUE_PROMPT_B64;
11
+ if (!encodedPrompt) {
12
+ throw new RuntimeError("MISSING_RESCUE_PROMPT", "Background rescue prompt is missing.", "worker");
13
+ }
14
+ const prompt = Buffer.from(encodedPrompt, "base64").toString("utf8");
15
+ const reusedSession = context.env.KIMI_PLUGIN_CC_RESCUE_REUSED_SESSION === "1";
16
+ await executeRescueJob(jobId, prompt, context, { workerPid: process.pid, reusedSession });
17
+ return;
18
+ }
19
+ // kind === "ask"
20
+ //
21
+ // v1.0 note: the ask worker no longer threads `reusedSession` or
22
+ // `rawPrompt` into executeAskJob — both were inputs to v0.4's
23
+ // `announceSessionTitle`, which is gone (kimi-web has no PATCH
24
+ // endpoint). The base64-encoded prompt + job id are sufficient.
25
+ const encodedPrompt = context.env.KIMI_PLUGIN_CC_ASK_PROMPT_B64;
26
+ if (!encodedPrompt) {
27
+ throw new RuntimeError("MISSING_ASK_PROMPT", "Background ask prompt is missing.", "worker");
28
+ }
29
+ const prompt = Buffer.from(encodedPrompt, "base64").toString("utf8");
30
+ await executeAskJob(jobId, prompt, context, { workerPid: process.pid });
31
+ }