agent-yes 1.267.2 → 1.269.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.
Files changed (108) hide show
  1. package/README.md +73 -0
  2. package/dist/{SUPPORTED_CLIS-CpWKDCtL.js → SUPPORTED_CLIS-Bne04-66.js} +2 -2
  3. package/dist/SUPPORTED_CLIS-CdyjhpZP.js +12 -0
  4. package/dist/{agent-yes.config-DzNini0E.js → agent-yes.config-2xKNJM5Y.js} +2 -2
  5. package/dist/{agentShare-D0reU6LW.js → agentShare-BI126Ej9.js} +8 -8
  6. package/dist/askCli-Bbxt9Umz.js +272 -0
  7. package/dist/{browser-DSduVZeZ.js → browser-CorYp8Yu.js} +1 -1
  8. package/dist/{browser-B7eYrBy7.js → browser-_2ndf80G.js} +5 -5
  9. package/dist/callback-BnUbjH7p.js +13 -0
  10. package/dist/{callback-Dlqwh9n7.js → callback-DI2r3NrV.js} +5 -5
  11. package/dist/{callbackCore-K99x3C75.js → callbackCore-Bbe7rpN2.js} +1 -1
  12. package/dist/{callbackCore-DcptaZwH.js → callbackCore-C0Cg9RbS.js} +1 -1
  13. package/dist/{channels-DQaZbare.js → channels-3gz9gHP7.js} +6 -6
  14. package/dist/{channels-yE1YUHCl.js → channels-CkaCyvEp.js} +2 -2
  15. package/dist/channels.js +5 -5
  16. package/dist/cli.js +11 -11
  17. package/dist/{cmdPs-D8gsRFVV.js → cmdPs-CVnlflxP.js} +7 -7
  18. package/dist/{configShared-DtHMBCl7.js → configShared-DYxlvrH0.js} +1 -1
  19. package/dist/{cwdConflictWarn-BpweoAiy.js → cwdConflictWarn-Buy3r_qc.js} +1 -1
  20. package/dist/{cwdPassthroughHint-BlRuOGo_.js → cwdPassthroughHint-BlWO-h5w.js} +1 -1
  21. package/dist/{e2e-CUMZC53I.js → e2e-OLfcNxiv.js} +1 -1
  22. package/dist/{expose-DyiZ5AU0.js → expose-Bfo-dZdy.js} +1 -1
  23. package/dist/{forkNested-B_4c-TQk.js → forkNested-jc2ER9xn.js} +1 -1
  24. package/dist/{hist-BoZPJ2ah.js → hist-DiOYRXuD.js} +1 -1
  25. package/dist/{html2canvas-pro.esm-cq4l_ZIw.js → html2canvas-pro.esm-D19oTQeV.js} +1 -1
  26. package/dist/identity-BmDYOuZE.js +3 -0
  27. package/dist/identity-CLaJ9-uE.js +109 -0
  28. package/dist/index.js +4 -3
  29. package/dist/{initMsg-D8cmdY76.js → initMsg-Bat5PUDf.js} +1 -1
  30. package/dist/{initMsg-BKqWDYFk.js → initMsg-CTAIqBWF.js} +8 -4
  31. package/dist/{needsInput-103zhZ90.js → needsInput-DhfIwxse.js} +1 -1
  32. package/dist/{nodeRuntime-QihGFQbI.js → nodeRuntime-CKc1BN5J.js} +1 -1
  33. package/dist/{notifyDaemon-TKtSYtEO.js → notifyDaemon-Cf4KHDOr.js} +7 -7
  34. package/dist/{notifyStore-CEDiVyis.js → notifyStore-0hlFPa6z.js} +1 -1
  35. package/dist/{notifyStore-Cbw2ZJ0X.js → notifyStore-Dus2D1XA.js} +1 -1
  36. package/dist/{openBrowser-Cs46NiH6.js → openBrowser-Dmg2Oi4q.js} +1 -1
  37. package/dist/{parentLink-CTgZZCgw.js → parentLink-BwX4jaGD.js} +1 -1
  38. package/dist/{parentLink-CQrn998L.js → parentLink-DS-XflaJ.js} +1 -1
  39. package/dist/{parentPingLoop-BEvJ2ElR.js → parentPingLoop-Bxd3dXWO.js} +6 -6
  40. package/dist/{peer-DIl5xtx8.js → peer-Dr4zvtcd.js} +3 -3
  41. package/dist/peer-u1AUAZeA.js +4 -0
  42. package/dist/{remotes-M8FbM92D.js → remotes-BiftUp09.js} +3 -3
  43. package/dist/{remotes-DdAfueOu.js → remotes-IzQHupeo.js} +3 -3
  44. package/dist/{rustBinary-B9FEbqTu.js → rustBinary-B2lNFJSe.js} +2 -2
  45. package/dist/{schedule-Bmh5uaDr.js → schedule-D1CNvuKT.js} +8 -7
  46. package/dist/{serve-BqRNIxeq.js → serve-BaHCO0bU.js} +49 -42
  47. package/dist/{setup-GYkQYxlf.js → setup-B8EL0SWL.js} +4 -4
  48. package/dist/{share-Db_zb6gH.js → share-DTPZgbfH.js} +2 -2
  49. package/dist/{share-NUhi-Wdd.js → share-RgJUhzqG.js} +2 -2
  50. package/dist/{spawnGate-CZlMs6uV.js → spawnGate-C6YKlnmb.js} +1 -1
  51. package/dist/{spawnGate-DN4wNXHq.js → spawnGate-CS3oFdJ9.js} +2 -2
  52. package/dist/{store-BW5NBouz.js → store-C7I2CAu9.js} +1 -1
  53. package/dist/{subcommands-DJpVXIb6.js → subcommands-03ghe-aZ.js} +192 -157
  54. package/dist/{subcommands-DLrJfG4D.js → subcommands-BS4lnTrb.js} +6 -6
  55. package/dist/{systemPathLink-BHv1I6wh.js → systemPathLink-DGVcx0fg.js} +1 -1
  56. package/dist/{terminal-CRLPUaKF.js → terminal-DrCP-200.js} +2 -2
  57. package/dist/terminal.js +1 -1
  58. package/dist/todoAutomation-BETGOik9.js +302 -0
  59. package/dist/{todoCli-BYGZ7_C5.js → todoCli-DUoxA1dh.js} +164 -37
  60. package/dist/todoIdentity-BffBxRHA.js +138 -0
  61. package/dist/{todoAutomation-BQRW614S.js → todoStore-Wk8U4zJ8.js} +63 -280
  62. package/dist/{tray-CzGs9-ch.js → tray-Bxq_sfvU.js} +1 -1
  63. package/dist/trayApp-BDMzlxRo.js +5 -0
  64. package/dist/{trayApp-DLMUu2sB.js → trayApp-DC4qjut7.js} +2 -2
  65. package/dist/{ts-ClwhahH9.js → ts-D0Pk-WJg.js} +14 -8
  66. package/dist/{versionChecker-ZQYirfMp.js → versionChecker-V6ceVsu9.js} +2 -2
  67. package/dist/{webrtcLink-DyCj380i.js → webrtcLink-B72AQ2eb.js} +2 -2
  68. package/dist/{webrtcRemote-Bl8eowma.js → webrtcRemote-D68dkqFp.js} +3 -3
  69. package/dist/{widget-DtZ1WXkf.js → widget-BM03OkC1.js} +3 -3
  70. package/dist/widget.js +1 -1
  71. package/dist/widgets.js +5 -5
  72. package/dist/{workspaceConfig-CBKlgA_q.js → workspaceConfig-EK7XNsgR.js} +1 -1
  73. package/dist/{ws-CiR2nOD3.js → ws-6l7Wb_0r.js} +7 -7
  74. package/dist/{ws-Ba372Tym.js → ws-pRFrqms9.js} +3 -3
  75. package/lab/ui/console-logic.js +64 -0
  76. package/lab/ui/index.html +58 -4
  77. package/package.json +1 -1
  78. package/ts/askCli.spec.ts +304 -0
  79. package/ts/askCli.ts +378 -0
  80. package/ts/identity.spec.ts +80 -1
  81. package/ts/identity.ts +16 -9
  82. package/ts/index.ts +6 -1
  83. package/ts/initMsg.spec.ts +22 -14
  84. package/ts/initMsg.ts +7 -2
  85. package/ts/ipcLock.spec.ts +107 -0
  86. package/ts/ipcLock.ts +130 -0
  87. package/ts/serve.ts +68 -17
  88. package/ts/subcommandMirror.spec.ts +76 -0
  89. package/ts/subcommands.spec.ts +115 -10
  90. package/ts/subcommands.ts +108 -36
  91. package/ts/todoAutomation.spec.ts +64 -0
  92. package/ts/todoAutomation.ts +25 -2
  93. package/ts/todoBlock.spec.ts +31 -1
  94. package/ts/todoBlock.ts +33 -1
  95. package/ts/todoCli.spec.ts +199 -4
  96. package/ts/todoCli.ts +266 -42
  97. package/ts/todoIdentity.spec.ts +85 -0
  98. package/ts/todoIdentity.ts +76 -0
  99. package/ts/todoLifecycle.spec.ts +13 -1
  100. package/ts/todoLifecycle.ts +35 -1
  101. package/ts/todoRoot.spec.ts +117 -0
  102. package/ts/todoRoot.ts +90 -0
  103. package/ts/todoStore.spec.ts +25 -0
  104. package/ts/todoStore.ts +36 -2
  105. package/dist/SUPPORTED_CLIS-DDs0xUvV.js +0 -11
  106. package/dist/callback-Bgehh-IW.js +0 -13
  107. package/dist/peer-Cvntlw1i.js +0 -4
  108. package/dist/trayApp-CoD89Y1_.js +0 -5
@@ -2,121 +2,22 @@ import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
2
2
  import { t as agentYesHome } from "./agentYesHome-CtmsY6I-.js";
3
3
  import { a as updateGlobalPidStatus, i as readGlobalPids } from "./globalPidIndex-Dq4KY2IV.js";
4
4
  import { a as recordOutbox, i as recordMessage, n as readMailbox, t as partyMatches } from "./messageLog-BYFwioFY.js";
5
- import { t as loadSharedCliDefaults } from "./configShared-DtHMBCl7.js";
6
- import { n as isWorkingScreen, r as parseMenu, t as classifyNeedsInput } from "./needsInput-103zhZ90.js";
7
- import { S as postmortemStartedAt, _ as filterSinceSeq, a as getCursor, b as maxSeq, f as readInbox, o as heartbeatWatcher, p as setCursor, r as clearWatcher, s as hostId, v as filterSinceTs, y as filterUnread } from "./notifyStore-Cbw2ZJ0X.js";
8
- import { n as isWebrtcSpec } from "./webrtcLink-DyCj380i.js";
9
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-DdAfueOu.js";
5
+ import { t as loadSharedCliDefaults } from "./configShared-DYxlvrH0.js";
6
+ import { t as formatIdentity } from "./identity-CLaJ9-uE.js";
7
+ import { n as isWorkingScreen, r as parseMenu, t as classifyNeedsInput } from "./needsInput-DhfIwxse.js";
8
+ import { S as postmortemStartedAt, _ as filterSinceSeq, a as getCursor, b as maxSeq, f as readInbox, o as heartbeatWatcher, p as setCursor, r as clearWatcher, s as hostId, v as filterSinceTs, y as filterUnread } from "./notifyStore-Dus2D1XA.js";
9
+ import { n as isWebrtcSpec } from "./webrtcLink-B72AQ2eb.js";
10
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-IzQHupeo.js";
10
11
  import { spawn } from "child_process";
11
12
  import ms from "ms";
12
13
  import yargs from "yargs";
13
14
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
14
15
  import { homedir } from "os";
15
16
  import path from "path";
16
- import { readFileSync } from "node:fs";
17
- import path$1 from "node:path";
18
- import { homedir as homedir$1, hostname as hostname$1, userInfo as userInfo$1 } from "node:os";
17
+ import { lock } from "proper-lockfile";
19
18
  import { fileURLToPath } from "node:url";
20
19
  import { randomBytes } from "crypto";
21
20
 
22
- //#region ts/identity.ts
23
- /**
24
- * The standardized agent identity string:
25
- *
26
- * <username>@<hostname>:<path>:<branch>#<pid>
27
- * e.g. sno@Mac:~/ws/symval/symval/tree/crm:main#30402
28
- *
29
- * One line answers "who is this agent" across every surface that names one —
30
- * the `ay send` envelope header, `ay whoami`, logs. The branch segment usually
31
- * carries the lane name for free (worktree checkouts like `crm-yamamoto-wifi`
32
- * or `fix/t173-tone-core` name their purpose), which is why there is no
33
- * separate role concept.
34
- *
35
- * Parsing (right-to-left, for consumers that need it): `#<pid>` from the end;
36
- * the branch is after the last `:` (git forbids `:` in ref names); the
37
- * username is before the first `@`; the hostname is between that `@` and the
38
- * next `:` (hostnames contain no `:`). The path in the middle may itself
39
- * contain `@`/`:` (Windows drives), which is why parsing anchors on the ends.
40
- *
41
- * Every token is clamped before rendering: the identity is embedded in the
42
- * `<ay-msg …>` envelope's OPEN tag, which is not nonce-protected — whitespace
43
- * or `>` in a token could forge header structure.
44
- */
45
- /** Replace anything outside a conservative charset so a token can never break
46
- * out of the envelope header line. Collapses runs to a single `-`. */
47
- function safeToken(raw, max) {
48
- return raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max);
49
- }
50
- /** The local username, header-safe ("John Smith" → "John-Smith"). */
51
- function localUser() {
52
- try {
53
- return safeToken(userInfo$1().username, 32) || "unknown";
54
- } catch {
55
- return "unknown";
56
- }
57
- }
58
- /** The local hostname's first label ("Macs-MBP.local" → "Macs-MBP"). */
59
- function localHost() {
60
- return safeToken(hostname$1().split(".")[0] ?? "", 32) || "unknown";
61
- }
62
- /**
63
- * The checked-out branch of the git repo containing `cwd`, or a short commit
64
- * id when detached, or null when `cwd` is not inside a git checkout. Pure
65
- * file reads — never spawns git — so it is cheap enough to run on every send:
66
- * walk up to the nearest `.git`, follow a worktree's `gitdir:` indirection
67
- * file, then parse `HEAD`.
68
- */
69
- function readGitBranch(cwd) {
70
- let dir = path$1.resolve(cwd);
71
- for (let depth = 0; depth < 32; depth++) {
72
- const dotGit = path$1.join(dir, ".git");
73
- let gitDir = null;
74
- try {
75
- const content = readFileSync(dotGit, "utf8");
76
- const m = /^gitdir:\s*(.+)\s*$/m.exec(content);
77
- gitDir = m ? path$1.resolve(dir, m[1].trim()) : null;
78
- } catch (err) {
79
- if (err?.code === "EISDIR") gitDir = dotGit;
80
- }
81
- if (gitDir) try {
82
- const head = readFileSync(path$1.join(gitDir, "HEAD"), "utf8").trim();
83
- const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
84
- if (ref) return safeBranch(ref[1]);
85
- if (/^[0-9a-f]{40}$/i.test(head)) return head.slice(0, 12);
86
- return null;
87
- } catch {
88
- return null;
89
- }
90
- const parent = path$1.dirname(dir);
91
- if (parent === dir) return null;
92
- dir = parent;
93
- }
94
- return null;
95
- }
96
- /** Branch names never contain `:`/space/`~`/`^` (git forbids them), but they
97
- * DO allow `/` and `#` — keep `/` (readable, unambiguous: parsing anchors on
98
- * `#<pid>` at the end) and clamp everything else defensively. */
99
- function safeBranch(raw) {
100
- return raw.replace(/[^A-Za-z0-9._/#-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
101
- }
102
- /** Abbreviate the home directory to `~`, mirroring how `ay ls` prints cwds. */
103
- function tildify(p) {
104
- const home = homedir$1();
105
- return p.startsWith(home) ? "~" + p.slice(home.length) : p;
106
- }
107
- /**
108
- * Render the standardized identity for an agent. `user`/`host` default to the
109
- * local machine (correct for the envelope: the sending wrapper runs on the
110
- * agent's own host), `branch` auto-detects from `cwd` unless given.
111
- */
112
- function formatIdentity(parts) {
113
- const user = parts.user ?? localUser();
114
- const host = parts.host ?? localHost();
115
- const branch = parts.branch === void 0 ? readGitBranch(parts.cwd) : parts.branch;
116
- return `${user}@${host}:${tildify(parts.cwd)}${branch ? `:${branch}` : ""}#${parts.pid}`;
117
- }
118
-
119
- //#endregion
120
21
  //#region ts/agentTree.ts
121
22
  /**
122
23
  * Link records into a forest via parent_pid === wrapper_pid. Records whose
@@ -469,6 +370,117 @@ function buildStoredResult(pid, result, writtenAt) {
469
370
  };
470
371
  }
471
372
 
373
+ //#endregion
374
+ //#region ts/ipcLock.ts
375
+ /**
376
+ * Cross-process mutual exclusion for writing to ONE agent's stdin.
377
+ *
378
+ * What this guarantees is ATOMICITY, not ordering. Two independent senders
379
+ * (two console viewers, an `ay send` racing a viewer, two `ay` processes) have
380
+ * no defined arrival order at the host, and nothing here invents one —
381
+ * per-client ordering is the browser's single-in-flight send queue
382
+ * (`createInputSender` in lab/ui/console-logic.js). What this prevents is the
383
+ * two of them SPLICING into each other:
384
+ *
385
+ * - `writeToIpc` does not write a payload in one syscall. A FIFO's kernel
386
+ * buffer is small (~8KB), so it loops on EAGAIN/partial writes while the
387
+ * agent drains its stdin. POSIX only promises atomicity up to PIPE_BUF
388
+ * (512 bytes on macOS), so two concurrent writers interleave BYTES in the
389
+ * middle of each other's payloads — not messages arriving in a surprising
390
+ * order, but one message cut in half by another.
391
+ * - Several input actions are inherently multi-write: `ay send` writes the
392
+ * body, waits ~200ms for the CLI's paste handling to settle, then writes
393
+ * the Enter. `ay key` paces a key sequence 40ms apart. `ay stop` writes a
394
+ * graceful command, Enter, then Ctrl-C twice. Every gap in those is a
395
+ * window where another writer's bytes can land mid-transaction — which is
396
+ * how a keystroke ends up fused into the middle of a sent message, or a
397
+ * menu navigation gets a stray character.
398
+ *
399
+ * So the unit protected here is the TRANSACTION, not the write: callers wrap
400
+ * a whole logical action, and `writeToIpc` itself stays lock-free (and thus
401
+ * non-reentrant by construction).
402
+ *
403
+ * FAIL-OPEN is deliberate. Making input delivery depend on acquiring a lock
404
+ * would add a brand-new way for an agent to become unreachable, which is worse
405
+ * than the splicing this fixes. If the lock cannot be acquired within the
406
+ * budget, the transaction proceeds unprotected with a warning — exactly the
407
+ * behavior that existed before this module, so this can only improve on it.
408
+ */
409
+ /**
410
+ * How long a legitimate holder may hold before we give up waiting.
411
+ *
412
+ * Derived, not picked: one `writeToIpc` can legitimately take up to
413
+ * `IPC_WRITE_TIMEOUT_MS` (10s) when the agent's stdin is backed up — and that
414
+ * is precisely when splicing is most likely, so a short budget would abandon
415
+ * the guarantee in the one case that needs it. A wait this long only ever
416
+ * materialises against an agent that is already not draining, where the
417
+ * waiter's own write would be hanging anyway. Crashed holders are handled by
418
+ * `stale` below, not by this budget.
419
+ */
420
+ const ACQUIRE_BUDGET_MS = 12e3;
421
+ /**
422
+ * When to steal a lock as abandoned. Must exceed the longest LEGITIMATE hold,
423
+ * or a slow-but-alive sender gets its lock stolen mid-transaction — which
424
+ * would reintroduce exactly the splice this module prevents. The worst legal
425
+ * case is `ay send` against a wedged reader: two 10s writes plus the 200ms
426
+ * settle gap.
427
+ */
428
+ const STALE_MS = 3e4;
429
+ const RETRY_MS = 15;
430
+ /**
431
+ * Targets this process currently holds. `proper-lockfile` is not reentrant: a
432
+ * nested acquire from the same process on the same target would block until
433
+ * the budget expired, wedging input for 12s. Nesting is not expected today,
434
+ * but the cost of being wrong is "this agent accepts no input", so the guard
435
+ * is cheaper than the audit.
436
+ */
437
+ const heldByThisProcess = /* @__PURE__ */ new Set();
438
+ /** Lock target for an agent's stdin, keyed by pid.
439
+ *
440
+ * Under `$AGENT_YES_HOME`, NOT beside the FIFO: on Windows the "FIFO" is a
441
+ * named pipe (`\\.\pipe\…`), which has no containing directory to put a
442
+ * sibling lockfile in. Keying by pid also stays stable if the fifo path
443
+ * changes shape between runtimes.
444
+ */
445
+ function ipcLockTarget(pid) {
446
+ return path.join(agentYesHome(), "locks", `ipc-${pid}`);
447
+ }
448
+ /**
449
+ * Run `fn` with exclusive access to `pid`'s stdin across processes.
450
+ *
451
+ * `onFallback` is called (once) if the lock could not be acquired and `fn` is
452
+ * about to run unprotected — callers use it to warn. `fn`'s own errors
453
+ * propagate unchanged, and the lock is always released.
454
+ */
455
+ async function withIpcLock(pid, fn, onFallback) {
456
+ if (heldByThisProcess.has(pid)) return fn();
457
+ let release;
458
+ const target = ipcLockTarget(pid);
459
+ try {
460
+ await mkdir(path.dirname(target), { recursive: true });
461
+ release = await lock(target, {
462
+ lockfilePath: `${target}.lock`,
463
+ realpath: false,
464
+ stale: STALE_MS,
465
+ retries: {
466
+ retries: Math.ceil(ACQUIRE_BUDGET_MS / RETRY_MS),
467
+ minTimeout: RETRY_MS,
468
+ maxTimeout: RETRY_MS
469
+ }
470
+ });
471
+ } catch (err) {
472
+ onFallback?.(err instanceof Error ? err.message : String(err));
473
+ return fn();
474
+ }
475
+ heldByThisProcess.add(pid);
476
+ try {
477
+ return await fn();
478
+ } finally {
479
+ heldByThisProcess.delete(pid);
480
+ await release().catch(() => {});
481
+ }
482
+ }
483
+
472
484
  //#endregion
473
485
  //#region ts/subcommands.ts
474
486
  /**
@@ -789,6 +801,8 @@ const SUBCOMMANDS = new Set([
789
801
  "restart",
790
802
  "note",
791
803
  "todo",
804
+ "ask",
805
+ "answer",
792
806
  "ch",
793
807
  "channels",
794
808
  "term",
@@ -893,7 +907,7 @@ async function runSubcommand(argv) {
893
907
  switch (sub) {
894
908
  case "ls":
895
909
  case "list": return await cmdLs(rest);
896
- case "ps": return await (await import("./cmdPs-D8gsRFVV.js")).cmdPs(rest);
910
+ case "ps": return await (await import("./cmdPs-CVnlflxP.js")).cmdPs(rest);
897
911
  case "status": return await cmdStatus(rest);
898
912
  case "whoami": return await cmdWhoami(rest);
899
913
  case "result": return await cmdResult(rest);
@@ -904,7 +918,7 @@ async function runSubcommand(argv) {
904
918
  case "tail": return await cmdRead(rest, { mode: "tail" });
905
919
  case "head": return await cmdRead(rest, { mode: "head" });
906
920
  case "hist":
907
- case "history": return await (await import("./hist-BoZPJ2ah.js")).cmdHist(rest);
921
+ case "history": return await (await import("./hist-DiOYRXuD.js")).cmdHist(rest);
908
922
  case "send": return await cmdSend(rest);
909
923
  case "msgs": return await cmdMsgs(rest);
910
924
  case "key": return await cmdKey(rest);
@@ -915,57 +929,72 @@ async function runSubcommand(argv) {
915
929
  case "exit": return await cmdExit(rest);
916
930
  case "restart": return await cmdRestart(rest);
917
931
  case "note": return await cmdNote(rest);
932
+ case "ask":
933
+ case "answer": {
934
+ const { runAskSubcommand, runAnswerSubcommand } = await import("./askCli-Bbxt9Umz.js");
935
+ const deps = {
936
+ resolveAgent: (keyword) => resolveOne(keyword, {
937
+ all: true,
938
+ active: false,
939
+ json: false,
940
+ latest: false,
941
+ cwdScope: null
942
+ }),
943
+ send: cmdSend
944
+ };
945
+ return sub === "ask" ? await runAskSubcommand(rest, deps) : await runAnswerSubcommand(rest, deps);
946
+ }
918
947
  case "todo": {
919
- const { runTodoSubcommand } = await import("./todoCli-BYGZ7_C5.js");
948
+ const { runTodoSubcommand } = await import("./todoCli-DUoxA1dh.js");
920
949
  return runTodoSubcommand(rest);
921
950
  }
922
951
  case "ch":
923
952
  case "channels": {
924
- const { cmdCh } = await import("./channels-DQaZbare.js");
953
+ const { cmdCh } = await import("./channels-3gz9gHP7.js");
925
954
  return cmdCh(rest);
926
955
  }
927
956
  case "term": {
928
- const { cmdTerm } = await import("./terminal-CRLPUaKF.js");
957
+ const { cmdTerm } = await import("./terminal-DrCP-200.js");
929
958
  return cmdTerm(rest);
930
959
  }
931
960
  case "widget": {
932
- const { cmdWidget } = await import("./widget-DtZ1WXkf.js");
961
+ const { cmdWidget } = await import("./widget-BM03OkC1.js");
933
962
  return cmdWidget(rest);
934
963
  }
935
964
  case "mint": {
936
- const { cmdMint } = await import("./widget-DtZ1WXkf.js");
965
+ const { cmdMint } = await import("./widget-BM03OkC1.js");
937
966
  return cmdMint(rest);
938
967
  }
939
968
  case "serve": {
940
- const { cmdServe } = await import("./serve-BqRNIxeq.js");
969
+ const { cmdServe } = await import("./serve-BaHCO0bU.js");
941
970
  return cmdServe(rest);
942
971
  }
943
972
  case "tray": {
944
- const { cmdTray } = await import("./trayApp-CoD89Y1_.js");
973
+ const { cmdTray } = await import("./trayApp-BDMzlxRo.js");
945
974
  return cmdTray(rest);
946
975
  }
947
976
  case "setup": {
948
- const { cmdSetup } = await import("./setup-GYkQYxlf.js");
977
+ const { cmdSetup } = await import("./setup-B8EL0SWL.js");
949
978
  return cmdSetup(rest);
950
979
  }
951
980
  case "ws": {
952
- const { cmdWs } = await import("./ws-CiR2nOD3.js");
981
+ const { cmdWs } = await import("./ws-6l7Wb_0r.js");
953
982
  return cmdWs(rest);
954
983
  }
955
984
  case "schedule": {
956
- const { cmdSchedule } = await import("./schedule-Bmh5uaDr.js");
985
+ const { cmdSchedule } = await import("./schedule-D1CNvuKT.js");
957
986
  return cmdSchedule(rest);
958
987
  }
959
988
  case "remote": {
960
- const { cmdRemote } = await import("./remotes-M8FbM92D.js");
989
+ const { cmdRemote } = await import("./remotes-BiftUp09.js");
961
990
  return cmdRemote(rest);
962
991
  }
963
992
  case "expose": {
964
- const { cmdExpose } = await import("./expose-DyiZ5AU0.js");
993
+ const { cmdExpose } = await import("./expose-Bfo-dZdy.js");
965
994
  return cmdExpose(rest);
966
995
  }
967
996
  case "callback": {
968
- const { cmdCallback } = await import("./callback-Bgehh-IW.js");
997
+ const { cmdCallback } = await import("./callback-BnUbjH7p.js");
969
998
  return cmdCallback(rest);
970
999
  }
971
1000
  case "reap":
@@ -1428,7 +1457,7 @@ async function fetchRemoteRecordsRaw(url, token, opts) {
1428
1457
  let base = url;
1429
1458
  let bearer = token;
1430
1459
  if (isWebrtcSpec(url)) {
1431
- const { startWebrtcBridge } = await import("./webrtcRemote-Bl8eowma.js");
1460
+ const { startWebrtcBridge } = await import("./webrtcRemote-D68dkqFp.js");
1432
1461
  bridge = await startWebrtcBridge(url);
1433
1462
  base = bridge.baseUrl;
1434
1463
  bearer = bridge.token;
@@ -2886,16 +2915,18 @@ async function cmdSend(rest) {
2886
2915
  const canConfirm = trailing === "\r" && Boolean(fullBody) && !noWait;
2887
2916
  let confirmed = true;
2888
2917
  let lastScreen = [];
2889
- if (fullBody && trailing) {
2890
- await writeToIpc(fifoPath, fullBody);
2891
- if (canConfirm && record.log_file) {
2892
- await waitForLogQuiet(record.log_file, SEND_SETTLE_QUIET_MS, SEND_SETTLE_MAX_MS);
2893
- ({confirmed, screen: lastScreen} = await submitAndConfirm(record, fifoPath, trailing));
2894
- } else {
2895
- await new Promise((r) => setTimeout(r, 200));
2896
- await writeToIpc(fifoPath, trailing);
2897
- }
2898
- } else await writeToIpc(fifoPath, fullBody + trailing);
2918
+ await withIpcLock(record.pid, async () => {
2919
+ if (fullBody && trailing) {
2920
+ await writeToIpc(fifoPath, fullBody);
2921
+ if (canConfirm && record.log_file) {
2922
+ await waitForLogQuiet(record.log_file, SEND_SETTLE_QUIET_MS, SEND_SETTLE_MAX_MS);
2923
+ ({confirmed, screen: lastScreen} = await submitAndConfirm(record, fifoPath, trailing));
2924
+ } else {
2925
+ await new Promise((r) => setTimeout(r, 200));
2926
+ await writeToIpc(fifoPath, trailing);
2927
+ }
2928
+ } else await writeToIpc(fifoPath, fullBody + trailing);
2929
+ }, (why) => process.stderr.write(`warning: ay send writing pid ${record.pid} without the input lock (${why})\n`));
2899
2930
  const payload = body + trailing;
2900
2931
  const status = confirmed ? "sent" : "sent but NOT confirmed submitted";
2901
2932
  process.stdout.write(`${status} to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
@@ -3098,7 +3129,7 @@ async function cmdKey(rest) {
3098
3129
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
3099
3130
  });
3100
3131
  const sender = await enforceSendGuards(record, Boolean(argv.force) || process.env.AGENT_YES_FORCE_SEND === "1");
3101
- await writeKeysPaced(record.fifo_file, byteSeqs, Math.max(0, argv.pace));
3132
+ await withIpcLock(record.pid, () => writeKeysPaced(record.fifo_file, byteSeqs, Math.max(0, argv.pace)), (why) => process.stderr.write(`warning: ay key/select writing pid ${record.pid} without the input lock (${why})\n`));
3102
3133
  process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${keyNames.join(" ")}\n`);
3103
3134
  await recordKeyEvent(sender, record, "key", keyNames.join(" "));
3104
3135
  return 0;
@@ -3148,7 +3179,7 @@ async function cmdSelect(rest) {
3148
3179
  if (!menu) throw new Error(`pid ${record.pid} (${record.cli}) is not parked on a selection menu (not needs_input).\n Check with: ay status ${record.pid}`);
3149
3180
  if (menu.options.length > 0 && !menu.options.includes(n)) throw new Error(`option ${n} is out of range — this menu offers ${menu.options.join(", ")}.`);
3150
3181
  const byteSeqs = menuSelectKeys(menu.cursor, n).map((k) => controlCodeFromName(k));
3151
- await writeKeysPaced(record.fifo_file, byteSeqs, Math.max(0, argv.pace));
3182
+ await withIpcLock(record.pid, () => writeKeysPaced(record.fifo_file, byteSeqs, Math.max(0, argv.pace)), (why) => process.stderr.write(`warning: ay key/select writing pid ${record.pid} without the input lock (${why})\n`));
3152
3183
  const delta = n - menu.cursor;
3153
3184
  const moved = delta === 0 ? "cursor already there" : `${Math.abs(delta)}× ${delta > 0 ? "down" : "up"}`;
3154
3185
  process.stdout.write(`pid ${record.pid} (${record.cli}): selected option ${n} (${moved} + enter)\n`);
@@ -3311,15 +3342,17 @@ async function cmdStop(rest) {
3311
3342
  strategy = `double Ctrl+C (no known /exit for cli "${record.cli}")`;
3312
3343
  } else throw new Error(`unknown --method=${method}`);
3313
3344
  const fifoPath = record.fifo_file;
3314
- if (payload === "double-ctrl-c") {
3315
- await writeToIpc(fifoPath, "");
3316
- await new Promise((r) => setTimeout(r, 200));
3317
- await writeToIpc(fifoPath, "");
3318
- } else {
3319
- await writeToIpc(fifoPath, payload);
3320
- await new Promise((r) => setTimeout(r, 200));
3321
- await writeToIpc(fifoPath, "\r");
3322
- }
3345
+ await withIpcLock(record.pid, async () => {
3346
+ if (payload === "double-ctrl-c") {
3347
+ await writeToIpc(fifoPath, "");
3348
+ await new Promise((r) => setTimeout(r, 200));
3349
+ await writeToIpc(fifoPath, "");
3350
+ } else {
3351
+ await writeToIpc(fifoPath, payload);
3352
+ await new Promise((r) => setTimeout(r, 200));
3353
+ await writeToIpc(fifoPath, "\r");
3354
+ }
3355
+ }, (why) => process.stderr.write(`warning: ay stop writing pid ${record.pid} without the input lock (${why})\n`));
3323
3356
  process.stdout.write(`stopping pid ${record.pid} (${record.cli}) via ${strategy}\n`);
3324
3357
  process.stderr.write(`\n ay status ${record.pid} # confirm it exited\n ay ls --all # see exit codes\n`);
3325
3358
  return 0;
@@ -3349,16 +3382,18 @@ async function gracefulExitAgent(record, reason) {
3349
3382
  await writeNote(record.pid, `↩ exit — ${reason}`).catch(() => {});
3350
3383
  const fifoPath = record.fifo_file;
3351
3384
  const graceful = GRACEFUL_EXIT_COMMANDS[record.cli];
3352
- if (graceful) {
3353
- await writeToIpc(fifoPath, graceful);
3385
+ return withIpcLock(record.pid, async () => {
3386
+ if (graceful) {
3387
+ await writeToIpc(fifoPath, graceful);
3388
+ await new Promise((r) => setTimeout(r, 200));
3389
+ await writeToIpc(fifoPath, "\r");
3390
+ return { strategy: `'${graceful}' + Enter` };
3391
+ }
3392
+ await writeToIpc(fifoPath, "");
3354
3393
  await new Promise((r) => setTimeout(r, 200));
3355
- await writeToIpc(fifoPath, "\r");
3356
- return { strategy: `'${graceful}' + Enter` };
3357
- }
3358
- await writeToIpc(fifoPath, "");
3359
- await new Promise((r) => setTimeout(r, 200));
3360
- await writeToIpc(fifoPath, "");
3361
- return { strategy: `double Ctrl+C (no known /exit for cli "${record.cli}")` };
3394
+ await writeToIpc(fifoPath, "");
3395
+ return { strategy: `double Ctrl+C (no known /exit for cli "${record.cli}")` };
3396
+ }, (why) => process.stderr.write(`warning: ay exit writing pid ${record.pid} without the input lock (${why})\n`));
3362
3397
  }
3363
3398
  async function cmdExit(rest) {
3364
3399
  const argv = await yargs(rest).usage("Usage: ay exit <keyword> [reason]").option("all", {
@@ -3465,7 +3500,7 @@ async function cmdAttach(rest) {
3465
3500
  const winsizeDir = path.join(ayHome, "winsize");
3466
3501
  await mkdir(winsizeDir, { recursive: true });
3467
3502
  const winsizePath = path.join(winsizeDir, String(record.pid));
3468
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-BqRNIxeq.js");
3503
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-BaHCO0bU.js");
3469
3504
  const daemonBase = await resolveDaemonHttpBase().catch(() => null);
3470
3505
  const daemonToken = daemonBase ? await loadTokenReadOnly().catch(() => null) ?? "" : "";
3471
3506
  const capViewer = `attach:${process.pid}`;
@@ -4175,7 +4210,7 @@ async function cmdNotify(rest) {
4175
4210
  }
4176
4211
  const ensure = async () => {
4177
4212
  if (!argv["ensure-daemon"]) return;
4178
- const { ensureDaemon } = await import("./notifyDaemon-TKtSYtEO.js");
4213
+ const { ensureDaemon } = await import("./notifyDaemon-Cf4KHDOr.js");
4179
4214
  await ensureDaemon().catch(() => null);
4180
4215
  };
4181
4216
  await heartbeatWatcher(parent, selfStartedAt);
@@ -4249,7 +4284,7 @@ async function cmdNotifyCursor(args) {
4249
4284
  }
4250
4285
  async function cmdNotifyd(rest) {
4251
4286
  const sub = rest[0] ?? "status";
4252
- const daemon = await import("./notifyDaemon-TKtSYtEO.js");
4287
+ const daemon = await import("./notifyDaemon-Cf4KHDOr.js");
4253
4288
  switch (sub) {
4254
4289
  case "run": return daemon.runDaemon();
4255
4290
  case "once": return daemon.runDaemon({ once: true });
@@ -4275,5 +4310,5 @@ async function cmdNotifyd(rest) {
4275
4310
  }
4276
4311
 
4277
4312
  //#endregion
4278
- export { buildAgentForest as $, menuSelectKeys as A, resolveOne as B, isSubcommand as C, lastStdinAt as D, lastReadAt as E, recentMessageEdges as F, snapshotStatus as G, resolveResumeArgs as H, recentReadEdges as I, submitAndConfirm as J, stdinActivityPath as K, renderLogTailLines as L, readLogForRender as M, readNotes as N, listRecords as O, readPtysize as P, TYPING_BADGE as Q, renderRawLog as R, isSlashCommand as S, isUserTyping as T, restartHintLines as U, resolveReadWindow as V, runSubcommand as W, writeKeysPaced as X, waitForLogQuiet as Y, writeToIpc as Z, finalizedLines as _, TYPING_WINDOW_MS as a, isExitRequest as b, cmdHelp as c, deriveLiveState as d, flattenForest as et, deriveLiveStatus as f, extractTaskCounts as g, extractNeedsInput as h, READ_WINDOW_MS as i, readAgentPtysize as j, matchKeyword as k, controlCodeFromName as l, extractMenu as m, MAX_RENDER_BYTES as n, backoffWhileTyping as o, extractBadges as p, stopTipForCli as q, READ_PAGE_DEFAULT as r, cmdDeepseek as s, GRACEFUL_EXIT_COMMANDS as t, cursorAbs as u, isAgentStuck as v, isUnknownManagerToken as w, isPidAlive as x, isBareManagerInvocation as y, renderRawLogLines as z };
4279
- //# sourceMappingURL=subcommands-DJpVXIb6.js.map
4313
+ export { TYPING_BADGE as $, menuSelectKeys as A, resolveOne as B, isSubcommand as C, lastStdinAt as D, lastReadAt as E, recentMessageEdges as F, snapshotStatus as G, resolveResumeArgs as H, recentReadEdges as I, submitAndConfirm as J, stdinActivityPath as K, renderLogTailLines as L, readLogForRender as M, readNotes as N, listRecords as O, readPtysize as P, withIpcLock as Q, renderRawLog as R, isSlashCommand as S, isUserTyping as T, restartHintLines as U, resolveReadWindow as V, runSubcommand as W, writeKeysPaced as X, waitForLogQuiet as Y, writeToIpc as Z, finalizedLines as _, TYPING_WINDOW_MS as a, isExitRequest as b, cmdHelp as c, deriveLiveState as d, buildAgentForest as et, deriveLiveStatus as f, extractTaskCounts as g, extractNeedsInput as h, READ_WINDOW_MS as i, readAgentPtysize as j, matchKeyword as k, controlCodeFromName as l, extractMenu as m, MAX_RENDER_BYTES as n, backoffWhileTyping as o, extractBadges as p, stopTipForCli as q, READ_PAGE_DEFAULT as r, cmdDeepseek as s, GRACEFUL_EXIT_COMMANDS as t, flattenForest as tt, cursorAbs as u, isAgentStuck as v, isUnknownManagerToken as w, isPidAlive as x, isBareManagerInvocation as y, renderRawLogLines as z };
4314
+ //# sourceMappingURL=subcommands-03ghe-aZ.js.map
@@ -1,11 +1,11 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
2
  import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
- import "./e2e-CUMZC53I.js";
5
- import "./configShared-DtHMBCl7.js";
6
- import { A as menuSelectKeys, B as resolveOne, C as isSubcommand, D as lastStdinAt, E as lastReadAt, F as recentMessageEdges, G as snapshotStatus, H as resolveResumeArgs, I as recentReadEdges, J as submitAndConfirm, K as stdinActivityPath, L as renderLogTailLines, M as readLogForRender, N as readNotes, O as listRecords, P as readPtysize, R as renderRawLog, S as isSlashCommand, T as isUserTyping, U as restartHintLines, V as resolveReadWindow, W as runSubcommand, X as writeKeysPaced, Y as waitForLogQuiet, Z as writeToIpc, _ as finalizedLines, a as TYPING_WINDOW_MS, b as isExitRequest, c as cmdHelp, d as deriveLiveState, f as deriveLiveStatus, g as extractTaskCounts, h as extractNeedsInput, i as READ_WINDOW_MS, j as readAgentPtysize, k as matchKeyword, l as controlCodeFromName, m as extractMenu, n as MAX_RENDER_BYTES, o as backoffWhileTyping, p as extractBadges, q as stopTipForCli, r as READ_PAGE_DEFAULT, s as cmdDeepseek, t as GRACEFUL_EXIT_COMMANDS, u as cursorAbs, v as isAgentStuck, w as isUnknownManagerToken, x as isPidAlive, y as isBareManagerInvocation, z as renderRawLogLines } from "./subcommands-DJpVXIb6.js";
7
- import "./notifyStore-Cbw2ZJ0X.js";
8
- import "./webrtcLink-DyCj380i.js";
9
- import "./remotes-DdAfueOu.js";
4
+ import "./e2e-OLfcNxiv.js";
5
+ import "./configShared-DYxlvrH0.js";
6
+ import { A as menuSelectKeys, B as resolveOne, C as isSubcommand, D as lastStdinAt, E as lastReadAt, F as recentMessageEdges, G as snapshotStatus, H as resolveResumeArgs, I as recentReadEdges, J as submitAndConfirm, K as stdinActivityPath, L as renderLogTailLines, M as readLogForRender, N as readNotes, O as listRecords, P as readPtysize, R as renderRawLog, S as isSlashCommand, T as isUserTyping, U as restartHintLines, V as resolveReadWindow, W as runSubcommand, X as writeKeysPaced, Y as waitForLogQuiet, Z as writeToIpc, _ as finalizedLines, a as TYPING_WINDOW_MS, b as isExitRequest, c as cmdHelp, d as deriveLiveState, f as deriveLiveStatus, g as extractTaskCounts, h as extractNeedsInput, i as READ_WINDOW_MS, j as readAgentPtysize, k as matchKeyword, l as controlCodeFromName, m as extractMenu, n as MAX_RENDER_BYTES, o as backoffWhileTyping, p as extractBadges, q as stopTipForCli, r as READ_PAGE_DEFAULT, s as cmdDeepseek, t as GRACEFUL_EXIT_COMMANDS, u as cursorAbs, v as isAgentStuck, w as isUnknownManagerToken, x as isPidAlive, y as isBareManagerInvocation, z as renderRawLogLines } from "./subcommands-03ghe-aZ.js";
7
+ import "./notifyStore-Dus2D1XA.js";
8
+ import "./webrtcLink-B72AQ2eb.js";
9
+ import "./remotes-IzQHupeo.js";
10
10
 
11
11
  export { READ_WINDOW_MS, cmdHelp, isBareManagerInvocation, isSubcommand, isUnknownManagerToken, lastReadAt, runSubcommand };
@@ -88,4 +88,4 @@ function linkBunBinsToSystemPath(opts) {
88
88
 
89
89
  //#endregion
90
90
  export { linkBunBinsToSystemPath };
91
- //# sourceMappingURL=systemPathLink-BHv1I6wh.js.map
91
+ //# sourceMappingURL=systemPathLink-DGVcx0fg.js.map
@@ -98,7 +98,7 @@ async function cmdTermMint(args) {
98
98
  if (flags.ro && flags.interactive) throw new Error("--ro and --interactive are mutually exclusive");
99
99
  const ttlSec = parseTtlSec(typeof flags.ttl === "string" ? flags.ttl : "15m");
100
100
  const canSend = flags.interactive === true;
101
- const { mintScopedTermToken } = await import("./serve-BqRNIxeq.js");
101
+ const { mintScopedTermToken } = await import("./serve-BaHCO0bU.js");
102
102
  const r = await mintScopedTermToken(pid, {
103
103
  ttlSec,
104
104
  canSend
@@ -135,4 +135,4 @@ async function cmdTerm(args) {
135
135
 
136
136
  //#endregion
137
137
  export { cmdTerm };
138
- //# sourceMappingURL=terminal-CRLPUaKF.js.map
138
+ //# sourceMappingURL=terminal-DrCP-200.js.map
package/dist/terminal.js CHANGED
@@ -1,3 +1,3 @@
1
- import { t as AyTerminal } from "./browser-DSduVZeZ.js";
1
+ import { t as AyTerminal } from "./browser-CorYp8Yu.js";
2
2
 
3
3
  export { AyTerminal, AyTerminal as default };