@dadado/agent-kit-cli 5.17.0 → 6.0.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/LICENSE +201 -74
- package/NOTICE +2 -0
- package/README.md +3 -3
- package/dashboard/README.md +10 -1
- package/dashboard/dashboard-data.mjs +23 -13
- package/dashboard/dashboard.html +58 -0
- package/dashboard/lib/processes.mjs +1 -1
- package/dashboard/lib/run-logs.d.mts +34 -0
- package/dashboard/lib/run-logs.mjs +218 -0
- package/dashboard/lib/semantic-model.mjs +4 -3
- package/dashboard/serve.mjs +4 -4
- package/dashboard/start-broadcast.mjs +1 -1
- package/dashboard/start.mjs +1 -1
- package/dist/{add-HTYIFKMN.js → add-YTCDLPUS.js} +4 -4
- package/dist/{chunk-SMGFN3Z4.js → chunk-2FQX2O4I.js} +4 -4
- package/dist/{chunk-PNG7LO2F.js → chunk-3DGTPONS.js} +1 -1
- package/dist/{chunk-VY7MTPJ2.js → chunk-426GITN7.js} +1 -1
- package/dist/{chunk-2IPXQFJE.js → chunk-4CRBUEJ6.js} +2 -2
- package/dist/{chunk-STGFBAYT.js → chunk-4U7DLAQH.js} +11 -19
- package/dist/{chunk-77V75A7O.js → chunk-5B4DVO6C.js} +1 -1
- package/dist/{chunk-4IWG5CVE.js → chunk-B3VR5YBQ.js} +10 -5
- package/dist/{chunk-OUQZC744.js → chunk-B5CH47OS.js} +852 -74
- package/dist/{chunk-2RDRMGD3.js → chunk-BBVBYQJO.js} +2 -2
- package/dist/chunk-C26I6HTO.js +39 -0
- package/dist/{chunk-AW74INUW.js → chunk-DBPNGZNH.js} +6 -5
- package/dist/{chunk-DQA434Y4.js → chunk-DBXDV5O7.js} +3 -3
- package/dist/{chunk-S6GMFDKV.js → chunk-DRND2KYV.js} +2 -2
- package/dist/{chunk-F4VMYR7U.js → chunk-GN4SAZF2.js} +4 -4
- package/dist/{chunk-ZXKZBTXE.js → chunk-HAFRAXGV.js} +1 -1
- package/dist/{chunk-CBCW5R5J.js → chunk-MCH3E46V.js} +2 -2
- package/dist/{chunk-WW3YZKHW.js → chunk-NDIGZXFF.js} +26 -5
- package/dist/{chunk-TFZ2JUXH.js → chunk-PAB35H27.js} +523 -73
- package/dist/{chunk-NQ7IQ5XT.js → chunk-PFEUZXVT.js} +1 -1
- package/dist/{chunk-W24CMQ32.js → chunk-PPKX55GI.js} +3 -3
- package/dist/{chunk-ZCTSUQRR.js → chunk-T6P5ZMG2.js} +3 -3
- package/dist/{chunk-N3HTJVBR.js → chunk-VTOUQAVR.js} +1 -1
- package/dist/{chunk-B73KQ5KX.js → chunk-WFXHMP47.js} +11 -11
- package/dist/chunk-WGLPZXNU.js +45 -0
- package/dist/{chunk-KD2MYBB6.js → chunk-WUWRGOUV.js} +2 -2
- package/dist/{contribute-C6AW2P6J.js → contribute-A7NCWTUM.js} +5 -5
- package/dist/{cursor-awareness-KTL2RGKD.js → cursor-awareness-TN5JXD2C.js} +3 -3
- package/dist/{diff-VHNR62SI.js → diff-4MDU5SLE.js} +6 -6
- package/dist/{doctor-PET2HRRJ.js → doctor-MQAXOFKM.js} +16 -14
- package/dist/{dogfood-KKFVRWT6.js → dogfood-2JAHSYQA.js} +4 -4
- package/dist/{handoff-3UI6VKVG.js → handoff-MK5JG3J5.js} +4 -4
- package/dist/{hook-DYIMKJYL.js → hook-D2WHBKRN.js} +12 -12
- package/dist/index.js +26 -24
- package/dist/{init-Q4LBM6KT.js → init-7T6KNDFE.js} +17 -16
- package/dist/install-6G3D7J4U.js +35 -0
- package/dist/{monitors-QV2IS3MP.js → monitors-PDXBLB3U.js} +1 -1
- package/dist/{plan-index-JMC7Z4DW.js → plan-index-V7T5MKL6.js} +3 -3
- package/dist/{public-inbound-radar-WP3XF7GS.js → public-inbound-radar-5U4JMBRU.js} +3 -3
- package/dist/{remote-issues-SZ4HDQL5.js → remote-issues-APYOMIJC.js} +4 -4
- package/dist/{run-I3BFGULT.js → run-MWOI7UW5.js} +7 -6
- package/dist/{run-plan-FO7CL4M3.js → run-plan-UWQD3BIJ.js} +37 -6
- package/dist/{scan-NI2SWJ2E.js → scan-3MIUE7OR.js} +2 -2
- package/dist/{setup-global-M42GEODI.js → setup-global-AT4DD3MG.js} +9 -9
- package/dist/{status-XPVQVQMX.js → status-MXNTBKPL.js} +11 -11
- package/dist/{update-FJ2EBZVO.js → update-P62637ZI.js} +12 -12
- package/package.json +10 -6
- package/dist/chunk-5VCOWTTP.js +0 -6
- package/dist/install-RHOS3OYZ.js +0 -34
|
@@ -8,18 +8,19 @@ import {
|
|
|
8
8
|
extractHandoffNamedPlans,
|
|
9
9
|
namedPlanCandidatePaths,
|
|
10
10
|
parsePlanFrontmatter
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-WUWRGOUV.js";
|
|
12
12
|
import {
|
|
13
13
|
LineSplitter,
|
|
14
14
|
RedactingStreamBuffer,
|
|
15
15
|
claudeRedactions,
|
|
16
|
+
formatHitlGateLine,
|
|
16
17
|
formatHitlSummary,
|
|
17
18
|
operatorQuitStop,
|
|
18
19
|
parseStreamLine,
|
|
19
20
|
redactSecrets,
|
|
20
21
|
renderStreamEvent,
|
|
21
22
|
whichBinary
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-B5CH47OS.js";
|
|
23
24
|
import {
|
|
24
25
|
TtySpinner,
|
|
25
26
|
shouldUseVisualMotion,
|
|
@@ -30,19 +31,22 @@ import {
|
|
|
30
31
|
logger
|
|
31
32
|
} from "./chunk-ZB43YMHW.js";
|
|
32
33
|
import {
|
|
33
|
-
HANDOFF_REL
|
|
34
|
-
|
|
34
|
+
HANDOFF_REL,
|
|
35
|
+
STATE_ROOT_ENV,
|
|
36
|
+
ensureLoopLogsDir,
|
|
37
|
+
runnerStatePaths
|
|
38
|
+
} from "./chunk-C26I6HTO.js";
|
|
35
39
|
import {
|
|
36
40
|
fileExists,
|
|
37
41
|
readJson
|
|
38
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-5B4DVO6C.js";
|
|
39
43
|
|
|
40
44
|
// src/mission-control/live-run.ts
|
|
41
45
|
import { readFile as readFile2 } from "fs/promises";
|
|
42
46
|
import path2 from "path";
|
|
43
47
|
|
|
44
48
|
// src/plan-loop/plan-state.ts
|
|
45
|
-
import {
|
|
49
|
+
import { readdir, readFile } from "fs/promises";
|
|
46
50
|
import path from "path";
|
|
47
51
|
function countPendingTodos(raw) {
|
|
48
52
|
return parsePlanFrontmatter(raw).todos.filter(
|
|
@@ -486,18 +490,21 @@ async function withLiveTui(opts, run) {
|
|
|
486
490
|
|
|
487
491
|
// src/plan-loop/detect.ts
|
|
488
492
|
var DETECT_ORDER = ["cursor-agent", "claude"];
|
|
493
|
+
var EXPLICIT_ONLY_BACKENDS = ["codex", "cursor-acp"];
|
|
489
494
|
var INSTALL_HINTS = {
|
|
490
495
|
"cursor-agent": "Install the Cursor Agent CLI and ensure `cursor-agent` is on PATH (https://cursor.com/docs/cli).",
|
|
491
|
-
claude: "Install Claude Code and ensure `claude` is on PATH (https://code.claude.com/docs/en/quickstart)."
|
|
496
|
+
claude: "Install Claude Code and ensure `claude` is on PATH (https://code.claude.com/docs/en/quickstart).",
|
|
497
|
+
"cursor-acp": "Install the Cursor Agent CLI and ensure `cursor-agent` is on PATH (https://cursor.com/docs/cli/acp); sign in with `cursor-agent login`.",
|
|
498
|
+
codex: "Install the Codex CLI and ensure `codex` is on PATH (https://developers.openai.com/codex/cli)."
|
|
492
499
|
};
|
|
493
500
|
function isBackendId(id) {
|
|
494
|
-
return id === "cursor-agent" || id === "claude";
|
|
501
|
+
return id === "cursor-agent" || id === "claude" || id === "codex" || id === "cursor-acp";
|
|
495
502
|
}
|
|
496
503
|
function isRequestedBackend(id) {
|
|
497
504
|
return id === "auto" || isBackendId(id);
|
|
498
505
|
}
|
|
499
506
|
function listDetectBackendIds() {
|
|
500
|
-
return ["auto", ...DETECT_ORDER];
|
|
507
|
+
return ["auto", ...DETECT_ORDER, ...EXPLICIT_ONLY_BACKENDS];
|
|
501
508
|
}
|
|
502
509
|
async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
503
510
|
const id = requested.trim().toLowerCase();
|
|
@@ -534,8 +541,220 @@ async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
|
534
541
|
return { ok: true, id, bin };
|
|
535
542
|
}
|
|
536
543
|
|
|
544
|
+
// src/plan-loop/driver-events.ts
|
|
545
|
+
import { createInterface } from "readline";
|
|
546
|
+
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
547
|
+
var DRIVER_PROTOCOL_MAJOR = 1;
|
|
548
|
+
var DRIVER_PROTOCOL_VERSION = "1.1.0";
|
|
549
|
+
var DRIVER_EVENTS_FORMATS = ["ndjson"];
|
|
550
|
+
var INBOUND_MAX_CHARS = 64 * 1024;
|
|
551
|
+
var AGENT_PENDING_MAX_CHARS = 4 * 1024 * 1024;
|
|
552
|
+
function formatDriverEvent(event, now = () => /* @__PURE__ */ new Date()) {
|
|
553
|
+
return `${JSON.stringify({ v: DRIVER_PROTOCOL_MAJOR, ts: now().toISOString(), ...event })}
|
|
554
|
+
`;
|
|
555
|
+
}
|
|
556
|
+
function createDriverEmitter(write = (text) => {
|
|
557
|
+
process.stdout.write(text);
|
|
558
|
+
}, now) {
|
|
559
|
+
return {
|
|
560
|
+
emit(event) {
|
|
561
|
+
try {
|
|
562
|
+
write(formatDriverEvent(event, now));
|
|
563
|
+
} catch {
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function parseDriverInbound(line) {
|
|
569
|
+
const text = line.trim();
|
|
570
|
+
if (text.length > INBOUND_MAX_CHARS) return { ok: false, error: "line too long" };
|
|
571
|
+
let value;
|
|
572
|
+
try {
|
|
573
|
+
value = JSON.parse(text);
|
|
574
|
+
} catch {
|
|
575
|
+
return { ok: false, error: "not JSON" };
|
|
576
|
+
}
|
|
577
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
578
|
+
return { ok: false, error: "not a JSON object" };
|
|
579
|
+
}
|
|
580
|
+
const msg = value;
|
|
581
|
+
if (msg.v !== void 0 && msg.v !== DRIVER_PROTOCOL_MAJOR) {
|
|
582
|
+
return { ok: false, error: `unsupported protocol v${String(msg.v)}` };
|
|
583
|
+
}
|
|
584
|
+
if (msg.type === "stop") {
|
|
585
|
+
return {
|
|
586
|
+
ok: true,
|
|
587
|
+
message: { type: "stop", ...typeof msg.reason === "string" ? { reason: msg.reason } : {} }
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
if (msg.type !== "hitl_reply")
|
|
591
|
+
return { ok: false, error: `unknown type ${JSON.stringify(msg.type)}` };
|
|
592
|
+
if (typeof msg.askId !== "string" || !/^[a-z0-9-]+$/.test(msg.askId)) {
|
|
593
|
+
return { ok: false, error: "hitl_reply.askId must be a kebab-case string" };
|
|
594
|
+
}
|
|
595
|
+
if (typeof msg.reply === "number") {
|
|
596
|
+
if (!Number.isInteger(msg.reply) || msg.reply < 1) {
|
|
597
|
+
return { ok: false, error: "hitl_reply.reply number must be a positive integer" };
|
|
598
|
+
}
|
|
599
|
+
return { ok: true, message: { type: "hitl_reply", askId: msg.askId, reply: msg.reply } };
|
|
600
|
+
}
|
|
601
|
+
if (typeof msg.reply === "string" && msg.reply.trim() && !/[\r\n]/.test(msg.reply)) {
|
|
602
|
+
return { ok: true, message: { type: "hitl_reply", askId: msg.askId, reply: msg.reply.trim() } };
|
|
603
|
+
}
|
|
604
|
+
return { ok: false, error: "hitl_reply.reply must be a number or a one-line non-empty string" };
|
|
605
|
+
}
|
|
606
|
+
function inboundToRawAnswer(gate, parsed) {
|
|
607
|
+
if (!parsed.ok) return { kind: "stop", cause: "protocol error", detail: parsed.error };
|
|
608
|
+
const message = parsed.message;
|
|
609
|
+
if (message.type === "stop") return { kind: "stop", cause: "driver stop" };
|
|
610
|
+
if (message.askId !== gate.askId) {
|
|
611
|
+
return {
|
|
612
|
+
kind: "stop",
|
|
613
|
+
cause: "protocol error",
|
|
614
|
+
detail: `reply for ${message.askId} while gate ${gate.askId} is open`
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
return { kind: "line", text: String(message.reply) };
|
|
618
|
+
}
|
|
619
|
+
function createDriverAnswerReader(opts) {
|
|
620
|
+
const input = opts.input ?? process.stdin;
|
|
621
|
+
const queue = [];
|
|
622
|
+
let ended = false;
|
|
623
|
+
let waiter = null;
|
|
624
|
+
let rl = null;
|
|
625
|
+
const ensure = () => {
|
|
626
|
+
if (rl) return;
|
|
627
|
+
rl = createInterface({ input, terminal: false, crlfDelay: Number.POSITIVE_INFINITY });
|
|
628
|
+
rl.on("line", (line) => {
|
|
629
|
+
if (!line.trim()) return;
|
|
630
|
+
if (waiter) {
|
|
631
|
+
const w = waiter;
|
|
632
|
+
waiter = null;
|
|
633
|
+
w(line);
|
|
634
|
+
} else queue.push(line);
|
|
635
|
+
});
|
|
636
|
+
rl.on("close", () => {
|
|
637
|
+
ended = true;
|
|
638
|
+
if (waiter) {
|
|
639
|
+
const w = waiter;
|
|
640
|
+
waiter = null;
|
|
641
|
+
w(null);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
const nextLine = (signal) => new Promise((resolve) => {
|
|
646
|
+
const queued = queue.shift();
|
|
647
|
+
if (queued !== void 0) {
|
|
648
|
+
resolve(queued);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (ended) {
|
|
652
|
+
resolve(null);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const onAbort = () => {
|
|
656
|
+
if (waiter === settle) waiter = null;
|
|
657
|
+
resolve(null);
|
|
658
|
+
};
|
|
659
|
+
const settle = (line) => {
|
|
660
|
+
signal?.removeEventListener("abort", onAbort);
|
|
661
|
+
resolve(line);
|
|
662
|
+
};
|
|
663
|
+
if (signal?.aborted) {
|
|
664
|
+
resolve(null);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
668
|
+
waiter = settle;
|
|
669
|
+
});
|
|
670
|
+
return {
|
|
671
|
+
read: async (gate, readOpts) => {
|
|
672
|
+
ensure();
|
|
673
|
+
opts.emitter.emit({
|
|
674
|
+
type: "hitl_gate",
|
|
675
|
+
tick: opts.tick(),
|
|
676
|
+
askId: gate.askId,
|
|
677
|
+
labels: [...gate.labels],
|
|
678
|
+
detection: gate.detection,
|
|
679
|
+
line: formatHitlGateLine(gate)
|
|
680
|
+
});
|
|
681
|
+
const line = await nextLine(readOpts.signal);
|
|
682
|
+
if (line === null) return { kind: "eof" };
|
|
683
|
+
return inboundToRawAnswer(gate, parseDriverInbound(line));
|
|
684
|
+
},
|
|
685
|
+
close: () => {
|
|
686
|
+
const r = rl;
|
|
687
|
+
rl = null;
|
|
688
|
+
r?.close();
|
|
689
|
+
if (typeof input.pause === "function") {
|
|
690
|
+
input.pause();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
var DriverAgentSink = class {
|
|
696
|
+
constructor(emitter, tick) {
|
|
697
|
+
this.emitter = emitter;
|
|
698
|
+
this.tick = tick;
|
|
699
|
+
}
|
|
700
|
+
emitter;
|
|
701
|
+
tick;
|
|
702
|
+
pending = "";
|
|
703
|
+
dropped = false;
|
|
704
|
+
decoder = new StringDecoder2("utf8");
|
|
705
|
+
feed(chunk) {
|
|
706
|
+
try {
|
|
707
|
+
const text = typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
708
|
+
if (!text) return;
|
|
709
|
+
const lines = (this.pending + text).split("\n");
|
|
710
|
+
this.pending = lines.pop() ?? "";
|
|
711
|
+
for (const line of lines) {
|
|
712
|
+
if (this.dropped) {
|
|
713
|
+
this.dropped = false;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
this.take(line);
|
|
717
|
+
}
|
|
718
|
+
if (this.pending.length > AGENT_PENDING_MAX_CHARS) {
|
|
719
|
+
this.pending = "";
|
|
720
|
+
this.dropped = true;
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
end() {
|
|
726
|
+
try {
|
|
727
|
+
const rest = this.pending + this.decoder.end();
|
|
728
|
+
this.pending = "";
|
|
729
|
+
if (rest && !this.dropped) this.take(rest);
|
|
730
|
+
this.dropped = false;
|
|
731
|
+
} catch {
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
take(raw) {
|
|
735
|
+
const line = raw.replace(/\r$/, "");
|
|
736
|
+
if (!line.trim()) return;
|
|
737
|
+
const trimmed = line.trim();
|
|
738
|
+
if (trimmed.startsWith("{")) {
|
|
739
|
+
try {
|
|
740
|
+
const event = JSON.parse(trimmed);
|
|
741
|
+
if (typeof event === "object" && event !== null && !Array.isArray(event)) {
|
|
742
|
+
this.emitter.emit({
|
|
743
|
+
type: "agent_event",
|
|
744
|
+
tick: this.tick,
|
|
745
|
+
event
|
|
746
|
+
});
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
this.emitter.emit({ type: "agent_text", tick: this.tick, text: line });
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
537
756
|
// src/plan-loop/run-loop.ts
|
|
538
|
-
import {
|
|
757
|
+
import { readFile as readFile4, rm, unlink } from "fs/promises";
|
|
539
758
|
import path5 from "path";
|
|
540
759
|
|
|
541
760
|
// src/plan-loop/external-review.ts
|
|
@@ -595,7 +814,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
595
814
|
const emitOut = (s) => {
|
|
596
815
|
if (!s) return;
|
|
597
816
|
output += s;
|
|
598
|
-
|
|
817
|
+
if (options.writeOut) options.writeOut(s);
|
|
818
|
+
else process.stdout.write(s);
|
|
599
819
|
};
|
|
600
820
|
const emitErr = (s) => {
|
|
601
821
|
if (!s) return;
|
|
@@ -728,7 +948,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
728
948
|
|
|
729
949
|
// src/plan-loop/sentinel.ts
|
|
730
950
|
import { readFile as readFile3 } from "fs/promises";
|
|
731
|
-
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[
|
|
951
|
+
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—-].*)?)/i;
|
|
732
952
|
function takeFromText(text) {
|
|
733
953
|
if (!text) return null;
|
|
734
954
|
const m = SENTINEL_RE.exec(text);
|
|
@@ -839,8 +1059,87 @@ async function resolveTickSentinel(lastResult, logPath, fromFile = parseSentinel
|
|
|
839
1059
|
return fromTurn ?? fromFile(logPath);
|
|
840
1060
|
}
|
|
841
1061
|
|
|
1062
|
+
// src/plan-loop/usage-limit.ts
|
|
1063
|
+
var LIMIT_TEXT_RE = /\b(?:usage|rate)[ _-]?limit|\blimit (?:reached|exceeded)|\bquota (?:exceeded|reached)|\btoo many requests\b|\bout of (?:extra )?usage\b/i;
|
|
1064
|
+
var NON_LIMIT_STATUSES = /* @__PURE__ */ new Set(["allowed", "allowed_warning"]);
|
|
1065
|
+
function isRecord(v) {
|
|
1066
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1067
|
+
}
|
|
1068
|
+
function oneLine(text, max = 200) {
|
|
1069
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
1070
|
+
return line.length > max ? `${line.slice(0, max - 1)}\u2026` : line;
|
|
1071
|
+
}
|
|
1072
|
+
function fromRateLimitEvent(event) {
|
|
1073
|
+
const info = isRecord(event.rate_limit_info) ? event.rate_limit_info : event;
|
|
1074
|
+
const status = typeof info.status === "string" ? info.status : null;
|
|
1075
|
+
if (!status || NON_LIMIT_STATUSES.has(status)) return null;
|
|
1076
|
+
const parts = [status];
|
|
1077
|
+
if (typeof info.rateLimitType === "string") parts.unshift(info.rateLimitType);
|
|
1078
|
+
if (typeof info.resetsAt === "number" && Number.isFinite(info.resetsAt)) {
|
|
1079
|
+
const ms = info.resetsAt < 1e12 ? info.resetsAt * 1e3 : info.resetsAt;
|
|
1080
|
+
parts.push(`resets ${new Date(ms).toISOString()}`);
|
|
1081
|
+
}
|
|
1082
|
+
return { source: "rate_limit_event", detail: parts.join(" ") };
|
|
1083
|
+
}
|
|
1084
|
+
function fromErrorResult(event) {
|
|
1085
|
+
if (event.is_error !== true) return null;
|
|
1086
|
+
const texts = [];
|
|
1087
|
+
if (typeof event.result === "string") texts.push(event.result);
|
|
1088
|
+
if (typeof event.subtype === "string") texts.push(event.subtype);
|
|
1089
|
+
if (Array.isArray(event.errors)) {
|
|
1090
|
+
for (const e of event.errors) if (typeof e === "string") texts.push(e);
|
|
1091
|
+
}
|
|
1092
|
+
if (isRecord(event.error) && typeof event.error.message === "string") {
|
|
1093
|
+
texts.push(event.error.message);
|
|
1094
|
+
}
|
|
1095
|
+
const hit = texts.find((t) => LIMIT_TEXT_RE.test(t));
|
|
1096
|
+
return hit ? { source: "result", detail: oneLine(hit) } : null;
|
|
1097
|
+
}
|
|
1098
|
+
function fromCodexError(event) {
|
|
1099
|
+
const message = isRecord(event.error) && typeof event.error.message === "string" ? event.error.message : typeof event.message === "string" ? event.message : "";
|
|
1100
|
+
return LIMIT_TEXT_RE.test(message) ? { source: "result", detail: oneLine(message) } : null;
|
|
1101
|
+
}
|
|
1102
|
+
var PRIORITY = {
|
|
1103
|
+
rate_limit_event: 3,
|
|
1104
|
+
result: 2,
|
|
1105
|
+
stderr: 1
|
|
1106
|
+
};
|
|
1107
|
+
function detectUsageLimit(logText) {
|
|
1108
|
+
let last = null;
|
|
1109
|
+
const take = (hit) => {
|
|
1110
|
+
if (!last || PRIORITY[hit.source] >= PRIORITY[last.source]) last = hit;
|
|
1111
|
+
};
|
|
1112
|
+
for (const raw of logText.split("\n")) {
|
|
1113
|
+
const line = raw.trim();
|
|
1114
|
+
if (!line) continue;
|
|
1115
|
+
if (!line.startsWith("{")) {
|
|
1116
|
+
if (LIMIT_TEXT_RE.test(line)) take({ source: "stderr", detail: oneLine(line) });
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
let event;
|
|
1120
|
+
try {
|
|
1121
|
+
event = JSON.parse(line);
|
|
1122
|
+
} catch {
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
if (!isRecord(event)) continue;
|
|
1126
|
+
const hit = event.type === "rate_limit_event" ? fromRateLimitEvent(event) : event.type === "result" ? fromErrorResult(event) : event.type === "turn.failed" || event.type === "error" || event.jsonrpc === "2.0" && isRecord(event.error) ? fromCodexError(event) : null;
|
|
1127
|
+
if (hit) take(hit);
|
|
1128
|
+
}
|
|
1129
|
+
return last;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
842
1132
|
// src/plan-loop/run-loop.ts
|
|
843
1133
|
var TICK_PROMPT = '/run-plan - single tick from the headless runner (agent-kit run-plan). Read .cursor/HANDOFF.md and the active plan in .cursor/plans/. Mark the next to-do as in_progress in the frontmatter, execute ONLY that to-do, mark completed and update HANDOFF. If there is a commitable diff: run /git-staging without asking for confirmation. NEVER /git-prod. Do NOT re-arm an internal Loop skill - the external runner starts the next agent. End the response with exactly one line: "LOOP_TICK_RESULT: continue" if implementable to-dos remain, or "LOOP_TICK_RESULT: stop - <reason>" (plan exhausted, external blocker, or human decision needed).';
|
|
1134
|
+
function driverSentinel(sentinel) {
|
|
1135
|
+
if (!sentinel) return null;
|
|
1136
|
+
const line = formatSentinelLine(sentinel);
|
|
1137
|
+
return {
|
|
1138
|
+
kind: sentinel.kind,
|
|
1139
|
+
...sentinel.kind === "stop" ? { reason: sentinel.reason } : {},
|
|
1140
|
+
line: line || null
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
844
1143
|
async function runTickWithSpinner(label, backend, opts, plain) {
|
|
845
1144
|
if (shouldUseLiveTui({ plain })) {
|
|
846
1145
|
return withLiveTui(
|
|
@@ -884,60 +1183,119 @@ function sleep(ms) {
|
|
|
884
1183
|
return new Promise((r) => setTimeout(r, ms));
|
|
885
1184
|
}
|
|
886
1185
|
async function runPlanLoop(opts) {
|
|
887
|
-
const
|
|
888
|
-
const
|
|
1186
|
+
const statePaths = runnerStatePaths(opts.root);
|
|
1187
|
+
const stopFile = statePaths.stopFile;
|
|
1188
|
+
const logDir = statePaths.loopLogsDir;
|
|
1189
|
+
let tick = 0;
|
|
1190
|
+
let ticksRun = 0;
|
|
1191
|
+
const driver = opts.events ? (() => {
|
|
1192
|
+
const emitter = createDriverEmitter(opts.events.write, opts.events.now);
|
|
1193
|
+
return {
|
|
1194
|
+
emitter,
|
|
1195
|
+
reader: createDriverAnswerReader({
|
|
1196
|
+
input: opts.events.input,
|
|
1197
|
+
emitter,
|
|
1198
|
+
tick: () => tick
|
|
1199
|
+
}),
|
|
1200
|
+
writeErr: opts.events.writeErr ?? ((text) => {
|
|
1201
|
+
process.stderr.write(text);
|
|
1202
|
+
})
|
|
1203
|
+
};
|
|
1204
|
+
})() : null;
|
|
1205
|
+
const say = (message, level = "info") => {
|
|
1206
|
+
if (driver) driver.emitter.emit({ type: "log", level, message });
|
|
1207
|
+
else if (level === "error") logger.error(message);
|
|
1208
|
+
else console.log(message);
|
|
1209
|
+
};
|
|
1210
|
+
const emit = (event) => driver?.emitter.emit(event);
|
|
1211
|
+
const finishRun = (exitCode, reason, pending2, planExhausted = false) => {
|
|
1212
|
+
emit({ type: "run_end", exitCode, ticks: ticksRun, pending: pending2, reason, planExhausted });
|
|
1213
|
+
driver?.reader.close();
|
|
1214
|
+
return exitCode;
|
|
1215
|
+
};
|
|
889
1216
|
const planPath = await resolveActivePlanPath(opts.root);
|
|
890
1217
|
if (!planPath) {
|
|
891
|
-
|
|
892
|
-
return 1;
|
|
1218
|
+
say("No active plan in .cursor/plans/", "error");
|
|
1219
|
+
return finishRun(1, "no active plan", null);
|
|
893
1220
|
}
|
|
894
1221
|
const pending = async () => countPendingTodos(await readPlan(planPath));
|
|
895
|
-
|
|
1222
|
+
if (statePaths.invalid !== void 0) {
|
|
1223
|
+
say(
|
|
1224
|
+
`${STATE_ROOT_ENV}=${statePaths.invalid} is not supported (use .cursor or .agent-kit); using .cursor.`,
|
|
1225
|
+
"warn"
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
await ensureLoopLogsDir(statePaths);
|
|
896
1229
|
try {
|
|
897
1230
|
await unlink(stopFile);
|
|
898
1231
|
} catch {
|
|
899
1232
|
}
|
|
900
1233
|
const onSigInt = () => {
|
|
901
|
-
|
|
902
|
-
|
|
1234
|
+
if (driver) {
|
|
1235
|
+
emit({
|
|
1236
|
+
type: "run_end",
|
|
1237
|
+
exitCode: 130,
|
|
1238
|
+
ticks: ticksRun,
|
|
1239
|
+
pending: null,
|
|
1240
|
+
reason: "interrupted (SIGINT)",
|
|
1241
|
+
planExhausted: false
|
|
1242
|
+
});
|
|
1243
|
+
} else {
|
|
1244
|
+
console.log("");
|
|
1245
|
+
console.log("Loop interrupted (Ctrl+C). State saved in HANDOFF by the last tick.");
|
|
1246
|
+
}
|
|
903
1247
|
process.exit(130);
|
|
904
1248
|
};
|
|
905
1249
|
process.on("SIGINT", onSigInt);
|
|
906
1250
|
try {
|
|
907
|
-
const persona = await loadCliRunPlanPersona(opts.root);
|
|
1251
|
+
const persona = driver ? null : await loadCliRunPlanPersona(opts.root);
|
|
908
1252
|
const banners = createPersonaBannerPrinter(persona);
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1253
|
+
const pendingAtStart = await pending();
|
|
1254
|
+
emit({
|
|
1255
|
+
type: "run_start",
|
|
1256
|
+
protocol: DRIVER_PROTOCOL_VERSION,
|
|
1257
|
+
backend: opts.backend.id,
|
|
1258
|
+
plan: path5.basename(planPath),
|
|
1259
|
+
pending: pendingAtStart,
|
|
1260
|
+
maxTicks: opts.maxTicks,
|
|
1261
|
+
hitl: opts.noHitl ? "off" : "driver"
|
|
1262
|
+
});
|
|
1263
|
+
say(`Active plan: ${path5.basename(planPath)}`);
|
|
1264
|
+
say(`Pending to-dos: ${pendingAtStart} | max ticks: ${opts.maxTicks}`);
|
|
1265
|
+
say(`Backend: ${opts.backend.id}`);
|
|
912
1266
|
if (persona) {
|
|
913
|
-
|
|
1267
|
+
say(`CLI persona: ${persona.displayName ?? persona.id}`);
|
|
914
1268
|
}
|
|
915
1269
|
if (opts.dryRun) {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
return 0;
|
|
1270
|
+
say("--dry-run: no agent will be started. Tick prompt:");
|
|
1271
|
+
say(TICK_PROMPT);
|
|
1272
|
+
return finishRun(0, "dry-run", pendingAtStart);
|
|
919
1273
|
}
|
|
920
1274
|
const binary = await opts.backend.resolve();
|
|
921
1275
|
if (!binary) {
|
|
922
|
-
|
|
923
|
-
return 1;
|
|
1276
|
+
say(`${opts.backend.id} not found on PATH`, "error");
|
|
1277
|
+
return finishRun(1, `${opts.backend.id} not found on PATH`, pendingAtStart);
|
|
924
1278
|
}
|
|
925
|
-
let tick = 0;
|
|
926
1279
|
let stopReason;
|
|
927
1280
|
let planExhausted = false;
|
|
928
1281
|
let exitCode = 0;
|
|
1282
|
+
let endReason = "";
|
|
1283
|
+
let backend = opts.backend;
|
|
1284
|
+
let limitSwitched = false;
|
|
929
1285
|
while (true) {
|
|
930
1286
|
tick += 1;
|
|
931
1287
|
if (tick > opts.maxTicks) {
|
|
932
1288
|
const msg = `Budget of ${opts.maxTicks} ticks reached - stopping. Run again to continue.`;
|
|
933
1289
|
if (banners) banners.stop(msg);
|
|
934
|
-
else
|
|
1290
|
+
else say(msg);
|
|
1291
|
+
endReason = `budget of ${opts.maxTicks} ticks reached`;
|
|
935
1292
|
break;
|
|
936
1293
|
}
|
|
937
1294
|
if (await fileExists(stopFile)) {
|
|
938
1295
|
const msg = `Stop file found (${stopFile}) - stopping.`;
|
|
939
1296
|
if (banners) banners.stop(msg);
|
|
940
|
-
else
|
|
1297
|
+
else say(msg);
|
|
1298
|
+
endReason = "stop file found";
|
|
941
1299
|
try {
|
|
942
1300
|
await rm(stopFile, { force: true });
|
|
943
1301
|
} catch {
|
|
@@ -946,81 +1304,159 @@ async function runPlanLoop(opts) {
|
|
|
946
1304
|
}
|
|
947
1305
|
const before = await pending();
|
|
948
1306
|
if (before === 0) {
|
|
949
|
-
|
|
1307
|
+
say("Plan exhausted (0 pending to-dos) - nothing to do.");
|
|
950
1308
|
planExhausted = true;
|
|
1309
|
+
endReason = "plan exhausted";
|
|
951
1310
|
break;
|
|
952
1311
|
}
|
|
953
1312
|
const logPath = path5.join(logDir, `tick-${stamp()}.log`);
|
|
954
1313
|
const relLog = path5.relative(opts.root, logPath);
|
|
955
|
-
console.log("");
|
|
1314
|
+
if (!driver) console.log("");
|
|
956
1315
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
957
1316
|
if (banners) banners.tickStart(tickLine);
|
|
958
|
-
else
|
|
1317
|
+
else say(tickLine);
|
|
1318
|
+
ticksRun = tick;
|
|
1319
|
+
emit({
|
|
1320
|
+
type: "tick_start",
|
|
1321
|
+
tick,
|
|
1322
|
+
maxTicks: opts.maxTicks,
|
|
1323
|
+
pending: before,
|
|
1324
|
+
log: relLog,
|
|
1325
|
+
prompt: TICK_PROMPT,
|
|
1326
|
+
backend: backend.id
|
|
1327
|
+
});
|
|
959
1328
|
let agentExit = 0;
|
|
960
1329
|
let hitlStop;
|
|
961
1330
|
let lastResult;
|
|
1331
|
+
let replies = [];
|
|
1332
|
+
let limit = null;
|
|
1333
|
+
const endTick = (pendingAfter, sentinel2) => emit({
|
|
1334
|
+
type: "tick_end",
|
|
1335
|
+
tick,
|
|
1336
|
+
backend: backend.id,
|
|
1337
|
+
limit,
|
|
1338
|
+
exitCode: agentExit,
|
|
1339
|
+
pendingBefore: before,
|
|
1340
|
+
pendingAfter,
|
|
1341
|
+
result: driverSentinel(sentinel2),
|
|
1342
|
+
replies: replies.map(({ askId, reply, label, line, at }) => ({
|
|
1343
|
+
askId,
|
|
1344
|
+
reply,
|
|
1345
|
+
label,
|
|
1346
|
+
line,
|
|
1347
|
+
at
|
|
1348
|
+
})),
|
|
1349
|
+
stop: hitlStop ? {
|
|
1350
|
+
askId: hitlStop.askId,
|
|
1351
|
+
cause: hitlStop.cause,
|
|
1352
|
+
message: hitlStop.message,
|
|
1353
|
+
exitCode: hitlStop.exitCode
|
|
1354
|
+
} : null
|
|
1355
|
+
});
|
|
962
1356
|
try {
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1357
|
+
const runOpts = {
|
|
1358
|
+
workspace: opts.root,
|
|
1359
|
+
prompt: TICK_PROMPT,
|
|
1360
|
+
model: opts.model,
|
|
1361
|
+
logPath,
|
|
1362
|
+
hitl: { policy: opts.noHitl ? "off" : "prompt" }
|
|
1363
|
+
};
|
|
1364
|
+
const result = driver ? await backend.run({
|
|
1365
|
+
...runOpts,
|
|
1366
|
+
log: (line) => say(line),
|
|
1367
|
+
render: new DriverAgentSink(driver.emitter, tick),
|
|
1368
|
+
hitl: {
|
|
1369
|
+
policy: opts.noHitl ? "off" : "driver",
|
|
1370
|
+
readAnswer: driver.reader.read,
|
|
1371
|
+
write: driver.writeErr
|
|
1372
|
+
}
|
|
1373
|
+
}) : await runTickWithSpinner(`tick ${tick}`, backend, runOpts, opts.plain === true);
|
|
975
1374
|
agentExit = result.exitCode;
|
|
976
|
-
for (const line of formatHitlSummary(result.hitl))
|
|
1375
|
+
for (const line of formatHitlSummary(result.hitl)) say(line);
|
|
977
1376
|
hitlStop = result.hitl?.stop;
|
|
1377
|
+
replies = result.hitl?.replies ?? [];
|
|
978
1378
|
lastResult = result.lastResult;
|
|
979
1379
|
} catch (err) {
|
|
980
|
-
|
|
981
|
-
|
|
1380
|
+
say(String(err), "error");
|
|
1381
|
+
agentExit = 1;
|
|
1382
|
+
endTick(null, null);
|
|
1383
|
+
return finishRun(1, `tick ${tick} failed to start: ${String(err)}`, await pending());
|
|
982
1384
|
}
|
|
983
1385
|
if (hitlStop) {
|
|
984
|
-
const msg = `${
|
|
1386
|
+
const msg = `${backend.id} tick ended at a HITL gate without a reply - stopping. See log: ${relLog}`;
|
|
985
1387
|
if (banners) {
|
|
986
1388
|
banners.tickEnd("hitl stop");
|
|
987
1389
|
banners.stop(msg);
|
|
988
1390
|
} else {
|
|
989
|
-
|
|
1391
|
+
say(msg);
|
|
990
1392
|
}
|
|
1393
|
+
endTick(await pending(), null);
|
|
991
1394
|
exitCode = hitlStop.exitCode;
|
|
1395
|
+
endReason = hitlStop.message;
|
|
992
1396
|
break;
|
|
993
1397
|
}
|
|
1398
|
+
let logText = "";
|
|
994
1399
|
try {
|
|
995
|
-
|
|
996
|
-
if (logText.includes("Too many MCP tools")) {
|
|
997
|
-
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
998
|
-
if (banners) banners.stop(msg);
|
|
999
|
-
else console.log(msg);
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1400
|
+
logText = await readFile4(logPath, "utf8");
|
|
1002
1401
|
} catch {
|
|
1003
1402
|
}
|
|
1403
|
+
limit = detectUsageLimit(logText);
|
|
1404
|
+
if (limit) {
|
|
1405
|
+
const hit = limit;
|
|
1406
|
+
const fallback = opts.onLimit && !limitSwitched && opts.onLimit.id !== backend.id ? opts.onLimit : null;
|
|
1407
|
+
const fallbackBin = fallback ? await fallback.resolve() : null;
|
|
1408
|
+
endTick(await pending(), null);
|
|
1409
|
+
if (fallback && fallbackBin) {
|
|
1410
|
+
const msg2 = `${backend.id} stopped on a usage limit (${hit.detail}); next tick continues the plan on ${fallback.id} (--on-limit). See log: ${relLog}`;
|
|
1411
|
+
if (banners) banners.tickEnd("usage limit");
|
|
1412
|
+
say(msg2, "warn");
|
|
1413
|
+
backend = fallback;
|
|
1414
|
+
limitSwitched = true;
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
const why = fallback ? ` (--on-limit ${fallback.id} not found on PATH)` : "";
|
|
1418
|
+
const msg = `${backend.id} stopped on a usage limit (${hit.detail})${why} - stopping. The plan is unchanged: run again after the reset, or with --backend <other>. See log: ${relLog}`;
|
|
1419
|
+
if (banners) {
|
|
1420
|
+
banners.tickEnd("usage limit");
|
|
1421
|
+
banners.stop(msg);
|
|
1422
|
+
} else {
|
|
1423
|
+
say(msg);
|
|
1424
|
+
}
|
|
1425
|
+
endReason = `usage-limit: ${backend.id}`;
|
|
1426
|
+
break;
|
|
1427
|
+
}
|
|
1428
|
+
if (logText.includes("Too many MCP tools")) {
|
|
1429
|
+
const msg = "Too many MCP tools for the headless model - disable servers (cursor-agent mcp disable <id>) and run again.";
|
|
1430
|
+
if (banners) banners.stop(msg);
|
|
1431
|
+
else say(msg);
|
|
1432
|
+
endTick(await pending(), null);
|
|
1433
|
+
endReason = "too many MCP tools";
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1004
1436
|
const status = await resolveTickResultStatus(lastResult, logPath);
|
|
1005
1437
|
if (status?.isError) {
|
|
1006
1438
|
const detail = status.errors[0] ? `: ${status.errors[0].split("\n")[0]}` : "";
|
|
1007
|
-
const msg = `${
|
|
1439
|
+
const msg = `${backend.id} tick failed (${status.subtype ?? "is_error"}${detail}) - stopping. See log: ${relLog}`;
|
|
1008
1440
|
if (banners) {
|
|
1009
1441
|
banners.tickEnd(status.subtype ?? "is_error");
|
|
1010
1442
|
banners.stop(msg);
|
|
1011
1443
|
} else {
|
|
1012
|
-
|
|
1444
|
+
say(msg);
|
|
1013
1445
|
}
|
|
1446
|
+
endTick(await pending(), null);
|
|
1447
|
+
endReason = `tick failed (${status.subtype ?? "is_error"})`;
|
|
1014
1448
|
break;
|
|
1015
1449
|
}
|
|
1016
1450
|
if (agentExit !== 0) {
|
|
1017
|
-
const msg = `${
|
|
1451
|
+
const msg = `${backend.id} exited with code ${agentExit} - stopping. See log: ${relLog}`;
|
|
1018
1452
|
if (banners) {
|
|
1019
1453
|
banners.tickEnd(`exit ${agentExit}`);
|
|
1020
1454
|
banners.stop(msg);
|
|
1021
1455
|
} else {
|
|
1022
|
-
|
|
1456
|
+
say(msg);
|
|
1023
1457
|
}
|
|
1458
|
+
endTick(await pending(), null);
|
|
1459
|
+
endReason = `${backend.id} exited with code ${agentExit}`;
|
|
1024
1460
|
break;
|
|
1025
1461
|
}
|
|
1026
1462
|
const sentinel = await resolveTickSentinel(lastResult, logPath);
|
|
@@ -1028,45 +1464,58 @@ async function runPlanLoop(opts) {
|
|
|
1028
1464
|
if (banners) {
|
|
1029
1465
|
banners.tickEnd(`pending: ${after}`);
|
|
1030
1466
|
}
|
|
1467
|
+
endTick(after, sentinel);
|
|
1031
1468
|
if (sentinel.kind === "continue") {
|
|
1032
1469
|
} else if (sentinel.kind === "stop") {
|
|
1033
|
-
|
|
1470
|
+
say(`Agent requested stop: ${formatSentinelLine(sentinel)}`);
|
|
1034
1471
|
stopReason = sentinel.reason;
|
|
1472
|
+
endReason = `agent requested stop: ${sentinel.reason}`;
|
|
1035
1473
|
if (after === 0 || isPlanExhaustedReason(sentinel.reason)) {
|
|
1036
1474
|
planExhausted = true;
|
|
1037
1475
|
}
|
|
1038
1476
|
break;
|
|
1039
1477
|
} else if (after < before) {
|
|
1040
|
-
|
|
1041
|
-
`warn: tick without sentinel, but progress (${before} -> ${after}) - continuing
|
|
1478
|
+
say(
|
|
1479
|
+
`warn: tick without sentinel, but progress (${before} -> ${after}) - continuing.`,
|
|
1480
|
+
"warn"
|
|
1042
1481
|
);
|
|
1043
1482
|
} else {
|
|
1044
1483
|
const msg = `Tick without sentinel and no progress (${before} -> ${after}) - stopping for safety.`;
|
|
1045
1484
|
if (banners) banners.stop(msg);
|
|
1046
|
-
else
|
|
1485
|
+
else say(msg);
|
|
1486
|
+
endReason = "tick without sentinel and no progress";
|
|
1047
1487
|
break;
|
|
1048
1488
|
}
|
|
1049
1489
|
if (after === 0) {
|
|
1050
|
-
|
|
1490
|
+
say("All to-dos completed. Suggesting /git-prod stays with the human (HITL).");
|
|
1051
1491
|
planExhausted = true;
|
|
1492
|
+
endReason = "plan exhausted";
|
|
1052
1493
|
break;
|
|
1053
1494
|
}
|
|
1054
1495
|
if (opts.sleepSeconds > 0) {
|
|
1055
1496
|
if (banners) banners.sleep(opts.sleepSeconds);
|
|
1056
|
-
|
|
1497
|
+
if (driver) await sleep(opts.sleepSeconds * 1e3);
|
|
1498
|
+
else
|
|
1499
|
+
await withCliProgress(
|
|
1500
|
+
`sleep ${opts.sleepSeconds}s`,
|
|
1501
|
+
() => sleep(opts.sleepSeconds * 1e3)
|
|
1502
|
+
);
|
|
1057
1503
|
}
|
|
1058
1504
|
}
|
|
1059
1505
|
const pendingNow = await pending();
|
|
1060
|
-
console.log("");
|
|
1506
|
+
if (!driver) console.log("");
|
|
1061
1507
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
1062
1508
|
if (banners) banners.phaseComplete(finishDetail);
|
|
1063
|
-
|
|
1509
|
+
say(
|
|
1064
1510
|
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path5.relative(opts.root, logDir)}/`
|
|
1065
1511
|
);
|
|
1066
1512
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
1067
|
-
await armExternalPlanReview(
|
|
1513
|
+
await armExternalPlanReview(
|
|
1514
|
+
opts.root,
|
|
1515
|
+
driver ? { log: (line) => say(line), writeOut: driver.writeErr } : {}
|
|
1516
|
+
);
|
|
1068
1517
|
}
|
|
1069
|
-
return exitCode;
|
|
1518
|
+
return finishRun(exitCode, endReason || "stopped", pendingNow, planExhausted);
|
|
1070
1519
|
} finally {
|
|
1071
1520
|
process.off("SIGINT", onSigInt);
|
|
1072
1521
|
}
|
|
@@ -1077,5 +1526,6 @@ export {
|
|
|
1077
1526
|
withLiveTui,
|
|
1078
1527
|
listDetectBackendIds,
|
|
1079
1528
|
detectAgentBackend,
|
|
1529
|
+
DRIVER_EVENTS_FORMATS,
|
|
1080
1530
|
runPlanLoop
|
|
1081
1531
|
};
|