kimi-plugin-cc 2.4.4 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +21 -0
  4. package/README.md +50 -34
  5. package/dist/commands/swarm.js +1 -1
  6. package/dist/dsh/companion.js +174 -0
  7. package/dist/dsh/index.js +156 -0
  8. package/dist/hooks/config-safety.js +3 -2
  9. package/dist/hooks/install-paths.js +2 -2
  10. package/dist/kimi-engine.js +1 -0
  11. package/dist/swarm-activation.js +1 -1
  12. package/dist/version.js +1 -1
  13. package/docs/migration.md +4 -4
  14. package/docs/models.md +5 -1
  15. package/docs/registry-releases.md +20 -17
  16. package/docs/safety.md +28 -15
  17. package/docs/swarm-recovery.md +1 -1
  18. package/docs/upstream-2.4.1-certification.md +54 -0
  19. package/package.json +41 -6
  20. package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
  21. package/plugins/kimi-codex/dist/commands/swarm.js +1 -1
  22. package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
  23. package/plugins/kimi-codex/dist/dsh/index.js +156 -0
  24. package/plugins/kimi-codex/dist/hooks/config-safety.js +3 -2
  25. package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
  26. package/plugins/kimi-codex/dist/kimi-engine.js +1 -0
  27. package/plugins/kimi-codex/dist/swarm-activation.js +1 -1
  28. package/plugins/kimi-codex/dist/version.js +1 -1
  29. package/plugins/kimi-dsh/LICENSE +201 -0
  30. package/plugins/kimi-dsh/README.md +80 -0
  31. package/plugins/kimi-dsh/cordis.patch.yml +45 -0
  32. package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
  33. package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
  34. package/plugins/kimi-dsh/dist/cli-client.js +876 -0
  35. package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
  36. package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
  37. package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
  38. package/plugins/kimi-dsh/dist/commands/models.js +140 -0
  39. package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
  40. package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
  41. package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
  42. package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
  43. package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
  44. package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
  45. package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
  46. package/plugins/kimi-dsh/dist/commands/result.js +66 -0
  47. package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
  48. package/plugins/kimi-dsh/dist/commands/review.js +220 -0
  49. package/plugins/kimi-dsh/dist/commands/setup.js +1047 -0
  50. package/plugins/kimi-dsh/dist/commands/status.js +27 -0
  51. package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
  52. package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
  53. package/plugins/kimi-dsh/dist/companion.js +97 -0
  54. package/plugins/kimi-dsh/dist/config.js +22 -0
  55. package/plugins/kimi-dsh/dist/dsh/companion.js +174 -0
  56. package/plugins/kimi-dsh/dist/dsh/index.js +156 -0
  57. package/plugins/kimi-dsh/dist/errors.js +40 -0
  58. package/plugins/kimi-dsh/dist/git.js +224 -0
  59. package/plugins/kimi-dsh/dist/help.js +43 -0
  60. package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
  61. package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
  62. package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
  63. package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
  64. package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
  65. package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
  66. package/plugins/kimi-dsh/dist/hooks/managed-block.js +619 -0
  67. package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
  68. package/plugins/kimi-dsh/dist/job-id.js +12 -0
  69. package/plugins/kimi-dsh/dist/job-store.js +659 -0
  70. package/plugins/kimi-dsh/dist/jobs.js +217 -0
  71. package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
  72. package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
  73. package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
  74. package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
  75. package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
  76. package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
  77. package/plugins/kimi-dsh/dist/logging.js +22 -0
  78. package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
  79. package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
  80. package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
  81. package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
  82. package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
  83. package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
  84. package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
  85. package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
  86. package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
  87. package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
  88. package/plugins/kimi-dsh/dist/model-command.js +32 -0
  89. package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
  90. package/plugins/kimi-dsh/dist/package.json +1 -0
  91. package/plugins/kimi-dsh/dist/parsing.js +620 -0
  92. package/plugins/kimi-dsh/dist/paths.js +27 -0
  93. package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
  94. package/plugins/kimi-dsh/dist/process-tree.js +479 -0
  95. package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
  96. package/plugins/kimi-dsh/dist/render.js +177 -0
  97. package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
  98. package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
  99. package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
  100. package/plugins/kimi-dsh/dist/session-title.js +293 -0
  101. package/plugins/kimi-dsh/dist/stream-json.js +346 -0
  102. package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
  103. package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
  104. package/plugins/kimi-dsh/dist/types.js +1 -0
  105. package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
  106. package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
  107. package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
  108. package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
  109. package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
  110. package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
  111. package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
  112. package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
  113. package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
  114. package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
  115. package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
  116. package/plugins/kimi-dsh/dist/version.js +7 -0
  117. package/plugins/kimi-dsh/package.json +48 -0
  118. package/plugins/kimi-dsh/scripts/companion.sh +37 -0
  119. package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,620 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ /**
3
+ * Unknown flag-shaped tokens hard-fail instead of being absorbed into prose.
4
+ * Parsing still switches to prose at the first non-flag token or after `--`.
5
+ */
6
+ function looksLikeFlag(token) {
7
+ if (!token.startsWith("-")) {
8
+ return false;
9
+ }
10
+ if (token.startsWith("--") && token.length > 2) {
11
+ return true;
12
+ }
13
+ // Short flag: a single dash followed by exactly one letter (`-r`, `-m`).
14
+ return /^-[a-zA-Z]$/.test(token);
15
+ }
16
+ export const ASK_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
17
+ export const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
18
+ export const REVIEW_SUPPORTED_FLAGS = "--base <ref>, -m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>";
19
+ export const JOB_LOOKUP_SUPPORTED_FLAGS = "--type <review|challenge|rescue|review_gate|ask>, --json";
20
+ export const SETUP_SUPPORTED_FLAGS = "--check, --uninstall, --all (with --uninstall), --enable-review-gate, --disable-review-gate, --models [--json] (without other setup flags)";
21
+ export const RECOVER_SWARM_SUPPORTED_FLAGS = "--apply";
22
+ export const REPAIR_SESSIONS_SUPPORTED_FLAGS = "--apply, --all";
23
+ export const REPLAY_SUPPORTED_FLAGS = "none (one job-id required)";
24
+ /** Validate syntax only; upstream per-model supportEfforts remains authoritative. */
25
+ export function validateThinkingEffort(value, stage = "args.parse", lowercase = true) {
26
+ const effort = lowercase ? value?.toLowerCase() : value;
27
+ if (!effort || !/^[a-z][a-z0-9-]{0,31}$/.test(effort)) {
28
+ throw new RuntimeError("INVALID_ARGS", "--thinking requires an effort matching [a-z][a-z0-9-]{0,31} (e.g. off, low, medium, high).", stage);
29
+ }
30
+ return effort;
31
+ }
32
+ export const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --turns <N>";
33
+ export const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --thinking <off|low|medium|high>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
34
+ /**
35
+ * Parser for /kimi:swarm. Foreground-only: no --background/--fresh/--resume.
36
+ * Trailing tokens are the objective.
37
+ */
38
+ export function parseSwarmArgs(argv) {
39
+ let model;
40
+ let thinkingEffort;
41
+ let budgetMs;
42
+ let cap;
43
+ let maxConcurrency;
44
+ let write = false;
45
+ let trailingTokens;
46
+ for (let index = 0; index < argv.length; index += 1) {
47
+ const token = argv[index];
48
+ if (token === "--thinking" || token.startsWith("--thinking=")) {
49
+ thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
50
+ continue;
51
+ }
52
+ if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
53
+ throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
54
+ }
55
+ if (token === "--") {
56
+ trailingTokens = argv.slice(index + 1);
57
+ break;
58
+ }
59
+ if (!token.startsWith("-")) {
60
+ trailingTokens = argv.slice(index);
61
+ break;
62
+ }
63
+ switch (token) {
64
+ case "-m":
65
+ case "--model": {
66
+ const value = argv[index + 1];
67
+ if (!value) {
68
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "swarm.parse");
69
+ }
70
+ if (value.startsWith("-")) {
71
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "swarm.parse");
72
+ }
73
+ model = value;
74
+ index += 1;
75
+ break;
76
+ }
77
+ case "--write":
78
+ write = true;
79
+ break;
80
+ case "--budget": {
81
+ const value = argv[index + 1];
82
+ if (!value || value.startsWith("-")) {
83
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "swarm.parse");
84
+ }
85
+ budgetMs = parseDurationMs(value, "--budget", "swarm.parse", "safety");
86
+ index += 1;
87
+ break;
88
+ }
89
+ case "--cap": {
90
+ cap = parsePositiveIntFlag("--cap", argv[index + 1]);
91
+ index += 1;
92
+ break;
93
+ }
94
+ case "--max-concurrency": {
95
+ maxConcurrency = parsePositiveIntFlag("--max-concurrency", argv[index + 1]);
96
+ index += 1;
97
+ break;
98
+ }
99
+ default:
100
+ if (looksLikeFlag(token)) {
101
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for swarm. Supported flags: ${SWARM_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped objective text to pass it through.`, "swarm.parse");
102
+ }
103
+ trailingTokens = argv.slice(index);
104
+ index = argv.length;
105
+ break;
106
+ }
107
+ }
108
+ return {
109
+ budgetMs,
110
+ cap,
111
+ maxConcurrency,
112
+ write,
113
+ model,
114
+ thinkingEffort,
115
+ objective: trailingTokens?.join(" "),
116
+ };
117
+ }
118
+ /**
119
+ * Shared positive-integer flag parser for swarm's `--cap` / `--max-concurrency`.
120
+ * Both must be a strict positive integer; `--max-concurrency` in particular maps
121
+ * to kimi-code's `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY`, whose upstream resolver
122
+ * THROWS on a non-positive-integer — so the same `Number.isInteger && > 0`
123
+ * predicate guarantees every accepted value round-trips through `String()`.
124
+ */
125
+ function parsePositiveIntFlag(flag, value) {
126
+ if (!value || value.startsWith("-")) {
127
+ throw new RuntimeError("INVALID_ARGS", `${flag} requires a positive integer.`, "swarm.parse");
128
+ }
129
+ const parsed = Number(value);
130
+ if (!Number.isInteger(parsed) || parsed <= 0) {
131
+ throw new RuntimeError("INVALID_ARGS", `${flag} must be a positive integer.`, "swarm.parse");
132
+ }
133
+ return parsed;
134
+ }
135
+ /**
136
+ * Upper bound for any `--budget` duration. The budget is the SOLE hard
137
+ * wall-clock bound on the plugin's autonomous/fan-out surfaces (pursue, swarm),
138
+ * so a typo'd or absurd value (`999999h`, a pasted nanosecond count) would
139
+ * effectively disable that bound — defeating the one guarantee these modes rest
140
+ * on. 24h is far above any legitimate budget (pursue defaults to 45m, swarm to
141
+ * 30m) yet still finite, so the AbortController always fires. Above it we
142
+ * hard-fail INVALID_ARGS rather than silently clamp — a wrapper LLM passing a
143
+ * runaway budget should see the rejection, not a quietly-different value.
144
+ */
145
+ export const MAX_DURATION_MS = 24 * 60 * 60 * 1_000;
146
+ /**
147
+ * Parse a duration token (`30m`, `1h`, `90s`, or a bare integer = minutes)
148
+ * into milliseconds. Throws INVALID_ARGS on a malformed, non-positive, or
149
+ * absurdly large value (> {@link MAX_DURATION_MS}).
150
+ */
151
+ export function parseDurationMs(token, flag, stage, budgetKind) {
152
+ const limitReason = budgetKind === "safety"
153
+ ? "the budget is a safety ceiling, not a scheduler"
154
+ : "the budget is a response-time limit; a timed-out job is still a safe failure";
155
+ const match = /^(\d+)(s|m|h)?$/.exec(token.trim());
156
+ if (!match) {
157
+ throw new RuntimeError("INVALID_ARGS", `${flag} expects a duration like 30m, 1h, or 90s (bare number = minutes); got "${token}".`, stage);
158
+ }
159
+ const value = Number(match[1]);
160
+ const unit = match[2] ?? "m";
161
+ if (!Number.isFinite(value) || value <= 0) {
162
+ throw new RuntimeError("INVALID_ARGS", `${flag} must be a positive duration.`, stage);
163
+ }
164
+ const unitMs = unit === "s" ? 1_000 : unit === "h" ? 3_600_000 : 60_000;
165
+ const ms = value * unitMs;
166
+ if (ms > MAX_DURATION_MS) {
167
+ throw new RuntimeError("INVALID_ARGS", `${flag} must be at most 24h (got "${token}"); ${limitReason}.`, stage);
168
+ }
169
+ return ms;
170
+ }
171
+ /**
172
+ * Parser for /kimi:pursue (autonomous goal mode). Foreground-only in the
173
+ * prototype: no --background/--fresh/--resume (the goalId/sessionId split makes
174
+ * session resume unreliable — see pursue.ts). Trailing tokens are the objective.
175
+ */
176
+ export function parsePursueArgs(argv) {
177
+ let model;
178
+ let thinkingEffort;
179
+ let budgetMs;
180
+ let turns;
181
+ let trailingTokens;
182
+ for (let index = 0; index < argv.length; index += 1) {
183
+ const token = argv[index];
184
+ if (token === "--thinking" || token.startsWith("--thinking=")) {
185
+ thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
186
+ continue;
187
+ }
188
+ if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
189
+ throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
190
+ }
191
+ if (token === "--") {
192
+ trailingTokens = argv.slice(index + 1);
193
+ break;
194
+ }
195
+ if (!token.startsWith("-")) {
196
+ trailingTokens = argv.slice(index);
197
+ break;
198
+ }
199
+ switch (token) {
200
+ case "-m":
201
+ case "--model": {
202
+ const value = argv[index + 1];
203
+ if (!value) {
204
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "pursue.parse");
205
+ }
206
+ if (value.startsWith("-")) {
207
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "pursue.parse");
208
+ }
209
+ model = value;
210
+ index += 1;
211
+ break;
212
+ }
213
+ case "--budget": {
214
+ const value = argv[index + 1];
215
+ if (!value || value.startsWith("-")) {
216
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "pursue.parse");
217
+ }
218
+ budgetMs = parseDurationMs(value, "--budget", "pursue.parse", "safety");
219
+ index += 1;
220
+ break;
221
+ }
222
+ case "--turns": {
223
+ const value = argv[index + 1];
224
+ if (!value || value.startsWith("-")) {
225
+ throw new RuntimeError("INVALID_ARGS", "--turns requires a positive integer.", "pursue.parse");
226
+ }
227
+ const parsedTurns = Number(value);
228
+ if (!Number.isInteger(parsedTurns) || parsedTurns <= 0) {
229
+ throw new RuntimeError("INVALID_ARGS", "--turns must be a positive integer.", "pursue.parse");
230
+ }
231
+ turns = parsedTurns;
232
+ index += 1;
233
+ break;
234
+ }
235
+ default:
236
+ if (looksLikeFlag(token)) {
237
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for pursue. Supported flags: ${PURSUE_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped objective text to pass it through.`, "pursue.parse");
238
+ }
239
+ trailingTokens = argv.slice(index);
240
+ index = argv.length;
241
+ break;
242
+ }
243
+ }
244
+ return {
245
+ budgetMs,
246
+ turns,
247
+ model,
248
+ thinkingEffort,
249
+ objective: trailingTokens?.join(" "),
250
+ };
251
+ }
252
+ const VALUE_TAKING_FLAGS = new Set(["--thinking", "-m", "--model", "--budget", "--resume", "--base", "--cap", "--max-concurrency", "--turns", "--type"]);
253
+ /**
254
+ * `--help` / `-h` counts only inside the leading flag region: before `--`
255
+ * and before the first prose token. The value of a value-taking flag is
256
+ * skipped, so `-m kimi -h` is still a help request (`kimi` is a value, not
257
+ * prose) and `--budget --help` asks for help rather than failing on a bad
258
+ * duration. Once prose starts (`ask what does du -h print`), nothing after it
259
+ * is a flag — the whole remainder is the prompt.
260
+ */
261
+ export function hasHelpFlag(argv) {
262
+ for (let index = 0; index < argv.length; index += 1) {
263
+ const token = argv[index];
264
+ if (token === "--")
265
+ return false;
266
+ if (token === "--help" || token === "-h")
267
+ return true;
268
+ if (!token.startsWith("-"))
269
+ return false;
270
+ if (VALUE_TAKING_FLAGS.has(token)) {
271
+ const value = argv[index + 1];
272
+ if (value === "--help" || value === "-h")
273
+ return true;
274
+ index += 1;
275
+ }
276
+ }
277
+ return false;
278
+ }
279
+ export function parseAskArgs(argv) {
280
+ if (hasHelpFlag(argv))
281
+ return { help: true, background: false, wait: false, fresh: false, resume: false };
282
+ let budgetMs;
283
+ let model;
284
+ let thinkingEffort;
285
+ let background = false;
286
+ let wait = false;
287
+ let fresh = false;
288
+ let resume = false;
289
+ let resumeTarget;
290
+ let explicitResumeTarget = false;
291
+ let trailingTokens;
292
+ for (let index = 0; index < argv.length; index += 1) {
293
+ const token = argv[index];
294
+ if (token === "--thinking" || token.startsWith("--thinking=")) {
295
+ thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
296
+ continue;
297
+ }
298
+ if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
299
+ throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
300
+ }
301
+ if (token === "--") {
302
+ trailingTokens = argv.slice(index + 1);
303
+ break;
304
+ }
305
+ if (!token.startsWith("-")) {
306
+ trailingTokens = argv.slice(index);
307
+ break;
308
+ }
309
+ switch (token) {
310
+ case "--budget": {
311
+ const value = argv[index + 1];
312
+ if (!value || value.startsWith("-")) {
313
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "ask.parse");
314
+ }
315
+ budgetMs = parseDurationMs(value, "--budget", "ask.parse", "response");
316
+ index += 1;
317
+ break;
318
+ }
319
+ case "-m":
320
+ case "--model": {
321
+ const value = argv[index + 1];
322
+ if (!value) {
323
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "args.parse");
324
+ }
325
+ if (value.startsWith("-")) {
326
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "args.parse");
327
+ }
328
+ model = value;
329
+ index += 1;
330
+ break;
331
+ }
332
+ case "--background":
333
+ background = true;
334
+ break;
335
+ case "--wait":
336
+ wait = true;
337
+ break;
338
+ case "--fresh":
339
+ fresh = true;
340
+ break;
341
+ case "-r":
342
+ resume = true;
343
+ break;
344
+ case "--resume": {
345
+ const value = argv[index + 1];
346
+ if (!value || value.startsWith("-")) {
347
+ throw new RuntimeError("INVALID_ARGS", "--resume requires a job-id or session-id. Use -r to resume the latest ask session for this repo.", "ask.parse");
348
+ }
349
+ resume = true;
350
+ resumeTarget = value;
351
+ explicitResumeTarget = true;
352
+ index += 1;
353
+ break;
354
+ }
355
+ default:
356
+ if (looksLikeFlag(token)) {
357
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ask. Supported flags: ${ASK_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped prompt text to pass it through.`, "args.parse");
358
+ }
359
+ trailingTokens = argv.slice(index);
360
+ index = argv.length;
361
+ break;
362
+ }
363
+ }
364
+ if (fresh && resume) {
365
+ throw new RuntimeError("INVALID_ARGS", "ask does not allow --fresh and --resume together.", "ask.parse");
366
+ }
367
+ if (explicitResumeTarget && trailingTokens?.length) {
368
+ throw new RuntimeError("INVALID_ARGS", "--resume only accepts a job-id or session-id. Use -r to resume the latest ask session with a prompt.", "ask.parse");
369
+ }
370
+ return {
371
+ budgetMs,
372
+ background,
373
+ wait,
374
+ fresh,
375
+ resume,
376
+ resumeTarget,
377
+ model,
378
+ thinkingEffort,
379
+ prompt: trailingTokens?.join(" "),
380
+ };
381
+ }
382
+ export function parseReviewArgs(argv, commandName = "review") {
383
+ const parsed = parseKnownFlags(argv, new Set(["--budget", "--base", "--background", "--wait", "-m", "--model"]), commandName);
384
+ return {
385
+ budgetMs: parsed.budgetMs,
386
+ base: parsed.base,
387
+ background: parsed.background,
388
+ wait: parsed.wait,
389
+ model: parsed.model,
390
+ thinkingEffort: parsed.thinkingEffort,
391
+ focus: parsed.trailingText,
392
+ };
393
+ }
394
+ export function parseRescueArgs(argv) {
395
+ if (hasHelpFlag(argv))
396
+ return { help: true, background: false, wait: false, fresh: false, resume: false };
397
+ let budgetMs;
398
+ let model;
399
+ let thinkingEffort;
400
+ let background = false;
401
+ let wait = false;
402
+ let fresh = false;
403
+ let resume = false;
404
+ let resumeTarget;
405
+ let explicitResumeTarget = false;
406
+ let trailingTokens;
407
+ for (let index = 0; index < argv.length; index += 1) {
408
+ const token = argv[index];
409
+ if (token === "--thinking" || token.startsWith("--thinking=")) {
410
+ thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
411
+ continue;
412
+ }
413
+ if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
414
+ throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
415
+ }
416
+ if (token === "--") {
417
+ trailingTokens = argv.slice(index + 1);
418
+ break;
419
+ }
420
+ if (!token.startsWith("-")) {
421
+ trailingTokens = argv.slice(index);
422
+ break;
423
+ }
424
+ switch (token) {
425
+ case "--budget": {
426
+ const value = argv[index + 1];
427
+ if (!value || value.startsWith("-")) {
428
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "rescue.parse");
429
+ }
430
+ budgetMs = parseDurationMs(value, "--budget", "rescue.parse", "response");
431
+ index += 1;
432
+ break;
433
+ }
434
+ case "-m":
435
+ case "--model": {
436
+ const value = argv[index + 1];
437
+ if (!value) {
438
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "args.parse");
439
+ }
440
+ if (value.startsWith("-")) {
441
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "args.parse");
442
+ }
443
+ model = value;
444
+ index += 1;
445
+ break;
446
+ }
447
+ case "--background":
448
+ background = true;
449
+ break;
450
+ case "--wait":
451
+ wait = true;
452
+ break;
453
+ case "--fresh":
454
+ fresh = true;
455
+ break;
456
+ case "-r":
457
+ resume = true;
458
+ break;
459
+ case "--resume": {
460
+ const value = argv[index + 1];
461
+ if (!value || value.startsWith("-")) {
462
+ throw new RuntimeError("INVALID_ARGS", "--resume requires a job-id or session-id. Use -r to resume the latest rescue session for this repo.", "rescue.parse");
463
+ }
464
+ resume = true;
465
+ resumeTarget = value;
466
+ explicitResumeTarget = true;
467
+ index += 1;
468
+ break;
469
+ }
470
+ default:
471
+ if (looksLikeFlag(token)) {
472
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for rescue. Supported flags: ${RESCUE_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped prompt text to pass it through.`, "args.parse");
473
+ }
474
+ trailingTokens = argv.slice(index);
475
+ index = argv.length;
476
+ break;
477
+ }
478
+ }
479
+ if (fresh && resume) {
480
+ throw new RuntimeError("INVALID_ARGS", "rescue does not allow --fresh and --resume together.", "rescue.parse");
481
+ }
482
+ if (explicitResumeTarget && trailingTokens?.length) {
483
+ throw new RuntimeError("INVALID_ARGS", "--resume only accepts a job-id or session-id. Use -r to resume the latest rescue session with a prompt.", "rescue.parse");
484
+ }
485
+ return {
486
+ budgetMs,
487
+ background,
488
+ wait,
489
+ fresh,
490
+ resume,
491
+ resumeTarget,
492
+ model,
493
+ thinkingEffort,
494
+ prompt: trailingTokens?.join(" "),
495
+ };
496
+ }
497
+ export function parseJobLookupArgs(argv) {
498
+ let type;
499
+ let jobId;
500
+ let json = false;
501
+ for (let index = 0; index < argv.length; index += 1) {
502
+ const token = argv[index];
503
+ if (token === "--type") {
504
+ const value = argv[index + 1];
505
+ if (!value || !isManagedCommandType(value)) {
506
+ throw new RuntimeError("INVALID_ARGS", "--type requires one of review, challenge, rescue, review_gate, ask.", "args.parse");
507
+ }
508
+ type = value;
509
+ index += 1;
510
+ continue;
511
+ }
512
+ if (token === "--json") {
513
+ json = true;
514
+ continue;
515
+ }
516
+ if (token.startsWith("-")) {
517
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token}. Supported flags: ${JOB_LOOKUP_SUPPORTED_FLAGS}.`, "args.parse");
518
+ }
519
+ if (jobId) {
520
+ throw new RuntimeError("INVALID_ARGS", "Only one optional job id may be supplied.", "args.parse");
521
+ }
522
+ jobId = token;
523
+ }
524
+ return {
525
+ jobId,
526
+ type,
527
+ json,
528
+ };
529
+ }
530
+ function isManagedCommandType(value) {
531
+ return (value === "review" ||
532
+ value === "challenge" ||
533
+ value === "rescue" ||
534
+ value === "review_gate" ||
535
+ value === "ask");
536
+ }
537
+ function parseKnownFlags(argv, knownFlags, commandName) {
538
+ let budgetMs;
539
+ let model;
540
+ let thinkingEffort;
541
+ let base;
542
+ let background = false;
543
+ let wait = false;
544
+ let trailingTokens;
545
+ for (let index = 0; index < argv.length; index += 1) {
546
+ const token = argv[index];
547
+ if (token === "--thinking" || token.startsWith("--thinking=")) {
548
+ thinkingEffort = validateThinkingEffort(token === "--thinking" ? argv[++index] : token.slice("--thinking=".length));
549
+ continue;
550
+ }
551
+ if (token === "--no-thinking" || token.startsWith("--no-thinking=")) {
552
+ throw new RuntimeError("INVALID_ARGS", "--no-thinking is unsupported; use --thinking off", "args.parse");
553
+ }
554
+ if (token === "--") {
555
+ trailingTokens = argv.slice(index + 1);
556
+ break;
557
+ }
558
+ if (!knownFlags.has(token)) {
559
+ if (looksLikeFlag(token)) {
560
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ${commandName}. Supported flags: ${REVIEW_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped focus text to pass it through.`, "args.parse");
561
+ }
562
+ trailingTokens = argv.slice(index);
563
+ break;
564
+ }
565
+ switch (token) {
566
+ case "--budget": {
567
+ const value = argv[index + 1];
568
+ if (!value || value.startsWith("-")) {
569
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", `${commandName}.parse`);
570
+ }
571
+ budgetMs = parseDurationMs(value, "--budget", `${commandName}.parse`, "response");
572
+ index += 1;
573
+ break;
574
+ }
575
+ case "-m":
576
+ case "--model": {
577
+ const value = argv[index + 1];
578
+ if (!value) {
579
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "args.parse");
580
+ }
581
+ if (value.startsWith("-")) {
582
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "args.parse");
583
+ }
584
+ model = value;
585
+ index += 1;
586
+ break;
587
+ }
588
+ case "--base": {
589
+ const value = argv[index + 1];
590
+ if (!value) {
591
+ throw new RuntimeError("INVALID_ARGS", "--base requires a ref value.", "args.parse");
592
+ }
593
+ if (value.startsWith("-")) {
594
+ throw new RuntimeError("INVALID_ARGS", "--base value cannot start with '-'; pass a branch, tag, or commit ref.", "args.parse");
595
+ }
596
+ base = value;
597
+ index += 1;
598
+ break;
599
+ }
600
+ case "--background":
601
+ background = true;
602
+ break;
603
+ case "--wait":
604
+ wait = true;
605
+ break;
606
+ // The early `if (!knownFlags.has(token))` gate above means we only
607
+ // reach this switch with tokens that ARE in knownFlags, and every
608
+ // member is handled above. A `default` arm would be unreachable.
609
+ }
610
+ }
611
+ return {
612
+ budgetMs,
613
+ base,
614
+ background,
615
+ wait,
616
+ model,
617
+ thinkingEffort,
618
+ trailingText: trailingTokens?.join(" "),
619
+ };
620
+ }
@@ -0,0 +1,27 @@
1
+ import { constants } from "node:fs";
2
+ import { access, mkdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { resolvePluginDataRoot } from "./plugin-data.js";
5
+ export function resolvePluginPaths(env) {
6
+ const claudePluginData = resolvePluginDataRoot(env);
7
+ const pluginRoot = path.join(claudePluginData, "kimi-plugin-cc");
8
+ return {
9
+ claudePluginData,
10
+ pluginRoot,
11
+ stateDbPath: path.join(pluginRoot, "state.db"),
12
+ logsDir: path.join(pluginRoot, "logs"),
13
+ artifactsDir: path.join(pluginRoot, "artifacts"),
14
+ worktreesDir: path.join(pluginRoot, "worktrees"),
15
+ configPath: path.join(pluginRoot, "config.json"),
16
+ };
17
+ }
18
+ export async function ensurePluginPaths(paths) {
19
+ await mkdir(paths.pluginRoot, { recursive: true });
20
+ await mkdir(paths.logsDir, { recursive: true });
21
+ await mkdir(paths.artifactsDir, { recursive: true });
22
+ await access(paths.pluginRoot, constants.R_OK | constants.W_OK);
23
+ await access(paths.logsDir, constants.R_OK | constants.W_OK);
24
+ await access(paths.artifactsDir, constants.R_OK | constants.W_OK);
25
+ // worktreesDir is created lazily by the write-swarm path (not every command
26
+ // needs it), so it is intentionally NOT required here.
27
+ }