agentflow-core 0.7.0 → 0.8.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.
- package/README.md +99 -0
- package/dist/{chunk-DY7YHFIB.js → chunk-BYWLDTZK.js} +2 -1
- package/dist/{chunk-5PRHVYYD.js → chunk-NVFWBTAZ.js} +198 -89
- package/dist/cli.cjs +225 -112
- package/dist/cli.js +12 -8
- package/dist/index.cjs +1657 -166
- package/dist/index.d.cts +917 -37
- package/dist/index.d.ts +917 -37
- package/dist/index.js +1381 -10
- package/dist/{loader-LYRR6LMM.js → loader-JMFEFI3Q.js} +1 -1
- package/package.json +7 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
graphToJson,
|
|
3
3
|
loadGraph
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BYWLDTZK.js";
|
|
5
5
|
|
|
6
6
|
// src/graph-query.ts
|
|
7
7
|
function getNode(graph, nodeId) {
|
|
@@ -155,7 +155,7 @@ function groupByTraceId(graphs) {
|
|
|
155
155
|
}
|
|
156
156
|
function stitchTrace(graphs) {
|
|
157
157
|
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
158
|
-
const traceId = graphs[0]
|
|
158
|
+
const traceId = graphs[0]?.traceId ?? "";
|
|
159
159
|
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
160
160
|
const childMap = /* @__PURE__ */ new Map();
|
|
161
161
|
let rootGraph = null;
|
|
@@ -207,7 +207,7 @@ function getTraceTree(trace) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
// src/process-audit.ts
|
|
210
|
-
import { execSync } from "child_process";
|
|
210
|
+
import { execFileSync, execSync } from "child_process";
|
|
211
211
|
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
212
212
|
import { basename, join } from "path";
|
|
213
213
|
function isPidAlive(pid) {
|
|
@@ -229,7 +229,7 @@ function pidMatchesName(pid, name) {
|
|
|
229
229
|
function readPidFile(path) {
|
|
230
230
|
try {
|
|
231
231
|
const pid = parseInt(readFileSync(path, "utf8").trim(), 10);
|
|
232
|
-
return isNaN(pid) ? null : pid;
|
|
232
|
+
return Number.isNaN(pid) ? null : pid;
|
|
233
233
|
} catch {
|
|
234
234
|
return null;
|
|
235
235
|
}
|
|
@@ -264,8 +264,15 @@ function auditSystemd(config) {
|
|
|
264
264
|
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
265
265
|
const unit = config.systemdUnit;
|
|
266
266
|
try {
|
|
267
|
-
const raw =
|
|
268
|
-
|
|
267
|
+
const raw = execFileSync(
|
|
268
|
+
"systemctl",
|
|
269
|
+
[
|
|
270
|
+
"--user",
|
|
271
|
+
"show",
|
|
272
|
+
unit,
|
|
273
|
+
"--property=ActiveState,SubState,MainPID,NRestarts,Result",
|
|
274
|
+
"--no-pager"
|
|
275
|
+
],
|
|
269
276
|
{ encoding: "utf8", timeout: 5e3 }
|
|
270
277
|
);
|
|
271
278
|
const props = {};
|
|
@@ -273,11 +280,11 @@ function auditSystemd(config) {
|
|
|
273
280
|
const [k, ...v] = line.split("=");
|
|
274
281
|
if (k) props[k.trim()] = v.join("=").trim();
|
|
275
282
|
}
|
|
276
|
-
const activeState = props
|
|
277
|
-
const subState = props
|
|
278
|
-
const mainPid = parseInt(props
|
|
279
|
-
const restarts = parseInt(props
|
|
280
|
-
const result = props
|
|
283
|
+
const activeState = props.ActiveState ?? "unknown";
|
|
284
|
+
const subState = props.SubState ?? "unknown";
|
|
285
|
+
const mainPid = parseInt(props.MainPID ?? "0", 10);
|
|
286
|
+
const restarts = parseInt(props.NRestarts ?? "0", 10);
|
|
287
|
+
const result = props.Result ?? "unknown";
|
|
281
288
|
return {
|
|
282
289
|
unit,
|
|
283
290
|
activeState,
|
|
@@ -330,10 +337,10 @@ function readCmdline(pid) {
|
|
|
330
337
|
}
|
|
331
338
|
function getOsProcesses(processName) {
|
|
332
339
|
try {
|
|
333
|
-
const raw = execSync(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
);
|
|
340
|
+
const raw = execSync(`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`, {
|
|
341
|
+
encoding: "utf8",
|
|
342
|
+
timeout: 5e3
|
|
343
|
+
});
|
|
337
344
|
const results = [];
|
|
338
345
|
for (const line of raw.split("\n")) {
|
|
339
346
|
if (!line.includes(processName)) continue;
|
|
@@ -341,7 +348,7 @@ function getOsProcesses(processName) {
|
|
|
341
348
|
const trimmed = line.trim();
|
|
342
349
|
const parts = trimmed.split(/\s+/);
|
|
343
350
|
const pid = parseInt(parts[0] ?? "0", 10);
|
|
344
|
-
if (isNaN(pid) || pid <= 0) continue;
|
|
351
|
+
if (Number.isNaN(pid) || pid <= 0) continue;
|
|
345
352
|
const cpu = parts[1] ?? "0";
|
|
346
353
|
const mem = parts[2] ?? "0";
|
|
347
354
|
const elapsed = parts[3] ?? "";
|
|
@@ -356,9 +363,11 @@ function getOsProcesses(processName) {
|
|
|
356
363
|
}
|
|
357
364
|
}
|
|
358
365
|
function discoverProcessConfig(dirs) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
366
|
+
const configs = discoverAllProcessConfigs(dirs);
|
|
367
|
+
return configs.length > 0 ? configs[0] ?? null : null;
|
|
368
|
+
}
|
|
369
|
+
function discoverAllProcessConfigs(dirs) {
|
|
370
|
+
const configs = /* @__PURE__ */ new Map();
|
|
362
371
|
for (const dir of dirs) {
|
|
363
372
|
if (!existsSync(dir)) continue;
|
|
364
373
|
let entries;
|
|
@@ -374,35 +383,63 @@ function discoverProcessConfig(dirs) {
|
|
|
374
383
|
} catch {
|
|
375
384
|
continue;
|
|
376
385
|
}
|
|
377
|
-
if (f.endsWith(".pid")
|
|
378
|
-
|
|
379
|
-
if (!
|
|
380
|
-
processName
|
|
386
|
+
if (f.endsWith(".pid")) {
|
|
387
|
+
const name = basename(f, ".pid");
|
|
388
|
+
if (!configs.has(name)) {
|
|
389
|
+
configs.set(name, { processName: name });
|
|
381
390
|
}
|
|
391
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
392
|
+
if (!cfg.pidFile) cfg.pidFile = fp;
|
|
382
393
|
}
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
processName
|
|
394
|
+
if (f === "workers.json" || f.endsWith("-workers.json")) {
|
|
395
|
+
const name = f === "workers.json" ? "" : basename(f, "-workers.json");
|
|
396
|
+
if (name && !configs.has(name)) {
|
|
397
|
+
configs.set(name, { processName: name });
|
|
398
|
+
}
|
|
399
|
+
if (name) {
|
|
400
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
401
|
+
if (!cfg.workersFile) cfg.workersFile = fp;
|
|
387
402
|
}
|
|
388
403
|
}
|
|
389
404
|
}
|
|
390
405
|
}
|
|
391
|
-
if (!processName && !pidFile && !workersFile) return null;
|
|
392
|
-
if (!processName) processName = "agent";
|
|
393
|
-
let systemdUnit;
|
|
394
406
|
try {
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{ encoding: "utf8", timeout: 3e3 }
|
|
407
|
+
const raw = execSync(
|
|
408
|
+
"systemctl --user list-units --type=service --all --no-legend --no-pager 2>/dev/null",
|
|
409
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
399
410
|
);
|
|
400
|
-
|
|
401
|
-
|
|
411
|
+
for (const line of raw.trim().split("\n")) {
|
|
412
|
+
if (!line.trim()) continue;
|
|
413
|
+
const parts = line.trim().split(/\s+/);
|
|
414
|
+
const unitName = parts[0] ?? "";
|
|
415
|
+
const loadState = parts[1] ?? "";
|
|
416
|
+
if (!unitName.endsWith(".service") || loadState !== "loaded") continue;
|
|
417
|
+
const name = unitName.replace(".service", "");
|
|
418
|
+
if (/^(dbus|gpg-agent|dirmngr|keyboxd|snapd\.|pk-|launchpadlib-)/.test(name)) continue;
|
|
419
|
+
if (!configs.has(name)) {
|
|
420
|
+
configs.set(name, { processName: name });
|
|
421
|
+
}
|
|
422
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
423
|
+
cfg.systemdUnit = unitName;
|
|
402
424
|
}
|
|
403
425
|
} catch {
|
|
404
426
|
}
|
|
405
|
-
|
|
427
|
+
for (const cfg of configs.values()) {
|
|
428
|
+
if (cfg.systemdUnit !== void 0) continue;
|
|
429
|
+
try {
|
|
430
|
+
const unitName = `${cfg.processName}.service`;
|
|
431
|
+
const result = execFileSync(
|
|
432
|
+
"systemctl",
|
|
433
|
+
["--user", "show", unitName, "--property=LoadState", "--no-pager"],
|
|
434
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
435
|
+
);
|
|
436
|
+
if (result.includes("LoadState=loaded")) {
|
|
437
|
+
cfg.systemdUnit = unitName;
|
|
438
|
+
}
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return [...configs.values()];
|
|
406
443
|
}
|
|
407
444
|
function auditProcesses(config) {
|
|
408
445
|
const pidFile = auditPidFile(config);
|
|
@@ -418,16 +455,45 @@ function auditProcesses(config) {
|
|
|
418
455
|
}
|
|
419
456
|
}
|
|
420
457
|
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
458
|
+
const childPids = /* @__PURE__ */ new Set();
|
|
459
|
+
for (const knownPid of knownPids) {
|
|
460
|
+
try {
|
|
461
|
+
const childrenRaw = readFileSync(
|
|
462
|
+
`/proc/${knownPid}/task/${knownPid}/children`,
|
|
463
|
+
"utf8"
|
|
464
|
+
).trim();
|
|
465
|
+
if (childrenRaw) {
|
|
466
|
+
for (const c of childrenRaw.split(/\s+/)) {
|
|
467
|
+
const cp = parseInt(c, 10);
|
|
468
|
+
if (!Number.isNaN(cp)) childPids.add(cp);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
for (const p of osProcesses) {
|
|
475
|
+
if (knownPids.has(p.pid)) continue;
|
|
476
|
+
try {
|
|
477
|
+
const statusContent = readFileSync(`/proc/${p.pid}/status`, "utf8");
|
|
478
|
+
const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
|
|
479
|
+
if (ppidMatch) {
|
|
480
|
+
const ppid = parseInt(ppidMatch[1] ?? "0", 10);
|
|
481
|
+
if (knownPids.has(ppid)) childPids.add(p.pid);
|
|
482
|
+
}
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
421
486
|
const selfPid = process.pid;
|
|
422
487
|
const selfPpid = process.ppid;
|
|
423
488
|
const orphans = osProcesses.filter(
|
|
424
|
-
(p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
489
|
+
(p) => !knownPids.has(p.pid) && !childPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
425
490
|
);
|
|
426
491
|
const problems = [];
|
|
427
492
|
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
428
493
|
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
429
494
|
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
430
|
-
if (systemd && systemd.restarts > 10)
|
|
495
|
+
if (systemd && systemd.restarts > 10)
|
|
496
|
+
problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
431
497
|
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
432
498
|
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
433
499
|
}
|
|
@@ -436,15 +502,24 @@ function auditProcesses(config) {
|
|
|
436
502
|
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
437
503
|
}
|
|
438
504
|
}
|
|
439
|
-
if (orphans.length > 0)
|
|
505
|
+
if (orphans.length > 0)
|
|
506
|
+
problems.push(
|
|
507
|
+
`${orphans.length} orphan process(es) not tracked by PID file or workers registry`
|
|
508
|
+
);
|
|
440
509
|
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
441
510
|
}
|
|
442
511
|
function formatAuditReport(result) {
|
|
443
512
|
const lines = [];
|
|
444
513
|
lines.push("");
|
|
445
|
-
lines.push(
|
|
446
|
-
|
|
447
|
-
|
|
514
|
+
lines.push(
|
|
515
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"
|
|
516
|
+
);
|
|
517
|
+
lines.push(
|
|
518
|
+
"\u2551 \u{1F50D} P R O C E S S A U D I T \u2551"
|
|
519
|
+
);
|
|
520
|
+
lines.push(
|
|
521
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
522
|
+
);
|
|
448
523
|
if (result.pidFile) {
|
|
449
524
|
const pf = result.pidFile;
|
|
450
525
|
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
@@ -462,25 +537,33 @@ function formatAuditReport(result) {
|
|
|
462
537
|
}
|
|
463
538
|
if (result.workers) {
|
|
464
539
|
const w = result.workers;
|
|
465
|
-
lines.push(
|
|
466
|
-
|
|
540
|
+
lines.push(
|
|
541
|
+
`
|
|
542
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`
|
|
543
|
+
);
|
|
467
544
|
for (const worker of w.workers) {
|
|
468
545
|
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
469
|
-
lines.push(
|
|
546
|
+
lines.push(
|
|
547
|
+
` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`
|
|
548
|
+
);
|
|
470
549
|
}
|
|
471
550
|
}
|
|
472
551
|
if (result.osProcesses.length > 0) {
|
|
473
552
|
lines.push(`
|
|
474
553
|
OS Processes (${result.osProcesses.length} total)`);
|
|
475
554
|
for (const p of result.osProcesses) {
|
|
476
|
-
lines.push(
|
|
555
|
+
lines.push(
|
|
556
|
+
` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed.padEnd(10)} ${p.command.substring(0, 50)}`
|
|
557
|
+
);
|
|
477
558
|
}
|
|
478
559
|
}
|
|
479
560
|
if (result.orphans.length > 0) {
|
|
480
561
|
lines.push(`
|
|
481
562
|
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
482
563
|
for (const p of result.orphans) {
|
|
483
|
-
lines.push(
|
|
564
|
+
lines.push(
|
|
565
|
+
` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`
|
|
566
|
+
);
|
|
484
567
|
lines.push(` Started: ${p.started}`);
|
|
485
568
|
lines.push(` Command: ${p.cmdline || p.command}`);
|
|
486
569
|
}
|
|
@@ -520,14 +603,14 @@ function parseArgs(argv) {
|
|
|
520
603
|
if (args[0] === "live") args.shift();
|
|
521
604
|
let i = 0;
|
|
522
605
|
while (i < args.length) {
|
|
523
|
-
const arg = args[i];
|
|
606
|
+
const arg = args[i] ?? "";
|
|
524
607
|
if (arg === "--help" || arg === "-h") {
|
|
525
608
|
printUsage();
|
|
526
609
|
process.exit(0);
|
|
527
610
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
528
611
|
i++;
|
|
529
612
|
const v = parseInt(args[i] ?? "", 10);
|
|
530
|
-
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
613
|
+
if (!Number.isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
531
614
|
i++;
|
|
532
615
|
} else if (arg === "--recursive" || arg === "-R") {
|
|
533
616
|
config.recursive = true;
|
|
@@ -608,12 +691,22 @@ function scanFiles(dirs, recursive) {
|
|
|
608
691
|
if (!stat.isFile()) continue;
|
|
609
692
|
if (f.endsWith(".json")) {
|
|
610
693
|
seen.add(fp);
|
|
611
|
-
const entry = {
|
|
694
|
+
const entry = {
|
|
695
|
+
filename: f,
|
|
696
|
+
path: fp,
|
|
697
|
+
mtime: stat.mtime.getTime(),
|
|
698
|
+
ext: ".json"
|
|
699
|
+
};
|
|
612
700
|
results.push(entry);
|
|
613
701
|
dirResults.push(entry);
|
|
614
702
|
} else if (f.endsWith(".jsonl")) {
|
|
615
703
|
seen.add(fp);
|
|
616
|
-
const entry = {
|
|
704
|
+
const entry = {
|
|
705
|
+
filename: f,
|
|
706
|
+
path: fp,
|
|
707
|
+
mtime: stat.mtime.getTime(),
|
|
708
|
+
ext: ".jsonl"
|
|
709
|
+
};
|
|
617
710
|
results.push(entry);
|
|
618
711
|
dirResults.push(entry);
|
|
619
712
|
}
|
|
@@ -675,7 +768,7 @@ function findTimestamp(obj) {
|
|
|
675
768
|
if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
|
|
676
769
|
if (typeof val === "string") {
|
|
677
770
|
const d = Date.parse(val);
|
|
678
|
-
if (!isNaN(d)) return d;
|
|
771
|
+
if (!Number.isNaN(d)) return d;
|
|
679
772
|
}
|
|
680
773
|
}
|
|
681
774
|
return 0;
|
|
@@ -707,7 +800,7 @@ function extractDetail(obj) {
|
|
|
707
800
|
}
|
|
708
801
|
return parts.join(" | ") || "";
|
|
709
802
|
}
|
|
710
|
-
function tryLoadTrace(
|
|
803
|
+
function tryLoadTrace(_fp, raw) {
|
|
711
804
|
if (typeof raw !== "object" || raw === null) return null;
|
|
712
805
|
const obj = raw;
|
|
713
806
|
if (!("nodes" in obj)) return null;
|
|
@@ -817,7 +910,7 @@ function processJsonlFile(file) {
|
|
|
817
910
|
if (!content) return [];
|
|
818
911
|
const lines = content.split("\n");
|
|
819
912
|
const lineCount = lines.length;
|
|
820
|
-
const lastObj = JSON.parse(lines[lines.length - 1]);
|
|
913
|
+
const lastObj = JSON.parse(lines[lines.length - 1] ?? "{}");
|
|
821
914
|
const name = lastObj.jobId ?? lastObj.agentId ?? lastObj.name ?? lastObj.id ?? nameFromFile(file.filename);
|
|
822
915
|
if (lastObj.action !== void 0 || lastObj.jobId !== void 0) {
|
|
823
916
|
const status2 = findStatus(lastObj);
|
|
@@ -913,7 +1006,7 @@ function processJsonlFile(file) {
|
|
|
913
1006
|
}
|
|
914
1007
|
const parts = [];
|
|
915
1008
|
if (model) {
|
|
916
|
-
const shortModel = model.includes("/") ? model.split("/").pop() : model;
|
|
1009
|
+
const shortModel = model.includes("/") ? model.split("/").pop() ?? model : model;
|
|
917
1010
|
parts.push(shortModel.slice(0, 20));
|
|
918
1011
|
}
|
|
919
1012
|
if (toolCalls.length > 0) {
|
|
@@ -958,7 +1051,8 @@ function writeLine(lines, text) {
|
|
|
958
1051
|
}
|
|
959
1052
|
function flushLines(lines) {
|
|
960
1053
|
process.stdout.write("\x1B[H");
|
|
961
|
-
process.stdout.write(lines.join("\n")
|
|
1054
|
+
process.stdout.write(`${lines.join("\n")}
|
|
1055
|
+
`);
|
|
962
1056
|
process.stdout.write("\x1B[J");
|
|
963
1057
|
}
|
|
964
1058
|
var prevFileCount = 0;
|
|
@@ -1052,7 +1146,7 @@ function render(config) {
|
|
|
1052
1146
|
const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
|
|
1053
1147
|
groups.push({
|
|
1054
1148
|
name: groupName,
|
|
1055
|
-
source: records[0]
|
|
1149
|
+
source: records[0]?.source ?? "trace",
|
|
1056
1150
|
status,
|
|
1057
1151
|
lastTs,
|
|
1058
1152
|
detail: `${records.length} agents`,
|
|
@@ -1077,15 +1171,15 @@ function render(config) {
|
|
|
1077
1171
|
if (age > 36e5 || age < 0) continue;
|
|
1078
1172
|
const idx = 11 - Math.floor(age / 3e5);
|
|
1079
1173
|
if (idx >= 0 && idx < 12) {
|
|
1080
|
-
buckets[idx]
|
|
1081
|
-
if (r.status === "error") failBuckets[idx]
|
|
1174
|
+
buckets[idx] = (buckets[idx] ?? 0) + 1;
|
|
1175
|
+
if (r.status === "error") failBuckets[idx] = (failBuckets[idx] ?? 0) + 1;
|
|
1082
1176
|
}
|
|
1083
1177
|
}
|
|
1084
1178
|
const maxBucket = Math.max(...buckets, 1);
|
|
1085
1179
|
const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1086
1180
|
const spark = buckets.map((v, i) => {
|
|
1087
1181
|
const level = Math.round(v / maxBucket * 8);
|
|
1088
|
-
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
1182
|
+
return ((failBuckets[i] ?? 0) > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
1089
1183
|
}).join("");
|
|
1090
1184
|
let auditResult = null;
|
|
1091
1185
|
if (now - lastAuditTime > 1e4) {
|
|
@@ -1098,8 +1192,10 @@ function render(config) {
|
|
|
1098
1192
|
cachedAuditResult = auditResult;
|
|
1099
1193
|
lastAuditTime = now;
|
|
1100
1194
|
} catch (err) {
|
|
1101
|
-
process.stderr.write(
|
|
1102
|
-
`
|
|
1195
|
+
process.stderr.write(
|
|
1196
|
+
`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
|
|
1197
|
+
`
|
|
1198
|
+
);
|
|
1103
1199
|
}
|
|
1104
1200
|
}
|
|
1105
1201
|
} else {
|
|
@@ -1156,7 +1252,7 @@ function render(config) {
|
|
|
1156
1252
|
return new Date(ts).toLocaleTimeString();
|
|
1157
1253
|
}
|
|
1158
1254
|
function truncate(s, max) {
|
|
1159
|
-
return s.length > max ? s.slice(0, max - 1)
|
|
1255
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
1160
1256
|
}
|
|
1161
1257
|
const termWidth = process.stdout.columns || 120;
|
|
1162
1258
|
const detailWidth = Math.max(20, termWidth - 60);
|
|
@@ -1197,7 +1293,8 @@ function render(config) {
|
|
|
1197
1293
|
if (ar.systemd) {
|
|
1198
1294
|
const si = ar.systemd.activeState === "active" ? `${C.green}\u25CF${C.reset}` : ar.systemd.crashLooping ? `${C.yellow}\u25CF${C.reset}` : ar.systemd.failed ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1199
1295
|
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1200
|
-
if (ar.systemd.restarts > 0)
|
|
1296
|
+
if (ar.systemd.restarts > 0)
|
|
1297
|
+
sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1201
1298
|
}
|
|
1202
1299
|
let pidLabel = "";
|
|
1203
1300
|
if (ar.pidFile?.pid) {
|
|
@@ -1206,7 +1303,10 @@ function render(config) {
|
|
|
1206
1303
|
}
|
|
1207
1304
|
writeLine(L, "");
|
|
1208
1305
|
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1209
|
-
writeLine(
|
|
1306
|
+
writeLine(
|
|
1307
|
+
L,
|
|
1308
|
+
` ${healthIcon} ${healthLabel}${pidLabel}${sysdLabel} ${C.bold}Procs${C.reset} ${C.dim}${ar.osProcesses.length}${C.reset} ${ar.orphans.length > 0 ? `${C.red}Orphans ${ar.orphans.length}${C.reset}` : `${C.dim}Orphans 0${C.reset}`}`
|
|
1309
|
+
);
|
|
1210
1310
|
if (workerParts.length > 0) {
|
|
1211
1311
|
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1212
1312
|
}
|
|
@@ -1218,7 +1318,10 @@ function render(config) {
|
|
|
1218
1318
|
if (ar.orphans.length > 0) {
|
|
1219
1319
|
for (const o of ar.orphans.slice(0, 5)) {
|
|
1220
1320
|
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1221
|
-
writeLine(
|
|
1321
|
+
writeLine(
|
|
1322
|
+
L,
|
|
1323
|
+
` ${C.red}?${C.reset} ${C.dim}pid=${o.pid} cpu=${o.cpu} mem=${o.mem} up=${o.elapsed}${C.reset} ${C.dim}${cmd}${C.reset}`
|
|
1324
|
+
);
|
|
1222
1325
|
}
|
|
1223
1326
|
if (ar.orphans.length > 5) {
|
|
1224
1327
|
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
@@ -1292,7 +1395,7 @@ function render(config) {
|
|
|
1292
1395
|
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
1293
1396
|
const tg = tree[i];
|
|
1294
1397
|
const depth = getDistDepth(dt, tg.spanId);
|
|
1295
|
-
const indent =
|
|
1398
|
+
const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
|
|
1296
1399
|
const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
1297
1400
|
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1298
1401
|
const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
@@ -1313,7 +1416,7 @@ function render(config) {
|
|
|
1313
1416
|
const t = new Date(r.lastActive).toLocaleTimeString();
|
|
1314
1417
|
const agent = truncate(r.id, 26).padEnd(26);
|
|
1315
1418
|
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1316
|
-
const ageStr = age < 60 ? age
|
|
1419
|
+
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
|
|
1317
1420
|
const det = truncate(r.detail, detailWidth);
|
|
1318
1421
|
writeLine(
|
|
1319
1422
|
L,
|
|
@@ -1327,7 +1430,7 @@ function render(config) {
|
|
|
1327
1430
|
for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
|
|
1328
1431
|
}
|
|
1329
1432
|
writeLine(L, "");
|
|
1330
|
-
const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
|
|
1433
|
+
const dirLabel = config.dirs.length === 1 ? config.dirs[0] ?? "" : `${config.dirs.length} directories`;
|
|
1331
1434
|
writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
|
|
1332
1435
|
writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
1333
1436
|
flushLines(L);
|
|
@@ -1378,7 +1481,7 @@ function startLive(argv) {
|
|
|
1378
1481
|
};
|
|
1379
1482
|
process.on("SIGINT", () => {
|
|
1380
1483
|
cleanup();
|
|
1381
|
-
console.log(C.dim
|
|
1484
|
+
console.log(`${C.dim}Monitor stopped.${C.reset}`);
|
|
1382
1485
|
process.exit(0);
|
|
1383
1486
|
});
|
|
1384
1487
|
process.on("SIGTERM", () => {
|
|
@@ -1642,7 +1745,7 @@ function agentIdFromFilename(filePath) {
|
|
|
1642
1745
|
const cleaned = base.replace(/-state$/, "");
|
|
1643
1746
|
return `alfred-${cleaned}`;
|
|
1644
1747
|
}
|
|
1645
|
-
function deriveAgentId(
|
|
1748
|
+
function deriveAgentId(_command) {
|
|
1646
1749
|
return "orchestrator";
|
|
1647
1750
|
}
|
|
1648
1751
|
function fileTimestamp() {
|
|
@@ -1752,14 +1855,18 @@ async function runTraced(config) {
|
|
|
1752
1855
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
1753
1856
|
const outPath = join3(resolvedTracesDir, filename);
|
|
1754
1857
|
const resolvedOut = resolve2(outPath);
|
|
1755
|
-
if (!resolvedOut.startsWith(resolvedTracesDir
|
|
1756
|
-
throw new Error(
|
|
1858
|
+
if (!resolvedOut.startsWith(`${resolvedTracesDir}/`) && resolvedOut !== resolvedTracesDir) {
|
|
1859
|
+
throw new Error(
|
|
1860
|
+
`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`
|
|
1861
|
+
);
|
|
1757
1862
|
}
|
|
1758
1863
|
writeFileSync(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
1759
1864
|
tracePaths.push(outPath);
|
|
1760
1865
|
}
|
|
1761
1866
|
if (tracePaths.length > 0) {
|
|
1762
|
-
console.log(
|
|
1867
|
+
console.log(
|
|
1868
|
+
`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`
|
|
1869
|
+
);
|
|
1763
1870
|
}
|
|
1764
1871
|
return {
|
|
1765
1872
|
exitCode,
|
|
@@ -1805,7 +1912,7 @@ function createTraceStore(dir) {
|
|
|
1805
1912
|
const filePath = join4(dir, `${graph.id}.json`);
|
|
1806
1913
|
const resolvedBase = resolve3(dir);
|
|
1807
1914
|
const resolvedPath = resolve3(filePath);
|
|
1808
|
-
if (!resolvedPath.startsWith(resolvedBase
|
|
1915
|
+
if (!resolvedPath.startsWith(`${resolvedBase}/`) && resolvedPath !== resolvedBase) {
|
|
1809
1916
|
throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
|
|
1810
1917
|
}
|
|
1811
1918
|
await writeFile(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
@@ -1935,7 +2042,8 @@ function toAsciiTree(graph) {
|
|
|
1935
2042
|
const children = getChildren(graph, nodeId);
|
|
1936
2043
|
const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
|
|
1937
2044
|
for (let i = 0; i < children.length; i++) {
|
|
1938
|
-
|
|
2045
|
+
const childId = children[i]?.id;
|
|
2046
|
+
if (childId) renderNode(childId, childPrefix, i === children.length - 1, false);
|
|
1939
2047
|
}
|
|
1940
2048
|
}
|
|
1941
2049
|
renderNode(graph.rootNodeId, "", true, true);
|
|
@@ -2134,10 +2242,10 @@ function parseDuration(input) {
|
|
|
2134
2242
|
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
|
|
2135
2243
|
if (!match) {
|
|
2136
2244
|
const n = parseInt(input, 10);
|
|
2137
|
-
return isNaN(n) ? 0 : n * 1e3;
|
|
2245
|
+
return Number.isNaN(n) ? 0 : n * 1e3;
|
|
2138
2246
|
}
|
|
2139
|
-
const value = parseFloat(match[1]);
|
|
2140
|
-
switch (match[2]
|
|
2247
|
+
const value = parseFloat(match[1] ?? "0");
|
|
2248
|
+
switch (match[2]?.toLowerCase()) {
|
|
2141
2249
|
case "s":
|
|
2142
2250
|
return value * 1e3;
|
|
2143
2251
|
case "m":
|
|
@@ -2164,7 +2272,7 @@ function loadWatchState(filePath) {
|
|
|
2164
2272
|
}
|
|
2165
2273
|
}
|
|
2166
2274
|
function saveWatchState(filePath, state) {
|
|
2167
|
-
const tmp = filePath
|
|
2275
|
+
const tmp = `${filePath}.tmp`;
|
|
2168
2276
|
try {
|
|
2169
2277
|
writeFileSync2(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
2170
2278
|
renameSync(tmp, filePath);
|
|
@@ -2180,12 +2288,12 @@ function estimateInterval(history) {
|
|
|
2180
2288
|
const sorted = [...history].sort((a, b) => a - b);
|
|
2181
2289
|
const deltas = [];
|
|
2182
2290
|
for (let i = 1; i < sorted.length; i++) {
|
|
2183
|
-
const d = sorted[i] - sorted[i - 1];
|
|
2291
|
+
const d = (sorted[i] ?? 0) - (sorted[i - 1] ?? 0);
|
|
2184
2292
|
if (d > 0) deltas.push(d);
|
|
2185
2293
|
}
|
|
2186
2294
|
if (deltas.length === 0) return 0;
|
|
2187
2295
|
deltas.sort((a, b) => a - b);
|
|
2188
|
-
return deltas[Math.floor(deltas.length / 2)];
|
|
2296
|
+
return deltas[Math.floor(deltas.length / 2)] ?? 0;
|
|
2189
2297
|
}
|
|
2190
2298
|
function detectTransitions(previous, currentRecords, config, now) {
|
|
2191
2299
|
const alerts = [];
|
|
@@ -2342,7 +2450,7 @@ function parseWatchArgs(argv) {
|
|
|
2342
2450
|
if (args[0] === "watch") args.shift();
|
|
2343
2451
|
let i = 0;
|
|
2344
2452
|
while (i < args.length) {
|
|
2345
|
-
const arg = args[i];
|
|
2453
|
+
const arg = args[i] ?? "";
|
|
2346
2454
|
if (arg === "--help" || arg === "-h") {
|
|
2347
2455
|
printWatchUsage();
|
|
2348
2456
|
process.exit(0);
|
|
@@ -2365,8 +2473,8 @@ function parseWatchArgs(argv) {
|
|
|
2365
2473
|
i++;
|
|
2366
2474
|
const val = args[i] ?? "";
|
|
2367
2475
|
if (val === "telegram") {
|
|
2368
|
-
const botToken = process.env
|
|
2369
|
-
const chatId = process.env
|
|
2476
|
+
const botToken = process.env.AGENTFLOW_TELEGRAM_BOT_TOKEN ?? "";
|
|
2477
|
+
const chatId = process.env.AGENTFLOW_TELEGRAM_CHAT_ID ?? "";
|
|
2370
2478
|
if (botToken && chatId) {
|
|
2371
2479
|
notifyChannels.push({ type: "telegram", botToken, chatId });
|
|
2372
2480
|
} else {
|
|
@@ -2383,7 +2491,7 @@ function parseWatchArgs(argv) {
|
|
|
2383
2491
|
} else if (arg === "--poll") {
|
|
2384
2492
|
i++;
|
|
2385
2493
|
const v = parseInt(args[i] ?? "", 10);
|
|
2386
|
-
if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2494
|
+
if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2387
2495
|
i++;
|
|
2388
2496
|
} else if (arg === "--cooldown") {
|
|
2389
2497
|
i++;
|
|
@@ -2411,7 +2519,7 @@ function parseWatchArgs(argv) {
|
|
|
2411
2519
|
}
|
|
2412
2520
|
notifyChannels.unshift({ type: "stdout" });
|
|
2413
2521
|
if (!stateFilePath) {
|
|
2414
|
-
stateFilePath = join5(dirs[0], ".agentflow-watch-state.json");
|
|
2522
|
+
stateFilePath = join5(dirs[0] ?? ".", ".agentflow-watch-state.json");
|
|
2415
2523
|
}
|
|
2416
2524
|
return {
|
|
2417
2525
|
dirs,
|
|
@@ -2499,7 +2607,7 @@ agentflow watch started`);
|
|
|
2499
2607
|
console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
|
|
2500
2608
|
console.log(` Alert on: ${condLabels.join(", ")}`);
|
|
2501
2609
|
console.log(
|
|
2502
|
-
` Notify: stdout${channelLabels.length > 0 ?
|
|
2610
|
+
` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
|
|
2503
2611
|
);
|
|
2504
2612
|
console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
|
|
2505
2613
|
console.log(` State: ${config.stateFilePath}`);
|
|
@@ -2572,6 +2680,7 @@ export {
|
|
|
2572
2680
|
stitchTrace,
|
|
2573
2681
|
getTraceTree,
|
|
2574
2682
|
discoverProcessConfig,
|
|
2683
|
+
discoverAllProcessConfigs,
|
|
2575
2684
|
auditProcesses,
|
|
2576
2685
|
formatAuditReport,
|
|
2577
2686
|
startLive,
|