agent-yes 1.266.0 → 1.267.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 (52) hide show
  1. package/dist/{SUPPORTED_CLIS-AFV5pjSx.js → SUPPORTED_CLIS-BByfWaoz.js} +2 -2
  2. package/dist/{SUPPORTED_CLIS-DRVuSo48.js → SUPPORTED_CLIS-Bob214Yb.js} +3 -3
  3. package/dist/{agentShare-qdFSp9Uq.js → agentShare-TGChdMCD.js} +3 -3
  4. package/dist/{callback-DfNFdZin.js → callback-B2thylEC.js} +3 -3
  5. package/dist/{callback-Da7MRLh6.js → callback-D93yB1xz.js} +3 -3
  6. package/dist/cli.js +11 -11
  7. package/dist/cmdPs-D7NlwNFV.js +577 -0
  8. package/dist/{cwdConflictWarn-CtQhl-vF.js → cwdConflictWarn-BpweoAiy.js} +1 -1
  9. package/dist/{cwdPassthroughHint-apaX_O7p.js → cwdPassthroughHint-BlRuOGo_.js} +1 -1
  10. package/dist/{expose-BXn6My6A.js → expose-DyiZ5AU0.js} +1 -1
  11. package/dist/{forkNested-CDfWPiHw.js → forkNested-B_4c-TQk.js} +1 -1
  12. package/dist/{hist-BpocAe1N.js → hist-BoZPJ2ah.js} +1 -1
  13. package/dist/index.js +2 -2
  14. package/dist/{initMsg-C-w4A_eF.js → initMsg-BKqWDYFk.js} +1 -1
  15. package/dist/{initMsg-BjRseolL.js → initMsg-D8cmdY76.js} +1 -1
  16. package/dist/{nodeRuntime-CRoEMWkp.js → nodeRuntime-QihGFQbI.js} +1 -1
  17. package/dist/{notifyDaemon-BFzqcdN1.js → notifyDaemon-B-vCwFi3.js} +3 -3
  18. package/dist/{openBrowser-DhQ9pJRi.js → openBrowser-Cs46NiH6.js} +1 -1
  19. package/dist/{parentLink-B798ASCW.js → parentLink-CQrn998L.js} +1 -1
  20. package/dist/{parentLink-C0cIRjfL.js → parentLink-CTgZZCgw.js} +1 -1
  21. package/dist/{parentPingLoop-BIKxzbPH.js → parentPingLoop-CVD0sREv.js} +4 -4
  22. package/dist/{remotes-iMhdZytp.js → remotes-DdAfueOu.js} +2 -2
  23. package/dist/{remotes-Dg_JscKk.js → remotes-M8FbM92D.js} +1 -1
  24. package/dist/{rustBinary-JGNROWfY.js → rustBinary-Bf9514x8.js} +2 -2
  25. package/dist/{schedule-DbLpjgl5.js → schedule-fgKVzax1.js} +6 -6
  26. package/dist/{serve-ucVXOci1.js → serve-DM5-VJPX.js} +26 -26
  27. package/dist/{setup-De7uYRZb.js → setup-DNuxXwYS.js} +4 -4
  28. package/dist/{spawnGate-BvaZeHE7.js → spawnGate-CZlMs6uV.js} +1 -1
  29. package/dist/{spawnGate-CEXVmTtv.js → spawnGate-DN4wNXHq.js} +2 -2
  30. package/dist/{subcommands-C04exkIM.js → subcommands-3rBreZdT.js} +2 -2
  31. package/dist/{subcommands-DtF31RoW.js → subcommands-ujgVZRT3.js} +24 -24
  32. package/dist/{systemPathLink-7TFPwJna.js → systemPathLink-BHv1I6wh.js} +1 -1
  33. package/dist/{terminal-Dm32SXU-.js → terminal-jJ2YDBDS.js} +2 -2
  34. package/dist/{todoCli-c_R7D7Y-.js → todoCli-BYGZ7_C5.js} +1 -1
  35. package/dist/{tray-DZnGjJBK.js → tray-CzGs9-ch.js} +1 -1
  36. package/dist/trayApp-CPDWlxfV.js +5 -0
  37. package/dist/{trayApp-CfkPG0wy.js → trayApp-DbV2E8dZ.js} +2 -2
  38. package/dist/{ts-CGtoPER5.js → ts-CQIqcW7_.js} +5 -5
  39. package/dist/{versionChecker-CJgW9DHY.js → versionChecker-D49xkrMv.js} +2 -2
  40. package/dist/{webrtcRemote-BDg0jjTs.js → webrtcRemote-Bl8eowma.js} +1 -1
  41. package/dist/{widget-DXtaX3QR.js → widget-CRXwzRP7.js} +3 -3
  42. package/dist/{workspaceConfig-HiWQzq6A.js → workspaceConfig-CBKlgA_q.js} +1 -1
  43. package/dist/{ws-aKRZpI9b.js → ws-B4ZoCy71.js} +3 -3
  44. package/dist/{ws-CqlS5JhN.js → ws-DomYQT_I.js} +3 -3
  45. package/lab/ui/sw.js +10 -1
  46. package/package.json +1 -1
  47. package/ts/cmdPs.spec.ts +389 -0
  48. package/ts/cmdPs.ts +343 -0
  49. package/ts/procStats.spec.ts +506 -0
  50. package/ts/procStats.ts +484 -0
  51. package/ts/subcommands.ts +9 -4
  52. package/dist/trayApp-CZbwLK9t.js +0 -5
@@ -0,0 +1,577 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import "./globalPidIndex-Dq4KY2IV.js";
3
+ import "./messageLog-BYFwioFY.js";
4
+ import "./e2e-CUMZC53I.js";
5
+ import "./configShared-DtHMBCl7.js";
6
+ import { $ as buildAgentForest, O as listRecords, d as deriveLiveState, et as flattenForest } from "./subcommands-ujgVZRT3.js";
7
+ import "./notifyStore-Cbw2ZJ0X.js";
8
+ import "./webrtcLink-DyCj380i.js";
9
+ import "./remotes-DdAfueOu.js";
10
+ import yargs from "yargs";
11
+ import { execFile } from "node:child_process";
12
+ import { readFileSync } from "node:fs";
13
+ import path from "node:path";
14
+ import { homedir } from "node:os";
15
+ import { readFile, readdir } from "node:fs/promises";
16
+ import { promisify } from "node:util";
17
+
18
+ //#region ts/procStats.ts
19
+ /**
20
+ * Per-process-TREE resource rollup, for `ay ps`.
21
+ *
22
+ * Why this exists: `ay ls` lists WRAPPER pids, but a single ay-managed agent is
23
+ * a whole subtree — the wrapper, the CLI, its version process, `bg-pty-host`,
24
+ * `bg-spare`, the daemon, plus whatever the agent itself spawned. On a busy box
25
+ * one session can be 30+ processes. `ps`/`htop`/`glances` see those as
26
+ * unrelated rows and cannot attribute them to an agent; only ay knows the
27
+ * wrapper pids, so only ay can roll the cost up to "this session costs 612 MiB".
28
+ *
29
+ * Two things here are deliberately NOT the obvious approach:
30
+ *
31
+ * 1. CPU is a SAMPLED DELTA, never `ps`'s lifetime average. A session alive for
32
+ * 7 days shows a meaningless smeared percentage; "is this agent busy right
33
+ * now" needs two reads of the cumulative counter a moment apart. That's why
34
+ * the caller must await a sampling window — see `sampleTrees`.
35
+ *
36
+ * 2. Rollup walks the FULL descendant closure, not direct children. Agents spawn
37
+ * subagents that spawn shells; a depth-1 sum undercounts badly.
38
+ *
39
+ * BEST-EFFORT by contract, like `procStartTime.ts`: every reader returns null /
40
+ * empty when it cannot answer (no /proc, unreadable pid, a process that exited
41
+ * mid-sample). Callers render "-" rather than failing — this is an inspection
42
+ * command, it must never be the thing that breaks.
43
+ */
44
+ const execFileAsync = promisify(execFile);
45
+ /**
46
+ * USER_HZ. The kernel reports utime/stime in clock ticks, and this is 100 on
47
+ * every Linux ABI we run on — it is fixed at the syscall boundary, NOT the
48
+ * configurable CONFIG_HZ. There is no cheap way to query it from Node.
49
+ */
50
+ const USER_HZ = 100;
51
+ /** Assumed page size when we cannot measure the real one. */
52
+ const DEFAULT_PAGE_SIZE = 4096;
53
+ /**
54
+ * Real page size for the `rss` field of /proc/<pid>/stat, which is in PAGES.
55
+ *
56
+ * Hardcoding 4 KiB under-reports by 4× on a 16 KiB-page arm64 kernel, so measure
57
+ * it instead — for free, with no `getconf` spawn: /proc/self/status reports our
58
+ * own VmRSS in kB while /proc/self/stat reports the same figure in pages, and
59
+ * the ratio is the page size. Snapped to the nearest supported power of two
60
+ * because VmRSS is kB-rounded. Measured once per process, then cached.
61
+ */
62
+ let cachedPageSize = null;
63
+ function derivePageSize(statLine, statusText) {
64
+ const close = statLine.lastIndexOf(")");
65
+ if (close < 0) return DEFAULT_PAGE_SIZE;
66
+ const pages = Number(statLine.slice(close + 1).trim().split(/\s+/)[21]);
67
+ const kb = Number(/^VmRSS:\s+(\d+)/m.exec(statusText)?.[1]);
68
+ if (!Number.isFinite(pages) || !Number.isFinite(kb) || pages <= 0 || kb <= 0) return DEFAULT_PAGE_SIZE;
69
+ const raw = kb * 1024 / pages;
70
+ let best = DEFAULT_PAGE_SIZE;
71
+ for (const candidate of [
72
+ 4096,
73
+ 8192,
74
+ 16384,
75
+ 65536
76
+ ]) if (Math.abs(raw - candidate) < Math.abs(raw - best)) best = candidate;
77
+ return best;
78
+ }
79
+ async function pageSize(deps) {
80
+ if (cachedPageSize !== null) return cachedPageSize;
81
+ const readSys = deps?.readSys ?? defaultReadSys;
82
+ const [stat, status] = await Promise.all([readSys("self/stat"), readSys("self/status")]);
83
+ cachedPageSize = stat && status ? derivePageSize(stat, status) : DEFAULT_PAGE_SIZE;
84
+ return cachedPageSize;
85
+ }
86
+ /**
87
+ * Parse the fields of a /proc/<pid>/stat line that we need.
88
+ *
89
+ * As in `parseLinuxStartTime`, the parse MUST start after the last `)`: field 2
90
+ * is the comm and may contain spaces and parens, so naive splitting mis-indexes
91
+ * everything after it. Post-`)` fields begin at field 3, so field N is at index
92
+ * N-3: state=3→0, ppid=4→1, utime=14→11, stime=15→12, starttime=22→19, rss=24→21.
93
+ */
94
+ function parseProcStat(pid, stat, pageSizeBytes = DEFAULT_PAGE_SIZE) {
95
+ const close = stat.lastIndexOf(")");
96
+ if (close < 0) return null;
97
+ const f = stat.slice(close + 1).trim().split(/\s+/);
98
+ const state = f[0];
99
+ const ppid = Number(f[1]);
100
+ const utime = Number(f[11]);
101
+ const stime = Number(f[12]);
102
+ const rssPages = Number(f[21]);
103
+ if (!state || !Number.isFinite(ppid) || !Number.isFinite(utime) || !Number.isFinite(stime)) return null;
104
+ return {
105
+ pid,
106
+ ppid,
107
+ rss: Number.isFinite(rssPages) ? rssPages * pageSizeBytes : 0,
108
+ cpuSeconds: (utime + stime) / USER_HZ,
109
+ state,
110
+ startToken: f[19] ?? ""
111
+ };
112
+ }
113
+ async function defaultListPids() {
114
+ try {
115
+ const entries = await readdir("/proc");
116
+ const out = [];
117
+ for (const e of entries) if (/^\d+$/.test(e)) out.push(Number(e));
118
+ return out;
119
+ } catch {
120
+ return [];
121
+ }
122
+ }
123
+ async function defaultReadStat(pid) {
124
+ try {
125
+ return await readFile(`/proc/${pid}/stat`, "utf8");
126
+ } catch {
127
+ return null;
128
+ }
129
+ }
130
+ async function defaultReadSys(name) {
131
+ try {
132
+ return await readFile(`/proc/${name}`, "utf8");
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+ async function defaultReadPsTable() {
138
+ try {
139
+ const { stdout } = await execFileAsync("ps", ["-eo", "pid=,ppid=,rss=,time=,state="], {
140
+ encoding: "utf8",
141
+ timeout: 3e3,
142
+ maxBuffer: 16 * 1024 * 1024
143
+ });
144
+ return stdout;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+ /** Parse `[[dd-]hh:]mm:ss` (ps `time=`) into seconds. */
150
+ function parsePsTime(raw) {
151
+ const [dayPart, clockPart] = raw.includes("-") ? raw.split("-", 2) : [null, raw];
152
+ const parts = (clockPart ?? "").split(":").map(Number);
153
+ if (parts.some((n) => !Number.isFinite(n))) return 0;
154
+ let seconds = 0;
155
+ for (const p of parts) seconds = seconds * 60 + p;
156
+ if (dayPart !== null) {
157
+ const days = Number(dayPart);
158
+ if (Number.isFinite(days)) seconds += days * 86400;
159
+ }
160
+ return seconds;
161
+ }
162
+ /** Parse the `ps -eo pid=,ppid=,rss=,time=,state=` fallback table. */
163
+ function parsePsTable(out) {
164
+ const procs = /* @__PURE__ */ new Map();
165
+ for (const line of out.split("\n")) {
166
+ const m = line.trim().split(/\s+/);
167
+ if (m.length < 5) continue;
168
+ const [pidS, ppidS, rssS, timeS, state] = m;
169
+ const pid = Number(pidS);
170
+ const ppid = Number(ppidS);
171
+ const rssKib = Number(rssS);
172
+ if (!Number.isFinite(pid) || !Number.isFinite(ppid)) continue;
173
+ procs.set(pid, {
174
+ pid,
175
+ ppid,
176
+ rss: Number.isFinite(rssKib) ? rssKib * 1024 : 0,
177
+ cpuSeconds: parsePsTime(timeS ?? ""),
178
+ state: state ?? "?",
179
+ startToken: ""
180
+ });
181
+ }
182
+ return procs;
183
+ }
184
+ /** One snapshot of every visible process, keyed by pid. */
185
+ async function snapshotProcs(deps) {
186
+ if ((deps?.platform ?? process.platform) !== "linux" && !deps?.listPids) {
187
+ const table = await (deps?.readPsTable ?? defaultReadPsTable)();
188
+ return table ? parsePsTable(table) : /* @__PURE__ */ new Map();
189
+ }
190
+ const pids = await (deps?.listPids ?? defaultListPids)();
191
+ const readStat = deps?.readStat ?? defaultReadStat;
192
+ const bytesPerPage = await pageSize(deps);
193
+ const procs = /* @__PURE__ */ new Map();
194
+ const samples = await Promise.all(pids.map(async (pid) => {
195
+ const stat = await readStat(pid);
196
+ return stat ? parseProcStat(pid, stat, bytesPerPage) : null;
197
+ }));
198
+ for (const s of samples) if (s) procs.set(s.pid, s);
199
+ return procs;
200
+ }
201
+ /** ppid → children index, for descendant walks. */
202
+ function buildChildIndex(procs) {
203
+ const kids = /* @__PURE__ */ new Map();
204
+ for (const p of procs.values()) {
205
+ const list = kids.get(p.ppid);
206
+ if (list) list.push(p.pid);
207
+ else kids.set(p.ppid, [p.pid]);
208
+ }
209
+ return kids;
210
+ }
211
+ /**
212
+ * Every pid in the subtree rooted at `root`, inclusive.
213
+ *
214
+ * Iterative with a seen-set: a corrupt/racing snapshot can present a parent
215
+ * cycle, and recursion would blow the stack rather than degrade.
216
+ */
217
+ function descendantsOf(root, kids, seen = /* @__PURE__ */ new Set()) {
218
+ const out = /* @__PURE__ */ new Set();
219
+ const stack = [root];
220
+ while (stack.length > 0) {
221
+ const pid = stack.pop();
222
+ if (out.has(pid) || seen.has(pid)) continue;
223
+ out.add(pid);
224
+ for (const child of kids.get(pid) ?? []) stack.push(child);
225
+ }
226
+ return out;
227
+ }
228
+ function rollup(root, members, first, second, elapsedSeconds) {
229
+ let rss = 0;
230
+ let cpuDelta = 0;
231
+ let count = 0;
232
+ for (const pid of members) {
233
+ const now = second.get(pid);
234
+ if (!now) continue;
235
+ count++;
236
+ rss += now.rss;
237
+ const before = first.get(pid);
238
+ const reused = before !== void 0 && before.startToken !== "" && now.startToken !== "" && before.startToken !== now.startToken;
239
+ if (before && !reused) cpuDelta += Math.max(0, now.cpuSeconds - before.cpuSeconds);
240
+ }
241
+ return {
242
+ pid: root,
243
+ rss,
244
+ cpuPercent: elapsedSeconds > 0 ? cpuDelta / elapsedSeconds * 100 : 0,
245
+ procs: count
246
+ };
247
+ }
248
+ /**
249
+ * Sample every root's subtree over a real time window.
250
+ *
251
+ * Roots are attributed in the order given, and a pid is claimed by the FIRST
252
+ * root that reaches it — so a nested agent whose wrapper is itself listed as a
253
+ * root is never double-counted into its parent's total.
254
+ *
255
+ * Pass DEEPEST-FIRST (children before parents). A nested agent must claim its
256
+ * own subtree before its parent walks through it; parent-first instead makes the
257
+ * parent absorb everything and renders the child as a bogus 0-proc row.
258
+ */
259
+ async function sampleTrees(roots, opts = {}) {
260
+ const windowMs = Math.max(100, opts.windowMs ?? 1e3);
261
+ const firstStart = Date.now();
262
+ const first = await snapshotProcs(opts.deps);
263
+ const firstMid = (firstStart + Date.now()) / 2;
264
+ await new Promise((r) => setTimeout(r, windowMs));
265
+ const secondStart = Date.now();
266
+ const second = await snapshotProcs(opts.deps);
267
+ const secondMid = (secondStart + Date.now()) / 2;
268
+ const elapsedSeconds = Math.max(.001, (secondMid - firstMid) / 1e3);
269
+ const kids = buildChildIndex(second);
270
+ const claimed = /* @__PURE__ */ new Set();
271
+ const trees = /* @__PURE__ */ new Map();
272
+ for (const root of roots) {
273
+ const members = descendantsOf(root, kids, claimed);
274
+ for (const pid of members) claimed.add(pid);
275
+ trees.set(root, rollup(root, members, first, second, elapsedSeconds));
276
+ }
277
+ const rest = /* @__PURE__ */ new Set();
278
+ for (const pid of second.keys()) if (!claimed.has(pid)) rest.add(pid);
279
+ return {
280
+ trees,
281
+ unattributed: rollup(0, rest, first, second, elapsedSeconds)
282
+ };
283
+ }
284
+ /** Parse /proc/meminfo (`MemTotal: 16316360 kB`) into a kB-valued map. */
285
+ function parseMeminfo(raw) {
286
+ const out = /* @__PURE__ */ new Map();
287
+ for (const line of raw.split("\n")) {
288
+ const m = /^(\w+):\s+(\d+)/.exec(line);
289
+ if (m?.[1] && m[2]) out.set(m[1], Number(m[2]) * 1024);
290
+ }
291
+ return out;
292
+ }
293
+ async function systemStats(procs, deps) {
294
+ const readSys = deps?.readSys ?? defaultReadSys;
295
+ const [loadRaw, memRaw] = await Promise.all([readSys("loadavg"), readSys("meminfo")]);
296
+ const loadFields = loadRaw?.trim().split(/\s+/) ?? [];
297
+ const load = loadFields.length >= 3 ? [
298
+ Number(loadFields[0]),
299
+ Number(loadFields[1]),
300
+ Number(loadFields[2])
301
+ ] : null;
302
+ const mem = memRaw ? parseMeminfo(memRaw) : /* @__PURE__ */ new Map();
303
+ let zombies = 0;
304
+ for (const p of procs.values()) if (p.state === "Z") zombies++;
305
+ const os = await import("node:os");
306
+ const osLoad = os.loadavg();
307
+ const loadFallback = process.platform !== "win32" && osLoad.some((n) => n > 0) ? [
308
+ osLoad[0],
309
+ osLoad[1],
310
+ osLoad[2]
311
+ ] : null;
312
+ return {
313
+ load: load && load.every(Number.isFinite) ? load : loadFallback,
314
+ ncpu: os.cpus().length || 1,
315
+ memTotalBytes: mem.get("MemTotal") ?? os.totalmem() ?? null,
316
+ memAvailableBytes: mem.get("MemAvailable") ?? os.freemem() ?? null,
317
+ swapTotalBytes: mem.get("SwapTotal") ?? null,
318
+ swapFreeBytes: mem.get("SwapFree") ?? null,
319
+ zombies
320
+ };
321
+ }
322
+ /** `612Mi`, `1.4Gi` — fixed-width-ish, no trailing noise. */
323
+ function humanBytes(bytes) {
324
+ if (bytes === null || !Number.isFinite(bytes)) return "-";
325
+ for (const [scale, suffix] of [
326
+ [1024 ** 3, "Gi"],
327
+ [1024 ** 2, "Mi"],
328
+ [1024, "Ki"]
329
+ ]) if (bytes >= scale) {
330
+ const v = bytes / scale;
331
+ return `${v >= 100 ? Math.round(v) : v.toFixed(1)}${suffix}`;
332
+ }
333
+ return `${Math.round(bytes)}B`;
334
+ }
335
+
336
+ //#endregion
337
+ //#region ts/cmdPs.ts
338
+ /**
339
+ * `ay ps` — what each agent COSTS, rolled up per process tree.
340
+ *
341
+ * The split from `ay ls`: `ls` answers "what is running and what is it doing"
342
+ * (prompt, git, badges, forest); `ps` answers "what is this box spending on
343
+ * each agent, and is any of it safe to reclaim". Same rows, different question.
344
+ *
345
+ * `ps` used to be a plain alias for `ls`. It is now its own command, because the
346
+ * number you actually want is not in any `ps`/`htop` output: one agent is a
347
+ * whole subtree of wrapper + CLI + pty hosts + subagents, and only ay knows
348
+ * which pids collapse into which session.
349
+ *
350
+ * Deliberately local-only. Resource numbers are per-machine and there is nothing
351
+ * meaningful to sum across remotes; `ay ls` remains the fleet-wide view.
352
+ */
353
+ /** Default CPU sampling window. Long enough to be meaningful, short enough to feel instant. */
354
+ const DEFAULT_WINDOW_MS = 1e3;
355
+ function shortenPath(p) {
356
+ const home = homedir();
357
+ return p.startsWith(home) ? "~" + p.slice(home.length) : p;
358
+ }
359
+ /** Trailing path segment that identifies a worktree — `/code/x/foo/tree/main` → `foo`. */
360
+ function repoLabel(cwd) {
361
+ const parts = cwd.split(/[\\/]+/).filter(Boolean);
362
+ const treeAt = parts.lastIndexOf("tree");
363
+ if (treeAt > 0) return parts[treeAt - 1];
364
+ return parts[parts.length - 1] ?? shortenPath(cwd);
365
+ }
366
+ /** `15.23 12.52 9.86 (8 cpu, 1.9x) mem 13.0/15Gi swap 4.8/4.8Gi FULL zombies 1440` */
367
+ function formatSystemLine(sys) {
368
+ const bits = [];
369
+ if (sys.load) {
370
+ const [one, five, fifteen] = sys.load;
371
+ const ratio = one / Math.max(1, sys.ncpu);
372
+ bits.push(`load ${one.toFixed(2)} ${five.toFixed(2)} ${fifteen.toFixed(2)} (${sys.ncpu} cpu, ${ratio.toFixed(1)}x)`);
373
+ }
374
+ if (sys.memTotalBytes !== null) {
375
+ const used = sys.memAvailableBytes !== null ? sys.memTotalBytes - sys.memAvailableBytes : null;
376
+ bits.push(`mem ${humanBytes(used)}/${humanBytes(sys.memTotalBytes)}`);
377
+ }
378
+ if (sys.swapTotalBytes !== null && sys.swapTotalBytes > 0) {
379
+ const usedSwap = sys.swapFreeBytes !== null ? sys.swapTotalBytes - sys.swapFreeBytes : null;
380
+ const full = sys.swapFreeBytes !== null && sys.swapFreeBytes <= 0 ? " FULL" : "";
381
+ bits.push(`swap ${humanBytes(usedSwap)}/${humanBytes(sys.swapTotalBytes)}${full}`);
382
+ }
383
+ if (sys.zombies > 0) bits.push(`zombies ${sys.zombies}`);
384
+ return bits.join(" ");
385
+ }
386
+ function pad(s, n) {
387
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
388
+ }
389
+ function padStart(s, n) {
390
+ return s.length >= n ? s : " ".repeat(n - s.length) + s;
391
+ }
392
+ function renderTable(rows, unattributed) {
393
+ const cells = rows.map((r) => ({
394
+ pid: String(r.pid),
395
+ cli: r.cli,
396
+ status: r.status,
397
+ cpu: r.stats.cpuPercent.toFixed(1),
398
+ rss: humanBytes(r.stats.rss),
399
+ procs: String(r.stats.procs),
400
+ repo: r.repo,
401
+ self: r.self
402
+ }));
403
+ if (unattributed && unattributed.procs > 0) cells.push({
404
+ pid: "-",
405
+ cli: "unmanaged",
406
+ status: "-",
407
+ cpu: unattributed.cpuPercent.toFixed(1),
408
+ rss: humanBytes(unattributed.rss),
409
+ procs: String(unattributed.procs),
410
+ repo: "-",
411
+ self: false
412
+ });
413
+ const w = {
414
+ pid: Math.max(3, ...cells.map((c) => c.pid.length)),
415
+ cli: Math.max(3, ...cells.map((c) => c.cli.length)),
416
+ status: Math.max(6, ...cells.map((c) => c.status.length)),
417
+ cpu: Math.max(5, ...cells.map((c) => c.cpu.length)),
418
+ rss: Math.max(5, ...cells.map((c) => c.rss.length)),
419
+ procs: Math.max(5, ...cells.map((c) => c.procs.length)),
420
+ repo: Math.max(4, ...cells.map((c) => c.repo.length))
421
+ };
422
+ const line = (pid, cli, status, cpu, rss, procs, repo, tail = "") => `${pad(pid, w.pid)} ${pad(cli, w.cli)} ${pad(status, w.status)} ${padStart(cpu, w.cpu)} ${padStart(rss, w.rss)} ${padStart(procs, w.procs)} ${pad(repo, w.repo)}${tail}`;
423
+ const out = [line("PID", "CLI", "STATUS", "CPU%", "RSS", "PROCS", "REPO")];
424
+ for (const c of cells) out.push(line(c.pid, c.cli, c.status, c.cpu, c.rss, c.procs, c.repo, c.self ? " ← this session" : ""));
425
+ return out.join("\n");
426
+ }
427
+ async function cmdPs(rest) {
428
+ const y = yargs(rest).usage("Usage: ay ps [keyword] [options]\n\nPer-agent resource usage, rolled up across each agent's whole process tree\n(wrapper + CLI + pty hosts + subagents). Local-only; use `ay ls` for the\nfleet view and per-agent prompts.").option("all", {
429
+ type: "boolean",
430
+ default: false,
431
+ description: "Include exited agents"
432
+ }).option("json", {
433
+ type: "boolean",
434
+ default: false,
435
+ description: "Output as JSON"
436
+ }).option("sort", {
437
+ type: "string",
438
+ choices: [
439
+ "rss",
440
+ "cpu",
441
+ "procs",
442
+ "pid"
443
+ ],
444
+ default: "rss",
445
+ description: "Sort rows by resource usage (default: rss, biggest first)"
446
+ }).option("tree", {
447
+ type: "boolean",
448
+ default: false,
449
+ description: "Keep ay ls's parent>child forest order instead of sorting"
450
+ }).option("interval", {
451
+ type: "number",
452
+ default: DEFAULT_WINDOW_MS / 1e3,
453
+ description: "CPU sampling window in seconds — CPU% is a live delta, not a lifetime average"
454
+ }).option("cwd", {
455
+ type: "string",
456
+ description: "Restrict to agents whose cwd starts with dir"
457
+ }).option("help", {
458
+ alias: "h",
459
+ type: "boolean",
460
+ default: false,
461
+ description: "Show this help"
462
+ }).example("ay ps", "biggest agents first, with box vitals").example("ay ps --sort cpu", "who is actually burning CPU right now").example("ay ps --json", "machine-readable rollup").help(false).version(false).exitProcess(false);
463
+ const argv = await y.parseAsync();
464
+ if (argv.help || argv.h) {
465
+ process.stdout.write(await y.getHelp() + "\n");
466
+ return 0;
467
+ }
468
+ const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
469
+ const records = await listRecords(keyword, {
470
+ all: argv.all,
471
+ active: false,
472
+ json: false,
473
+ latest: false,
474
+ cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
475
+ });
476
+ if (records.length === 0) {
477
+ process.stderr.write(keyword ? `no agents matched "${keyword}"\n` : "no running agents\n");
478
+ return 0;
479
+ }
480
+ const ordered = flattenForest(buildAgentForest(records)).map((r) => r.record);
481
+ const windowMs = Math.max(100, (Number.isFinite(argv.interval) ? argv.interval : 1) * 1e3);
482
+ const { trees, unattributed } = await sampleTrees([...ordered].reverse().map((r) => r.pid), { windowMs });
483
+ const states = new Map(await Promise.all(ordered.map(async (r) => [r.pid, (await deriveLiveState(r)).state])));
484
+ const selfPid = process.pid;
485
+ const rows = ordered.map((r) => ({
486
+ pid: r.pid,
487
+ cli: r.cli,
488
+ status: states.get(r.pid) ?? r.status,
489
+ cwd: r.cwd,
490
+ repo: repoLabel(r.cwd),
491
+ stats: trees.get(r.pid) ?? {
492
+ pid: r.pid,
493
+ rss: 0,
494
+ cpuPercent: 0,
495
+ procs: 0
496
+ },
497
+ self: trees.get(r.pid) !== void 0 && isSelfTree(r.pid, selfPid)
498
+ }));
499
+ if (!argv.tree) {
500
+ const key = String(argv.sort);
501
+ rows.sort((a, b) => {
502
+ if (key === "cpu") return b.stats.cpuPercent - a.stats.cpuPercent;
503
+ if (key === "procs") return b.stats.procs - a.stats.procs;
504
+ if (key === "pid") return a.pid - b.pid;
505
+ return b.stats.rss - a.stats.rss;
506
+ });
507
+ }
508
+ const sys = await systemStats(await snapshotProcs());
509
+ if (argv.json) {
510
+ process.stdout.write(JSON.stringify({
511
+ system: sys,
512
+ agents: rows.map((r) => ({
513
+ pid: r.pid,
514
+ cli: r.cli,
515
+ status: r.status,
516
+ cwd: r.cwd,
517
+ rssBytes: r.stats.rss,
518
+ cpuPercent: Number(r.stats.cpuPercent.toFixed(2)),
519
+ procs: r.stats.procs,
520
+ self: r.self
521
+ })),
522
+ unattributed: {
523
+ rssBytes: unattributed.rss,
524
+ cpuPercent: Number(unattributed.cpuPercent.toFixed(2)),
525
+ procs: unattributed.procs
526
+ }
527
+ }, null, 2) + "\n");
528
+ return 0;
529
+ }
530
+ const header = formatSystemLine(sys);
531
+ if (header) process.stdout.write(` ${header}\n\n`);
532
+ process.stdout.write(renderTable(rows, unattributed) + "\n");
533
+ return 0;
534
+ }
535
+ /**
536
+ * Is `rootPid` the wrapper of the tree this very process lives in?
537
+ *
538
+ * Walks our own ancestry rather than the snapshot: cheap, and correct even when
539
+ * the snapshot raced. Linux-only detail (reads /proc), returns false elsewhere —
540
+ * mislabelling the self row is cosmetic.
541
+ */
542
+ function isSelfTree(rootPid, selfPid, readPpid = readPpidSync, platform = process.platform) {
543
+ if (platform !== "linux") return false;
544
+ let pid = selfPid;
545
+ for (let hops = 0; hops < 64 && pid > 1; hops++) {
546
+ if (pid === rootPid) return true;
547
+ const ppid = readPpid(pid);
548
+ if (ppid === null) return false;
549
+ pid = ppid;
550
+ }
551
+ return false;
552
+ }
553
+ /**
554
+ * ppid out of one `/proc/<pid>/stat` line.
555
+ *
556
+ * Split from the read so the parsing — the part that can actually be wrong — is
557
+ * testable on hosts without /proc. Field 2 is `comm`, wrapped in parens and NOT
558
+ * escaped: a process named `foo) bar (baz` is legal, so the fields after it can
559
+ * only be found from the LAST `)`, never by splitting on whitespace.
560
+ */
561
+ function parsePpidFromStat(stat) {
562
+ const close = stat.lastIndexOf(")");
563
+ if (close < 0) return null;
564
+ const ppid = Number(stat.slice(close + 1).trim().split(/\s+/)[1]);
565
+ return Number.isFinite(ppid) ? ppid : null;
566
+ }
567
+ function readPpidSync(pid) {
568
+ try {
569
+ return parsePpidFromStat(readFileSync(`/proc/${pid}/stat`, "utf8"));
570
+ } catch {
571
+ return null;
572
+ }
573
+ }
574
+
575
+ //#endregion
576
+ export { cmdPs };
577
+ //# sourceMappingURL=cmdPs-D7NlwNFV.js.map
@@ -61,4 +61,4 @@ async function warnIfCwdOccupied(rawArgv) {
61
61
 
62
62
  //#endregion
63
63
  export { warnIfCwdOccupied };
64
- //# sourceMappingURL=cwdConflictWarn-CtQhl-vF.js.map
64
+ //# sourceMappingURL=cwdConflictWarn-BpweoAiy.js.map
@@ -85,4 +85,4 @@ function detectCwdPassthrough(argv) {
85
85
 
86
86
  //#endregion
87
87
  export { SUPPRESS_CWD_WARN_ENV, detectCwdPassthrough };
88
- //# sourceMappingURL=cwdPassthroughHint-apaX_O7p.js.map
88
+ //# sourceMappingURL=cwdPassthroughHint-BlRuOGo_.js.map
@@ -549,4 +549,4 @@ async function cmdExpose(args) {
549
549
 
550
550
  //#endregion
551
551
  export { cmdExpose, ensureExposure, listExposures, stopExposure };
552
- //# sourceMappingURL=expose-BXn6My6A.js.map
552
+ //# sourceMappingURL=expose-DyiZ5AU0.js.map
@@ -138,4 +138,4 @@ async function confirmAgentStarted(pid, graceMs = 1500, aborted) {
138
138
 
139
139
  //#endregion
140
140
  export { buildSpawnTutorial, confirmAgentStarted, predictedLogPath, shouldForkNested, waitForRegistration };
141
- //# sourceMappingURL=forkNested-CDfWPiHw.js.map
141
+ //# sourceMappingURL=forkNested-B_4c-TQk.js.map
@@ -439,4 +439,4 @@ async function cmdHist(rest) {
439
439
 
440
440
  //#endregion
441
441
  export { cmdHist };
442
- //# sourceMappingURL=hist-BpocAe1N.js.map
442
+ //# sourceMappingURL=hist-BoZPJ2ah.js.map
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
- import "./versionChecker-CJgW9DHY.js";
3
- import { a as hasTodoStore, i as answerAsk, l as AgentContext, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-CGtoPER5.js";
2
+ import "./versionChecker-D49xkrMv.js";
3
+ import { a as hasTodoStore, i as answerAsk, l as AgentContext, n as agentYes, o as listAsks, r as config, s as listAsksForProject, t as CLIS_CONFIG } from "./ts-CQIqcW7_.js";
4
4
  import { _ as isKnownKind, a as renderTree, b as statesOf, c as monitorHint, d as openStore, f as DONE_STATE, g as initialState, h as canTransition, i as renderDigest, l as CycleError, m as ORPHANED_STATE, o as unblockedTasks, p as LIFECYCLES, r as openBlockers, s as describeBlock, t as reconcileTodos, u as TodoStore, v as nextStates, x as removeControlCharacters, y as requiredGate } from "./todoAutomation-BQRW614S.js";
5
5
  import "./JsonlStore-CPGIR0Wy.js";
6
6
  import "./globalPidIndex-Dq4KY2IV.js";
@@ -82,4 +82,4 @@ function buildInitMsg(prompt, spawner, nonce) {
82
82
 
83
83
  //#endregion
84
84
  export { replyTargetOf as n, shortenHome as r, buildInitMsg as t };
85
- //# sourceMappingURL=initMsg-C-w4A_eF.js.map
85
+ //# sourceMappingURL=initMsg-BKqWDYFk.js.map
@@ -1,3 +1,3 @@
1
- import { n as replyTargetOf, r as shortenHome, t as buildInitMsg } from "./initMsg-C-w4A_eF.js";
1
+ import { n as replyTargetOf, r as shortenHome, t as buildInitMsg } from "./initMsg-BKqWDYFk.js";
2
2
 
3
3
  export { buildInitMsg };
@@ -53,4 +53,4 @@ async function ensureNodeRuntime(which = Bun.which) {
53
53
 
54
54
  //#endregion
55
55
  export { liveEnv as n, ensureNodeRuntime as t };
56
- //# sourceMappingURL=nodeRuntime-CRoEMWkp.js.map
56
+ //# sourceMappingURL=nodeRuntime-QihGFQbI.js.map
@@ -3,10 +3,10 @@ import "./globalPidIndex-Dq4KY2IV.js";
3
3
  import "./messageLog-BYFwioFY.js";
4
4
  import "./e2e-CUMZC53I.js";
5
5
  import "./configShared-DtHMBCl7.js";
6
- import { L as renderLogTailLines, O as listRecords, d as deriveLiveState, x as isPidAlive } from "./subcommands-DtF31RoW.js";
6
+ import { L as renderLogTailLines, O as listRecords, d as deriveLiveState, x as isPidAlive } from "./subcommands-ujgVZRT3.js";
7
7
  import { f as readInbox, g as daemonLockOwnerPath, h as daemonLockDir, i as gcInboxes, l as listInboxParents, m as shouldStealLock, n as appendEvent, s as hostId, u as liveWatchers, x as notifyDir } from "./notifyStore-Cbw2ZJ0X.js";
8
8
  import "./webrtcLink-DyCj380i.js";
9
- import "./remotes-iMhdZytp.js";
9
+ import "./remotes-DdAfueOu.js";
10
10
  import { mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
11
11
  import { randomUUID } from "node:crypto";
12
12
  import { execFile, execFileSync } from "node:child_process";
@@ -863,4 +863,4 @@ async function ensureDaemon() {
863
863
 
864
864
  //#endregion
865
865
  export { daemonStatus, ensureDaemon, requestDaemonStop, runDaemon };
866
- //# sourceMappingURL=notifyDaemon-BFzqcdN1.js.map
866
+ //# sourceMappingURL=notifyDaemon-B-vCwFi3.js.map
@@ -68,4 +68,4 @@ async function offerOpenInBrowser(url, prompt = "Open in browser now? [Y/n] ") {
68
68
 
69
69
  //#endregion
70
70
  export { offerOpenInBrowser };
71
- //# sourceMappingURL=openBrowser-DhQ9pJRi.js.map
71
+ //# sourceMappingURL=openBrowser-Cs46NiH6.js.map
@@ -1,5 +1,5 @@
1
1
  import "./logger-CDIsZ-Pp.js";
2
2
  import "./globalPidIndex-Dq4KY2IV.js";
3
- import { n as spawnerFromRecords, t as resolveSpawner } from "./parentLink-C0cIRjfL.js";
3
+ import { n as spawnerFromRecords, t as resolveSpawner } from "./parentLink-CTgZZCgw.js";
4
4
 
5
5
  export { resolveSpawner };
@@ -50,4 +50,4 @@ function spawnerFromRecords(records, parentPid) {
50
50
 
51
51
  //#endregion
52
52
  export { spawnerFromRecords as n, resolveSpawner as t };
53
- //# sourceMappingURL=parentLink-C0cIRjfL.js.map
53
+ //# sourceMappingURL=parentLink-CTgZZCgw.js.map