agentflow-core 0.6.0 → 0.6.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/dist/{chunk-VF4FSBXR.js → chunk-BOSYI5YM.js} +322 -268
- package/dist/cli.cjs +326 -270
- package/dist/cli.js +1 -1
- package/dist/index.cjs +324 -270
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -96,8 +96,8 @@ var init_loader = __esm({
|
|
|
96
96
|
var import_path3 = require("path");
|
|
97
97
|
|
|
98
98
|
// src/live.ts
|
|
99
|
-
var
|
|
100
|
-
var
|
|
99
|
+
var import_node_fs2 = require("fs");
|
|
100
|
+
var import_node_path2 = require("path");
|
|
101
101
|
|
|
102
102
|
// src/graph-query.ts
|
|
103
103
|
function getChildren(graph, nodeId) {
|
|
@@ -244,6 +244,264 @@ function getTraceTree(trace) {
|
|
|
244
244
|
|
|
245
245
|
// src/live.ts
|
|
246
246
|
init_loader();
|
|
247
|
+
|
|
248
|
+
// src/process-audit.ts
|
|
249
|
+
var import_node_child_process = require("child_process");
|
|
250
|
+
var import_node_fs = require("fs");
|
|
251
|
+
var import_node_path = require("path");
|
|
252
|
+
function isPidAlive(pid) {
|
|
253
|
+
try {
|
|
254
|
+
process.kill(pid, 0);
|
|
255
|
+
return true;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function pidMatchesName(pid, name) {
|
|
261
|
+
try {
|
|
262
|
+
const cmdline = (0, import_node_fs.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
263
|
+
return cmdline.includes(name);
|
|
264
|
+
} catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function readPidFile(path) {
|
|
269
|
+
try {
|
|
270
|
+
const pid = parseInt((0, import_node_fs.readFileSync)(path, "utf8").trim(), 10);
|
|
271
|
+
return isNaN(pid) ? null : pid;
|
|
272
|
+
} catch {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function auditPidFile(config) {
|
|
277
|
+
if (!config.pidFile) return null;
|
|
278
|
+
const pid = readPidFile(config.pidFile);
|
|
279
|
+
if (pid === null) {
|
|
280
|
+
return {
|
|
281
|
+
path: config.pidFile,
|
|
282
|
+
pid: null,
|
|
283
|
+
alive: false,
|
|
284
|
+
matchesProcess: false,
|
|
285
|
+
stale: !(0, import_node_fs.existsSync)(config.pidFile),
|
|
286
|
+
reason: (0, import_node_fs.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const alive = isPidAlive(pid);
|
|
290
|
+
const matchesProcess = alive ? pidMatchesName(pid, config.processName) : false;
|
|
291
|
+
const stale = !alive || alive && !matchesProcess;
|
|
292
|
+
let reason;
|
|
293
|
+
if (alive && matchesProcess) {
|
|
294
|
+
reason = `PID ${pid} alive and matches ${config.processName}`;
|
|
295
|
+
} else if (alive && !matchesProcess) {
|
|
296
|
+
reason = `PID ${pid} alive but is NOT ${config.processName} (PID reused by another process)`;
|
|
297
|
+
} else {
|
|
298
|
+
reason = `PID ${pid} no longer exists`;
|
|
299
|
+
}
|
|
300
|
+
return { path: config.pidFile, pid, alive, matchesProcess, stale, reason };
|
|
301
|
+
}
|
|
302
|
+
function auditSystemd(config) {
|
|
303
|
+
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
304
|
+
const unit = config.systemdUnit;
|
|
305
|
+
try {
|
|
306
|
+
const raw = (0, import_node_child_process.execSync)(
|
|
307
|
+
`systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
|
|
308
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
309
|
+
);
|
|
310
|
+
const props = {};
|
|
311
|
+
for (const line of raw.trim().split("\n")) {
|
|
312
|
+
const [k, ...v] = line.split("=");
|
|
313
|
+
if (k) props[k.trim()] = v.join("=").trim();
|
|
314
|
+
}
|
|
315
|
+
const activeState = props["ActiveState"] ?? "unknown";
|
|
316
|
+
const subState = props["SubState"] ?? "unknown";
|
|
317
|
+
const mainPid = parseInt(props["MainPID"] ?? "0", 10);
|
|
318
|
+
const restarts = parseInt(props["NRestarts"] ?? "0", 10);
|
|
319
|
+
const result = props["Result"] ?? "unknown";
|
|
320
|
+
return {
|
|
321
|
+
unit,
|
|
322
|
+
activeState,
|
|
323
|
+
subState,
|
|
324
|
+
mainPid,
|
|
325
|
+
restarts,
|
|
326
|
+
result,
|
|
327
|
+
crashLooping: activeState === "activating" && subState === "auto-restart",
|
|
328
|
+
failed: activeState === "failed"
|
|
329
|
+
};
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function auditWorkers(config) {
|
|
335
|
+
if (!config.workersFile || !(0, import_node_fs.existsSync)(config.workersFile)) return null;
|
|
336
|
+
try {
|
|
337
|
+
const data = JSON.parse((0, import_node_fs.readFileSync)(config.workersFile, "utf8"));
|
|
338
|
+
const orchPid = data.pid ?? null;
|
|
339
|
+
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
340
|
+
const workers = [];
|
|
341
|
+
for (const [name, info] of Object.entries(data.tools ?? {})) {
|
|
342
|
+
const w = info;
|
|
343
|
+
const wPid = w.pid ?? null;
|
|
344
|
+
const wAlive = wPid ? isPidAlive(wPid) : false;
|
|
345
|
+
workers.push({
|
|
346
|
+
name,
|
|
347
|
+
pid: wPid,
|
|
348
|
+
declaredStatus: w.status ?? "unknown",
|
|
349
|
+
alive: wAlive,
|
|
350
|
+
stale: w.status === "running" && !wAlive
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
orchestratorPid: orchPid,
|
|
355
|
+
orchestratorAlive: orchAlive,
|
|
356
|
+
startedAt: data.started_at ?? "",
|
|
357
|
+
workers
|
|
358
|
+
};
|
|
359
|
+
} catch {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function getOsProcesses(processName) {
|
|
364
|
+
try {
|
|
365
|
+
const raw = (0, import_node_child_process.execSync)(`ps aux`, { encoding: "utf8", timeout: 5e3 });
|
|
366
|
+
return raw.split("\n").filter((line) => line.includes(processName) && !line.includes("process-audit") && !line.includes("grep")).map((line) => {
|
|
367
|
+
const parts = line.trim().split(/\s+/);
|
|
368
|
+
return {
|
|
369
|
+
pid: parseInt(parts[1] ?? "0", 10),
|
|
370
|
+
cpu: parts[2] ?? "0",
|
|
371
|
+
mem: parts[3] ?? "0",
|
|
372
|
+
command: parts.slice(10).join(" ")
|
|
373
|
+
};
|
|
374
|
+
}).filter((p) => !isNaN(p.pid) && p.pid > 0);
|
|
375
|
+
} catch {
|
|
376
|
+
return [];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function discoverProcessConfig(dirs) {
|
|
380
|
+
let pidFile;
|
|
381
|
+
let workersFile;
|
|
382
|
+
let processName = "";
|
|
383
|
+
for (const dir of dirs) {
|
|
384
|
+
if (!(0, import_node_fs.existsSync)(dir)) continue;
|
|
385
|
+
let entries;
|
|
386
|
+
try {
|
|
387
|
+
entries = (0, import_node_fs.readdirSync)(dir);
|
|
388
|
+
} catch {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
for (const f of entries) {
|
|
392
|
+
const fp = (0, import_node_path.join)(dir, f);
|
|
393
|
+
try {
|
|
394
|
+
if (!(0, import_node_fs.statSync)(fp).isFile()) continue;
|
|
395
|
+
} catch {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (f.endsWith(".pid") && !pidFile) {
|
|
399
|
+
pidFile = fp;
|
|
400
|
+
if (!processName) {
|
|
401
|
+
processName = (0, import_node_path.basename)(f, ".pid");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
405
|
+
workersFile = fp;
|
|
406
|
+
if (!processName && f !== "workers.json") {
|
|
407
|
+
processName = (0, import_node_path.basename)(f, "-workers.json");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (!processName && !pidFile && !workersFile) return null;
|
|
413
|
+
if (!processName) processName = "agent";
|
|
414
|
+
return { processName, pidFile, workersFile };
|
|
415
|
+
}
|
|
416
|
+
function auditProcesses(config) {
|
|
417
|
+
const pidFile = auditPidFile(config);
|
|
418
|
+
const systemd = auditSystemd(config);
|
|
419
|
+
const workers = auditWorkers(config);
|
|
420
|
+
const osProcesses = getOsProcesses(config.processName);
|
|
421
|
+
const knownPids = /* @__PURE__ */ new Set();
|
|
422
|
+
if (pidFile?.pid && !pidFile.stale) knownPids.add(pidFile.pid);
|
|
423
|
+
if (workers) {
|
|
424
|
+
if (workers.orchestratorPid) knownPids.add(workers.orchestratorPid);
|
|
425
|
+
for (const w of workers.workers) {
|
|
426
|
+
if (w.pid) knownPids.add(w.pid);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
430
|
+
const orphans = osProcesses.filter((p) => !knownPids.has(p.pid));
|
|
431
|
+
const problems = [];
|
|
432
|
+
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
433
|
+
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
434
|
+
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
435
|
+
if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
436
|
+
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
437
|
+
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
438
|
+
}
|
|
439
|
+
if (workers) {
|
|
440
|
+
for (const w of workers.workers) {
|
|
441
|
+
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
|
|
445
|
+
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
446
|
+
}
|
|
447
|
+
function formatAuditReport(result) {
|
|
448
|
+
const lines = [];
|
|
449
|
+
lines.push("");
|
|
450
|
+
lines.push("\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");
|
|
451
|
+
lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
|
|
452
|
+
lines.push("\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");
|
|
453
|
+
if (result.pidFile) {
|
|
454
|
+
const pf = result.pidFile;
|
|
455
|
+
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
456
|
+
lines.push(`
|
|
457
|
+
PID File: ${pf.path}`);
|
|
458
|
+
lines.push(` ${icon} ${pf.reason}`);
|
|
459
|
+
}
|
|
460
|
+
if (result.systemd) {
|
|
461
|
+
const sd = result.systemd;
|
|
462
|
+
const icon = sd.activeState === "active" ? "\u{1F7E2}" : sd.crashLooping ? "\u{1F7E1}" : sd.failed ? "\u{1F534}" : "\u26AA";
|
|
463
|
+
lines.push(`
|
|
464
|
+
Systemd: ${sd.unit}`);
|
|
465
|
+
lines.push(` ${icon} State: ${sd.activeState} (${sd.subState}) Result: ${sd.result}`);
|
|
466
|
+
lines.push(` Main PID: ${sd.mainPid || "none"} Restarts: ${sd.restarts}`);
|
|
467
|
+
}
|
|
468
|
+
if (result.workers) {
|
|
469
|
+
const w = result.workers;
|
|
470
|
+
lines.push(`
|
|
471
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
|
|
472
|
+
for (const worker of w.workers) {
|
|
473
|
+
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
474
|
+
lines.push(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (result.osProcesses.length > 0) {
|
|
478
|
+
lines.push(`
|
|
479
|
+
OS Processes (${result.osProcesses.length} total)`);
|
|
480
|
+
for (const p of result.osProcesses) {
|
|
481
|
+
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} ${p.command.substring(0, 55)}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (result.orphans.length > 0) {
|
|
485
|
+
lines.push(`
|
|
486
|
+
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
487
|
+
for (const p of result.orphans) {
|
|
488
|
+
lines.push(` PID ${p.pid} \u2014 not tracked by PID file or workers registry`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
lines.push("");
|
|
492
|
+
if (result.problems.length === 0) {
|
|
493
|
+
lines.push(" \u2705 All checks passed \u2014 no process issues detected.");
|
|
494
|
+
} else {
|
|
495
|
+
lines.push(` \u26A0\uFE0F ${result.problems.length} issue(s):`);
|
|
496
|
+
for (const p of result.problems) {
|
|
497
|
+
lines.push(` \u2022 ${p}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
lines.push("");
|
|
501
|
+
return lines.join("\n");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/live.ts
|
|
247
505
|
var C = {
|
|
248
506
|
reset: "\x1B[0m",
|
|
249
507
|
bold: "\x1B[1m",
|
|
@@ -276,13 +534,13 @@ function parseArgs(argv) {
|
|
|
276
534
|
config.recursive = true;
|
|
277
535
|
i++;
|
|
278
536
|
} else if (!arg.startsWith("-")) {
|
|
279
|
-
config.dirs.push((0,
|
|
537
|
+
config.dirs.push((0, import_node_path2.resolve)(arg));
|
|
280
538
|
i++;
|
|
281
539
|
} else {
|
|
282
540
|
i++;
|
|
283
541
|
}
|
|
284
542
|
}
|
|
285
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
543
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
|
|
286
544
|
return config;
|
|
287
545
|
}
|
|
288
546
|
function printUsage() {
|
|
@@ -318,7 +576,7 @@ function scanFiles(dirs, recursive) {
|
|
|
318
576
|
const seen = /* @__PURE__ */ new Set();
|
|
319
577
|
function scanDir(d, topLevel) {
|
|
320
578
|
try {
|
|
321
|
-
const dirStat = (0,
|
|
579
|
+
const dirStat = (0, import_node_fs2.statSync)(d);
|
|
322
580
|
const dirMtime = dirStat.mtime.getTime();
|
|
323
581
|
const cachedMtime = dirMtimeCache.get(d);
|
|
324
582
|
if (cachedMtime === dirMtime) {
|
|
@@ -334,13 +592,13 @@ function scanFiles(dirs, recursive) {
|
|
|
334
592
|
}
|
|
335
593
|
}
|
|
336
594
|
const dirResults = [];
|
|
337
|
-
for (const f of (0,
|
|
595
|
+
for (const f of (0, import_node_fs2.readdirSync)(d)) {
|
|
338
596
|
if (f.startsWith(".")) continue;
|
|
339
|
-
const fp = (0,
|
|
597
|
+
const fp = (0, import_node_path2.join)(d, f);
|
|
340
598
|
if (seen.has(fp)) continue;
|
|
341
599
|
let stat;
|
|
342
600
|
try {
|
|
343
|
-
stat = (0,
|
|
601
|
+
stat = (0, import_node_fs2.statSync)(fp);
|
|
344
602
|
} catch {
|
|
345
603
|
continue;
|
|
346
604
|
}
|
|
@@ -372,13 +630,13 @@ function scanFiles(dirs, recursive) {
|
|
|
372
630
|
}
|
|
373
631
|
function safeReadJson(fp) {
|
|
374
632
|
try {
|
|
375
|
-
return JSON.parse((0,
|
|
633
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
376
634
|
} catch {
|
|
377
635
|
return null;
|
|
378
636
|
}
|
|
379
637
|
}
|
|
380
638
|
function nameFromFile(filename) {
|
|
381
|
-
return (0,
|
|
639
|
+
return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
382
640
|
}
|
|
383
641
|
function normalizeStatus(val) {
|
|
384
642
|
if (typeof val !== "string") return "unknown";
|
|
@@ -556,7 +814,7 @@ function processJsonFile(file) {
|
|
|
556
814
|
}
|
|
557
815
|
function processJsonlFile(file) {
|
|
558
816
|
try {
|
|
559
|
-
const content = (0,
|
|
817
|
+
const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
|
|
560
818
|
if (!content) return [];
|
|
561
819
|
const lines = content.split("\n");
|
|
562
820
|
const lineCount = lines.length;
|
|
@@ -708,6 +966,9 @@ var prevFileCount = 0;
|
|
|
708
966
|
var newExecCount = 0;
|
|
709
967
|
var sessionStart = Date.now();
|
|
710
968
|
var firstRender = true;
|
|
969
|
+
var cachedAuditConfig = null;
|
|
970
|
+
var cachedAuditResult = null;
|
|
971
|
+
var lastAuditTime = 0;
|
|
711
972
|
var fileCache = /* @__PURE__ */ new Map();
|
|
712
973
|
function getRecordsCached(f) {
|
|
713
974
|
const cached = fileCache.get(f.path);
|
|
@@ -827,6 +1088,22 @@ function render(config) {
|
|
|
827
1088
|
const level = Math.round(v / maxBucket * 8);
|
|
828
1089
|
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
829
1090
|
}).join("");
|
|
1091
|
+
let auditResult = null;
|
|
1092
|
+
if (now - lastAuditTime > 1e4) {
|
|
1093
|
+
if (!cachedAuditConfig) {
|
|
1094
|
+
cachedAuditConfig = discoverProcessConfig(config.dirs);
|
|
1095
|
+
}
|
|
1096
|
+
if (cachedAuditConfig) {
|
|
1097
|
+
try {
|
|
1098
|
+
auditResult = auditProcesses(cachedAuditConfig);
|
|
1099
|
+
cachedAuditResult = auditResult;
|
|
1100
|
+
lastAuditTime = now;
|
|
1101
|
+
} catch {
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
} else {
|
|
1105
|
+
auditResult = cachedAuditResult;
|
|
1106
|
+
}
|
|
830
1107
|
const distributedTraces = [];
|
|
831
1108
|
if (allTraces.length > 1) {
|
|
832
1109
|
const traceGroups = groupByTraceId(allTraces);
|
|
@@ -907,6 +1184,41 @@ function render(config) {
|
|
|
907
1184
|
);
|
|
908
1185
|
writeLine(L, "");
|
|
909
1186
|
writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
1187
|
+
if (auditResult) {
|
|
1188
|
+
const ar = auditResult;
|
|
1189
|
+
const healthy = ar.problems.length === 0;
|
|
1190
|
+
const healthIcon = healthy ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1191
|
+
const healthLabel = healthy ? `${C.green}healthy${C.reset}` : `${C.red}${ar.problems.length} issue(s)${C.reset}`;
|
|
1192
|
+
const workerParts = [];
|
|
1193
|
+
if (ar.workers) {
|
|
1194
|
+
for (const w of ar.workers.workers) {
|
|
1195
|
+
const wIcon = w.declaredStatus === "running" && w.alive ? `${C.green}\u25CF${C.reset}` : w.stale ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1196
|
+
workerParts.push(`${wIcon} ${w.name}`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
let sysdLabel = "";
|
|
1200
|
+
if (ar.systemd) {
|
|
1201
|
+
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}`;
|
|
1202
|
+
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1203
|
+
if (ar.systemd.restarts > 0) sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1204
|
+
}
|
|
1205
|
+
let pidLabel = "";
|
|
1206
|
+
if (ar.pidFile?.pid) {
|
|
1207
|
+
const pi = ar.pidFile.alive && ar.pidFile.matchesProcess ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1208
|
+
pidLabel = ` ${C.bold}PID${C.reset} ${pi} ${ar.pidFile.pid}`;
|
|
1209
|
+
}
|
|
1210
|
+
writeLine(L, "");
|
|
1211
|
+
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1212
|
+
writeLine(L, ` ${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}`}`);
|
|
1213
|
+
if (workerParts.length > 0) {
|
|
1214
|
+
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1215
|
+
}
|
|
1216
|
+
if (!healthy) {
|
|
1217
|
+
for (const p of ar.problems.slice(0, 3)) {
|
|
1218
|
+
writeLine(L, ` ${C.red}\u2022${C.reset} ${C.dim}${p}${C.reset}`);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
910
1222
|
writeLine(L, "");
|
|
911
1223
|
writeLine(
|
|
912
1224
|
L,
|
|
@@ -1025,13 +1337,13 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
1025
1337
|
}
|
|
1026
1338
|
function startLive(argv) {
|
|
1027
1339
|
const config = parseArgs(argv);
|
|
1028
|
-
const valid = config.dirs.filter((d) => (0,
|
|
1340
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
|
|
1029
1341
|
if (valid.length === 0) {
|
|
1030
1342
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1031
1343
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1032
1344
|
process.exit(1);
|
|
1033
1345
|
}
|
|
1034
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
1346
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
|
|
1035
1347
|
if (invalid.length > 0) {
|
|
1036
1348
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1037
1349
|
}
|
|
@@ -1040,7 +1352,7 @@ function startLive(argv) {
|
|
|
1040
1352
|
let debounce = null;
|
|
1041
1353
|
for (const dir of config.dirs) {
|
|
1042
1354
|
try {
|
|
1043
|
-
(0,
|
|
1355
|
+
(0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
|
|
1044
1356
|
if (debounce) clearTimeout(debounce);
|
|
1045
1357
|
debounce = setTimeout(() => render(config), 500);
|
|
1046
1358
|
});
|
|
@@ -1054,262 +1366,6 @@ function startLive(argv) {
|
|
|
1054
1366
|
});
|
|
1055
1367
|
}
|
|
1056
1368
|
|
|
1057
|
-
// src/process-audit.ts
|
|
1058
|
-
var import_node_child_process = require("child_process");
|
|
1059
|
-
var import_node_fs2 = require("fs");
|
|
1060
|
-
var import_node_path2 = require("path");
|
|
1061
|
-
function isPidAlive(pid) {
|
|
1062
|
-
try {
|
|
1063
|
-
process.kill(pid, 0);
|
|
1064
|
-
return true;
|
|
1065
|
-
} catch {
|
|
1066
|
-
return false;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
function pidMatchesName(pid, name) {
|
|
1070
|
-
try {
|
|
1071
|
-
const cmdline = (0, import_node_fs2.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
1072
|
-
return cmdline.includes(name);
|
|
1073
|
-
} catch {
|
|
1074
|
-
return false;
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
function readPidFile(path) {
|
|
1078
|
-
try {
|
|
1079
|
-
const pid = parseInt((0, import_node_fs2.readFileSync)(path, "utf8").trim(), 10);
|
|
1080
|
-
return isNaN(pid) ? null : pid;
|
|
1081
|
-
} catch {
|
|
1082
|
-
return null;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
function auditPidFile(config) {
|
|
1086
|
-
if (!config.pidFile) return null;
|
|
1087
|
-
const pid = readPidFile(config.pidFile);
|
|
1088
|
-
if (pid === null) {
|
|
1089
|
-
return {
|
|
1090
|
-
path: config.pidFile,
|
|
1091
|
-
pid: null,
|
|
1092
|
-
alive: false,
|
|
1093
|
-
matchesProcess: false,
|
|
1094
|
-
stale: !(0, import_node_fs2.existsSync)(config.pidFile),
|
|
1095
|
-
reason: (0, import_node_fs2.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
const alive = isPidAlive(pid);
|
|
1099
|
-
const matchesProcess = alive ? pidMatchesName(pid, config.processName) : false;
|
|
1100
|
-
const stale = !alive || alive && !matchesProcess;
|
|
1101
|
-
let reason;
|
|
1102
|
-
if (alive && matchesProcess) {
|
|
1103
|
-
reason = `PID ${pid} alive and matches ${config.processName}`;
|
|
1104
|
-
} else if (alive && !matchesProcess) {
|
|
1105
|
-
reason = `PID ${pid} alive but is NOT ${config.processName} (PID reused by another process)`;
|
|
1106
|
-
} else {
|
|
1107
|
-
reason = `PID ${pid} no longer exists`;
|
|
1108
|
-
}
|
|
1109
|
-
return { path: config.pidFile, pid, alive, matchesProcess, stale, reason };
|
|
1110
|
-
}
|
|
1111
|
-
function auditSystemd(config) {
|
|
1112
|
-
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
1113
|
-
const unit = config.systemdUnit;
|
|
1114
|
-
try {
|
|
1115
|
-
const raw = (0, import_node_child_process.execSync)(
|
|
1116
|
-
`systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
|
|
1117
|
-
{ encoding: "utf8", timeout: 5e3 }
|
|
1118
|
-
);
|
|
1119
|
-
const props = {};
|
|
1120
|
-
for (const line of raw.trim().split("\n")) {
|
|
1121
|
-
const [k, ...v] = line.split("=");
|
|
1122
|
-
if (k) props[k.trim()] = v.join("=").trim();
|
|
1123
|
-
}
|
|
1124
|
-
const activeState = props["ActiveState"] ?? "unknown";
|
|
1125
|
-
const subState = props["SubState"] ?? "unknown";
|
|
1126
|
-
const mainPid = parseInt(props["MainPID"] ?? "0", 10);
|
|
1127
|
-
const restarts = parseInt(props["NRestarts"] ?? "0", 10);
|
|
1128
|
-
const result = props["Result"] ?? "unknown";
|
|
1129
|
-
return {
|
|
1130
|
-
unit,
|
|
1131
|
-
activeState,
|
|
1132
|
-
subState,
|
|
1133
|
-
mainPid,
|
|
1134
|
-
restarts,
|
|
1135
|
-
result,
|
|
1136
|
-
crashLooping: activeState === "activating" && subState === "auto-restart",
|
|
1137
|
-
failed: activeState === "failed"
|
|
1138
|
-
};
|
|
1139
|
-
} catch {
|
|
1140
|
-
return null;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
function auditWorkers(config) {
|
|
1144
|
-
if (!config.workersFile || !(0, import_node_fs2.existsSync)(config.workersFile)) return null;
|
|
1145
|
-
try {
|
|
1146
|
-
const data = JSON.parse((0, import_node_fs2.readFileSync)(config.workersFile, "utf8"));
|
|
1147
|
-
const orchPid = data.pid ?? null;
|
|
1148
|
-
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
1149
|
-
const workers = [];
|
|
1150
|
-
for (const [name, info] of Object.entries(data.tools ?? {})) {
|
|
1151
|
-
const w = info;
|
|
1152
|
-
const wPid = w.pid ?? null;
|
|
1153
|
-
const wAlive = wPid ? isPidAlive(wPid) : false;
|
|
1154
|
-
workers.push({
|
|
1155
|
-
name,
|
|
1156
|
-
pid: wPid,
|
|
1157
|
-
declaredStatus: w.status ?? "unknown",
|
|
1158
|
-
alive: wAlive,
|
|
1159
|
-
stale: w.status === "running" && !wAlive
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
return {
|
|
1163
|
-
orchestratorPid: orchPid,
|
|
1164
|
-
orchestratorAlive: orchAlive,
|
|
1165
|
-
startedAt: data.started_at ?? "",
|
|
1166
|
-
workers
|
|
1167
|
-
};
|
|
1168
|
-
} catch {
|
|
1169
|
-
return null;
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
function getOsProcesses(processName) {
|
|
1173
|
-
try {
|
|
1174
|
-
const raw = (0, import_node_child_process.execSync)(`ps aux`, { encoding: "utf8", timeout: 5e3 });
|
|
1175
|
-
return raw.split("\n").filter((line) => line.includes(processName) && !line.includes("process-audit") && !line.includes("grep")).map((line) => {
|
|
1176
|
-
const parts = line.trim().split(/\s+/);
|
|
1177
|
-
return {
|
|
1178
|
-
pid: parseInt(parts[1] ?? "0", 10),
|
|
1179
|
-
cpu: parts[2] ?? "0",
|
|
1180
|
-
mem: parts[3] ?? "0",
|
|
1181
|
-
command: parts.slice(10).join(" ")
|
|
1182
|
-
};
|
|
1183
|
-
}).filter((p) => !isNaN(p.pid) && p.pid > 0);
|
|
1184
|
-
} catch {
|
|
1185
|
-
return [];
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
function discoverProcessConfig(dirs) {
|
|
1189
|
-
let pidFile;
|
|
1190
|
-
let workersFile;
|
|
1191
|
-
let processName = "";
|
|
1192
|
-
for (const dir of dirs) {
|
|
1193
|
-
if (!(0, import_node_fs2.existsSync)(dir)) continue;
|
|
1194
|
-
let entries;
|
|
1195
|
-
try {
|
|
1196
|
-
entries = (0, import_node_fs2.readdirSync)(dir);
|
|
1197
|
-
} catch {
|
|
1198
|
-
continue;
|
|
1199
|
-
}
|
|
1200
|
-
for (const f of entries) {
|
|
1201
|
-
const fp = (0, import_node_path2.join)(dir, f);
|
|
1202
|
-
try {
|
|
1203
|
-
if (!(0, import_node_fs2.statSync)(fp).isFile()) continue;
|
|
1204
|
-
} catch {
|
|
1205
|
-
continue;
|
|
1206
|
-
}
|
|
1207
|
-
if (f.endsWith(".pid") && !pidFile) {
|
|
1208
|
-
pidFile = fp;
|
|
1209
|
-
if (!processName) {
|
|
1210
|
-
processName = (0, import_node_path2.basename)(f, ".pid");
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
1214
|
-
workersFile = fp;
|
|
1215
|
-
if (!processName && f !== "workers.json") {
|
|
1216
|
-
processName = (0, import_node_path2.basename)(f, "-workers.json");
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
if (!processName && !pidFile && !workersFile) return null;
|
|
1222
|
-
if (!processName) processName = "agent";
|
|
1223
|
-
return { processName, pidFile, workersFile };
|
|
1224
|
-
}
|
|
1225
|
-
function auditProcesses(config) {
|
|
1226
|
-
const pidFile = auditPidFile(config);
|
|
1227
|
-
const systemd = auditSystemd(config);
|
|
1228
|
-
const workers = auditWorkers(config);
|
|
1229
|
-
const osProcesses = getOsProcesses(config.processName);
|
|
1230
|
-
const knownPids = /* @__PURE__ */ new Set();
|
|
1231
|
-
if (pidFile?.pid && !pidFile.stale) knownPids.add(pidFile.pid);
|
|
1232
|
-
if (workers) {
|
|
1233
|
-
if (workers.orchestratorPid) knownPids.add(workers.orchestratorPid);
|
|
1234
|
-
for (const w of workers.workers) {
|
|
1235
|
-
if (w.pid) knownPids.add(w.pid);
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
1239
|
-
const orphans = osProcesses.filter((p) => !knownPids.has(p.pid));
|
|
1240
|
-
const problems = [];
|
|
1241
|
-
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
1242
|
-
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
1243
|
-
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
1244
|
-
if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
1245
|
-
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
1246
|
-
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
1247
|
-
}
|
|
1248
|
-
if (workers) {
|
|
1249
|
-
for (const w of workers.workers) {
|
|
1250
|
-
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
|
|
1254
|
-
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
1255
|
-
}
|
|
1256
|
-
function formatAuditReport(result) {
|
|
1257
|
-
const lines = [];
|
|
1258
|
-
lines.push("");
|
|
1259
|
-
lines.push("\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");
|
|
1260
|
-
lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
|
|
1261
|
-
lines.push("\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");
|
|
1262
|
-
if (result.pidFile) {
|
|
1263
|
-
const pf = result.pidFile;
|
|
1264
|
-
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
1265
|
-
lines.push(`
|
|
1266
|
-
PID File: ${pf.path}`);
|
|
1267
|
-
lines.push(` ${icon} ${pf.reason}`);
|
|
1268
|
-
}
|
|
1269
|
-
if (result.systemd) {
|
|
1270
|
-
const sd = result.systemd;
|
|
1271
|
-
const icon = sd.activeState === "active" ? "\u{1F7E2}" : sd.crashLooping ? "\u{1F7E1}" : sd.failed ? "\u{1F534}" : "\u26AA";
|
|
1272
|
-
lines.push(`
|
|
1273
|
-
Systemd: ${sd.unit}`);
|
|
1274
|
-
lines.push(` ${icon} State: ${sd.activeState} (${sd.subState}) Result: ${sd.result}`);
|
|
1275
|
-
lines.push(` Main PID: ${sd.mainPid || "none"} Restarts: ${sd.restarts}`);
|
|
1276
|
-
}
|
|
1277
|
-
if (result.workers) {
|
|
1278
|
-
const w = result.workers;
|
|
1279
|
-
lines.push(`
|
|
1280
|
-
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
|
|
1281
|
-
for (const worker of w.workers) {
|
|
1282
|
-
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
1283
|
-
lines.push(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
if (result.osProcesses.length > 0) {
|
|
1287
|
-
lines.push(`
|
|
1288
|
-
OS Processes (${result.osProcesses.length} total)`);
|
|
1289
|
-
for (const p of result.osProcesses) {
|
|
1290
|
-
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} ${p.command.substring(0, 55)}`);
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
if (result.orphans.length > 0) {
|
|
1294
|
-
lines.push(`
|
|
1295
|
-
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
1296
|
-
for (const p of result.orphans) {
|
|
1297
|
-
lines.push(` PID ${p.pid} \u2014 not tracked by PID file or workers registry`);
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
lines.push("");
|
|
1301
|
-
if (result.problems.length === 0) {
|
|
1302
|
-
lines.push(" \u2705 All checks passed \u2014 no process issues detected.");
|
|
1303
|
-
} else {
|
|
1304
|
-
lines.push(` \u26A0\uFE0F ${result.problems.length} issue(s):`);
|
|
1305
|
-
for (const p of result.problems) {
|
|
1306
|
-
lines.push(` \u2022 ${p}`);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
lines.push("");
|
|
1310
|
-
return lines.join("\n");
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
1369
|
// src/runner.ts
|
|
1314
1370
|
var import_node_child_process2 = require("child_process");
|
|
1315
1371
|
var import_node_fs3 = require("fs");
|