@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.
@@ -2,7 +2,7 @@ import {
2
2
  REPO_ROOT_ENV,
3
3
  bundledDashboardCandidates,
4
4
  findDashboardStart
5
- } from "./chunk-HFUH3HVS.js";
5
+ } from "./chunk-XDCCYBCJ.js";
6
6
  import {
7
7
  HELMET_ACCENT,
8
8
  HELMET_FILL,
@@ -11,7 +11,7 @@ import {
11
11
  redactSecrets,
12
12
  resolveHitlRunOptions,
13
13
  spawnLogged
14
- } from "./chunk-B5CH47OS.js";
14
+ } from "./chunk-ESNAFDYT.js";
15
15
  import {
16
16
  ensureLoopLogsDir,
17
17
  runnerStatePaths
@@ -2071,6 +2071,7 @@ export {
2071
2071
  RESERVED_GATE_IDS,
2072
2072
  HITL_UNANSWERED_EXIT_CODE,
2073
2073
  formatHitlGateLine,
2074
+ findHitlGateInText,
2074
2075
  operatorQuitStop,
2075
2076
  formatHitlSummary,
2076
2077
  hitlExitCode,
@@ -3,7 +3,7 @@ import {
3
3
  ScrollRegion,
4
4
  renderMcLive,
5
5
  runMcTuiLoop
6
- } from "./chunk-PCCQUWE5.js";
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-B5CH47OS.js";
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 readFile4, rm, unlink } from "fs/promises";
758
- import path5 from "path";
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 path3 from "path";
763
- var CANONICAL_REL = path3.join(".cursor", "scripts", "plan-external-review.sh");
764
- var FALLBACK_REL = path3.join("scripts", "plan-external-review.sh");
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 = path3.join(root, CANONICAL_REL);
787
- const fallbackPath = path3.join(root, FALLBACK_REL);
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 path4 from "path";
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
- path4.join(root, ".cursor", "context", "config.json")
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 = path4.join(root, "registry", "personas", "core", personaId, "persona.json");
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 readFile3 } from "fs/promises";
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 readFile3(logPath, "utf8"));
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 readFile3(logPath, "utf8");
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: path5.basename(planPath),
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: ${path5.basename(planPath)}`);
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 rm(stopFile, { force: true });
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 = path5.join(logDir, `tick-${stamp()}.log`);
1313
- const relLog = path5.relative(opts.root, logPath);
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
- return finishRun(1, `tick ${tick} failed to start: ${String(err)}`, await pending());
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 readFile4(logPath, "utf8");
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 ${path5.relative(opts.root, logDir)}/`
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-4U7DLAQH.js";
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-T6P5ZMG2.js";
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: Boolean(args["no-open"]),
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,
@@ -18,7 +18,7 @@ import {
18
18
  REQUIRED_SECRET_PATTERNS,
19
19
  detectGitDirty,
20
20
  runScanner
21
- } from "./chunk-T6P5ZMG2.js";
21
+ } from "./chunk-U5F6RJN4.js";
22
22
  import {
23
23
  CONTEXT_CONFIG_REL,
24
24
  contextConfigPath
@@ -7,7 +7,7 @@ import {
7
7
  findDashboardStart,
8
8
  readPreferredBrowserFromWorkspace,
9
9
  resolveDashboardSnapshotRoot
10
- } from "./chunk-HFUH3HVS.js";
10
+ } from "./chunk-XDCCYBCJ.js";
11
11
  import "./chunk-ZB43YMHW.js";
12
12
  export {
13
13
  applyDashboardOpenEnv,
@@ -3,8 +3,9 @@ import {
3
3
  dashboardProcessTitle,
4
4
  dashboardSpawnEnv,
5
5
  findDashboardStart,
6
+ negatedFlag,
6
7
  resolveDashboardSnapshotRoot
7
- } from "./chunk-HFUH3HVS.js";
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: Boolean(args["no-open"]),
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-4U7DLAQH.js";
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-T6P5ZMG2.js";
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-NDIGZXFF.js";
5
- import "./chunk-B5CH47OS.js";
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-7T6KNDFE.js").then((m) => m.initCommand),
217
- install: () => import("./install-6G3D7J4U.js").then((m) => m.installCommand),
218
- scan: () => import("./scan-3MIUE7OR.js").then((m) => m.scanCommand),
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-MQAXOFKM.js").then((m) => m.doctorCommand),
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-MXNTBKPL.js").then((m) => m.statusCommand),
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-MWOI7UW5.js").then((m) => m.runCommand),
233
- "run-plan": () => import("./run-plan-UWQD3BIJ.js").then((m) => m.runPlanCommand),
234
- "run-plan-all": () => import("./run-MWOI7UW5.js").then((m) => m.runPlanAllCommand),
235
- dashboard: () => import("./dashboard-YSNUGBFY.js").then((m) => m.dashboardCommand),
236
- "dashboard-broadcast": () => import("./dashboard-broadcast-M4CMW7RX.js").then((m) => m.dashboardBroadcastCommand),
237
- "mission-control": () => import("./mission-control-LJACEVVN.js").then((m) => m.missionControlCommand),
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-WFXHMP47.js";
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-4U7DLAQH.js";
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-T6P5ZMG2.js";
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-WFXHMP47.js";
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-4U7DLAQH.js";
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-T6P5ZMG2.js";
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-PCCQUWE5.js";
6
+ } from "./chunk-3LLXDHIU.js";
7
7
  import {
8
8
  resolveDashboardSnapshotRoot
9
- } from "./chunk-HFUH3HVS.js";
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-PAB35H27.js";
8
- import "./chunk-PCCQUWE5.js";
9
- import "./chunk-HFUH3HVS.js";
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-NDIGZXFF.js";
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-B5CH47OS.js";
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
- return { exitCode: hitlExitCode(result.exitCode, result.hitl) };
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: Boolean(args["no-hitl"]),
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-PAB35H27.js";
7
- import "./chunk-PCCQUWE5.js";
8
- import "./chunk-HFUH3HVS.js";
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-B5CH47OS.js";
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: Boolean(args["no-hitl"]),
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
  });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runScanner
3
- } from "./chunk-T6P5ZMG2.js";
3
+ } from "./chunk-U5F6RJN4.js";
4
4
  import "./chunk-F6C3GFKV.js";
5
5
  import {
6
6
  logger
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-PNFF6OU3.js";
8
8
  import {
9
9
  runScanner
10
- } from "./chunk-T6P5ZMG2.js";
10
+ } from "./chunk-U5F6RJN4.js";
11
11
  import "./chunk-F6C3GFKV.js";
12
12
  import {
13
13
  logger
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dadado/agent-kit-cli",
3
- "version": "6.0.0",
3
+ "version": "6.1.1",
4
4
  "description": "Agent Kit CLI: HITL framework install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, context).",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",