@dadado/agent-kit-cli 5.15.1 → 5.17.0

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 (72) hide show
  1. package/dashboard/dashboard-data.mjs +69 -65
  2. package/dashboard/lib/guards.d.mts +5 -0
  3. package/dashboard/lib/guards.mjs +31 -0
  4. package/dashboard/lib/identity-probe.mjs +56 -0
  5. package/dashboard/lib/pipeline-cache.mjs +58 -0
  6. package/dashboard/lib/processes.mjs +71 -0
  7. package/dashboard/serve.mjs +43 -1
  8. package/dashboard/start-broadcast.mjs +2 -12
  9. package/dashboard/start.mjs +2 -13
  10. package/dist/add-HTYIFKMN.js +150 -0
  11. package/dist/chunk-2IPXQFJE.js +253 -0
  12. package/dist/chunk-2RDRMGD3.js +82 -0
  13. package/dist/chunk-2YU2H7I6.js +42 -0
  14. package/dist/chunk-4IWG5CVE.js +214 -0
  15. package/dist/chunk-5VCOWTTP.js +6 -0
  16. package/dist/chunk-77V75A7O.js +39 -0
  17. package/dist/chunk-7HDQEK6T.js +26 -0
  18. package/dist/chunk-AW74INUW.js +1456 -0
  19. package/dist/chunk-B73KQ5KX.js +972 -0
  20. package/dist/chunk-CBCW5R5J.js +484 -0
  21. package/dist/chunk-DE3LQFSJ.js +18 -0
  22. package/dist/chunk-DQA434Y4.js +106 -0
  23. package/dist/chunk-F4VMYR7U.js +318 -0
  24. package/dist/chunk-F6C3GFKV.js +57 -0
  25. package/dist/chunk-HFUH3HVS.js +282 -0
  26. package/dist/chunk-HS7LVVLO.js +74 -0
  27. package/dist/chunk-IHROBTNM.js +135 -0
  28. package/dist/chunk-KD2MYBB6.js +213 -0
  29. package/dist/chunk-MAPOQEA2.js +43 -0
  30. package/dist/chunk-N3HTJVBR.js +341 -0
  31. package/dist/chunk-NQ7IQ5XT.js +26 -0
  32. package/dist/chunk-OUQZC744.js +1315 -0
  33. package/dist/chunk-PCCQUWE5.js +817 -0
  34. package/dist/chunk-PNFF6OU3.js +273 -0
  35. package/dist/chunk-PNG7LO2F.js +53 -0
  36. package/dist/chunk-QTON2GQX.js +203 -0
  37. package/dist/chunk-S6GMFDKV.js +50 -0
  38. package/dist/chunk-SMGFN3Z4.js +503 -0
  39. package/dist/chunk-STGFBAYT.js +641 -0
  40. package/dist/chunk-TFZ2JUXH.js +1081 -0
  41. package/dist/chunk-VY7MTPJ2.js +205 -0
  42. package/dist/chunk-W24CMQ32.js +525 -0
  43. package/dist/chunk-WW3YZKHW.js +165 -0
  44. package/dist/chunk-ZB43YMHW.js +20 -0
  45. package/dist/chunk-ZCTSUQRR.js +874 -0
  46. package/dist/chunk-ZXKZBTXE.js +17 -0
  47. package/dist/contribute-C6AW2P6J.js +367 -0
  48. package/dist/cursor-awareness-KTL2RGKD.js +77 -0
  49. package/dist/dashboard-YSNUGBFY.js +21 -0
  50. package/dist/dashboard-broadcast-M4CMW7RX.js +119 -0
  51. package/dist/diff-VHNR62SI.js +74 -0
  52. package/dist/doctor-PET2HRRJ.js +236 -0
  53. package/dist/dogfood-KKFVRWT6.js +366 -0
  54. package/dist/guard-EHXF76ID.js +530 -0
  55. package/dist/handoff-3UI6VKVG.js +302 -0
  56. package/dist/hook-DYIMKJYL.js +1074 -0
  57. package/dist/index.js +62 -14068
  58. package/dist/init-Q4LBM6KT.js +127 -0
  59. package/dist/install-RHOS3OYZ.js +34 -0
  60. package/dist/mission-control-LJACEVVN.js +89 -0
  61. package/dist/monitors-QV2IS3MP.js +176 -0
  62. package/dist/plan-index-JMC7Z4DW.js +44 -0
  63. package/dist/public-inbound-radar-WP3XF7GS.js +68 -0
  64. package/dist/remote-issues-SZ4HDQL5.js +44 -0
  65. package/dist/run-I3BFGULT.js +243 -0
  66. package/dist/run-plan-FO7CL4M3.js +118 -0
  67. package/dist/scan-NI2SWJ2E.js +33 -0
  68. package/dist/setup-global-M42GEODI.js +340 -0
  69. package/dist/status-XPVQVQMX.js +150 -0
  70. package/dist/update-FJ2EBZVO.js +283 -0
  71. package/dist/validate-V2CW7J4B.js +201 -0
  72. package/package.json +3 -3
@@ -0,0 +1,1315 @@
1
+ import {
2
+ HEADLESS_DENY_RULES
3
+ } from "./chunk-MAPOQEA2.js";
4
+ import {
5
+ HELMET_ACCENT,
6
+ LABEL_MUTED,
7
+ SPACE_MARKS,
8
+ STATUS_ERR,
9
+ STATUS_OK,
10
+ STATUS_WARN,
11
+ shouldUseWelcomeColor,
12
+ withKoloristColor
13
+ } from "./chunk-QTON2GQX.js";
14
+
15
+ // src/plan-loop/hitl-relay.ts
16
+ import { createInterface } from "readline";
17
+ import { StringDecoder } from "string_decoder";
18
+
19
+ // src/plan-loop/line-splitter.ts
20
+ var PENDING_MAX_CHARS = 4 * 1024 * 1024;
21
+ var LineSplitter = class {
22
+ constructor(maxChars = PENDING_MAX_CHARS) {
23
+ this.maxChars = maxChars;
24
+ }
25
+ maxChars;
26
+ parts = [];
27
+ length = 0;
28
+ dropping = false;
29
+ /** Calls `onLine` for every line the chunk completes (without the `\n`). */
30
+ feed(text, onLine) {
31
+ let start = 0;
32
+ let nl = text.indexOf("\n", start);
33
+ while (nl !== -1) {
34
+ const segment = text.slice(start, nl);
35
+ if (this.dropping) {
36
+ this.dropping = false;
37
+ } else if (this.parts.length === 0) {
38
+ onLine(segment);
39
+ } else {
40
+ this.parts.push(segment);
41
+ const line = this.parts.join("");
42
+ this.parts = [];
43
+ this.length = 0;
44
+ onLine(line);
45
+ }
46
+ start = nl + 1;
47
+ nl = text.indexOf("\n", start);
48
+ }
49
+ if (start >= text.length || this.dropping) return;
50
+ const tail = start === 0 ? text : text.slice(start);
51
+ this.parts.push(tail);
52
+ this.length += tail.length;
53
+ if (this.length > this.maxChars) {
54
+ this.parts = [];
55
+ this.length = 0;
56
+ this.dropping = true;
57
+ }
58
+ }
59
+ /** The held partial line ("" when none or when it was dropped); resets. */
60
+ end() {
61
+ const rest = this.dropping ? "" : this.parts.join("");
62
+ this.parts = [];
63
+ this.length = 0;
64
+ this.dropping = false;
65
+ return rest;
66
+ }
67
+ };
68
+
69
+ // src/plan-loop/hitl-relay.ts
70
+ var HITL_GATE_LINE_RE = /^HITL_GATE:\s*([a-z0-9-]+)\s*\|\s*(.+)$/;
71
+ var HITL_REPLY_PREFIX = "HITL_REPLY:";
72
+ var RESERVED_GATE_IDS = ["git-prod", "kit-prod"];
73
+ var FALLBACK_ASK_ID = "prose-fallback";
74
+ var HITL_UNANSWERED_EXIT_CODE = 4;
75
+ var HITL_SIGINT_EXIT_CODE = 130;
76
+ var NUMBERED_LINE_RE = /^\s*(\d+)\.\s+(.+?)\s*$/;
77
+ var FALLBACK_WORDING_RES = [
78
+ /reply with (?:the |a )?number/i,
79
+ /ask questions is not available/i,
80
+ /skip or cancel means stop/i
81
+ ];
82
+ var TICK_SENTINEL_RE = /LOOP_TICK_RESULT:/;
83
+ function parseHitlGateLine(line) {
84
+ const m = HITL_GATE_LINE_RE.exec(line.trim());
85
+ if (!m?.[1] || !m[2]) return null;
86
+ const labels = m[2].split("|").map((s) => s.trim()).filter((s) => s.length > 0);
87
+ if (labels.length === 0) return null;
88
+ return { askId: m[1], labels, detection: "sentinel" };
89
+ }
90
+ function findHitlGateInText(text) {
91
+ let last = null;
92
+ for (const line of text.split(/\r?\n/)) {
93
+ const gate = parseHitlGateLine(line);
94
+ if (gate) last = gate;
95
+ }
96
+ return last;
97
+ }
98
+ function cleanFallbackLabel(raw) {
99
+ let label = raw.trim();
100
+ const bold = /^\*\*(.+?)\*\*/.exec(label);
101
+ if (bold?.[1]) label = bold[1];
102
+ else label = label.replace(/\*\*/g, "").replace(/^`(.+)`$/, "$1");
103
+ label = label.split(/\s+[—–]\s+/)[0] ?? label;
104
+ return label.replace(/[.:]+$/, "").trim();
105
+ }
106
+ function detectFallbackGate(text) {
107
+ if (!FALLBACK_WORDING_RES.some((re) => re.test(text))) return null;
108
+ const labels = [];
109
+ let expected = 1;
110
+ for (const line of text.split(/\r?\n/)) {
111
+ const m = NUMBERED_LINE_RE.exec(line);
112
+ if (!m?.[1] || !m[2]) continue;
113
+ const n = Number(m[1]);
114
+ if (n === 1) {
115
+ labels.length = 0;
116
+ expected = 1;
117
+ }
118
+ if (n !== expected) continue;
119
+ labels.push(cleanFallbackLabel(m[2]));
120
+ expected += 1;
121
+ }
122
+ if (labels.length < 2) return null;
123
+ return { askId: FALLBACK_ASK_ID, labels, detection: "fallback" };
124
+ }
125
+ function detectHitlGate(resultText, assistantTexts) {
126
+ if (TICK_SENTINEL_RE.test(resultText)) return null;
127
+ const fromResult = findHitlGateInText(resultText);
128
+ if (fromResult) return fromResult;
129
+ for (let i = assistantTexts.length - 1; i >= 0; i -= 1) {
130
+ const text = assistantTexts[i] ?? "";
131
+ if (TICK_SENTINEL_RE.test(text)) return null;
132
+ const gate = findHitlGateInText(text);
133
+ if (gate) return gate;
134
+ }
135
+ const fallback = detectFallbackGate(resultText);
136
+ if (fallback) return fallback;
137
+ for (let i = assistantTexts.length - 1; i >= 0; i -= 1) {
138
+ const gate = detectFallbackGate(assistantTexts[i] ?? "");
139
+ if (gate) return gate;
140
+ }
141
+ return null;
142
+ }
143
+ function isReservedGateId(askId) {
144
+ return RESERVED_GATE_IDS.includes(askId);
145
+ }
146
+ var STOP_WORDS = /* @__PURE__ */ new Set(["skip", "cancel"]);
147
+ function resolveOperatorAnswer(input, labels) {
148
+ const text = input.trim();
149
+ if (!text) return { kind: "stop", cause: "empty" };
150
+ const exact = labels.indexOf(text);
151
+ if (exact !== -1) return { kind: "reply", n: exact + 1, label: labels[exact] ?? text };
152
+ if (/^\d+$/.test(text)) {
153
+ const n = Number(text);
154
+ const label = labels[n - 1];
155
+ if (n >= 1 && label !== void 0) return { kind: "reply", n, label };
156
+ }
157
+ const lower = text.toLowerCase();
158
+ if (STOP_WORDS.has(lower)) return { kind: "stop", cause: lower };
159
+ return { kind: "other", text };
160
+ }
161
+ function formatHitlReply(askId, answer) {
162
+ if (answer.kind === "reply") {
163
+ return `${HITL_REPLY_PREFIX} ${askId} | operator reply ${answer.n} | ${answer.label}`;
164
+ }
165
+ if (answer.kind === "other") {
166
+ return `${HITL_REPLY_PREFIX} ${askId} | operator reply other | ${answer.text}`;
167
+ }
168
+ throw new Error("a stop answer has no reply stamp");
169
+ }
170
+ function createTerminalAnswerReader(opts = {}) {
171
+ return (_gate, readOpts) => new Promise((resolve) => {
172
+ const input = opts.input ?? process.stdin;
173
+ const output = opts.output ?? process.stdout;
174
+ const terminal = opts.terminal ?? Boolean(input.isTTY);
175
+ const rl = createInterface({ input, output, terminal });
176
+ let settled = false;
177
+ const finish = (answer) => {
178
+ if (settled) return;
179
+ settled = true;
180
+ readOpts.signal?.removeEventListener("abort", onAbort);
181
+ rl.close();
182
+ if (typeof input.pause === "function") {
183
+ input.pause();
184
+ }
185
+ resolve(answer);
186
+ };
187
+ const onAbort = () => finish({ kind: "eof" });
188
+ if (readOpts.signal?.aborted) {
189
+ onAbort();
190
+ return;
191
+ }
192
+ readOpts.signal?.addEventListener("abort", onAbort, { once: true });
193
+ rl.on("SIGINT", () => finish({ kind: "sigint" }));
194
+ rl.on("close", () => finish({ kind: "eof" }));
195
+ rl.question("> ", (line) => finish({ kind: "line", text: line }));
196
+ });
197
+ }
198
+ function emptyHitlRecord() {
199
+ return { replies: [], fallbackDetections: 0 };
200
+ }
201
+ function operatorStop(askId, cause) {
202
+ return {
203
+ askId,
204
+ cause,
205
+ message: `stopped-by-operator: gate ${askId}, no reply (${cause})`,
206
+ exitCode: cause === "SIGINT" ? HITL_SIGINT_EXIT_CODE : HITL_UNANSWERED_EXIT_CODE
207
+ };
208
+ }
209
+ function operatorQuitStop(key) {
210
+ return {
211
+ askId: "none",
212
+ cause: "quit",
213
+ message: `stopped-by-operator: quit (${key}), no gate open`,
214
+ exitCode: HITL_SIGINT_EXIT_CODE
215
+ };
216
+ }
217
+ function unansweredStop(askId, cause) {
218
+ return {
219
+ askId,
220
+ cause,
221
+ message: `stopped: unanswered gate (${cause})`,
222
+ exitCode: HITL_UNANSWERED_EXIT_CODE
223
+ };
224
+ }
225
+ function noRelayStop(askId, backend) {
226
+ return {
227
+ askId,
228
+ cause: "no relay",
229
+ message: `stopped: unanswered gate (backend ${backend} has no relay)`,
230
+ exitCode: HITL_UNANSWERED_EXIT_CODE
231
+ };
232
+ }
233
+ function reservedStop(askId) {
234
+ return {
235
+ askId,
236
+ cause: "reserved",
237
+ message: `stopped: gate ${askId} is operator-gated; run the /${askId} slash yourself (never headless)`,
238
+ exitCode: HITL_UNANSWERED_EXIT_CODE
239
+ };
240
+ }
241
+ function formatGatePrompt(gate) {
242
+ const head = gate.detection === "fallback" ? `HITL gate (prose fallback, ask-id ${gate.askId})` : `HITL gate ${gate.askId}`;
243
+ const lines = [
244
+ "",
245
+ `${head}: reply with the number or the label; anything else is sent as "other"; empty, skip, cancel or Ctrl-C stops the run.`,
246
+ ...gate.labels.map((label, i) => ` ${i + 1}. ${label}`)
247
+ ];
248
+ return `${lines.join("\n")}
249
+ `;
250
+ }
251
+ async function askOperator(gate, opts) {
252
+ if (isReservedGateId(gate.askId)) return { kind: "stop", stop: reservedStop(gate.askId) };
253
+ if (opts.policy === "off") return { kind: "stop", stop: unansweredStop(gate.askId, "--no-hitl") };
254
+ if (!opts.isTTY) return { kind: "stop", stop: unansweredStop(gate.askId, "no TTY") };
255
+ opts.onPromptStart?.();
256
+ let raw;
257
+ try {
258
+ opts.write(formatGatePrompt(gate));
259
+ raw = await opts.readAnswer(gate, { signal: opts.signal });
260
+ } finally {
261
+ opts.onPromptEnd?.();
262
+ }
263
+ if (opts.signal?.aborted) {
264
+ return { kind: "stop", stop: unansweredStop(gate.askId, "child exited") };
265
+ }
266
+ if (raw.kind === "eof") return { kind: "stop", stop: operatorStop(gate.askId, "EOF") };
267
+ if (raw.kind === "sigint") return { kind: "stop", stop: operatorStop(gate.askId, "SIGINT") };
268
+ const answer = resolveOperatorAnswer(raw.text, gate.labels);
269
+ if (answer.kind === "stop") return { kind: "stop", stop: operatorStop(gate.askId, answer.cause) };
270
+ const line = formatHitlReply(gate.askId, answer);
271
+ const stamp = {
272
+ askId: gate.askId,
273
+ reply: answer.kind === "reply" ? `operator reply ${answer.n}` : "operator reply other",
274
+ label: answer.kind === "reply" ? answer.label : answer.text,
275
+ line,
276
+ at: (/* @__PURE__ */ new Date()).toISOString(),
277
+ detection: gate.detection
278
+ };
279
+ return { kind: "reply", stamp };
280
+ }
281
+ function formatHitlSummary(record) {
282
+ if (!record) return [];
283
+ const lines = record.replies.map(
284
+ (s) => `HITL ${s.askId}: ${s.reply} (${s.label}) ${s.at}${s.detection === "fallback" ? " [fallback]" : ""}`
285
+ );
286
+ if (record.fallbackDetections > 0) {
287
+ lines.push(`hitl-detect: prose-fallback x${record.fallbackDetections}`);
288
+ }
289
+ if (record.stop) lines.push(record.stop.message);
290
+ return lines;
291
+ }
292
+ function hitlExitCode(childExitCode, record) {
293
+ if (record?.stop) return record.stop.exitCode;
294
+ return childExitCode;
295
+ }
296
+ function userEventLine(text) {
297
+ return `${JSON.stringify({ type: "user", message: { role: "user", content: text } })}
298
+ `;
299
+ }
300
+ var TurnWatcher = class {
301
+ constructor(onResult) {
302
+ this.onResult = onResult;
303
+ }
304
+ onResult;
305
+ lines = new LineSplitter();
306
+ assistantTexts = [];
307
+ decoder = new StringDecoder("utf8");
308
+ feed(chunk) {
309
+ const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
310
+ if (!text) return;
311
+ this.lines.feed(text, (line) => this.take(line));
312
+ }
313
+ end() {
314
+ this.lines.feed(this.decoder.end(), (line) => this.take(line));
315
+ const rest = this.lines.end();
316
+ if (rest.trim()) this.take(rest);
317
+ }
318
+ take(line) {
319
+ const trimmed = line.trim();
320
+ if (!trimmed.startsWith("{")) return;
321
+ let event;
322
+ try {
323
+ event = JSON.parse(trimmed);
324
+ } catch {
325
+ return;
326
+ }
327
+ if (typeof event !== "object" || event === null) return;
328
+ const ev = event;
329
+ if (ev.type === "assistant") {
330
+ const content = ev.message?.content;
331
+ if (typeof content === "string") {
332
+ if (content) this.assistantTexts.push(content);
333
+ } else if (Array.isArray(content)) {
334
+ for (const part of content) {
335
+ if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") {
336
+ this.assistantTexts.push(part.text);
337
+ }
338
+ }
339
+ }
340
+ return;
341
+ }
342
+ if (ev.type === "result") {
343
+ const texts = this.assistantTexts;
344
+ this.assistantTexts = [];
345
+ this.onResult({
346
+ resultText: typeof ev.result === "string" ? ev.result : "",
347
+ assistantTexts: texts,
348
+ isError: ev.is_error === true,
349
+ subtype: typeof ev.subtype === "string" ? ev.subtype : null,
350
+ errors: Array.isArray(ev.errors) ? ev.errors.filter((e) => typeof e === "string") : []
351
+ });
352
+ }
353
+ }
354
+ };
355
+
356
+ // src/plan-loop/backends.ts
357
+ import { execFileSync, spawn } from "child_process";
358
+ import { createWriteStream } from "fs";
359
+ import { access } from "fs/promises";
360
+ import path from "path";
361
+ import { StringDecoder as StringDecoder3 } from "string_decoder";
362
+
363
+ // src/plan-loop/stream-render.ts
364
+ import { StringDecoder as StringDecoder2 } from "string_decoder";
365
+ import { trueColor } from "kolorist";
366
+
367
+ // src/plan-loop/stream-events.ts
368
+ function isRecord(value) {
369
+ return typeof value === "object" && value !== null && !Array.isArray(value);
370
+ }
371
+ function asString(value) {
372
+ return typeof value === "string" ? value : null;
373
+ }
374
+ function asNumber(value) {
375
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
376
+ }
377
+ function oneLine(text) {
378
+ return text.replace(/\s+/g, " ").trim();
379
+ }
380
+ var SUMMARY_KEYS = [
381
+ "command",
382
+ "file_path",
383
+ "path",
384
+ "pattern",
385
+ "query",
386
+ "url",
387
+ "description",
388
+ "prompt",
389
+ "content"
390
+ ];
391
+ function summarizeToolInput(input) {
392
+ if (typeof input === "string") return oneLine(input);
393
+ if (!isRecord(input)) return input === void 0 ? "" : oneLine(JSON.stringify(input));
394
+ for (const key of SUMMARY_KEYS) {
395
+ const value = input[key];
396
+ if (typeof value === "string" && value.trim()) return oneLine(value);
397
+ }
398
+ const keys = Object.keys(input);
399
+ if (keys.length === 0) return "";
400
+ return oneLine(JSON.stringify(input));
401
+ }
402
+ function firstLineOfContent(content) {
403
+ if (typeof content === "string") return firstNonEmptyLine(content);
404
+ if (Array.isArray(content)) {
405
+ for (const part of content) {
406
+ if (isRecord(part) && typeof part.text === "string") {
407
+ const line = firstNonEmptyLine(part.text);
408
+ if (line) return line;
409
+ }
410
+ }
411
+ return "";
412
+ }
413
+ if (isRecord(content)) return oneLine(JSON.stringify(content));
414
+ return "";
415
+ }
416
+ function firstNonEmptyLine(text) {
417
+ for (const line of text.split(/\r?\n/)) {
418
+ const trimmed = line.trim();
419
+ if (trimmed) return trimmed;
420
+ }
421
+ return "";
422
+ }
423
+ function parseAssistant(event) {
424
+ const message = event.message;
425
+ const content = isRecord(message) ? message.content : void 0;
426
+ if (typeof content === "string") return content ? [{ kind: "text", text: content }] : [];
427
+ if (!Array.isArray(content)) return [{ kind: "silent" }];
428
+ const out = [];
429
+ for (const part of content) {
430
+ if (!isRecord(part)) continue;
431
+ const type = asString(part.type);
432
+ if (type === "text" && typeof part.text === "string") {
433
+ if (part.text) out.push({ kind: "text", text: part.text });
434
+ } else if (type === "tool_use" || type === "server_tool_use") {
435
+ out.push({
436
+ kind: "tool_use",
437
+ name: asString(part.name) ?? "tool",
438
+ summary: summarizeToolInput(part.input)
439
+ });
440
+ }
441
+ }
442
+ return out.length > 0 ? out : [{ kind: "silent" }];
443
+ }
444
+ var HITL_REPLY_PREFIX2 = "HITL_REPLY:";
445
+ function parseUser(event) {
446
+ const message = event.message;
447
+ const content = isRecord(message) ? message.content : void 0;
448
+ if (typeof content === "string") {
449
+ if (event.isReplay === true && content.startsWith(HITL_REPLY_PREFIX2)) {
450
+ return [{ kind: "hitl_reply", line: oneLine(content) }];
451
+ }
452
+ return [{ kind: "prompt", chars: content.length }];
453
+ }
454
+ if (!Array.isArray(content)) return [{ kind: "silent" }];
455
+ const out = [];
456
+ let promptChars = 0;
457
+ for (const part of content) {
458
+ if (!isRecord(part)) continue;
459
+ const type = asString(part.type);
460
+ if (type === "tool_result") {
461
+ out.push({
462
+ kind: "tool_result",
463
+ ok: part.is_error !== true,
464
+ line: firstLineOfContent(part.content)
465
+ });
466
+ } else if (type === "text" && typeof part.text === "string") {
467
+ if (event.isReplay === true && part.text.startsWith(HITL_REPLY_PREFIX2)) {
468
+ out.push({ kind: "hitl_reply", line: oneLine(part.text) });
469
+ continue;
470
+ }
471
+ promptChars += part.text.length;
472
+ }
473
+ }
474
+ if (promptChars > 0) out.push({ kind: "prompt", chars: promptChars });
475
+ return out.length > 0 ? out : [{ kind: "silent" }];
476
+ }
477
+ function parseResult(event) {
478
+ const isError = event.is_error === true;
479
+ const subtype = asString(event.subtype);
480
+ return {
481
+ kind: "result",
482
+ ok: !isError,
483
+ status: subtype ?? (isError ? "error" : "success"),
484
+ costUsd: asNumber(event.total_cost_usd),
485
+ durationMs: asNumber(event.duration_ms),
486
+ apiMs: asNumber(event.duration_api_ms),
487
+ turns: asNumber(event.num_turns)
488
+ };
489
+ }
490
+ function parseSystem(event) {
491
+ const subtype = asString(event.subtype) ?? "";
492
+ if (!subtype.startsWith("hook_")) return { kind: "silent" };
493
+ const phase = subtype.slice("hook_".length);
494
+ const name = asString(event.hook_name) ?? asString(event.hook_event) ?? "hook";
495
+ const details = [];
496
+ const outcome = asString(event.outcome);
497
+ if (outcome) details.push(outcome);
498
+ const exitCode = asNumber(event.exit_code);
499
+ if (exitCode !== void 0) details.push(`exit ${exitCode}`);
500
+ return { kind: "hook", name, phase, detail: details.join(", ") };
501
+ }
502
+ function percent(value) {
503
+ const n = asNumber(value);
504
+ return n === void 0 ? null : `${Math.round(n * 100)}%`;
505
+ }
506
+ function parseRateLimit(event) {
507
+ const info = isRecord(event.rate_limit_info) ? event.rate_limit_info : event;
508
+ const status = asString(info.status) ?? "unknown";
509
+ const type = asString(info.rateLimitType);
510
+ const parts = [];
511
+ if (type) parts.push(type);
512
+ parts.push(status);
513
+ const windows = isRecord(info.unifiedWindows) ? info.unifiedWindows : null;
514
+ if (windows) {
515
+ for (const [name, window] of Object.entries(windows)) {
516
+ if (!isRecord(window)) continue;
517
+ const used = percent(window.utilization);
518
+ if (used) parts.push(`${name} ${used}`);
519
+ }
520
+ }
521
+ return { kind: "rate_limit", line: parts.join(" \xB7 "), severe: status !== "allowed" };
522
+ }
523
+ function parseCursorToolCall(event) {
524
+ const call = isRecord(event.tool_call) ? event.tool_call : null;
525
+ const entry = call ? Object.entries(call).find(([, v]) => isRecord(v)) : void 0;
526
+ const name = entry ? entry[0].replace(/ToolCall$/, "") : "tool";
527
+ const body = entry && isRecord(entry[1]) ? entry[1] : {};
528
+ const subtype = asString(event.subtype) ?? "";
529
+ if (subtype === "started") {
530
+ return { kind: "tool_use", name, summary: summarizeToolInput(body.args) };
531
+ }
532
+ if (subtype === "completed") {
533
+ const result = isRecord(body.result) ? body.result : null;
534
+ const failed = result !== null && ("error" in result || "failure" in result || "rejected" in result);
535
+ let line = "";
536
+ if (result) {
537
+ const payload = failed ? result.error ?? result.failure ?? result.rejected : result.success;
538
+ line = firstLineOfContent(
539
+ typeof payload === "string" ? payload : payload === void 0 ? result : payload
540
+ );
541
+ }
542
+ return { kind: "tool_result", ok: !failed, line };
543
+ }
544
+ return { kind: "silent" };
545
+ }
546
+ function parseStreamLine(line) {
547
+ const trimmed = line.trim();
548
+ if (!trimmed || !trimmed.startsWith("{")) return [{ kind: "unparsed" }];
549
+ let event;
550
+ try {
551
+ event = JSON.parse(trimmed);
552
+ } catch {
553
+ return [{ kind: "unparsed" }];
554
+ }
555
+ if (!isRecord(event)) return [{ kind: "unparsed" }];
556
+ switch (asString(event.type)) {
557
+ case "assistant":
558
+ return parseAssistant(event);
559
+ case "user":
560
+ return parseUser(event);
561
+ case "result":
562
+ return [parseResult(event)];
563
+ case "system":
564
+ return [parseSystem(event)];
565
+ case "rate_limit_event":
566
+ return [parseRateLimit(event)];
567
+ case "tool_call":
568
+ return [parseCursorToolCall(event)];
569
+ default:
570
+ return [{ kind: "silent" }];
571
+ }
572
+ }
573
+
574
+ // src/plan-loop/stream-render.ts
575
+ var PENDING_MAX_CHARS2 = 4 * 1024 * 1024;
576
+ var MIN_COLUMNS = 40;
577
+ function paint(hex, text, color) {
578
+ if (!color) return text;
579
+ return withKoloristColor(
580
+ () => trueColor(
581
+ Number.parseInt(hex.slice(1, 3), 16),
582
+ Number.parseInt(hex.slice(3, 5), 16),
583
+ Number.parseInt(hex.slice(5, 7), 16)
584
+ )(text)
585
+ );
586
+ }
587
+ function truncate(text, width) {
588
+ if (text.length <= width) return text;
589
+ return `${text.slice(0, Math.max(1, width - 1))}\u2026`;
590
+ }
591
+ function formatDuration(ms) {
592
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
593
+ const seconds = ms / 1e3;
594
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
595
+ const minutes = Math.floor(seconds / 60);
596
+ const rest = Math.round(seconds - minutes * 60);
597
+ return `${minutes}m ${rest}s`;
598
+ }
599
+ function formatCost(usd) {
600
+ return `$${usd.toFixed(usd < 0.1 ? 4 : 2)}`;
601
+ }
602
+ function renderStreamEvent(event, opts) {
603
+ const width = Math.max(MIN_COLUMNS, opts.columns);
604
+ const color = opts.color;
605
+ switch (event.kind) {
606
+ case "text":
607
+ return event.text;
608
+ case "tool_use": {
609
+ const head = `${SPACE_MARKS.diamond} ${event.name}`;
610
+ const summary = truncate(event.summary, width - head.length - 3);
611
+ return paint(HELMET_ACCENT, summary ? `${head} ${SPACE_MARKS.tick} ${summary}` : head, color);
612
+ }
613
+ case "tool_result": {
614
+ const label = event.ok ? "ok" : "error";
615
+ const head = ` ${SPACE_MARKS.tick} ${label}`;
616
+ const line = truncate(oneLine(event.line), width - head.length - 3);
617
+ const text = line ? `${head} ${SPACE_MARKS.tick} ${line}` : head;
618
+ return paint(event.ok ? LABEL_MUTED : STATUS_ERR, text, color);
619
+ }
620
+ case "result": {
621
+ const parts = [`${SPACE_MARKS.star} result ${event.status}`];
622
+ if (event.costUsd !== void 0) parts.push(formatCost(event.costUsd));
623
+ if (event.durationMs !== void 0) parts.push(formatDuration(event.durationMs));
624
+ else if (event.apiMs !== void 0) parts.push(`${formatDuration(event.apiMs)} api`);
625
+ if (event.turns !== void 0) {
626
+ parts.push(`${event.turns} turn${event.turns === 1 ? "" : "s"}`);
627
+ }
628
+ return paint(event.ok ? STATUS_OK : STATUS_ERR, parts.join(` ${SPACE_MARKS.tick} `), color);
629
+ }
630
+ case "hook": {
631
+ const detail = event.detail ? ` (${event.detail})` : "";
632
+ return paint(
633
+ LABEL_MUTED,
634
+ truncate(`${SPACE_MARKS.tick} hook ${event.name} ${event.phase}${detail}`, width),
635
+ color
636
+ );
637
+ }
638
+ case "rate_limit":
639
+ return paint(
640
+ event.severe ? STATUS_ERR : STATUS_WARN,
641
+ truncate(`! rate limit ${SPACE_MARKS.tick} ${event.line}`, width),
642
+ color
643
+ );
644
+ case "prompt":
645
+ return paint(
646
+ LABEL_MUTED,
647
+ `${SPACE_MARKS.tick} prompt sent ${SPACE_MARKS.tick} ${event.chars} chars`,
648
+ color
649
+ );
650
+ case "hitl_reply":
651
+ return paint(HELMET_ACCENT, truncate(`\u21A9 ${event.line}`, width), color);
652
+ default:
653
+ return null;
654
+ }
655
+ }
656
+ var StreamRenderer = class {
657
+ pending = "";
658
+ dropped = false;
659
+ textOpen = false;
660
+ decoder = new StringDecoder2("utf8");
661
+ write;
662
+ color;
663
+ columns;
664
+ constructor(opts = {}) {
665
+ this.write = opts.write ?? ((text) => process.stdout.write(text));
666
+ this.color = shouldUseWelcomeColor({
667
+ stdoutIsTTY: opts.isTTY ?? Boolean(process.stdout.isTTY),
668
+ env: opts.env
669
+ });
670
+ this.columns = opts.columns ?? process.stdout.columns ?? 80;
671
+ }
672
+ feed(chunk) {
673
+ try {
674
+ const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
675
+ if (!text) return;
676
+ const combined = this.pending + text;
677
+ const lines = combined.split("\n");
678
+ this.pending = lines.pop() ?? "";
679
+ for (const line of lines) {
680
+ if (this.dropped) {
681
+ this.dropped = false;
682
+ continue;
683
+ }
684
+ this.renderLine(line);
685
+ }
686
+ if (this.pending.length > PENDING_MAX_CHARS2) {
687
+ this.pending = "";
688
+ this.dropped = true;
689
+ }
690
+ } catch {
691
+ }
692
+ }
693
+ end() {
694
+ try {
695
+ const rest = this.pending + this.decoder.end();
696
+ this.pending = "";
697
+ if (rest && !this.dropped) this.renderLine(rest);
698
+ this.dropped = false;
699
+ this.closeText();
700
+ } catch {
701
+ }
702
+ }
703
+ renderLine(line) {
704
+ for (const event of parseStreamLine(line)) {
705
+ const rendered = renderStreamEvent(event, { color: this.color, columns: this.columns });
706
+ if (rendered === null) continue;
707
+ if (event.kind === "text") {
708
+ this.write(rendered);
709
+ this.textOpen = !rendered.endsWith("\n");
710
+ continue;
711
+ }
712
+ this.closeText();
713
+ this.write(`${rendered}
714
+ `);
715
+ }
716
+ }
717
+ closeText() {
718
+ if (!this.textOpen) return;
719
+ this.textOpen = false;
720
+ this.write("\n");
721
+ }
722
+ };
723
+ function createStreamRenderer(opts = {}) {
724
+ return new StreamRenderer(opts);
725
+ }
726
+
727
+ // src/plan-loop/backends.ts
728
+ var HITL_KILL_GRACE_MS = 5e3;
729
+ function resolveHitlRunOptions(hitl, transport) {
730
+ return {
731
+ transport,
732
+ policy: hitl?.policy ?? "prompt",
733
+ isTTY: hitl?.isTTY ?? Boolean(process.stdin.isTTY),
734
+ readAnswer: hitl?.readAnswer ?? createTerminalAnswerReader(),
735
+ write: hitl?.write ?? ((text) => process.stdout.write(text)),
736
+ onPromptStart: hitl?.onPromptStart,
737
+ onPromptEnd: hitl?.onPromptEnd,
738
+ killAfterMs: hitl?.killAfterMs
739
+ };
740
+ }
741
+ var CLAUDE_ENV_PASSTHROUGH = [
742
+ "ANTHROPIC_BASE_URL",
743
+ "ANTHROPIC_AUTH_TOKEN",
744
+ "ANTHROPIC_API_KEY"
745
+ ];
746
+ function redactSecrets(text, secrets) {
747
+ let out = text;
748
+ const ordered = secrets.filter((s) => s.value.length > 0).sort((a, b) => b.value.length - a.value.length);
749
+ for (const s of ordered) {
750
+ out = out.split(s.value).join(`[${s.label}]`);
751
+ }
752
+ return out;
753
+ }
754
+ var REDACT_HOLD_MAX_CHARS = 64 * 1024;
755
+ var RedactingStreamBuffer = class {
756
+ pending = "";
757
+ decoder = new StringDecoder3("utf8");
758
+ secrets;
759
+ keep;
760
+ constructor(secrets) {
761
+ this.secrets = secrets.filter((s) => s.value.length > 0);
762
+ this.keep = this.secrets.length > 0 ? Math.max(...this.secrets.map((s) => s.value.length)) - 1 : 0;
763
+ }
764
+ /** Append a chunk; returns the redacted text that is safe to emit now. */
765
+ push(chunk) {
766
+ const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
767
+ if (this.secrets.length === 0) return text;
768
+ this.pending += text;
769
+ let cut = this.pending.lastIndexOf("\n") + 1;
770
+ if (cut === 0) {
771
+ if (this.pending.length < REDACT_HOLD_MAX_CHARS) return "";
772
+ cut = this.pending.length - this.keep;
773
+ }
774
+ if (cut <= 0) return "";
775
+ let moved = true;
776
+ while (moved) {
777
+ moved = false;
778
+ for (const s of this.secrets) {
779
+ let idx = this.pending.indexOf(s.value, Math.max(0, cut - s.value.length + 1));
780
+ while (idx !== -1 && idx < cut) {
781
+ if (idx + s.value.length > cut) {
782
+ cut = idx;
783
+ moved = true;
784
+ break;
785
+ }
786
+ idx = this.pending.indexOf(s.value, idx + 1);
787
+ }
788
+ if (moved) break;
789
+ }
790
+ }
791
+ if (cut <= 0) return "";
792
+ const out = redactSecrets(this.pending.slice(0, cut), this.secrets);
793
+ this.pending = this.pending.slice(cut);
794
+ return out;
795
+ }
796
+ /** Emit whatever is still held back (call once, on stream end). */
797
+ flush() {
798
+ const rest = this.pending + this.decoder.end();
799
+ this.pending = "";
800
+ return this.secrets.length === 0 ? rest : redactSecrets(rest, this.secrets);
801
+ }
802
+ };
803
+ function mergeChildEnv(overrides) {
804
+ const merged = { ...process.env };
805
+ if (!overrides) return merged;
806
+ for (const [key, value] of Object.entries(overrides)) {
807
+ if (value === void 0) delete merged[key];
808
+ else merged[key] = value;
809
+ }
810
+ return merged;
811
+ }
812
+ var CLAUDE_NESTED_SESSION_KEYS = [
813
+ "CLAUDECODE",
814
+ "CLAUDE_CODE_SESSION_ID",
815
+ "CLAUDE_CODE_CHILD_SESSION",
816
+ "CLAUDE_CODE_BRIDGE_SESSION_ID"
817
+ ];
818
+ function claudeChildEnv(overrides) {
819
+ const env = mergeChildEnv(overrides);
820
+ for (const key of CLAUDE_NESTED_SESSION_KEYS) delete env[key];
821
+ return env;
822
+ }
823
+ var CLAUDE_MAX_TURNS_ENV = "AGENT_KIT_CLAUDE_MAX_TURNS";
824
+ var CLAUDE_MAX_BUDGET_USD_ENV = "AGENT_KIT_CLAUDE_MAX_BUDGET_USD";
825
+ function claudeRunCaps(env) {
826
+ const caps = {};
827
+ const invalid = [];
828
+ const turnsRaw = env[CLAUDE_MAX_TURNS_ENV];
829
+ if (turnsRaw !== void 0 && turnsRaw !== "") {
830
+ const n = Number(turnsRaw);
831
+ if (Number.isInteger(n) && n > 0) caps.maxTurns = n;
832
+ else invalid.push(`${CLAUDE_MAX_TURNS_ENV} (want a positive integer)`);
833
+ }
834
+ const budgetRaw = env[CLAUDE_MAX_BUDGET_USD_ENV];
835
+ if (budgetRaw !== void 0 && budgetRaw !== "") {
836
+ const n = Number(budgetRaw);
837
+ if (Number.isFinite(n) && n > 0) caps.maxBudgetUsd = n;
838
+ else invalid.push(`${CLAUDE_MAX_BUDGET_USD_ENV} (want a positive number)`);
839
+ }
840
+ return { caps, invalid };
841
+ }
842
+ var REDACT_DERIVED_MIN_CHARS = 4;
843
+ var REDACT_DERIVED_SKIP = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]);
844
+ function derivedSecretForms(value) {
845
+ const forms = [
846
+ value.replaceAll("/", "\\/"),
847
+ // JSON-escaped (JSON.stringify leaves `/` alone)
848
+ encodeURIComponent(value),
849
+ Buffer.from(value, "utf8").toString("base64"),
850
+ Buffer.from(value, "utf8").toString("base64url")
851
+ ];
852
+ return forms.filter((f) => f !== value);
853
+ }
854
+ function derivedUrlForms(value) {
855
+ const forms = [];
856
+ const trimmed = value.replace(/\/+$/, "");
857
+ if (trimmed !== value) forms.push(trimmed);
858
+ else forms.push(`${value}/`);
859
+ try {
860
+ const url = new URL(value);
861
+ forms.push(url.origin, url.host, url.hostname);
862
+ } catch {
863
+ }
864
+ return forms.filter((f) => f !== value);
865
+ }
866
+ function claudeRedactions(env) {
867
+ const entries = [];
868
+ const seen = /* @__PURE__ */ new Set();
869
+ const add = (label, value, derived) => {
870
+ if (!value || seen.has(value)) return;
871
+ if (derived && (value.length < REDACT_DERIVED_MIN_CHARS || REDACT_DERIVED_SKIP.has(value))) {
872
+ return;
873
+ }
874
+ seen.add(value);
875
+ entries.push({ label, value });
876
+ };
877
+ for (const key of CLAUDE_ENV_PASSTHROUGH) {
878
+ const value = env[key];
879
+ if (!value) continue;
880
+ add(key, value, false);
881
+ const derived = key === "ANTHROPIC_BASE_URL" ? derivedUrlForms(value) : derivedSecretForms(value);
882
+ for (const form of derived) add(key, form, true);
883
+ }
884
+ return entries;
885
+ }
886
+ async function whichBinary(bin) {
887
+ try {
888
+ const out = execFileSync("which", [bin], { encoding: "utf8" }).trim();
889
+ return out || null;
890
+ } catch {
891
+ return null;
892
+ }
893
+ }
894
+ function spawnLogged(command, args, logPath, options = {}) {
895
+ const spawnFn = options.spawnFn ?? spawn;
896
+ const redact = options.redact ?? [];
897
+ const redactedError = (err) => new Error(redactSecrets(String(err), redact));
898
+ const hitl = options.hitl;
899
+ const stdinTransport = hitl?.transport.kind === "stdin-stream-json";
900
+ return new Promise((resolve, reject) => {
901
+ let out;
902
+ let child;
903
+ let logError = null;
904
+ try {
905
+ out = createWriteStream(logPath, { flags: "w" });
906
+ out.on("error", (err) => {
907
+ logError = err;
908
+ reject(redactedError(err));
909
+ });
910
+ child = spawnFn(command, args, {
911
+ stdio: [stdinTransport ? "pipe" : "ignore", "pipe", "pipe"],
912
+ ...options.cwd ? { cwd: options.cwd } : {},
913
+ ...options.env ? { env: options.env } : {}
914
+ });
915
+ } catch (err) {
916
+ reject(redactedError(err));
917
+ return;
918
+ }
919
+ const render = options.render ?? createStreamRenderer();
920
+ const relay = hitl ? createRelay(hitl, child, redactedError) : null;
921
+ const emit = (text) => {
922
+ if (text.length === 0) return;
923
+ out.write(text);
924
+ render.feed(text);
925
+ relay?.feed(text);
926
+ };
927
+ if (relay?.startError) {
928
+ out.end();
929
+ reject(relay.startError);
930
+ return;
931
+ }
932
+ options.onSpawn?.({
933
+ backend: options.backendId ?? "claude",
934
+ pid: child.pid,
935
+ stop: (stop) => {
936
+ if (relay) {
937
+ relay.stop(stop);
938
+ return;
939
+ }
940
+ try {
941
+ child.kill("SIGTERM");
942
+ } catch {
943
+ }
944
+ }
945
+ });
946
+ const buffers = [];
947
+ const tee = (stream) => {
948
+ if (!stream) return;
949
+ if (redact.length === 0) {
950
+ stream.on("data", (chunk) => emit(chunk));
951
+ return;
952
+ }
953
+ const buffer = new RedactingStreamBuffer(redact);
954
+ buffers.push(buffer);
955
+ stream.on("data", (chunk) => emit(buffer.push(chunk)));
956
+ };
957
+ tee(child.stdout);
958
+ tee(child.stderr);
959
+ child.on("error", (err) => {
960
+ out.end();
961
+ reject(redactedError(err));
962
+ });
963
+ child.on("close", (code) => {
964
+ for (const buffer of buffers) emit(buffer.flush());
965
+ render.end();
966
+ relay?.finish();
967
+ const finish = () => {
968
+ out.end((err) => {
969
+ const failure = err ?? logError;
970
+ if (failure) reject(redactedError(failure));
971
+ else if (relay) {
972
+ const lastResult = relay.lastResult();
973
+ resolve({
974
+ exitCode: code ?? 1,
975
+ hitl: relay.record,
976
+ ...lastResult ? { lastResult } : {}
977
+ });
978
+ } else resolve({ exitCode: code ?? 1 });
979
+ });
980
+ };
981
+ if (relay) relay.closed().then(finish, finish);
982
+ else finish();
983
+ });
984
+ });
985
+ }
986
+ function createRelay(hitl, child, redactedError) {
987
+ const record = emptyHitlRecord();
988
+ const stdinTransport = hitl.transport.kind === "stdin-stream-json";
989
+ const abort = new AbortController();
990
+ const grace = hitl.killAfterMs ?? HITL_KILL_GRACE_MS;
991
+ let startError = null;
992
+ let closed = false;
993
+ let stdinEnded = false;
994
+ let pendingAsk = null;
995
+ const timers = [];
996
+ let resolveClosed = () => {
997
+ };
998
+ const closedPromise = new Promise((resolve) => {
999
+ resolveClosed = resolve;
1000
+ });
1001
+ const stdin = child.stdin;
1002
+ if (hitl.transport.kind === "stdin-stream-json") {
1003
+ if (!stdin) {
1004
+ startError = new Error("claude child has no writable stdin (stream-json transport)");
1005
+ } else {
1006
+ stdin.on("error", () => {
1007
+ });
1008
+ try {
1009
+ stdin.write(userEventLine(hitl.transport.firstPrompt));
1010
+ } catch (err) {
1011
+ startError = redactedError(err);
1012
+ }
1013
+ }
1014
+ }
1015
+ const endStdin = () => {
1016
+ if (!stdinTransport || stdinEnded) return;
1017
+ stdinEnded = true;
1018
+ try {
1019
+ stdin?.end();
1020
+ } catch {
1021
+ }
1022
+ };
1023
+ const stopChild = () => {
1024
+ endStdin();
1025
+ const term = setTimeout(() => {
1026
+ if (closed) return;
1027
+ child.kill("SIGTERM");
1028
+ const kill = setTimeout(() => {
1029
+ if (!closed) child.kill("SIGKILL");
1030
+ }, grace);
1031
+ kill.unref?.();
1032
+ timers.push(kill);
1033
+ }, grace);
1034
+ term.unref?.();
1035
+ timers.push(term);
1036
+ };
1037
+ let lastResult;
1038
+ const watcher = new TurnWatcher((turn) => {
1039
+ lastResult = turn;
1040
+ const { resultText, assistantTexts } = turn;
1041
+ if (record.stop || closed) return;
1042
+ const gate = detectHitlGate(resultText, assistantTexts);
1043
+ if (!gate) {
1044
+ endStdin();
1045
+ return;
1046
+ }
1047
+ if (gate.detection === "fallback") record.fallbackDetections += 1;
1048
+ if (hitl.transport.kind === "none") {
1049
+ record.stop = noRelayStop(gate.askId, hitl.transport.backend);
1050
+ return;
1051
+ }
1052
+ const ask = askOperator(gate, {
1053
+ isTTY: hitl.isTTY,
1054
+ policy: hitl.policy,
1055
+ readAnswer: hitl.readAnswer,
1056
+ write: hitl.write,
1057
+ signal: abort.signal,
1058
+ onPromptStart: hitl.onPromptStart,
1059
+ onPromptEnd: hitl.onPromptEnd
1060
+ }).then(
1061
+ (outcome) => {
1062
+ if (record.stop) return;
1063
+ if (outcome.kind === "stop") {
1064
+ record.stop = outcome.stop;
1065
+ stopChild();
1066
+ return;
1067
+ }
1068
+ if (closed || !stdin) {
1069
+ record.stop = unansweredStop(gate.askId, "child exited");
1070
+ return;
1071
+ }
1072
+ record.replies.push(outcome.stamp);
1073
+ hitl.write(`\u2192 ${outcome.stamp.line}
1074
+ `);
1075
+ stdin.write(userEventLine(outcome.stamp.line));
1076
+ },
1077
+ (err) => {
1078
+ record.stop = unansweredStop(gate.askId, "child exited");
1079
+ hitl.write(`hitl relay: ${redactedError(err).message}
1080
+ `);
1081
+ stopChild();
1082
+ }
1083
+ );
1084
+ pendingAsk = ask;
1085
+ });
1086
+ child.on("close", () => {
1087
+ closed = true;
1088
+ for (const t of timers) clearTimeout(t);
1089
+ abort.abort();
1090
+ const settle = () => resolveClosed();
1091
+ if (pendingAsk) pendingAsk.then(settle, settle);
1092
+ else settle();
1093
+ });
1094
+ return {
1095
+ startError,
1096
+ record,
1097
+ lastResult: () => lastResult,
1098
+ feed: (text) => watcher.feed(text),
1099
+ finish: () => watcher.end(),
1100
+ closed: () => closedPromise,
1101
+ stop: (stop) => {
1102
+ if (closed || record.stop) return;
1103
+ record.stop = stop;
1104
+ stopChild();
1105
+ }
1106
+ };
1107
+ }
1108
+ function cursorAgentHeadlessArgs(opts) {
1109
+ const args = [
1110
+ "-p",
1111
+ "--force",
1112
+ "--sandbox",
1113
+ "disabled",
1114
+ "--output-format",
1115
+ "stream-json",
1116
+ "--workspace",
1117
+ opts.workspace
1118
+ ];
1119
+ if (opts.model) args.push("--model", opts.model);
1120
+ args.push(opts.prompt);
1121
+ return args;
1122
+ }
1123
+ var cursorAgentBackend = {
1124
+ id: "cursor-agent",
1125
+ async resolve() {
1126
+ return whichBinary("cursor-agent");
1127
+ },
1128
+ async run(opts) {
1129
+ const args = cursorAgentHeadlessArgs({
1130
+ workspace: opts.workspace,
1131
+ prompt: opts.prompt,
1132
+ model: opts.model
1133
+ });
1134
+ const env = opts.env ? mergeChildEnv(opts.env) : void 0;
1135
+ return spawnLogged("cursor-agent", args, opts.logPath, {
1136
+ spawnFn: opts.spawnFn,
1137
+ env,
1138
+ redact: claudeRedactions(env ?? process.env),
1139
+ hitl: resolveHitlRunOptions(opts.hitl, { kind: "none", backend: "cursor-agent" }),
1140
+ render: opts.render,
1141
+ onSpawn: opts.onSpawn,
1142
+ backendId: "cursor-agent"
1143
+ });
1144
+ }
1145
+ };
1146
+ var CLAUDE_MIN_VERSION = "2.1.259";
1147
+ function parseClaudeVersion(raw) {
1148
+ if (!raw) return null;
1149
+ const m = /(\d+)\.(\d+)\.(\d+)/.exec(raw);
1150
+ return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
1151
+ }
1152
+ function compareVersions(a, b) {
1153
+ const pa = a.split(".").map(Number);
1154
+ const pb = b.split(".").map(Number);
1155
+ for (let i = 0; i < 3; i += 1) {
1156
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
1157
+ if (d !== 0) return d;
1158
+ }
1159
+ return 0;
1160
+ }
1161
+ var claudeVersionCache = /* @__PURE__ */ new Map();
1162
+ function defaultVersionFn(bin) {
1163
+ try {
1164
+ return execFileSync(bin, ["--version"], {
1165
+ encoding: "utf8",
1166
+ stdio: ["ignore", "pipe", "ignore"]
1167
+ });
1168
+ } catch {
1169
+ return null;
1170
+ }
1171
+ }
1172
+ function checkClaudeVersion(bin, versionFn = defaultVersionFn) {
1173
+ if (!claudeVersionCache.has(bin)) {
1174
+ claudeVersionCache.set(bin, parseClaudeVersion(versionFn(bin)));
1175
+ }
1176
+ const version = claudeVersionCache.get(bin) ?? null;
1177
+ if (version === null) {
1178
+ return {
1179
+ ok: true,
1180
+ version: null,
1181
+ warning: `claude tick: could not read \`claude --version\`; the tick needs claude >= ${CLAUDE_MIN_VERSION} (--permission-prompts). Continuing.`
1182
+ };
1183
+ }
1184
+ if (compareVersions(version, CLAUDE_MIN_VERSION) < 0) {
1185
+ return {
1186
+ ok: false,
1187
+ version,
1188
+ message: `claude ${version} is older than ${CLAUDE_MIN_VERSION}, the first release that accepts \`--permission-prompts none\`. Run \`claude update\` and retry.`
1189
+ };
1190
+ }
1191
+ return { ok: true, version };
1192
+ }
1193
+ function claudeHeadlessArgs(opts = {}) {
1194
+ const args = [
1195
+ "-p",
1196
+ "--output-format",
1197
+ "stream-json",
1198
+ "--verbose",
1199
+ "--dangerously-skip-permissions",
1200
+ "--permission-prompts",
1201
+ "none",
1202
+ "--input-format",
1203
+ "stream-json",
1204
+ "--replay-user-messages"
1205
+ ];
1206
+ if (opts.maxTurns !== void 0) {
1207
+ args.push("--max-turns", String(opts.maxTurns));
1208
+ }
1209
+ if (opts.maxBudgetUsd !== void 0) {
1210
+ args.push("--max-budget-usd", String(opts.maxBudgetUsd));
1211
+ }
1212
+ if (opts.model) {
1213
+ args.push("--model", opts.model);
1214
+ }
1215
+ args.push("--disallowedTools", ...HEADLESS_DENY_RULES);
1216
+ return args;
1217
+ }
1218
+ async function missingClaudeAdapter(workspace, prompt) {
1219
+ const m = /^\/([A-Za-z0-9][A-Za-z0-9_-]*)/.exec(prompt.trimStart());
1220
+ if (!m?.[1]) return null;
1221
+ const rel = path.join(".claude", "commands", `${m[1]}.md`);
1222
+ try {
1223
+ await access(path.join(workspace, rel));
1224
+ return null;
1225
+ } catch {
1226
+ return rel;
1227
+ }
1228
+ }
1229
+ var claudeBackend = {
1230
+ id: "claude",
1231
+ async resolve() {
1232
+ return whichBinary("claude");
1233
+ },
1234
+ async run(opts) {
1235
+ const log = opts.log ?? ((line) => console.log(line));
1236
+ const env = claudeChildEnv(opts.env);
1237
+ const redact = claudeRedactions(env);
1238
+ const version = checkClaudeVersion("claude", opts.versionFn);
1239
+ if (!version.ok) {
1240
+ throw new Error(`claude backend: ${version.message}`);
1241
+ }
1242
+ if (version.warning) log(version.warning);
1243
+ const adapter = await missingClaudeAdapter(opts.workspace, opts.prompt);
1244
+ if (adapter) {
1245
+ throw new Error(
1246
+ `claude backend: ${adapter} is missing in the workspace, so the tick's leading slash would be sent as literal text. Generate the Claude Code adapters with \`agent-kit install --claude\` and retry.`
1247
+ );
1248
+ }
1249
+ const { caps, invalid } = claudeRunCaps(env);
1250
+ for (const problem of invalid)
1251
+ log(`claude tick: ignoring invalid ${problem}; running uncapped.`);
1252
+ const capText = [
1253
+ caps.maxTurns !== void 0 ? `max-turns=${caps.maxTurns}` : null,
1254
+ caps.maxBudgetUsd !== void 0 ? `max-budget-usd=${caps.maxBudgetUsd}` : null
1255
+ ].filter(Boolean).join(" ");
1256
+ const presence = CLAUDE_ENV_PASSTHROUGH.map(
1257
+ (key) => `${key}=${env[key] ? "set" : "unset"}`
1258
+ ).join(" ");
1259
+ log(
1260
+ `claude tick: claude -p (bypass permissions, stream-json, stdin relay${capText ? `, ${capText}` : ""}). env: ${presence}`
1261
+ );
1262
+ const args = claudeHeadlessArgs({ model: opts.model, ...caps });
1263
+ try {
1264
+ return await spawnLogged("claude", args, opts.logPath, {
1265
+ cwd: opts.workspace,
1266
+ env,
1267
+ spawnFn: opts.spawnFn,
1268
+ redact,
1269
+ hitl: resolveHitlRunOptions(opts.hitl, {
1270
+ kind: "stdin-stream-json",
1271
+ firstPrompt: opts.prompt
1272
+ }),
1273
+ render: opts.render,
1274
+ onSpawn: opts.onSpawn,
1275
+ backendId: "claude"
1276
+ });
1277
+ } catch (err) {
1278
+ throw new Error(`claude backend failed to start: ${redactSecrets(String(err), redact)}`);
1279
+ }
1280
+ }
1281
+ };
1282
+ var BACKENDS = {
1283
+ "cursor-agent": cursorAgentBackend,
1284
+ claude: claudeBackend
1285
+ };
1286
+ function getBackend(id) {
1287
+ const backend = BACKENDS[id];
1288
+ if (!backend) {
1289
+ throw new Error(`Unknown backend '${id}'. Supported: ${Object.keys(BACKENDS).join(", ")}`);
1290
+ }
1291
+ return backend;
1292
+ }
1293
+
1294
+ export {
1295
+ LineSplitter,
1296
+ RESERVED_GATE_IDS,
1297
+ HITL_UNANSWERED_EXIT_CODE,
1298
+ operatorQuitStop,
1299
+ formatHitlSummary,
1300
+ hitlExitCode,
1301
+ parseStreamLine,
1302
+ renderStreamEvent,
1303
+ resolveHitlRunOptions,
1304
+ redactSecrets,
1305
+ RedactingStreamBuffer,
1306
+ claudeChildEnv,
1307
+ claudeRunCaps,
1308
+ claudeRedactions,
1309
+ whichBinary,
1310
+ spawnLogged,
1311
+ cursorAgentHeadlessArgs,
1312
+ checkClaudeVersion,
1313
+ claudeHeadlessArgs,
1314
+ getBackend
1315
+ };