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,19 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { runReviewGateStopHook } from "../commands/review-gate.js";
3
+ import { formatError } from "../errors.js";
4
+ async function main() {
5
+ const raw = readFileSync(0, "utf8");
6
+ const input = JSON.parse(raw);
7
+ const context = {
8
+ cwd: process.env.KIMI_PLUGIN_CC_WORKSPACE_CWD || process.cwd(),
9
+ env: process.env,
10
+ stdout: process.stdout,
11
+ stderr: process.stderr,
12
+ };
13
+ const output = await runReviewGateStopHook(input, context);
14
+ process.stdout.write(`${JSON.stringify(output)}\n`);
15
+ }
16
+ main().catch((error) => {
17
+ process.stderr.write(`${formatError(error)}\n`);
18
+ process.exitCode = 1;
19
+ });
@@ -0,0 +1,559 @@
1
+ import { createRequire } from "node:module";
2
+ import { RuntimeError, formatError } from "./errors.js";
3
+ export class JobStore {
4
+ db;
5
+ constructor(paths) {
6
+ // Open the adapter into a local before assigning to this.db so the
7
+ // catch can release the handle even if pragma throws AFTER the
8
+ // adapter opened — `journal_mode = WAL` on a read-only mount, for
9
+ // example, would otherwise leak the handle because the constructor
10
+ // exits via throw before any caller reference exists for cleanup.
11
+ let db;
12
+ try {
13
+ db = createSqliteAdapter(paths.stateDbPath);
14
+ db.pragma("journal_mode = WAL");
15
+ db.pragma("busy_timeout = 5000");
16
+ }
17
+ catch (error) {
18
+ try {
19
+ db?.close();
20
+ }
21
+ catch {
22
+ // Best-effort — original error takes priority over close failure.
23
+ }
24
+ throw translateSqliteError(error);
25
+ }
26
+ this.db = db;
27
+ // Every `this.db.exec` / `tableHasColumn` below can throw on a
28
+ // corrupt DB, full disk, mid-migration failure, etc. Catch
29
+ // internally, close the adapter, and rethrow the translated error
30
+ // so the SQLite handle never leaks past a failed migration.
31
+ try {
32
+ this.db.exec(`
33
+ CREATE TABLE IF NOT EXISTS jobs (
34
+ job_id TEXT PRIMARY KEY,
35
+ repo_id TEXT NOT NULL,
36
+ command_type TEXT NOT NULL,
37
+ created_at TEXT NOT NULL,
38
+ updated_at TEXT NOT NULL,
39
+ cwd TEXT NOT NULL,
40
+ model TEXT,
41
+ thinking INTEGER,
42
+ background INTEGER NOT NULL,
43
+ pid INTEGER,
44
+ kimi_pid INTEGER,
45
+ status TEXT NOT NULL,
46
+ kimi_session_id TEXT,
47
+ operation_kind TEXT,
48
+ intended_engine TEXT,
49
+ observed_engine TEXT,
50
+ kimi_version TEXT,
51
+ system_version TEXT,
52
+ kimi_command TEXT,
53
+ kimi_prefix_args TEXT,
54
+ plan_certification TEXT,
55
+ resumed_from_job_id TEXT,
56
+ safety_profile TEXT,
57
+ agent_profile TEXT NOT NULL,
58
+ prompt_digest TEXT NOT NULL,
59
+ summary TEXT NOT NULL,
60
+ phase TEXT,
61
+ final_output_path TEXT,
62
+ stream_log_path TEXT NOT NULL,
63
+ error TEXT
64
+ );
65
+ `);
66
+ if (!tableHasColumn(this.db, "phase")) {
67
+ this.db.exec(`ALTER TABLE jobs ADD COLUMN phase TEXT;`);
68
+ }
69
+ // Additive provenance migration. Existing rows deliberately remain NULL:
70
+ // v1.9.5 plus kimi-code 0.33/0.34 could have routed to native v2 before the
71
+ // legacy pin landed, so a blanket historical v1 backfill would invent fact.
72
+ const provenanceColumns = [
73
+ "operation_kind",
74
+ "intended_engine",
75
+ "observed_engine",
76
+ "kimi_version",
77
+ "system_version",
78
+ "kimi_command",
79
+ "kimi_prefix_args",
80
+ "plan_certification",
81
+ "resumed_from_job_id",
82
+ "safety_profile",
83
+ ];
84
+ for (const column of provenanceColumns) {
85
+ if (!tableHasColumn(this.db, column)) {
86
+ this.db.exec(`ALTER TABLE jobs ADD COLUMN ${column} TEXT;`);
87
+ }
88
+ }
89
+ this.db.exec(`
90
+
91
+ CREATE INDEX IF NOT EXISTS jobs_repo_updated_idx
92
+ ON jobs (repo_id, updated_at DESC);
93
+
94
+ CREATE INDEX IF NOT EXISTS jobs_repo_type_updated_idx
95
+ ON jobs (repo_id, command_type, updated_at DESC);
96
+
97
+ CREATE INDEX IF NOT EXISTS jobs_session_idx
98
+ ON jobs (repo_id, kimi_session_id, updated_at DESC);
99
+
100
+ DROP INDEX IF EXISTS jobs_running_rescue_session_idx;
101
+
102
+ -- Reconcile orphaned duplicate running rows before creating the unique
103
+ -- partial index. A 0.1.3 database with a hard-crashed worker can contain
104
+ -- multiple (repo_id, command_type, kimi_session_id) rows at status='running',
105
+ -- which would fail the CREATE UNIQUE INDEX at open and brick the store.
106
+ -- Keep the most recently updated row per group; mark the rest as failed.
107
+ UPDATE jobs
108
+ SET status = 'failed',
109
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
110
+ error = COALESCE(
111
+ error,
112
+ '{"code":"JOB_STORE_ORPHANED_ON_UPGRADE","message":"Orphaned duplicate running job reconciled on 0.1.4 schema upgrade","stage":"job-store.migrate"}'
113
+ )
114
+ WHERE status = 'running'
115
+ AND command_type IN ('rescue', 'ask')
116
+ AND kimi_session_id IS NOT NULL
117
+ AND rowid NOT IN (
118
+ SELECT rowid FROM (
119
+ SELECT rowid,
120
+ ROW_NUMBER() OVER (
121
+ PARTITION BY repo_id, command_type, kimi_session_id
122
+ ORDER BY updated_at DESC, rowid DESC
123
+ ) AS rn
124
+ FROM jobs
125
+ WHERE status = 'running'
126
+ AND command_type IN ('rescue', 'ask')
127
+ AND kimi_session_id IS NOT NULL
128
+ )
129
+ WHERE rn = 1
130
+ );
131
+
132
+ CREATE UNIQUE INDEX IF NOT EXISTS jobs_running_session_idx
133
+ ON jobs (repo_id, command_type, kimi_session_id)
134
+ WHERE status = 'running' AND command_type IN ('rescue', 'ask');
135
+
136
+ -- 0.1.6 migration: rename adversarial_review to challenge
137
+ UPDATE jobs SET command_type = 'challenge' WHERE command_type = 'adversarial_review';
138
+ `);
139
+ }
140
+ catch (error) {
141
+ // Migration failed — close the adapter so the OS handle is released
142
+ // before propagating. Wrap close() in its own try so a double-fault
143
+ // doesn't mask the original migration error.
144
+ try {
145
+ this.db.close();
146
+ }
147
+ catch {
148
+ // intentionally swallow — original error takes priority
149
+ }
150
+ throw translateSqliteError(error);
151
+ }
152
+ }
153
+ close() {
154
+ this.db.close();
155
+ }
156
+ createJob(input) {
157
+ const now = new Date().toISOString();
158
+ try {
159
+ this.db.run(`
160
+ INSERT INTO jobs (
161
+ job_id, repo_id, command_type, created_at, updated_at, cwd, model, thinking,
162
+ background, pid, kimi_pid, status, kimi_session_id,
163
+ operation_kind, intended_engine, observed_engine, kimi_version, system_version,
164
+ kimi_command, kimi_prefix_args, plan_certification, resumed_from_job_id, safety_profile,
165
+ agent_profile, prompt_digest, summary, phase, final_output_path, stream_log_path, error
166
+ )
167
+ VALUES (
168
+ @job_id, @repo_id, @command_type, @created_at, @updated_at, @cwd, @model, @thinking,
169
+ @background, @pid, @kimi_pid, @status, @kimi_session_id,
170
+ @operation_kind, @intended_engine, @observed_engine, @kimi_version, @system_version,
171
+ @kimi_command, @kimi_prefix_args, @plan_certification, @resumed_from_job_id, @safety_profile,
172
+ @agent_profile, @prompt_digest, @summary, @phase, @final_output_path, @stream_log_path, @error
173
+ )
174
+ `, {
175
+ ...serializeRecord(input),
176
+ created_at: now,
177
+ updated_at: now,
178
+ });
179
+ }
180
+ catch (error) {
181
+ if (isSqliteConstraintError(error)) {
182
+ if (input.command_type === "rescue") {
183
+ throw new RuntimeError("RESCUE_ALREADY_RUNNING", `A rescue job for session ${input.kimi_session_id ?? "<unknown>"} is already running in this repo. Use /kimi:status to find it.`, "rescue.resume", error instanceof Error ? { cause: error } : undefined);
184
+ }
185
+ if (input.command_type === "ask") {
186
+ throw new RuntimeError("ASK_ALREADY_RUNNING", `An ask session ${input.kimi_session_id ?? "<unknown>"} is already running in this repo. Use /kimi:status to find it.`, "ask.resume", error instanceof Error ? { cause: error } : undefined);
187
+ }
188
+ }
189
+ throw translateSqliteError(error);
190
+ }
191
+ return this.getJob(input.job_id);
192
+ }
193
+ getJob(jobId) {
194
+ const row = this.db.get("SELECT * FROM jobs WHERE job_id = ?", jobId);
195
+ return row ? hydrateRow(row) : null;
196
+ }
197
+ findLatestJob(options) {
198
+ const conditions = ["repo_id = @repo_id"];
199
+ const params = {
200
+ repo_id: options.repoId,
201
+ };
202
+ if (options.commandType) {
203
+ conditions.push("command_type = @command_type");
204
+ params.command_type = options.commandType;
205
+ }
206
+ if (options.terminalOnly) {
207
+ conditions.push("status IN ('completed', 'failed', 'cancelled')");
208
+ }
209
+ if (options.runningOnly) {
210
+ conditions.push("status = 'running'");
211
+ }
212
+ const row = this.db.get(`SELECT * FROM jobs WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC LIMIT 1`, params);
213
+ return row ? hydrateRow(row) : null;
214
+ }
215
+ listRunningJobsWithProcessHints() {
216
+ const rows = this.db.all(`SELECT * FROM jobs WHERE status = 'running' AND (pid IS NOT NULL OR kimi_pid IS NOT NULL)`);
217
+ return rows.map(hydrateRow);
218
+ }
219
+ findRescueJobBySession(repoId, sessionId) {
220
+ const row = this.db.get(`
221
+ SELECT *
222
+ FROM jobs
223
+ WHERE repo_id = ?
224
+ AND command_type = 'rescue'
225
+ AND kimi_session_id = ?
226
+ ORDER BY updated_at DESC
227
+ LIMIT 1
228
+ `, repoId, sessionId);
229
+ return row ? hydrateRow(row) : null;
230
+ }
231
+ findAskJobBySession(repoId, sessionId) {
232
+ const row = this.db.get(`
233
+ SELECT *
234
+ FROM jobs
235
+ WHERE repo_id = ?
236
+ AND command_type = 'ask'
237
+ AND kimi_session_id = ?
238
+ ORDER BY updated_at DESC
239
+ LIMIT 1
240
+ `, repoId, sessionId);
241
+ return row ? hydrateRow(row) : null;
242
+ }
243
+ updateRunningJob(jobId, patch) {
244
+ return this.updateWhere(jobId, patch, "status = 'running'");
245
+ }
246
+ /**
247
+ * Evidence-only backfill for pre-provenance rows. COALESCE prevents forensic
248
+ * inspection from overwriting a field already recorded at execution time.
249
+ * updated_at is intentionally unchanged so a status lookup cannot reorder
250
+ * historical jobs merely because their logs were inspected.
251
+ */
252
+ backfillHistoricalProvenance(jobId, patch) {
253
+ this.db.run(`
254
+ UPDATE jobs
255
+ SET operation_kind = COALESCE(operation_kind, @operation_kind),
256
+ observed_engine = COALESCE(observed_engine, @observed_engine),
257
+ kimi_version = COALESCE(kimi_version, @kimi_version),
258
+ system_version = COALESCE(system_version, @system_version)
259
+ WHERE job_id = @job_id
260
+ `, {
261
+ job_id: jobId,
262
+ operation_kind: patch.operation_kind ?? null,
263
+ observed_engine: patch.observed_engine ?? null,
264
+ kimi_version: patch.kimi_version ?? null,
265
+ system_version: patch.system_version ?? null,
266
+ });
267
+ return this.getJob(jobId);
268
+ }
269
+ markCompleted(jobId, patch) {
270
+ return this.updateWhere(jobId, { ...patch, status: "completed", pid: null, kimi_pid: null }, "status = 'running'");
271
+ }
272
+ markFailed(jobId, patch) {
273
+ return this.updateWhere(jobId, { ...patch, status: "failed", pid: null, kimi_pid: null }, "status = 'running'");
274
+ }
275
+ markCancelled(jobId, patch) {
276
+ return this.updateWhere(jobId, { ...patch, status: "cancelled", pid: null, kimi_pid: null }, "status = 'running'");
277
+ }
278
+ static JOBS_COLUMNS = new Set([
279
+ "status", "summary", "phase", "pid", "kimi_pid",
280
+ "final_output_path", "stream_log_path", "kimi_session_id",
281
+ "error", "command_type", "prompt_digest", "repo_id",
282
+ "cwd", "model", "thinking", "background", "agent_profile",
283
+ "operation_kind", "intended_engine", "observed_engine", "kimi_version",
284
+ "system_version", "kimi_command", "kimi_prefix_args", "plan_certification",
285
+ "resumed_from_job_id", "safety_profile",
286
+ ]);
287
+ updateWhere(jobId, patch, whereClause) {
288
+ const updates = ["updated_at = @updated_at"];
289
+ const params = {
290
+ job_id: jobId,
291
+ updated_at: new Date().toISOString(),
292
+ };
293
+ for (const [key, value] of Object.entries(serializePatch(patch))) {
294
+ if (key === "job_id" || key === "created_at" || key === "updated_at" || value === undefined) {
295
+ continue;
296
+ }
297
+ if (!JobStore.JOBS_COLUMNS.has(key))
298
+ continue;
299
+ updates.push(`${key} = @${key}`);
300
+ params[key] = value;
301
+ }
302
+ const statement = `
303
+ UPDATE jobs
304
+ SET ${updates.join(", ")}
305
+ WHERE job_id = @job_id${whereClause ? ` AND ${whereClause}` : ""}
306
+ `;
307
+ this.db.run(statement, params);
308
+ return this.getJob(jobId);
309
+ }
310
+ }
311
+ function hydrateRow(row) {
312
+ return {
313
+ ...row,
314
+ thinking: row.thinking === null ? null : Boolean(row.thinking),
315
+ background: Boolean(row.background),
316
+ error: row.error ? JSON.parse(row.error) : null,
317
+ };
318
+ }
319
+ function serializeRecord(record) {
320
+ return {
321
+ ...record,
322
+ phase: record.phase ?? null,
323
+ operation_kind: record.operation_kind ?? null,
324
+ intended_engine: record.intended_engine ?? null,
325
+ observed_engine: record.observed_engine ?? null,
326
+ kimi_version: record.kimi_version ?? null,
327
+ system_version: record.system_version ?? null,
328
+ kimi_command: record.kimi_command ?? null,
329
+ kimi_prefix_args: record.kimi_prefix_args ?? null,
330
+ plan_certification: record.plan_certification ?? null,
331
+ resumed_from_job_id: record.resumed_from_job_id ?? null,
332
+ safety_profile: record.safety_profile ?? null,
333
+ thinking: record.thinking === null ? null : Number(record.thinking),
334
+ background: Number(record.background),
335
+ error: record.error ? JSON.stringify(record.error) : null,
336
+ };
337
+ }
338
+ function serializePatch(patch) {
339
+ const result = {};
340
+ for (const [key, value] of Object.entries(patch)) {
341
+ if (key === "thinking") {
342
+ result[key] = value === null ? null : Number(Boolean(value));
343
+ continue;
344
+ }
345
+ if (key === "background") {
346
+ result[key] = Number(Boolean(value));
347
+ continue;
348
+ }
349
+ if (key === "error") {
350
+ result[key] = value ? JSON.stringify(value) : null;
351
+ continue;
352
+ }
353
+ result[key] = value;
354
+ }
355
+ return result;
356
+ }
357
+ function createSqliteAdapter(filename) {
358
+ const require = createRequire(import.meta.url);
359
+ if (typeof Bun !== "undefined") {
360
+ const { Database } = require("bun:sqlite");
361
+ const db = new Database(filename);
362
+ return {
363
+ pragma(statement) {
364
+ db.exec(`PRAGMA ${statement}`);
365
+ },
366
+ exec(statement) {
367
+ db.exec(statement);
368
+ },
369
+ run(statement, params) {
370
+ const query = db.query(rewriteNamedParamsForBun(statement));
371
+ if (Array.isArray(params)) {
372
+ query.run(...params);
373
+ return;
374
+ }
375
+ query.run(rewriteNamedBindingsForBun((params ?? {})));
376
+ },
377
+ get(statement, ...params) {
378
+ const query = db.query(rewriteNamedParamsForBun(statement));
379
+ if (params.length === 1) {
380
+ const first = params[0];
381
+ return query.get(typeof first === "object" && first !== null && !Array.isArray(first)
382
+ ? rewriteNamedBindingsForBun(first)
383
+ : first);
384
+ }
385
+ return query.get(...params);
386
+ },
387
+ all(statement, ...params) {
388
+ const query = db.query(rewriteNamedParamsForBun(statement));
389
+ if (params.length === 1) {
390
+ const first = params[0];
391
+ return query.all(typeof first === "object" && first !== null && !Array.isArray(first)
392
+ ? rewriteNamedBindingsForBun(first)
393
+ : first);
394
+ }
395
+ return query.all(...params);
396
+ },
397
+ close() {
398
+ db.close();
399
+ },
400
+ };
401
+ }
402
+ // Node 22.5+ ships node:sqlite as a built-in. We use it via createRequire so the compiled
403
+ // dist/ has zero native dependencies — no `bun install` step required for drop-in plugin
404
+ // installs, no platform-specific compiled binaries to ship. See package.json engines.node.
405
+ let nodeSqlite;
406
+ try {
407
+ nodeSqlite = require("node:sqlite");
408
+ }
409
+ catch (error) {
410
+ throw new RuntimeError("JOB_STORE_UNSUPPORTED_RUNTIME", "kimi-plugin-cc requires Node >= 22.5 for its built-in node:sqlite module. Upgrade Node, or set KIMI_PLUGIN_CC_NODE_BIN to a qualifying binary.", "job-store", error instanceof Error ? { cause: error } : undefined);
411
+ }
412
+ const db = new nodeSqlite.DatabaseSync(filename);
413
+ return {
414
+ pragma(statement) {
415
+ db.exec(`PRAGMA ${statement}`);
416
+ },
417
+ exec(statement) {
418
+ db.exec(statement);
419
+ },
420
+ run(statement, params) {
421
+ // Cast through `any` so the prepared-statement methods are called as methods (preserving
422
+ // `this`). Pulling them off the instance via `const fn = stmt.run` strips the binding and
423
+ // node:sqlite throws "Illegal invocation".
424
+ const stmt = db.prepare(statement);
425
+ if (Array.isArray(params)) {
426
+ stmt.run(...params);
427
+ return;
428
+ }
429
+ stmt.run(normalizeNodeSqliteBindings((params ?? {})));
430
+ },
431
+ get(statement, ...params) {
432
+ const stmt = db.prepare(statement);
433
+ if (params.length === 1 && isPlainObject(params[0])) {
434
+ return stmt.get(normalizeNodeSqliteBindings(params[0]));
435
+ }
436
+ return stmt.get(...params);
437
+ },
438
+ all(statement, ...params) {
439
+ const stmt = db.prepare(statement);
440
+ if (params.length === 1 && isPlainObject(params[0])) {
441
+ return stmt.all(normalizeNodeSqliteBindings(params[0]));
442
+ }
443
+ return stmt.all(...params);
444
+ },
445
+ close() {
446
+ db.close();
447
+ },
448
+ };
449
+ }
450
+ // node:sqlite's prepared-statement binding API accepts objects whose keys omit the parameter
451
+ // prefix (so SQL `@job_id` binds from `{ job_id: ... }`). Normalize optional values to null,
452
+ // coerce booleans to 0/1, and fail fast on unsupported binding types before they reach the
453
+ // native layer as opaque SQLITE_MISMATCH errors.
454
+ function normalizeNodeSqliteBindings(bindings) {
455
+ const result = {};
456
+ for (const [key, value] of Object.entries(bindings)) {
457
+ if (value === undefined) {
458
+ result[key] = null;
459
+ continue;
460
+ }
461
+ if (value === null) {
462
+ result[key] = null;
463
+ continue;
464
+ }
465
+ if (typeof value === "boolean") {
466
+ result[key] = value ? 1 : 0;
467
+ continue;
468
+ }
469
+ if (typeof value === "string" ||
470
+ typeof value === "number" ||
471
+ typeof value === "bigint" ||
472
+ value instanceof Uint8Array) {
473
+ result[key] = value;
474
+ continue;
475
+ }
476
+ throw new Error(`node:sqlite binding for ${key} has unsupported type ${typeof value} (${value?.constructor?.name ?? "unknown"})`);
477
+ }
478
+ return result;
479
+ }
480
+ function isPlainObject(value) {
481
+ return typeof value === "object" && value !== null && !Array.isArray(value);
482
+ }
483
+ function rewriteNamedParamsForBun(statement) {
484
+ return statement.replaceAll(/@([A-Za-z0-9_]+)/g, (_, key) => `$${key}`);
485
+ }
486
+ function rewriteNamedBindingsForBun(bindings) {
487
+ return Object.fromEntries(Object.entries(bindings).map(([key, value]) => [`$${key}`, value]));
488
+ }
489
+ function tableHasColumn(db, columnName) {
490
+ const columns = db.all(`PRAGMA table_info(jobs)`);
491
+ return columns.some((column) => column.name === columnName);
492
+ }
493
+ function isSqliteConstraintError(error) {
494
+ const message = formatError(error).toLowerCase();
495
+ return message.includes("unique constraint") || message.includes("constraint failed");
496
+ }
497
+ function isSqliteBusyError(error) {
498
+ const message = formatError(error).toLowerCase();
499
+ return message.includes("database is locked") || message.includes("busy");
500
+ }
501
+ function translateSqliteError(error) {
502
+ if (isSqliteBusyError(error)) {
503
+ return new RuntimeError("JOB_STORE_BUSY", "The plugin job store is locked by another process. Wait a moment and retry, or check for stuck rescue workers with /kimi:status.", "job-store", error instanceof Error ? { cause: error } : undefined);
504
+ }
505
+ if (error instanceof RuntimeError) {
506
+ return error;
507
+ }
508
+ return new RuntimeError("JOB_STORE_ERROR", `Job store operation failed: ${formatError(error)}`, "job-store", error instanceof Error ? { cause: error } : undefined);
509
+ }
510
+ /**
511
+ * Run a function with a temporary `JobStore` whose handle is closed when
512
+ * the function returns OR when the function throws after construction
513
+ * succeeded.
514
+ *
515
+ * Construction-failure note: if `new JobStore(paths)` itself throws,
516
+ * this helper cannot close anything — the constructor's own try/catch
517
+ * owns adapter-handle cleanup on mid-migration failures. The helper
518
+ * handles "store opened cleanly, function threw"; the constructor
519
+ * handles "open or migrate failed mid-construction." Together they
520
+ * make the leak class unreachable.
521
+ *
522
+ * Consolidates the repeated
523
+ *
524
+ * const store = new JobStore(paths);
525
+ * try { ... } finally { store.close(); }
526
+ *
527
+ * dance that every command file used to copy. For the rare case where
528
+ * the caller needs to keep a reference to the store past the function
529
+ * body (e.g., async cancel handlers that close elsewhere), continue
530
+ * using the explicit `new JobStore` + try/finally idiom — `withJobStore`
531
+ * is for the common single-scope case.
532
+ */
533
+ export async function withJobStore(paths, fn) {
534
+ // Construct INSIDE the try. `new JobStore` must not live above this
535
+ // line — the constructor can throw after opening the SQLite adapter
536
+ // (pragma failure, schema migration), and the helper's `finally`
537
+ // is the second line of defense behind the constructor's own
538
+ // cleanup. Moving `new JobStore` above the try would silently
539
+ // reintroduce a handle leak the JSDoc above promises against.
540
+ let store;
541
+ try {
542
+ store = new JobStore(paths);
543
+ return await fn(store);
544
+ }
545
+ finally {
546
+ store?.close();
547
+ }
548
+ }
549
+ /** Synchronous companion to `withJobStore` for callers that don't await inside. */
550
+ export function withJobStoreSync(paths, fn) {
551
+ let store;
552
+ try {
553
+ store = new JobStore(paths);
554
+ return fn(store);
555
+ }
556
+ finally {
557
+ store?.close();
558
+ }
559
+ }