agent-yes 1.122.3 → 1.124.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/default.config.yaml +19 -0
  2. package/dist/SUPPORTED_CLIS-Cvm7yo5d.js +8 -0
  3. package/dist/{SUPPORTED_CLIS-BleNYXA2.js → SUPPORTED_CLIS-D_-bIOlW.js} +2 -2
  4. package/dist/{agent-yes.config-z-IPzH5U.js → agent-yes.config-D6ycMApr.js} +2 -65
  5. package/dist/cli.js +6 -6
  6. package/dist/configShared-C5QaNPnz.js +71 -0
  7. package/dist/{globalPidIndex-gZuTvTBs.js → globalPidIndex-C7r2m6s7.js} +19 -20
  8. package/dist/index.js +4 -4
  9. package/dist/pidStore-C4c2O15q.js +5 -0
  10. package/dist/{pidStore-B5vBu8Px.js → pidStore-CGKIhaJO.js} +5 -4
  11. package/dist/reaper-BLVA780B.js +3 -0
  12. package/dist/{reaper-Dj8R7ltI.js → reaper-BkjPN7mw.js} +24 -2
  13. package/dist/{remotes-CpGcTr7A.js → remotes-BRCDVnR7.js} +1 -1
  14. package/dist/{remotes-D2fqaRU8.js → remotes-D8GvSbhf.js} +1 -1
  15. package/dist/{schedule-e4f7NlA2.js → schedule-D2cn8N7o.js} +7 -7
  16. package/dist/{serve-CzztmZ_N.js → serve-Bo3bDXQG.js} +202 -58
  17. package/dist/{setup-CPyRNiIA.js → setup-CvOr258q.js} +3 -3
  18. package/dist/{share-CS9XVrLF.js → share-YuM6-Q6A.js} +71 -13
  19. package/dist/{subcommands-CQowpr1t.js → subcommands-ClVHy-xI.js} +647 -32
  20. package/dist/subcommands-Llf9o8nh.js +7 -0
  21. package/dist/{tray-DjCIyakK.js → tray-BVnJLThD.js} +1 -1
  22. package/dist/{ts-9GThuc3w.js → ts-DGIglR4L.js} +10 -7
  23. package/dist/{versionChecker-Bv9XKddN.js → versionChecker-gaQkM2Hy.js} +2 -2
  24. package/dist/{workspaceConfig-XP2NEWmV.js → workspaceConfig-BJO4fzEn.js} +1 -1
  25. package/lab/ui/console-logic.js +222 -10
  26. package/lab/ui/icon.svg +5 -0
  27. package/lab/ui/index.html +1152 -28
  28. package/lab/ui/landing.html +276 -0
  29. package/lab/ui/manifest.webmanifest +14 -0
  30. package/lab/ui/sw.js +56 -0
  31. package/package.json +5 -1
  32. package/ts/agentTree.spec.ts +92 -0
  33. package/ts/agentTree.ts +149 -0
  34. package/ts/configShared.ts +4 -0
  35. package/ts/globalPidIndex.ts +28 -20
  36. package/ts/idleWaiter.spec.ts +7 -1
  37. package/ts/index.ts +9 -0
  38. package/ts/lsWatch.spec.ts +61 -0
  39. package/ts/lsWatch.ts +94 -0
  40. package/ts/needsInput.spec.ts +55 -0
  41. package/ts/needsInput.ts +68 -0
  42. package/ts/pidStore.ts +3 -0
  43. package/ts/reaper.spec.ts +26 -2
  44. package/ts/reaper.ts +25 -0
  45. package/ts/resultEnvelope.spec.ts +43 -0
  46. package/ts/resultEnvelope.ts +88 -0
  47. package/ts/serve.ts +276 -41
  48. package/ts/share.ts +144 -27
  49. package/ts/subcommands.ts +0 -0
  50. package/ts/todoParse.spec.ts +68 -0
  51. package/ts/todoParse.ts +88 -0
  52. package/ts/utils.spec.ts +4 -1
  53. package/dist/SUPPORTED_CLIS-ClaOErso.js +0 -8
  54. package/dist/pidStore-7y1cTcAE.js +0 -5
  55. package/dist/reaper-HqcUms2d.js +0 -3
  56. package/dist/subcommands-KAbIcd8_.js +0 -6
@@ -1,11 +1,300 @@
1
- import { i as readGlobalPids } from "./globalPidIndex-gZuTvTBs.js";
2
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-D2fqaRU8.js";
1
+ import { t as agentYesHome } from "./agentYesHome-BvaUOzCV.js";
2
+ import { a as updateGlobalPidStatus, i as readGlobalPids } from "./globalPidIndex-C7r2m6s7.js";
3
+ import { t as loadSharedCliDefaults } from "./configShared-C5QaNPnz.js";
4
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-D8GvSbhf.js";
3
5
  import ms from "ms";
4
6
  import yargs from "yargs";
5
7
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
6
8
  import { homedir } from "os";
7
9
  import path from "path";
8
10
 
11
+ //#region ts/agentTree.ts
12
+ /**
13
+ * Link records into a forest via parent_pid === wrapper_pid. Records whose
14
+ * parent isn't present in the set (top-level agents, or links into agents
15
+ * filtered out by a keyword/scope) become roots. Root and sibling order follows
16
+ * the input order, so a caller that pre-sorts (e.g. newest-first) is preserved.
17
+ */
18
+ function buildAgentForest(records) {
19
+ const nodes = records.map((record) => ({
20
+ record,
21
+ children: []
22
+ }));
23
+ const byWrapper = /* @__PURE__ */ new Map();
24
+ for (const n of nodes) {
25
+ const w = n.record.wrapper_pid;
26
+ if (typeof w === "number" && w > 0) byWrapper.set(w, n);
27
+ }
28
+ const roots = [];
29
+ for (const n of nodes) {
30
+ const p = n.record.parent_pid;
31
+ const parent = typeof p === "number" && p > 0 ? byWrapper.get(p) : void 0;
32
+ if (parent && parent !== n) parent.children.push(n);
33
+ else roots.push(n);
34
+ }
35
+ const seen = /* @__PURE__ */ new Set();
36
+ const mark = (n) => {
37
+ if (seen.has(n)) return;
38
+ seen.add(n);
39
+ n.children.forEach(mark);
40
+ };
41
+ roots.forEach(mark);
42
+ for (const n of nodes) if (!seen.has(n)) roots.push(n);
43
+ return roots;
44
+ }
45
+ /**
46
+ * Depth-first flatten a forest into rows carrying a box-drawing branch prefix.
47
+ * A `visited` guard makes a pathological parent_pid cycle terminate instead of
48
+ * recursing forever.
49
+ */
50
+ function flattenForest(roots) {
51
+ const rows = [];
52
+ const visited = /* @__PURE__ */ new Set();
53
+ const walk = (node, ancestorsLast) => {
54
+ if (visited.has(node)) return;
55
+ visited.add(node);
56
+ const depth = ancestorsLast.length;
57
+ let prefix = "";
58
+ for (let i = 0; i < depth - 1; i++) prefix += ancestorsLast[i] ? " " : "│ ";
59
+ if (depth > 0) prefix += ancestorsLast[depth - 1] ? "└─ " : "├─ ";
60
+ rows.push({
61
+ record: node.record,
62
+ prefix,
63
+ depth
64
+ });
65
+ node.children.forEach((c, i) => walk(c, [...ancestorsLast, i === node.children.length - 1]));
66
+ };
67
+ for (const r of roots) walk(r, []);
68
+ return rows;
69
+ }
70
+
71
+ //#endregion
72
+ //#region ts/todoParse.ts
73
+ /**
74
+ * Parse an agent's todo/task list out of its RENDERED TUI screen.
75
+ *
76
+ * Source of truth for EVERY CLI (claude, codex, gemini, …) is the screen the
77
+ * agent draws — never a CLI-specific session file. The durable copy is the
78
+ * per-pid raw log (`<cwd>/.agent-yes/<pid>.raw.log`); rendering it through a
79
+ * headless xterm (see renderRawLog) collapses the reflow/redraw frames into the
80
+ * final coherent text, which is what we scan here.
81
+ *
82
+ * The todo list in these TUIs renders as a tree block anchored by the `⎿`
83
+ * branch glyph, one marker per line:
84
+ *
85
+ * ⎿ ☒ Wire up the parser
86
+ * ☒ Add the badge
87
+ * ◼ Compute in /api/ls ← in progress
88
+ * ◻ Render in the console ← pending
89
+ * ◻ Tests
90
+ *
91
+ * Badge = `${done}/${total}` (done is the numerator → "2/5").
92
+ *
93
+ * This parse is deliberately conservative: we only report a count when a block
94
+ * is confidently detected (the `⎿` anchor + ≥2 consecutive marker lines), so an
95
+ * agent that merely prints a check glyph in prose never produces a phantom badge.
96
+ */
97
+ const DONE = new Set([
98
+ "✔",
99
+ "☑",
100
+ "✓",
101
+ "☒"
102
+ ]);
103
+ const IN_PROGRESS = new Set(["◼"]);
104
+ const PENDING = new Set(["◻", "☐"]);
105
+ const ANCHOR = "⎿";
106
+ function markerOf(line) {
107
+ let s = line.replace(/^\s+/, "");
108
+ if (s.startsWith(ANCHOR)) s = s.slice(1).replace(/^\s+/, "");
109
+ const ch = [...s][0];
110
+ if (ch === void 0) return null;
111
+ if (DONE.has(ch)) return "done";
112
+ if (IN_PROGRESS.has(ch)) return "inprogress";
113
+ if (PENDING.has(ch)) return "pending";
114
+ return null;
115
+ }
116
+ /**
117
+ * Find the MOST RECENT confidently-detected todo block in the rendered lines and
118
+ * return its {done, total}. Returns null when none qualifies (caller omits the
119
+ * badge entirely — never shows "0/0").
120
+ *
121
+ * A block is a maximal run of consecutive marker lines. It only counts when it
122
+ * is anchored — the `⎿` glyph appears on the run's first line or the line
123
+ * directly above it — and has ≥2 marker lines. The last qualifying block wins,
124
+ * since the agent's current todo state is the one drawn most recently.
125
+ */
126
+ function parseTaskCounts(lines) {
127
+ let best = null;
128
+ const n = lines.length;
129
+ let i = 0;
130
+ while (i < n) {
131
+ if (markerOf(lines[i]) === null) {
132
+ i++;
133
+ continue;
134
+ }
135
+ let hasAnchor = i > 0 && lines[i - 1].includes(ANCHOR);
136
+ const counts = {
137
+ done: 0,
138
+ inprogress: 0,
139
+ pending: 0
140
+ };
141
+ let j = i;
142
+ for (; j < n; j++) {
143
+ const mk = markerOf(lines[j]);
144
+ if (mk === null) break;
145
+ if (lines[j].includes(ANCHOR)) hasAnchor = true;
146
+ counts[mk]++;
147
+ }
148
+ const total = counts.done + counts.inprogress + counts.pending;
149
+ if (hasAnchor && total >= 2) best = {
150
+ done: counts.done,
151
+ total
152
+ };
153
+ i = j === i ? i + 1 : j;
154
+ }
155
+ return best;
156
+ }
157
+
158
+ //#endregion
159
+ //#region ts/needsInput.ts
160
+ function reTest(re, s) {
161
+ return (re.global || re.sticky ? new RegExp(re.source, re.flags.replace(/[gy]/g, "")) : re).test(s);
162
+ }
163
+ function isChromeLine(s) {
164
+ const t = s.trim();
165
+ return !t || /^─+$/.test(t) || /^esc to (interrupt|cancel)/i.test(t) || /\? for shortcuts/.test(t) || /\d+%\s*until auto-compact/i.test(t);
166
+ }
167
+ /**
168
+ * Returns a NeedsInput when the screen shows an unresolved selection menu, else
169
+ * null. `cfg.working` short-circuits to null (an actively-working agent isn't
170
+ * blocked). Pure + synchronous so it's trivially unit-testable.
171
+ */
172
+ function classifyNeedsInput(lines, cfg) {
173
+ const patterns = cfg.needsInput ?? [];
174
+ if (patterns.length === 0) return null;
175
+ const text = lines.join("\n");
176
+ if ((cfg.working ?? []).some((re) => reTest(re, text))) return null;
177
+ if (!patterns.some((re) => reTest(re, text))) return null;
178
+ let last = -1;
179
+ for (let i = 0; i < lines.length; i++) if (patterns.some((re) => reTest(re, lines[i]))) last = i;
180
+ const start = Math.max(0, last - 6);
181
+ const end = Math.min(lines.length, last + 6);
182
+ return { question: lines.slice(start, end).map((l) => l.trim()).filter((l) => l && !isChromeLine(l)).join(" • ").slice(0, 400) };
183
+ }
184
+
185
+ //#endregion
186
+ //#region ts/lsWatch.ts
187
+ /**
188
+ * Diff the previous per-pid states against the current snapshot and return the
189
+ * transition events to emit plus the next prev-map. Pure: no I/O, no clock —
190
+ * the caller passes `ts`.
191
+ *
192
+ * Emits an event when:
193
+ * - an agent is seen for the first time (baseline, `prev_state: null`)
194
+ * - its `state` or `question` changed since last tick
195
+ * - it vanished from the live set without us ever seeing it `stopped` (reaped
196
+ * between ticks) — a synthetic `stopped` event so a "done" transition is
197
+ * never silently dropped.
198
+ */
199
+ function diffLsStates(prev, cur, ts) {
200
+ const events = [];
201
+ const next = /* @__PURE__ */ new Map();
202
+ const curPids = /* @__PURE__ */ new Set();
203
+ for (const a of cur) {
204
+ curPids.add(a.pid);
205
+ next.set(a.pid, a);
206
+ const p = prev.get(a.pid);
207
+ if (!p) events.push({
208
+ ...toEvent(a, ts),
209
+ prev_state: null
210
+ });
211
+ else if (p.state !== a.state || p.question !== a.question) events.push({
212
+ ...toEvent(a, ts),
213
+ prev_state: p.state
214
+ });
215
+ }
216
+ for (const [pid, p] of prev) if (!curPids.has(pid) && p.state !== "stopped") events.push({
217
+ ts,
218
+ pid,
219
+ cli: p.cli,
220
+ cwd: p.cwd,
221
+ state: "stopped",
222
+ question: null,
223
+ prev_state: p.state
224
+ });
225
+ return {
226
+ events,
227
+ next
228
+ };
229
+ }
230
+ function toEvent(a, ts) {
231
+ return {
232
+ ts,
233
+ pid: a.pid,
234
+ cli: a.cli,
235
+ cwd: a.cwd,
236
+ state: a.state,
237
+ question: a.question
238
+ };
239
+ }
240
+
241
+ //#endregion
242
+ //#region ts/resultEnvelope.ts
243
+ /**
244
+ * Structured result envelope — P4 of the orchestrator-observability work.
245
+ *
246
+ * A fan-out parent that spawned a sub-agent wants its *outcome* (branch, commit
247
+ * SHAs, changed files, status, blockers, a summary) as machine-readable data,
248
+ * not by grepping `ay tail`. This is the agent-yes analog of an in-harness
249
+ * Agent tool's `<result>` block. The sub-agent deposits one JSON envelope when
250
+ * it finishes; the parent pulls it with `ay result <keyword>`.
251
+ *
252
+ * Why a PERSISTED file, not a query-time screen scrape (the model that
253
+ * `needs_input`/activity use): a completion record is read AFTER the agent is
254
+ * done — exactly when its rendered screen is gone and its log may be reaped. It
255
+ * must outlive the process, so it is written once to
256
+ * `$AGENT_YES_HOME/results/<pid>.json` and read back verbatim. It is keyed by
257
+ * the wrapper pid the agent already knows via the injected `AGENT_YES_PID` env
258
+ * var, so depositing needs no new spawn-time wiring in either runtime.
259
+ *
260
+ * This module is the pure, fs-free core (path math + input normalization) so it
261
+ * is trivially unit-testable, mirroring `lsWatch.ts` / `needsInput.ts`. The fs
262
+ * read/write + CLI live in `subcommands.ts` (`cmdResult`).
263
+ */
264
+ /** Directory holding the per-pid result files. */
265
+ function resultsDir() {
266
+ return path.join(agentYesHome(), "results");
267
+ }
268
+ /** Absolute path of one agent's result envelope. */
269
+ function resultPath(pid) {
270
+ return path.join(resultsDir(), `${pid}.json`);
271
+ }
272
+ /**
273
+ * Coerce raw write-side input into an envelope payload. If it parses as JSON we
274
+ * keep it as-is (object, array, or scalar — the agent owns the shape). If it
275
+ * does NOT parse, we don't reject: a bare string is a perfectly good summary, so
276
+ * we wrap it as `{ summary }`. Empty / whitespace-only input is an error the
277
+ * caller should surface (returns null).
278
+ */
279
+ function normalizeEnvelope(raw) {
280
+ const trimmed = raw.trim();
281
+ if (trimmed.length === 0) return null;
282
+ try {
283
+ return JSON.parse(trimmed);
284
+ } catch {
285
+ return { summary: trimmed };
286
+ }
287
+ }
288
+ /** Wrap a normalized payload with correlation metadata for persistence. */
289
+ function buildStoredResult(pid, result, writtenAt) {
290
+ return {
291
+ pid,
292
+ written_at: writtenAt,
293
+ result
294
+ };
295
+ }
296
+
297
+ //#endregion
9
298
  //#region ts/subcommands.ts
10
299
  /**
11
300
  * `ay ls / read / cat / tail / head / send` subcommand implementations.
@@ -189,6 +478,7 @@ const SUBCOMMANDS = new Set([
189
478
  "list",
190
479
  "ps",
191
480
  "status",
481
+ "result",
192
482
  "read",
193
483
  "cat",
194
484
  "tail",
@@ -223,6 +513,7 @@ async function runSubcommand(argv) {
223
513
  case "list":
224
514
  case "ps": return await cmdLs(rest);
225
515
  case "status": return await cmdStatus(rest);
516
+ case "result": return await cmdResult(rest);
226
517
  case "read":
227
518
  case "cat": return await cmdRead(rest, { mode: "cat" });
228
519
  case "tail": return await cmdRead(rest, { mode: "tail" });
@@ -233,23 +524,23 @@ async function runSubcommand(argv) {
233
524
  case "restart": return await cmdRestart(rest);
234
525
  case "note": return await cmdNote(rest);
235
526
  case "serve": {
236
- const { cmdServe } = await import("./serve-CzztmZ_N.js");
527
+ const { cmdServe } = await import("./serve-Bo3bDXQG.js");
237
528
  return cmdServe(rest);
238
529
  }
239
530
  case "setup": {
240
- const { cmdSetup } = await import("./setup-CPyRNiIA.js");
531
+ const { cmdSetup } = await import("./setup-CvOr258q.js");
241
532
  return cmdSetup(rest);
242
533
  }
243
534
  case "schedule": {
244
- const { cmdSchedule } = await import("./schedule-e4f7NlA2.js");
535
+ const { cmdSchedule } = await import("./schedule-D2cn8N7o.js");
245
536
  return cmdSchedule(rest);
246
537
  }
247
538
  case "remote": {
248
- const { cmdRemote } = await import("./remotes-CpGcTr7A.js");
539
+ const { cmdRemote } = await import("./remotes-BRCDVnR7.js");
249
540
  return cmdRemote(rest);
250
541
  }
251
542
  case "reap":
252
- await (await import("./reaper-HqcUms2d.js")).sweep();
543
+ await (await import("./reaper-BLVA780B.js")).sweep();
253
544
  return 0;
254
545
  case "help": return cmdHelp();
255
546
  default: return null;
@@ -261,7 +552,7 @@ async function runSubcommand(argv) {
261
552
  }
262
553
  }
263
554
  function cmdHelp() {
264
- process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n ay reap kill process groups leaked by dead agents\n\nRemote:\n ay setup guided setup: pick a workspace, share to agent-yes.com\n ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
555
+ process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n ay setup guided setup: pick a workspace, share to agent-yes.com\n ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
265
556
  return 0;
266
557
  }
267
558
  function matchKeyword(record, keyword) {
@@ -629,6 +920,35 @@ async function runAllRemotesLs(opts) {
629
920
  }
630
921
  return 0;
631
922
  }
923
+ /**
924
+ * The live display state of one agent: stopped (exited) / idle (alive+quiet) /
925
+ * active (alive+recent output) / needs_input (alive but parked on an unanswered
926
+ * menu). Shared by the `ay ls` human table AND its `--json` output so both report
927
+ * needs_input identically — an orchestrator parsing `ay ls --json` is the primary
928
+ * consumer.
929
+ */
930
+ async function deriveLiveState(r) {
931
+ if (!isPidAlive(r.pid)) return {
932
+ state: "stopped",
933
+ question: null
934
+ };
935
+ let state = "active";
936
+ if (r.log_file) {
937
+ const mtime = await stat(r.log_file).then((s) => s.mtimeMs).catch(() => null);
938
+ state = mtime !== null && Date.now() - mtime > IDLE_THRESHOLD_MS ? "idle" : "active";
939
+ }
940
+ if (r.log_file) {
941
+ const ni = await extractNeedsInput(r.log_file, r.cli);
942
+ if (ni) return {
943
+ state: "needs_input",
944
+ question: ni.question
945
+ };
946
+ }
947
+ return {
948
+ state,
949
+ question: null
950
+ };
951
+ }
632
952
  async function cmdLs(rest) {
633
953
  const y = yargs(rest).usage("Usage: ay ls [keyword] [options]\n ay list [keyword] [options]\n ay ps [keyword] [options]\n\nList running agents. Optionally filter by keyword (pid, cwd substring, or prompt substring).").option("all", {
634
954
  type: "boolean",
@@ -642,6 +962,15 @@ async function cmdLs(rest) {
642
962
  type: "boolean",
643
963
  default: false,
644
964
  description: "Output as JSON array"
965
+ }).option("watch", {
966
+ alias: "w",
967
+ type: "boolean",
968
+ default: false,
969
+ description: "Stream agent state transitions (needs_input | idle | active | stopped) as NDJSON across all matched agents — one event stream for a whole fan-out, instead of N per-pid `ay status --watch`es. Runs until Ctrl-C."
970
+ }).option("interval", {
971
+ type: "number",
972
+ default: 2,
973
+ description: "Poll interval in seconds (--watch)"
645
974
  }).option("latest", {
646
975
  type: "boolean",
647
976
  default: false,
@@ -658,7 +987,7 @@ async function cmdLs(rest) {
658
987
  type: "boolean",
659
988
  default: false,
660
989
  description: "Show this help"
661
- }).example("ay ls", "list running agents").example("ay ls --all-remotes", "include all configured remote machines").example("ay ls --all", "include exited agents").example("ay ls --json", "machine-readable output").example("ay ls symval", "filter by cwd/prompt keyword").help(false).version(false).exitProcess(false);
990
+ }).example("ay ls", "list running agents").example("ay ls --all-remotes", "include all configured remote machines").example("ay ls --all", "include exited agents").example("ay ls --json", "machine-readable output").example("ay ls --watch", "stream state transitions for a whole fan-out as NDJSON").example("ay ls symval", "filter by cwd/prompt keyword").help(false).version(false).exitProcess(false);
662
991
  const argv = await y.parseAsync();
663
992
  if (argv.help || argv.h) {
664
993
  process.stdout.write(await y.getHelp() + "\n");
@@ -684,9 +1013,45 @@ async function cmdLs(rest) {
684
1013
  latest: argv.latest,
685
1014
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
686
1015
  };
1016
+ if (argv.watch) {
1017
+ const intervalMs = Math.max(500, (Number.isFinite(argv.interval) ? argv.interval : 2) * 1e3);
1018
+ process.stderr.write(`watching agents every ${intervalMs / 1e3}s… (Ctrl-C to stop)\n`);
1019
+ let prev = /* @__PURE__ */ new Map();
1020
+ const tick = async () => {
1021
+ const recs = await listRecords(keyword, opts);
1022
+ const cur = await Promise.all(recs.map(async (r) => {
1023
+ const { state, question } = await deriveLiveState(r);
1024
+ return {
1025
+ pid: r.pid,
1026
+ cli: r.cli,
1027
+ cwd: r.cwd,
1028
+ state,
1029
+ question
1030
+ };
1031
+ }));
1032
+ const { events, next } = diffLsStates(prev, cur, Date.now());
1033
+ for (const e of events) process.stdout.write(JSON.stringify(e) + "\n");
1034
+ prev = next;
1035
+ };
1036
+ await tick();
1037
+ await new Promise((resolve) => {
1038
+ const timer = setInterval(() => {
1039
+ tick();
1040
+ }, intervalMs);
1041
+ process.on("SIGINT", () => {
1042
+ clearInterval(timer);
1043
+ resolve();
1044
+ });
1045
+ });
1046
+ return 0;
1047
+ }
687
1048
  const records = await listRecords(keyword, opts);
688
1049
  if (opts.json) {
689
- process.stdout.write(JSON.stringify(records, null, 2) + "\n");
1050
+ const enriched = await Promise.all(records.map(async (r) => ({
1051
+ ...r,
1052
+ ...await deriveLiveState(r)
1053
+ })));
1054
+ process.stdout.write(JSON.stringify(enriched, null, 2) + "\n");
690
1055
  return 0;
691
1056
  }
692
1057
  if (records.length === 0) {
@@ -704,22 +1069,22 @@ async function cmdLs(rest) {
704
1069
  };
705
1070
  const fixedWidth = widths.pid + widths.cli + widths.status + widths.age + widths.cwd + 10;
706
1071
  const promptBudget = Math.max(20, termWidth - fixedWidth - 1);
1072
+ const forestRows = flattenForest(buildAgentForest(records));
707
1073
  const notes = await readNotes();
708
- const rows = await Promise.all(records.map(async (r) => {
709
- let displayStatus;
710
- if (!isPidAlive(r.pid)) displayStatus = "stopped";
711
- else if (r.log_file) {
712
- const mtime = await stat(r.log_file).then((s) => s.mtimeMs).catch(() => null);
713
- displayStatus = mtime !== null && Date.now() - mtime > IDLE_THRESHOLD_MS ? "idle" : "active";
714
- } else displayStatus = "active";
1074
+ const rows = await Promise.all(forestRows.map(async ({ record: r, prefix }) => {
1075
+ const displayStatus = (await deriveLiveState(r)).state;
715
1076
  const note = notes.get(r.pid);
1077
+ const tasks = displayStatus !== "stopped" && r.log_file ? await extractTaskCounts(r.log_file) : null;
1078
+ const badge = tasks ? `${tasks.done}/${tasks.total} ` : "";
1079
+ const budget = Math.max(8, promptBudget - prefix.length - badge.length);
716
1080
  let label;
717
1081
  let hasNote = false;
718
1082
  if (note) {
719
- label = truncate(note, promptBudget);
1083
+ label = truncate(note, budget);
720
1084
  hasNote = true;
721
- } else if (r.log_file && displayStatus !== "stopped") label = truncate(await extractActivity(r.log_file) ?? (r.prompt ? `→ ${r.prompt}` : ""), promptBudget);
722
- else label = truncate(r.prompt ? `→ ${r.prompt}` : "", promptBudget);
1085
+ } else if (r.log_file && displayStatus !== "stopped") label = truncate(await extractActivity(r.log_file) ?? (r.prompt ? `→ ${r.prompt}` : ""), budget);
1086
+ else label = truncate(r.prompt ? `→ ${r.prompt}` : "", budget);
1087
+ label = prefix + (hasNote ? "* " : "") + badge + label;
723
1088
  return {
724
1089
  pid: String(r.pid),
725
1090
  cli: r.cli,
@@ -746,16 +1111,16 @@ async function cmdLs(rest) {
746
1111
  r.status.padEnd(widths.status),
747
1112
  r.age.padEnd(widths.age),
748
1113
  r.cwd.padEnd(widths.cwd),
749
- r.hasNote ? `* ${r.label}` : r.label
1114
+ r.label
750
1115
  ].join(" ") + "\n");
751
1116
  if (!opts.json && rows.length > 0) {
752
1117
  const alive = rows.find((r) => r._alive);
753
1118
  const stopped = rows.find((r) => !r._alive);
754
1119
  const hints = ["\n"];
755
1120
  if (alive) {
756
- hints.push(` ay status ${alive.pid} # JSON status snapshot\n`);
757
- hints.push(` ay status ${alive.pid} --watch # stream changes as JSON\n`);
758
- hints.push(` ay status ${alive.pid} --wait-idle # block until state == idle\n`);
1121
+ hints.push(` ay status ${alive.pid} # JSON status snapshot (+ question)\n`);
1122
+ hints.push(` ay status ${alive.pid} --watch # stream state changes as JSON\n`);
1123
+ hints.push(` ay status ${alive.pid} --wait # block until it needs you (needs_input|idle|stopped)\n`);
759
1124
  hints.push(` ay tail ${alive.pid} # view latest output\n`);
760
1125
  hints.push(` ay tail -f ${alive.pid} # follow live output\n`);
761
1126
  hints.push(` ay send ${alive.pid} "next: ..." # send a prompt (keyword: pid, cwd, or prompt substring)\n`);
@@ -1095,6 +1460,91 @@ async function extractActivity(logPath) {
1095
1460
  return null;
1096
1461
  }
1097
1462
  }
1463
+ /**
1464
+ * Extract the agent's current task progress ({done,total}) from its rendered TUI
1465
+ * screen — works for every CLI since the source is the drawn todo block, not a
1466
+ * CLI-specific session file. Reads a generous tail (the latest todo block can be
1467
+ * scrolled well back from the very last lines), renders the whole window through
1468
+ * xterm so reflow/redraw frames collapse to coherent text, then scans for the
1469
+ * most recent ⎿-anchored block. Returns null when none is confidently detected.
1470
+ */
1471
+ async function extractTaskCounts(logPath) {
1472
+ const TAIL_BYTES = 256 * 1024;
1473
+ let buf;
1474
+ try {
1475
+ const fh = await open(logPath, "r");
1476
+ try {
1477
+ const { size } = await fh.stat();
1478
+ if (size === 0) return null;
1479
+ if (size <= TAIL_BYTES) {
1480
+ const data = await fh.readFile();
1481
+ buf = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1482
+ } else {
1483
+ const tmp = Buffer.alloc(TAIL_BYTES);
1484
+ const { bytesRead } = await fh.read(tmp, 0, TAIL_BYTES, size - TAIL_BYTES);
1485
+ buf = new Uint8Array(tmp.buffer, 0, bytesRead);
1486
+ }
1487
+ } finally {
1488
+ await fh.close();
1489
+ }
1490
+ } catch {
1491
+ return null;
1492
+ }
1493
+ try {
1494
+ return parseTaskCounts((await renderRawLog(buf, {
1495
+ mode: "cat",
1496
+ n: 0
1497
+ })).split("\n"));
1498
+ } catch {
1499
+ return null;
1500
+ }
1501
+ }
1502
+ let _cliDefaults = null;
1503
+ function cliDefaults() {
1504
+ return _cliDefaults ??= loadSharedCliDefaults().catch(() => ({}));
1505
+ }
1506
+ /**
1507
+ * Detect whether the agent is blocked on an interactive selection menu it didn't
1508
+ * auto-resolve (state `needs_input`). Reads the same 32 KB tail as extractActivity
1509
+ * and renders it through xterm, then runs the CLI's `needsInput`/`working`
1510
+ * patterns. Returns null when no menu is detected (or the CLI defines none).
1511
+ */
1512
+ async function extractNeedsInput(logPath, cli) {
1513
+ const cfg = (await cliDefaults())[cli];
1514
+ if (!cfg?.needsInput?.length) return null;
1515
+ const TAIL_BYTES = 32 * 1024;
1516
+ let buf;
1517
+ try {
1518
+ const fh = await open(logPath, "r");
1519
+ try {
1520
+ const { size } = await fh.stat();
1521
+ if (size === 0) return null;
1522
+ if (size <= TAIL_BYTES) {
1523
+ const data = await fh.readFile();
1524
+ buf = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
1525
+ } else {
1526
+ const tmp = Buffer.alloc(TAIL_BYTES);
1527
+ const { bytesRead } = await fh.read(tmp, 0, TAIL_BYTES, size - TAIL_BYTES);
1528
+ buf = new Uint8Array(tmp.buffer, 0, bytesRead);
1529
+ }
1530
+ } finally {
1531
+ await fh.close();
1532
+ }
1533
+ } catch {
1534
+ return null;
1535
+ }
1536
+ try {
1537
+ return classifyNeedsInput((await renderRawLog(buf, {
1538
+ mode: "tail",
1539
+ n: 40
1540
+ })).split("\n"), {
1541
+ needsInput: cfg.needsInput,
1542
+ working: cfg.working
1543
+ });
1544
+ } catch {
1545
+ return null;
1546
+ }
1547
+ }
1098
1548
  function extractActivityFromLines(lines) {
1099
1549
  const isChrome = (l) => {
1100
1550
  const s = l.trim();
@@ -1261,8 +1711,8 @@ async function writeToIpc(ipcPath, payload) {
1261
1711
  });
1262
1712
  });
1263
1713
  } else {
1264
- const { openSync, writeFileSync, closeSync } = await import("fs");
1265
- const fd = openSync(ipcPath, "w");
1714
+ const { openSync, writeFileSync, closeSync, constants } = await import("fs");
1715
+ const fd = openSync(ipcPath, constants.O_WRONLY | constants.O_NONBLOCK);
1266
1716
  try {
1267
1717
  writeFileSync(fd, payload);
1268
1718
  } finally {
@@ -1297,6 +1747,14 @@ async function cmdStop(rest) {
1297
1747
  const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
1298
1748
  if (!keyword) throw new Error("usage: ay stop <keyword> [--method=auto|graceful|double-ctrl-c]");
1299
1749
  const record = await resolveOne(keyword, opts);
1750
+ if (!isPidAlive(record.pid)) {
1751
+ await updateGlobalPidStatus(record.pid, {
1752
+ status: "exited",
1753
+ exit_reason: "already-stopped"
1754
+ }).catch(() => {});
1755
+ process.stdout.write(`pid ${record.pid} (${record.cli}) already stopped — marked exited\n`);
1756
+ return 0;
1757
+ }
1300
1758
  if (!record.fifo_file) throw new Error(`pid ${record.pid}: no fifo_file — cannot send shutdown command`);
1301
1759
  const method = String(argv.method).toLowerCase();
1302
1760
  const graceful = GRACEFUL_EXIT_COMMANDS[record.cli];
@@ -1572,6 +2030,14 @@ async function snapshotStatus(record) {
1572
2030
  state = logMtimeMs !== null && Date.now() - logMtimeMs > IDLE_THRESHOLD_MS ? "idle" : "active";
1573
2031
  } else state = "active";
1574
2032
  const activity = state !== "stopped" && record.log_file ? await extractActivity(record.log_file) : null;
2033
+ let question = null;
2034
+ if (state !== "stopped" && record.log_file) {
2035
+ const ni = await extractNeedsInput(record.log_file, record.cli);
2036
+ if (ni) {
2037
+ state = "needs_input";
2038
+ question = ni.question;
2039
+ }
2040
+ }
1575
2041
  const note = (await readNotes()).get(record.pid) ?? null;
1576
2042
  return {
1577
2043
  pid: record.pid,
@@ -1579,6 +2045,7 @@ async function snapshotStatus(record) {
1579
2045
  cwd: record.cwd,
1580
2046
  state,
1581
2047
  activity,
2048
+ question,
1582
2049
  note,
1583
2050
  log_mtime_ms: logMtimeMs,
1584
2051
  started_at: record.started_at,
@@ -1594,13 +2061,17 @@ async function cmdStatus(rest) {
1594
2061
  type: "boolean",
1595
2062
  default: false,
1596
2063
  description: "Stream changes as JSON"
2064
+ }).option("wait", {
2065
+ type: "boolean",
2066
+ default: false,
2067
+ description: "Block until the agent needs attention (needs_input | idle | stopped), then emit it. Exit 0 reached, 2 timeout. The JSON `state` says which — this is the primitive an orchestrator wants: it returns on a blocking question, not just on done."
1597
2068
  }).option("wait-idle", {
1598
2069
  type: "boolean",
1599
2070
  default: false,
1600
- description: "Block until state == idle. Exit 0 idle, 1 stopped, 2 timeout"
2071
+ description: "Block until state == idle. Exit 0 idle, 1 stopped, 2 timeout. Does NOT return on needs_input (a blocked menu) — use --wait for that."
1601
2072
  }).option("timeout", {
1602
2073
  type: "string",
1603
- description: "Timeout for --wait-idle (e.g. 30s, 5m). Default: no timeout"
2074
+ description: "Timeout for --wait/--wait-idle (e.g. 30s, 5m). Default: no timeout"
1604
2075
  }).option("interval", {
1605
2076
  type: "number",
1606
2077
  default: 2,
@@ -1621,12 +2092,13 @@ async function cmdStatus(rest) {
1621
2092
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
1622
2093
  };
1623
2094
  const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
1624
- if (!keyword) throw new Error("usage: ay status <keyword> [--watch | --wait-idle] [--timeout=Ns]");
2095
+ if (!keyword) throw new Error("usage: ay status <keyword> [--watch | --wait | --wait-idle] [--timeout=Ns]");
1625
2096
  {
1626
2097
  const remote = await resolveRemoteSpec(keyword);
1627
2098
  if (remote) return runRemoteStatus(remote);
1628
2099
  }
1629
2100
  const watch = argv.watch;
2101
+ const wait = argv.wait;
1630
2102
  const waitIdle = argv["wait-idle"];
1631
2103
  const intervalFlag = argv.interval;
1632
2104
  const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1e3);
@@ -1640,6 +2112,21 @@ async function cmdStatus(rest) {
1640
2112
  } : snap;
1641
2113
  process.stdout.write(JSON.stringify(out) + "\n");
1642
2114
  };
2115
+ if (wait) {
2116
+ const startedAt = Date.now();
2117
+ for (;;) {
2118
+ const snap = await snapshotStatus(record);
2119
+ if (snap.state === "needs_input" || snap.state === "idle" || snap.state === "stopped") {
2120
+ emit(snap);
2121
+ return 0;
2122
+ }
2123
+ if (timeoutMs !== null && Date.now() - startedAt >= timeoutMs) {
2124
+ emit(snap);
2125
+ return 2;
2126
+ }
2127
+ await new Promise((r) => setTimeout(r, intervalMs));
2128
+ }
2129
+ }
1643
2130
  if (waitIdle) {
1644
2131
  const startedAt = Date.now();
1645
2132
  for (;;) {
@@ -1667,11 +2154,12 @@ async function cmdStatus(rest) {
1667
2154
  let prev = null;
1668
2155
  const tick = async () => {
1669
2156
  const snap = await snapshotStatus(record);
1670
- if (prev === null || snap.state !== prev.state || snap.activity !== prev.activity || snap.exit_code !== prev.exit_code) {
2157
+ if (prev === null || snap.state !== prev.state || snap.activity !== prev.activity || snap.question !== prev.question || snap.exit_code !== prev.exit_code) {
1671
2158
  emit(snap, Date.now());
1672
2159
  prev = {
1673
2160
  state: snap.state,
1674
2161
  activity: snap.activity,
2162
+ question: snap.question,
1675
2163
  exit_code: snap.exit_code
1676
2164
  };
1677
2165
  }
@@ -1686,7 +2174,134 @@ async function cmdStatus(rest) {
1686
2174
  });
1687
2175
  return 0;
1688
2176
  }
2177
+ /** Read all of stdin as a UTF-8 string (for `ay result set -` / piped JSON). */
2178
+ async function readStdin() {
2179
+ const chunks = [];
2180
+ for await (const chunk of process.stdin) chunks.push(chunk);
2181
+ return Buffer.concat(chunks).toString("utf8");
2182
+ }
2183
+ /** Load a persisted result envelope, or null if none has been deposited yet. */
2184
+ async function loadStoredResult(pid) {
2185
+ try {
2186
+ const raw = await readFile(resultPath(pid), "utf8");
2187
+ return JSON.parse(raw);
2188
+ } catch {
2189
+ return null;
2190
+ }
2191
+ }
2192
+ /**
2193
+ * `ay result` — two modes:
2194
+ *
2195
+ * ay result set ['<json>' | -] write side, run BY the agent. Keyed off the
2196
+ * injected AGENT_YES_PID (or --pid N). Stores
2197
+ * the envelope to ~/.agent-yes/results/<pid>.json.
2198
+ *
2199
+ * ay result <keyword> [--wait] read side, run by the parent. Resolves the
2200
+ * agent and emits the stored envelope as JSON.
2201
+ *
2202
+ * Read-side exit codes (so an orchestrator can branch without parsing):
2203
+ * 0 envelope found and emitted
2204
+ * 1 agent stopped WITHOUT depositing one (it's done; there's no result)
2205
+ * 2 no envelope yet AND agent is still alive (pending) / --wait timed out
2206
+ */
2207
+ async function cmdResult(rest) {
2208
+ if (rest[0] === "set") return await cmdResultSet(rest.slice(1));
2209
+ const argv = await yargs(rest).usage("Usage: ay result <keyword> [--wait] [--timeout Ns]").option("wait", {
2210
+ type: "boolean",
2211
+ default: false,
2212
+ description: "Block until the agent deposits its result envelope (exit 0), or exits without one (exit 1), or --timeout elapses (exit 2)."
2213
+ }).option("timeout", {
2214
+ type: "string",
2215
+ description: "Timeout for --wait (e.g. 30s, 5m)"
2216
+ }).option("interval", {
2217
+ type: "number",
2218
+ default: 2,
2219
+ description: "Poll interval in seconds"
2220
+ }).option("latest", {
2221
+ type: "boolean",
2222
+ default: false,
2223
+ description: "Use most recent match"
2224
+ }).option("cwd", {
2225
+ type: "string",
2226
+ description: "Restrict to agents under this dir"
2227
+ }).help(false).version(false).exitProcess(false).parseAsync();
2228
+ const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
2229
+ if (!keyword) throw new Error("usage: ay result <keyword> [--wait] | ay result set '<json>'");
2230
+ const record = await resolveOne(keyword, {
2231
+ all: true,
2232
+ active: false,
2233
+ json: false,
2234
+ latest: argv.latest,
2235
+ cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
2236
+ });
2237
+ const intervalMs = Math.max(500, (Number.isFinite(argv.interval) ? argv.interval : 2) * 1e3);
2238
+ const timeoutMs = typeof argv.timeout === "string" && argv.timeout.length > 0 ? ms(argv.timeout) ?? NaN : null;
2239
+ if (timeoutMs !== null && !Number.isFinite(timeoutMs)) throw new Error(`invalid --timeout value: ${argv.timeout}`);
2240
+ const emitFound = (stored) => {
2241
+ process.stdout.write(JSON.stringify({
2242
+ pid: record.pid,
2243
+ cli: record.cli,
2244
+ cwd: record.cwd,
2245
+ found: true,
2246
+ written_at: stored.written_at,
2247
+ result: stored.result
2248
+ }) + "\n");
2249
+ };
2250
+ const emitMissing = (state) => {
2251
+ process.stdout.write(JSON.stringify({
2252
+ pid: record.pid,
2253
+ cli: record.cli,
2254
+ cwd: record.cwd,
2255
+ found: false,
2256
+ state
2257
+ }) + "\n");
2258
+ };
2259
+ const startedAt = Date.now();
2260
+ for (;;) {
2261
+ const stored = await loadStoredResult(record.pid);
2262
+ if (stored) {
2263
+ emitFound(stored);
2264
+ return 0;
2265
+ }
2266
+ const snap = await snapshotStatus(record);
2267
+ if (snap.state === "stopped") {
2268
+ const last = await loadStoredResult(record.pid);
2269
+ if (last) {
2270
+ emitFound(last);
2271
+ return 0;
2272
+ }
2273
+ emitMissing("stopped");
2274
+ return 1;
2275
+ }
2276
+ if (!argv.wait) {
2277
+ emitMissing(snap.state);
2278
+ return 2;
2279
+ }
2280
+ if (timeoutMs !== null && Date.now() - startedAt >= timeoutMs) {
2281
+ emitMissing(snap.state);
2282
+ return 2;
2283
+ }
2284
+ await new Promise((r) => setTimeout(r, intervalMs));
2285
+ }
2286
+ }
2287
+ /** `ay result set [<json> | -]` — deposit THIS agent's envelope. */
2288
+ async function cmdResultSet(rest) {
2289
+ const argv = await yargs(rest).usage("Usage: ay result set ['<json>' | -]").option("pid", {
2290
+ type: "number",
2291
+ description: "Target pid (default: $AGENT_YES_PID — the agent's own wrapper)"
2292
+ }).help(false).version(false).exitProcess(false).parseAsync();
2293
+ const pid = Number.isFinite(argv.pid) ? Number(argv.pid) : Number(process.env.AGENT_YES_PID);
2294
+ if (!Number.isFinite(pid) || pid <= 0) throw new Error("ay result set: no target pid — run inside an ay-managed agent (AGENT_YES_PID is set) or pass --pid");
2295
+ const positional = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
2296
+ const result = normalizeEnvelope(positional !== void 0 && positional !== "-" ? positional : await readStdin());
2297
+ if (result === null) throw new Error("ay result set: empty input — pass a JSON object, text, or pipe via stdin");
2298
+ await mkdir(resultsDir(), { recursive: true });
2299
+ const stored = buildStoredResult(pid, result, Date.now());
2300
+ await writeFile(resultPath(pid), JSON.stringify(stored) + "\n");
2301
+ process.stdout.write(`result envelope written for pid ${pid}\n`);
2302
+ return 0;
2303
+ }
1689
2304
 
1690
2305
  //#endregion
1691
- export { finalizedLines as a, listRecords as c, renderRawLog as d, resolveOne as f, writeToIpc as g, stopTipForCli as h, cursorAbs as i, matchKeyword as l, snapshotStatus as m, cmdHelp as n, isPidAlive as o, runSubcommand as p, controlCodeFromName as r, isSubcommand as s, GRACEFUL_EXIT_COMMANDS as t, readNotes as u };
1692
- //# sourceMappingURL=subcommands-CQowpr1t.js.map
2306
+ export { stopTipForCli as _, extractNeedsInput as a, isPidAlive as c, matchKeyword as d, readNotes as f, snapshotStatus as g, runSubcommand as h, cursorAbs as i, isSubcommand as l, resolveOne as m, cmdHelp as n, extractTaskCounts as o, renderRawLog as p, controlCodeFromName as r, finalizedLines as s, GRACEFUL_EXIT_COMMANDS as t, listRecords as u, writeToIpc as v };
2307
+ //# sourceMappingURL=subcommands-ClVHy-xI.js.map