@dadado/agent-kit-cli 5.17.0 → 5.18.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.
- package/dashboard/README.md +10 -1
- package/dashboard/dashboard-data.mjs +12 -2
- package/dashboard/dashboard.html +58 -0
- package/dashboard/lib/run-logs.d.mts +34 -0
- package/dashboard/lib/run-logs.mjs +218 -0
- package/dist/{add-HTYIFKMN.js → add-KQJE3ZTG.js} +3 -3
- package/dist/{chunk-KD2MYBB6.js → chunk-3J3I2W22.js} +1 -1
- package/dist/{chunk-2RDRMGD3.js → chunk-6FXQINND.js} +2 -2
- package/dist/{chunk-OUQZC744.js → chunk-B5CH47OS.js} +852 -74
- package/dist/chunk-C26I6HTO.js +39 -0
- package/dist/{chunk-N3HTJVBR.js → chunk-GSCXFJB7.js} +1 -1
- package/dist/{chunk-STGFBAYT.js → chunk-HRILMD5N.js} +7 -15
- package/dist/{chunk-B73KQ5KX.js → chunk-KIXK7KOW.js} +7 -7
- package/dist/{chunk-S6GMFDKV.js → chunk-P6PMKYWU.js} +1 -1
- package/dist/{chunk-WW3YZKHW.js → chunk-RZ5TNBHU.js} +25 -4
- package/dist/{chunk-TFZ2JUXH.js → chunk-TOWH3XCJ.js} +520 -70
- package/dist/{chunk-W24CMQ32.js → chunk-TZYKLR5J.js} +1 -1
- package/dist/{chunk-DQA434Y4.js → chunk-UEKWZ7BN.js} +2 -2
- package/dist/chunk-WGLPZXNU.js +45 -0
- package/dist/{chunk-AW74INUW.js → chunk-WSJ77EHO.js} +1 -1
- package/dist/{chunk-F4VMYR7U.js → chunk-YJMZ6UM2.js} +2 -2
- package/dist/{chunk-SMGFN3Z4.js → chunk-YMYLO5LG.js} +2 -2
- package/dist/{chunk-PNG7LO2F.js → chunk-ZDCEAGD4.js} +1 -1
- package/dist/{contribute-C6AW2P6J.js → contribute-LFT762Y4.js} +4 -4
- package/dist/{diff-VHNR62SI.js → diff-56KMRCER.js} +5 -5
- package/dist/{doctor-PET2HRRJ.js → doctor-AA64XVEY.js} +9 -8
- package/dist/{handoff-3UI6VKVG.js → handoff-34SDU4FF.js} +2 -2
- package/dist/{hook-DYIMKJYL.js → hook-KRIXBLNW.js} +7 -7
- package/dist/index.js +19 -17
- package/dist/{init-Q4LBM6KT.js → init-XP5EAUSJ.js} +12 -11
- package/dist/{install-RHOS3OYZ.js → install-SHDEX54F.js} +12 -11
- package/dist/{plan-index-JMC7Z4DW.js → plan-index-O3545OA7.js} +2 -2
- package/dist/{run-I3BFGULT.js → run-FADX6QCP.js} +6 -5
- package/dist/{run-plan-FO7CL4M3.js → run-plan-MO4IFAKV.js} +36 -5
- package/dist/{setup-global-M42GEODI.js → setup-global-CBWLXOQG.js} +6 -6
- package/dist/{status-XPVQVQMX.js → status-BSIXWZMY.js} +7 -7
- package/dist/{update-FJ2EBZVO.js → update-FJX3DA6X.js} +9 -9
- package/package.json +1 -1
- package/dist/chunk-5VCOWTTP.js +0 -6
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findOnPath
|
|
3
|
+
} from "./chunk-WGLPZXNU.js";
|
|
1
4
|
import {
|
|
2
5
|
HEADLESS_DENY_RULES
|
|
3
6
|
} from "./chunk-MAPOQEA2.js";
|
|
@@ -38,10 +41,10 @@ var LineSplitter = class {
|
|
|
38
41
|
onLine(segment);
|
|
39
42
|
} else {
|
|
40
43
|
this.parts.push(segment);
|
|
41
|
-
const
|
|
44
|
+
const line3 = this.parts.join("");
|
|
42
45
|
this.parts = [];
|
|
43
46
|
this.length = 0;
|
|
44
|
-
onLine(
|
|
47
|
+
onLine(line3);
|
|
45
48
|
}
|
|
46
49
|
start = nl + 1;
|
|
47
50
|
nl = text.indexOf("\n", start);
|
|
@@ -80,17 +83,20 @@ var FALLBACK_WORDING_RES = [
|
|
|
80
83
|
/skip or cancel means stop/i
|
|
81
84
|
];
|
|
82
85
|
var TICK_SENTINEL_RE = /LOOP_TICK_RESULT:/;
|
|
83
|
-
function parseHitlGateLine(
|
|
84
|
-
const m = HITL_GATE_LINE_RE.exec(
|
|
86
|
+
function parseHitlGateLine(line3) {
|
|
87
|
+
const m = HITL_GATE_LINE_RE.exec(line3.trim());
|
|
85
88
|
if (!m?.[1] || !m[2]) return null;
|
|
86
89
|
const labels = m[2].split("|").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
87
90
|
if (labels.length === 0) return null;
|
|
88
91
|
return { askId: m[1], labels, detection: "sentinel" };
|
|
89
92
|
}
|
|
93
|
+
function formatHitlGateLine(gate) {
|
|
94
|
+
return `HITL_GATE: ${gate.askId} | ${gate.labels.join(" | ")}`;
|
|
95
|
+
}
|
|
90
96
|
function findHitlGateInText(text) {
|
|
91
97
|
let last = null;
|
|
92
|
-
for (const
|
|
93
|
-
const gate = parseHitlGateLine(
|
|
98
|
+
for (const line3 of text.split(/\r?\n/)) {
|
|
99
|
+
const gate = parseHitlGateLine(line3);
|
|
94
100
|
if (gate) last = gate;
|
|
95
101
|
}
|
|
96
102
|
return last;
|
|
@@ -107,8 +113,8 @@ function detectFallbackGate(text) {
|
|
|
107
113
|
if (!FALLBACK_WORDING_RES.some((re) => re.test(text))) return null;
|
|
108
114
|
const labels = [];
|
|
109
115
|
let expected = 1;
|
|
110
|
-
for (const
|
|
111
|
-
const m = NUMBERED_LINE_RE.exec(
|
|
116
|
+
for (const line3 of text.split(/\r?\n/)) {
|
|
117
|
+
const m = NUMBERED_LINE_RE.exec(line3);
|
|
112
118
|
if (!m?.[1] || !m[2]) continue;
|
|
113
119
|
const n = Number(m[1]);
|
|
114
120
|
if (n === 1) {
|
|
@@ -192,7 +198,7 @@ function createTerminalAnswerReader(opts = {}) {
|
|
|
192
198
|
readOpts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
193
199
|
rl.on("SIGINT", () => finish({ kind: "sigint" }));
|
|
194
200
|
rl.on("close", () => finish({ kind: "eof" }));
|
|
195
|
-
rl.question("> ", (
|
|
201
|
+
rl.question("> ", (line3) => finish({ kind: "line", text: line3 }));
|
|
196
202
|
});
|
|
197
203
|
}
|
|
198
204
|
function emptyHitlRecord() {
|
|
@@ -222,6 +228,15 @@ function unansweredStop(askId, cause) {
|
|
|
222
228
|
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
223
229
|
};
|
|
224
230
|
}
|
|
231
|
+
function driverStop(askId, raw) {
|
|
232
|
+
const detail = raw.detail ? `: ${raw.detail}` : "";
|
|
233
|
+
return {
|
|
234
|
+
askId,
|
|
235
|
+
cause: raw.cause,
|
|
236
|
+
message: raw.cause === "driver stop" ? `stopped-by-operator: gate ${askId}, no reply (driver stop)` : `stopped: unanswered gate (driver protocol error${detail})`,
|
|
237
|
+
exitCode: HITL_UNANSWERED_EXIT_CODE
|
|
238
|
+
};
|
|
239
|
+
}
|
|
225
240
|
function noRelayStop(askId, backend) {
|
|
226
241
|
return {
|
|
227
242
|
askId,
|
|
@@ -251,7 +266,9 @@ function formatGatePrompt(gate) {
|
|
|
251
266
|
async function askOperator(gate, opts) {
|
|
252
267
|
if (isReservedGateId(gate.askId)) return { kind: "stop", stop: reservedStop(gate.askId) };
|
|
253
268
|
if (opts.policy === "off") return { kind: "stop", stop: unansweredStop(gate.askId, "--no-hitl") };
|
|
254
|
-
if (!opts.isTTY
|
|
269
|
+
if (!opts.isTTY && opts.policy !== "driver") {
|
|
270
|
+
return { kind: "stop", stop: unansweredStop(gate.askId, "no TTY") };
|
|
271
|
+
}
|
|
255
272
|
opts.onPromptStart?.();
|
|
256
273
|
let raw;
|
|
257
274
|
try {
|
|
@@ -265,14 +282,15 @@ async function askOperator(gate, opts) {
|
|
|
265
282
|
}
|
|
266
283
|
if (raw.kind === "eof") return { kind: "stop", stop: operatorStop(gate.askId, "EOF") };
|
|
267
284
|
if (raw.kind === "sigint") return { kind: "stop", stop: operatorStop(gate.askId, "SIGINT") };
|
|
285
|
+
if (raw.kind === "stop") return { kind: "stop", stop: driverStop(gate.askId, raw) };
|
|
268
286
|
const answer = resolveOperatorAnswer(raw.text, gate.labels);
|
|
269
287
|
if (answer.kind === "stop") return { kind: "stop", stop: operatorStop(gate.askId, answer.cause) };
|
|
270
|
-
const
|
|
288
|
+
const line3 = formatHitlReply(gate.askId, answer);
|
|
271
289
|
const stamp = {
|
|
272
290
|
askId: gate.askId,
|
|
273
291
|
reply: answer.kind === "reply" ? `operator reply ${answer.n}` : "operator reply other",
|
|
274
292
|
label: answer.kind === "reply" ? answer.label : answer.text,
|
|
275
|
-
line,
|
|
293
|
+
line: line3,
|
|
276
294
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
277
295
|
detection: gate.detection
|
|
278
296
|
};
|
|
@@ -308,15 +326,15 @@ var TurnWatcher = class {
|
|
|
308
326
|
feed(chunk) {
|
|
309
327
|
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
310
328
|
if (!text) return;
|
|
311
|
-
this.lines.feed(text, (
|
|
329
|
+
this.lines.feed(text, (line3) => this.take(line3));
|
|
312
330
|
}
|
|
313
331
|
end() {
|
|
314
|
-
this.lines.feed(this.decoder.end(), (
|
|
332
|
+
this.lines.feed(this.decoder.end(), (line3) => this.take(line3));
|
|
315
333
|
const rest = this.lines.end();
|
|
316
334
|
if (rest.trim()) this.take(rest);
|
|
317
335
|
}
|
|
318
|
-
take(
|
|
319
|
-
const trimmed =
|
|
336
|
+
take(line3) {
|
|
337
|
+
const trimmed = line3.trim();
|
|
320
338
|
if (!trimmed.startsWith("{")) return;
|
|
321
339
|
let event;
|
|
322
340
|
try {
|
|
@@ -354,10 +372,335 @@ var TurnWatcher = class {
|
|
|
354
372
|
};
|
|
355
373
|
|
|
356
374
|
// src/plan-loop/backends.ts
|
|
357
|
-
import { execFileSync, spawn } from "child_process";
|
|
358
|
-
import { createWriteStream } from "fs";
|
|
375
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
376
|
+
import { createWriteStream as createWriteStream2 } from "fs";
|
|
377
|
+
import { access as access2 } from "fs/promises";
|
|
378
|
+
import path2 from "path";
|
|
379
|
+
import { StringDecoder as StringDecoder4 } from "string_decoder";
|
|
380
|
+
|
|
381
|
+
// src/plan-loop/codex.ts
|
|
382
|
+
import { execFileSync } from "child_process";
|
|
359
383
|
import { access } from "fs/promises";
|
|
360
384
|
import path from "path";
|
|
385
|
+
|
|
386
|
+
// src/plan-loop/vendor-cli-pins.ts
|
|
387
|
+
var VENDOR_CLI_PINS = {
|
|
388
|
+
claude: {
|
|
389
|
+
bin: "claude",
|
|
390
|
+
min: "2.1.259",
|
|
391
|
+
tested: "2.1.292",
|
|
392
|
+
testedOn: "2026-10-08 `claude --version` (Linux)",
|
|
393
|
+
versionRe: /(\d+\.\d+\.\d+)/
|
|
394
|
+
},
|
|
395
|
+
"cursor-agent": {
|
|
396
|
+
bin: "cursor-agent",
|
|
397
|
+
min: null,
|
|
398
|
+
tested: "2026.10.01",
|
|
399
|
+
testedOn: "2026-10-08 `cursor-agent --version` = 2026.10.01-e373342 (Linux)",
|
|
400
|
+
versionRe: /(\d{4}\.\d{2}\.\d{2})(?:-[0-9a-f]+)?/
|
|
401
|
+
},
|
|
402
|
+
codex: {
|
|
403
|
+
bin: "codex",
|
|
404
|
+
min: null,
|
|
405
|
+
tested: "0.160.1",
|
|
406
|
+
testedOn: "2026-10-08 `codex --version` = codex-cli 0.160.1 (Linux)",
|
|
407
|
+
versionRe: /(\d+\.\d+\.\d+)/
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
function parseVendorVersion(id, raw) {
|
|
411
|
+
if (!raw) return null;
|
|
412
|
+
return VENDOR_CLI_PINS[id].versionRe.exec(raw)?.[1] ?? null;
|
|
413
|
+
}
|
|
414
|
+
function compareVendorVersions(a, b) {
|
|
415
|
+
const pa = a.split(/\D+/).filter(Boolean).map(Number);
|
|
416
|
+
const pb = b.split(/\D+/).filter(Boolean).map(Number);
|
|
417
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
|
|
418
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
419
|
+
if (d !== 0) return d;
|
|
420
|
+
}
|
|
421
|
+
return 0;
|
|
422
|
+
}
|
|
423
|
+
function vendorVersionStatus(id, raw) {
|
|
424
|
+
const pin = VENDOR_CLI_PINS[id];
|
|
425
|
+
const version = parseVendorVersion(id, raw);
|
|
426
|
+
if (!version) return { status: "unknown", version: null };
|
|
427
|
+
if (pin.min && compareVendorVersions(version, pin.min) < 0) {
|
|
428
|
+
return {
|
|
429
|
+
status: "too-old",
|
|
430
|
+
version,
|
|
431
|
+
message: `${pin.bin} ${version} is older than ${pin.min}, the oldest version the headless runner supports.`
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (compareVendorVersions(version, pin.tested) > 0) {
|
|
435
|
+
return {
|
|
436
|
+
status: "newer-than-tested",
|
|
437
|
+
version,
|
|
438
|
+
message: `${pin.bin} ${version} is newer than ${pin.tested}, the last version the kit's headless contract was checked against; continuing (a changed output format is named here first).`
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return { status: "supported", version };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/plan-loop/codex.ts
|
|
445
|
+
var CODEX_CHATGPT_LOGIN_ENV = "AGENT_KIT_CODEX_CHATGPT_LOGIN";
|
|
446
|
+
var CODEX_MAX_RESUMES = 50;
|
|
447
|
+
function codexExecArgs(opts) {
|
|
448
|
+
const args = [
|
|
449
|
+
"exec",
|
|
450
|
+
"--json",
|
|
451
|
+
"--color",
|
|
452
|
+
"never",
|
|
453
|
+
"--cd",
|
|
454
|
+
opts.workspace,
|
|
455
|
+
"--dangerously-bypass-approvals-and-sandbox"
|
|
456
|
+
];
|
|
457
|
+
if (opts.model) args.push("--model", opts.model);
|
|
458
|
+
args.push("-");
|
|
459
|
+
return args;
|
|
460
|
+
}
|
|
461
|
+
function codexResumeArgs(opts) {
|
|
462
|
+
const args = ["exec", "resume", "--json", "--dangerously-bypass-approvals-and-sandbox"];
|
|
463
|
+
if (opts.model) args.push("--model", opts.model);
|
|
464
|
+
args.push(opts.threadId, "-");
|
|
465
|
+
return args;
|
|
466
|
+
}
|
|
467
|
+
function resolveCodexAuth(env) {
|
|
468
|
+
if (env[CODEX_CHATGPT_LOGIN_ENV] === "1") return { mode: "chatgpt-login", env };
|
|
469
|
+
const key = env.CODEX_API_KEY || env.OPENAI_API_KEY;
|
|
470
|
+
if (!key) {
|
|
471
|
+
return {
|
|
472
|
+
mode: "refused",
|
|
473
|
+
message: `codex backend: API-key mode needs OPENAI_API_KEY (or CODEX_API_KEY) in the environment. Using a ChatGPT sign-in is off by default until OpenAI clears it for third-party apps; set ${CODEX_CHATGPT_LOGIN_ENV}=1 to run on your own codex login.`
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return { mode: "api-key", env: { ...env, CODEX_API_KEY: key } };
|
|
477
|
+
}
|
|
478
|
+
function codexSlashPointer(name) {
|
|
479
|
+
return [
|
|
480
|
+
`Read \`.cursor/commands/${name}.md\` now and follow that contract exactly \u2014 it is the source of truth for /${name}; this paragraph is only a thin adapter for the Codex CLI.`,
|
|
481
|
+
"",
|
|
482
|
+
"Adapter rules (Codex CLI, headless):",
|
|
483
|
+
"- Ask questions is unavailable here: print one line `HITL_GATE: <ask-id> | <label 1> | <label 2> | ...` immediately followed by the same labels as one numbered list, one list per message, then end your turn and WAIT for the answer (ask-id and labels: `.cursor/skills/core/hitl-gates/SKILL.md`). The answer arrives as your next prompt, `HITL_REPLY: <ask-id> | operator reply <n> | <label>`; cite that line as the Ask provenance.",
|
|
484
|
+
"- Skip or cancel means stop.",
|
|
485
|
+
"- Never `/git-prod` without an explicit operator yes.",
|
|
486
|
+
"- Do not clone Cursor hooks or invent behavior beyond the SoT file."
|
|
487
|
+
].join("\n");
|
|
488
|
+
}
|
|
489
|
+
async function codexPrompt(workspace, prompt) {
|
|
490
|
+
const m = /^\/([A-Za-z0-9][A-Za-z0-9_-]*)/.exec(prompt.trimStart());
|
|
491
|
+
if (!m?.[1]) return { ok: true, prompt };
|
|
492
|
+
const rel = path.join(".cursor", "commands", `${m[1]}.md`);
|
|
493
|
+
try {
|
|
494
|
+
await access(path.join(workspace, rel));
|
|
495
|
+
} catch {
|
|
496
|
+
return { ok: false, missing: rel };
|
|
497
|
+
}
|
|
498
|
+
return { ok: true, prompt: `${codexSlashPointer(m[1])}
|
|
499
|
+
|
|
500
|
+
${prompt}` };
|
|
501
|
+
}
|
|
502
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
503
|
+
var line = (event) => JSON.stringify(event);
|
|
504
|
+
function createCodexTranslator(onThread) {
|
|
505
|
+
let lastMessage = "";
|
|
506
|
+
let threadId = "";
|
|
507
|
+
return (raw) => {
|
|
508
|
+
const text = raw.trim();
|
|
509
|
+
if (!text.startsWith("{")) return [];
|
|
510
|
+
let event;
|
|
511
|
+
try {
|
|
512
|
+
event = JSON.parse(text);
|
|
513
|
+
} catch {
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
if (!isRecord(event)) return [];
|
|
517
|
+
const item = isRecord(event.item) ? event.item : null;
|
|
518
|
+
const itemType = item ? item.type ?? item.item_type : void 0;
|
|
519
|
+
const isMessage = itemType === "agent_message" || itemType === "assistant_message";
|
|
520
|
+
switch (event.type) {
|
|
521
|
+
case "thread.started":
|
|
522
|
+
if (typeof event.thread_id === "string") {
|
|
523
|
+
threadId = event.thread_id;
|
|
524
|
+
onThread(threadId);
|
|
525
|
+
}
|
|
526
|
+
return [line({ type: "system", subtype: "init", session_id: threadId, model: "codex" })];
|
|
527
|
+
case "item.started":
|
|
528
|
+
if (itemType === "command_execution" && typeof item?.command === "string") {
|
|
529
|
+
return [
|
|
530
|
+
line({
|
|
531
|
+
type: "assistant",
|
|
532
|
+
message: {
|
|
533
|
+
content: [{ type: "tool_use", name: "shell", input: { command: item.command } }]
|
|
534
|
+
}
|
|
535
|
+
})
|
|
536
|
+
];
|
|
537
|
+
}
|
|
538
|
+
return [];
|
|
539
|
+
case "item.completed":
|
|
540
|
+
if (isMessage && typeof item?.text === "string") {
|
|
541
|
+
lastMessage = item.text;
|
|
542
|
+
return [
|
|
543
|
+
line({ type: "assistant", message: { content: [{ type: "text", text: item.text }] } })
|
|
544
|
+
];
|
|
545
|
+
}
|
|
546
|
+
if (item && itemType === "command_execution") {
|
|
547
|
+
const exit = typeof item.exit_code === "number" ? item.exit_code : null;
|
|
548
|
+
return [
|
|
549
|
+
line({
|
|
550
|
+
type: "user",
|
|
551
|
+
message: {
|
|
552
|
+
content: [
|
|
553
|
+
{
|
|
554
|
+
type: "tool_result",
|
|
555
|
+
content: typeof item.aggregated_output === "string" ? item.aggregated_output : "",
|
|
556
|
+
is_error: exit !== null && exit !== 0
|
|
557
|
+
}
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
return [];
|
|
564
|
+
case "turn.completed": {
|
|
565
|
+
const result = lastMessage;
|
|
566
|
+
lastMessage = "";
|
|
567
|
+
return [
|
|
568
|
+
line({
|
|
569
|
+
type: "result",
|
|
570
|
+
subtype: "success",
|
|
571
|
+
is_error: false,
|
|
572
|
+
result,
|
|
573
|
+
session_id: threadId,
|
|
574
|
+
...isRecord(event.usage) ? { usage: event.usage } : {}
|
|
575
|
+
})
|
|
576
|
+
];
|
|
577
|
+
}
|
|
578
|
+
case "turn.failed": {
|
|
579
|
+
const message = isRecord(event.error) && typeof event.error.message === "string" ? event.error.message : "codex turn failed";
|
|
580
|
+
lastMessage = "";
|
|
581
|
+
return [
|
|
582
|
+
line({
|
|
583
|
+
type: "result",
|
|
584
|
+
subtype: "error_during_execution",
|
|
585
|
+
is_error: true,
|
|
586
|
+
result: message,
|
|
587
|
+
errors: [message],
|
|
588
|
+
session_id: threadId
|
|
589
|
+
})
|
|
590
|
+
];
|
|
591
|
+
}
|
|
592
|
+
default:
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
var codexVersionCache = /* @__PURE__ */ new Map();
|
|
598
|
+
function readCodexVersion(bin) {
|
|
599
|
+
if (!codexVersionCache.has(bin)) {
|
|
600
|
+
let out = null;
|
|
601
|
+
try {
|
|
602
|
+
out = execFileSync(bin, ["--version"], {
|
|
603
|
+
encoding: "utf8",
|
|
604
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
605
|
+
timeout: 1e4
|
|
606
|
+
});
|
|
607
|
+
} catch {
|
|
608
|
+
out = null;
|
|
609
|
+
}
|
|
610
|
+
codexVersionCache.set(bin, out);
|
|
611
|
+
}
|
|
612
|
+
return codexVersionCache.get(bin) ?? null;
|
|
613
|
+
}
|
|
614
|
+
function mergeRecords(records) {
|
|
615
|
+
const merged = emptyHitlRecord();
|
|
616
|
+
for (const r of records) {
|
|
617
|
+
merged.replies.push(...r.replies);
|
|
618
|
+
merged.fallbackDetections += r.fallbackDetections;
|
|
619
|
+
if (r.stop) merged.stop = r.stop;
|
|
620
|
+
}
|
|
621
|
+
return merged;
|
|
622
|
+
}
|
|
623
|
+
var codexBackend = {
|
|
624
|
+
id: "codex",
|
|
625
|
+
async resolve() {
|
|
626
|
+
return whichBinary("codex");
|
|
627
|
+
},
|
|
628
|
+
async run(opts) {
|
|
629
|
+
const log = opts.log ?? ((text) => console.log(text));
|
|
630
|
+
const auth = resolveCodexAuth(mergeChildEnv(opts.env));
|
|
631
|
+
if (auth.mode === "refused") throw new Error(auth.message);
|
|
632
|
+
const env = auth.env;
|
|
633
|
+
const redact = claudeRedactions(env);
|
|
634
|
+
const pin = vendorVersionStatus("codex", (opts.versionFn ?? readCodexVersion)("codex"));
|
|
635
|
+
if (pin.status === "newer-than-tested" || pin.status === "too-old") log(pin.message);
|
|
636
|
+
const prepared = await codexPrompt(opts.workspace, opts.prompt);
|
|
637
|
+
if (!prepared.ok) {
|
|
638
|
+
throw new Error(
|
|
639
|
+
`codex backend: ${prepared.missing} is missing in the workspace, so the tick's leading slash has no contract to point codex at. Install the kit (\`agent-kit install\`) and retry.`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
log(
|
|
643
|
+
`codex tick: codex exec --json (no approvals, resume relay, ${auth.mode === "api-key" ? "API key" : "ChatGPT sign-in, opt-in"}).`
|
|
644
|
+
);
|
|
645
|
+
let threadId = null;
|
|
646
|
+
const translate = createCodexTranslator((id) => {
|
|
647
|
+
threadId = id;
|
|
648
|
+
});
|
|
649
|
+
const hitl = resolveHitlRunOptions(opts.hitl, { kind: "resume", backend: "codex" });
|
|
650
|
+
const common = {
|
|
651
|
+
cwd: opts.workspace,
|
|
652
|
+
env,
|
|
653
|
+
redact,
|
|
654
|
+
spawnFn: opts.spawnFn,
|
|
655
|
+
hitl,
|
|
656
|
+
render: opts.render,
|
|
657
|
+
onSpawn: opts.onSpawn,
|
|
658
|
+
backendId: "codex",
|
|
659
|
+
translate
|
|
660
|
+
};
|
|
661
|
+
const records = [];
|
|
662
|
+
let result;
|
|
663
|
+
try {
|
|
664
|
+
result = await spawnLogged(
|
|
665
|
+
"codex",
|
|
666
|
+
codexExecArgs({ workspace: opts.workspace, model: opts.model }),
|
|
667
|
+
opts.logPath,
|
|
668
|
+
{ ...common, stdinText: prepared.prompt }
|
|
669
|
+
);
|
|
670
|
+
if (result.hitl) records.push(result.hitl);
|
|
671
|
+
for (let i = 0; result.resumeWith && i < CODEX_MAX_RESUMES; i += 1) {
|
|
672
|
+
const reply = result.resumeWith;
|
|
673
|
+
const thread = threadId;
|
|
674
|
+
if (!thread) {
|
|
675
|
+
const record = emptyHitlRecord();
|
|
676
|
+
record.stop = {
|
|
677
|
+
...unansweredStop("none", "child exited"),
|
|
678
|
+
message: "stopped: codex reported no thread id, so the reply could not be delivered"
|
|
679
|
+
};
|
|
680
|
+
records.push(record);
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
result = await spawnLogged(
|
|
684
|
+
"codex",
|
|
685
|
+
codexResumeArgs({ threadId: thread, model: opts.model }),
|
|
686
|
+
opts.logPath,
|
|
687
|
+
{ ...common, stdinText: reply, appendLog: true }
|
|
688
|
+
);
|
|
689
|
+
if (result.hitl) records.push(result.hitl);
|
|
690
|
+
}
|
|
691
|
+
} catch (err) {
|
|
692
|
+
throw new Error(`codex backend failed to start: ${redactSecrets(String(err), redact)}`);
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
exitCode: result.exitCode,
|
|
696
|
+
hitl: mergeRecords(records),
|
|
697
|
+
...result.lastResult ? { lastResult: result.lastResult } : {}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// src/plan-loop/cursor-acp.ts
|
|
703
|
+
import { createWriteStream } from "fs";
|
|
361
704
|
import { StringDecoder as StringDecoder3 } from "string_decoder";
|
|
362
705
|
|
|
363
706
|
// src/plan-loop/stream-render.ts
|
|
@@ -365,7 +708,7 @@ import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
|
365
708
|
import { trueColor } from "kolorist";
|
|
366
709
|
|
|
367
710
|
// src/plan-loop/stream-events.ts
|
|
368
|
-
function
|
|
711
|
+
function isRecord2(value) {
|
|
369
712
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
370
713
|
}
|
|
371
714
|
function asString(value) {
|
|
@@ -390,7 +733,7 @@ var SUMMARY_KEYS = [
|
|
|
390
733
|
];
|
|
391
734
|
function summarizeToolInput(input) {
|
|
392
735
|
if (typeof input === "string") return oneLine(input);
|
|
393
|
-
if (!
|
|
736
|
+
if (!isRecord2(input)) return input === void 0 ? "" : oneLine(JSON.stringify(input));
|
|
394
737
|
for (const key of SUMMARY_KEYS) {
|
|
395
738
|
const value = input[key];
|
|
396
739
|
if (typeof value === "string" && value.trim()) return oneLine(value);
|
|
@@ -403,31 +746,31 @@ function firstLineOfContent(content) {
|
|
|
403
746
|
if (typeof content === "string") return firstNonEmptyLine(content);
|
|
404
747
|
if (Array.isArray(content)) {
|
|
405
748
|
for (const part of content) {
|
|
406
|
-
if (
|
|
407
|
-
const
|
|
408
|
-
if (
|
|
749
|
+
if (isRecord2(part) && typeof part.text === "string") {
|
|
750
|
+
const line3 = firstNonEmptyLine(part.text);
|
|
751
|
+
if (line3) return line3;
|
|
409
752
|
}
|
|
410
753
|
}
|
|
411
754
|
return "";
|
|
412
755
|
}
|
|
413
|
-
if (
|
|
756
|
+
if (isRecord2(content)) return oneLine(JSON.stringify(content));
|
|
414
757
|
return "";
|
|
415
758
|
}
|
|
416
759
|
function firstNonEmptyLine(text) {
|
|
417
|
-
for (const
|
|
418
|
-
const trimmed =
|
|
760
|
+
for (const line3 of text.split(/\r?\n/)) {
|
|
761
|
+
const trimmed = line3.trim();
|
|
419
762
|
if (trimmed) return trimmed;
|
|
420
763
|
}
|
|
421
764
|
return "";
|
|
422
765
|
}
|
|
423
766
|
function parseAssistant(event) {
|
|
424
767
|
const message = event.message;
|
|
425
|
-
const content =
|
|
768
|
+
const content = isRecord2(message) ? message.content : void 0;
|
|
426
769
|
if (typeof content === "string") return content ? [{ kind: "text", text: content }] : [];
|
|
427
770
|
if (!Array.isArray(content)) return [{ kind: "silent" }];
|
|
428
771
|
const out = [];
|
|
429
772
|
for (const part of content) {
|
|
430
|
-
if (!
|
|
773
|
+
if (!isRecord2(part)) continue;
|
|
431
774
|
const type = asString(part.type);
|
|
432
775
|
if (type === "text" && typeof part.text === "string") {
|
|
433
776
|
if (part.text) out.push({ kind: "text", text: part.text });
|
|
@@ -444,7 +787,7 @@ function parseAssistant(event) {
|
|
|
444
787
|
var HITL_REPLY_PREFIX2 = "HITL_REPLY:";
|
|
445
788
|
function parseUser(event) {
|
|
446
789
|
const message = event.message;
|
|
447
|
-
const content =
|
|
790
|
+
const content = isRecord2(message) ? message.content : void 0;
|
|
448
791
|
if (typeof content === "string") {
|
|
449
792
|
if (event.isReplay === true && content.startsWith(HITL_REPLY_PREFIX2)) {
|
|
450
793
|
return [{ kind: "hitl_reply", line: oneLine(content) }];
|
|
@@ -455,7 +798,7 @@ function parseUser(event) {
|
|
|
455
798
|
const out = [];
|
|
456
799
|
let promptChars = 0;
|
|
457
800
|
for (const part of content) {
|
|
458
|
-
if (!
|
|
801
|
+
if (!isRecord2(part)) continue;
|
|
459
802
|
const type = asString(part.type);
|
|
460
803
|
if (type === "tool_result") {
|
|
461
804
|
out.push({
|
|
@@ -504,16 +847,16 @@ function percent(value) {
|
|
|
504
847
|
return n === void 0 ? null : `${Math.round(n * 100)}%`;
|
|
505
848
|
}
|
|
506
849
|
function parseRateLimit(event) {
|
|
507
|
-
const info =
|
|
850
|
+
const info = isRecord2(event.rate_limit_info) ? event.rate_limit_info : event;
|
|
508
851
|
const status = asString(info.status) ?? "unknown";
|
|
509
852
|
const type = asString(info.rateLimitType);
|
|
510
853
|
const parts = [];
|
|
511
854
|
if (type) parts.push(type);
|
|
512
855
|
parts.push(status);
|
|
513
|
-
const windows =
|
|
856
|
+
const windows = isRecord2(info.unifiedWindows) ? info.unifiedWindows : null;
|
|
514
857
|
if (windows) {
|
|
515
858
|
for (const [name, window] of Object.entries(windows)) {
|
|
516
|
-
if (!
|
|
859
|
+
if (!isRecord2(window)) continue;
|
|
517
860
|
const used = percent(window.utilization);
|
|
518
861
|
if (used) parts.push(`${name} ${used}`);
|
|
519
862
|
}
|
|
@@ -521,30 +864,30 @@ function parseRateLimit(event) {
|
|
|
521
864
|
return { kind: "rate_limit", line: parts.join(" \xB7 "), severe: status !== "allowed" };
|
|
522
865
|
}
|
|
523
866
|
function parseCursorToolCall(event) {
|
|
524
|
-
const call =
|
|
525
|
-
const entry = call ? Object.entries(call).find(([, v]) =>
|
|
867
|
+
const call = isRecord2(event.tool_call) ? event.tool_call : null;
|
|
868
|
+
const entry = call ? Object.entries(call).find(([, v]) => isRecord2(v)) : void 0;
|
|
526
869
|
const name = entry ? entry[0].replace(/ToolCall$/, "") : "tool";
|
|
527
|
-
const body = entry &&
|
|
870
|
+
const body = entry && isRecord2(entry[1]) ? entry[1] : {};
|
|
528
871
|
const subtype = asString(event.subtype) ?? "";
|
|
529
872
|
if (subtype === "started") {
|
|
530
873
|
return { kind: "tool_use", name, summary: summarizeToolInput(body.args) };
|
|
531
874
|
}
|
|
532
875
|
if (subtype === "completed") {
|
|
533
|
-
const result =
|
|
876
|
+
const result = isRecord2(body.result) ? body.result : null;
|
|
534
877
|
const failed = result !== null && ("error" in result || "failure" in result || "rejected" in result);
|
|
535
|
-
let
|
|
878
|
+
let line3 = "";
|
|
536
879
|
if (result) {
|
|
537
880
|
const payload = failed ? result.error ?? result.failure ?? result.rejected : result.success;
|
|
538
|
-
|
|
881
|
+
line3 = firstLineOfContent(
|
|
539
882
|
typeof payload === "string" ? payload : payload === void 0 ? result : payload
|
|
540
883
|
);
|
|
541
884
|
}
|
|
542
|
-
return { kind: "tool_result", ok: !failed, line };
|
|
885
|
+
return { kind: "tool_result", ok: !failed, line: line3 };
|
|
543
886
|
}
|
|
544
887
|
return { kind: "silent" };
|
|
545
888
|
}
|
|
546
|
-
function parseStreamLine(
|
|
547
|
-
const trimmed =
|
|
889
|
+
function parseStreamLine(line3) {
|
|
890
|
+
const trimmed = line3.trim();
|
|
548
891
|
if (!trimmed || !trimmed.startsWith("{")) return [{ kind: "unparsed" }];
|
|
549
892
|
let event;
|
|
550
893
|
try {
|
|
@@ -552,7 +895,7 @@ function parseStreamLine(line) {
|
|
|
552
895
|
} catch {
|
|
553
896
|
return [{ kind: "unparsed" }];
|
|
554
897
|
}
|
|
555
|
-
if (!
|
|
898
|
+
if (!isRecord2(event)) return [{ kind: "unparsed" }];
|
|
556
899
|
switch (asString(event.type)) {
|
|
557
900
|
case "assistant":
|
|
558
901
|
return parseAssistant(event);
|
|
@@ -613,8 +956,8 @@ function renderStreamEvent(event, opts) {
|
|
|
613
956
|
case "tool_result": {
|
|
614
957
|
const label = event.ok ? "ok" : "error";
|
|
615
958
|
const head = ` ${SPACE_MARKS.tick} ${label}`;
|
|
616
|
-
const
|
|
617
|
-
const text =
|
|
959
|
+
const line3 = truncate(oneLine(event.line), width - head.length - 3);
|
|
960
|
+
const text = line3 ? `${head} ${SPACE_MARKS.tick} ${line3}` : head;
|
|
618
961
|
return paint(event.ok ? LABEL_MUTED : STATUS_ERR, text, color);
|
|
619
962
|
}
|
|
620
963
|
case "result": {
|
|
@@ -676,12 +1019,12 @@ var StreamRenderer = class {
|
|
|
676
1019
|
const combined = this.pending + text;
|
|
677
1020
|
const lines = combined.split("\n");
|
|
678
1021
|
this.pending = lines.pop() ?? "";
|
|
679
|
-
for (const
|
|
1022
|
+
for (const line3 of lines) {
|
|
680
1023
|
if (this.dropped) {
|
|
681
1024
|
this.dropped = false;
|
|
682
1025
|
continue;
|
|
683
1026
|
}
|
|
684
|
-
this.renderLine(
|
|
1027
|
+
this.renderLine(line3);
|
|
685
1028
|
}
|
|
686
1029
|
if (this.pending.length > PENDING_MAX_CHARS2) {
|
|
687
1030
|
this.pending = "";
|
|
@@ -700,8 +1043,8 @@ var StreamRenderer = class {
|
|
|
700
1043
|
} catch {
|
|
701
1044
|
}
|
|
702
1045
|
}
|
|
703
|
-
renderLine(
|
|
704
|
-
for (const event of parseStreamLine(
|
|
1046
|
+
renderLine(line3) {
|
|
1047
|
+
for (const event of parseStreamLine(line3)) {
|
|
705
1048
|
const rendered = renderStreamEvent(event, { color: this.color, columns: this.columns });
|
|
706
1049
|
if (rendered === null) continue;
|
|
707
1050
|
if (event.kind === "text") {
|
|
@@ -724,7 +1067,402 @@ function createStreamRenderer(opts = {}) {
|
|
|
724
1067
|
return new StreamRenderer(opts);
|
|
725
1068
|
}
|
|
726
1069
|
|
|
1070
|
+
// src/plan-loop/cursor-acp.ts
|
|
1071
|
+
var ACP_PROTOCOL_VERSION = 1;
|
|
1072
|
+
var CURSOR_ASK_QUESTION_ID = "cursor-ask-question";
|
|
1073
|
+
var ACP_EXIT_GRACE_MS = 1e3;
|
|
1074
|
+
var ACP_KILL_GRACE_MS = 5e3;
|
|
1075
|
+
var ACP_MAX_TURNS = 50;
|
|
1076
|
+
function cursorAcpArgs() {
|
|
1077
|
+
return ["acp"];
|
|
1078
|
+
}
|
|
1079
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1080
|
+
function acpInitializeParams() {
|
|
1081
|
+
return {
|
|
1082
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
1083
|
+
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
|
|
1084
|
+
clientInfo: { name: "agent-kit", version: "1" }
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function acpPermissionOutcome(params) {
|
|
1088
|
+
const options = isRecord3(params) && Array.isArray(params.options) ? params.options : [];
|
|
1089
|
+
const pick = options.find((o) => isRecord3(o) && (o.optionId === "allow-once" || o.kind === "allow_once")) ?? options.find(
|
|
1090
|
+
(o) => isRecord3(o) && (typeof o.optionId === "string" && o.optionId.startsWith("allow") || typeof o.kind === "string" && o.kind.startsWith("allow"))
|
|
1091
|
+
);
|
|
1092
|
+
if (isRecord3(pick) && typeof pick.optionId === "string") {
|
|
1093
|
+
return { outcome: { outcome: "selected", optionId: pick.optionId } };
|
|
1094
|
+
}
|
|
1095
|
+
return { outcome: { outcome: "cancelled" } };
|
|
1096
|
+
}
|
|
1097
|
+
function acpQuestions(params) {
|
|
1098
|
+
const raw = isRecord3(params) && Array.isArray(params.questions) ? params.questions : [];
|
|
1099
|
+
const out = [];
|
|
1100
|
+
for (const q of raw) {
|
|
1101
|
+
if (!isRecord3(q) || typeof q.id !== "string" || !Array.isArray(q.options)) continue;
|
|
1102
|
+
const options = q.options.filter((o) => isRecord3(o) && typeof o.id === "string").map((o) => ({
|
|
1103
|
+
id: o.id,
|
|
1104
|
+
label: String(typeof o.label === "string" ? o.label : o.id).replace(/\|/g, "/").trim()
|
|
1105
|
+
}));
|
|
1106
|
+
if (options.length === 0) continue;
|
|
1107
|
+
out.push({ id: q.id, prompt: typeof q.prompt === "string" ? q.prompt : "", options });
|
|
1108
|
+
}
|
|
1109
|
+
return out;
|
|
1110
|
+
}
|
|
1111
|
+
var line2 = (event) => `${JSON.stringify(event)}
|
|
1112
|
+
`;
|
|
1113
|
+
async function runCursorAcp(opts) {
|
|
1114
|
+
const say = opts.log ?? ((text) => console.log(text));
|
|
1115
|
+
const env = mergeChildEnv(opts.env);
|
|
1116
|
+
const redact = claudeRedactions(env);
|
|
1117
|
+
const hitl = resolveHitlRunOptions(opts.hitl, { kind: "none", backend: "cursor-acp" });
|
|
1118
|
+
const render = opts.render ?? createStreamRenderer();
|
|
1119
|
+
const spawnFn = opts.spawnFn ?? (await import("child_process")).spawn;
|
|
1120
|
+
const record = emptyHitlRecord();
|
|
1121
|
+
const abort = new AbortController();
|
|
1122
|
+
if (opts.model) say("cursor-acp: --model is not sent over ACP; the agent's default model runs.");
|
|
1123
|
+
say("cursor tick: cursor-agent acp (JSON-RPC stdio, HITL relay, tool permissions allow-once).");
|
|
1124
|
+
const out = createWriteStream(opts.logPath, { flags: "w" });
|
|
1125
|
+
let logError = null;
|
|
1126
|
+
out.on("error", (err) => {
|
|
1127
|
+
logError = err;
|
|
1128
|
+
});
|
|
1129
|
+
const logLine = (text) => out.write(`${redactSecrets(text, redact)}
|
|
1130
|
+
`);
|
|
1131
|
+
const child = spawnFn("cursor-agent", cursorAcpArgs(), {
|
|
1132
|
+
cwd: opts.workspace,
|
|
1133
|
+
env,
|
|
1134
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1135
|
+
});
|
|
1136
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1137
|
+
let closed = false;
|
|
1138
|
+
let exitCode = null;
|
|
1139
|
+
let spawnError = null;
|
|
1140
|
+
let resolveClosed = () => {
|
|
1141
|
+
};
|
|
1142
|
+
const closedPromise = new Promise((resolve) => {
|
|
1143
|
+
resolveClosed = resolve;
|
|
1144
|
+
});
|
|
1145
|
+
const gone = (code2) => {
|
|
1146
|
+
if (closed) return;
|
|
1147
|
+
closed = true;
|
|
1148
|
+
exitCode = code2;
|
|
1149
|
+
abort.abort();
|
|
1150
|
+
for (const p of pending.values()) {
|
|
1151
|
+
p.reject({ message: `cursor-agent acp exited (code ${code2 ?? "signal"})` });
|
|
1152
|
+
}
|
|
1153
|
+
pending.clear();
|
|
1154
|
+
resolveClosed();
|
|
1155
|
+
};
|
|
1156
|
+
child.on("close", (code2) => gone(code2));
|
|
1157
|
+
child.on("error", (err) => {
|
|
1158
|
+
spawnError = new Error(redactSecrets(String(err), redact));
|
|
1159
|
+
gone(null);
|
|
1160
|
+
});
|
|
1161
|
+
let nextId = 1;
|
|
1162
|
+
let turnText = "";
|
|
1163
|
+
let sessionId = null;
|
|
1164
|
+
const send = (msg) => {
|
|
1165
|
+
const text = JSON.stringify(msg);
|
|
1166
|
+
logLine(text);
|
|
1167
|
+
try {
|
|
1168
|
+
child.stdin?.write(`${text}
|
|
1169
|
+
`);
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
const request = (method, params) => new Promise((resolve, reject) => {
|
|
1174
|
+
if (closed) {
|
|
1175
|
+
reject({ message: "cursor-agent acp is not running" });
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const id = nextId++;
|
|
1179
|
+
pending.set(id, { resolve, reject });
|
|
1180
|
+
send({ jsonrpc: "2.0", id, method, params });
|
|
1181
|
+
});
|
|
1182
|
+
const respond = (id, result) => send({ jsonrpc: "2.0", id, result });
|
|
1183
|
+
const respondError = (id, code2, message) => send({ jsonrpc: "2.0", id, error: { code: code2, message } });
|
|
1184
|
+
const ask = (gate) => askOperator(gate, {
|
|
1185
|
+
isTTY: hitl.isTTY,
|
|
1186
|
+
policy: hitl.policy,
|
|
1187
|
+
readAnswer: hitl.readAnswer,
|
|
1188
|
+
write: hitl.write,
|
|
1189
|
+
signal: abort.signal,
|
|
1190
|
+
onPromptStart: hitl.onPromptStart,
|
|
1191
|
+
onPromptEnd: hitl.onPromptEnd
|
|
1192
|
+
});
|
|
1193
|
+
const cancelSession = () => {
|
|
1194
|
+
if (sessionId && !closed)
|
|
1195
|
+
send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId } });
|
|
1196
|
+
};
|
|
1197
|
+
const handleAskQuestion = async (id, params) => {
|
|
1198
|
+
const answers = [];
|
|
1199
|
+
for (const q of acpQuestions(params)) {
|
|
1200
|
+
if (record.stop) break;
|
|
1201
|
+
const gate = {
|
|
1202
|
+
askId: CURSOR_ASK_QUESTION_ID,
|
|
1203
|
+
labels: q.options.map((o) => o.label),
|
|
1204
|
+
detection: "sentinel"
|
|
1205
|
+
};
|
|
1206
|
+
const outcome = await ask(gate);
|
|
1207
|
+
if (outcome.kind === "stop") {
|
|
1208
|
+
record.stop = outcome.stop;
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
record.replies.push(outcome.stamp);
|
|
1212
|
+
hitl.write(`\u2192 ${outcome.stamp.line}
|
|
1213
|
+
`);
|
|
1214
|
+
const n = /operator reply (\d+)/.exec(outcome.stamp.reply)?.[1];
|
|
1215
|
+
const option = n ? q.options[Number(n) - 1] : void 0;
|
|
1216
|
+
if (!option) {
|
|
1217
|
+
hitl.write(
|
|
1218
|
+
"cursor-acp: a free-text answer has no cursor/ask_question option; the question is answered as cancelled.\n"
|
|
1219
|
+
);
|
|
1220
|
+
respond(id, { outcome: { outcome: "cancelled" } });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
answers.push({ questionId: q.id, selectedOptionIds: [option.id] });
|
|
1224
|
+
}
|
|
1225
|
+
if (record.stop) {
|
|
1226
|
+
respond(id, { outcome: { outcome: "cancelled" } });
|
|
1227
|
+
cancelSession();
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
respond(id, { outcome: { outcome: "answered", answers } });
|
|
1231
|
+
};
|
|
1232
|
+
const handleRequest = (msg) => {
|
|
1233
|
+
const { id, method, params } = msg;
|
|
1234
|
+
if (method === "session/request_permission") {
|
|
1235
|
+
respond(id, acpPermissionOutcome(params));
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
if (method === "cursor/ask_question") {
|
|
1239
|
+
void handleAskQuestion(id, params);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
respondError(id, -32601, `agent-kit does not implement ${String(method)}`);
|
|
1243
|
+
};
|
|
1244
|
+
const handleNotification = (msg) => {
|
|
1245
|
+
if (msg.method !== "session/update" || !isRecord3(msg.params)) return;
|
|
1246
|
+
const update = isRecord3(msg.params.update) ? msg.params.update : null;
|
|
1247
|
+
if (!update) return;
|
|
1248
|
+
if (update.sessionUpdate === "agent_message_chunk") {
|
|
1249
|
+
const content = isRecord3(update.content) ? update.content : null;
|
|
1250
|
+
if (content?.type === "text" && typeof content.text === "string") turnText += content.text;
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (update.sessionUpdate === "tool_call") {
|
|
1254
|
+
const title = typeof update.title === "string" ? update.title : "tool";
|
|
1255
|
+
render.feed(
|
|
1256
|
+
line2({
|
|
1257
|
+
type: "assistant",
|
|
1258
|
+
message: {
|
|
1259
|
+
content: [
|
|
1260
|
+
{
|
|
1261
|
+
type: "tool_use",
|
|
1262
|
+
name: typeof update.kind === "string" ? update.kind : "tool",
|
|
1263
|
+
input: { title }
|
|
1264
|
+
}
|
|
1265
|
+
]
|
|
1266
|
+
}
|
|
1267
|
+
})
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
const take = (raw) => {
|
|
1272
|
+
const text = redactSecrets(raw.replace(/\r$/, ""), redact);
|
|
1273
|
+
if (!text.trim()) return;
|
|
1274
|
+
out.write(`${text}
|
|
1275
|
+
`);
|
|
1276
|
+
let msg;
|
|
1277
|
+
try {
|
|
1278
|
+
msg = JSON.parse(text);
|
|
1279
|
+
} catch {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (!isRecord3(msg)) return;
|
|
1283
|
+
if (typeof msg.method === "string") {
|
|
1284
|
+
if (msg.id !== void 0 && msg.id !== null) handleRequest(msg);
|
|
1285
|
+
else handleNotification(msg);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
if (typeof msg.id === "number") {
|
|
1289
|
+
const p = pending.get(msg.id);
|
|
1290
|
+
if (!p) return;
|
|
1291
|
+
pending.delete(msg.id);
|
|
1292
|
+
if (isRecord3(msg.error)) {
|
|
1293
|
+
p.reject({
|
|
1294
|
+
message: typeof msg.error.message === "string" ? msg.error.message : "JSON-RPC error",
|
|
1295
|
+
...typeof msg.error.code === "number" ? { code: msg.error.code } : {}
|
|
1296
|
+
});
|
|
1297
|
+
} else p.resolve(msg.result);
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
const decoder = new StringDecoder3("utf8");
|
|
1301
|
+
const splitter = new LineSplitter();
|
|
1302
|
+
child.stdout?.on("data", (chunk) => {
|
|
1303
|
+
splitter.feed(typeof chunk === "string" ? chunk : decoder.write(chunk), take);
|
|
1304
|
+
});
|
|
1305
|
+
const errBuffer = new RedactingStreamBuffer(redact);
|
|
1306
|
+
child.stderr?.on("data", (chunk) => {
|
|
1307
|
+
const text = redact.length ? errBuffer.push(chunk) : String(chunk);
|
|
1308
|
+
if (text) out.write(text);
|
|
1309
|
+
});
|
|
1310
|
+
child.stdin?.on("error", () => {
|
|
1311
|
+
});
|
|
1312
|
+
let stopped = false;
|
|
1313
|
+
const endChild = () => {
|
|
1314
|
+
if (stopped) return;
|
|
1315
|
+
stopped = true;
|
|
1316
|
+
try {
|
|
1317
|
+
child.stdin?.end();
|
|
1318
|
+
} catch {
|
|
1319
|
+
}
|
|
1320
|
+
const term = setTimeout(() => {
|
|
1321
|
+
if (closed) return;
|
|
1322
|
+
child.kill("SIGTERM");
|
|
1323
|
+
const kill = setTimeout(() => {
|
|
1324
|
+
if (!closed) child.kill("SIGKILL");
|
|
1325
|
+
}, ACP_KILL_GRACE_MS);
|
|
1326
|
+
kill.unref?.();
|
|
1327
|
+
}, ACP_EXIT_GRACE_MS);
|
|
1328
|
+
term.unref?.();
|
|
1329
|
+
};
|
|
1330
|
+
opts.onSpawn?.({
|
|
1331
|
+
backend: "cursor-acp",
|
|
1332
|
+
pid: child.pid,
|
|
1333
|
+
stop: (stop) => {
|
|
1334
|
+
if (closed || record.stop) return;
|
|
1335
|
+
record.stop = stop;
|
|
1336
|
+
cancelSession();
|
|
1337
|
+
abort.abort();
|
|
1338
|
+
endChild();
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1341
|
+
let lastResult;
|
|
1342
|
+
const finishTurn = (text, isError, subtype, errors) => {
|
|
1343
|
+
render.feed(line2({ type: "assistant", message: { content: [{ type: "text", text }] } }));
|
|
1344
|
+
render.feed(
|
|
1345
|
+
line2({
|
|
1346
|
+
type: "result",
|
|
1347
|
+
subtype,
|
|
1348
|
+
is_error: isError,
|
|
1349
|
+
result: text,
|
|
1350
|
+
session_id: sessionId ?? "",
|
|
1351
|
+
...errors.length ? { errors } : {}
|
|
1352
|
+
})
|
|
1353
|
+
);
|
|
1354
|
+
lastResult = { resultText: text, assistantTexts: text ? [text] : [], isError, subtype, errors };
|
|
1355
|
+
};
|
|
1356
|
+
const failed = (message, subtype = "error") => finishTurn(message, true, subtype, [message]);
|
|
1357
|
+
const session = async () => {
|
|
1358
|
+
const init = await request("initialize", acpInitializeParams());
|
|
1359
|
+
const methods = isRecord3(init) && Array.isArray(init.authMethods) ? init.authMethods : [];
|
|
1360
|
+
if (methods.some((m) => isRecord3(m) && m.id === "cursor_login")) {
|
|
1361
|
+
try {
|
|
1362
|
+
await request("authenticate", { methodId: "cursor_login" });
|
|
1363
|
+
} catch (err) {
|
|
1364
|
+
const message = err.message ?? String(err);
|
|
1365
|
+
throw {
|
|
1366
|
+
message: `Cursor sign-in needed: run \`cursor-agent login\` in a terminal (the kit never handles Cursor credentials). ${message}`
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
const created = await request("session/new", { cwd: opts.workspace, mcpServers: [] });
|
|
1371
|
+
if (!isRecord3(created) || typeof created.sessionId !== "string") {
|
|
1372
|
+
throw { message: "cursor-agent acp returned no sessionId" };
|
|
1373
|
+
}
|
|
1374
|
+
sessionId = created.sessionId;
|
|
1375
|
+
render.feed(
|
|
1376
|
+
line2({ type: "system", subtype: "init", session_id: sessionId, model: "cursor-acp" })
|
|
1377
|
+
);
|
|
1378
|
+
let prompt = opts.prompt;
|
|
1379
|
+
for (let turn = 0; turn < ACP_MAX_TURNS && !record.stop; turn += 1) {
|
|
1380
|
+
turnText = "";
|
|
1381
|
+
let stopReason = "end_turn";
|
|
1382
|
+
try {
|
|
1383
|
+
const res = await request("session/prompt", {
|
|
1384
|
+
sessionId,
|
|
1385
|
+
prompt: [{ type: "text", text: prompt }]
|
|
1386
|
+
});
|
|
1387
|
+
if (isRecord3(res) && typeof res.stopReason === "string") stopReason = res.stopReason;
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
failed(err.message ?? String(err));
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const text = turnText;
|
|
1393
|
+
finishTurn(text, stopReason === "refusal", stopReason, []);
|
|
1394
|
+
if (record.stop || stopReason === "cancelled") return;
|
|
1395
|
+
const gate = detectHitlGate(text, []);
|
|
1396
|
+
if (!gate) return;
|
|
1397
|
+
if (gate.detection === "fallback") record.fallbackDetections += 1;
|
|
1398
|
+
const outcome = await ask(gate);
|
|
1399
|
+
if (outcome.kind === "stop") {
|
|
1400
|
+
record.stop = outcome.stop;
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
record.replies.push(outcome.stamp);
|
|
1404
|
+
hitl.write(`\u2192 ${outcome.stamp.line}
|
|
1405
|
+
`);
|
|
1406
|
+
prompt = outcome.stamp.line;
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
await session().catch((err) => {
|
|
1410
|
+
failed(err.message ?? String(err));
|
|
1411
|
+
});
|
|
1412
|
+
const failure = spawnError;
|
|
1413
|
+
if (failure) {
|
|
1414
|
+
out.end();
|
|
1415
|
+
throw new Error(`cursor-acp backend failed to start: ${failure.message}`);
|
|
1416
|
+
}
|
|
1417
|
+
const endedByClient = !closed;
|
|
1418
|
+
endChild();
|
|
1419
|
+
await closedPromise;
|
|
1420
|
+
const rest = splitter.end();
|
|
1421
|
+
if (rest.trim()) take(rest);
|
|
1422
|
+
const errRest = errBuffer.flush();
|
|
1423
|
+
if (errRest) out.write(errRest);
|
|
1424
|
+
render.end();
|
|
1425
|
+
await new Promise((resolve) => out.end(() => resolve()));
|
|
1426
|
+
if (logError) throw new Error(`cursor-acp backend: ${redactSecrets(String(logError), redact)}`);
|
|
1427
|
+
const code = lastResult?.isError ? 1 : endedByClient ? 0 : exitCode ?? 1;
|
|
1428
|
+
return { exitCode: code, hitl: record, ...lastResult ? { lastResult } : {} };
|
|
1429
|
+
}
|
|
1430
|
+
var cursorAcpBackend = {
|
|
1431
|
+
id: "cursor-acp",
|
|
1432
|
+
async resolve() {
|
|
1433
|
+
return whichBinary("cursor-agent");
|
|
1434
|
+
},
|
|
1435
|
+
run: runCursorAcp
|
|
1436
|
+
};
|
|
1437
|
+
|
|
727
1438
|
// src/plan-loop/backends.ts
|
|
1439
|
+
function createLineTranslator(translate) {
|
|
1440
|
+
const decoder = new StringDecoder4("utf8");
|
|
1441
|
+
const lines = new LineSplitter();
|
|
1442
|
+
const run = (line3, out) => {
|
|
1443
|
+
try {
|
|
1444
|
+
out.push(...translate(line3));
|
|
1445
|
+
} catch {
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
return {
|
|
1449
|
+
push(chunk) {
|
|
1450
|
+
const text = typeof chunk === "string" ? chunk : decoder.write(chunk);
|
|
1451
|
+
const out = [];
|
|
1452
|
+
lines.feed(text, (line3) => run(line3, out));
|
|
1453
|
+
return out.length ? `${out.join("\n")}
|
|
1454
|
+
` : "";
|
|
1455
|
+
},
|
|
1456
|
+
end() {
|
|
1457
|
+
const out = [];
|
|
1458
|
+
lines.feed(decoder.end(), (line3) => run(line3, out));
|
|
1459
|
+
const rest = lines.end();
|
|
1460
|
+
if (rest.trim()) run(rest, out);
|
|
1461
|
+
return out.length ? `${out.join("\n")}
|
|
1462
|
+
` : "";
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
728
1466
|
var HITL_KILL_GRACE_MS = 5e3;
|
|
729
1467
|
function resolveHitlRunOptions(hitl, transport) {
|
|
730
1468
|
return {
|
|
@@ -754,7 +1492,7 @@ function redactSecrets(text, secrets) {
|
|
|
754
1492
|
var REDACT_HOLD_MAX_CHARS = 64 * 1024;
|
|
755
1493
|
var RedactingStreamBuffer = class {
|
|
756
1494
|
pending = "";
|
|
757
|
-
decoder = new
|
|
1495
|
+
decoder = new StringDecoder4("utf8");
|
|
758
1496
|
secrets;
|
|
759
1497
|
keep;
|
|
760
1498
|
constructor(secrets) {
|
|
@@ -839,6 +1577,14 @@ function claudeRunCaps(env) {
|
|
|
839
1577
|
}
|
|
840
1578
|
return { caps, invalid };
|
|
841
1579
|
}
|
|
1580
|
+
var VENDOR_SECRET_ENV = [
|
|
1581
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
1582
|
+
"CURSOR_API_KEY",
|
|
1583
|
+
"CURSOR_AUTH_TOKEN",
|
|
1584
|
+
"OPENAI_API_KEY",
|
|
1585
|
+
"CODEX_API_KEY",
|
|
1586
|
+
"CODEX_ACCESS_TOKEN"
|
|
1587
|
+
];
|
|
842
1588
|
var REDACT_DERIVED_MIN_CHARS = 4;
|
|
843
1589
|
var REDACT_DERIVED_SKIP = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]);
|
|
844
1590
|
function derivedSecretForms(value) {
|
|
@@ -874,7 +1620,7 @@ function claudeRedactions(env) {
|
|
|
874
1620
|
seen.add(value);
|
|
875
1621
|
entries.push({ label, value });
|
|
876
1622
|
};
|
|
877
|
-
for (const key of CLAUDE_ENV_PASSTHROUGH) {
|
|
1623
|
+
for (const key of [...CLAUDE_ENV_PASSTHROUGH, ...VENDOR_SECRET_ENV]) {
|
|
878
1624
|
const value = env[key];
|
|
879
1625
|
if (!value) continue;
|
|
880
1626
|
add(key, value, false);
|
|
@@ -884,12 +1630,7 @@ function claudeRedactions(env) {
|
|
|
884
1630
|
return entries;
|
|
885
1631
|
}
|
|
886
1632
|
async function whichBinary(bin) {
|
|
887
|
-
|
|
888
|
-
const out = execFileSync("which", [bin], { encoding: "utf8" }).trim();
|
|
889
|
-
return out || null;
|
|
890
|
-
} catch {
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
1633
|
+
return findOnPath(bin);
|
|
893
1634
|
}
|
|
894
1635
|
function spawnLogged(command, args, logPath, options = {}) {
|
|
895
1636
|
const spawnFn = options.spawnFn ?? spawn;
|
|
@@ -897,18 +1638,19 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
897
1638
|
const redactedError = (err) => new Error(redactSecrets(String(err), redact));
|
|
898
1639
|
const hitl = options.hitl;
|
|
899
1640
|
const stdinTransport = hitl?.transport.kind === "stdin-stream-json";
|
|
1641
|
+
const stdinPiped = stdinTransport || options.stdinText !== void 0;
|
|
900
1642
|
return new Promise((resolve, reject) => {
|
|
901
1643
|
let out;
|
|
902
1644
|
let child;
|
|
903
1645
|
let logError = null;
|
|
904
1646
|
try {
|
|
905
|
-
out =
|
|
1647
|
+
out = createWriteStream2(logPath, { flags: options.appendLog ? "a" : "w" });
|
|
906
1648
|
out.on("error", (err) => {
|
|
907
1649
|
logError = err;
|
|
908
1650
|
reject(redactedError(err));
|
|
909
1651
|
});
|
|
910
1652
|
child = spawnFn(command, args, {
|
|
911
|
-
stdio: [
|
|
1653
|
+
stdio: [stdinPiped ? "pipe" : "ignore", "pipe", "pipe"],
|
|
912
1654
|
...options.cwd ? { cwd: options.cwd } : {},
|
|
913
1655
|
...options.env ? { env: options.env } : {}
|
|
914
1656
|
});
|
|
@@ -918,12 +1660,25 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
918
1660
|
}
|
|
919
1661
|
const render = options.render ?? createStreamRenderer();
|
|
920
1662
|
const relay = hitl ? createRelay(hitl, child, redactedError) : null;
|
|
921
|
-
const
|
|
1663
|
+
const translator = options.translate ? createLineTranslator(options.translate) : null;
|
|
1664
|
+
const sinks = (text) => {
|
|
922
1665
|
if (text.length === 0) return;
|
|
923
|
-
out.write(text);
|
|
924
1666
|
render.feed(text);
|
|
925
1667
|
relay?.feed(text);
|
|
926
1668
|
};
|
|
1669
|
+
const emit = (text) => {
|
|
1670
|
+
if (text.length === 0) return;
|
|
1671
|
+
out.write(text);
|
|
1672
|
+
sinks(translator ? translator.push(text) : text);
|
|
1673
|
+
};
|
|
1674
|
+
if (options.stdinText !== void 0 && !stdinTransport) {
|
|
1675
|
+
const stdin = child.stdin;
|
|
1676
|
+
if (stdin) {
|
|
1677
|
+
stdin.on("error", () => {
|
|
1678
|
+
});
|
|
1679
|
+
stdin.end(options.stdinText);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
927
1682
|
if (relay?.startError) {
|
|
928
1683
|
out.end();
|
|
929
1684
|
reject(relay.startError);
|
|
@@ -962,6 +1717,7 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
962
1717
|
});
|
|
963
1718
|
child.on("close", (code) => {
|
|
964
1719
|
for (const buffer of buffers) emit(buffer.flush());
|
|
1720
|
+
if (translator) sinks(translator.end());
|
|
965
1721
|
render.end();
|
|
966
1722
|
relay?.finish();
|
|
967
1723
|
const finish = () => {
|
|
@@ -970,10 +1726,12 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
970
1726
|
if (failure) reject(redactedError(failure));
|
|
971
1727
|
else if (relay) {
|
|
972
1728
|
const lastResult = relay.lastResult();
|
|
1729
|
+
const resumeWith = relay.resumeWith();
|
|
973
1730
|
resolve({
|
|
974
1731
|
exitCode: code ?? 1,
|
|
975
1732
|
hitl: relay.record,
|
|
976
|
-
...lastResult ? { lastResult } : {}
|
|
1733
|
+
...lastResult ? { lastResult } : {},
|
|
1734
|
+
...resumeWith ? { resumeWith } : {}
|
|
977
1735
|
});
|
|
978
1736
|
} else resolve({ exitCode: code ?? 1 });
|
|
979
1737
|
});
|
|
@@ -986,6 +1744,8 @@ function spawnLogged(command, args, logPath, options = {}) {
|
|
|
986
1744
|
function createRelay(hitl, child, redactedError) {
|
|
987
1745
|
const record = emptyHitlRecord();
|
|
988
1746
|
const stdinTransport = hitl.transport.kind === "stdin-stream-json";
|
|
1747
|
+
const resumeTransport = hitl.transport.kind === "resume";
|
|
1748
|
+
let resumeWith;
|
|
989
1749
|
const abort = new AbortController();
|
|
990
1750
|
const grace = hitl.killAfterMs ?? HITL_KILL_GRACE_MS;
|
|
991
1751
|
let startError = null;
|
|
@@ -1065,6 +1825,13 @@ function createRelay(hitl, child, redactedError) {
|
|
|
1065
1825
|
stopChild();
|
|
1066
1826
|
return;
|
|
1067
1827
|
}
|
|
1828
|
+
if (resumeTransport) {
|
|
1829
|
+
record.replies.push(outcome.stamp);
|
|
1830
|
+
hitl.write(`\u2192 ${outcome.stamp.line}
|
|
1831
|
+
`);
|
|
1832
|
+
resumeWith = outcome.stamp.line;
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1068
1835
|
if (closed || !stdin) {
|
|
1069
1836
|
record.stop = unansweredStop(gate.askId, "child exited");
|
|
1070
1837
|
return;
|
|
@@ -1086,7 +1853,7 @@ function createRelay(hitl, child, redactedError) {
|
|
|
1086
1853
|
child.on("close", () => {
|
|
1087
1854
|
closed = true;
|
|
1088
1855
|
for (const t of timers) clearTimeout(t);
|
|
1089
|
-
abort.abort();
|
|
1856
|
+
if (!resumeTransport) abort.abort();
|
|
1090
1857
|
const settle = () => resolveClosed();
|
|
1091
1858
|
if (pendingAsk) pendingAsk.then(settle, settle);
|
|
1092
1859
|
else settle();
|
|
@@ -1095,6 +1862,7 @@ function createRelay(hitl, child, redactedError) {
|
|
|
1095
1862
|
startError,
|
|
1096
1863
|
record,
|
|
1097
1864
|
lastResult: () => lastResult,
|
|
1865
|
+
resumeWith: () => resumeWith,
|
|
1098
1866
|
feed: (text) => watcher.feed(text),
|
|
1099
1867
|
finish: () => watcher.end(),
|
|
1100
1868
|
closed: () => closedPromise,
|
|
@@ -1143,7 +1911,7 @@ var cursorAgentBackend = {
|
|
|
1143
1911
|
});
|
|
1144
1912
|
}
|
|
1145
1913
|
};
|
|
1146
|
-
var CLAUDE_MIN_VERSION = "2.1.259";
|
|
1914
|
+
var CLAUDE_MIN_VERSION = VENDOR_CLI_PINS.claude.min ?? "2.1.259";
|
|
1147
1915
|
function parseClaudeVersion(raw) {
|
|
1148
1916
|
if (!raw) return null;
|
|
1149
1917
|
const m = /(\d+)\.(\d+)\.(\d+)/.exec(raw);
|
|
@@ -1161,7 +1929,7 @@ function compareVersions(a, b) {
|
|
|
1161
1929
|
var claudeVersionCache = /* @__PURE__ */ new Map();
|
|
1162
1930
|
function defaultVersionFn(bin) {
|
|
1163
1931
|
try {
|
|
1164
|
-
return
|
|
1932
|
+
return execFileSync2(bin, ["--version"], {
|
|
1165
1933
|
encoding: "utf8",
|
|
1166
1934
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1167
1935
|
});
|
|
@@ -1188,6 +1956,8 @@ function checkClaudeVersion(bin, versionFn = defaultVersionFn) {
|
|
|
1188
1956
|
message: `claude ${version} is older than ${CLAUDE_MIN_VERSION}, the first release that accepts \`--permission-prompts none\`. Run \`claude update\` and retry.`
|
|
1189
1957
|
};
|
|
1190
1958
|
}
|
|
1959
|
+
const pin = vendorVersionStatus("claude", version);
|
|
1960
|
+
if (pin.status === "newer-than-tested") return { ok: true, version, warning: pin.message };
|
|
1191
1961
|
return { ok: true, version };
|
|
1192
1962
|
}
|
|
1193
1963
|
function claudeHeadlessArgs(opts = {}) {
|
|
@@ -1218,9 +1988,9 @@ function claudeHeadlessArgs(opts = {}) {
|
|
|
1218
1988
|
async function missingClaudeAdapter(workspace, prompt) {
|
|
1219
1989
|
const m = /^\/([A-Za-z0-9][A-Za-z0-9_-]*)/.exec(prompt.trimStart());
|
|
1220
1990
|
if (!m?.[1]) return null;
|
|
1221
|
-
const rel =
|
|
1991
|
+
const rel = path2.join(".claude", "commands", `${m[1]}.md`);
|
|
1222
1992
|
try {
|
|
1223
|
-
await
|
|
1993
|
+
await access2(path2.join(workspace, rel));
|
|
1224
1994
|
return null;
|
|
1225
1995
|
} catch {
|
|
1226
1996
|
return rel;
|
|
@@ -1232,7 +2002,7 @@ var claudeBackend = {
|
|
|
1232
2002
|
return whichBinary("claude");
|
|
1233
2003
|
},
|
|
1234
2004
|
async run(opts) {
|
|
1235
|
-
const log = opts.log ?? ((
|
|
2005
|
+
const log = opts.log ?? ((line3) => console.log(line3));
|
|
1236
2006
|
const env = claudeChildEnv(opts.env);
|
|
1237
2007
|
const redact = claudeRedactions(env);
|
|
1238
2008
|
const version = checkClaudeVersion("claude", opts.versionFn);
|
|
@@ -1279,14 +2049,19 @@ var claudeBackend = {
|
|
|
1279
2049
|
}
|
|
1280
2050
|
}
|
|
1281
2051
|
};
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
2052
|
+
function backends() {
|
|
2053
|
+
return {
|
|
2054
|
+
"cursor-agent": cursorAgentBackend,
|
|
2055
|
+
claude: claudeBackend,
|
|
2056
|
+
codex: codexBackend,
|
|
2057
|
+
"cursor-acp": cursorAcpBackend
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
1286
2060
|
function getBackend(id) {
|
|
1287
|
-
const
|
|
2061
|
+
const all = backends();
|
|
2062
|
+
const backend = all[id];
|
|
1288
2063
|
if (!backend) {
|
|
1289
|
-
throw new Error(`Unknown backend '${id}'. Supported: ${Object.keys(
|
|
2064
|
+
throw new Error(`Unknown backend '${id}'. Supported: ${Object.keys(all).join(", ")}`);
|
|
1290
2065
|
}
|
|
1291
2066
|
return backend;
|
|
1292
2067
|
}
|
|
@@ -1295,11 +2070,14 @@ export {
|
|
|
1295
2070
|
LineSplitter,
|
|
1296
2071
|
RESERVED_GATE_IDS,
|
|
1297
2072
|
HITL_UNANSWERED_EXIT_CODE,
|
|
2073
|
+
formatHitlGateLine,
|
|
1298
2074
|
operatorQuitStop,
|
|
1299
2075
|
formatHitlSummary,
|
|
1300
2076
|
hitlExitCode,
|
|
2077
|
+
codexBackend,
|
|
1301
2078
|
parseStreamLine,
|
|
1302
2079
|
renderStreamEvent,
|
|
2080
|
+
cursorAcpBackend,
|
|
1303
2081
|
resolveHitlRunOptions,
|
|
1304
2082
|
redactSecrets,
|
|
1305
2083
|
RedactingStreamBuffer,
|