agent-dag 1.36.0 → 1.36.2
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/web/index.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
document.documentElement.setAttribute("data-theme", stored === "light" ? "light" : "dark");
|
|
41
41
|
})();
|
|
42
42
|
</script>
|
|
43
|
-
<script type="module" crossorigin src="/assets/index-
|
|
43
|
+
<script type="module" crossorigin src="/assets/index-DMZuxcfY.js"></script>
|
|
44
44
|
<link rel="stylesheet" crossorigin href="/assets/index-CaAP5Ufx.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-dag",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.2",
|
|
4
4
|
"description": "Live deck of Claude Code and Codex agents — watch tool calls, token spend and every Claude Code subagent on one calm canvas. Also available as npx ccdeck and npx agent-dag.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -268,11 +268,53 @@ async function readSwap(platform = process.platform) {
|
|
|
268
268
|
const TOP_N = 8;
|
|
269
269
|
|
|
270
270
|
/**
|
|
271
|
-
*
|
|
271
|
+
* The `ps` argument list, which is not the same list on both Unixes.
|
|
272
272
|
*
|
|
273
|
-
* `-
|
|
274
|
-
*
|
|
275
|
-
*
|
|
273
|
+
* `-r` was shipped for both and means two different things. On BSD it sorts the
|
|
274
|
+
* output by current CPU, which is the ordering the panel is built around. On
|
|
275
|
+
* Linux procps it is *"restrict the selection to only running processes"* — a
|
|
276
|
+
* filter on state `R`, applied in PID order. A Linux deck therefore listed
|
|
277
|
+
* whatever happened to be on a CPU at the instant of the sample: usually one or
|
|
278
|
+
* two rows on an idle machine, and never the busiest ones, since a process
|
|
279
|
+
* pinning a core while blocked on I/O sits in `D` and one merely burning CPU
|
|
280
|
+
* over time is normally caught in `S`. Nothing errored and nothing was empty,
|
|
281
|
+
* which is why it survived two releases (#492).
|
|
282
|
+
*
|
|
283
|
+
* `--sort=-pcpu` is procps' own way to say what `-r` says on BSD. The column
|
|
284
|
+
* order is deliberately identical on both so one parser reads both, and `comm`
|
|
285
|
+
* stays last so a name containing a space survives intact.
|
|
286
|
+
*
|
|
287
|
+
* Keyed on linux rather than on darwin, because linux is the platform that is
|
|
288
|
+
* wrong: `-r` sorts on every BSD, while `--sort` is a procps long option that
|
|
289
|
+
* would make FreeBSD and OpenBSD exit non-zero. This way the only branch that
|
|
290
|
+
* changes is the one that was broken.
|
|
291
|
+
*
|
|
292
|
+
* Pure and exported for the same reason the parsers are: the command
|
|
293
|
+
* construction is the part that differs per platform, and a fixture cannot
|
|
294
|
+
* prove which flags were passed to produce it.
|
|
295
|
+
*/
|
|
296
|
+
export function psArgs(platform = process.platform) {
|
|
297
|
+
// procps: an explicit CPU sort, and `comm` in `-o` is what keeps argv — and
|
|
298
|
+
// any prompt or token on it — out of the panel. Linux `comm` comes from
|
|
299
|
+
// /proc/<pid>/comm and is capped at 15 characters.
|
|
300
|
+
if (platform === "linux") return ["-eo", "pid,pcpu,pmem,comm", "--sort=-pcpu"];
|
|
301
|
+
// BSD/macOS: `-c` prints the accounting name rather than the argument vector,
|
|
302
|
+
// and `-r` sorts by current CPU.
|
|
303
|
+
return ["-Aceo", "pid,pcpu,pmem,comm", "-r"];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Rows out of `ps -o pid,pcpu,pmem,comm`, in that column order on both Unixes —
|
|
308
|
+
* see psArgs for how each platform is asked for it.
|
|
309
|
+
*
|
|
310
|
+
* `pcpu` is a percentage of ONE core on both, so a multi-threaded process runs
|
|
311
|
+
* past 100 and that is information rather than an error: 157 is one and a half
|
|
312
|
+
* cores. cpuFromDeltas puts the Windows column on this same scale.
|
|
313
|
+
*
|
|
314
|
+
* There is no row limit in the query because neither `ps` has one and `run`
|
|
315
|
+
* deliberately never inherits a shell, so there is no `| head` to pipe into.
|
|
316
|
+
* The loop below stops at `limit` instead, which costs one parse of a string
|
|
317
|
+
* we have already paid to read.
|
|
276
318
|
*/
|
|
277
319
|
export function parsePsProcesses(text, limit = TOP_N) {
|
|
278
320
|
const lines = String(text ?? "").trim().split("\n");
|
|
@@ -287,50 +329,110 @@ export function parsePsProcesses(text, limit = TOP_N) {
|
|
|
287
329
|
}
|
|
288
330
|
|
|
289
331
|
/**
|
|
290
|
-
* Rows out of
|
|
332
|
+
* Rows out of `Get-Process`.
|
|
291
333
|
*
|
|
292
|
-
* `
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
334
|
+
* NOT `Win32_PerfFormattedData_PerfProc_Process`, which is what this used and
|
|
335
|
+
* which is not a class you may assume exists. It is published by perflib, and
|
|
336
|
+
* perflib is deregistered often enough to matter — a corporate image, a bad
|
|
337
|
+
* in-place upgrade, a half-run `lodctr`. On a machine reported from the field
|
|
338
|
+
* the class was simply absent (`Get-CimInstance: Invalid class`), and `typeperf`
|
|
339
|
+
* failed identically, which places the fault below WMI rather than in it. WMI
|
|
340
|
+
* was only mirroring what perflib had stopped publishing.
|
|
298
341
|
*
|
|
299
|
-
* `
|
|
342
|
+
* `Get-Process` reads through NtQuerySystemInformation instead, so it depends on
|
|
343
|
+
* nothing that can be unregistered. The cost is that its `CPU` is total
|
|
344
|
+
* processor SECONDS since the process started, not a rate — so a percentage has
|
|
345
|
+
* to be derived from two readings, exactly the way the machine-wide figure is
|
|
346
|
+
* already derived from two tick samples. Reliability is worth one extra poll:
|
|
347
|
+
* an instant number from a class that may not exist is worth nothing at all.
|
|
300
348
|
*/
|
|
301
|
-
export function
|
|
349
|
+
export function parseGetProcessJson(json, totalMem) {
|
|
302
350
|
let rows;
|
|
303
351
|
try { rows = typeof json === "string" ? JSON.parse(json) : json; }
|
|
304
352
|
catch { return []; }
|
|
305
353
|
if (!rows) return [];
|
|
306
354
|
if (!Array.isArray(rows)) rows = [rows];
|
|
307
|
-
const cores = os.cpus().length || 1;
|
|
308
355
|
return rows
|
|
309
|
-
.filter(r => r && r.
|
|
356
|
+
.filter(r => r && r.ProcessName)
|
|
310
357
|
.map(r => ({
|
|
311
|
-
pid: Number(r.
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
|
|
358
|
+
pid: Number(r.Id) || 0,
|
|
359
|
+
name: String(r.ProcessName),
|
|
360
|
+
// Null rather than 0 when the process denies the read: a system process
|
|
361
|
+
// we cannot query has an unknown CPU time, and calling that zero would
|
|
362
|
+
// rank it as idle.
|
|
363
|
+
cpuSec: typeof r.CPU === "number" ? r.CPU : null,
|
|
315
364
|
mem: totalMem > 0
|
|
316
365
|
? Math.round((Number(r.WorkingSetPrivate) || 0) / totalMem * 1000) / 10
|
|
317
366
|
: 0,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Turn two `Get-Process` readings into a percentage per process.
|
|
372
|
+
*
|
|
373
|
+
* `prev` maps pid to the cpuSec of the previous reading. A pid absent from it —
|
|
374
|
+
* a process that started since — has no delta and reports null rather than a
|
|
375
|
+
* number invented from its whole lifetime, which would rank a freshly spawned
|
|
376
|
+
* compiler as though it had been burning a core since boot.
|
|
377
|
+
*
|
|
378
|
+
* Per core, NOT per machine, because that is what the column beside it means:
|
|
379
|
+
* `ps -o pcpu` is a percentage of one core on both Unixes and is reported
|
|
380
|
+
* unmodified, so a row reading 157 there is a process using one and a half
|
|
381
|
+
* cores. This used to divide by the core count and clamp to 100 on the reasoning
|
|
382
|
+
* that Unix reported 0-100 — it does not, and never did, so the normalisation
|
|
383
|
+
* corrected a scale that already matched and introduced the mismatch it was
|
|
384
|
+
* written to prevent: on a 12-core machine six busy cores read 600 on macOS and
|
|
385
|
+
* 50 on Windows (#493). One CPU-second burned per wall-second is 100 here, on
|
|
386
|
+
* every platform.
|
|
387
|
+
*
|
|
388
|
+
* Core count is deliberately not a parameter any more. The aggregate meter's
|
|
389
|
+
* 0-100 convention (see cpuPercent) is a different question with a different
|
|
390
|
+
* answer, and the only way this drifts back is if a core count is in reach.
|
|
391
|
+
*/
|
|
392
|
+
export function cpuFromDeltas(rows, prev, elapsedMs, limit = TOP_N) {
|
|
393
|
+
const secs = elapsedMs / 1000;
|
|
394
|
+
const out = rows.map(r => {
|
|
395
|
+
const before = prev instanceof Map ? prev.get(r.pid) : undefined;
|
|
396
|
+
let cpu = null;
|
|
397
|
+
if (r.cpuSec != null && before != null && secs > 0) {
|
|
398
|
+
const d = r.cpuSec - before;
|
|
399
|
+
// A counter that went backwards means the pid was reused by a different
|
|
400
|
+
// process; report nothing rather than a negative or a wild number.
|
|
401
|
+
if (d >= 0) cpu = Math.round((d / secs) * 1000) / 10;
|
|
402
|
+
}
|
|
403
|
+
return { pid: r.pid, cpu, mem: r.mem, name: r.name };
|
|
404
|
+
});
|
|
405
|
+
// Until the second reading lands there is no CPU to sort on, so the list is
|
|
406
|
+
// ordered by memory — which is a real answer to "what is this machine doing",
|
|
407
|
+
// not a placeholder.
|
|
408
|
+
const haveCpu = out.some(r => r.cpu != null);
|
|
409
|
+
out.sort(haveCpu
|
|
410
|
+
? (a, b) => (b.cpu ?? -1) - (a.cpu ?? -1)
|
|
411
|
+
: (a, b) => b.mem - a.mem);
|
|
412
|
+
return out.slice(0, limit);
|
|
322
413
|
}
|
|
323
414
|
|
|
324
415
|
/** The process list, on demand only — never on the ambient timer. */
|
|
416
|
+
/** Previous Windows reading, so the next one can be a rate. Cleared with the
|
|
417
|
+
* rest of the sampler state. */
|
|
418
|
+
let prevProcCpu = null;
|
|
419
|
+
let prevProcAt = 0;
|
|
420
|
+
|
|
325
421
|
export async function readProcesses(platform = process.platform) {
|
|
326
422
|
if (platform === "win32") {
|
|
327
423
|
const out = await run("powershell.exe", [
|
|
328
424
|
"-NoProfile", "-NonInteractive", "-Command",
|
|
329
|
-
"Get-
|
|
425
|
+
"Get-Process | Select-Object Id,ProcessName,CPU,@{n='WorkingSetPrivate';e={$_.PrivateMemorySize64}} | ConvertTo-Json -Compress",
|
|
330
426
|
], 6_000);
|
|
331
|
-
|
|
427
|
+
if (!out) return [];
|
|
428
|
+
const rows = parseGetProcessJson(out.trim(), os.totalmem());
|
|
429
|
+
const now = Date.now();
|
|
430
|
+
const result = cpuFromDeltas(rows, prevProcCpu, now - prevProcAt);
|
|
431
|
+
prevProcCpu = new Map(rows.filter(r => r.cpuSec != null).map(r => [r.pid, r.cpuSec]));
|
|
432
|
+
prevProcAt = now;
|
|
433
|
+
return result;
|
|
332
434
|
}
|
|
333
|
-
const out = await run("ps",
|
|
435
|
+
const out = await run("ps", psArgs(platform), 4_000);
|
|
334
436
|
return out ? parsePsProcesses(out) : [];
|
|
335
437
|
}
|
|
336
438
|
|
|
@@ -386,6 +488,8 @@ export function stopSystemMetrics() {
|
|
|
386
488
|
memory = null;
|
|
387
489
|
cores = null;
|
|
388
490
|
swap = null;
|
|
491
|
+
prevProcCpu = null;
|
|
492
|
+
prevProcAt = 0;
|
|
389
493
|
}
|
|
390
494
|
|
|
391
495
|
/**
|