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.
- package/dist/{SUPPORTED_CLIS-AFV5pjSx.js → SUPPORTED_CLIS-BByfWaoz.js} +2 -2
- package/dist/{SUPPORTED_CLIS-DRVuSo48.js → SUPPORTED_CLIS-Bob214Yb.js} +3 -3
- package/dist/{agentShare-qdFSp9Uq.js → agentShare-TGChdMCD.js} +3 -3
- package/dist/{callback-DfNFdZin.js → callback-B2thylEC.js} +3 -3
- package/dist/{callback-Da7MRLh6.js → callback-D93yB1xz.js} +3 -3
- package/dist/cli.js +11 -11
- package/dist/cmdPs-D7NlwNFV.js +577 -0
- package/dist/{cwdConflictWarn-CtQhl-vF.js → cwdConflictWarn-BpweoAiy.js} +1 -1
- package/dist/{cwdPassthroughHint-apaX_O7p.js → cwdPassthroughHint-BlRuOGo_.js} +1 -1
- package/dist/{expose-BXn6My6A.js → expose-DyiZ5AU0.js} +1 -1
- package/dist/{forkNested-CDfWPiHw.js → forkNested-B_4c-TQk.js} +1 -1
- package/dist/{hist-BpocAe1N.js → hist-BoZPJ2ah.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{initMsg-C-w4A_eF.js → initMsg-BKqWDYFk.js} +1 -1
- package/dist/{initMsg-BjRseolL.js → initMsg-D8cmdY76.js} +1 -1
- package/dist/{nodeRuntime-CRoEMWkp.js → nodeRuntime-QihGFQbI.js} +1 -1
- package/dist/{notifyDaemon-BFzqcdN1.js → notifyDaemon-B-vCwFi3.js} +3 -3
- package/dist/{openBrowser-DhQ9pJRi.js → openBrowser-Cs46NiH6.js} +1 -1
- package/dist/{parentLink-B798ASCW.js → parentLink-CQrn998L.js} +1 -1
- package/dist/{parentLink-C0cIRjfL.js → parentLink-CTgZZCgw.js} +1 -1
- package/dist/{parentPingLoop-BIKxzbPH.js → parentPingLoop-CVD0sREv.js} +4 -4
- package/dist/{remotes-iMhdZytp.js → remotes-DdAfueOu.js} +2 -2
- package/dist/{remotes-Dg_JscKk.js → remotes-M8FbM92D.js} +1 -1
- package/dist/{rustBinary-JGNROWfY.js → rustBinary-Bf9514x8.js} +2 -2
- package/dist/{schedule-DbLpjgl5.js → schedule-fgKVzax1.js} +6 -6
- package/dist/{serve-ucVXOci1.js → serve-DM5-VJPX.js} +26 -26
- package/dist/{setup-De7uYRZb.js → setup-DNuxXwYS.js} +4 -4
- package/dist/{spawnGate-BvaZeHE7.js → spawnGate-CZlMs6uV.js} +1 -1
- package/dist/{spawnGate-CEXVmTtv.js → spawnGate-DN4wNXHq.js} +2 -2
- package/dist/{subcommands-C04exkIM.js → subcommands-3rBreZdT.js} +2 -2
- package/dist/{subcommands-DtF31RoW.js → subcommands-ujgVZRT3.js} +24 -24
- package/dist/{systemPathLink-7TFPwJna.js → systemPathLink-BHv1I6wh.js} +1 -1
- package/dist/{terminal-Dm32SXU-.js → terminal-jJ2YDBDS.js} +2 -2
- package/dist/{todoCli-c_R7D7Y-.js → todoCli-BYGZ7_C5.js} +1 -1
- package/dist/{tray-DZnGjJBK.js → tray-CzGs9-ch.js} +1 -1
- package/dist/trayApp-CPDWlxfV.js +5 -0
- package/dist/{trayApp-CfkPG0wy.js → trayApp-DbV2E8dZ.js} +2 -2
- package/dist/{ts-CGtoPER5.js → ts-CQIqcW7_.js} +5 -5
- package/dist/{versionChecker-CJgW9DHY.js → versionChecker-D49xkrMv.js} +2 -2
- package/dist/{webrtcRemote-BDg0jjTs.js → webrtcRemote-Bl8eowma.js} +1 -1
- package/dist/{widget-DXtaX3QR.js → widget-CRXwzRP7.js} +3 -3
- package/dist/{workspaceConfig-HiWQzq6A.js → workspaceConfig-CBKlgA_q.js} +1 -1
- package/dist/{ws-aKRZpI9b.js → ws-B4ZoCy71.js} +3 -3
- package/dist/{ws-CqlS5JhN.js → ws-DomYQT_I.js} +3 -3
- package/lab/ui/sw.js +10 -1
- package/package.json +1 -1
- package/ts/cmdPs.spec.ts +389 -0
- package/ts/cmdPs.ts +343 -0
- package/ts/procStats.spec.ts +506 -0
- package/ts/procStats.ts +484 -0
- package/ts/subcommands.ts +9 -4
- 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
|
|
@@ -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-
|
|
141
|
+
//# sourceMappingURL=forkNested-B_4c-TQk.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import "./versionChecker-
|
|
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-
|
|
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";
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
866
|
+
//# sourceMappingURL=notifyDaemon-B-vCwFi3.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-
|
|
3
|
+
import { n as spawnerFromRecords, t as resolveSpawner } from "./parentLink-CTgZZCgw.js";
|
|
4
4
|
|
|
5
5
|
export { resolveSpawner };
|