@dadado/agent-kit-cli 6.1.0 → 6.2.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 +3 -1
- package/dashboard/dashboard-data.mjs +13 -2
- package/dashboard/dashboard.html +95 -2
- package/dashboard/lib/gate-exit-tickets.d.mts +45 -0
- package/dashboard/lib/gate-exit-tickets.mjs +233 -0
- package/dashboard/lib/semantic-model.d.mts +21 -0
- package/dashboard/lib/semantic-model.mjs +47 -0
- package/dist/{add-YTCDLPUS.js → add-Y2426TGU.js} +3 -3
- package/dist/{chunk-PCCQUWE5.js → chunk-3LLXDHIU.js} +1 -1
- package/dist/{chunk-YP4OOK4T.js → chunk-BEKQ6PVD.js} +2 -2
- package/dist/{chunk-3DGTPONS.js → chunk-C2SJDYAM.js} +1 -1
- package/dist/{chunk-C26I6HTO.js → chunk-C4Q77GGJ.js} +0 -1
- package/dist/{chunk-DRND2KYV.js → chunk-CER2APV2.js} +1 -1
- package/dist/{chunk-BBVBYQJO.js → chunk-CWDXNBEU.js} +2 -2
- package/dist/{chunk-U5F6RJN4.js → chunk-GO3K752H.js} +4 -1
- package/dist/chunk-GUQYNUV4.js +61 -0
- package/dist/{chunk-TMFOLRU2.js → chunk-IR7FFPUU.js} +8 -8
- package/dist/{chunk-ESNAFDYT.js → chunk-JEI6HACW.js} +44 -1
- package/dist/chunk-KVY425NM.js +519 -0
- package/dist/{chunk-WUWRGOUV.js → chunk-L5V3W3DY.js} +13 -1
- package/dist/chunk-MO63KZBP.js +516 -0
- package/dist/chunk-MUTTNRX4.js +53 -0
- package/dist/{chunk-DBPNGZNH.js → chunk-NSSXSZLL.js} +13 -2
- package/dist/chunk-QTBTKEXK.js +77 -0
- package/dist/{chunk-PPKX55GI.js → chunk-SPYIP76F.js} +1 -1
- package/dist/{chunk-DBXDV5O7.js → chunk-TGNFGMPV.js} +2 -2
- package/dist/{chunk-2FQX2O4I.js → chunk-TW4BF5OE.js} +2 -2
- package/dist/{chunk-GN4SAZF2.js → chunk-VO73OL5V.js} +2 -2
- package/dist/{chunk-VTOUQAVR.js → chunk-X45O2AIE.js} +9 -1
- package/dist/{chunk-HFUH3HVS.js → chunk-XDCCYBCJ.js} +7 -1
- package/dist/{chunk-TPCCSWK6.js → chunk-YRFSTHR5.js} +30 -526
- package/dist/{chunk-3XKVQS2K.js → chunk-Z7ABX5QI.js} +13 -9
- package/dist/{contribute-A7NCWTUM.js → contribute-TVST3CJO.js} +4 -4
- package/dist/countdown-QXJD6TDB.js +479 -0
- package/dist/{dashboard-YSNUGBFY.js → dashboard-YNCFISNO.js} +1 -1
- package/dist/{dashboard-broadcast-M4CMW7RX.js → dashboard-broadcast-NK7PU72L.js} +3 -2
- package/dist/{diff-4MDU5SLE.js → diff-MECE5RZB.js} +5 -5
- package/dist/{doctor-SS6Y2T6H.js → doctor-VLZNXJNA.js} +9 -9
- package/dist/ground-control-NMYYIVCV.js +434 -0
- package/dist/{handoff-MK5JG3J5.js → handoff-5AGE2D3T.js} +3 -2
- package/dist/{hook-D2WHBKRN.js → hook-XCZWX462.js} +7 -6
- package/dist/index.js +33 -23
- package/dist/{init-GVVBA2BM.js → init-HKMRU2V4.js} +12 -12
- package/dist/{install-R4HPI7VK.js → install-X2J6KWZ5.js} +12 -12
- package/dist/{mission-control-LJACEVVN.js → mission-control-MHQU73XC.js} +2 -2
- package/dist/{plan-index-V7T5MKL6.js → plan-index-BNLUU6T5.js} +3 -2
- package/dist/{run-YV6XTFZY.js → run-R66G276Q.js} +63 -15
- package/dist/{run-plan-KT443FO6.js → run-plan-24Z5K4P3.js} +17 -10
- package/dist/{scan-K6TQGV4S.js → scan-UX4HARMJ.js} +1 -1
- package/dist/{setup-global-AT4DD3MG.js → setup-global-ADJFMOZX.js} +6 -6
- package/dist/{status-MPJHI5RP.js → status-UB6NL3Q3.js} +8 -8
- package/dist/{update-P62637ZI.js → update-PFBFY7GT.js} +9 -9
- package/package.json +1 -1
|
@@ -1,30 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyGateExit,
|
|
3
|
+
landGateExit
|
|
4
|
+
} from "./chunk-KVY425NM.js";
|
|
1
5
|
import {
|
|
2
6
|
InputLine,
|
|
3
7
|
ScrollRegion,
|
|
4
8
|
renderMcLive,
|
|
5
9
|
runMcTuiLoop
|
|
6
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3LLXDHIU.js";
|
|
7
11
|
import {
|
|
8
12
|
extractHandoffNamedPlans,
|
|
9
13
|
namedPlanCandidatePaths,
|
|
10
14
|
parsePlanFrontmatter
|
|
11
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-L5V3W3DY.js";
|
|
12
16
|
import {
|
|
13
17
|
LineSplitter,
|
|
14
18
|
RedactingStreamBuffer,
|
|
15
19
|
claudeRedactions,
|
|
16
|
-
findHitlGateInText,
|
|
17
20
|
formatHitlGateLine,
|
|
18
21
|
formatHitlSummary,
|
|
19
22
|
operatorQuitStop,
|
|
20
23
|
parseStreamLine,
|
|
21
24
|
redactSecrets,
|
|
22
|
-
renderStreamEvent
|
|
23
|
-
|
|
24
|
-
} from "./chunk-ESNAFDYT.js";
|
|
25
|
-
import {
|
|
26
|
-
findOnPath
|
|
27
|
-
} from "./chunk-WGLPZXNU.js";
|
|
25
|
+
renderStreamEvent
|
|
26
|
+
} from "./chunk-JEI6HACW.js";
|
|
28
27
|
import {
|
|
29
28
|
TtySpinner,
|
|
30
29
|
shouldUseVisualMotion,
|
|
@@ -39,7 +38,7 @@ import {
|
|
|
39
38
|
STATE_ROOT_ENV,
|
|
40
39
|
ensureLoopLogsDir,
|
|
41
40
|
runnerStatePaths
|
|
42
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-C4Q77GGJ.js";
|
|
43
42
|
import {
|
|
44
43
|
fileExists,
|
|
45
44
|
readJson
|
|
@@ -492,496 +491,6 @@ async function withLiveTui(opts, run) {
|
|
|
492
491
|
}
|
|
493
492
|
}
|
|
494
493
|
|
|
495
|
-
// src/plan-loop/detect.ts
|
|
496
|
-
var DETECT_ORDER = ["cursor-agent", "claude"];
|
|
497
|
-
var EXPLICIT_ONLY_BACKENDS = ["codex", "cursor-acp"];
|
|
498
|
-
var INSTALL_HINTS = {
|
|
499
|
-
"cursor-agent": "Install the Cursor Agent CLI and ensure `cursor-agent` is on PATH (https://cursor.com/docs/cli).",
|
|
500
|
-
claude: "Install Claude Code and ensure `claude` is on PATH (https://code.claude.com/docs/en/quickstart).",
|
|
501
|
-
"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`.",
|
|
502
|
-
codex: "Install the Codex CLI and ensure `codex` is on PATH (https://developers.openai.com/codex/cli)."
|
|
503
|
-
};
|
|
504
|
-
function isBackendId(id) {
|
|
505
|
-
return id === "cursor-agent" || id === "claude" || id === "codex" || id === "cursor-acp";
|
|
506
|
-
}
|
|
507
|
-
function isRequestedBackend(id) {
|
|
508
|
-
return id === "auto" || isBackendId(id);
|
|
509
|
-
}
|
|
510
|
-
function listDetectBackendIds() {
|
|
511
|
-
return ["auto", ...DETECT_ORDER, ...EXPLICIT_ONLY_BACKENDS];
|
|
512
|
-
}
|
|
513
|
-
async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
514
|
-
const id = requested.trim().toLowerCase();
|
|
515
|
-
if (id === "glm" || id.startsWith("glm-") || id === "glm4") {
|
|
516
|
-
return {
|
|
517
|
-
ok: false,
|
|
518
|
-
message: "No documented GLM CLI binary is registered. Refusing a stub that would claim to work. Use --backend auto, cursor-agent, or claude."
|
|
519
|
-
};
|
|
520
|
-
}
|
|
521
|
-
if (!isRequestedBackend(id)) {
|
|
522
|
-
return {
|
|
523
|
-
ok: false,
|
|
524
|
-
message: `Unknown backend '${requested}'. Supported: ${listDetectBackendIds().join(", ")}.`
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
if (id === "auto") {
|
|
528
|
-
for (const candidate of DETECT_ORDER) {
|
|
529
|
-
const bin2 = await whichFn(candidate);
|
|
530
|
-
if (bin2) return { ok: true, id: candidate, bin: bin2 };
|
|
531
|
-
}
|
|
532
|
-
return {
|
|
533
|
-
ok: false,
|
|
534
|
-
message: [
|
|
535
|
-
"No agent CLI found on PATH (tried: cursor-agent, claude).",
|
|
536
|
-
INSTALL_HINTS["cursor-agent"],
|
|
537
|
-
INSTALL_HINTS.claude
|
|
538
|
-
].join(" ")
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
const bin = await whichFn(id);
|
|
542
|
-
if (!bin) {
|
|
543
|
-
return { ok: false, message: `${id} not found on PATH. ${INSTALL_HINTS[id]}` };
|
|
544
|
-
}
|
|
545
|
-
return { ok: true, id, bin };
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// src/plan-loop/gate-exit.ts
|
|
549
|
-
import { execFile } from "child_process";
|
|
550
|
-
import { mkdir, mkdtemp, readFile as readFile3, rename, rm, writeFile } from "fs/promises";
|
|
551
|
-
import { tmpdir } from "os";
|
|
552
|
-
import path3 from "path";
|
|
553
|
-
var GATE_EXIT_BRANCH_PREFIX = "gate-exit/";
|
|
554
|
-
var GATE_EXIT_MARKER_RELPATH = ".cursor/context/gate-exit.json";
|
|
555
|
-
var GATE_EXIT_NOTE_DIR = ".cursor/gate-exit";
|
|
556
|
-
var GATE_EXIT_DEFAULT_BASE = "staging";
|
|
557
|
-
var GATE_EXIT_MARKER_SCHEMA = 1;
|
|
558
|
-
var GATE_EXIT_STAMP_RE = /^\d{8}T\d{6}Z$/;
|
|
559
|
-
var GATE_EXIT_SUMMARY_MAX_CHARS = 8 * 1024;
|
|
560
|
-
var SAFE_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
561
|
-
var FALLBACK_GIT_IDENTITY = [
|
|
562
|
-
"-c",
|
|
563
|
-
"user.name=agent-kit",
|
|
564
|
-
"-c",
|
|
565
|
-
"user.email=agent-kit@noreply.invalid"
|
|
566
|
-
];
|
|
567
|
-
var LAND_ON_GATE_DESCRIPTION = "Gate exit: when the run stops at an unanswered gate, is stopped by the operator, or fails, push the outcome note to a gate-exit/<UTC stamp> branch from a temporary worktree and open a PR/MR when gh/glab is authenticated (root tree untouched; off by default)";
|
|
568
|
-
function gateExitStamp(now = /* @__PURE__ */ new Date()) {
|
|
569
|
-
return now.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[-:]/g, "");
|
|
570
|
-
}
|
|
571
|
-
function isGateExitStamp(value) {
|
|
572
|
-
return GATE_EXIT_STAMP_RE.test(value);
|
|
573
|
-
}
|
|
574
|
-
function gateExitBranch(stamp2) {
|
|
575
|
-
if (!isGateExitStamp(stamp2)) throw new Error(`invalid gate-exit stamp: ${stamp2}`);
|
|
576
|
-
return `${GATE_EXIT_BRANCH_PREFIX}${stamp2}`;
|
|
577
|
-
}
|
|
578
|
-
function isSafeBaseRef(ref) {
|
|
579
|
-
return SAFE_REF_RE.test(ref) && !ref.includes("..") && !ref.endsWith("/") && !ref.endsWith(".lock");
|
|
580
|
-
}
|
|
581
|
-
function classifyGateExit(input) {
|
|
582
|
-
if (input.stop) {
|
|
583
|
-
return input.stop.message.startsWith("stopped-by-operator") ? "stopped-by-operator" : "gate";
|
|
584
|
-
}
|
|
585
|
-
return input.failed ? "failure" : null;
|
|
586
|
-
}
|
|
587
|
-
function gateExitMarkerPath(root) {
|
|
588
|
-
return path3.join(root, GATE_EXIT_MARKER_RELPATH);
|
|
589
|
-
}
|
|
590
|
-
async function readGateExitMarker(root) {
|
|
591
|
-
let raw;
|
|
592
|
-
try {
|
|
593
|
-
raw = await readFile3(gateExitMarkerPath(root), "utf8");
|
|
594
|
-
} catch (err) {
|
|
595
|
-
if (err.code === "ENOENT") return { kind: "absent" };
|
|
596
|
-
return { kind: "invalid", detail: String(err) };
|
|
597
|
-
}
|
|
598
|
-
try {
|
|
599
|
-
const parsed = JSON.parse(raw);
|
|
600
|
-
if (parsed?.schema !== GATE_EXIT_MARKER_SCHEMA || !Array.isArray(parsed.pending) || !Array.isArray(parsed.skipped)) {
|
|
601
|
-
return { kind: "invalid", detail: "unexpected marker shape" };
|
|
602
|
-
}
|
|
603
|
-
const pending = parsed.pending.filter(
|
|
604
|
-
(e) => typeof e?.stamp === "string" && isGateExitStamp(e.stamp)
|
|
605
|
-
);
|
|
606
|
-
return {
|
|
607
|
-
kind: "ok",
|
|
608
|
-
marker: { schema: GATE_EXIT_MARKER_SCHEMA, pending, skipped: parsed.skipped.map(String) }
|
|
609
|
-
};
|
|
610
|
-
} catch (err) {
|
|
611
|
-
return { kind: "invalid", detail: String(err) };
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
async function writeGateExitMarker(root, marker) {
|
|
615
|
-
const file = gateExitMarkerPath(root);
|
|
616
|
-
await mkdir(path3.dirname(file), { recursive: true });
|
|
617
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
618
|
-
await writeFile(tmp, `${JSON.stringify(marker, null, 2)}
|
|
619
|
-
`, "utf8");
|
|
620
|
-
await rename(tmp, file);
|
|
621
|
-
}
|
|
622
|
-
function gateExitRedactions(env) {
|
|
623
|
-
const entries = claudeRedactions(env);
|
|
624
|
-
const seen = new Set(entries.map((e) => e.value));
|
|
625
|
-
for (const key of ["GH_TOKEN", "GITHUB_TOKEN", "GITLAB_TOKEN", "GLAB_TOKEN"]) {
|
|
626
|
-
const value = env[key];
|
|
627
|
-
if (value && !seen.has(value)) {
|
|
628
|
-
seen.add(value);
|
|
629
|
-
entries.push({ label: key, value });
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
return entries;
|
|
633
|
-
}
|
|
634
|
-
function gateExitHeadline(stamp2, outcome) {
|
|
635
|
-
const what = outcome.trigger === "failure" ? "run failed" : outcome.trigger === "stopped-by-operator" ? `stopped by operator at gate \`${outcome.askId ?? "none"}\`` : `unanswered gate \`${outcome.askId ?? "none"}\``;
|
|
636
|
-
return `# Gate exit ${stamp2}: ${what}`;
|
|
637
|
-
}
|
|
638
|
-
function buildGateExitNote(input) {
|
|
639
|
-
const { outcome } = input;
|
|
640
|
-
const gateLine = outcome.askId && input.labels.length > 0 ? `\`HITL_GATE: ${outcome.askId} | ${input.labels.join(" | ")}\`` : "(no `HITL_GATE:` line recorded)";
|
|
641
|
-
const plan = outcome.plan !== void 0 ? `${outcome.plan} (ticks: ${outcome.ticks ?? 0}, pending to-dos: ${outcome.pending ?? "unknown"})` : "(none)";
|
|
642
|
-
const lines = [
|
|
643
|
-
gateExitHeadline(input.stamp, outcome),
|
|
644
|
-
"",
|
|
645
|
-
"- **Provenance:** agent-inferred. Written by `agent-kit` at the end of a headless run; no operator reply exists for this gate and none was inferred.",
|
|
646
|
-
`- **Trigger:** ${outcome.trigger}`,
|
|
647
|
-
`- **Ask id:** ${outcome.askId ? `\`${outcome.askId}\`` : "(none)"}`,
|
|
648
|
-
`- **Gate:** ${gateLine}`,
|
|
649
|
-
`- **Stop:** ${outcome.reason}`,
|
|
650
|
-
`- **Exit code:** ${outcome.exitCode}`,
|
|
651
|
-
`- **Command:** \`agent-kit ${outcome.command}\` (backend ${outcome.backend})`,
|
|
652
|
-
`- **Plan:** ${plan}`,
|
|
653
|
-
`- **Log:** ${input.logRel ? `\`${input.logRel}\` (on the machine that ran; not pushed)` : "(none)"}`,
|
|
654
|
-
`- **Root tree:** ${input.dirtyPaths > 0 ? `dirty (${input.dirtyPaths} path(s)), reported and not cleaned` : "clean"}`,
|
|
655
|
-
`- **Base:** \`${input.base}\``,
|
|
656
|
-
"",
|
|
657
|
-
"Next: answer the gate in a live session (`/continue-plan` or `/run-plan`), then accept or discard this ticket (`agent-kit ground-control`). Never `/git-prod` from a headless run."
|
|
658
|
-
];
|
|
659
|
-
const summary = outcome.summary?.trim();
|
|
660
|
-
if (summary) {
|
|
661
|
-
const capped = summary.length > GATE_EXIT_SUMMARY_MAX_CHARS ? `${summary.slice(0, GATE_EXIT_SUMMARY_MAX_CHARS)}
|
|
662
|
-
|
|
663
|
-
(summary truncated at ${GATE_EXIT_SUMMARY_MAX_CHARS} characters)` : summary;
|
|
664
|
-
lines.push("", "## Run summary", "", "```text", capped.replaceAll("```", "'''"), "```");
|
|
665
|
-
}
|
|
666
|
-
return `${lines.join("\n")}
|
|
667
|
-
`;
|
|
668
|
-
}
|
|
669
|
-
function collectStrings(value, out, depth) {
|
|
670
|
-
if (depth > 8) return;
|
|
671
|
-
if (typeof value === "string") out.push(value);
|
|
672
|
-
else if (Array.isArray(value)) for (const v of value) collectStrings(v, out, depth + 1);
|
|
673
|
-
else if (value && typeof value === "object") {
|
|
674
|
-
for (const v of Object.values(value)) collectStrings(v, out, depth + 1);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
function decodeRunLog(raw) {
|
|
678
|
-
const out = [];
|
|
679
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
680
|
-
out.push(line);
|
|
681
|
-
if (!line.startsWith("{")) continue;
|
|
682
|
-
try {
|
|
683
|
-
collectStrings(JSON.parse(line), out, 0);
|
|
684
|
-
} catch {
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
return out.join("\n");
|
|
688
|
-
}
|
|
689
|
-
async function findGateLabels(askId, sources) {
|
|
690
|
-
const fromText = sources.gateText ? findHitlGateInText(sources.gateText) : null;
|
|
691
|
-
if (fromText?.askId === askId) return fromText.labels;
|
|
692
|
-
if (!sources.logPath) return [];
|
|
693
|
-
try {
|
|
694
|
-
const gate = findHitlGateInText(decodeRunLog(await readFile3(sources.logPath, "utf8")));
|
|
695
|
-
return gate?.askId === askId ? gate.labels : [];
|
|
696
|
-
} catch {
|
|
697
|
-
return [];
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
var defaultExec = (bin, args, opts) => new Promise((resolve) => {
|
|
701
|
-
execFile(
|
|
702
|
-
bin,
|
|
703
|
-
[...args],
|
|
704
|
-
{
|
|
705
|
-
cwd: opts.cwd,
|
|
706
|
-
env: { ...opts.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
707
|
-
encoding: "utf8",
|
|
708
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
709
|
-
timeout: 12e4
|
|
710
|
-
},
|
|
711
|
-
(err, stdout, stderr) => {
|
|
712
|
-
const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
|
|
713
|
-
resolve({
|
|
714
|
-
code,
|
|
715
|
-
stdout: String(stdout ?? ""),
|
|
716
|
-
stderr: String(stderr ?? err?.message ?? "")
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
);
|
|
720
|
-
});
|
|
721
|
-
function isOutside(parent, child) {
|
|
722
|
-
const rel = path3.relative(parent, child);
|
|
723
|
-
return rel.startsWith("..") || path3.isAbsolute(rel);
|
|
724
|
-
}
|
|
725
|
-
async function landGateExit(opts) {
|
|
726
|
-
const env = opts.env ?? process.env;
|
|
727
|
-
const exec = opts.exec ?? defaultExec;
|
|
728
|
-
const which = opts.which ?? ((bin) => findOnPath(bin, { env }));
|
|
729
|
-
const redactions = gateExitRedactions(env);
|
|
730
|
-
const red2 = (text) => redactSecrets(text, redactions);
|
|
731
|
-
const log = (line) => opts.log?.(red2(`gate-exit: ${line}`));
|
|
732
|
-
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
733
|
-
const stamp2 = gateExitStamp(now);
|
|
734
|
-
const branch = gateExitBranch(stamp2);
|
|
735
|
-
const outcome = opts.outcome;
|
|
736
|
-
const requestedBase = opts.base ?? GATE_EXIT_DEFAULT_BASE;
|
|
737
|
-
const result = {
|
|
738
|
-
stamp: stamp2,
|
|
739
|
-
branch: null,
|
|
740
|
-
committed: false,
|
|
741
|
-
pushed: false,
|
|
742
|
-
url: null,
|
|
743
|
-
markerWritten: false,
|
|
744
|
-
note: ""
|
|
745
|
-
};
|
|
746
|
-
const git = (args, cwd) => exec("git", args, { cwd, env });
|
|
747
|
-
const labels = outcome.askId ? await findGateLabels(outcome.askId, { gateText: outcome.gateText, logPath: outcome.logPath }) : [];
|
|
748
|
-
const top = await git(["rev-parse", "--show-toplevel"], opts.root);
|
|
749
|
-
const repo = top.code === 0 ? top.stdout.trim() : null;
|
|
750
|
-
let dirtyPaths = 0;
|
|
751
|
-
if (repo) {
|
|
752
|
-
const status = await git(["status", "--porcelain"], repo);
|
|
753
|
-
dirtyPaths = status.stdout.split("\n").filter((l) => l.trim()).length;
|
|
754
|
-
if (dirtyPaths > 0) {
|
|
755
|
-
log(`root tree is dirty (${dirtyPaths} path(s)); reported, not cleaned`);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
let base = isSafeBaseRef(requestedBase) ? requestedBase : GATE_EXIT_DEFAULT_BASE;
|
|
759
|
-
const logRel = outcome.logPath ? path3.relative(opts.root, outcome.logPath) : null;
|
|
760
|
-
const buildNote = () => red2(buildGateExitNote({ stamp: stamp2, outcome, labels, base, logRel, dirtyPaths }));
|
|
761
|
-
const writeMarker = async () => {
|
|
762
|
-
const entry = {
|
|
763
|
-
stamp: stamp2,
|
|
764
|
-
openedAt: now.toISOString(),
|
|
765
|
-
trigger: outcome.trigger,
|
|
766
|
-
askId: outcome.askId ?? null,
|
|
767
|
-
labels: labels.map(red2),
|
|
768
|
-
base,
|
|
769
|
-
branch: result.branch,
|
|
770
|
-
pushed: result.pushed,
|
|
771
|
-
url: result.url
|
|
772
|
-
};
|
|
773
|
-
const current = await readGateExitMarker(opts.root);
|
|
774
|
-
if (current.kind === "invalid") {
|
|
775
|
-
log(
|
|
776
|
-
`${GATE_EXIT_MARKER_RELPATH} is unreadable (${current.detail}); left as is, entry ${stamp2} not recorded`
|
|
777
|
-
);
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
const marker = current.kind === "ok" ? { ...current.marker, pending: [...current.marker.pending, entry] } : { schema: GATE_EXIT_MARKER_SCHEMA, pending: [entry], skipped: [] };
|
|
781
|
-
try {
|
|
782
|
-
await writeGateExitMarker(opts.root, marker);
|
|
783
|
-
result.markerWritten = true;
|
|
784
|
-
} catch (err) {
|
|
785
|
-
log(`could not write ${GATE_EXIT_MARKER_RELPATH}: ${String(err)}`);
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
if (repo) {
|
|
789
|
-
const ignored = await git(["check-ignore", "-q", "--", GATE_EXIT_MARKER_RELPATH], opts.root);
|
|
790
|
-
if (ignored.code !== 0) {
|
|
791
|
-
log(
|
|
792
|
-
`${GATE_EXIT_MARKER_RELPATH} is not gitignored here; add it to .gitignore (agent-kit update adds it)`
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
};
|
|
797
|
-
if (!repo) {
|
|
798
|
-
log("not a git repository; no branch landed");
|
|
799
|
-
result.note = buildNote();
|
|
800
|
-
await writeMarker();
|
|
801
|
-
return result;
|
|
802
|
-
}
|
|
803
|
-
const origin = await git(["remote", "get-url", "origin"], repo);
|
|
804
|
-
const hasOrigin = origin.code === 0 && origin.stdout.trim().length > 0;
|
|
805
|
-
const originUrl = hasOrigin ? origin.stdout.trim() : "";
|
|
806
|
-
let startRef = null;
|
|
807
|
-
if (hasOrigin) {
|
|
808
|
-
const fetched = await git(["fetch", "--quiet", "origin", base], repo);
|
|
809
|
-
if (fetched.code !== 0)
|
|
810
|
-
log(`fetch origin ${base} failed: ${fetched.stderr.trim().split("\n")[0] ?? ""}`);
|
|
811
|
-
const verify = await git(
|
|
812
|
-
["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${base}^{commit}`],
|
|
813
|
-
repo
|
|
814
|
-
);
|
|
815
|
-
if (verify.code === 0) {
|
|
816
|
-
startRef = `refs/remotes/origin/${base}`;
|
|
817
|
-
} else {
|
|
818
|
-
const head = await git(
|
|
819
|
-
["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
|
|
820
|
-
repo
|
|
821
|
-
);
|
|
822
|
-
const fallback = head.stdout.trim().replace(/^origin\//, "");
|
|
823
|
-
if (head.code === 0 && isSafeBaseRef(fallback)) {
|
|
824
|
-
log(`origin/${base} not found; using the remote default branch ${fallback}`);
|
|
825
|
-
base = fallback;
|
|
826
|
-
startRef = `refs/remotes/origin/${fallback}`;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
} else {
|
|
830
|
-
log("no `origin` remote: the branch stays local, nothing is pushed");
|
|
831
|
-
const local = await git(
|
|
832
|
-
["rev-parse", "--verify", "--quiet", `refs/heads/${base}^{commit}`],
|
|
833
|
-
repo
|
|
834
|
-
);
|
|
835
|
-
startRef = local.code === 0 ? `refs/heads/${base}` : "HEAD";
|
|
836
|
-
}
|
|
837
|
-
result.note = buildNote();
|
|
838
|
-
if (!startRef) {
|
|
839
|
-
log(`no base to branch from (origin/${base} missing); note not landed`);
|
|
840
|
-
await writeMarker();
|
|
841
|
-
return result;
|
|
842
|
-
}
|
|
843
|
-
let tmp = null;
|
|
844
|
-
let wt = null;
|
|
845
|
-
try {
|
|
846
|
-
tmp = await mkdtemp(path3.join(opts.tmpRoot ?? tmpdir(), "agent-kit-gate-exit-"));
|
|
847
|
-
if (!isOutside(repo, tmp)) {
|
|
848
|
-
log("temporary directory resolves inside the repository; refusing to land there");
|
|
849
|
-
return result;
|
|
850
|
-
}
|
|
851
|
-
wt = path3.join(tmp, "wt");
|
|
852
|
-
const added = await git(["worktree", "add", "--detach", "--quiet", wt, startRef], repo);
|
|
853
|
-
if (added.code !== 0) {
|
|
854
|
-
wt = null;
|
|
855
|
-
log(`worktree add failed: ${added.stderr.trim().split("\n")[0] ?? ""}`);
|
|
856
|
-
return result;
|
|
857
|
-
}
|
|
858
|
-
const noteRel = `${GATE_EXIT_NOTE_DIR}/${stamp2}.md`;
|
|
859
|
-
await mkdir(path3.join(wt, GATE_EXIT_NOTE_DIR), { recursive: true });
|
|
860
|
-
await writeFile(path3.join(wt, noteRel), result.note, "utf8");
|
|
861
|
-
const add = await git(["add", "-f", "--", noteRel], wt);
|
|
862
|
-
if (add.code !== 0) {
|
|
863
|
-
log(`git add failed: ${add.stderr.trim().split("\n")[0] ?? ""}`);
|
|
864
|
-
return result;
|
|
865
|
-
}
|
|
866
|
-
const email = await git(["config", "user.email"], wt);
|
|
867
|
-
const identity = email.code === 0 && email.stdout.trim() ? [] : FALLBACK_GIT_IDENTITY;
|
|
868
|
-
const subject = `docs(gate-exit): record the ${outcome.trigger} of ${stamp2}`;
|
|
869
|
-
const body = `Ask id: ${outcome.askId ?? "none"}. Provenance: agent-inferred.`;
|
|
870
|
-
const commit = await git([...identity, "commit", "--quiet", "-m", subject, "-m", body], wt);
|
|
871
|
-
if (commit.code !== 0) {
|
|
872
|
-
log(`commit failed: ${commit.stderr.trim().split("\n")[0] ?? ""}`);
|
|
873
|
-
return result;
|
|
874
|
-
}
|
|
875
|
-
result.committed = true;
|
|
876
|
-
if (hasOrigin) {
|
|
877
|
-
const push = await git(["push", "--quiet", "origin", `HEAD:refs/heads/${branch}`], wt);
|
|
878
|
-
if (push.code === 0) {
|
|
879
|
-
result.pushed = true;
|
|
880
|
-
result.branch = branch;
|
|
881
|
-
log(`pushed ${branch}`);
|
|
882
|
-
} else {
|
|
883
|
-
log(`push failed: ${push.stderr.trim().split("\n")[0] ?? ""}`);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
if (!result.pushed) {
|
|
887
|
-
const keep = await git(["branch", branch, "HEAD"], wt);
|
|
888
|
-
if (keep.code === 0) {
|
|
889
|
-
result.branch = branch;
|
|
890
|
-
log(`branch ${branch} kept locally; push it with: git push origin ${branch}`);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
if (result.pushed) {
|
|
894
|
-
result.url = await openReview({
|
|
895
|
-
branch,
|
|
896
|
-
base,
|
|
897
|
-
title: `${outcome.trigger === "failure" ? "Gate exit: run failed" : `Gate exit: ${outcome.askId ?? "gate"}`} (${stamp2})`,
|
|
898
|
-
body: result.note,
|
|
899
|
-
cwd: wt,
|
|
900
|
-
tmp,
|
|
901
|
-
originUrl,
|
|
902
|
-
exec,
|
|
903
|
-
which,
|
|
904
|
-
env,
|
|
905
|
-
log
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
} catch (err) {
|
|
909
|
-
log(`landing failed: ${String(err)}`);
|
|
910
|
-
} finally {
|
|
911
|
-
if (wt) await git(["worktree", "remove", "--force", wt], repo);
|
|
912
|
-
if (tmp) await rm(tmp, { recursive: true, force: true }).catch(() => void 0);
|
|
913
|
-
await git(["worktree", "prune"], repo);
|
|
914
|
-
await writeMarker();
|
|
915
|
-
}
|
|
916
|
-
return result;
|
|
917
|
-
}
|
|
918
|
-
function lastUrl(text) {
|
|
919
|
-
const urls = text.match(/https?:\/\/\S+/g);
|
|
920
|
-
return urls?.at(-1) ?? null;
|
|
921
|
-
}
|
|
922
|
-
async function openReview(input) {
|
|
923
|
-
const forges = /gitlab/i.test(input.originUrl) ? ["glab", "gh"] : ["gh", "glab"];
|
|
924
|
-
for (const forge of forges) {
|
|
925
|
-
const bin = await input.which(forge);
|
|
926
|
-
if (!bin) continue;
|
|
927
|
-
const auth = await input.exec(bin, ["auth", "status"], { cwd: input.cwd, env: input.env });
|
|
928
|
-
if (auth.code !== 0) {
|
|
929
|
-
input.log(`${forge} is not authenticated; skipping the ${forge === "gh" ? "PR" : "MR"}`);
|
|
930
|
-
continue;
|
|
931
|
-
}
|
|
932
|
-
let created;
|
|
933
|
-
if (forge === "gh") {
|
|
934
|
-
const bodyFile = path3.join(input.tmp, "pr-body.md");
|
|
935
|
-
await writeFile(bodyFile, input.body, "utf8");
|
|
936
|
-
created = await input.exec(
|
|
937
|
-
bin,
|
|
938
|
-
[
|
|
939
|
-
"pr",
|
|
940
|
-
"create",
|
|
941
|
-
"--base",
|
|
942
|
-
input.base,
|
|
943
|
-
"--head",
|
|
944
|
-
input.branch,
|
|
945
|
-
"--title",
|
|
946
|
-
input.title,
|
|
947
|
-
"--body-file",
|
|
948
|
-
bodyFile
|
|
949
|
-
],
|
|
950
|
-
{ cwd: input.cwd, env: input.env }
|
|
951
|
-
);
|
|
952
|
-
} else {
|
|
953
|
-
created = await input.exec(
|
|
954
|
-
bin,
|
|
955
|
-
[
|
|
956
|
-
"mr",
|
|
957
|
-
"create",
|
|
958
|
-
"--source-branch",
|
|
959
|
-
input.branch,
|
|
960
|
-
"--target-branch",
|
|
961
|
-
input.base,
|
|
962
|
-
"--title",
|
|
963
|
-
input.title,
|
|
964
|
-
"--description",
|
|
965
|
-
input.body,
|
|
966
|
-
"--yes"
|
|
967
|
-
],
|
|
968
|
-
{ cwd: input.cwd, env: input.env }
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
if (created.code === 0) {
|
|
972
|
-
const url = lastUrl(created.stdout);
|
|
973
|
-
input.log(`${forge === "gh" ? "PR" : "MR"} opened${url ? `: ${url}` : ""}`);
|
|
974
|
-
return url;
|
|
975
|
-
}
|
|
976
|
-
input.log(`${forge} could not open the review: ${created.stderr.trim().split("\n")[0] ?? ""}`);
|
|
977
|
-
return null;
|
|
978
|
-
}
|
|
979
|
-
input.log(
|
|
980
|
-
`no authenticated gh/glab; open the review by hand: gh pr create --base ${input.base} --head ${input.branch} (or glab mr create --source-branch ${input.branch} --target-branch ${input.base})`
|
|
981
|
-
);
|
|
982
|
-
return null;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
494
|
// src/plan-loop/driver-events.ts
|
|
986
495
|
import { createInterface } from "readline";
|
|
987
496
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
@@ -1195,14 +704,14 @@ var DriverAgentSink = class {
|
|
|
1195
704
|
};
|
|
1196
705
|
|
|
1197
706
|
// src/plan-loop/run-loop.ts
|
|
1198
|
-
import { readFile as
|
|
1199
|
-
import
|
|
707
|
+
import { readFile as readFile4, rm, unlink } from "fs/promises";
|
|
708
|
+
import path5 from "path";
|
|
1200
709
|
|
|
1201
710
|
// src/plan-loop/external-review.ts
|
|
1202
711
|
import { spawn } from "child_process";
|
|
1203
|
-
import
|
|
1204
|
-
var CANONICAL_REL =
|
|
1205
|
-
var FALLBACK_REL =
|
|
712
|
+
import path3 from "path";
|
|
713
|
+
var CANONICAL_REL = path3.join(".cursor", "scripts", "plan-external-review.sh");
|
|
714
|
+
var FALLBACK_REL = path3.join("scripts", "plan-external-review.sh");
|
|
1206
715
|
function isPlanExhaustedReason(reason) {
|
|
1207
716
|
const r = reason.trim().toLowerCase();
|
|
1208
717
|
if (!r) return false;
|
|
@@ -1224,8 +733,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
1224
733
|
const existsFn = options.existsFn ?? fileExists;
|
|
1225
734
|
const log = options.log ?? ((line) => console.log(line));
|
|
1226
735
|
const force = options.force === true;
|
|
1227
|
-
const canonicalPath =
|
|
1228
|
-
const fallbackPath =
|
|
736
|
+
const canonicalPath = path3.join(root, CANONICAL_REL);
|
|
737
|
+
const fallbackPath = path3.join(root, FALLBACK_REL);
|
|
1229
738
|
let scriptPath = null;
|
|
1230
739
|
let scriptRel = CANONICAL_REL;
|
|
1231
740
|
if (await existsFn(canonicalPath)) {
|
|
@@ -1292,7 +801,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
1292
801
|
}
|
|
1293
802
|
|
|
1294
803
|
// src/plan-loop/persona-banners.ts
|
|
1295
|
-
import
|
|
804
|
+
import path4 from "path";
|
|
1296
805
|
import {
|
|
1297
806
|
blue,
|
|
1298
807
|
cyan,
|
|
@@ -1328,7 +837,7 @@ function resolveColor(name, fallback) {
|
|
|
1328
837
|
async function resolveCliPersonaId(root) {
|
|
1329
838
|
try {
|
|
1330
839
|
const cfg = await readJson(
|
|
1331
|
-
|
|
840
|
+
path4.join(root, ".cursor", "context", "config.json")
|
|
1332
841
|
);
|
|
1333
842
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
1334
843
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -1339,7 +848,7 @@ async function resolveCliPersonaId(root) {
|
|
|
1339
848
|
}
|
|
1340
849
|
async function loadPersonaPack(root, personaId) {
|
|
1341
850
|
try {
|
|
1342
|
-
const personaPath =
|
|
851
|
+
const personaPath = path4.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
1343
852
|
const pack = await readJson(personaPath);
|
|
1344
853
|
if (!pack || typeof pack.id !== "string") return null;
|
|
1345
854
|
return pack;
|
|
@@ -1388,7 +897,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
1388
897
|
}
|
|
1389
898
|
|
|
1390
899
|
// src/plan-loop/sentinel.ts
|
|
1391
|
-
import { readFile as
|
|
900
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1392
901
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—-].*)?)/i;
|
|
1393
902
|
function takeFromText(text) {
|
|
1394
903
|
if (!text) return null;
|
|
@@ -1457,14 +966,14 @@ function parseTickResultStatus(content) {
|
|
|
1457
966
|
}
|
|
1458
967
|
async function parseTickResultStatusFromLogFile(logPath) {
|
|
1459
968
|
try {
|
|
1460
|
-
return parseTickResultStatus(await
|
|
969
|
+
return parseTickResultStatus(await readFile3(logPath, "utf8"));
|
|
1461
970
|
} catch {
|
|
1462
971
|
return null;
|
|
1463
972
|
}
|
|
1464
973
|
}
|
|
1465
974
|
async function parseSentinelFromLogFile(logPath) {
|
|
1466
975
|
try {
|
|
1467
|
-
const content = await
|
|
976
|
+
const content = await readFile3(logPath, "utf8");
|
|
1468
977
|
return parseSentinelFromLog(content);
|
|
1469
978
|
} catch {
|
|
1470
979
|
return { kind: "missing" };
|
|
@@ -1696,7 +1205,7 @@ async function runPlanLoop(opts) {
|
|
|
1696
1205
|
return finishRun(1, "no active plan", null);
|
|
1697
1206
|
}
|
|
1698
1207
|
const pending = async () => countPendingTodos(await readPlan(planPath));
|
|
1699
|
-
landPlan =
|
|
1208
|
+
landPlan = path5.basename(planPath);
|
|
1700
1209
|
if (statePaths.invalid !== void 0) {
|
|
1701
1210
|
say(
|
|
1702
1211
|
`${STATE_ROOT_ENV}=${statePaths.invalid} is not supported (use .cursor or .agent-kit); using .cursor.`,
|
|
@@ -1733,12 +1242,12 @@ async function runPlanLoop(opts) {
|
|
|
1733
1242
|
type: "run_start",
|
|
1734
1243
|
protocol: DRIVER_PROTOCOL_VERSION,
|
|
1735
1244
|
backend: opts.backend.id,
|
|
1736
|
-
plan:
|
|
1245
|
+
plan: path5.basename(planPath),
|
|
1737
1246
|
pending: pendingAtStart,
|
|
1738
1247
|
maxTicks: opts.maxTicks,
|
|
1739
1248
|
hitl: opts.noHitl ? "off" : "driver"
|
|
1740
1249
|
});
|
|
1741
|
-
say(`Active plan: ${
|
|
1250
|
+
say(`Active plan: ${path5.basename(planPath)}`);
|
|
1742
1251
|
say(`Pending to-dos: ${pendingAtStart} | max ticks: ${opts.maxTicks}`);
|
|
1743
1252
|
say(`Backend: ${opts.backend.id}`);
|
|
1744
1253
|
if (persona) {
|
|
@@ -1775,7 +1284,7 @@ async function runPlanLoop(opts) {
|
|
|
1775
1284
|
else say(msg);
|
|
1776
1285
|
endReason = "stop file found";
|
|
1777
1286
|
try {
|
|
1778
|
-
await
|
|
1287
|
+
await rm(stopFile, { force: true });
|
|
1779
1288
|
} catch {
|
|
1780
1289
|
}
|
|
1781
1290
|
break;
|
|
@@ -1787,10 +1296,10 @@ async function runPlanLoop(opts) {
|
|
|
1787
1296
|
endReason = "plan exhausted";
|
|
1788
1297
|
break;
|
|
1789
1298
|
}
|
|
1790
|
-
const logPath =
|
|
1299
|
+
const logPath = path5.join(logDir, `tick-${stamp()}.log`);
|
|
1791
1300
|
landLogPath = logPath;
|
|
1792
1301
|
landBackend = backend.id;
|
|
1793
|
-
const relLog =
|
|
1302
|
+
const relLog = path5.relative(opts.root, logPath);
|
|
1794
1303
|
if (!driver) console.log("");
|
|
1795
1304
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
1796
1305
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -1884,7 +1393,7 @@ async function runPlanLoop(opts) {
|
|
|
1884
1393
|
}
|
|
1885
1394
|
let logText = "";
|
|
1886
1395
|
try {
|
|
1887
|
-
logText = await
|
|
1396
|
+
logText = await readFile4(logPath, "utf8");
|
|
1888
1397
|
} catch {
|
|
1889
1398
|
}
|
|
1890
1399
|
limit = detectUsageLimit(logText);
|
|
@@ -1998,7 +1507,7 @@ async function runPlanLoop(opts) {
|
|
|
1998
1507
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
1999
1508
|
if (banners) banners.phaseComplete(finishDetail);
|
|
2000
1509
|
say(
|
|
2001
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
1510
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path5.relative(opts.root, logDir)}/`
|
|
2002
1511
|
);
|
|
2003
1512
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
2004
1513
|
await armExternalPlanReview(
|
|
@@ -2016,11 +1525,6 @@ async function runPlanLoop(opts) {
|
|
|
2016
1525
|
export {
|
|
2017
1526
|
shouldUseLiveTui,
|
|
2018
1527
|
withLiveTui,
|
|
2019
|
-
listDetectBackendIds,
|
|
2020
|
-
detectAgentBackend,
|
|
2021
|
-
LAND_ON_GATE_DESCRIPTION,
|
|
2022
|
-
classifyGateExit,
|
|
2023
|
-
landGateExit,
|
|
2024
1528
|
DRIVER_EVENTS_FORMATS,
|
|
2025
1529
|
runPlanLoop
|
|
2026
1530
|
};
|