agent-yes 1.300.3 → 1.300.4

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 (58) hide show
  1. package/dist/{SUPPORTED_CLIS-CWJr4TJH.js → SUPPORTED_CLIS-BIg_X-jE.js} +2 -2
  2. package/dist/{SUPPORTED_CLIS-ChSO8FiS.js → SUPPORTED_CLIS-D7lOxOnw.js} +3 -3
  3. package/dist/{agentShare-NkW0b0mg.js → agentShare-BfHUZgk4.js} +4 -4
  4. package/dist/{callback-CjlaFXp1.js → callback-COR0F2eB.js} +3 -3
  5. package/dist/{callback-B8CUyag4.js → callback-yWhCmi4k.js} +3 -3
  6. package/dist/{channels-aYwLMbFV.js → channels-rhxQArqN.js} +2 -2
  7. package/dist/cli.js +5 -5
  8. package/dist/{cmdPs-C8DIl8V6.js → cmdPs-B3jTE4f3.js} +3 -3
  9. package/dist/index.js +2 -2
  10. package/dist/{notifyDaemon-DWIfzEE7.js → notifyDaemon-BIo7Nkk2.js} +3 -3
  11. package/dist/{parentPingLoop-Bh3KuFcW.js → parentPingLoop-9T4Pzp6c.js} +2 -2
  12. package/dist/{remotes-B4v0Nowo.js → remotes-B4CNXwDS.js} +2 -2
  13. package/dist/{remotes-DnMQWaqQ.js → remotes-CQTEY8Gt.js} +1 -1
  14. package/dist/{rustBinary-gI_r3g9K.js → rustBinary-CJXUPUWd.js} +48 -5
  15. package/dist/rustBinary-FhGG3Yfl.js +4 -0
  16. package/dist/{schedule-DryC--yx.js → schedule-DmijwnTA.js} +4 -4
  17. package/dist/{serve-DVoYVgBo.js → serve-F5tXYO0-.js} +99 -32
  18. package/dist/{serveRust-DDQiZmMf.js → serveRust-BAawe83i.js} +16 -2
  19. package/dist/{setup--1zRHaGW.js → setup-mCb1JbTX.js} +2 -2
  20. package/dist/{share-C9oGvjXT.js → share-CLataFnI.js} +18 -12
  21. package/dist/share-nmgSIjYP.js +4 -0
  22. package/dist/{shareCmd-BI3hRWGO.js → shareCmd-aOKimJeT.js} +25 -7
  23. package/dist/{subcommands-BviFOQZE.js → subcommands-DRq0MgSm.js} +2 -2
  24. package/dist/{subcommands-UWLkF5pq.js → subcommands-Ddo2TYZs.js} +404 -77
  25. package/dist/{terminal-DviH1E3C.js → terminal-DEXrWOa9.js} +2 -2
  26. package/dist/trayApp-0RVjo43f.js +5 -0
  27. package/dist/{trayApp-PJrwfyDs.js → trayApp-BUXx7cES.js} +2 -2
  28. package/dist/{ts--JmSY117.js → ts-CRbycTni.js} +5 -5
  29. package/dist/{versionChecker-CCGSbjio.js → versionChecker-DeQEhRGB.js} +2 -2
  30. package/dist/{webrtcRemote-Cd4vTrgv.js → webrtcRemote-Cy5kB8aR.js} +115 -14
  31. package/dist/{widget-DgBDIGRQ.js → widget-DneXqnL8.js} +3 -3
  32. package/dist/{ws-DnBg8-g-.js → ws-DPw1lVTi.js} +3 -3
  33. package/dist/{ws-dc7MP054.js → ws-Dr-0Ckq7.js} +2 -2
  34. package/package.json +1 -1
  35. package/ts/remoteCache.bun.spec.ts +191 -0
  36. package/ts/remoteCache.spec.ts +191 -0
  37. package/ts/remoteCache.ts +193 -0
  38. package/ts/rustBinary.bun.spec.ts +39 -2
  39. package/ts/rustBinary.spec.ts +53 -2
  40. package/ts/rustBinary.ts +61 -3
  41. package/ts/serve.spec.ts +103 -0
  42. package/ts/serve.ts +100 -15
  43. package/ts/serveRust.spec.ts +24 -1
  44. package/ts/serveRust.ts +19 -0
  45. package/ts/share.bun.spec.ts +54 -2
  46. package/ts/share.spec.ts +54 -2
  47. package/ts/share.ts +40 -15
  48. package/ts/shareCmd.ts +33 -2
  49. package/ts/strayServe.ts +1 -0
  50. package/ts/subcommands.spec.ts +36 -3
  51. package/ts/subcommands.ts +384 -98
  52. package/ts/webrtcRemote.ts +66 -20
  53. package/ts/webrtcSignal.bun.spec.ts +120 -0
  54. package/ts/webrtcSignal.spec.ts +120 -0
  55. package/ts/webrtcSignal.ts +143 -0
  56. package/dist/rustBinary-BNmAJEBb.js +0 -4
  57. package/dist/share-Bmnd_qT0.js +0 -4
  58. package/dist/trayApp-BDIb8fOj.js +0 -5
@@ -8,7 +8,7 @@ import { n as localHost, r as localUser, t as formatIdentity } from "./identity-
8
8
  import { n as isWorkingScreen, r as parseMenu, t as classifyNeedsInput } from "./needsInput-BHopZGM8.js";
9
9
  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-2SWKGwZI.js";
10
10
  import { n as isWebrtcSpec } from "./webrtcLink-DFRyWNMs.js";
11
- import { l as readRemotes, u as resolveRemoteSpec } from "./remotes-B4v0Nowo.js";
11
+ import { l as readRemotes, u as resolveRemoteSpec } from "./remotes-B4CNXwDS.js";
12
12
  import { execFile } from "child_process";
13
13
  import ms from "ms";
14
14
  import yargs from "yargs";
@@ -17,7 +17,7 @@ import { appendFile, mkdir, open, readFile, readdir, rename, rm, stat, writeFile
17
17
  import { homedir } from "os";
18
18
  import path from "path";
19
19
  import { lock } from "proper-lockfile";
20
- import { execFileSync as execFileSync$1 } from "node:child_process";
20
+ import { execFileSync as execFileSync$1, spawn as spawn$1 } from "node:child_process";
21
21
  import { createHash, randomBytes } from "crypto";
22
22
  import { promisify } from "util";
23
23
 
@@ -762,6 +762,137 @@ async function readAncestryTable(read = readProcessTable) {
762
762
  }
763
763
  }
764
764
 
765
+ //#endregion
766
+ //#region ts/remoteCache.ts
767
+ /** Rows younger than this are served as-is without kicking a refresh. */
768
+ const REMOTE_CACHE_FRESH_MS = 3e4;
769
+ function remoteCachePath(alias) {
770
+ return path.join(agentYesHome(), "remotes", `${encodeURIComponent(alias)}.cache.json`);
771
+ }
772
+ /** The persisted snapshot, or null for missing/corrupt — a cold cache, never an error. */
773
+ async function readRemoteSnapshot(alias) {
774
+ try {
775
+ const parsed = JSON.parse(await readFile(remoteCachePath(alias), "utf-8"));
776
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
777
+ const s = parsed;
778
+ if (s.version !== 1 || !Array.isArray(s.records)) return null;
779
+ const fetchedAt = typeof s.fetchedAt === "number" && Number.isFinite(s.fetchedAt) ? s.fetchedAt : null;
780
+ const e = s.error;
781
+ const error = e && typeof e === "object" && typeof e.message === "string" && typeof e.since === "number" ? {
782
+ message: e.message,
783
+ since: e.since,
784
+ lastTriedAt: Number(e.lastTriedAt) || e.since
785
+ } : null;
786
+ return {
787
+ version: 1,
788
+ alias,
789
+ fetchedAt,
790
+ records: s.records,
791
+ error
792
+ };
793
+ } catch {
794
+ return null;
795
+ }
796
+ }
797
+ /** tmp + rename, like the health file: a concurrent reader never sees a torn file. */
798
+ async function writeRemoteSnapshot(snap) {
799
+ const dest = remoteCachePath(snap.alias);
800
+ await mkdir(path.dirname(dest), { recursive: true });
801
+ const tmp = `${dest}.${process.pid}.tmp`;
802
+ await writeFile(tmp, JSON.stringify(snap));
803
+ await rename(tmp, dest);
804
+ }
805
+ /**
806
+ * Fold one fetch into the snapshot. Success replaces the rows and clears the
807
+ * error; failure keeps the last good rows and extends the streak, preserving
808
+ * `since` so the operator sees "unreachable since 14:02", not "since now".
809
+ */
810
+ function noteSnapshotResult(prev, alias, result, now) {
811
+ if (result.ok) return {
812
+ version: 1,
813
+ alias,
814
+ fetchedAt: now,
815
+ records: result.records,
816
+ error: null
817
+ };
818
+ return {
819
+ version: 1,
820
+ alias,
821
+ fetchedAt: prev?.fetchedAt ?? null,
822
+ records: prev?.records ?? [],
823
+ error: {
824
+ message: result.message,
825
+ since: prev?.error?.since ?? now,
826
+ lastTriedAt: now
827
+ }
828
+ };
829
+ }
830
+ /** Whether the rows are old enough to warrant a refresh. Fails open to "stale". */
831
+ function isStale(snap, now, freshMs = REMOTE_CACHE_FRESH_MS) {
832
+ if (!snap || snap.fetchedAt === null) return true;
833
+ const age = now - snap.fetchedAt;
834
+ return age < 0 || age >= freshMs;
835
+ }
836
+ /** "0s", "42s", "3m", "5h", "2d" — the table's AGE column vocabulary. */
837
+ function humanizeAge(ms) {
838
+ if (ms < 1e3) return "0s";
839
+ const s = Math.floor(ms / 1e3);
840
+ if (s < 60) return `${s}s`;
841
+ const m = Math.floor(s / 60);
842
+ if (m < 60) return `${m}m`;
843
+ const h = Math.floor(m / 60);
844
+ if (h < 24) return `${h}h`;
845
+ return `${Math.floor(h / 24)}d`;
846
+ }
847
+ /** Local wall-clock HH:MM, for "unreachable since 14:02". */
848
+ function hhmm(ts) {
849
+ const d = new Date(ts);
850
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
851
+ }
852
+ /**
853
+ * The per-remote tag printed beside its section: where the rows came from and
854
+ * how old they are. `refreshing` says a background refresh was just kicked.
855
+ */
856
+ function cacheAgeLabel(snap, now, refreshing) {
857
+ const base = snap && snap.fetchedAt !== null ? `cached ${humanizeAge(now - snap.fetchedAt)} ago` : "no cache yet";
858
+ if (snap?.error) return `${base}, unreachable since ${hhmm(snap.error.since)}`;
859
+ return refreshing ? `${base}, refreshing…` : base;
860
+ }
861
+ /**
862
+ * `--waitFor=all|local|<alias>[,<alias>…]`. Unknown aliases are an error
863
+ * rather than silently waited-for-nothing: the flag exists so a caller can
864
+ * trust the rows it gets, and a typo must not become a false "fresh".
865
+ */
866
+ function parseWaitFor(value, aliases) {
867
+ const v = value.trim();
868
+ if (!v) return { error: "--waitFor needs a value: all, local, or an alias list" };
869
+ if (v === "all") return {
870
+ local: true,
871
+ remotes: [...aliases]
872
+ };
873
+ if (v === "local") return {
874
+ local: true,
875
+ remotes: []
876
+ };
877
+ const want = v.split(",").map((s) => s.trim()).filter(Boolean);
878
+ const unknown = want.filter((a) => !aliases.includes(a));
879
+ if (unknown.length) return { error: `--waitFor: unknown remote ${unknown.join(", ")} (configured: ${aliases.join(", ") || "none"})` };
880
+ return {
881
+ local: false,
882
+ remotes: [...new Set(want)]
883
+ };
884
+ }
885
+ /** The `--json` shape for one remote: additive, so a parser can decide on staleness. */
886
+ function jsonRemote(snap, now) {
887
+ return {
888
+ fetchedAt: snap?.fetchedAt ?? null,
889
+ stale: isStale(snap, now),
890
+ ageMs: snap && snap.fetchedAt !== null ? now - snap.fetchedAt : null,
891
+ error: snap?.error ?? null,
892
+ records: snap?.records ?? []
893
+ };
894
+ }
895
+
765
896
  //#endregion
766
897
  //#region ts/remoteHealth.ts
767
898
  /**
@@ -1737,6 +1868,7 @@ function mergeRecords(...buckets) {
1737
1868
  }
1738
1869
  const SUBCOMMANDS = new Set([
1739
1870
  "ls",
1871
+ "ls-refresh",
1740
1872
  "list",
1741
1873
  "ps",
1742
1874
  "status",
@@ -2011,7 +2143,7 @@ async function runSubcommand(argv) {
2011
2143
  switch (sub) {
2012
2144
  case "ls":
2013
2145
  case "list": return await cmdLs(rest);
2014
- case "ps": return await (await import("./cmdPs-C8DIl8V6.js")).cmdPs(rest);
2146
+ case "ps": return await (await import("./cmdPs-B3jTE4f3.js")).cmdPs(rest);
2015
2147
  case "status": return await cmdStatus(rest);
2016
2148
  case "whoami": return await cmdWhoami(rest);
2017
2149
  case "result": return await cmdResult(rest);
@@ -2025,6 +2157,7 @@ async function runSubcommand(argv) {
2025
2157
  case "history": return await (await import("./hist-DZxq7NMA.js")).cmdHist(rest);
2026
2158
  case "send": return await cmdSend(rest);
2027
2159
  case "send-drain": return await cmdSendDrain(rest);
2160
+ case "ls-refresh": return await cmdLsRefresh(rest);
2028
2161
  case "msgs": return await cmdMsgs(rest);
2029
2162
  case "key": return await cmdKey(rest);
2030
2163
  case "select": return await cmdSelect(rest);
@@ -2055,51 +2188,51 @@ async function runSubcommand(argv) {
2055
2188
  }
2056
2189
  case "ch":
2057
2190
  case "channels": {
2058
- const { cmdCh } = await import("./channels-aYwLMbFV.js");
2191
+ const { cmdCh } = await import("./channels-rhxQArqN.js");
2059
2192
  return cmdCh(rest);
2060
2193
  }
2061
2194
  case "term": {
2062
- const { cmdTerm } = await import("./terminal-DviH1E3C.js");
2195
+ const { cmdTerm } = await import("./terminal-DEXrWOa9.js");
2063
2196
  return cmdTerm(rest);
2064
2197
  }
2065
2198
  case "widget": {
2066
- const { cmdWidget } = await import("./widget-DgBDIGRQ.js");
2199
+ const { cmdWidget } = await import("./widget-DneXqnL8.js");
2067
2200
  return cmdWidget(rest);
2068
2201
  }
2069
2202
  case "mint": {
2070
- const { cmdMint } = await import("./widget-DgBDIGRQ.js");
2203
+ const { cmdMint } = await import("./widget-DneXqnL8.js");
2071
2204
  return cmdMint(rest);
2072
2205
  }
2073
2206
  case "serve": {
2074
- const { cmdServe } = await import("./serve-DVoYVgBo.js");
2207
+ const { cmdServe } = await import("./serve-F5tXYO0-.js");
2075
2208
  return cmdServe(rest);
2076
2209
  }
2077
2210
  case "tray": {
2078
- const { cmdTray } = await import("./trayApp-BDIb8fOj.js");
2211
+ const { cmdTray } = await import("./trayApp-0RVjo43f.js");
2079
2212
  return cmdTray(rest);
2080
2213
  }
2081
2214
  case "setup": {
2082
- const { cmdSetup } = await import("./setup--1zRHaGW.js");
2215
+ const { cmdSetup } = await import("./setup-mCb1JbTX.js");
2083
2216
  return cmdSetup(rest);
2084
2217
  }
2085
2218
  case "ws": {
2086
- const { cmdWs } = await import("./ws-DnBg8-g-.js");
2219
+ const { cmdWs } = await import("./ws-DPw1lVTi.js");
2087
2220
  return cmdWs(rest);
2088
2221
  }
2089
2222
  case "schedule": {
2090
- const { cmdSchedule } = await import("./schedule-DryC--yx.js");
2223
+ const { cmdSchedule } = await import("./schedule-DmijwnTA.js");
2091
2224
  return cmdSchedule(rest);
2092
2225
  }
2093
2226
  case "remote": {
2094
- const { cmdRemote } = await import("./remotes-DnMQWaqQ.js");
2227
+ const { cmdRemote } = await import("./remotes-CQTEY8Gt.js");
2095
2228
  return cmdRemote(rest);
2096
2229
  }
2097
2230
  case "share": {
2098
- const { cmdShare } = await import("./shareCmd-BI3hRWGO.js");
2231
+ const { cmdShare } = await import("./shareCmd-aOKimJeT.js");
2099
2232
  return cmdShare(rest);
2100
2233
  }
2101
2234
  case "connect": {
2102
- const { cmdConnect } = await import("./remotes-DnMQWaqQ.js");
2235
+ const { cmdConnect } = await import("./remotes-CQTEY8Gt.js");
2103
2236
  return cmdConnect(rest);
2104
2237
  }
2105
2238
  case "expose": {
@@ -2107,7 +2240,7 @@ async function runSubcommand(argv) {
2107
2240
  return cmdExpose(rest);
2108
2241
  }
2109
2242
  case "callback": {
2110
- const { cmdCallback } = await import("./callback-B8CUyag4.js");
2243
+ const { cmdCallback } = await import("./callback-yWhCmi4k.js");
2111
2244
  return cmdCallback(rest);
2112
2245
  }
2113
2246
  case "reap":
@@ -2118,7 +2251,7 @@ async function runSubcommand(argv) {
2118
2251
  return await cmdDsh(rest);
2119
2252
  }
2120
2253
  case "gc": {
2121
- const { gcOldBinaryDirs } = await import("./rustBinary-BNmAJEBb.js");
2254
+ const { gcOldBinaryDirs } = await import("./rustBinary-FhGG3Yfl.js");
2122
2255
  const { gcLogs } = await import("./globalPidIndex-DC93JhHQ.js");
2123
2256
  const bins = gcOldBinaryDirs();
2124
2257
  const logs = await gcLogs();
@@ -2181,7 +2314,7 @@ async function cmdHelp(managerCommands = true) {
2181
2314
  const shareAliasNote = managerCommands ? `` : ` (\`ay\` only — \`cy share\`/\`cy connect\` are prompts)\n`;
2182
2315
  const self = process.env.AGENT_YES_PID ? await resolveSender() : null;
2183
2316
  const agentSection = self ? await buildAgentContextSection(self) : "";
2184
- process.stdout.write(agentSection + "ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay ps [keyword] per-agent CPU/RSS, rolled up over each\n agent's whole process tree, + box vitals\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay tail <keyword> --until TEXT block until TEXT is printed, then exit 0\n [--timeout 10m] [-q] [--regex] (1 = agent exited without it, 2 = no match,\n [--fail-on TEXT] [--count N] 3 = --fail-on hit first)\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n" + readAliasNote + " ay cat <keyword> full log\n ay head <keyword> first N lines\n ay hist [-n 6] [--all] [--json] past agent conversations (claude/codex\n transcripts, incl. exited sessions);\n this cwd unless --all\n ay send <keyword> <msg> send a message (keyword '.' = agent in this cwd)\n ay msgs [keyword] [--in|--out] inter-agent message log (sent + received)\n ay ch mk|join|send|read|tail <topic> local-first E2E channels: AI ↔ humans on a topic (ay ch help)\n ay term embed <pid> <script> to embed a live read-only agent terminal in a page (ay term help)\n ay widget ls | read selection|dom read an opted-in page widget's context (selection/DOM) (ay widget help)\n ay mint <target> --caps ... mint a scoped, short-TTL capability token (embeddable in a page)\n ay key <keyword> <key...> send raw keystrokes (down/up/enter/esc/…) — drives menus\n ay select <keyword> <N> pick option N of a needs_input selection menu\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay notify watch --unread get notified when sub-agents finish/stuck/crash (writes to inbox)\n ay whoami [--json] (inside an agent) your own registry identity + reply address\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 ay gc reclaim old-version binary cache dirs + stale session logs\n ay dsh-legacy [args...] launch the DeepSeek Harness terminal client (dsh-tui)\n" + wsLines + "\nRemote:\n" + setupLine + " ay share [local|lan|tailscale|webrtc] share this machine: one URL for web console + CLI\n ay connect <share-url> [alias] save another machine's share URL as a remote\n" + shareAliasNote + " 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 expose <port> share localhost:<port> at https://<id>.agent-yes.com (private link)\n ay callback --expires 7d mint an embeddable message-me widget for one agent\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent (naming a CLI is what launches one — bare 'ay' shows this help):\n ay <claude|codex|...> [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n cy [options] -- [prompt] shortcut for 'ay claude' (bare 'cy' starts claude)\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");
2317
+ process.stdout.write(agentSection + "ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents (remotes from cache, refreshed\n [--waitFor=all|local|<alias>] in the background; --waitFor blocks for fresh rows)\n ay ps [keyword] per-agent CPU/RSS, rolled up over each\n agent's whole process tree, + box vitals\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay tail <keyword> --until TEXT block until TEXT is printed, then exit 0\n [--timeout 10m] [-q] [--regex] (1 = agent exited without it, 2 = no match,\n [--fail-on TEXT] [--count N] 3 = --fail-on hit first)\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n" + readAliasNote + " ay cat <keyword> full log\n ay head <keyword> first N lines\n ay hist [-n 6] [--all] [--json] past agent conversations (claude/codex\n transcripts, incl. exited sessions);\n this cwd unless --all\n ay send <keyword> <msg> send a message (keyword '.' = agent in this cwd)\n ay msgs [keyword] [--in|--out] inter-agent message log (sent + received)\n ay ch mk|join|send|read|tail <topic> local-first E2E channels: AI ↔ humans on a topic (ay ch help)\n ay term embed <pid> <script> to embed a live read-only agent terminal in a page (ay term help)\n ay widget ls | read selection|dom read an opted-in page widget's context (selection/DOM) (ay widget help)\n ay mint <target> --caps ... mint a scoped, short-TTL capability token (embeddable in a page)\n ay key <keyword> <key...> send raw keystrokes (down/up/enter/esc/…) — drives menus\n ay select <keyword> <N> pick option N of a needs_input selection menu\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay restart <keyword> [--fresh] stop (if live) + relaunch resuming the session; --fresh replays the prompt\n ay status <keyword> agent status snapshot\n ay notify watch --unread get notified when sub-agents finish/stuck/crash (writes to inbox)\n ay whoami [--json] (inside an agent) your own registry identity + reply address\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 ay gc reclaim old-version binary cache dirs + stale session logs\n ay dsh-legacy [args...] launch the DeepSeek Harness terminal client (dsh-tui)\n" + wsLines + "\nRemote:\n" + setupLine + " ay share [local|lan|tailscale|webrtc] share this machine: one URL for web console + CLI\n ay connect <share-url> [alias] save another machine's share URL as a remote\n" + shareAliasNote + " 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 expose <port> share localhost:<port> at https://<id>.agent-yes.com (private link)\n ay callback --expires 7d mint an embeddable message-me widget for one agent\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent (naming a CLI is what launches one — bare 'ay' shows this help):\n ay <claude|codex|...> [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n cy [options] -- [prompt] shortcut for 'ay claude' (bare 'cy' starts claude)\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");
2185
2318
  return 0;
2186
2319
  }
2187
2320
  /** Looks like an agent_id (12 hex, or a longer injected one) rather than a word. */
@@ -2380,6 +2513,13 @@ async function runRemoteLs(remote, opts) {
2380
2513
  return 1;
2381
2514
  }
2382
2515
  const records = await res.json();
2516
+ if (!remote.keyword && (await readRemotes()).has(remote.label)) {
2517
+ const now = Date.now();
2518
+ await writeRemoteSnapshot(noteSnapshotResult(await readRemoteSnapshot(remote.label), remote.label, {
2519
+ ok: true,
2520
+ records
2521
+ }, now)).catch(() => {});
2522
+ }
2383
2523
  if (records.length === 0) {
2384
2524
  process.stderr.write(remote.keyword ? `no agents matched "${remote.keyword}" on ${remote.label}\n` : `no running agents on ${remote.label}\n`);
2385
2525
  return 0;
@@ -2717,7 +2857,7 @@ async function fetchRemoteRecordsRaw(url, token, opts, onReachable) {
2717
2857
  let base = url;
2718
2858
  let bearer = token;
2719
2859
  if (isWebrtcSpec(url)) {
2720
- const { startWebrtcBridge } = await import("./webrtcRemote-Cd4vTrgv.js");
2860
+ const { startWebrtcBridge } = await import("./webrtcRemote-Cy5kB8aR.js");
2721
2861
  bridge = await startWebrtcBridge(url);
2722
2862
  base = bridge.baseUrl;
2723
2863
  bearer = bridge.token;
@@ -2729,13 +2869,93 @@ async function fetchRemoteRecordsRaw(url, token, opts, onReachable) {
2729
2869
  onReachable?.(true);
2730
2870
  if (!res.ok) return [];
2731
2871
  return await res.json();
2732
- } catch {
2733
- onReachable?.(false);
2872
+ } catch (e) {
2873
+ onReachable?.(false, String(e?.message ?? e).split("\n")[0]);
2734
2874
  return [];
2735
2875
  } finally {
2736
2876
  bridge?.close();
2737
2877
  }
2738
2878
  }
2879
+ /** Probe one remote and fold the outcome into its snapshot. Never throws. */
2880
+ async function refreshRemoteSnapshot(alias, cfg, opts, now) {
2881
+ let ok = false;
2882
+ let why = "did not answer";
2883
+ const records = await fetchRemoteRecordsRaw(cfg.url, cfg.token, opts, (r, w) => {
2884
+ ok = r;
2885
+ if (w) why = w;
2886
+ });
2887
+ const snap = noteSnapshotResult(await readRemoteSnapshot(alias), alias, ok ? {
2888
+ ok: true,
2889
+ records
2890
+ } : {
2891
+ ok: false,
2892
+ message: why
2893
+ }, now);
2894
+ await writeRemoteSnapshot(snap).catch(() => {});
2895
+ return {
2896
+ snap,
2897
+ ok
2898
+ };
2899
+ }
2900
+ /** Run `ay <args…>` detached from this process (same launcher as the send drainer). */
2901
+ function spawnDetachedAy(args) {
2902
+ const launcher = drainerLauncher(process.execPath, process.argv[1], Bun.which("ay"));
2903
+ if (!launcher) return null;
2904
+ const [cmd, ...pre] = launcher;
2905
+ try {
2906
+ const child = spawn$1(cmd, [...pre, ...args], {
2907
+ detached: true,
2908
+ stdio: "ignore"
2909
+ });
2910
+ child.on("error", () => {});
2911
+ child.unref();
2912
+ return child.pid ?? null;
2913
+ } catch {
2914
+ return null;
2915
+ }
2916
+ }
2917
+ /**
2918
+ * `ay ls-refresh <alias…> [--all] [--active] [--keyword k]` — hidden: the
2919
+ * background half of cached-first `ay ls`. Probes each alias once, writes its
2920
+ * snapshot and health, and exits. One refresher per alias at a time (pid
2921
+ * lock), so a burst of listings does not fan out into a burst of probes.
2922
+ */
2923
+ async function cmdLsRefresh(rest) {
2924
+ const aliases = [];
2925
+ const opts = {
2926
+ all: false,
2927
+ active: false,
2928
+ keyword: void 0
2929
+ };
2930
+ for (let i = 0; i < rest.length; i++) {
2931
+ const a = rest[i];
2932
+ if (a === "--all") opts.all = true;
2933
+ else if (a === "--active") opts.active = true;
2934
+ else if (a === "--keyword") opts.keyword = rest[++i];
2935
+ else if (a.startsWith("--keyword=")) opts.keyword = a.slice(10);
2936
+ else if (!a.startsWith("-")) aliases.push(a);
2937
+ }
2938
+ const remotes = await readRemotes();
2939
+ const now = Date.now();
2940
+ await Promise.all(aliases.map(async (alias) => {
2941
+ const cfg = remotes.get(alias);
2942
+ if (!cfg) return;
2943
+ const lock = path.join(agentYesHome(), "remotes", `${encodeURIComponent(alias)}.refresh.lock`);
2944
+ const owner = Number((await readFile(lock, "utf-8").catch(() => "")).trim());
2945
+ if (owner > 0 && owner !== process.pid && pidAlive(owner)) return;
2946
+ await mkdir(path.dirname(lock), { recursive: true }).catch(() => {});
2947
+ await writeFile(lock, String(process.pid)).catch(() => {});
2948
+ try {
2949
+ const { ok } = await refreshRemoteSnapshot(alias, cfg, opts, now);
2950
+ const health = await readRemoteHealth();
2951
+ health[alias] = noteRemoteResult(health[alias], ok, now);
2952
+ await writeRemoteHealth(health).catch(() => {});
2953
+ } finally {
2954
+ await rm(lock, { force: true }).catch(() => {});
2955
+ }
2956
+ }));
2957
+ return 0;
2958
+ }
2739
2959
  async function runAllRemotesLs(opts) {
2740
2960
  const remotes = await readRemotes();
2741
2961
  const localOpts = {
@@ -2747,30 +2967,72 @@ async function runAllRemotesLs(opts) {
2747
2967
  };
2748
2968
  const health = await readRemoteHealth();
2749
2969
  const healthNow = Date.now();
2750
- const skipped = new Set([...remotes.keys()].filter((alias) => shouldSkipRemote(health[alias], healthNow)));
2751
- const probed = [...remotes.entries()].filter(([alias]) => !skipped.has(alias));
2752
- const [localResult, ...remoteResults] = await Promise.allSettled([listRecords(opts.keyword, localOpts).then((recs) => ({
2970
+ const waiting = new Set(opts.waitFor?.remotes ?? []);
2971
+ const fetchOpts = {
2972
+ all: opts.all,
2973
+ active: opts.active,
2974
+ keyword: opts.keyword
2975
+ };
2976
+ const [localResult, ...waited] = await Promise.allSettled([listRecords(opts.keyword, localOpts).then((recs) => ({
2753
2977
  host: "local",
2754
2978
  records: recs
2755
- })), ...probed.map(([alias, cfg]) => {
2756
- let reachable = false;
2757
- return fetchRemoteRecordsRaw(cfg.url, cfg.token, opts, (ok) => {
2758
- reachable = ok;
2759
- }).then((records) => ({
2760
- host: alias,
2761
- records,
2762
- ok: reachable
2763
- }));
2979
+ })), ...[...remotes.entries()].filter(([alias]) => waiting.has(alias)).map(async ([alias, cfg]) => {
2980
+ const budget = new Promise((resolve) => {
2981
+ setTimeout(() => resolve("timeout"), opts.timeoutMs).unref?.();
2982
+ });
2983
+ const res = await Promise.race([refreshRemoteSnapshot(alias, cfg, fetchOpts, healthNow), budget]);
2984
+ if (res === "timeout") return {
2985
+ alias,
2986
+ snap: await readRemoteSnapshot(alias),
2987
+ ok: false,
2988
+ failure: `timed out after ${Math.round(opts.timeoutMs / 1e3)}s`
2989
+ };
2990
+ const failure = res.ok ? null : res.snap.error ? `unreachable since ${hhmm(res.snap.error.since)} (${res.snap.error.message})` : "unreachable";
2991
+ return {
2992
+ alias,
2993
+ snap: res.snap,
2994
+ ok: res.ok,
2995
+ failure
2996
+ };
2764
2997
  })]);
2765
- {
2766
- const next = pruneRemoteHealth({ ...health }, remotes.keys());
2767
- for (const [i, [alias]] of probed.entries()) {
2768
- const res = remoteResults[i];
2769
- const ok = res?.status === "fulfilled" && res.value.ok === true;
2770
- next[alias] = noteRemoteResult(health[alias], ok, healthNow);
2998
+ const nextHealth = pruneRemoteHealth({ ...health }, remotes.keys());
2999
+ const waitedByAlias = /* @__PURE__ */ new Map();
3000
+ for (const r of waited) if (r.status === "fulfilled") waitedByAlias.set(r.value.alias, r.value);
3001
+ const sections = [];
3002
+ const background = [];
3003
+ for (const [alias] of remotes) {
3004
+ const w = waitedByAlias.get(alias);
3005
+ if (w) {
3006
+ nextHealth[alias] = noteRemoteResult(health[alias], w.ok, healthNow);
3007
+ sections.push({
3008
+ alias,
3009
+ snap: w.snap,
3010
+ live: w.ok,
3011
+ refreshing: false,
3012
+ failure: w.failure
3013
+ });
3014
+ continue;
2771
3015
  }
2772
- await writeRemoteHealth(next).catch(() => {});
3016
+ const snap = await readRemoteSnapshot(alias);
3017
+ const refreshing = isStale(snap, healthNow) && !shouldSkipRemote(health[alias], healthNow);
3018
+ if (refreshing) background.push(alias);
3019
+ sections.push({
3020
+ alias,
3021
+ snap,
3022
+ live: false,
3023
+ refreshing,
3024
+ failure: null
3025
+ });
2773
3026
  }
3027
+ await writeRemoteHealth(nextHealth).catch(() => {});
3028
+ if (background.length) spawnDetachedAy([
3029
+ "ls-refresh",
3030
+ ...background,
3031
+ ...opts.all ? ["--all"] : [],
3032
+ ...opts.active ? ["--active"] : [],
3033
+ ...opts.keyword ? ["--keyword", opts.keyword] : []
3034
+ ]);
3035
+ const failed = sections.some((s) => waiting.has(s.alias) && s.failure);
2774
3036
  const byHost = [];
2775
3037
  const gitRootCache = /* @__PURE__ */ new Map();
2776
3038
  const gitInfoCache = /* @__PURE__ */ new Map();
@@ -2803,25 +3065,45 @@ async function runAllRemotesLs(opts) {
2803
3065
  records: enriched
2804
3066
  });
2805
3067
  }
2806
- for (const alias of skipped) {
2807
- const h = health[alias];
2808
- const leftMs = Math.max(0, remoteBackoffMs(h?.streak ?? 1) - (healthNow - (h?.lastFailedAt ?? healthNow)));
2809
- const left = leftMs >= 6e4 ? `${Math.round(leftMs / 6e4)}m` : `${Math.ceil(leftMs / 1e3)}s`;
2810
- process.stderr.write(`${alias}: unreachable — skipped after ${h?.streak ?? 0} failed attempt(s), retrying in ${left} (ay ls ${alias} to force a probe now)\n`);
2811
- }
2812
- for (const res of remoteResults) if (res.status === "fulfilled") byHost.push({
2813
- host: res.value.host,
2814
- records: res.value.records
3068
+ for (const s of sections) byHost.push({
3069
+ host: s.alias,
3070
+ records: s.snap?.records ?? [],
3071
+ cached: !s.live
2815
3072
  });
3073
+ if (opts.json) {
3074
+ const out = {
3075
+ local: byHost.find((h) => h.host === "local")?.records ?? [],
3076
+ remotes: Object.fromEntries(sections.map((s) => [s.alias, {
3077
+ ...jsonRemote(s.snap, healthNow),
3078
+ live: s.live,
3079
+ refreshing: s.refreshing,
3080
+ failure: s.failure
3081
+ }]))
3082
+ };
3083
+ await writeStdoutFlushed(JSON.stringify(out, null, 2) + "\n");
3084
+ return failed ? 1 : 0;
3085
+ }
3086
+ if (sections.length) {
3087
+ const aliasW = Math.max(...sections.map((s) => s.alias.length));
3088
+ for (const s of sections) process.stdout.write(`${s.alias.padEnd(aliasW)} ${s.failure ?? (s.live ? "live" : cacheAgeLabel(s.snap, healthNow, s.refreshing))}\n`);
3089
+ process.stdout.write("\n");
3090
+ }
2816
3091
  const rows = [];
2817
- for (const { host, records } of byHost) for (const { record, prefix } of flattenForest(buildAgentForest(records))) rows.push({
3092
+ for (const { host, records, cached } of byHost) for (const { record, prefix } of flattenForest(buildAgentForest(records))) rows.push({
2818
3093
  host,
2819
3094
  rec: record,
2820
- prefix
3095
+ prefix,
3096
+ cached: cached === true
2821
3097
  });
3098
+ const anyCached = sections.some((s) => !s.live);
3099
+ const footer = () => {
3100
+ if (rows.some((r) => r.cached)) process.stderr.write(`* = from cache (AGE is real; STATUS may have changed since)\n`);
3101
+ if (anyCached) process.stderr.write(`ay ls --waitFor=all # block until fresh data from every local+remote machine\n`);
3102
+ };
2822
3103
  if (rows.length === 0) {
2823
3104
  process.stderr.write("no running agents\n");
2824
- return 0;
3105
+ footer();
3106
+ return failed ? 1 : 0;
2825
3107
  }
2826
3108
  const termWidth = process.stdout.columns ?? 120;
2827
3109
  const now = Date.now();
@@ -2830,7 +3112,8 @@ async function runAllRemotesLs(opts) {
2830
3112
  const hostW = Math.max(4, ...rows.map((r) => r.host.length));
2831
3113
  const pidW = Math.max(3, ...rows.map((r) => String(r.rec.pid).length));
2832
3114
  const cliW = Math.max(3, ...rows.map((r) => String(r.rec.cli).length));
2833
- const statusW = Math.max(6, ...rows.map((r) => String(r.rec.status).length));
3115
+ const statusOf = (r) => String(r.rec.status) + (r.cached ? "*" : "");
3116
+ const statusW = Math.max(6, ...rows.map((r) => statusOf(r).length));
2834
3117
  const ageW = Math.max(3, ...rows.map((r) => ageOf(r.rec).length));
2835
3118
  const cwdW = Math.max(3, ...rows.map((r) => cwdLabel(r.rec).length));
2836
3119
  const promptBudget = Math.max(20, termWidth - hostW - pidW - cliW - statusW - ageW - cwdW - 12 - 1);
@@ -2843,7 +3126,8 @@ async function runAllRemotesLs(opts) {
2843
3126
  "CWD".padEnd(cwdW),
2844
3127
  "PROMPT"
2845
3128
  ].join(" ") + "\n");
2846
- for (const { host, rec, prefix } of rows) {
3129
+ for (const row of rows) {
3130
+ const { host, rec, prefix } = row;
2847
3131
  const flagStr = badgeLabels(rec.badges);
2848
3132
  const branchStr = branchLabel(rec.git);
2849
3133
  const gitStr = gitLabel(rec.git);
@@ -2854,13 +3138,14 @@ async function runAllRemotesLs(opts) {
2854
3138
  host.padEnd(hostW),
2855
3139
  String(rec.pid).padEnd(pidW),
2856
3140
  String(rec.cli).padEnd(cliW),
2857
- String(rec.status).padEnd(statusW),
3141
+ statusOf(row).padEnd(statusW),
2858
3142
  ageOf(rec).padEnd(ageW),
2859
3143
  cwdLabel(rec).padEnd(cwdW),
2860
3144
  label
2861
3145
  ].join(" ") + "\n");
2862
3146
  }
2863
- return 0;
3147
+ footer();
3148
+ return failed ? 1 : 0;
2864
3149
  }
2865
3150
  /**
2866
3151
  * Cheap live status from liveness + log quiescence only (no log-content read):
@@ -3196,12 +3481,19 @@ async function cmdLs(rest) {
3196
3481
  type: "boolean",
3197
3482
  default: false,
3198
3483
  description: "Only this machine's agents — skip configured remotes (the pre-default behaviour)"
3484
+ }).option("waitFor", {
3485
+ type: "string",
3486
+ description: "Block until fresh rows from this set instead of printing cached remote rows: all | local | <alias>[,<alias>…]. Each remote gets --timeout; a failure is reported per remote"
3487
+ }).option("timeout", {
3488
+ type: "number",
3489
+ default: 30,
3490
+ description: "Seconds to wait per remote with --waitFor"
3199
3491
  }).option("help", {
3200
3492
  alias: "h",
3201
3493
  type: "boolean",
3202
3494
  default: false,
3203
3495
  description: "Show this help"
3204
- }).example("ay ls", "list local + all configured remotes").example("ay ls --local", "only this machine's agents").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 myrepo", "filter by cwd/prompt keyword").help(false).version(false).exitProcess(false);
3496
+ }).example("ay ls", "local live + remotes from their last snapshot (refreshed in the background)").example("ay ls --waitFor=all", "block until every machine answered (or timed out)").example("ay ls --json --all-remotes", "{local, remotes:{alias:{fetchedAt, stale, records…}}}").example("ay ls --local", "only this machine's agents").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 myrepo", "filter by cwd/prompt keyword").help(false).version(false).exitProcess(false);
3205
3497
  const argv = await y.parseAsync();
3206
3498
  if (argv.help || argv.h) {
3207
3499
  process.stdout.write(await y.getHelp() + "\n");
@@ -3254,12 +3546,24 @@ async function cmdLs(rest) {
3254
3546
  });
3255
3547
  return 0;
3256
3548
  }
3257
- if (!argv.local && !opts.json && !argv.latest) {
3258
- const remotes = await readRemotes();
3259
- if (argv["all-remotes"] || remotes.size > 0) return runAllRemotesLs({
3549
+ const remotesCfg = await readRemotes();
3550
+ let waitFor = null;
3551
+ if (typeof argv.waitFor === "string") {
3552
+ const parsed = parseWaitFor(argv.waitFor, [...remotesCfg.keys()]);
3553
+ if ("error" in parsed) {
3554
+ process.stderr.write(parsed.error + "\n");
3555
+ return 2;
3556
+ }
3557
+ waitFor = parsed;
3558
+ }
3559
+ if (!argv.local && !argv.latest && (!opts.json || argv["all-remotes"] || waitFor)) {
3560
+ if (argv["all-remotes"] || waitFor || remotesCfg.size > 0) return runAllRemotesLs({
3260
3561
  all: argv.all,
3261
3562
  active: argv.active,
3262
- keyword
3563
+ keyword,
3564
+ waitFor,
3565
+ timeoutMs: Math.max(1, Number(argv.timeout) || 30) * 1e3,
3566
+ json: opts.json
3263
3567
  });
3264
3568
  }
3265
3569
  const records = await listRecords(keyword, opts);
@@ -3371,16 +3675,6 @@ async function cmdLs(rest) {
3371
3675
  }
3372
3676
  return 0;
3373
3677
  }
3374
- function humanizeAge(ms) {
3375
- if (ms < 1e3) return "0s";
3376
- const s = Math.floor(ms / 1e3);
3377
- if (s < 60) return `${s}s`;
3378
- const m = Math.floor(s / 60);
3379
- if (m < 60) return `${m}m`;
3380
- const h = Math.floor(m / 60);
3381
- if (h < 24) return `${h}h`;
3382
- return `${Math.floor(h / 24)}d`;
3383
- }
3384
3678
  function shortenPath(p) {
3385
3679
  const home = homedir();
3386
3680
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
@@ -4625,11 +4919,24 @@ function inspectSubmission(screen, identity) {
4625
4919
  composerVisible: false,
4626
4920
  inComposer: false
4627
4921
  };
4628
- const prompt = composerPromptRow(screen);
4629
- if (prompt < 0 || !identity.trim()) return unknown;
4922
+ if (!identity.trim()) return unknown;
4630
4923
  const compact = (text) => text.replace(/\s+/gu, "");
4631
4924
  const needle = compact(identity);
4632
4925
  const wrapped = identity.startsWith("<ay-msg ");
4926
+ const prompt = composerPromptRow(screen);
4927
+ if (prompt < 0) {
4928
+ if (wrapped) return unknown;
4929
+ let last = screen.length - 1;
4930
+ while (last >= 0 && !screen[last].trim()) last--;
4931
+ const echoed = screen.slice(0, Math.max(0, last)).filter((line) => compact(line).endsWith(needle)).length;
4932
+ return echoed ? {
4933
+ submission: "submitted",
4934
+ retry: false,
4935
+ transcriptMatches: echoed,
4936
+ composerVisible: false,
4937
+ inComposer: false
4938
+ } : unknown;
4939
+ }
4633
4940
  const transcriptLines = screen.slice(0, prompt);
4634
4941
  const transcript = compact(transcriptLines.join("\n"));
4635
4942
  let transcriptMatches = wrapped ? transcript.split(needle).length - 1 : 0;
@@ -5698,7 +6005,7 @@ async function cmdAttach(rest) {
5698
6005
  const winsizeDir = path.join(ayHome, "winsize");
5699
6006
  await mkdir(winsizeDir, { recursive: true });
5700
6007
  const winsizePath = path.join(winsizeDir, String(record.pid));
5701
- const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-DVoYVgBo.js");
6008
+ const { resolveDaemonHttpBase, loadTokenReadOnly } = await import("./serve-F5tXYO0-.js");
5702
6009
  const daemonBase = await resolveDaemonHttpBase().catch(() => null);
5703
6010
  const daemonToken = daemonBase ? await loadTokenReadOnly().catch(() => null) ?? "" : "";
5704
6011
  const capViewer = `attach:${process.pid}`;
@@ -5744,12 +6051,32 @@ async function cmdAttach(rest) {
5744
6051
  await writeWinsizeDirect(cols, rows);
5745
6052
  };
5746
6053
  for (let i = 0; i < 15 && (!process.stdout.columns || !process.stdout.rows); i++) await new Promise((r) => setTimeout(r, 100));
6054
+ if (process.platform === "win32" && !capMode) {
6055
+ const cols = process.stdout.columns ?? 0;
6056
+ const rows = process.stdout.rows ?? 0;
6057
+ if (cols > 0 && rows > 1) {
6058
+ await writeWinsizeDirect(cols, rows - 1);
6059
+ await new Promise((r) => setTimeout(r, 400));
6060
+ }
6061
+ }
5747
6062
  await sendResize();
5748
6063
  const capHeartbeat = capMode ? setInterval(() => {
5749
6064
  if (capMode) sendResize();
5750
6065
  }, 5e3) : null;
6066
+ const directHeartbeat = !capMode ? setInterval(() => {
6067
+ const cols = process.stdout.columns ?? 0;
6068
+ const rows = process.stdout.rows ?? 0;
6069
+ if (cols > 0 && rows > 0) writeFile(winsizePath, `${cols} ${rows} ${Date.now()}\n`).catch(() => {});
6070
+ }, 1e4) : null;
5751
6071
  const withdrawAttachCap = async () => {
5752
6072
  if (capHeartbeat) clearInterval(capHeartbeat);
6073
+ if (directHeartbeat) clearInterval(directHeartbeat);
6074
+ if (!capMode) {
6075
+ await rm(winsizePath, { force: true }).catch(() => {});
6076
+ try {
6077
+ process.kill(record.pid, "SIGWINCH");
6078
+ } catch {}
6079
+ }
5753
6080
  if (!daemonBase) return;
5754
6081
  try {
5755
6082
  await fetch(`${daemonBase}/api/presence?token=${encodeURIComponent(daemonToken)}`, {
@@ -6427,7 +6754,7 @@ async function cmdNotify(rest) {
6427
6754
  }
6428
6755
  const ensure = async () => {
6429
6756
  if (!argv["ensure-daemon"]) return;
6430
- const { ensureDaemon } = await import("./notifyDaemon-DWIfzEE7.js");
6757
+ const { ensureDaemon } = await import("./notifyDaemon-BIo7Nkk2.js");
6431
6758
  await ensureDaemon().catch(() => null);
6432
6759
  };
6433
6760
  await heartbeatWatcher(parent, selfStartedAt);
@@ -6501,7 +6828,7 @@ async function cmdNotifyCursor(args) {
6501
6828
  }
6502
6829
  async function cmdNotifyd(rest) {
6503
6830
  const sub = rest[0] ?? "status";
6504
- const daemon = await import("./notifyDaemon-DWIfzEE7.js");
6831
+ const daemon = await import("./notifyDaemon-BIo7Nkk2.js");
6505
6832
  switch (sub) {
6506
6833
  case "run": return daemon.runDaemon();
6507
6834
  case "once": return daemon.runDaemon({ once: true });
@@ -6528,4 +6855,4 @@ async function cmdNotifyd(rest) {
6528
6855
 
6529
6856
  //#endregion
6530
6857
  export { renderLogTailLines as $, isAgentStuck as A, lastStdinAt as B, extractBadges as C, finalizedLines as D, extractTaskCounts as E, isSubcommand as F, probeStdinReachable as G, matchKeyword as H, isUnknownManagerToken as I, readLogForRender as J, readAgentPtysize as K, isUnreachableWriteErrno as L, isExitRequest as M, isPidAlive as N, inspectSubmission as O, isSlashCommand as P, recentReadEdges as Q, isUserTyping as R, envelopeCostFor as S, extractNeedsInput as T, menuSelectKeys as U, listRecords as V, ownedCollapsedPaste as W, readPtysize as X, readNotes as Y, recentMessageEdges as Z, cwdLabel as _, shouldFramePaste as _t, READ_WINDOW_MS as a, restartHintLines as at, drainerLauncher as b, flattenForest as bt, SEND_EXIT_UNREACHABLE as c, snapshotStatus as ct, buildEnvelope as d, submitAndConfirm as dt, renderRawLog as et, cliDefaults as f, waitForLogQuiet as ft, cursorAbs as g, framePaste as gt, controlCodeFromName as h, withIpcLock as ht, READ_PAGE_DEFAULT as i, resolveResumeArgs as it, isBareManagerInvocation as j, isAgentIdShaped as k, TYPING_WINDOW_MS as l, stdinActivityPath as lt, confirmStdinUnreachable as m, writeToIpc as mt, GRACEFUL_EXIT_COMMANDS as n, resolveOne as nt, SEND_BODY_MAX_CHARS as o, runSubcommand as ot, cmdHelp as p, writeKeysPaced as pt, readComposerState as q, MAX_RENDER_BYTES as r, resolveReadWindow as rt, SEND_EXIT_QUEUED as s, sendPayloadCapError as st, COMPOSER_WINDOWS as t, renderRawLogLines as tt, backoffWhileTyping as u, stopTipForCli as ut, deriveLiveState as v, TYPING_BADGE as vt, extractMenu as w, envelopeAttribution as x, deriveLiveStatus as y, buildAgentForest as yt, lastReadAt as z };
6531
- //# sourceMappingURL=subcommands-UWLkF5pq.js.map
6858
+ //# sourceMappingURL=subcommands-Ddo2TYZs.js.map