@dadado/agent-kit-cli 6.0.0 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PCCQUWE5.js → chunk-3LLXDHIU.js} +1 -1
- package/dist/{chunk-NDIGZXFF.js → chunk-3XKVQS2K.js} +1 -1
- package/dist/{chunk-B5CH47OS.js → chunk-ESNAFDYT.js} +1 -0
- package/dist/{chunk-PAB35H27.js → chunk-OZYJUNAQ.js} +518 -23
- package/dist/{chunk-WFXHMP47.js → chunk-TMFOLRU2.js} +2 -2
- package/dist/{chunk-T6P5ZMG2.js → chunk-U5F6RJN4.js} +2 -1
- package/dist/{chunk-HFUH3HVS.js → chunk-XDCCYBCJ.js} +7 -1
- package/dist/{chunk-4U7DLAQH.js → chunk-YP4OOK4T.js} +1 -1
- package/dist/{dashboard-YSNUGBFY.js → dashboard-YNCFISNO.js} +1 -1
- package/dist/{dashboard-broadcast-M4CMW7RX.js → dashboard-broadcast-NK7PU72L.js} +3 -2
- package/dist/{doctor-MQAXOFKM.js → doctor-SS6Y2T6H.js} +2 -2
- package/dist/index.js +13 -13
- package/dist/{init-7T6KNDFE.js → init-GVVBA2BM.js} +3 -3
- package/dist/{install-6G3D7J4U.js → install-R4HPI7VK.js} +3 -3
- package/dist/{mission-control-LJACEVVN.js → mission-control-MHQU73XC.js} +2 -2
- package/dist/{run-MWOI7UW5.js → run-3B26CVIL.js} +51 -9
- package/dist/{run-plan-UWQD3BIJ.js → run-plan-APTBO2OT.js} +14 -5
- package/dist/{scan-3MIUE7OR.js → scan-K6TQGV4S.js} +1 -1
- package/dist/{status-MXNTBKPL.js → status-MPJHI5RP.js} +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
ScrollRegion,
|
|
4
4
|
renderMcLive,
|
|
5
5
|
runMcTuiLoop
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3LLXDHIU.js";
|
|
7
7
|
import {
|
|
8
8
|
extractHandoffNamedPlans,
|
|
9
9
|
namedPlanCandidatePaths,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
LineSplitter,
|
|
14
14
|
RedactingStreamBuffer,
|
|
15
15
|
claudeRedactions,
|
|
16
|
+
findHitlGateInText,
|
|
16
17
|
formatHitlGateLine,
|
|
17
18
|
formatHitlSummary,
|
|
18
19
|
operatorQuitStop,
|
|
@@ -20,7 +21,10 @@ import {
|
|
|
20
21
|
redactSecrets,
|
|
21
22
|
renderStreamEvent,
|
|
22
23
|
whichBinary
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-ESNAFDYT.js";
|
|
25
|
+
import {
|
|
26
|
+
findOnPath
|
|
27
|
+
} from "./chunk-WGLPZXNU.js";
|
|
24
28
|
import {
|
|
25
29
|
TtySpinner,
|
|
26
30
|
shouldUseVisualMotion,
|
|
@@ -541,6 +545,443 @@ async function detectAgentBackend(requested, whichFn = whichBinary) {
|
|
|
541
545
|
return { ok: true, id, bin };
|
|
542
546
|
}
|
|
543
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
|
+
|
|
544
985
|
// src/plan-loop/driver-events.ts
|
|
545
986
|
import { createInterface } from "readline";
|
|
546
987
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
@@ -754,14 +1195,14 @@ var DriverAgentSink = class {
|
|
|
754
1195
|
};
|
|
755
1196
|
|
|
756
1197
|
// src/plan-loop/run-loop.ts
|
|
757
|
-
import { readFile as
|
|
758
|
-
import
|
|
1198
|
+
import { readFile as readFile5, rm as rm2, unlink } from "fs/promises";
|
|
1199
|
+
import path6 from "path";
|
|
759
1200
|
|
|
760
1201
|
// src/plan-loop/external-review.ts
|
|
761
1202
|
import { spawn } from "child_process";
|
|
762
|
-
import
|
|
763
|
-
var CANONICAL_REL =
|
|
764
|
-
var FALLBACK_REL =
|
|
1203
|
+
import path4 from "path";
|
|
1204
|
+
var CANONICAL_REL = path4.join(".cursor", "scripts", "plan-external-review.sh");
|
|
1205
|
+
var FALLBACK_REL = path4.join("scripts", "plan-external-review.sh");
|
|
765
1206
|
function isPlanExhaustedReason(reason) {
|
|
766
1207
|
const r = reason.trim().toLowerCase();
|
|
767
1208
|
if (!r) return false;
|
|
@@ -783,8 +1224,8 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
783
1224
|
const existsFn = options.existsFn ?? fileExists;
|
|
784
1225
|
const log = options.log ?? ((line) => console.log(line));
|
|
785
1226
|
const force = options.force === true;
|
|
786
|
-
const canonicalPath =
|
|
787
|
-
const fallbackPath =
|
|
1227
|
+
const canonicalPath = path4.join(root, CANONICAL_REL);
|
|
1228
|
+
const fallbackPath = path4.join(root, FALLBACK_REL);
|
|
788
1229
|
let scriptPath = null;
|
|
789
1230
|
let scriptRel = CANONICAL_REL;
|
|
790
1231
|
if (await existsFn(canonicalPath)) {
|
|
@@ -851,7 +1292,7 @@ async function armExternalPlanReview(root, options = {}) {
|
|
|
851
1292
|
}
|
|
852
1293
|
|
|
853
1294
|
// src/plan-loop/persona-banners.ts
|
|
854
|
-
import
|
|
1295
|
+
import path5 from "path";
|
|
855
1296
|
import {
|
|
856
1297
|
blue,
|
|
857
1298
|
cyan,
|
|
@@ -887,7 +1328,7 @@ function resolveColor(name, fallback) {
|
|
|
887
1328
|
async function resolveCliPersonaId(root) {
|
|
888
1329
|
try {
|
|
889
1330
|
const cfg = await readJson(
|
|
890
|
-
|
|
1331
|
+
path5.join(root, ".cursor", "context", "config.json")
|
|
891
1332
|
);
|
|
892
1333
|
const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
|
|
893
1334
|
const id = modes?.[CLI_RUN_PLAN_MODE];
|
|
@@ -898,7 +1339,7 @@ async function resolveCliPersonaId(root) {
|
|
|
898
1339
|
}
|
|
899
1340
|
async function loadPersonaPack(root, personaId) {
|
|
900
1341
|
try {
|
|
901
|
-
const personaPath =
|
|
1342
|
+
const personaPath = path5.join(root, "registry", "personas", "core", personaId, "persona.json");
|
|
902
1343
|
const pack = await readJson(personaPath);
|
|
903
1344
|
if (!pack || typeof pack.id !== "string") return null;
|
|
904
1345
|
return pack;
|
|
@@ -947,7 +1388,7 @@ function createPersonaBannerPrinter(persona) {
|
|
|
947
1388
|
}
|
|
948
1389
|
|
|
949
1390
|
// src/plan-loop/sentinel.ts
|
|
950
|
-
import { readFile as
|
|
1391
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
951
1392
|
var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—-].*)?)/i;
|
|
952
1393
|
function takeFromText(text) {
|
|
953
1394
|
if (!text) return null;
|
|
@@ -1016,14 +1457,14 @@ function parseTickResultStatus(content) {
|
|
|
1016
1457
|
}
|
|
1017
1458
|
async function parseTickResultStatusFromLogFile(logPath) {
|
|
1018
1459
|
try {
|
|
1019
|
-
return parseTickResultStatus(await
|
|
1460
|
+
return parseTickResultStatus(await readFile4(logPath, "utf8"));
|
|
1020
1461
|
} catch {
|
|
1021
1462
|
return null;
|
|
1022
1463
|
}
|
|
1023
1464
|
}
|
|
1024
1465
|
async function parseSentinelFromLogFile(logPath) {
|
|
1025
1466
|
try {
|
|
1026
|
-
const content = await
|
|
1467
|
+
const content = await readFile4(logPath, "utf8");
|
|
1027
1468
|
return parseSentinelFromLog(content);
|
|
1028
1469
|
} catch {
|
|
1029
1470
|
return { kind: "missing" };
|
|
@@ -1208,6 +1649,42 @@ async function runPlanLoop(opts) {
|
|
|
1208
1649
|
else console.log(message);
|
|
1209
1650
|
};
|
|
1210
1651
|
const emit = (event) => driver?.emitter.emit(event);
|
|
1652
|
+
let landStop;
|
|
1653
|
+
let landFailed = false;
|
|
1654
|
+
let landLogPath;
|
|
1655
|
+
let landSummary;
|
|
1656
|
+
let landGateText;
|
|
1657
|
+
let landBackend = opts.backend.id;
|
|
1658
|
+
let landPlan;
|
|
1659
|
+
const landIfNeeded = async (exitCode, reason, pendingNow) => {
|
|
1660
|
+
if (!opts.landOnGate || opts.dryRun) return;
|
|
1661
|
+
const trigger = classifyGateExit({ stop: landStop, failed: landFailed });
|
|
1662
|
+
if (!trigger) return;
|
|
1663
|
+
const outcome = {
|
|
1664
|
+
trigger,
|
|
1665
|
+
exitCode,
|
|
1666
|
+
reason,
|
|
1667
|
+
command: "run-plan",
|
|
1668
|
+
backend: landBackend,
|
|
1669
|
+
ticks: ticksRun,
|
|
1670
|
+
pending: pendingNow,
|
|
1671
|
+
...landPlan ? { plan: landPlan } : {},
|
|
1672
|
+
...landStop ? { askId: landStop.askId } : {},
|
|
1673
|
+
...landLogPath ? { logPath: landLogPath } : {},
|
|
1674
|
+
...landSummary ? { summary: landSummary } : {},
|
|
1675
|
+
...landGateText ? { gateText: landGateText } : {}
|
|
1676
|
+
};
|
|
1677
|
+
try {
|
|
1678
|
+
await landGateExit({
|
|
1679
|
+
...opts.gateExit,
|
|
1680
|
+
root: opts.root,
|
|
1681
|
+
outcome,
|
|
1682
|
+
log: (line) => say(line, "warn")
|
|
1683
|
+
});
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
say(`gate-exit: landing failed: ${String(err)}`, "warn");
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1211
1688
|
const finishRun = (exitCode, reason, pending2, planExhausted = false) => {
|
|
1212
1689
|
emit({ type: "run_end", exitCode, ticks: ticksRun, pending: pending2, reason, planExhausted });
|
|
1213
1690
|
driver?.reader.close();
|
|
@@ -1219,6 +1696,7 @@ async function runPlanLoop(opts) {
|
|
|
1219
1696
|
return finishRun(1, "no active plan", null);
|
|
1220
1697
|
}
|
|
1221
1698
|
const pending = async () => countPendingTodos(await readPlan(planPath));
|
|
1699
|
+
landPlan = path6.basename(planPath);
|
|
1222
1700
|
if (statePaths.invalid !== void 0) {
|
|
1223
1701
|
say(
|
|
1224
1702
|
`${STATE_ROOT_ENV}=${statePaths.invalid} is not supported (use .cursor or .agent-kit); using .cursor.`,
|
|
@@ -1255,12 +1733,12 @@ async function runPlanLoop(opts) {
|
|
|
1255
1733
|
type: "run_start",
|
|
1256
1734
|
protocol: DRIVER_PROTOCOL_VERSION,
|
|
1257
1735
|
backend: opts.backend.id,
|
|
1258
|
-
plan:
|
|
1736
|
+
plan: path6.basename(planPath),
|
|
1259
1737
|
pending: pendingAtStart,
|
|
1260
1738
|
maxTicks: opts.maxTicks,
|
|
1261
1739
|
hitl: opts.noHitl ? "off" : "driver"
|
|
1262
1740
|
});
|
|
1263
|
-
say(`Active plan: ${
|
|
1741
|
+
say(`Active plan: ${path6.basename(planPath)}`);
|
|
1264
1742
|
say(`Pending to-dos: ${pendingAtStart} | max ticks: ${opts.maxTicks}`);
|
|
1265
1743
|
say(`Backend: ${opts.backend.id}`);
|
|
1266
1744
|
if (persona) {
|
|
@@ -1297,7 +1775,7 @@ async function runPlanLoop(opts) {
|
|
|
1297
1775
|
else say(msg);
|
|
1298
1776
|
endReason = "stop file found";
|
|
1299
1777
|
try {
|
|
1300
|
-
await
|
|
1778
|
+
await rm2(stopFile, { force: true });
|
|
1301
1779
|
} catch {
|
|
1302
1780
|
}
|
|
1303
1781
|
break;
|
|
@@ -1309,8 +1787,10 @@ async function runPlanLoop(opts) {
|
|
|
1309
1787
|
endReason = "plan exhausted";
|
|
1310
1788
|
break;
|
|
1311
1789
|
}
|
|
1312
|
-
const logPath =
|
|
1313
|
-
|
|
1790
|
+
const logPath = path6.join(logDir, `tick-${stamp()}.log`);
|
|
1791
|
+
landLogPath = logPath;
|
|
1792
|
+
landBackend = backend.id;
|
|
1793
|
+
const relLog = path6.relative(opts.root, logPath);
|
|
1314
1794
|
if (!driver) console.log("");
|
|
1315
1795
|
const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
|
|
1316
1796
|
if (banners) banners.tickStart(tickLine);
|
|
@@ -1376,11 +1856,17 @@ async function runPlanLoop(opts) {
|
|
|
1376
1856
|
hitlStop = result.hitl?.stop;
|
|
1377
1857
|
replies = result.hitl?.replies ?? [];
|
|
1378
1858
|
lastResult = result.lastResult;
|
|
1859
|
+
landSummary = lastResult?.resultText.trim() || lastResult?.assistantTexts.at(-1)?.trim() || void 0;
|
|
1860
|
+
landGateText = lastResult ? [lastResult.resultText, ...lastResult.assistantTexts].join("\n") : void 0;
|
|
1379
1861
|
} catch (err) {
|
|
1380
1862
|
say(String(err), "error");
|
|
1381
1863
|
agentExit = 1;
|
|
1382
1864
|
endTick(null, null);
|
|
1383
|
-
|
|
1865
|
+
const reason = `tick ${tick} failed to start: ${String(err)}`;
|
|
1866
|
+
const pendingNow2 = await pending();
|
|
1867
|
+
landFailed = true;
|
|
1868
|
+
await landIfNeeded(1, reason, pendingNow2);
|
|
1869
|
+
return finishRun(1, reason, pendingNow2);
|
|
1384
1870
|
}
|
|
1385
1871
|
if (hitlStop) {
|
|
1386
1872
|
const msg = `${backend.id} tick ended at a HITL gate without a reply - stopping. See log: ${relLog}`;
|
|
@@ -1393,11 +1879,12 @@ async function runPlanLoop(opts) {
|
|
|
1393
1879
|
endTick(await pending(), null);
|
|
1394
1880
|
exitCode = hitlStop.exitCode;
|
|
1395
1881
|
endReason = hitlStop.message;
|
|
1882
|
+
landStop = hitlStop;
|
|
1396
1883
|
break;
|
|
1397
1884
|
}
|
|
1398
1885
|
let logText = "";
|
|
1399
1886
|
try {
|
|
1400
|
-
logText = await
|
|
1887
|
+
logText = await readFile5(logPath, "utf8");
|
|
1401
1888
|
} catch {
|
|
1402
1889
|
}
|
|
1403
1890
|
limit = detectUsageLimit(logText);
|
|
@@ -1431,6 +1918,7 @@ async function runPlanLoop(opts) {
|
|
|
1431
1918
|
else say(msg);
|
|
1432
1919
|
endTick(await pending(), null);
|
|
1433
1920
|
endReason = "too many MCP tools";
|
|
1921
|
+
landFailed = true;
|
|
1434
1922
|
break;
|
|
1435
1923
|
}
|
|
1436
1924
|
const status = await resolveTickResultStatus(lastResult, logPath);
|
|
@@ -1445,6 +1933,7 @@ async function runPlanLoop(opts) {
|
|
|
1445
1933
|
}
|
|
1446
1934
|
endTick(await pending(), null);
|
|
1447
1935
|
endReason = `tick failed (${status.subtype ?? "is_error"})`;
|
|
1936
|
+
landFailed = true;
|
|
1448
1937
|
break;
|
|
1449
1938
|
}
|
|
1450
1939
|
if (agentExit !== 0) {
|
|
@@ -1457,6 +1946,7 @@ async function runPlanLoop(opts) {
|
|
|
1457
1946
|
}
|
|
1458
1947
|
endTick(await pending(), null);
|
|
1459
1948
|
endReason = `${backend.id} exited with code ${agentExit}`;
|
|
1949
|
+
landFailed = true;
|
|
1460
1950
|
break;
|
|
1461
1951
|
}
|
|
1462
1952
|
const sentinel = await resolveTickSentinel(lastResult, logPath);
|
|
@@ -1484,6 +1974,7 @@ async function runPlanLoop(opts) {
|
|
|
1484
1974
|
if (banners) banners.stop(msg);
|
|
1485
1975
|
else say(msg);
|
|
1486
1976
|
endReason = "tick without sentinel and no progress";
|
|
1977
|
+
landFailed = true;
|
|
1487
1978
|
break;
|
|
1488
1979
|
}
|
|
1489
1980
|
if (after === 0) {
|
|
@@ -1507,7 +1998,7 @@ async function runPlanLoop(opts) {
|
|
|
1507
1998
|
const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
|
|
1508
1999
|
if (banners) banners.phaseComplete(finishDetail);
|
|
1509
2000
|
say(
|
|
1510
|
-
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${
|
|
2001
|
+
`Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path6.relative(opts.root, logDir)}/`
|
|
1511
2002
|
);
|
|
1512
2003
|
if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
|
|
1513
2004
|
await armExternalPlanReview(
|
|
@@ -1515,6 +2006,7 @@ async function runPlanLoop(opts) {
|
|
|
1515
2006
|
driver ? { log: (line) => say(line), writeOut: driver.writeErr } : {}
|
|
1516
2007
|
);
|
|
1517
2008
|
}
|
|
2009
|
+
await landIfNeeded(exitCode, endReason || "stopped", pendingNow);
|
|
1518
2010
|
return finishRun(exitCode, endReason || "stopped", pendingNow, planExhausted);
|
|
1519
2011
|
} finally {
|
|
1520
2012
|
process.off("SIGINT", onSigInt);
|
|
@@ -1526,6 +2018,9 @@ export {
|
|
|
1526
2018
|
withLiveTui,
|
|
1527
2019
|
listDetectBackendIds,
|
|
1528
2020
|
detectAgentBackend,
|
|
2021
|
+
LAND_ON_GATE_DESCRIPTION,
|
|
2022
|
+
classifyGateExit,
|
|
2023
|
+
landGateExit,
|
|
1529
2024
|
DRIVER_EVENTS_FORMATS,
|
|
1530
2025
|
runPlanLoop
|
|
1531
2026
|
};
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
assessClaudeCommandAdapters,
|
|
17
17
|
executeSafeReadinessFixes
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-YP4OOK4T.js";
|
|
19
19
|
import {
|
|
20
20
|
writeReadinessSnapshot
|
|
21
21
|
} from "./chunk-HAFRAXGV.js";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
REPOSITORY_PROFILE_REL,
|
|
36
36
|
detectIde,
|
|
37
37
|
runScanner
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-U5F6RJN4.js";
|
|
39
39
|
import {
|
|
40
40
|
GIT_PLATFORM_META
|
|
41
41
|
} from "./chunk-F6C3GFKV.js";
|
|
@@ -436,7 +436,8 @@ var KIT_OWNED_IGNORE_PATTERNS = [
|
|
|
436
436
|
".cursor/dogfood/",
|
|
437
437
|
".cursor/context/readiness.json",
|
|
438
438
|
".cursor/context/flight-log.json",
|
|
439
|
-
".cursor/context/mission-timing.json"
|
|
439
|
+
".cursor/context/mission-timing.json",
|
|
440
|
+
".cursor/context/gate-exit.json"
|
|
440
441
|
];
|
|
441
442
|
function globToRegExp(glob) {
|
|
442
443
|
const body = glob.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*");
|
|
@@ -93,6 +93,11 @@ function readPreferredBrowserFromConfig(configPath, fsHooks = {}) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// src/commands/negated-flag.ts
|
|
97
|
+
function negatedFlag(args, name) {
|
|
98
|
+
return args[`no-${name}`] === true || args[name] === false;
|
|
99
|
+
}
|
|
100
|
+
|
|
96
101
|
// src/commands/dashboard.ts
|
|
97
102
|
function readPreferredBrowserFromWorkspace(cwd, readFile = readFileSync2) {
|
|
98
103
|
const resolved = resolveContextConfigPath(path.resolve(cwd), {
|
|
@@ -260,7 +265,7 @@ var dashboardCommand = defineCommand({
|
|
|
260
265
|
return;
|
|
261
266
|
}
|
|
262
267
|
const env = dashboardSpawnEnv({ ...process.env }, snapshotRoot, {
|
|
263
|
-
noOpen:
|
|
268
|
+
noOpen: negatedFlag(args, "open"),
|
|
264
269
|
browser: args.browser
|
|
265
270
|
});
|
|
266
271
|
process.title = dashboardProcessTitle(snapshotRoot);
|
|
@@ -271,6 +276,7 @@ var dashboardCommand = defineCommand({
|
|
|
271
276
|
|
|
272
277
|
export {
|
|
273
278
|
REPO_ROOT_ENV,
|
|
279
|
+
negatedFlag,
|
|
274
280
|
readPreferredBrowserFromWorkspace,
|
|
275
281
|
applyDashboardOpenEnv,
|
|
276
282
|
dashboardSpawnEnv,
|
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
dashboardProcessTitle,
|
|
4
4
|
dashboardSpawnEnv,
|
|
5
5
|
findDashboardStart,
|
|
6
|
+
negatedFlag,
|
|
6
7
|
resolveDashboardSnapshotRoot
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-XDCCYBCJ.js";
|
|
8
9
|
import {
|
|
9
10
|
logger
|
|
10
11
|
} from "./chunk-ZB43YMHW.js";
|
|
@@ -105,7 +106,7 @@ var dashboardBroadcastCommand = defineCommand({
|
|
|
105
106
|
}
|
|
106
107
|
const snapshotRoot = resolveDashboardSnapshotRoot(args.cwd);
|
|
107
108
|
const env = dashboardSpawnEnv({ ...process.env }, snapshotRoot, {
|
|
108
|
-
noOpen:
|
|
109
|
+
noOpen: negatedFlag(args, "open"),
|
|
109
110
|
browser: args.browser
|
|
110
111
|
});
|
|
111
112
|
process.title = dashboardProcessTitle(snapshotRoot);
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
assessHooksHealth,
|
|
3
3
|
executeSafeReadinessFixes,
|
|
4
4
|
refreshRepositoryProfile
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YP4OOK4T.js";
|
|
6
6
|
import {
|
|
7
7
|
writeReadinessSnapshot
|
|
8
8
|
} from "./chunk-HAFRAXGV.js";
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "./chunk-PNFF6OU3.js";
|
|
22
22
|
import {
|
|
23
23
|
runScanner
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-U5F6RJN4.js";
|
|
25
25
|
import "./chunk-F6C3GFKV.js";
|
|
26
26
|
import {
|
|
27
27
|
logger
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
rewriteRootArgvToRun
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-3XKVQS2K.js";
|
|
5
|
+
import "./chunk-ESNAFDYT.js";
|
|
6
6
|
import "./chunk-WGLPZXNU.js";
|
|
7
7
|
import "./chunk-MAPOQEA2.js";
|
|
8
8
|
import {
|
|
@@ -213,13 +213,13 @@ var main = defineCommand({
|
|
|
213
213
|
version: KIT_VERSION
|
|
214
214
|
},
|
|
215
215
|
subCommands: {
|
|
216
|
-
init: () => import("./init-
|
|
217
|
-
install: () => import("./install-
|
|
218
|
-
scan: () => import("./scan-
|
|
216
|
+
init: () => import("./init-GVVBA2BM.js").then((m) => m.initCommand),
|
|
217
|
+
install: () => import("./install-R4HPI7VK.js").then((m) => m.installCommand),
|
|
218
|
+
scan: () => import("./scan-K6TQGV4S.js").then((m) => m.scanCommand),
|
|
219
219
|
add: () => import("./add-YTCDLPUS.js").then((m) => m.addCommand),
|
|
220
|
-
doctor: () => import("./doctor-
|
|
220
|
+
doctor: () => import("./doctor-SS6Y2T6H.js").then((m) => m.doctorCommand),
|
|
221
221
|
"setup-global": () => import("./setup-global-AT4DD3MG.js").then((m) => m.setupGlobalCommand),
|
|
222
|
-
status: () => import("./status-
|
|
222
|
+
status: () => import("./status-MPJHI5RP.js").then((m) => m.statusCommand),
|
|
223
223
|
update: () => import("./update-P62637ZI.js").then((m) => m.updateCommand),
|
|
224
224
|
"cursor-awareness": () => import("./cursor-awareness-TN5JXD2C.js").then((m) => m.cursorAwarenessCommand),
|
|
225
225
|
"public-inbound-radar": () => import("./public-inbound-radar-5U4JMBRU.js").then((m) => m.publicInboundRadarCommand),
|
|
@@ -229,12 +229,12 @@ var main = defineCommand({
|
|
|
229
229
|
contribute: () => import("./contribute-A7NCWTUM.js").then((m) => m.contributeCommand),
|
|
230
230
|
handoff: () => import("./handoff-MK5JG3J5.js").then((m) => m.handoffCommand),
|
|
231
231
|
"plan-index": () => import("./plan-index-V7T5MKL6.js").then((m) => m.planIndexCommand),
|
|
232
|
-
run: () => import("./run-
|
|
233
|
-
"run-plan": () => import("./run-plan-
|
|
234
|
-
"run-plan-all": () => import("./run-
|
|
235
|
-
dashboard: () => import("./dashboard-
|
|
236
|
-
"dashboard-broadcast": () => import("./dashboard-broadcast-
|
|
237
|
-
"mission-control": () => import("./mission-control-
|
|
232
|
+
run: () => import("./run-3B26CVIL.js").then((m) => m.runCommand),
|
|
233
|
+
"run-plan": () => import("./run-plan-APTBO2OT.js").then((m) => m.runPlanCommand),
|
|
234
|
+
"run-plan-all": () => import("./run-3B26CVIL.js").then((m) => m.runPlanAllCommand),
|
|
235
|
+
dashboard: () => import("./dashboard-YNCFISNO.js").then((m) => m.dashboardCommand),
|
|
236
|
+
"dashboard-broadcast": () => import("./dashboard-broadcast-NK7PU72L.js").then((m) => m.dashboardBroadcastCommand),
|
|
237
|
+
"mission-control": () => import("./mission-control-MHQU73XC.js").then((m) => m.missionControlCommand),
|
|
238
238
|
hook: () => import("./hook-D2WHBKRN.js").then((m) => m.hookCommand),
|
|
239
239
|
guard: () => import("./guard-EHXF76ID.js").then((m) => m.guardCommand),
|
|
240
240
|
monitors: () => import("./monitors-PDXBLB3U.js").then((m) => m.monitorsCommand),
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
nextStepAfterInstall,
|
|
3
3
|
performInstall,
|
|
4
4
|
printInstallEpilogue
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TMFOLRU2.js";
|
|
6
6
|
import {
|
|
7
7
|
NON_INTERACTIVE_ROOT_ARGS
|
|
8
8
|
} from "./chunk-DBXDV5O7.js";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
logApplyStats
|
|
12
12
|
} from "./chunk-HS7LVVLO.js";
|
|
13
13
|
import "./chunk-3DGTPONS.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-YP4OOK4T.js";
|
|
15
15
|
import "./chunk-HAFRAXGV.js";
|
|
16
16
|
import "./chunk-PPKX55GI.js";
|
|
17
17
|
import "./chunk-WGLPZXNU.js";
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
withCliProgress
|
|
21
21
|
} from "./chunk-QTON2GQX.js";
|
|
22
22
|
import "./chunk-PNFF6OU3.js";
|
|
23
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-U5F6RJN4.js";
|
|
24
24
|
import "./chunk-F6C3GFKV.js";
|
|
25
25
|
import {
|
|
26
26
|
logger
|
|
@@ -3,18 +3,18 @@ import {
|
|
|
3
3
|
nextStepAfterInstall,
|
|
4
4
|
performInstall,
|
|
5
5
|
printInstallEpilogue
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-TMFOLRU2.js";
|
|
7
7
|
import "./chunk-DBXDV5O7.js";
|
|
8
8
|
import "./chunk-HS7LVVLO.js";
|
|
9
9
|
import "./chunk-3DGTPONS.js";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-YP4OOK4T.js";
|
|
11
11
|
import "./chunk-HAFRAXGV.js";
|
|
12
12
|
import "./chunk-PPKX55GI.js";
|
|
13
13
|
import "./chunk-WGLPZXNU.js";
|
|
14
14
|
import "./chunk-MAPOQEA2.js";
|
|
15
15
|
import "./chunk-QTON2GQX.js";
|
|
16
16
|
import "./chunk-PNFF6OU3.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-U5F6RJN4.js";
|
|
18
18
|
import "./chunk-F6C3GFKV.js";
|
|
19
19
|
import "./chunk-ZB43YMHW.js";
|
|
20
20
|
import "./chunk-GN4SAZF2.js";
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
findDashboardDataScript,
|
|
4
4
|
loadDashboardSnapshot,
|
|
5
5
|
runMcTuiLoop
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3LLXDHIU.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveDashboardSnapshotRoot
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XDCCYBCJ.js";
|
|
10
10
|
import "./chunk-QTON2GQX.js";
|
|
11
11
|
import {
|
|
12
12
|
logger
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
LAND_ON_GATE_DESCRIPTION,
|
|
3
|
+
classifyGateExit,
|
|
2
4
|
detectAgentBackend,
|
|
5
|
+
landGateExit,
|
|
3
6
|
listDetectBackendIds,
|
|
4
7
|
runPlanLoop,
|
|
5
8
|
shouldUseLiveTui,
|
|
6
9
|
withLiveTui
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import
|
|
10
|
+
} from "./chunk-OZYJUNAQ.js";
|
|
11
|
+
import "./chunk-3LLXDHIU.js";
|
|
12
|
+
import {
|
|
13
|
+
negatedFlag
|
|
14
|
+
} from "./chunk-XDCCYBCJ.js";
|
|
10
15
|
import "./chunk-WUWRGOUV.js";
|
|
11
16
|
import {
|
|
12
17
|
buildDispatchPrompt,
|
|
@@ -17,13 +22,13 @@ import {
|
|
|
17
22
|
readCommandFile,
|
|
18
23
|
runHeadlessDispatch,
|
|
19
24
|
unknownSlashMessage
|
|
20
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-3XKVQS2K.js";
|
|
21
26
|
import {
|
|
22
27
|
HITL_UNANSWERED_EXIT_CODE,
|
|
23
28
|
formatHitlSummary,
|
|
24
29
|
getBackend,
|
|
25
30
|
hitlExitCode
|
|
26
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-ESNAFDYT.js";
|
|
27
32
|
import "./chunk-WGLPZXNU.js";
|
|
28
33
|
import "./chunk-MAPOQEA2.js";
|
|
29
34
|
import "./chunk-QTON2GQX.js";
|
|
@@ -82,7 +87,8 @@ async function executeRun(input) {
|
|
|
82
87
|
dryRun: false,
|
|
83
88
|
backend,
|
|
84
89
|
noHitl: input.noHitl === true,
|
|
85
|
-
plain: input.plain === true
|
|
90
|
+
plain: input.plain === true,
|
|
91
|
+
landOnGate: input.landOnGate === true
|
|
86
92
|
});
|
|
87
93
|
return { exitCode: code };
|
|
88
94
|
}
|
|
@@ -129,7 +135,37 @@ async function executeRun(input) {
|
|
|
129
135
|
hitl: { policy: input.noHitl ? "off" : "prompt" }
|
|
130
136
|
});
|
|
131
137
|
for (const line of formatHitlSummary(result.hitl)) console.log(line);
|
|
132
|
-
|
|
138
|
+
const exitCode = hitlExitCode(result.exitCode, result.hitl);
|
|
139
|
+
const trigger = input.landOnGate ? classifyGateExit({ stop: result.hitl?.stop, failed: exitCode !== 0 }) : null;
|
|
140
|
+
if (trigger) {
|
|
141
|
+
const stop = result.hitl?.stop;
|
|
142
|
+
const summary = result.lastResult?.resultText.trim() || result.lastResult?.assistantTexts.at(-1)?.trim() || void 0;
|
|
143
|
+
try {
|
|
144
|
+
await landGateExit({
|
|
145
|
+
root,
|
|
146
|
+
outcome: {
|
|
147
|
+
trigger,
|
|
148
|
+
exitCode,
|
|
149
|
+
reason: stop?.message ?? `${detected.id} exited with code ${exitCode}`,
|
|
150
|
+
command: `run ${slash}`,
|
|
151
|
+
backend: detected.id,
|
|
152
|
+
logPath,
|
|
153
|
+
...stop ? { askId: stop.askId } : {},
|
|
154
|
+
...summary ? { summary } : {},
|
|
155
|
+
...result.lastResult ? {
|
|
156
|
+
gateText: [
|
|
157
|
+
result.lastResult.resultText,
|
|
158
|
+
...result.lastResult.assistantTexts
|
|
159
|
+
].join("\n")
|
|
160
|
+
} : {}
|
|
161
|
+
},
|
|
162
|
+
log: (line) => console.log(line)
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.log(`gate-exit: landing failed: ${String(err)}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { exitCode };
|
|
133
169
|
} catch (err) {
|
|
134
170
|
return { exitCode: 1, error: String(err) };
|
|
135
171
|
}
|
|
@@ -174,6 +210,11 @@ var runDispatchArgs = {
|
|
|
174
210
|
type: "boolean",
|
|
175
211
|
description: "Status lines instead of the Mission Control live view (the default on a TTY; non-TTY, NO_COLOR and CI are always plain)",
|
|
176
212
|
default: false
|
|
213
|
+
},
|
|
214
|
+
"land-on-gate": {
|
|
215
|
+
type: "boolean",
|
|
216
|
+
description: LAND_ON_GATE_DESCRIPTION,
|
|
217
|
+
default: false
|
|
177
218
|
}
|
|
178
219
|
};
|
|
179
220
|
async function runSlashCli(slash, args) {
|
|
@@ -197,8 +238,9 @@ async function runSlashCli(slash, args) {
|
|
|
197
238
|
dryRun: Boolean(args["dry-run"]),
|
|
198
239
|
maxTicks,
|
|
199
240
|
sleepSeconds,
|
|
200
|
-
noHitl:
|
|
201
|
-
plain: Boolean(args.plain)
|
|
241
|
+
noHitl: negatedFlag(args, "hitl"),
|
|
242
|
+
plain: Boolean(args.plain),
|
|
243
|
+
landOnGate: Boolean(args["land-on-gate"])
|
|
202
244
|
});
|
|
203
245
|
if (result.stdout) {
|
|
204
246
|
console.log(result.stdout);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DRIVER_EVENTS_FORMATS,
|
|
3
|
+
LAND_ON_GATE_DESCRIPTION,
|
|
3
4
|
detectAgentBackend,
|
|
4
5
|
listDetectBackendIds,
|
|
5
6
|
runPlanLoop
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import
|
|
7
|
+
} from "./chunk-OZYJUNAQ.js";
|
|
8
|
+
import "./chunk-3LLXDHIU.js";
|
|
9
|
+
import {
|
|
10
|
+
negatedFlag
|
|
11
|
+
} from "./chunk-XDCCYBCJ.js";
|
|
9
12
|
import "./chunk-WUWRGOUV.js";
|
|
10
13
|
import {
|
|
11
14
|
HITL_UNANSWERED_EXIT_CODE,
|
|
12
15
|
getBackend
|
|
13
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ESNAFDYT.js";
|
|
14
17
|
import "./chunk-WGLPZXNU.js";
|
|
15
18
|
import "./chunk-MAPOQEA2.js";
|
|
16
19
|
import "./chunk-QTON2GQX.js";
|
|
@@ -74,6 +77,11 @@ var runPlanCommand = defineCommand({
|
|
|
74
77
|
description: "Driver mode for apps: `ndjson` writes only NDJSON events to stdout and reads HITL answers as JSON lines on stdin (no TTY needed; see docs/driver-events-protocol.md)",
|
|
75
78
|
default: ""
|
|
76
79
|
},
|
|
80
|
+
"land-on-gate": {
|
|
81
|
+
type: "boolean",
|
|
82
|
+
description: LAND_ON_GATE_DESCRIPTION,
|
|
83
|
+
default: false
|
|
84
|
+
},
|
|
77
85
|
plain: {
|
|
78
86
|
type: "boolean",
|
|
79
87
|
description: "Status lines instead of the Mission Control live view (the default on a TTY; non-TTY, NO_COLOR and CI are always plain)",
|
|
@@ -136,8 +144,9 @@ var runPlanCommand = defineCommand({
|
|
|
136
144
|
model: args.model ? String(args.model) : void 0,
|
|
137
145
|
dryRun: Boolean(args["dry-run"]),
|
|
138
146
|
backend,
|
|
139
|
-
noHitl:
|
|
147
|
+
noHitl: negatedFlag(args, "hitl"),
|
|
140
148
|
plain: Boolean(args.plain),
|
|
149
|
+
landOnGate: Boolean(args["land-on-gate"]),
|
|
141
150
|
...events ? { events: {} } : {},
|
|
142
151
|
...onLimit ? { onLimit } : {}
|
|
143
152
|
});
|
package/package.json
CHANGED