agentflow-core 0.3.3 → 0.5.0

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/index.cjs CHANGED
@@ -20,7 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ checkGuards: () => checkGuards,
23
24
  createGraphBuilder: () => createGraphBuilder,
25
+ createTraceStore: () => createTraceStore,
24
26
  findWaitingOn: () => findWaitingOn,
25
27
  getChildren: () => getChildren,
26
28
  getCriticalPath: () => getCriticalPath,
@@ -39,7 +41,10 @@ __export(index_exports, {
39
41
  runTraced: () => runTraced,
40
42
  startLive: () => startLive,
41
43
  startWatch: () => startWatch,
42
- stitchTrace: () => stitchTrace
44
+ stitchTrace: () => stitchTrace,
45
+ toAsciiTree: () => toAsciiTree,
46
+ toTimeline: () => toTimeline,
47
+ withGuards: () => withGuards
43
48
  });
44
49
  module.exports = __toCommonJS(index_exports);
45
50
 
@@ -269,213 +274,6 @@ function createGraphBuilder(config) {
269
274
  return builder;
270
275
  }
271
276
 
272
- // src/loader.ts
273
- function toNodesMap(raw) {
274
- if (raw instanceof Map) return raw;
275
- if (Array.isArray(raw)) {
276
- return new Map(raw);
277
- }
278
- if (raw !== null && typeof raw === "object") {
279
- return new Map(Object.entries(raw));
280
- }
281
- return /* @__PURE__ */ new Map();
282
- }
283
- function loadGraph(input) {
284
- const raw = typeof input === "string" ? JSON.parse(input) : input;
285
- const nodes = toNodesMap(raw.nodes);
286
- return {
287
- id: raw.id ?? "",
288
- rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
289
- nodes,
290
- edges: raw.edges ?? [],
291
- startTime: raw.startTime ?? 0,
292
- endTime: raw.endTime ?? null,
293
- status: raw.status ?? "completed",
294
- trigger: raw.trigger ?? "unknown",
295
- agentId: raw.agentId ?? "unknown",
296
- events: raw.events ?? [],
297
- traceId: raw.traceId,
298
- spanId: raw.spanId,
299
- parentSpanId: raw.parentSpanId
300
- };
301
- }
302
- function graphToJson(graph) {
303
- const nodesObj = {};
304
- for (const [id, node] of graph.nodes) {
305
- nodesObj[id] = node;
306
- }
307
- return {
308
- id: graph.id,
309
- rootNodeId: graph.rootNodeId,
310
- nodes: nodesObj,
311
- edges: graph.edges,
312
- startTime: graph.startTime,
313
- endTime: graph.endTime,
314
- status: graph.status,
315
- trigger: graph.trigger,
316
- agentId: graph.agentId,
317
- events: graph.events,
318
- traceId: graph.traceId,
319
- spanId: graph.spanId,
320
- parentSpanId: graph.parentSpanId
321
- };
322
- }
323
-
324
- // src/runner.ts
325
- var import_node_child_process = require("child_process");
326
- var import_node_fs = require("fs");
327
- var import_node_path = require("path");
328
- function globToRegex(pattern) {
329
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
330
- return new RegExp(`^${escaped}$`);
331
- }
332
- function snapshotDir(dir, patterns) {
333
- const result = /* @__PURE__ */ new Map();
334
- if (!(0, import_node_fs.existsSync)(dir)) return result;
335
- for (const entry of (0, import_node_fs.readdirSync)(dir)) {
336
- if (!patterns.some((re) => re.test(entry))) continue;
337
- const full = (0, import_node_path.join)(dir, entry);
338
- try {
339
- const stat = (0, import_node_fs.statSync)(full);
340
- if (stat.isFile()) {
341
- result.set(full, stat.mtimeMs);
342
- }
343
- } catch {
344
- }
345
- }
346
- return result;
347
- }
348
- function agentIdFromFilename(filePath) {
349
- const base = (0, import_node_path.basename)(filePath, ".json");
350
- const cleaned = base.replace(/-state$/, "");
351
- return `alfred-${cleaned}`;
352
- }
353
- function deriveAgentId(command) {
354
- return "orchestrator";
355
- }
356
- function fileTimestamp() {
357
- return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
358
- }
359
- async function runTraced(config) {
360
- const {
361
- command,
362
- agentId = deriveAgentId(command),
363
- trigger = "cli",
364
- tracesDir = "./traces",
365
- watchDirs = [],
366
- watchPatterns = ["*.json"]
367
- } = config;
368
- if (command.length === 0) {
369
- throw new Error("runTraced: command must not be empty");
370
- }
371
- const resolvedTracesDir = (0, import_node_path.resolve)(tracesDir);
372
- const patterns = watchPatterns.map(globToRegex);
373
- const orchestrator = createGraphBuilder({ agentId, trigger });
374
- const { traceId, spanId } = orchestrator.traceContext;
375
- const beforeSnapshots = /* @__PURE__ */ new Map();
376
- for (const dir of watchDirs) {
377
- beforeSnapshots.set(dir, snapshotDir(dir, patterns));
378
- }
379
- const rootId = orchestrator.startNode({ type: "agent", name: agentId });
380
- const dispatchId = orchestrator.startNode({
381
- type: "tool",
382
- name: "dispatch-command",
383
- parentId: rootId
384
- });
385
- orchestrator.updateState(dispatchId, { command: command.join(" ") });
386
- const monitorId = orchestrator.startNode({
387
- type: "tool",
388
- name: "state-monitor",
389
- parentId: rootId
390
- });
391
- orchestrator.updateState(monitorId, {
392
- watchDirs,
393
- watchPatterns
394
- });
395
- const startMs = Date.now();
396
- const execCmd = command[0] ?? "";
397
- const execArgs = command.slice(1);
398
- process.env.AGENTFLOW_TRACE_ID = traceId;
399
- process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
400
- const result = (0, import_node_child_process.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
401
- delete process.env.AGENTFLOW_TRACE_ID;
402
- delete process.env.AGENTFLOW_PARENT_SPAN_ID;
403
- const exitCode = result.status ?? 1;
404
- const duration = (Date.now() - startMs) / 1e3;
405
- const stateChanges = [];
406
- for (const dir of watchDirs) {
407
- const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
408
- const after = snapshotDir(dir, patterns);
409
- for (const [filePath, mtime] of after) {
410
- const prevMtime = before.get(filePath);
411
- if (prevMtime === void 0 || mtime > prevMtime) {
412
- stateChanges.push(filePath);
413
- }
414
- }
415
- }
416
- orchestrator.updateState(monitorId, { stateChanges });
417
- orchestrator.endNode(monitorId);
418
- if (exitCode === 0) {
419
- orchestrator.endNode(dispatchId);
420
- } else {
421
- orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
422
- }
423
- orchestrator.updateState(rootId, {
424
- exitCode,
425
- duration,
426
- stateChanges
427
- });
428
- if (exitCode === 0) {
429
- orchestrator.endNode(rootId);
430
- } else {
431
- orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
432
- }
433
- const orchestratorGraph = orchestrator.build();
434
- const allGraphs = [orchestratorGraph];
435
- for (const filePath of stateChanges) {
436
- const childAgentId = agentIdFromFilename(filePath);
437
- const childBuilder = createGraphBuilder({
438
- agentId: childAgentId,
439
- trigger: "state-change",
440
- traceId,
441
- parentSpanId: spanId
442
- });
443
- const childRootId = childBuilder.startNode({
444
- type: "agent",
445
- name: childAgentId
446
- });
447
- childBuilder.updateState(childRootId, {
448
- stateFile: filePath,
449
- detectedBy: "runner-state-monitor"
450
- });
451
- childBuilder.endNode(childRootId);
452
- allGraphs.push(childBuilder.build());
453
- }
454
- if (!(0, import_node_fs.existsSync)(resolvedTracesDir)) {
455
- (0, import_node_fs.mkdirSync)(resolvedTracesDir, { recursive: true });
456
- }
457
- const ts = fileTimestamp();
458
- const tracePaths = [];
459
- for (const graph of allGraphs) {
460
- const filename = `${graph.agentId}-${ts}.json`;
461
- const outPath = (0, import_node_path.join)(resolvedTracesDir, filename);
462
- (0, import_node_fs.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
463
- tracePaths.push(outPath);
464
- }
465
- return {
466
- exitCode,
467
- traceId,
468
- spanId,
469
- tracePaths,
470
- stateChanges,
471
- duration
472
- };
473
- }
474
-
475
- // src/live.ts
476
- var import_node_fs2 = require("fs");
477
- var import_node_path2 = require("path");
478
-
479
277
  // src/graph-query.ts
480
278
  function getNode(graph, nodeId) {
481
279
  return graph.nodes.get(nodeId);
@@ -507,13 +305,13 @@ function getHungNodes(graph) {
507
305
  function getCriticalPath(graph) {
508
306
  const root = graph.nodes.get(graph.rootNodeId);
509
307
  if (!root) return [];
510
- function nodeDuration(node) {
308
+ function nodeDuration2(node) {
511
309
  const end = node.endTime ?? Date.now();
512
310
  return end - node.startTime;
513
311
  }
514
312
  function dfs(node) {
515
313
  if (node.children.length === 0) {
516
- return { duration: nodeDuration(node), path: [node] };
314
+ return { duration: nodeDuration2(node), path: [node] };
517
315
  }
518
316
  let bestChild = { duration: -1, path: [] };
519
317
  for (const childId of node.children) {
@@ -525,7 +323,7 @@ function getCriticalPath(graph) {
525
323
  }
526
324
  }
527
325
  return {
528
- duration: nodeDuration(node) + bestChild.duration,
326
+ duration: nodeDuration2(node) + bestChild.duration,
529
327
  path: [node, ...bestChild.path]
530
328
  };
531
329
  }
@@ -674,9 +472,224 @@ function getTraceTree(trace) {
674
472
  const children = trace.childMap.get(spanId) ?? [];
675
473
  for (const childSpan of children) walk(childSpan);
676
474
  }
677
- if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
678
- else result.push(trace.rootGraph);
679
- return result;
475
+ if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
476
+ else result.push(trace.rootGraph);
477
+ return result;
478
+ }
479
+
480
+ // src/guards.ts
481
+ var DEFAULT_TIMEOUTS = {
482
+ tool: 3e4,
483
+ // 30s
484
+ agent: 3e5,
485
+ // 5m
486
+ subagent: 3e5,
487
+ // 5m
488
+ wait: 6e5,
489
+ // 10m
490
+ decision: 3e4,
491
+ // 30s
492
+ custom: 3e4
493
+ // 30s
494
+ };
495
+ function checkGuards(graph, config) {
496
+ const violations = [];
497
+ const now = Date.now();
498
+ const timeouts = { ...DEFAULT_TIMEOUTS, ...config?.timeouts };
499
+ const maxReasoningSteps = config?.maxReasoningSteps ?? 25;
500
+ const maxDepth = config?.maxDepth ?? 10;
501
+ const maxAgentSpawns = config?.maxAgentSpawns ?? 50;
502
+ for (const node of graph.nodes.values()) {
503
+ if (node.status === "running" && node.endTime === null) {
504
+ const timeoutThreshold = timeouts[node.type];
505
+ const elapsed = now - node.startTime;
506
+ if (elapsed > timeoutThreshold) {
507
+ violations.push({
508
+ type: "timeout",
509
+ nodeId: node.id,
510
+ message: `Node ${node.id} (${node.type}: ${node.name}) has been running for ${elapsed}ms, exceeding timeout of ${timeoutThreshold}ms`,
511
+ timestamp: now
512
+ });
513
+ }
514
+ }
515
+ }
516
+ const depth = getDepth(graph);
517
+ if (depth > maxDepth) {
518
+ violations.push({
519
+ type: "spawn-explosion",
520
+ nodeId: graph.rootNodeId,
521
+ message: `Graph depth ${depth} exceeds maximum depth of ${maxDepth}`,
522
+ timestamp: now
523
+ });
524
+ }
525
+ let agentCount = 0;
526
+ for (const node of graph.nodes.values()) {
527
+ if (node.type === "agent" || node.type === "subagent") {
528
+ agentCount++;
529
+ }
530
+ }
531
+ if (agentCount > maxAgentSpawns) {
532
+ violations.push({
533
+ type: "spawn-explosion",
534
+ nodeId: graph.rootNodeId,
535
+ message: `Total agent/subagent count ${agentCount} exceeds maximum of ${maxAgentSpawns}`,
536
+ timestamp: now
537
+ });
538
+ }
539
+ violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
540
+ return violations;
541
+ }
542
+ function detectReasoningLoops(graph, maxSteps, timestamp) {
543
+ const violations = [];
544
+ const reported = /* @__PURE__ */ new Set();
545
+ function walk(nodeId, consecutiveCount, consecutiveType) {
546
+ const node = getNode(graph, nodeId);
547
+ if (!node) return;
548
+ let newCount;
549
+ let newType;
550
+ if (node.type === consecutiveType) {
551
+ newCount = consecutiveCount + 1;
552
+ newType = node.type;
553
+ } else {
554
+ newCount = 1;
555
+ newType = node.type;
556
+ }
557
+ if (newCount > maxSteps && !reported.has(newType)) {
558
+ reported.add(newType);
559
+ violations.push({
560
+ type: "reasoning-loop",
561
+ nodeId: node.id,
562
+ message: `Detected ${newCount} consecutive ${newType} nodes along path to ${node.name}`,
563
+ timestamp
564
+ });
565
+ }
566
+ const children = getChildren(graph, nodeId);
567
+ for (const child of children) {
568
+ walk(child.id, newCount, newType);
569
+ }
570
+ }
571
+ walk(graph.rootNodeId, 0, null);
572
+ return violations;
573
+ }
574
+ function withGuards(builder, config) {
575
+ const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
576
+ const onViolation = config?.onViolation ?? "warn";
577
+ function handleViolations(violations) {
578
+ if (violations.length === 0) return;
579
+ for (const violation of violations) {
580
+ const message = `Guard violation: ${violation.message}`;
581
+ switch (onViolation) {
582
+ case "warn":
583
+ logger(message);
584
+ break;
585
+ case "error":
586
+ logger(message);
587
+ builder.pushEvent({
588
+ eventType: "custom",
589
+ nodeId: violation.nodeId,
590
+ data: {
591
+ guardViolation: violation.type,
592
+ message: violation.message,
593
+ severity: "error"
594
+ }
595
+ });
596
+ break;
597
+ case "abort":
598
+ throw new Error(`AgentFlow guard violation: ${violation.message}`);
599
+ default:
600
+ logger(message);
601
+ }
602
+ }
603
+ }
604
+ return {
605
+ get graphId() {
606
+ return builder.graphId;
607
+ },
608
+ get traceContext() {
609
+ return builder.traceContext;
610
+ },
611
+ startNode: (opts) => builder.startNode(opts),
612
+ endNode: (nodeId, status) => {
613
+ builder.endNode(nodeId, status);
614
+ const snapshot = builder.getSnapshot();
615
+ const violations = checkGuards(snapshot, config);
616
+ handleViolations(violations);
617
+ },
618
+ failNode: (nodeId, error) => {
619
+ builder.failNode(nodeId, error);
620
+ const snapshot = builder.getSnapshot();
621
+ const violations = checkGuards(snapshot, config);
622
+ handleViolations(violations);
623
+ },
624
+ addEdge: (from, to, type) => builder.addEdge(from, to, type),
625
+ pushEvent: (event) => builder.pushEvent(event),
626
+ updateState: (nodeId, state) => builder.updateState(nodeId, state),
627
+ withParent: (parentId, fn) => builder.withParent(parentId, fn),
628
+ getSnapshot: () => builder.getSnapshot(),
629
+ build: () => {
630
+ const snapshot = builder.getSnapshot();
631
+ const violations = checkGuards(snapshot, config);
632
+ handleViolations(violations);
633
+ return builder.build();
634
+ }
635
+ };
636
+ }
637
+
638
+ // src/live.ts
639
+ var import_node_fs = require("fs");
640
+ var import_node_path = require("path");
641
+ var import_node_child_process = require("child_process");
642
+
643
+ // src/loader.ts
644
+ function toNodesMap(raw) {
645
+ if (raw instanceof Map) return raw;
646
+ if (Array.isArray(raw)) {
647
+ return new Map(raw);
648
+ }
649
+ if (raw !== null && typeof raw === "object") {
650
+ return new Map(Object.entries(raw));
651
+ }
652
+ return /* @__PURE__ */ new Map();
653
+ }
654
+ function loadGraph(input) {
655
+ const raw = typeof input === "string" ? JSON.parse(input) : input;
656
+ const nodes = toNodesMap(raw.nodes);
657
+ return {
658
+ id: raw.id ?? "",
659
+ rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
660
+ nodes,
661
+ edges: raw.edges ?? [],
662
+ startTime: raw.startTime ?? 0,
663
+ endTime: raw.endTime ?? null,
664
+ status: raw.status ?? "completed",
665
+ trigger: raw.trigger ?? "unknown",
666
+ agentId: raw.agentId ?? "unknown",
667
+ events: raw.events ?? [],
668
+ traceId: raw.traceId,
669
+ spanId: raw.spanId,
670
+ parentSpanId: raw.parentSpanId
671
+ };
672
+ }
673
+ function graphToJson(graph) {
674
+ const nodesObj = {};
675
+ for (const [id, node] of graph.nodes) {
676
+ nodesObj[id] = node;
677
+ }
678
+ return {
679
+ id: graph.id,
680
+ rootNodeId: graph.rootNodeId,
681
+ nodes: nodesObj,
682
+ edges: graph.edges,
683
+ startTime: graph.startTime,
684
+ endTime: graph.endTime,
685
+ status: graph.status,
686
+ trigger: graph.trigger,
687
+ agentId: graph.agentId,
688
+ events: graph.events,
689
+ traceId: graph.traceId,
690
+ spanId: graph.spanId,
691
+ parentSpanId: graph.parentSpanId
692
+ };
680
693
  }
681
694
 
682
695
  // src/live.ts
@@ -712,17 +725,18 @@ function parseArgs(argv) {
712
725
  config.recursive = true;
713
726
  i++;
714
727
  } else if (!arg.startsWith("-")) {
715
- config.dirs.push((0, import_node_path2.resolve)(arg));
728
+ config.dirs.push((0, import_node_path.resolve)(arg));
716
729
  i++;
717
730
  } else {
718
731
  i++;
719
732
  }
720
733
  }
721
- if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
734
+ if (config.dirs.length === 0) config.dirs.push((0, import_node_path.resolve)("."));
722
735
  return config;
723
736
  }
724
737
  function printUsage() {
725
- console.log(`
738
+ console.log(
739
+ `
726
740
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
727
741
 
728
742
  Auto-detects agent traces, state files, job schedulers, and session logs
@@ -743,20 +757,21 @@ Examples:
743
757
  agentflow live ./data
744
758
  agentflow live ./traces ./cron ./workers --refresh 5
745
759
  agentflow live /var/lib/myagent -R
746
- `.trim());
760
+ `.trim()
761
+ );
747
762
  }
748
763
  function scanFiles(dirs, recursive) {
749
764
  const results = [];
750
765
  const seen = /* @__PURE__ */ new Set();
751
766
  function scanDir(d, topLevel) {
752
767
  try {
753
- for (const f of (0, import_node_fs2.readdirSync)(d)) {
768
+ for (const f of (0, import_node_fs.readdirSync)(d)) {
754
769
  if (f.startsWith(".")) continue;
755
- const fp = (0, import_node_path2.join)(d, f);
770
+ const fp = (0, import_node_path.join)(d, f);
756
771
  if (seen.has(fp)) continue;
757
772
  let stat;
758
773
  try {
759
- stat = (0, import_node_fs2.statSync)(fp);
774
+ stat = (0, import_node_fs.statSync)(fp);
760
775
  } catch {
761
776
  continue;
762
777
  }
@@ -782,20 +797,22 @@ function scanFiles(dirs, recursive) {
782
797
  }
783
798
  function safeReadJson(fp) {
784
799
  try {
785
- return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
800
+ return JSON.parse((0, import_node_fs.readFileSync)(fp, "utf8"));
786
801
  } catch {
787
802
  return null;
788
803
  }
789
804
  }
790
805
  function nameFromFile(filename) {
791
- return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
806
+ return (0, import_node_path.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
792
807
  }
793
808
  function normalizeStatus(val) {
794
809
  if (typeof val !== "string") return "unknown";
795
810
  const s = val.toLowerCase();
796
811
  if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
797
- if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s)) return "error";
798
- if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s)) return "running";
812
+ if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s))
813
+ return "error";
814
+ if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s))
815
+ return "running";
799
816
  return "unknown";
800
817
  }
801
818
  function findStatus(obj) {
@@ -811,7 +828,17 @@ function findStatus(obj) {
811
828
  return "unknown";
812
829
  }
813
830
  function findTimestamp(obj) {
814
- for (const key of ["ts", "timestamp", "lastRunAtMs", "last_run", "lastExecution", "updated_at", "started_at", "endTime", "startTime"]) {
831
+ for (const key of [
832
+ "ts",
833
+ "timestamp",
834
+ "lastRunAtMs",
835
+ "last_run",
836
+ "lastExecution",
837
+ "updated_at",
838
+ "started_at",
839
+ "endTime",
840
+ "startTime"
841
+ ]) {
815
842
  const val = obj[key];
816
843
  if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
817
844
  if (typeof val === "string") {
@@ -823,7 +850,16 @@ function findTimestamp(obj) {
823
850
  }
824
851
  function extractDetail(obj) {
825
852
  const parts = [];
826
- for (const key of ["summary", "message", "description", "lastError", "error", "name", "jobId", "id"]) {
853
+ for (const key of [
854
+ "summary",
855
+ "message",
856
+ "description",
857
+ "lastError",
858
+ "error",
859
+ "name",
860
+ "jobId",
861
+ "id"
862
+ ]) {
827
863
  const val = obj[key];
828
864
  if (typeof val === "string" && val.length > 0 && val.length < 200) {
829
865
  parts.push(val.slice(0, 80));
@@ -883,7 +919,14 @@ function processJsonFile(file) {
883
919
  const status2 = findStatus(state);
884
920
  const ts2 = findTimestamp(state) || file.mtime;
885
921
  const detail2 = extractDetail(state);
886
- records.push({ id: String(name), source: "jobs", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
922
+ records.push({
923
+ id: String(name),
924
+ source: "jobs",
925
+ status: status2,
926
+ lastActive: ts2,
927
+ detail: detail2,
928
+ file: file.filename
929
+ });
887
930
  }
888
931
  return records;
889
932
  }
@@ -897,8 +940,26 @@ function processJsonFile(file) {
897
940
  const status2 = findStatus(w);
898
941
  const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
899
942
  const pid = w.pid;
900
- const detail2 = pid ? `pid: ${pid}` : extractDetail(w);
901
- records.push({ id: name, source: "workers", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
943
+ let validatedStatus = status2;
944
+ let pidAlive = true;
945
+ if (pid && (status2 === "running" || status2 === "ok")) {
946
+ try {
947
+ (0, import_node_child_process.execSync)(`kill -0 ${pid} 2>/dev/null`, { stdio: "ignore" });
948
+ } catch {
949
+ pidAlive = false;
950
+ validatedStatus = "error";
951
+ }
952
+ }
953
+ const pidLabel = pid ? pidAlive ? `pid: ${pid}` : `pid: ${pid} (dead)` : "";
954
+ const detail2 = pidLabel || extractDetail(w);
955
+ records.push({
956
+ id: name,
957
+ source: "workers",
958
+ status: validatedStatus,
959
+ lastActive: ts2,
960
+ detail: detail2,
961
+ file: file.filename
962
+ });
902
963
  }
903
964
  return records;
904
965
  }
@@ -906,12 +967,19 @@ function processJsonFile(file) {
906
967
  const status = findStatus(obj);
907
968
  const ts = findTimestamp(obj) || file.mtime;
908
969
  const detail = extractDetail(obj);
909
- records.push({ id: nameFromFile(file.filename), source: "state", status, lastActive: ts, detail, file: file.filename });
970
+ records.push({
971
+ id: nameFromFile(file.filename),
972
+ source: "state",
973
+ status,
974
+ lastActive: ts,
975
+ detail,
976
+ file: file.filename
977
+ });
910
978
  return records;
911
979
  }
912
980
  function processJsonlFile(file) {
913
981
  try {
914
- const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
982
+ const content = (0, import_node_fs.readFileSync)(file.path, "utf8").trim();
915
983
  if (!content) return [];
916
984
  const lines = content.split("\n");
917
985
  const lineCount = lines.length;
@@ -922,13 +990,22 @@ function processJsonlFile(file) {
922
990
  const ts2 = findTimestamp(lastObj) || file.mtime;
923
991
  const action = lastObj.action;
924
992
  const detail2 = action ? `${action} (${lineCount} entries)` : `${lineCount} entries`;
925
- return [{ id: String(name), source: "session", status: status2, lastActive: ts2, detail: detail2, file: file.filename }];
993
+ return [
994
+ {
995
+ id: String(name),
996
+ source: "session",
997
+ status: status2,
998
+ lastActive: ts2,
999
+ detail: detail2,
1000
+ file: file.filename
1001
+ }
1002
+ ];
926
1003
  }
927
1004
  const tail = lines.slice(Math.max(0, lineCount - 30));
928
1005
  let model = "";
929
1006
  let totalTokens = 0;
930
1007
  let totalCost = 0;
931
- let toolCalls = [];
1008
+ const toolCalls = [];
932
1009
  let lastUserMsg = "";
933
1010
  let lastAssistantMsg = "";
934
1011
  let errorCount = 0;
@@ -1027,7 +1104,16 @@ function processJsonlFile(file) {
1027
1104
  const status = errorCount > lineCount / 4 ? "error" : lastRole === "assistant" ? "ok" : "running";
1028
1105
  const ts = findTimestamp(lastObj) || file.mtime;
1029
1106
  const sessionName = sessionId ? sessionId.slice(0, 8) : nameFromFile(file.filename);
1030
- return [{ id: String(name !== "unknown" ? name : sessionName), source: "session", status, lastActive: ts, detail, file: file.filename }];
1107
+ return [
1108
+ {
1109
+ id: String(name !== "unknown" ? name : sessionName),
1110
+ source: "session",
1111
+ status,
1112
+ lastActive: ts,
1113
+ detail,
1114
+ file: file.filename
1115
+ }
1116
+ ];
1031
1117
  } catch {
1032
1118
  return [];
1033
1119
  }
@@ -1060,8 +1146,30 @@ function render(config) {
1060
1146
  if (r.traceData) allTraces.push(r.traceData);
1061
1147
  }
1062
1148
  }
1063
- const byFile = /* @__PURE__ */ new Map();
1149
+ const deduped = [];
1150
+ const seenAgents = /* @__PURE__ */ new Map();
1064
1151
  for (const r of allRecords) {
1152
+ if (r.source === "jobs" || r.source === "workers" || r.source === "state") {
1153
+ deduped.push(r);
1154
+ continue;
1155
+ }
1156
+ const key = `${r.source}:${r.id}`;
1157
+ const existing = seenAgents.get(key);
1158
+ if (!existing || r.lastActive > existing.lastActive) {
1159
+ seenAgents.set(key, r);
1160
+ }
1161
+ }
1162
+ for (const r of seenAgents.values()) deduped.push(r);
1163
+ const STALE_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
1164
+ const now = Date.now();
1165
+ const filtered = deduped.filter((r) => {
1166
+ if (r.source === "jobs" || r.source === "workers") return true;
1167
+ if (r.status === "running") return true;
1168
+ return now - r.lastActive < STALE_THRESHOLD_MS;
1169
+ });
1170
+ const activeRecords = filtered;
1171
+ const byFile = /* @__PURE__ */ new Map();
1172
+ for (const r of activeRecords) {
1065
1173
  const arr = byFile.get(r.file) ?? [];
1066
1174
  arr.push(r);
1067
1175
  byFile.set(r.file, arr);
@@ -1108,15 +1216,14 @@ function render(config) {
1108
1216
  }
1109
1217
  }
1110
1218
  groups.sort((a, b) => b.lastTs - a.lastTs);
1111
- const totExec = allRecords.length;
1112
- const totFail = allRecords.filter((r) => r.status === "error").length;
1113
- const totRunning = allRecords.filter((r) => r.status === "running").length;
1114
- const uniqueAgents = new Set(allRecords.map((r) => r.id)).size;
1219
+ const totExec = activeRecords.length;
1220
+ const totFail = activeRecords.filter((r) => r.status === "error").length;
1221
+ const totRunning = activeRecords.filter((r) => r.status === "running").length;
1222
+ const uniqueAgents = new Set(activeRecords.map((r) => r.id)).size;
1115
1223
  const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
1116
- const now = Date.now();
1117
1224
  const buckets = new Array(12).fill(0);
1118
1225
  const failBuckets = new Array(12).fill(0);
1119
- for (const r of allRecords) {
1226
+ for (const r of activeRecords) {
1120
1227
  const age = now - r.lastActive;
1121
1228
  if (age > 36e5 || age < 0) continue;
1122
1229
  const idx = 11 - Math.floor(age / 3e5);
@@ -1159,7 +1266,8 @@ function render(config) {
1159
1266
  if (g.fail > 0 && g.ok === 0 && g.running === 0) return `${C.red}error${C.reset}`;
1160
1267
  if (g.running > 0) return `${C.green}running${C.reset}`;
1161
1268
  if (g.fail > 0) return `${C.yellow}${g.ok}ok/${g.fail}err${C.reset}`;
1162
- if (g.ok > 0) return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1269
+ if (g.ok > 0)
1270
+ return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1163
1271
  return `${C.dim}idle${C.reset}`;
1164
1272
  }
1165
1273
  function sourceTag(s) {
@@ -1191,18 +1299,30 @@ function render(config) {
1191
1299
  }
1192
1300
  const L = [];
1193
1301
  writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1194
- writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`);
1302
+ writeLine(
1303
+ L,
1304
+ `${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`
1305
+ );
1195
1306
  const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
1196
1307
  const pad1 = Math.max(0, 64 - metaLine.length);
1197
- writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
1308
+ writeLine(
1309
+ L,
1310
+ `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`
1311
+ );
1198
1312
  writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1199
1313
  const sc = totFail === 0 ? C.green : C.yellow;
1200
1314
  writeLine(L, "");
1201
- writeLine(L, ` ${C.bold}Agents${C.reset} ${sc}${uniqueAgents}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`);
1315
+ writeLine(
1316
+ L,
1317
+ ` ${C.bold}Agents${C.reset} ${sc}${uniqueAgents}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`
1318
+ );
1202
1319
  writeLine(L, "");
1203
1320
  writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1204
1321
  writeLine(L, "");
1205
- writeLine(L, ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`);
1322
+ writeLine(
1323
+ L,
1324
+ ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`
1325
+ );
1206
1326
  let lineCount = 0;
1207
1327
  for (const g of groups) {
1208
1328
  if (lineCount > 35) break;
@@ -1213,13 +1333,19 @@ function render(config) {
1213
1333
  const name = truncate(g.name, 26).padEnd(26);
1214
1334
  const st = statusText(g);
1215
1335
  const det = truncate(g.detail, detailWidth);
1216
- writeLine(L, ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`);
1336
+ writeLine(
1337
+ L,
1338
+ ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`
1339
+ );
1217
1340
  lineCount++;
1218
1341
  } else {
1219
1342
  const name = truncate(g.name, 24).padEnd(24);
1220
1343
  const st = statusText(g);
1221
1344
  const tag = sourceTag(g.source);
1222
- writeLine(L, ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`);
1345
+ writeLine(
1346
+ L,
1347
+ ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`
1348
+ );
1223
1349
  lineCount++;
1224
1350
  const kids = g.children.slice(0, 12);
1225
1351
  for (let i = 0; i < kids.length; i++) {
@@ -1231,7 +1357,10 @@ function render(config) {
1231
1357
  const cName = truncate(child.id, 22).padEnd(22);
1232
1358
  const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
1233
1359
  const cDet = truncate(child.detail, detailWidth - 5);
1234
- writeLine(L, ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`);
1360
+ writeLine(
1361
+ L,
1362
+ ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`
1363
+ );
1235
1364
  lineCount++;
1236
1365
  }
1237
1366
  if (g.children.length > 12) {
@@ -1248,7 +1377,10 @@ function render(config) {
1248
1377
  const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1249
1378
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1250
1379
  const tid = dt.traceId.slice(0, 8);
1251
- writeLine(L, ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1380
+ writeLine(
1381
+ L,
1382
+ ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`
1383
+ );
1252
1384
  const tree = getTraceTree(dt);
1253
1385
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1254
1386
  const tg = tree[i];
@@ -1258,11 +1390,14 @@ function render(config) {
1258
1390
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1259
1391
  const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1260
1392
  const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
1261
- writeLine(L, `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`);
1393
+ writeLine(
1394
+ L,
1395
+ `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`
1396
+ );
1262
1397
  }
1263
1398
  }
1264
1399
  }
1265
- const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 6);
1400
+ const recentRecords = activeRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 6);
1266
1401
  if (recentRecords.length > 0) {
1267
1402
  writeLine(L, "");
1268
1403
  writeLine(L, ` ${C.bold}${C.under}Recent Activity${C.reset}`);
@@ -1273,7 +1408,10 @@ function render(config) {
1273
1408
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1274
1409
  const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1275
1410
  const det = truncate(r.detail, detailWidth);
1276
- writeLine(L, ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`);
1411
+ writeLine(
1412
+ L,
1413
+ ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`
1414
+ );
1277
1415
  }
1278
1416
  }
1279
1417
  if (files.length === 0) {
@@ -1295,39 +1433,540 @@ function getDistDepth(dt, spanId) {
1295
1433
  }
1296
1434
  function startLive(argv) {
1297
1435
  const config = parseArgs(argv);
1298
- const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
1436
+ const valid = config.dirs.filter((d) => (0, import_node_fs.existsSync)(d));
1299
1437
  if (valid.length === 0) {
1300
1438
  console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1301
1439
  console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
1302
1440
  process.exit(1);
1303
1441
  }
1304
- const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
1305
- if (invalid.length > 0) {
1306
- console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1442
+ const invalid = config.dirs.filter((d) => !(0, import_node_fs.existsSync)(d));
1443
+ if (invalid.length > 0) {
1444
+ console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1445
+ }
1446
+ config.dirs = valid;
1447
+ render(config);
1448
+ let debounce = null;
1449
+ for (const dir of config.dirs) {
1450
+ try {
1451
+ (0, import_node_fs.watch)(dir, { recursive: config.recursive }, () => {
1452
+ if (debounce) clearTimeout(debounce);
1453
+ debounce = setTimeout(() => render(config), 500);
1454
+ });
1455
+ } catch {
1456
+ }
1457
+ }
1458
+ setInterval(() => render(config), config.refreshMs);
1459
+ process.on("SIGINT", () => {
1460
+ console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1461
+ process.exit(0);
1462
+ });
1463
+ }
1464
+
1465
+ // src/runner.ts
1466
+ var import_node_child_process2 = require("child_process");
1467
+ var import_node_fs2 = require("fs");
1468
+ var import_node_path2 = require("path");
1469
+ function globToRegex(pattern) {
1470
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1471
+ return new RegExp(`^${escaped}$`);
1472
+ }
1473
+ function snapshotDir(dir, patterns) {
1474
+ const result = /* @__PURE__ */ new Map();
1475
+ if (!(0, import_node_fs2.existsSync)(dir)) return result;
1476
+ for (const entry of (0, import_node_fs2.readdirSync)(dir)) {
1477
+ if (!patterns.some((re) => re.test(entry))) continue;
1478
+ const full = (0, import_node_path2.join)(dir, entry);
1479
+ try {
1480
+ const stat = (0, import_node_fs2.statSync)(full);
1481
+ if (stat.isFile()) {
1482
+ result.set(full, stat.mtimeMs);
1483
+ }
1484
+ } catch {
1485
+ }
1486
+ }
1487
+ return result;
1488
+ }
1489
+ function agentIdFromFilename(filePath) {
1490
+ const base = (0, import_node_path2.basename)(filePath, ".json");
1491
+ const cleaned = base.replace(/-state$/, "");
1492
+ return `alfred-${cleaned}`;
1493
+ }
1494
+ function deriveAgentId(command) {
1495
+ return "orchestrator";
1496
+ }
1497
+ function fileTimestamp() {
1498
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
1499
+ }
1500
+ async function runTraced(config) {
1501
+ const {
1502
+ command,
1503
+ agentId = deriveAgentId(command),
1504
+ trigger = "cli",
1505
+ tracesDir = "./traces",
1506
+ watchDirs = [],
1507
+ watchPatterns = ["*.json"]
1508
+ } = config;
1509
+ if (command.length === 0) {
1510
+ throw new Error("runTraced: command must not be empty");
1511
+ }
1512
+ const resolvedTracesDir = (0, import_node_path2.resolve)(tracesDir);
1513
+ const patterns = watchPatterns.map(globToRegex);
1514
+ const orchestrator = createGraphBuilder({ agentId, trigger });
1515
+ const { traceId, spanId } = orchestrator.traceContext;
1516
+ const beforeSnapshots = /* @__PURE__ */ new Map();
1517
+ for (const dir of watchDirs) {
1518
+ beforeSnapshots.set(dir, snapshotDir(dir, patterns));
1519
+ }
1520
+ const rootId = orchestrator.startNode({ type: "agent", name: agentId });
1521
+ const dispatchId = orchestrator.startNode({
1522
+ type: "tool",
1523
+ name: "dispatch-command",
1524
+ parentId: rootId
1525
+ });
1526
+ orchestrator.updateState(dispatchId, { command: command.join(" ") });
1527
+ const monitorId = orchestrator.startNode({
1528
+ type: "tool",
1529
+ name: "state-monitor",
1530
+ parentId: rootId
1531
+ });
1532
+ orchestrator.updateState(monitorId, {
1533
+ watchDirs,
1534
+ watchPatterns
1535
+ });
1536
+ const startMs = Date.now();
1537
+ const execCmd = command[0] ?? "";
1538
+ const execArgs = command.slice(1);
1539
+ process.env.AGENTFLOW_TRACE_ID = traceId;
1540
+ process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
1541
+ const result = (0, import_node_child_process2.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
1542
+ delete process.env.AGENTFLOW_TRACE_ID;
1543
+ delete process.env.AGENTFLOW_PARENT_SPAN_ID;
1544
+ const exitCode = result.status ?? 1;
1545
+ const duration = (Date.now() - startMs) / 1e3;
1546
+ const stateChanges = [];
1547
+ for (const dir of watchDirs) {
1548
+ const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
1549
+ const after = snapshotDir(dir, patterns);
1550
+ for (const [filePath, mtime] of after) {
1551
+ const prevMtime = before.get(filePath);
1552
+ if (prevMtime === void 0 || mtime > prevMtime) {
1553
+ stateChanges.push(filePath);
1554
+ }
1555
+ }
1556
+ }
1557
+ orchestrator.updateState(monitorId, { stateChanges });
1558
+ orchestrator.endNode(monitorId);
1559
+ if (exitCode === 0) {
1560
+ orchestrator.endNode(dispatchId);
1561
+ } else {
1562
+ orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
1563
+ }
1564
+ orchestrator.updateState(rootId, {
1565
+ exitCode,
1566
+ duration,
1567
+ stateChanges
1568
+ });
1569
+ if (exitCode === 0) {
1570
+ orchestrator.endNode(rootId);
1571
+ } else {
1572
+ orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
1573
+ }
1574
+ const orchestratorGraph = orchestrator.build();
1575
+ const allGraphs = [orchestratorGraph];
1576
+ for (const filePath of stateChanges) {
1577
+ const childAgentId = agentIdFromFilename(filePath);
1578
+ const childBuilder = createGraphBuilder({
1579
+ agentId: childAgentId,
1580
+ trigger: "state-change",
1581
+ traceId,
1582
+ parentSpanId: spanId
1583
+ });
1584
+ const childRootId = childBuilder.startNode({
1585
+ type: "agent",
1586
+ name: childAgentId
1587
+ });
1588
+ childBuilder.updateState(childRootId, {
1589
+ stateFile: filePath,
1590
+ detectedBy: "runner-state-monitor"
1591
+ });
1592
+ childBuilder.endNode(childRootId);
1593
+ allGraphs.push(childBuilder.build());
1594
+ }
1595
+ if (!(0, import_node_fs2.existsSync)(resolvedTracesDir)) {
1596
+ (0, import_node_fs2.mkdirSync)(resolvedTracesDir, { recursive: true });
1597
+ }
1598
+ const ts = fileTimestamp();
1599
+ const tracePaths = [];
1600
+ for (const graph of allGraphs) {
1601
+ const filename = `${graph.agentId}-${ts}.json`;
1602
+ const outPath = (0, import_node_path2.join)(resolvedTracesDir, filename);
1603
+ (0, import_node_fs2.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
1604
+ tracePaths.push(outPath);
1605
+ }
1606
+ if (tracePaths.length > 0) {
1607
+ console.log(`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`);
1608
+ }
1609
+ return {
1610
+ exitCode,
1611
+ traceId,
1612
+ spanId,
1613
+ tracePaths,
1614
+ stateChanges,
1615
+ duration
1616
+ };
1617
+ }
1618
+
1619
+ // src/trace-store.ts
1620
+ var import_promises = require("fs/promises");
1621
+ var import_path = require("path");
1622
+ function createTraceStore(dir) {
1623
+ async function ensureDir() {
1624
+ await (0, import_promises.mkdir)(dir, { recursive: true });
1625
+ }
1626
+ async function loadAll() {
1627
+ await ensureDir();
1628
+ let files;
1629
+ try {
1630
+ files = await (0, import_promises.readdir)(dir);
1631
+ } catch {
1632
+ return [];
1633
+ }
1634
+ const graphs = [];
1635
+ for (const file of files) {
1636
+ if (!file.endsWith(".json")) continue;
1637
+ try {
1638
+ const content = await (0, import_promises.readFile)((0, import_path.join)(dir, file), "utf-8");
1639
+ const graph = loadGraph(content);
1640
+ graphs.push(graph);
1641
+ } catch {
1642
+ }
1643
+ }
1644
+ return graphs;
1645
+ }
1646
+ return {
1647
+ async save(graph) {
1648
+ await ensureDir();
1649
+ const json = graphToJson(graph);
1650
+ const filePath = (0, import_path.join)(dir, `${graph.id}.json`);
1651
+ await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
1652
+ return filePath;
1653
+ },
1654
+ async get(graphId) {
1655
+ await ensureDir();
1656
+ const filePath = (0, import_path.join)(dir, `${graphId}.json`);
1657
+ try {
1658
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
1659
+ return loadGraph(content);
1660
+ } catch {
1661
+ }
1662
+ const all = await loadAll();
1663
+ return all.find((g) => g.id === graphId) ?? null;
1664
+ },
1665
+ async list(opts) {
1666
+ let graphs = await loadAll();
1667
+ if (opts?.status) {
1668
+ graphs = graphs.filter((g) => g.status === opts.status);
1669
+ }
1670
+ graphs.sort((a, b) => b.startTime - a.startTime);
1671
+ if (opts?.limit && opts.limit > 0) {
1672
+ graphs = graphs.slice(0, opts.limit);
1673
+ }
1674
+ return graphs;
1675
+ },
1676
+ async getStuckSpans() {
1677
+ const graphs = await loadAll();
1678
+ const stuck = [];
1679
+ for (const graph of graphs) {
1680
+ for (const node of graph.nodes.values()) {
1681
+ if (node.status === "running" || node.status === "hung" || node.status === "timeout") {
1682
+ stuck.push(node);
1683
+ }
1684
+ }
1685
+ }
1686
+ return stuck;
1687
+ },
1688
+ async getReasoningLoops(threshold = 25) {
1689
+ const graphs = await loadAll();
1690
+ const results = [];
1691
+ for (const graph of graphs) {
1692
+ const loops = findLoopsInGraph(graph, threshold);
1693
+ if (loops.length > 0) {
1694
+ results.push({ graphId: graph.id, nodes: loops });
1695
+ }
1696
+ }
1697
+ return results;
1698
+ }
1699
+ };
1700
+ }
1701
+ function findLoopsInGraph(graph, threshold) {
1702
+ const loopNodes = [];
1703
+ function walk(nodeId, consecutiveCount, consecutiveType) {
1704
+ const node = graph.nodes.get(nodeId);
1705
+ if (!node) return;
1706
+ const newCount = node.type === consecutiveType ? consecutiveCount + 1 : 1;
1707
+ if (newCount > threshold) {
1708
+ loopNodes.push(node);
1709
+ }
1710
+ for (const childId of node.children) {
1711
+ walk(childId, newCount, node.type);
1712
+ }
1713
+ }
1714
+ walk(graph.rootNodeId, 0, null);
1715
+ return loopNodes;
1716
+ }
1717
+
1718
+ // src/visualize.ts
1719
+ var STATUS_ICONS = {
1720
+ completed: "\u2713",
1721
+ failed: "\u2717",
1722
+ running: "\u231B",
1723
+ hung: "\u231B",
1724
+ timeout: "\u231B"
1725
+ };
1726
+ function formatDuration(ms) {
1727
+ if (ms < 1e3) return `${ms}ms`;
1728
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
1729
+ if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
1730
+ return `${(ms / 36e5).toFixed(1)}h`;
1731
+ }
1732
+ function nodeDuration(node, graphEndTime) {
1733
+ const end = node.endTime ?? graphEndTime;
1734
+ return formatDuration(end - node.startTime);
1735
+ }
1736
+ function getGenAiInfo(node) {
1737
+ const parts = [];
1738
+ const meta = node.metadata;
1739
+ if (meta["gen_ai.request.model"]) {
1740
+ parts.push(String(meta["gen_ai.request.model"]));
1741
+ }
1742
+ const tokens = meta["gen_ai.usage.prompt_tokens"] ?? meta["gen_ai.usage.completion_tokens"];
1743
+ if (tokens !== void 0) {
1744
+ const prompt = meta["gen_ai.usage.prompt_tokens"] ?? 0;
1745
+ const completion = meta["gen_ai.usage.completion_tokens"] ?? 0;
1746
+ if (prompt || completion) {
1747
+ parts.push(`${prompt + completion} tok`);
1748
+ }
1749
+ }
1750
+ return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
1751
+ }
1752
+ function hasViolation(node, graph) {
1753
+ return graph.events.some(
1754
+ (e) => e.nodeId === node.id && e.eventType === "custom" && e.data.guardViolation !== void 0
1755
+ );
1756
+ }
1757
+ function toAsciiTree(graph) {
1758
+ if (graph.nodes.size === 0) return "(empty graph)";
1759
+ const now = Date.now();
1760
+ const endTime = graph.endTime ?? now;
1761
+ const lines = [];
1762
+ function renderNode(nodeId, prefix, isLast, isRoot) {
1763
+ const node = graph.nodes.get(nodeId);
1764
+ if (!node) return;
1765
+ const icon = STATUS_ICONS[node.status];
1766
+ const duration = nodeDuration(node, endTime);
1767
+ const genAi = getGenAiInfo(node);
1768
+ const violation = hasViolation(node, graph) ? " \u26A0" : "";
1769
+ const errorInfo = node.status === "failed" && node.metadata.error ? ` \u2014 ${node.metadata.error}` : "";
1770
+ const timeoutInfo = node.status === "timeout" ? " [TIMEOUT]" : "";
1771
+ const hungInfo = node.status === "hung" ? " [HUNG]" : "";
1772
+ const connector = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1773
+ const line = `${prefix}${connector}${icon} ${node.name} (${node.type}) ${duration}${genAi}${violation}${timeoutInfo}${hungInfo}${errorInfo}`;
1774
+ lines.push(line);
1775
+ const children = getChildren(graph, nodeId);
1776
+ const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
1777
+ for (let i = 0; i < children.length; i++) {
1778
+ renderNode(children[i].id, childPrefix, i === children.length - 1, false);
1779
+ }
1780
+ }
1781
+ renderNode(graph.rootNodeId, "", true, true);
1782
+ return lines.join("\n");
1783
+ }
1784
+ function toTimeline(graph) {
1785
+ if (graph.nodes.size === 0) return "(empty graph)";
1786
+ const now = Date.now();
1787
+ const graphStart = graph.startTime;
1788
+ const graphEnd = graph.endTime ?? now;
1789
+ const totalDuration = graphEnd - graphStart;
1790
+ if (totalDuration <= 0) return "(zero duration)";
1791
+ const barWidth = 60;
1792
+ const lines = [];
1793
+ const scaleLabels = [];
1794
+ const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
1795
+ for (let i = 0; i <= tickCount; i++) {
1796
+ const t = totalDuration * i / tickCount;
1797
+ scaleLabels.push(formatDuration(t));
1798
+ }
1799
+ let header = "";
1800
+ for (let i = 0; i < scaleLabels.length; i++) {
1801
+ const pos = Math.round(barWidth * i / tickCount);
1802
+ while (header.length < pos) header += " ";
1803
+ header += scaleLabels[i];
1804
+ }
1805
+ lines.push(header);
1806
+ let tickLine = "";
1807
+ for (let i = 0; i < barWidth; i++) {
1808
+ const tickPos = tickCount > 0 ? i * tickCount / barWidth : 0;
1809
+ if (Number.isInteger(Math.round(tickPos * 100) / 100) && Math.abs(tickPos - Math.round(tickPos)) < 0.01) {
1810
+ tickLine += "\u253C";
1811
+ } else {
1812
+ tickLine += "\u2500";
1813
+ }
1814
+ }
1815
+ lines.push(tickLine);
1816
+ const orderedNodes = [];
1817
+ function collectNodes(nodeId) {
1818
+ const node = graph.nodes.get(nodeId);
1819
+ if (!node) return;
1820
+ orderedNodes.push(node);
1821
+ const children = getChildren(graph, nodeId);
1822
+ for (const child of children) {
1823
+ collectNodes(child.id);
1824
+ }
1307
1825
  }
1308
- config.dirs = valid;
1309
- render(config);
1310
- let debounce = null;
1311
- for (const dir of config.dirs) {
1312
- try {
1313
- (0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
1314
- if (debounce) clearTimeout(debounce);
1315
- debounce = setTimeout(() => render(config), 500);
1316
- });
1317
- } catch {
1826
+ collectNodes(graph.rootNodeId);
1827
+ for (const node of orderedNodes) {
1828
+ const nodeStart = node.startTime - graphStart;
1829
+ const nodeEnd = (node.endTime ?? now) - graphStart;
1830
+ const startCol = Math.round(nodeStart / totalDuration * barWidth);
1831
+ const endCol = Math.max(startCol + 1, Math.round(nodeEnd / totalDuration * barWidth));
1832
+ let bar = "";
1833
+ for (let i = 0; i < barWidth; i++) {
1834
+ if (i >= startCol && i < endCol) {
1835
+ bar += "\u2588";
1836
+ } else {
1837
+ bar += " ";
1838
+ }
1318
1839
  }
1840
+ const icon = STATUS_ICONS[node.status];
1841
+ const duration = nodeDuration(node, graphEnd);
1842
+ const violation = hasViolation(node, graph) ? " \u26A0" : "";
1843
+ lines.push(`${bar} ${icon} ${node.name} (${duration})${violation}`);
1319
1844
  }
1320
- setInterval(() => render(config), config.refreshMs);
1321
- process.on("SIGINT", () => {
1322
- console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1323
- process.exit(0);
1324
- });
1845
+ return lines.join("\n");
1325
1846
  }
1326
1847
 
1327
1848
  // src/watch.ts
1328
1849
  var import_node_fs4 = require("fs");
1329
- var import_node_path3 = require("path");
1330
1850
  var import_node_os = require("os");
1851
+ var import_node_path3 = require("path");
1852
+
1853
+ // src/watch-alerts.ts
1854
+ var import_node_child_process3 = require("child_process");
1855
+ var import_node_http = require("http");
1856
+ var import_node_https = require("https");
1857
+ function formatAlertMessage(payload) {
1858
+ const time = new Date(payload.timestamp).toISOString();
1859
+ const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1860
+ return [
1861
+ `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1862
+ ` Status: ${arrow}`,
1863
+ payload.detail ? ` Detail: ${payload.detail}` : null,
1864
+ ` File: ${payload.file}`,
1865
+ ` Time: ${time}`
1866
+ ].filter(Boolean).join("\n");
1867
+ }
1868
+ function formatTelegram(payload) {
1869
+ const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1870
+ const time = new Date(payload.timestamp).toLocaleTimeString();
1871
+ return [
1872
+ `${icon} *AgentFlow Alert*`,
1873
+ `*${payload.condition}*: \`${payload.agentId}\``,
1874
+ `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1875
+ payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1876
+ `Time: ${time}`
1877
+ ].filter(Boolean).join("\n");
1878
+ }
1879
+ async function sendAlert(payload, channel) {
1880
+ try {
1881
+ switch (channel.type) {
1882
+ case "stdout":
1883
+ sendStdout(payload);
1884
+ break;
1885
+ case "telegram":
1886
+ await sendTelegram(payload, channel.botToken, channel.chatId);
1887
+ break;
1888
+ case "webhook":
1889
+ await sendWebhook(payload, channel.url);
1890
+ break;
1891
+ case "command":
1892
+ await sendCommand(payload, channel.cmd);
1893
+ break;
1894
+ }
1895
+ } catch (err) {
1896
+ const msg = err instanceof Error ? err.message : String(err);
1897
+ console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1898
+ }
1899
+ }
1900
+ function sendStdout(payload) {
1901
+ console.log(formatAlertMessage(payload));
1902
+ }
1903
+ function sendTelegram(payload, botToken, chatId) {
1904
+ const body = JSON.stringify({
1905
+ chat_id: chatId,
1906
+ text: formatTelegram(payload),
1907
+ parse_mode: "Markdown"
1908
+ });
1909
+ return new Promise((resolve4, reject) => {
1910
+ const req = (0, import_node_https.request)(
1911
+ `https://api.telegram.org/bot${botToken}/sendMessage`,
1912
+ {
1913
+ method: "POST",
1914
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
1915
+ },
1916
+ (res) => {
1917
+ res.resume();
1918
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1919
+ else reject(new Error(`Telegram API returned ${res.statusCode}`));
1920
+ }
1921
+ );
1922
+ req.on("error", reject);
1923
+ req.write(body);
1924
+ req.end();
1925
+ });
1926
+ }
1927
+ function sendWebhook(payload, url) {
1928
+ const body = JSON.stringify(payload);
1929
+ const isHttps = url.startsWith("https");
1930
+ const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1931
+ return new Promise((resolve4, reject) => {
1932
+ const req = doRequest(
1933
+ url,
1934
+ {
1935
+ method: "POST",
1936
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
1937
+ },
1938
+ (res) => {
1939
+ res.resume();
1940
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1941
+ else reject(new Error(`Webhook returned ${res.statusCode}`));
1942
+ }
1943
+ );
1944
+ req.on("error", reject);
1945
+ req.setTimeout(1e4, () => {
1946
+ req.destroy(new Error("Webhook timeout"));
1947
+ });
1948
+ req.write(body);
1949
+ req.end();
1950
+ });
1951
+ }
1952
+ function sendCommand(payload, cmd) {
1953
+ return new Promise((resolve4, reject) => {
1954
+ const env = {
1955
+ ...process.env,
1956
+ AGENTFLOW_ALERT_AGENT: payload.agentId,
1957
+ AGENTFLOW_ALERT_CONDITION: payload.condition,
1958
+ AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1959
+ AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1960
+ AGENTFLOW_ALERT_DETAIL: payload.detail,
1961
+ AGENTFLOW_ALERT_FILE: payload.file,
1962
+ AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1963
+ };
1964
+ (0, import_node_child_process3.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1965
+ if (err) reject(err);
1966
+ else resolve4();
1967
+ });
1968
+ });
1969
+ }
1331
1970
 
1332
1971
  // src/watch-state.ts
1333
1972
  var import_node_fs3 = require("fs");
@@ -1393,7 +2032,9 @@ function detectTransitions(previous, currentRecords, config, now) {
1393
2032
  const hasError = config.alertConditions.some((c) => c.type === "error");
1394
2033
  const hasRecovery = config.alertConditions.some((c) => c.type === "recovery");
1395
2034
  const staleConditions = config.alertConditions.filter((c) => c.type === "stale");
1396
- const consecutiveConditions = config.alertConditions.filter((c) => c.type === "consecutive-errors");
2035
+ const consecutiveConditions = config.alertConditions.filter(
2036
+ (c) => c.type === "consecutive-errors"
2037
+ );
1397
2038
  const byAgent = /* @__PURE__ */ new Map();
1398
2039
  for (const r of currentRecords) {
1399
2040
  const existing = byAgent.get(r.id);
@@ -1417,14 +2058,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1417
2058
  for (const cond of consecutiveConditions) {
1418
2059
  if (newConsec === cond.threshold) {
1419
2060
  if (canAlert(prev, `consecutive-errors:${cond.threshold}`, config.cooldownMs, now)) {
1420
- alerts.push(makePayload(
1421
- agentId,
1422
- `consecutive-errors (${cond.threshold})`,
1423
- prevStatus,
1424
- currStatus,
1425
- { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
1426
- config.dirs
1427
- ));
2061
+ alerts.push(
2062
+ makePayload(
2063
+ agentId,
2064
+ `consecutive-errors (${cond.threshold})`,
2065
+ prevStatus,
2066
+ currStatus,
2067
+ { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
2068
+ config.dirs
2069
+ )
2070
+ );
1428
2071
  }
1429
2072
  }
1430
2073
  }
@@ -1433,14 +2076,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1433
2076
  if (sinceActive > cond.durationMs && record.lastActive > 0) {
1434
2077
  if (canAlert(prev, "stale", config.cooldownMs, now)) {
1435
2078
  const mins = Math.floor(sinceActive / 6e4);
1436
- alerts.push(makePayload(
1437
- agentId,
1438
- "stale",
1439
- prevStatus,
1440
- currStatus,
1441
- { ...record, detail: `No update for ${mins}m. ${record.detail}` },
1442
- config.dirs
1443
- ));
2079
+ alerts.push(
2080
+ makePayload(
2081
+ agentId,
2082
+ "stale",
2083
+ prevStatus,
2084
+ currStatus,
2085
+ { ...record, detail: `No update for ${mins}m. ${record.detail}` },
2086
+ config.dirs
2087
+ )
2088
+ );
1444
2089
  }
1445
2090
  }
1446
2091
  }
@@ -1453,14 +2098,19 @@ function detectTransitions(previous, currentRecords, config, now) {
1453
2098
  if (canAlert(prev, "stale-auto", config.cooldownMs, now)) {
1454
2099
  const mins = Math.floor(sinceActive / 6e4);
1455
2100
  const expectedMins = Math.floor(expectedInterval / 6e4);
1456
- alerts.push(makePayload(
1457
- agentId,
1458
- "stale (auto)",
1459
- prevStatus,
1460
- currStatus,
1461
- { ...record, detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}` },
1462
- config.dirs
1463
- ));
2101
+ alerts.push(
2102
+ makePayload(
2103
+ agentId,
2104
+ "stale (auto)",
2105
+ prevStatus,
2106
+ currStatus,
2107
+ {
2108
+ ...record,
2109
+ detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}`
2110
+ },
2111
+ config.dirs
2112
+ )
2113
+ );
1464
2114
  }
1465
2115
  }
1466
2116
  }
@@ -1519,118 +2169,6 @@ function makePayload(agentId, condition, previousStatus, currentStatus, record,
1519
2169
  };
1520
2170
  }
1521
2171
 
1522
- // src/watch-alerts.ts
1523
- var import_node_https = require("https");
1524
- var import_node_http = require("http");
1525
- var import_node_child_process2 = require("child_process");
1526
- function formatAlertMessage(payload) {
1527
- const time = new Date(payload.timestamp).toISOString();
1528
- const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1529
- return [
1530
- `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1531
- ` Status: ${arrow}`,
1532
- payload.detail ? ` Detail: ${payload.detail}` : null,
1533
- ` File: ${payload.file}`,
1534
- ` Time: ${time}`
1535
- ].filter(Boolean).join("\n");
1536
- }
1537
- function formatTelegram(payload) {
1538
- const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1539
- const time = new Date(payload.timestamp).toLocaleTimeString();
1540
- return [
1541
- `${icon} *AgentFlow Alert*`,
1542
- `*${payload.condition}*: \`${payload.agentId}\``,
1543
- `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1544
- payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1545
- `Time: ${time}`
1546
- ].filter(Boolean).join("\n");
1547
- }
1548
- async function sendAlert(payload, channel) {
1549
- try {
1550
- switch (channel.type) {
1551
- case "stdout":
1552
- sendStdout(payload);
1553
- break;
1554
- case "telegram":
1555
- await sendTelegram(payload, channel.botToken, channel.chatId);
1556
- break;
1557
- case "webhook":
1558
- await sendWebhook(payload, channel.url);
1559
- break;
1560
- case "command":
1561
- await sendCommand(payload, channel.cmd);
1562
- break;
1563
- }
1564
- } catch (err) {
1565
- const msg = err instanceof Error ? err.message : String(err);
1566
- console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1567
- }
1568
- }
1569
- function sendStdout(payload) {
1570
- console.log(formatAlertMessage(payload));
1571
- }
1572
- function sendTelegram(payload, botToken, chatId) {
1573
- const body = JSON.stringify({
1574
- chat_id: chatId,
1575
- text: formatTelegram(payload),
1576
- parse_mode: "Markdown"
1577
- });
1578
- return new Promise((resolve4, reject) => {
1579
- const req = (0, import_node_https.request)(
1580
- `https://api.telegram.org/bot${botToken}/sendMessage`,
1581
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1582
- (res) => {
1583
- res.resume();
1584
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1585
- else reject(new Error(`Telegram API returned ${res.statusCode}`));
1586
- }
1587
- );
1588
- req.on("error", reject);
1589
- req.write(body);
1590
- req.end();
1591
- });
1592
- }
1593
- function sendWebhook(payload, url) {
1594
- const body = JSON.stringify(payload);
1595
- const isHttps = url.startsWith("https");
1596
- const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1597
- return new Promise((resolve4, reject) => {
1598
- const req = doRequest(
1599
- url,
1600
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1601
- (res) => {
1602
- res.resume();
1603
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve4();
1604
- else reject(new Error(`Webhook returned ${res.statusCode}`));
1605
- }
1606
- );
1607
- req.on("error", reject);
1608
- req.setTimeout(1e4, () => {
1609
- req.destroy(new Error("Webhook timeout"));
1610
- });
1611
- req.write(body);
1612
- req.end();
1613
- });
1614
- }
1615
- function sendCommand(payload, cmd) {
1616
- return new Promise((resolve4, reject) => {
1617
- const env = {
1618
- ...process.env,
1619
- AGENTFLOW_ALERT_AGENT: payload.agentId,
1620
- AGENTFLOW_ALERT_CONDITION: payload.condition,
1621
- AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1622
- AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1623
- AGENTFLOW_ALERT_DETAIL: payload.detail,
1624
- AGENTFLOW_ALERT_FILE: payload.file,
1625
- AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1626
- };
1627
- (0, import_node_child_process2.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1628
- if (err) reject(err);
1629
- else resolve4();
1630
- });
1631
- });
1632
- }
1633
-
1634
2172
  // src/watch.ts
1635
2173
  function parseWatchArgs(argv) {
1636
2174
  const dirs = [];
@@ -1672,7 +2210,9 @@ function parseWatchArgs(argv) {
1672
2210
  if (botToken && chatId) {
1673
2211
  notifyChannels.push({ type: "telegram", botToken, chatId });
1674
2212
  } else {
1675
- console.error("Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars");
2213
+ console.error(
2214
+ "Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars"
2215
+ );
1676
2216
  }
1677
2217
  } else if (val.startsWith("webhook:")) {
1678
2218
  notifyChannels.push({ type: "webhook", url: val.slice(8) });
@@ -1724,7 +2264,8 @@ function parseWatchArgs(argv) {
1724
2264
  };
1725
2265
  }
1726
2266
  function printWatchUsage() {
1727
- console.log(`
2267
+ console.log(
2268
+ `
1728
2269
  AgentFlow Watch \u2014 headless alert system for agent infrastructure.
1729
2270
 
1730
2271
  Polls directories for JSON/JSONL files, detects failures and stale
@@ -1767,7 +2308,8 @@ Examples:
1767
2308
  agentflow watch ./data ./cron --notify telegram --poll 60
1768
2309
  agentflow watch ./traces --notify webhook:https://hooks.slack.com/... --alert-on consecutive-errors:3
1769
2310
  agentflow watch ./data --notify "command:curl -X POST https://my-pagerduty/alert"
1770
- `.trim());
2311
+ `.trim()
2312
+ );
1771
2313
  }
1772
2314
  function startWatch(argv) {
1773
2315
  const config = parseWatchArgs(argv);
@@ -1796,7 +2338,9 @@ agentflow watch started`);
1796
2338
  console.log(` Directories: ${valid.join(", ")}`);
1797
2339
  console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
1798
2340
  console.log(` Alert on: ${condLabels.join(", ")}`);
1799
- console.log(` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`);
2341
+ console.log(
2342
+ ` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
2343
+ );
1800
2344
  console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
1801
2345
  console.log(` State: ${config.stateFilePath}`);
1802
2346
  console.log(` Hostname: ${(0, import_node_os.hostname)()}`);
@@ -1822,9 +2366,13 @@ agentflow watch started`);
1822
2366
  if (pollCount % 10 === 0) {
1823
2367
  const agentCount = Object.keys(state.agents).length;
1824
2368
  const errorCount = Object.values(state.agents).filter((a) => a.lastStatus === "error").length;
1825
- const runningCount = Object.values(state.agents).filter((a) => a.lastStatus === "running").length;
2369
+ const runningCount = Object.values(state.agents).filter(
2370
+ (a) => a.lastStatus === "running"
2371
+ ).length;
1826
2372
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1827
- console.log(`[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`);
2373
+ console.log(
2374
+ `[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`
2375
+ );
1828
2376
  }
1829
2377
  }
1830
2378
  poll();
@@ -1841,7 +2389,9 @@ agentflow watch started`);
1841
2389
  }
1842
2390
  // Annotate the CommonJS export names for ESM import in node:
1843
2391
  0 && (module.exports = {
2392
+ checkGuards,
1844
2393
  createGraphBuilder,
2394
+ createTraceStore,
1845
2395
  findWaitingOn,
1846
2396
  getChildren,
1847
2397
  getCriticalPath,
@@ -1860,5 +2410,8 @@ agentflow watch started`);
1860
2410
  runTraced,
1861
2411
  startLive,
1862
2412
  startWatch,
1863
- stitchTrace
2413
+ stitchTrace,
2414
+ toAsciiTree,
2415
+ toTimeline,
2416
+ withGuards
1864
2417
  });