agentflow-core 0.6.0 → 0.6.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/{chunk-VF4FSBXR.js → chunk-BXZC5ZMJ.js} +357 -268
- package/dist/cli.cjs +361 -270
- package/dist/cli.js +1 -1
- package/dist/index.cjs +359 -270
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- 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,288 @@ 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 readCmdline(pid) {
|
|
364
|
+
try {
|
|
365
|
+
return (0, import_node_fs.readFileSync)(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim();
|
|
366
|
+
} catch {
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function getOsProcesses(processName) {
|
|
371
|
+
try {
|
|
372
|
+
const raw = (0, import_node_child_process.execSync)(
|
|
373
|
+
`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`,
|
|
374
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
375
|
+
);
|
|
376
|
+
const results = [];
|
|
377
|
+
for (const line of raw.split("\n")) {
|
|
378
|
+
if (!line.includes(processName)) continue;
|
|
379
|
+
if (line.includes("process-audit") || line.includes(" grep ")) continue;
|
|
380
|
+
const trimmed = line.trim();
|
|
381
|
+
const parts = trimmed.split(/\s+/);
|
|
382
|
+
const pid = parseInt(parts[0] ?? "0", 10);
|
|
383
|
+
if (isNaN(pid) || pid <= 0) continue;
|
|
384
|
+
const cpu = parts[1] ?? "0";
|
|
385
|
+
const mem = parts[2] ?? "0";
|
|
386
|
+
const elapsed = parts[3] ?? "";
|
|
387
|
+
const started = parts.slice(4, 9).join(" ");
|
|
388
|
+
const command = parts.slice(9).join(" ");
|
|
389
|
+
const cmdline = readCmdline(pid);
|
|
390
|
+
results.push({ pid, cpu, mem, elapsed, started, command, cmdline });
|
|
391
|
+
}
|
|
392
|
+
return results;
|
|
393
|
+
} catch {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function discoverProcessConfig(dirs) {
|
|
398
|
+
let pidFile;
|
|
399
|
+
let workersFile;
|
|
400
|
+
let processName = "";
|
|
401
|
+
for (const dir of dirs) {
|
|
402
|
+
if (!(0, import_node_fs.existsSync)(dir)) continue;
|
|
403
|
+
let entries;
|
|
404
|
+
try {
|
|
405
|
+
entries = (0, import_node_fs.readdirSync)(dir);
|
|
406
|
+
} catch {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
for (const f of entries) {
|
|
410
|
+
const fp = (0, import_node_path.join)(dir, f);
|
|
411
|
+
try {
|
|
412
|
+
if (!(0, import_node_fs.statSync)(fp).isFile()) continue;
|
|
413
|
+
} catch {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (f.endsWith(".pid") && !pidFile) {
|
|
417
|
+
pidFile = fp;
|
|
418
|
+
if (!processName) {
|
|
419
|
+
processName = (0, import_node_path.basename)(f, ".pid");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
423
|
+
workersFile = fp;
|
|
424
|
+
if (!processName && f !== "workers.json") {
|
|
425
|
+
processName = (0, import_node_path.basename)(f, "-workers.json");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!processName && !pidFile && !workersFile) return null;
|
|
431
|
+
if (!processName) processName = "agent";
|
|
432
|
+
return { processName, pidFile, workersFile };
|
|
433
|
+
}
|
|
434
|
+
function auditProcesses(config) {
|
|
435
|
+
const pidFile = auditPidFile(config);
|
|
436
|
+
const systemd = auditSystemd(config);
|
|
437
|
+
const workers = auditWorkers(config);
|
|
438
|
+
const osProcesses = getOsProcesses(config.processName);
|
|
439
|
+
const knownPids = /* @__PURE__ */ new Set();
|
|
440
|
+
if (pidFile?.pid && !pidFile.stale) knownPids.add(pidFile.pid);
|
|
441
|
+
if (workers) {
|
|
442
|
+
if (workers.orchestratorPid) knownPids.add(workers.orchestratorPid);
|
|
443
|
+
for (const w of workers.workers) {
|
|
444
|
+
if (w.pid) knownPids.add(w.pid);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
448
|
+
const selfPid = process.pid;
|
|
449
|
+
const selfPpid = process.ppid;
|
|
450
|
+
const orphans = osProcesses.filter(
|
|
451
|
+
(p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
452
|
+
);
|
|
453
|
+
const problems = [];
|
|
454
|
+
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
455
|
+
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
456
|
+
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
457
|
+
if (systemd && systemd.restarts > 10) problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
458
|
+
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
459
|
+
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
460
|
+
}
|
|
461
|
+
if (workers) {
|
|
462
|
+
for (const w of workers.workers) {
|
|
463
|
+
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (orphans.length > 0) problems.push(`${orphans.length} orphan process(es) not tracked by PID file or workers registry`);
|
|
467
|
+
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
468
|
+
}
|
|
469
|
+
function formatAuditReport(result) {
|
|
470
|
+
const lines = [];
|
|
471
|
+
lines.push("");
|
|
472
|
+
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");
|
|
473
|
+
lines.push("\u2551 \u{1F50D} P R O C E S S A U D I T \u2551");
|
|
474
|
+
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");
|
|
475
|
+
if (result.pidFile) {
|
|
476
|
+
const pf = result.pidFile;
|
|
477
|
+
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
478
|
+
lines.push(`
|
|
479
|
+
PID File: ${pf.path}`);
|
|
480
|
+
lines.push(` ${icon} ${pf.reason}`);
|
|
481
|
+
}
|
|
482
|
+
if (result.systemd) {
|
|
483
|
+
const sd = result.systemd;
|
|
484
|
+
const icon = sd.activeState === "active" ? "\u{1F7E2}" : sd.crashLooping ? "\u{1F7E1}" : sd.failed ? "\u{1F534}" : "\u26AA";
|
|
485
|
+
lines.push(`
|
|
486
|
+
Systemd: ${sd.unit}`);
|
|
487
|
+
lines.push(` ${icon} State: ${sd.activeState} (${sd.subState}) Result: ${sd.result}`);
|
|
488
|
+
lines.push(` Main PID: ${sd.mainPid || "none"} Restarts: ${sd.restarts}`);
|
|
489
|
+
}
|
|
490
|
+
if (result.workers) {
|
|
491
|
+
const w = result.workers;
|
|
492
|
+
lines.push(`
|
|
493
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`);
|
|
494
|
+
for (const worker of w.workers) {
|
|
495
|
+
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
496
|
+
lines.push(` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (result.osProcesses.length > 0) {
|
|
500
|
+
lines.push(`
|
|
501
|
+
OS Processes (${result.osProcesses.length} total)`);
|
|
502
|
+
for (const p of result.osProcesses) {
|
|
503
|
+
lines.push(` 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)}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (result.orphans.length > 0) {
|
|
507
|
+
lines.push(`
|
|
508
|
+
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
509
|
+
for (const p of result.orphans) {
|
|
510
|
+
lines.push(` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`);
|
|
511
|
+
lines.push(` Started: ${p.started}`);
|
|
512
|
+
lines.push(` Command: ${p.cmdline || p.command}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
lines.push("");
|
|
516
|
+
if (result.problems.length === 0) {
|
|
517
|
+
lines.push(" \u2705 All checks passed \u2014 no process issues detected.");
|
|
518
|
+
} else {
|
|
519
|
+
lines.push(` \u26A0\uFE0F ${result.problems.length} issue(s):`);
|
|
520
|
+
for (const p of result.problems) {
|
|
521
|
+
lines.push(` \u2022 ${p}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
lines.push("");
|
|
525
|
+
return lines.join("\n");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/live.ts
|
|
247
529
|
var C = {
|
|
248
530
|
reset: "\x1B[0m",
|
|
249
531
|
bold: "\x1B[1m",
|
|
@@ -276,13 +558,13 @@ function parseArgs(argv) {
|
|
|
276
558
|
config.recursive = true;
|
|
277
559
|
i++;
|
|
278
560
|
} else if (!arg.startsWith("-")) {
|
|
279
|
-
config.dirs.push((0,
|
|
561
|
+
config.dirs.push((0, import_node_path2.resolve)(arg));
|
|
280
562
|
i++;
|
|
281
563
|
} else {
|
|
282
564
|
i++;
|
|
283
565
|
}
|
|
284
566
|
}
|
|
285
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
567
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
|
|
286
568
|
return config;
|
|
287
569
|
}
|
|
288
570
|
function printUsage() {
|
|
@@ -318,7 +600,7 @@ function scanFiles(dirs, recursive) {
|
|
|
318
600
|
const seen = /* @__PURE__ */ new Set();
|
|
319
601
|
function scanDir(d, topLevel) {
|
|
320
602
|
try {
|
|
321
|
-
const dirStat = (0,
|
|
603
|
+
const dirStat = (0, import_node_fs2.statSync)(d);
|
|
322
604
|
const dirMtime = dirStat.mtime.getTime();
|
|
323
605
|
const cachedMtime = dirMtimeCache.get(d);
|
|
324
606
|
if (cachedMtime === dirMtime) {
|
|
@@ -334,13 +616,13 @@ function scanFiles(dirs, recursive) {
|
|
|
334
616
|
}
|
|
335
617
|
}
|
|
336
618
|
const dirResults = [];
|
|
337
|
-
for (const f of (0,
|
|
619
|
+
for (const f of (0, import_node_fs2.readdirSync)(d)) {
|
|
338
620
|
if (f.startsWith(".")) continue;
|
|
339
|
-
const fp = (0,
|
|
621
|
+
const fp = (0, import_node_path2.join)(d, f);
|
|
340
622
|
if (seen.has(fp)) continue;
|
|
341
623
|
let stat;
|
|
342
624
|
try {
|
|
343
|
-
stat = (0,
|
|
625
|
+
stat = (0, import_node_fs2.statSync)(fp);
|
|
344
626
|
} catch {
|
|
345
627
|
continue;
|
|
346
628
|
}
|
|
@@ -372,13 +654,13 @@ function scanFiles(dirs, recursive) {
|
|
|
372
654
|
}
|
|
373
655
|
function safeReadJson(fp) {
|
|
374
656
|
try {
|
|
375
|
-
return JSON.parse((0,
|
|
657
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
|
|
376
658
|
} catch {
|
|
377
659
|
return null;
|
|
378
660
|
}
|
|
379
661
|
}
|
|
380
662
|
function nameFromFile(filename) {
|
|
381
|
-
return (0,
|
|
663
|
+
return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
382
664
|
}
|
|
383
665
|
function normalizeStatus(val) {
|
|
384
666
|
if (typeof val !== "string") return "unknown";
|
|
@@ -556,7 +838,7 @@ function processJsonFile(file) {
|
|
|
556
838
|
}
|
|
557
839
|
function processJsonlFile(file) {
|
|
558
840
|
try {
|
|
559
|
-
const content = (0,
|
|
841
|
+
const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
|
|
560
842
|
if (!content) return [];
|
|
561
843
|
const lines = content.split("\n");
|
|
562
844
|
const lineCount = lines.length;
|
|
@@ -708,6 +990,9 @@ var prevFileCount = 0;
|
|
|
708
990
|
var newExecCount = 0;
|
|
709
991
|
var sessionStart = Date.now();
|
|
710
992
|
var firstRender = true;
|
|
993
|
+
var cachedAuditConfig = null;
|
|
994
|
+
var cachedAuditResult = null;
|
|
995
|
+
var lastAuditTime = 0;
|
|
711
996
|
var fileCache = /* @__PURE__ */ new Map();
|
|
712
997
|
function getRecordsCached(f) {
|
|
713
998
|
const cached = fileCache.get(f.path);
|
|
@@ -827,6 +1112,24 @@ function render(config) {
|
|
|
827
1112
|
const level = Math.round(v / maxBucket * 8);
|
|
828
1113
|
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
829
1114
|
}).join("");
|
|
1115
|
+
let auditResult = null;
|
|
1116
|
+
if (now - lastAuditTime > 1e4) {
|
|
1117
|
+
if (!cachedAuditConfig) {
|
|
1118
|
+
cachedAuditConfig = discoverProcessConfig(config.dirs);
|
|
1119
|
+
}
|
|
1120
|
+
if (cachedAuditConfig) {
|
|
1121
|
+
try {
|
|
1122
|
+
auditResult = auditProcesses(cachedAuditConfig);
|
|
1123
|
+
cachedAuditResult = auditResult;
|
|
1124
|
+
lastAuditTime = now;
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
process.stderr.write(`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
|
|
1127
|
+
`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
} else {
|
|
1131
|
+
auditResult = cachedAuditResult;
|
|
1132
|
+
}
|
|
830
1133
|
const distributedTraces = [];
|
|
831
1134
|
if (allTraces.length > 1) {
|
|
832
1135
|
const traceGroups = groupByTraceId(allTraces);
|
|
@@ -907,6 +1210,50 @@ function render(config) {
|
|
|
907
1210
|
);
|
|
908
1211
|
writeLine(L, "");
|
|
909
1212
|
writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
1213
|
+
if (auditResult) {
|
|
1214
|
+
const ar = auditResult;
|
|
1215
|
+
const healthy = ar.problems.length === 0;
|
|
1216
|
+
const healthIcon = healthy ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1217
|
+
const healthLabel = healthy ? `${C.green}healthy${C.reset}` : `${C.red}${ar.problems.length} issue(s)${C.reset}`;
|
|
1218
|
+
const workerParts = [];
|
|
1219
|
+
if (ar.workers) {
|
|
1220
|
+
for (const w of ar.workers.workers) {
|
|
1221
|
+
const wIcon = w.declaredStatus === "running" && w.alive ? `${C.green}\u25CF${C.reset}` : w.stale ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1222
|
+
workerParts.push(`${wIcon} ${w.name}`);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
let sysdLabel = "";
|
|
1226
|
+
if (ar.systemd) {
|
|
1227
|
+
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}`;
|
|
1228
|
+
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1229
|
+
if (ar.systemd.restarts > 0) sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1230
|
+
}
|
|
1231
|
+
let pidLabel = "";
|
|
1232
|
+
if (ar.pidFile?.pid) {
|
|
1233
|
+
const pi = ar.pidFile.alive && ar.pidFile.matchesProcess ? `${C.green}\u25CF${C.reset}` : `${C.red}\u25CF${C.reset}`;
|
|
1234
|
+
pidLabel = ` ${C.bold}PID${C.reset} ${pi} ${ar.pidFile.pid}`;
|
|
1235
|
+
}
|
|
1236
|
+
writeLine(L, "");
|
|
1237
|
+
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1238
|
+
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}`}`);
|
|
1239
|
+
if (workerParts.length > 0) {
|
|
1240
|
+
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1241
|
+
}
|
|
1242
|
+
if (!healthy) {
|
|
1243
|
+
for (const p of ar.problems.slice(0, 3)) {
|
|
1244
|
+
writeLine(L, ` ${C.red}\u2022${C.reset} ${C.dim}${p}${C.reset}`);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (ar.orphans.length > 0) {
|
|
1248
|
+
for (const o of ar.orphans.slice(0, 5)) {
|
|
1249
|
+
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1250
|
+
writeLine(L, ` ${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}`);
|
|
1251
|
+
}
|
|
1252
|
+
if (ar.orphans.length > 5) {
|
|
1253
|
+
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
910
1257
|
writeLine(L, "");
|
|
911
1258
|
writeLine(
|
|
912
1259
|
L,
|
|
@@ -1025,13 +1372,13 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
1025
1372
|
}
|
|
1026
1373
|
function startLive(argv) {
|
|
1027
1374
|
const config = parseArgs(argv);
|
|
1028
|
-
const valid = config.dirs.filter((d) => (0,
|
|
1375
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
|
|
1029
1376
|
if (valid.length === 0) {
|
|
1030
1377
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1031
1378
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1032
1379
|
process.exit(1);
|
|
1033
1380
|
}
|
|
1034
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
1381
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
|
|
1035
1382
|
if (invalid.length > 0) {
|
|
1036
1383
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1037
1384
|
}
|
|
@@ -1040,7 +1387,7 @@ function startLive(argv) {
|
|
|
1040
1387
|
let debounce = null;
|
|
1041
1388
|
for (const dir of config.dirs) {
|
|
1042
1389
|
try {
|
|
1043
|
-
(0,
|
|
1390
|
+
(0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
|
|
1044
1391
|
if (debounce) clearTimeout(debounce);
|
|
1045
1392
|
debounce = setTimeout(() => render(config), 500);
|
|
1046
1393
|
});
|
|
@@ -1054,262 +1401,6 @@ function startLive(argv) {
|
|
|
1054
1401
|
});
|
|
1055
1402
|
}
|
|
1056
1403
|
|
|
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
1404
|
// src/runner.ts
|
|
1314
1405
|
var import_node_child_process2 = require("child_process");
|
|
1315
1406
|
var import_node_fs3 = require("fs");
|