agent-dag 1.36.0 → 1.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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-OvdPf_n3.js"></script>
43
+ <script type="module" crossorigin src="/assets/index-DWHO_6dI.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.0",
3
+ "version": "1.36.1",
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": {
@@ -287,48 +287,97 @@ export function parsePsProcesses(text, limit = TOP_N) {
287
287
  }
288
288
 
289
289
  /**
290
- * Rows out of the Windows performance counter.
290
+ * Rows out of `Get-Process`.
291
291
  *
292
- * `Get-Process` is the obvious call and the wrong one: its `CPU` property is
293
- * total processor SECONDS consumed since the process started, so sorting by it
294
- * ranks whatever has been running longest rather than whatever is busy now — a
295
- * different question, and not the one the panel asks. The formatted performance
296
- * counter publishes `PercentProcessorTime` directly, already a rate, so one
297
- * query gives the same thing `ps -r` gives on Unix without a second sample.
292
+ * NOT `Win32_PerfFormattedData_PerfProc_Process`, which is what this used and
293
+ * which is not a class you may assume exists. It is published by perflib, and
294
+ * perflib is deregistered often enough to matter a corporate image, a bad
295
+ * in-place upgrade, a half-run `lodctr`. On a machine reported from the field
296
+ * the class was simply absent (`Get-CimInstance: Invalid class`), and `typeperf`
297
+ * failed identically, which places the fault below WMI rather than in it. WMI
298
+ * was only mirroring what perflib had stopped publishing.
298
299
  *
299
- * `_Total` and `Idle` are pseudo-processes in that class and are dropped.
300
+ * `Get-Process` reads through NtQuerySystemInformation instead, so it depends on
301
+ * nothing that can be unregistered. The cost is that its `CPU` is total
302
+ * processor SECONDS since the process started, not a rate — so a percentage has
303
+ * to be derived from two readings, exactly the way the machine-wide figure is
304
+ * already derived from two tick samples. Reliability is worth one extra poll:
305
+ * an instant number from a class that may not exist is worth nothing at all.
300
306
  */
301
- export function parseWindowsProcesses(json, totalMem, limit = TOP_N) {
307
+ export function parseGetProcessJson(json, totalMem) {
302
308
  let rows;
303
309
  try { rows = typeof json === "string" ? JSON.parse(json) : json; }
304
310
  catch { return []; }
305
311
  if (!rows) return [];
306
312
  if (!Array.isArray(rows)) rows = [rows];
307
- const cores = os.cpus().length || 1;
308
313
  return rows
309
- .filter(r => r && r.Name && r.Name !== "_Total" && r.Name !== "Idle")
314
+ .filter(r => r && r.ProcessName)
310
315
  .map(r => ({
311
- pid: Number(r.IDProcess) || 0,
312
- // The counter is per-core-summed, exactly like macOS's 0-to-N00 scale, so
313
- // it is normalised here to the 0-100 the Unix branch already reports.
314
- cpu: Math.round((Number(r.PercentProcessorTime) || 0) / cores * 10) / 10,
316
+ pid: Number(r.Id) || 0,
317
+ name: String(r.ProcessName),
318
+ // Null rather than 0 when the process denies the read: a system process
319
+ // we cannot query has an unknown CPU time, and calling that zero would
320
+ // rank it as idle.
321
+ cpuSec: typeof r.CPU === "number" ? r.CPU : null,
315
322
  mem: totalMem > 0
316
323
  ? Math.round((Number(r.WorkingSetPrivate) || 0) / totalMem * 1000) / 10
317
324
  : 0,
318
- name: String(r.Name),
319
- }))
320
- .sort((a, b) => b.cpu - a.cpu)
321
- .slice(0, limit);
325
+ }));
326
+ }
327
+
328
+ /**
329
+ * Turn two `Get-Process` readings into a percentage per process.
330
+ *
331
+ * `prev` maps pid to the cpuSec of the previous reading. A pid absent from it —
332
+ * a process that started since — has no delta and reports null rather than a
333
+ * number invented from its whole lifetime, which would rank a freshly spawned
334
+ * compiler as though it had been burning a core since boot.
335
+ *
336
+ * Normalised by core count so a Windows row means the same thing as the Unix
337
+ * one: 100 is one machine, not one core.
338
+ */
339
+ export function cpuFromDeltas(rows, prev, elapsedMs, cores, limit = TOP_N) {
340
+ const secs = elapsedMs / 1000;
341
+ const out = rows.map(r => {
342
+ const before = prev instanceof Map ? prev.get(r.pid) : undefined;
343
+ let cpu = null;
344
+ if (r.cpuSec != null && before != null && secs > 0) {
345
+ const d = r.cpuSec - before;
346
+ // A counter that went backwards means the pid was reused by a different
347
+ // process; report nothing rather than a negative or a wild number.
348
+ if (d >= 0) cpu = Math.max(0, Math.min(100, Math.round((d / secs / Math.max(1, cores)) * 1000) / 10));
349
+ }
350
+ return { pid: r.pid, cpu, mem: r.mem, name: r.name };
351
+ });
352
+ // Until the second reading lands there is no CPU to sort on, so the list is
353
+ // ordered by memory — which is a real answer to "what is this machine doing",
354
+ // not a placeholder.
355
+ const haveCpu = out.some(r => r.cpu != null);
356
+ out.sort(haveCpu
357
+ ? (a, b) => (b.cpu ?? -1) - (a.cpu ?? -1)
358
+ : (a, b) => b.mem - a.mem);
359
+ return out.slice(0, limit);
322
360
  }
323
361
 
324
362
  /** The process list, on demand only — never on the ambient timer. */
363
+ /** Previous Windows reading, so the next one can be a rate. Cleared with the
364
+ * rest of the sampler state. */
365
+ let prevProcCpu = null;
366
+ let prevProcAt = 0;
367
+
325
368
  export async function readProcesses(platform = process.platform) {
326
369
  if (platform === "win32") {
327
370
  const out = await run("powershell.exe", [
328
371
  "-NoProfile", "-NonInteractive", "-Command",
329
- "Get-CimInstance Win32_PerfFormattedData_PerfProc_Process | Select-Object Name,IDProcess,PercentProcessorTime,WorkingSetPrivate | ConvertTo-Json -Compress",
372
+ "Get-Process | Select-Object Id,ProcessName,CPU,@{n='WorkingSetPrivate';e={$_.PrivateMemorySize64}} | ConvertTo-Json -Compress",
330
373
  ], 6_000);
331
- return out ? parseWindowsProcesses(out.trim(), os.totalmem()) : [];
374
+ if (!out) return [];
375
+ const rows = parseGetProcessJson(out.trim(), os.totalmem());
376
+ const now = Date.now();
377
+ const result = cpuFromDeltas(rows, prevProcCpu, now - prevProcAt, os.cpus().length);
378
+ prevProcCpu = new Map(rows.filter(r => r.cpuSec != null).map(r => [r.pid, r.cpuSec]));
379
+ prevProcAt = now;
380
+ return result;
332
381
  }
333
382
  const out = await run("ps", ["-Aceo", "pid,pcpu,pmem,comm", "-r"], 4_000);
334
383
  return out ? parsePsProcesses(out) : [];
@@ -386,6 +435,8 @@ export function stopSystemMetrics() {
386
435
  memory = null;
387
436
  cores = null;
388
437
  swap = null;
438
+ prevProcCpu = null;
439
+ prevProcAt = 0;
389
440
  }
390
441
 
391
442
  /**