agentflow-core 0.3.3 → 0.4.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/cli.cjs CHANGED
@@ -1,241 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
-
4
- // src/cli.ts
5
- var import_path = require("path");
6
-
7
- // src/runner.ts
8
- var import_node_child_process = require("child_process");
9
- var import_node_fs = require("fs");
10
- var import_node_path = require("path");
11
-
12
- // src/graph-builder.ts
13
- var import_crypto = require("crypto");
14
- function deepFreeze(obj) {
15
- if (obj === null || typeof obj !== "object") return obj;
16
- if (obj instanceof Map) {
17
- Object.freeze(obj);
18
- for (const value of obj.values()) {
19
- deepFreeze(value);
20
- }
21
- return obj;
22
- }
23
- Object.freeze(obj);
24
- const record = obj;
25
- for (const key of Object.keys(record)) {
26
- const value = record[key];
27
- if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
28
- deepFreeze(value);
29
- }
30
- }
31
- return obj;
32
- }
33
- function createCounterIdGenerator() {
34
- let counter = 0;
35
- return () => {
36
- counter++;
37
- return `node_${String(counter).padStart(3, "0")}`;
38
- };
39
- }
40
- function createGraphBuilder(config) {
41
- const generateId = config?.idGenerator ?? createCounterIdGenerator();
42
- const agentId = config?.agentId ?? "unknown";
43
- const trigger = config?.trigger ?? "manual";
44
- const spanId = (0, import_crypto.randomUUID)();
45
- const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? (0, import_crypto.randomUUID)();
46
- const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
47
- const graphId = generateId();
48
- const startTime = Date.now();
49
- const nodes = /* @__PURE__ */ new Map();
50
- const edges = [];
51
- const events = [];
52
- const parentStack = [];
53
- let rootNodeId = null;
54
- let built = false;
55
- function assertNotBuilt() {
56
- if (built) {
57
- throw new Error("GraphBuilder: cannot mutate after build() has been called");
58
- }
59
- }
60
- function getNode(nodeId) {
61
- const node = nodes.get(nodeId);
62
- if (!node) {
63
- throw new Error(`GraphBuilder: node "${nodeId}" does not exist`);
64
- }
65
- return node;
66
- }
67
- function recordEvent(nodeId, eventType, data = {}) {
68
- events.push({
69
- timestamp: Date.now(),
70
- eventType,
71
- nodeId,
72
- data
73
- });
74
- }
75
- function buildGraph() {
76
- if (rootNodeId === null) {
77
- throw new Error("GraphBuilder: cannot build a graph with no nodes");
78
- }
79
- let graphStatus = "completed";
80
- for (const node of nodes.values()) {
81
- if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
82
- graphStatus = "failed";
83
- break;
84
- }
85
- if (node.status === "running") {
86
- graphStatus = "running";
87
- }
88
- }
89
- const endTime = graphStatus === "running" ? null : Date.now();
90
- const frozenNodes = new Map(
91
- [...nodes.entries()].map(([id, mNode]) => [
92
- id,
93
- {
94
- id: mNode.id,
95
- type: mNode.type,
96
- name: mNode.name,
97
- startTime: mNode.startTime,
98
- endTime: mNode.endTime,
99
- status: mNode.status,
100
- parentId: mNode.parentId,
101
- children: [...mNode.children],
102
- metadata: { ...mNode.metadata },
103
- state: { ...mNode.state }
104
- }
105
- ])
106
- );
107
- const graph = {
108
- id: graphId,
109
- rootNodeId,
110
- nodes: frozenNodes,
111
- edges: [...edges],
112
- startTime,
113
- endTime,
114
- status: graphStatus,
115
- trigger,
116
- agentId,
117
- events: [...events],
118
- traceId,
119
- spanId,
120
- parentSpanId
121
- };
122
- return deepFreeze(graph);
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
123
21
  }
124
- const builder = {
125
- get graphId() {
126
- return graphId;
127
- },
128
- get traceContext() {
129
- return { traceId, spanId };
130
- },
131
- startNode(opts) {
132
- assertNotBuilt();
133
- const id = generateId();
134
- const parentId = opts.parentId ?? parentStack[parentStack.length - 1] ?? null;
135
- if (parentId !== null && !nodes.has(parentId)) {
136
- throw new Error(`GraphBuilder: parent node "${parentId}" does not exist`);
137
- }
138
- const node = {
139
- id,
140
- type: opts.type,
141
- name: opts.name,
142
- startTime: Date.now(),
143
- endTime: null,
144
- status: "running",
145
- parentId,
146
- children: [],
147
- metadata: opts.metadata ? { ...opts.metadata } : {},
148
- state: {}
149
- };
150
- nodes.set(id, node);
151
- if (parentId !== null) {
152
- const parent = nodes.get(parentId);
153
- if (parent) {
154
- parent.children.push(id);
155
- }
156
- edges.push({ from: parentId, to: id, type: "spawned" });
157
- }
158
- if (rootNodeId === null) {
159
- rootNodeId = id;
160
- }
161
- recordEvent(id, "agent_start", { type: opts.type, name: opts.name });
162
- return id;
163
- },
164
- endNode(nodeId, status = "completed") {
165
- assertNotBuilt();
166
- const node = getNode(nodeId);
167
- if (node.endTime !== null) {
168
- throw new Error(
169
- `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
170
- );
171
- }
172
- node.endTime = Date.now();
173
- node.status = status;
174
- recordEvent(nodeId, "agent_end", { status });
175
- },
176
- failNode(nodeId, error) {
177
- assertNotBuilt();
178
- const node = getNode(nodeId);
179
- if (node.endTime !== null) {
180
- throw new Error(
181
- `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
182
- );
183
- }
184
- const errorMessage = error instanceof Error ? error.message : error;
185
- const errorStack = error instanceof Error ? error.stack : void 0;
186
- node.endTime = Date.now();
187
- node.status = "failed";
188
- node.metadata.error = errorMessage;
189
- if (errorStack) {
190
- node.metadata.errorStack = errorStack;
191
- }
192
- recordEvent(nodeId, "tool_error", { error: errorMessage });
193
- },
194
- addEdge(from, to, type) {
195
- assertNotBuilt();
196
- getNode(from);
197
- getNode(to);
198
- edges.push({ from, to, type });
199
- recordEvent(from, "custom", { to, type, action: "edge_add" });
200
- },
201
- pushEvent(event) {
202
- assertNotBuilt();
203
- getNode(event.nodeId);
204
- events.push({
205
- ...event,
206
- timestamp: Date.now()
207
- });
208
- },
209
- updateState(nodeId, state) {
210
- assertNotBuilt();
211
- const node = getNode(nodeId);
212
- Object.assign(node.state, state);
213
- recordEvent(nodeId, "custom", { action: "state_update", ...state });
214
- },
215
- withParent(parentId, fn) {
216
- assertNotBuilt();
217
- getNode(parentId);
218
- parentStack.push(parentId);
219
- try {
220
- return fn();
221
- } finally {
222
- parentStack.pop();
223
- }
224
- },
225
- getSnapshot() {
226
- return buildGraph();
227
- },
228
- build() {
229
- assertNotBuilt();
230
- const graph = buildGraph();
231
- built = true;
232
- return graph;
233
- }
234
- };
235
- return builder;
236
- }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
237
32
 
238
33
  // src/loader.ts
34
+ var loader_exports = {};
35
+ __export(loader_exports, {
36
+ graphToJson: () => graphToJson,
37
+ loadGraph: () => loadGraph
38
+ });
239
39
  function toNodesMap(raw) {
240
40
  if (raw instanceof Map) return raw;
241
41
  if (Array.isArray(raw)) {
@@ -286,160 +86,30 @@ function graphToJson(graph) {
286
86
  parentSpanId: graph.parentSpanId
287
87
  };
288
88
  }
89
+ var init_loader = __esm({
90
+ "src/loader.ts"() {
91
+ "use strict";
92
+ }
93
+ });
289
94
 
290
- // src/runner.ts
291
- function globToRegex(pattern) {
292
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
293
- return new RegExp(`^${escaped}$`);
95
+ // src/cli.ts
96
+ var import_path3 = require("path");
97
+
98
+ // src/live.ts
99
+ var import_node_fs = require("fs");
100
+ var import_node_path = require("path");
101
+
102
+ // src/graph-query.ts
103
+ function getChildren(graph, nodeId) {
104
+ const node = graph.nodes.get(nodeId);
105
+ if (!node) return [];
106
+ const result = [];
107
+ for (const childId of node.children) {
108
+ const child = graph.nodes.get(childId);
109
+ if (child) result.push(child);
110
+ }
111
+ return result;
294
112
  }
295
- function snapshotDir(dir, patterns) {
296
- const result = /* @__PURE__ */ new Map();
297
- if (!(0, import_node_fs.existsSync)(dir)) return result;
298
- for (const entry of (0, import_node_fs.readdirSync)(dir)) {
299
- if (!patterns.some((re) => re.test(entry))) continue;
300
- const full = (0, import_node_path.join)(dir, entry);
301
- try {
302
- const stat = (0, import_node_fs.statSync)(full);
303
- if (stat.isFile()) {
304
- result.set(full, stat.mtimeMs);
305
- }
306
- } catch {
307
- }
308
- }
309
- return result;
310
- }
311
- function agentIdFromFilename(filePath) {
312
- const base = (0, import_node_path.basename)(filePath, ".json");
313
- const cleaned = base.replace(/-state$/, "");
314
- return `alfred-${cleaned}`;
315
- }
316
- function deriveAgentId(command) {
317
- return "orchestrator";
318
- }
319
- function fileTimestamp() {
320
- return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
321
- }
322
- async function runTraced(config) {
323
- const {
324
- command,
325
- agentId = deriveAgentId(command),
326
- trigger = "cli",
327
- tracesDir = "./traces",
328
- watchDirs = [],
329
- watchPatterns = ["*.json"]
330
- } = config;
331
- if (command.length === 0) {
332
- throw new Error("runTraced: command must not be empty");
333
- }
334
- const resolvedTracesDir = (0, import_node_path.resolve)(tracesDir);
335
- const patterns = watchPatterns.map(globToRegex);
336
- const orchestrator = createGraphBuilder({ agentId, trigger });
337
- const { traceId, spanId } = orchestrator.traceContext;
338
- const beforeSnapshots = /* @__PURE__ */ new Map();
339
- for (const dir of watchDirs) {
340
- beforeSnapshots.set(dir, snapshotDir(dir, patterns));
341
- }
342
- const rootId = orchestrator.startNode({ type: "agent", name: agentId });
343
- const dispatchId = orchestrator.startNode({
344
- type: "tool",
345
- name: "dispatch-command",
346
- parentId: rootId
347
- });
348
- orchestrator.updateState(dispatchId, { command: command.join(" ") });
349
- const monitorId = orchestrator.startNode({
350
- type: "tool",
351
- name: "state-monitor",
352
- parentId: rootId
353
- });
354
- orchestrator.updateState(monitorId, {
355
- watchDirs,
356
- watchPatterns
357
- });
358
- const startMs = Date.now();
359
- const execCmd = command[0] ?? "";
360
- const execArgs = command.slice(1);
361
- process.env.AGENTFLOW_TRACE_ID = traceId;
362
- process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
363
- const result = (0, import_node_child_process.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
364
- delete process.env.AGENTFLOW_TRACE_ID;
365
- delete process.env.AGENTFLOW_PARENT_SPAN_ID;
366
- const exitCode = result.status ?? 1;
367
- const duration = (Date.now() - startMs) / 1e3;
368
- const stateChanges = [];
369
- for (const dir of watchDirs) {
370
- const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
371
- const after = snapshotDir(dir, patterns);
372
- for (const [filePath, mtime] of after) {
373
- const prevMtime = before.get(filePath);
374
- if (prevMtime === void 0 || mtime > prevMtime) {
375
- stateChanges.push(filePath);
376
- }
377
- }
378
- }
379
- orchestrator.updateState(monitorId, { stateChanges });
380
- orchestrator.endNode(monitorId);
381
- if (exitCode === 0) {
382
- orchestrator.endNode(dispatchId);
383
- } else {
384
- orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
385
- }
386
- orchestrator.updateState(rootId, {
387
- exitCode,
388
- duration,
389
- stateChanges
390
- });
391
- if (exitCode === 0) {
392
- orchestrator.endNode(rootId);
393
- } else {
394
- orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
395
- }
396
- const orchestratorGraph = orchestrator.build();
397
- const allGraphs = [orchestratorGraph];
398
- for (const filePath of stateChanges) {
399
- const childAgentId = agentIdFromFilename(filePath);
400
- const childBuilder = createGraphBuilder({
401
- agentId: childAgentId,
402
- trigger: "state-change",
403
- traceId,
404
- parentSpanId: spanId
405
- });
406
- const childRootId = childBuilder.startNode({
407
- type: "agent",
408
- name: childAgentId
409
- });
410
- childBuilder.updateState(childRootId, {
411
- stateFile: filePath,
412
- detectedBy: "runner-state-monitor"
413
- });
414
- childBuilder.endNode(childRootId);
415
- allGraphs.push(childBuilder.build());
416
- }
417
- if (!(0, import_node_fs.existsSync)(resolvedTracesDir)) {
418
- (0, import_node_fs.mkdirSync)(resolvedTracesDir, { recursive: true });
419
- }
420
- const ts = fileTimestamp();
421
- const tracePaths = [];
422
- for (const graph of allGraphs) {
423
- const filename = `${graph.agentId}-${ts}.json`;
424
- const outPath = (0, import_node_path.join)(resolvedTracesDir, filename);
425
- (0, import_node_fs.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
426
- tracePaths.push(outPath);
427
- }
428
- return {
429
- exitCode,
430
- traceId,
431
- spanId,
432
- tracePaths,
433
- stateChanges,
434
- duration
435
- };
436
- }
437
-
438
- // src/live.ts
439
- var import_node_fs2 = require("fs");
440
- var import_node_path2 = require("path");
441
-
442
- // src/graph-query.ts
443
113
  function getFailures(graph) {
444
114
  const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
445
115
  return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
@@ -573,6 +243,7 @@ function getTraceTree(trace) {
573
243
  }
574
244
 
575
245
  // src/live.ts
246
+ init_loader();
576
247
  var C = {
577
248
  reset: "\x1B[0m",
578
249
  bold: "\x1B[1m",
@@ -605,17 +276,18 @@ function parseArgs(argv) {
605
276
  config.recursive = true;
606
277
  i++;
607
278
  } else if (!arg.startsWith("-")) {
608
- config.dirs.push((0, import_node_path2.resolve)(arg));
279
+ config.dirs.push((0, import_node_path.resolve)(arg));
609
280
  i++;
610
281
  } else {
611
282
  i++;
612
283
  }
613
284
  }
614
- if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
285
+ if (config.dirs.length === 0) config.dirs.push((0, import_node_path.resolve)("."));
615
286
  return config;
616
287
  }
617
288
  function printUsage() {
618
- console.log(`
289
+ console.log(
290
+ `
619
291
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
620
292
 
621
293
  Auto-detects agent traces, state files, job schedulers, and session logs
@@ -636,20 +308,21 @@ Examples:
636
308
  agentflow live ./data
637
309
  agentflow live ./traces ./cron ./workers --refresh 5
638
310
  agentflow live /var/lib/myagent -R
639
- `.trim());
311
+ `.trim()
312
+ );
640
313
  }
641
314
  function scanFiles(dirs, recursive) {
642
315
  const results = [];
643
316
  const seen = /* @__PURE__ */ new Set();
644
317
  function scanDir(d, topLevel) {
645
318
  try {
646
- for (const f of (0, import_node_fs2.readdirSync)(d)) {
319
+ for (const f of (0, import_node_fs.readdirSync)(d)) {
647
320
  if (f.startsWith(".")) continue;
648
- const fp = (0, import_node_path2.join)(d, f);
321
+ const fp = (0, import_node_path.join)(d, f);
649
322
  if (seen.has(fp)) continue;
650
323
  let stat;
651
324
  try {
652
- stat = (0, import_node_fs2.statSync)(fp);
325
+ stat = (0, import_node_fs.statSync)(fp);
653
326
  } catch {
654
327
  continue;
655
328
  }
@@ -675,20 +348,22 @@ function scanFiles(dirs, recursive) {
675
348
  }
676
349
  function safeReadJson(fp) {
677
350
  try {
678
- return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
351
+ return JSON.parse((0, import_node_fs.readFileSync)(fp, "utf8"));
679
352
  } catch {
680
353
  return null;
681
354
  }
682
355
  }
683
356
  function nameFromFile(filename) {
684
- return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
357
+ return (0, import_node_path.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
685
358
  }
686
359
  function normalizeStatus(val) {
687
360
  if (typeof val !== "string") return "unknown";
688
361
  const s = val.toLowerCase();
689
362
  if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
690
- if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s)) return "error";
691
- if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s)) return "running";
363
+ if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s))
364
+ return "error";
365
+ if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s))
366
+ return "running";
692
367
  return "unknown";
693
368
  }
694
369
  function findStatus(obj) {
@@ -704,7 +379,17 @@ function findStatus(obj) {
704
379
  return "unknown";
705
380
  }
706
381
  function findTimestamp(obj) {
707
- for (const key of ["ts", "timestamp", "lastRunAtMs", "last_run", "lastExecution", "updated_at", "started_at", "endTime", "startTime"]) {
382
+ for (const key of [
383
+ "ts",
384
+ "timestamp",
385
+ "lastRunAtMs",
386
+ "last_run",
387
+ "lastExecution",
388
+ "updated_at",
389
+ "started_at",
390
+ "endTime",
391
+ "startTime"
392
+ ]) {
708
393
  const val = obj[key];
709
394
  if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
710
395
  if (typeof val === "string") {
@@ -716,7 +401,16 @@ function findTimestamp(obj) {
716
401
  }
717
402
  function extractDetail(obj) {
718
403
  const parts = [];
719
- for (const key of ["summary", "message", "description", "lastError", "error", "name", "jobId", "id"]) {
404
+ for (const key of [
405
+ "summary",
406
+ "message",
407
+ "description",
408
+ "lastError",
409
+ "error",
410
+ "name",
411
+ "jobId",
412
+ "id"
413
+ ]) {
720
414
  const val = obj[key];
721
415
  if (typeof val === "string" && val.length > 0 && val.length < 200) {
722
416
  parts.push(val.slice(0, 80));
@@ -776,7 +470,14 @@ function processJsonFile(file) {
776
470
  const status2 = findStatus(state);
777
471
  const ts2 = findTimestamp(state) || file.mtime;
778
472
  const detail2 = extractDetail(state);
779
- records.push({ id: String(name), source: "jobs", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
473
+ records.push({
474
+ id: String(name),
475
+ source: "jobs",
476
+ status: status2,
477
+ lastActive: ts2,
478
+ detail: detail2,
479
+ file: file.filename
480
+ });
780
481
  }
781
482
  return records;
782
483
  }
@@ -791,7 +492,14 @@ function processJsonFile(file) {
791
492
  const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
792
493
  const pid = w.pid;
793
494
  const detail2 = pid ? `pid: ${pid}` : extractDetail(w);
794
- records.push({ id: name, source: "workers", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
495
+ records.push({
496
+ id: name,
497
+ source: "workers",
498
+ status: status2,
499
+ lastActive: ts2,
500
+ detail: detail2,
501
+ file: file.filename
502
+ });
795
503
  }
796
504
  return records;
797
505
  }
@@ -799,12 +507,19 @@ function processJsonFile(file) {
799
507
  const status = findStatus(obj);
800
508
  const ts = findTimestamp(obj) || file.mtime;
801
509
  const detail = extractDetail(obj);
802
- records.push({ id: nameFromFile(file.filename), source: "state", status, lastActive: ts, detail, file: file.filename });
510
+ records.push({
511
+ id: nameFromFile(file.filename),
512
+ source: "state",
513
+ status,
514
+ lastActive: ts,
515
+ detail,
516
+ file: file.filename
517
+ });
803
518
  return records;
804
519
  }
805
520
  function processJsonlFile(file) {
806
521
  try {
807
- const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
522
+ const content = (0, import_node_fs.readFileSync)(file.path, "utf8").trim();
808
523
  if (!content) return [];
809
524
  const lines = content.split("\n");
810
525
  const lineCount = lines.length;
@@ -815,13 +530,22 @@ function processJsonlFile(file) {
815
530
  const ts2 = findTimestamp(lastObj) || file.mtime;
816
531
  const action = lastObj.action;
817
532
  const detail2 = action ? `${action} (${lineCount} entries)` : `${lineCount} entries`;
818
- return [{ id: String(name), source: "session", status: status2, lastActive: ts2, detail: detail2, file: file.filename }];
533
+ return [
534
+ {
535
+ id: String(name),
536
+ source: "session",
537
+ status: status2,
538
+ lastActive: ts2,
539
+ detail: detail2,
540
+ file: file.filename
541
+ }
542
+ ];
819
543
  }
820
544
  const tail = lines.slice(Math.max(0, lineCount - 30));
821
545
  let model = "";
822
546
  let totalTokens = 0;
823
547
  let totalCost = 0;
824
- let toolCalls = [];
548
+ const toolCalls = [];
825
549
  let lastUserMsg = "";
826
550
  let lastAssistantMsg = "";
827
551
  let errorCount = 0;
@@ -920,7 +644,16 @@ function processJsonlFile(file) {
920
644
  const status = errorCount > lineCount / 4 ? "error" : lastRole === "assistant" ? "ok" : "running";
921
645
  const ts = findTimestamp(lastObj) || file.mtime;
922
646
  const sessionName = sessionId ? sessionId.slice(0, 8) : nameFromFile(file.filename);
923
- return [{ id: String(name !== "unknown" ? name : sessionName), source: "session", status, lastActive: ts, detail, file: file.filename }];
647
+ return [
648
+ {
649
+ id: String(name !== "unknown" ? name : sessionName),
650
+ source: "session",
651
+ status,
652
+ lastActive: ts,
653
+ detail,
654
+ file: file.filename
655
+ }
656
+ ];
924
657
  } catch {
925
658
  return [];
926
659
  }
@@ -1052,7 +785,8 @@ function render(config) {
1052
785
  if (g.fail > 0 && g.ok === 0 && g.running === 0) return `${C.red}error${C.reset}`;
1053
786
  if (g.running > 0) return `${C.green}running${C.reset}`;
1054
787
  if (g.fail > 0) return `${C.yellow}${g.ok}ok/${g.fail}err${C.reset}`;
1055
- if (g.ok > 0) return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
788
+ if (g.ok > 0)
789
+ return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1056
790
  return `${C.dim}idle${C.reset}`;
1057
791
  }
1058
792
  function sourceTag(s) {
@@ -1084,18 +818,30 @@ function render(config) {
1084
818
  }
1085
819
  const L = [];
1086
820
  writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1087
- 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}`);
821
+ writeLine(
822
+ L,
823
+ `${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}`
824
+ );
1088
825
  const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
1089
826
  const pad1 = Math.max(0, 64 - metaLine.length);
1090
- writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
827
+ writeLine(
828
+ L,
829
+ `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`
830
+ );
1091
831
  writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1092
832
  const sc = totFail === 0 ? C.green : C.yellow;
1093
833
  writeLine(L, "");
1094
- 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}`);
834
+ writeLine(
835
+ L,
836
+ ` ${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}`
837
+ );
1095
838
  writeLine(L, "");
1096
839
  writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1097
840
  writeLine(L, "");
1098
- writeLine(L, ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`);
841
+ writeLine(
842
+ L,
843
+ ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`
844
+ );
1099
845
  let lineCount = 0;
1100
846
  for (const g of groups) {
1101
847
  if (lineCount > 35) break;
@@ -1106,13 +852,19 @@ function render(config) {
1106
852
  const name = truncate(g.name, 26).padEnd(26);
1107
853
  const st = statusText(g);
1108
854
  const det = truncate(g.detail, detailWidth);
1109
- writeLine(L, ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`);
855
+ writeLine(
856
+ L,
857
+ ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`
858
+ );
1110
859
  lineCount++;
1111
860
  } else {
1112
861
  const name = truncate(g.name, 24).padEnd(24);
1113
862
  const st = statusText(g);
1114
863
  const tag = sourceTag(g.source);
1115
- writeLine(L, ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`);
864
+ writeLine(
865
+ L,
866
+ ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`
867
+ );
1116
868
  lineCount++;
1117
869
  const kids = g.children.slice(0, 12);
1118
870
  for (let i = 0; i < kids.length; i++) {
@@ -1124,7 +876,10 @@ function render(config) {
1124
876
  const cName = truncate(child.id, 22).padEnd(22);
1125
877
  const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
1126
878
  const cDet = truncate(child.detail, detailWidth - 5);
1127
- writeLine(L, ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`);
879
+ writeLine(
880
+ L,
881
+ ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`
882
+ );
1128
883
  lineCount++;
1129
884
  }
1130
885
  if (g.children.length > 12) {
@@ -1141,7 +896,10 @@ function render(config) {
1141
896
  const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1142
897
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1143
898
  const tid = dt.traceId.slice(0, 8);
1144
- writeLine(L, ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
899
+ writeLine(
900
+ L,
901
+ ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`
902
+ );
1145
903
  const tree = getTraceTree(dt);
1146
904
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1147
905
  const tg = tree[i];
@@ -1151,7 +909,10 @@ function render(config) {
1151
909
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1152
910
  const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1153
911
  const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
1154
- writeLine(L, `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`);
912
+ writeLine(
913
+ L,
914
+ `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`
915
+ );
1155
916
  }
1156
917
  }
1157
918
  }
@@ -1166,7 +927,10 @@ function render(config) {
1166
927
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1167
928
  const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1168
929
  const det = truncate(r.detail, detailWidth);
1169
- writeLine(L, ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`);
930
+ writeLine(
931
+ L,
932
+ ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`
933
+ );
1170
934
  }
1171
935
  }
1172
936
  if (files.length === 0) {
@@ -1188,13 +952,13 @@ function getDistDepth(dt, spanId) {
1188
952
  }
1189
953
  function startLive(argv) {
1190
954
  const config = parseArgs(argv);
1191
- const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
955
+ const valid = config.dirs.filter((d) => (0, import_node_fs.existsSync)(d));
1192
956
  if (valid.length === 0) {
1193
957
  console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1194
958
  console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
1195
959
  process.exit(1);
1196
960
  }
1197
- const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
961
+ const invalid = config.dirs.filter((d) => !(0, import_node_fs.existsSync)(d));
1198
962
  if (invalid.length > 0) {
1199
963
  console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1200
964
  }
@@ -1203,24 +967,947 @@ function startLive(argv) {
1203
967
  let debounce = null;
1204
968
  for (const dir of config.dirs) {
1205
969
  try {
1206
- (0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
970
+ (0, import_node_fs.watch)(dir, { recursive: config.recursive }, () => {
1207
971
  if (debounce) clearTimeout(debounce);
1208
972
  debounce = setTimeout(() => render(config), 500);
1209
973
  });
1210
974
  } catch {
1211
975
  }
1212
976
  }
1213
- setInterval(() => render(config), config.refreshMs);
1214
- process.on("SIGINT", () => {
1215
- console.log("\n" + C.dim + "Monitor stopped." + C.reset);
1216
- process.exit(0);
1217
- });
977
+ setInterval(() => render(config), config.refreshMs);
978
+ process.on("SIGINT", () => {
979
+ console.log("\n" + C.dim + "Monitor stopped." + C.reset);
980
+ process.exit(0);
981
+ });
982
+ }
983
+
984
+ // src/runner.ts
985
+ var import_node_child_process = require("child_process");
986
+ var import_node_fs2 = require("fs");
987
+ var import_node_path2 = require("path");
988
+
989
+ // src/graph-builder.ts
990
+ var import_crypto = require("crypto");
991
+ function deepFreeze(obj) {
992
+ if (obj === null || typeof obj !== "object") return obj;
993
+ if (obj instanceof Map) {
994
+ Object.freeze(obj);
995
+ for (const value of obj.values()) {
996
+ deepFreeze(value);
997
+ }
998
+ return obj;
999
+ }
1000
+ Object.freeze(obj);
1001
+ const record = obj;
1002
+ for (const key of Object.keys(record)) {
1003
+ const value = record[key];
1004
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
1005
+ deepFreeze(value);
1006
+ }
1007
+ }
1008
+ return obj;
1009
+ }
1010
+ function createCounterIdGenerator() {
1011
+ let counter = 0;
1012
+ return () => {
1013
+ counter++;
1014
+ return `node_${String(counter).padStart(3, "0")}`;
1015
+ };
1016
+ }
1017
+ function createGraphBuilder(config) {
1018
+ const generateId = config?.idGenerator ?? createCounterIdGenerator();
1019
+ const agentId = config?.agentId ?? "unknown";
1020
+ const trigger = config?.trigger ?? "manual";
1021
+ const spanId = (0, import_crypto.randomUUID)();
1022
+ const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? (0, import_crypto.randomUUID)();
1023
+ const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
1024
+ const graphId = generateId();
1025
+ const startTime = Date.now();
1026
+ const nodes = /* @__PURE__ */ new Map();
1027
+ const edges = [];
1028
+ const events = [];
1029
+ const parentStack = [];
1030
+ let rootNodeId = null;
1031
+ let built = false;
1032
+ function assertNotBuilt() {
1033
+ if (built) {
1034
+ throw new Error("GraphBuilder: cannot mutate after build() has been called");
1035
+ }
1036
+ }
1037
+ function getNode(nodeId) {
1038
+ const node = nodes.get(nodeId);
1039
+ if (!node) {
1040
+ throw new Error(`GraphBuilder: node "${nodeId}" does not exist`);
1041
+ }
1042
+ return node;
1043
+ }
1044
+ function recordEvent(nodeId, eventType, data = {}) {
1045
+ events.push({
1046
+ timestamp: Date.now(),
1047
+ eventType,
1048
+ nodeId,
1049
+ data
1050
+ });
1051
+ }
1052
+ function buildGraph() {
1053
+ if (rootNodeId === null) {
1054
+ throw new Error("GraphBuilder: cannot build a graph with no nodes");
1055
+ }
1056
+ let graphStatus = "completed";
1057
+ for (const node of nodes.values()) {
1058
+ if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
1059
+ graphStatus = "failed";
1060
+ break;
1061
+ }
1062
+ if (node.status === "running") {
1063
+ graphStatus = "running";
1064
+ }
1065
+ }
1066
+ const endTime = graphStatus === "running" ? null : Date.now();
1067
+ const frozenNodes = new Map(
1068
+ [...nodes.entries()].map(([id, mNode]) => [
1069
+ id,
1070
+ {
1071
+ id: mNode.id,
1072
+ type: mNode.type,
1073
+ name: mNode.name,
1074
+ startTime: mNode.startTime,
1075
+ endTime: mNode.endTime,
1076
+ status: mNode.status,
1077
+ parentId: mNode.parentId,
1078
+ children: [...mNode.children],
1079
+ metadata: { ...mNode.metadata },
1080
+ state: { ...mNode.state }
1081
+ }
1082
+ ])
1083
+ );
1084
+ const graph = {
1085
+ id: graphId,
1086
+ rootNodeId,
1087
+ nodes: frozenNodes,
1088
+ edges: [...edges],
1089
+ startTime,
1090
+ endTime,
1091
+ status: graphStatus,
1092
+ trigger,
1093
+ agentId,
1094
+ events: [...events],
1095
+ traceId,
1096
+ spanId,
1097
+ parentSpanId
1098
+ };
1099
+ return deepFreeze(graph);
1100
+ }
1101
+ const builder = {
1102
+ get graphId() {
1103
+ return graphId;
1104
+ },
1105
+ get traceContext() {
1106
+ return { traceId, spanId };
1107
+ },
1108
+ startNode(opts) {
1109
+ assertNotBuilt();
1110
+ const id = generateId();
1111
+ const parentId = opts.parentId ?? parentStack[parentStack.length - 1] ?? null;
1112
+ if (parentId !== null && !nodes.has(parentId)) {
1113
+ throw new Error(`GraphBuilder: parent node "${parentId}" does not exist`);
1114
+ }
1115
+ const node = {
1116
+ id,
1117
+ type: opts.type,
1118
+ name: opts.name,
1119
+ startTime: Date.now(),
1120
+ endTime: null,
1121
+ status: "running",
1122
+ parentId,
1123
+ children: [],
1124
+ metadata: opts.metadata ? { ...opts.metadata } : {},
1125
+ state: {}
1126
+ };
1127
+ nodes.set(id, node);
1128
+ if (parentId !== null) {
1129
+ const parent = nodes.get(parentId);
1130
+ if (parent) {
1131
+ parent.children.push(id);
1132
+ }
1133
+ edges.push({ from: parentId, to: id, type: "spawned" });
1134
+ }
1135
+ if (rootNodeId === null) {
1136
+ rootNodeId = id;
1137
+ }
1138
+ recordEvent(id, "agent_start", { type: opts.type, name: opts.name });
1139
+ return id;
1140
+ },
1141
+ endNode(nodeId, status = "completed") {
1142
+ assertNotBuilt();
1143
+ const node = getNode(nodeId);
1144
+ if (node.endTime !== null) {
1145
+ throw new Error(
1146
+ `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
1147
+ );
1148
+ }
1149
+ node.endTime = Date.now();
1150
+ node.status = status;
1151
+ recordEvent(nodeId, "agent_end", { status });
1152
+ },
1153
+ failNode(nodeId, error) {
1154
+ assertNotBuilt();
1155
+ const node = getNode(nodeId);
1156
+ if (node.endTime !== null) {
1157
+ throw new Error(
1158
+ `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
1159
+ );
1160
+ }
1161
+ const errorMessage = error instanceof Error ? error.message : error;
1162
+ const errorStack = error instanceof Error ? error.stack : void 0;
1163
+ node.endTime = Date.now();
1164
+ node.status = "failed";
1165
+ node.metadata.error = errorMessage;
1166
+ if (errorStack) {
1167
+ node.metadata.errorStack = errorStack;
1168
+ }
1169
+ recordEvent(nodeId, "tool_error", { error: errorMessage });
1170
+ },
1171
+ addEdge(from, to, type) {
1172
+ assertNotBuilt();
1173
+ getNode(from);
1174
+ getNode(to);
1175
+ edges.push({ from, to, type });
1176
+ recordEvent(from, "custom", { to, type, action: "edge_add" });
1177
+ },
1178
+ pushEvent(event) {
1179
+ assertNotBuilt();
1180
+ getNode(event.nodeId);
1181
+ events.push({
1182
+ ...event,
1183
+ timestamp: Date.now()
1184
+ });
1185
+ },
1186
+ updateState(nodeId, state) {
1187
+ assertNotBuilt();
1188
+ const node = getNode(nodeId);
1189
+ Object.assign(node.state, state);
1190
+ recordEvent(nodeId, "custom", { action: "state_update", ...state });
1191
+ },
1192
+ withParent(parentId, fn) {
1193
+ assertNotBuilt();
1194
+ getNode(parentId);
1195
+ parentStack.push(parentId);
1196
+ try {
1197
+ return fn();
1198
+ } finally {
1199
+ parentStack.pop();
1200
+ }
1201
+ },
1202
+ getSnapshot() {
1203
+ return buildGraph();
1204
+ },
1205
+ build() {
1206
+ assertNotBuilt();
1207
+ const graph = buildGraph();
1208
+ built = true;
1209
+ return graph;
1210
+ }
1211
+ };
1212
+ return builder;
1213
+ }
1214
+
1215
+ // src/runner.ts
1216
+ init_loader();
1217
+ function globToRegex(pattern) {
1218
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1219
+ return new RegExp(`^${escaped}$`);
1220
+ }
1221
+ function snapshotDir(dir, patterns) {
1222
+ const result = /* @__PURE__ */ new Map();
1223
+ if (!(0, import_node_fs2.existsSync)(dir)) return result;
1224
+ for (const entry of (0, import_node_fs2.readdirSync)(dir)) {
1225
+ if (!patterns.some((re) => re.test(entry))) continue;
1226
+ const full = (0, import_node_path2.join)(dir, entry);
1227
+ try {
1228
+ const stat = (0, import_node_fs2.statSync)(full);
1229
+ if (stat.isFile()) {
1230
+ result.set(full, stat.mtimeMs);
1231
+ }
1232
+ } catch {
1233
+ }
1234
+ }
1235
+ return result;
1236
+ }
1237
+ function agentIdFromFilename(filePath) {
1238
+ const base = (0, import_node_path2.basename)(filePath, ".json");
1239
+ const cleaned = base.replace(/-state$/, "");
1240
+ return `alfred-${cleaned}`;
1241
+ }
1242
+ function deriveAgentId(command) {
1243
+ return "orchestrator";
1244
+ }
1245
+ function fileTimestamp() {
1246
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
1247
+ }
1248
+ async function runTraced(config) {
1249
+ const {
1250
+ command,
1251
+ agentId = deriveAgentId(command),
1252
+ trigger = "cli",
1253
+ tracesDir = "./traces",
1254
+ watchDirs = [],
1255
+ watchPatterns = ["*.json"]
1256
+ } = config;
1257
+ if (command.length === 0) {
1258
+ throw new Error("runTraced: command must not be empty");
1259
+ }
1260
+ const resolvedTracesDir = (0, import_node_path2.resolve)(tracesDir);
1261
+ const patterns = watchPatterns.map(globToRegex);
1262
+ const orchestrator = createGraphBuilder({ agentId, trigger });
1263
+ const { traceId, spanId } = orchestrator.traceContext;
1264
+ const beforeSnapshots = /* @__PURE__ */ new Map();
1265
+ for (const dir of watchDirs) {
1266
+ beforeSnapshots.set(dir, snapshotDir(dir, patterns));
1267
+ }
1268
+ const rootId = orchestrator.startNode({ type: "agent", name: agentId });
1269
+ const dispatchId = orchestrator.startNode({
1270
+ type: "tool",
1271
+ name: "dispatch-command",
1272
+ parentId: rootId
1273
+ });
1274
+ orchestrator.updateState(dispatchId, { command: command.join(" ") });
1275
+ const monitorId = orchestrator.startNode({
1276
+ type: "tool",
1277
+ name: "state-monitor",
1278
+ parentId: rootId
1279
+ });
1280
+ orchestrator.updateState(monitorId, {
1281
+ watchDirs,
1282
+ watchPatterns
1283
+ });
1284
+ const startMs = Date.now();
1285
+ const execCmd = command[0] ?? "";
1286
+ const execArgs = command.slice(1);
1287
+ process.env.AGENTFLOW_TRACE_ID = traceId;
1288
+ process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
1289
+ const result = (0, import_node_child_process.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
1290
+ delete process.env.AGENTFLOW_TRACE_ID;
1291
+ delete process.env.AGENTFLOW_PARENT_SPAN_ID;
1292
+ const exitCode = result.status ?? 1;
1293
+ const duration = (Date.now() - startMs) / 1e3;
1294
+ const stateChanges = [];
1295
+ for (const dir of watchDirs) {
1296
+ const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
1297
+ const after = snapshotDir(dir, patterns);
1298
+ for (const [filePath, mtime] of after) {
1299
+ const prevMtime = before.get(filePath);
1300
+ if (prevMtime === void 0 || mtime > prevMtime) {
1301
+ stateChanges.push(filePath);
1302
+ }
1303
+ }
1304
+ }
1305
+ orchestrator.updateState(monitorId, { stateChanges });
1306
+ orchestrator.endNode(monitorId);
1307
+ if (exitCode === 0) {
1308
+ orchestrator.endNode(dispatchId);
1309
+ } else {
1310
+ orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
1311
+ }
1312
+ orchestrator.updateState(rootId, {
1313
+ exitCode,
1314
+ duration,
1315
+ stateChanges
1316
+ });
1317
+ if (exitCode === 0) {
1318
+ orchestrator.endNode(rootId);
1319
+ } else {
1320
+ orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
1321
+ }
1322
+ const orchestratorGraph = orchestrator.build();
1323
+ const allGraphs = [orchestratorGraph];
1324
+ for (const filePath of stateChanges) {
1325
+ const childAgentId = agentIdFromFilename(filePath);
1326
+ const childBuilder = createGraphBuilder({
1327
+ agentId: childAgentId,
1328
+ trigger: "state-change",
1329
+ traceId,
1330
+ parentSpanId: spanId
1331
+ });
1332
+ const childRootId = childBuilder.startNode({
1333
+ type: "agent",
1334
+ name: childAgentId
1335
+ });
1336
+ childBuilder.updateState(childRootId, {
1337
+ stateFile: filePath,
1338
+ detectedBy: "runner-state-monitor"
1339
+ });
1340
+ childBuilder.endNode(childRootId);
1341
+ allGraphs.push(childBuilder.build());
1342
+ }
1343
+ if (!(0, import_node_fs2.existsSync)(resolvedTracesDir)) {
1344
+ (0, import_node_fs2.mkdirSync)(resolvedTracesDir, { recursive: true });
1345
+ }
1346
+ const ts = fileTimestamp();
1347
+ const tracePaths = [];
1348
+ for (const graph of allGraphs) {
1349
+ const filename = `${graph.agentId}-${ts}.json`;
1350
+ const outPath = (0, import_node_path2.join)(resolvedTracesDir, filename);
1351
+ (0, import_node_fs2.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
1352
+ tracePaths.push(outPath);
1353
+ }
1354
+ if (tracePaths.length > 0) {
1355
+ console.log(`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`);
1356
+ }
1357
+ return {
1358
+ exitCode,
1359
+ traceId,
1360
+ spanId,
1361
+ tracePaths,
1362
+ stateChanges,
1363
+ duration
1364
+ };
1365
+ }
1366
+
1367
+ // src/trace-cli.ts
1368
+ var import_path2 = require("path");
1369
+
1370
+ // src/trace-store.ts
1371
+ var import_promises = require("fs/promises");
1372
+ var import_path = require("path");
1373
+ init_loader();
1374
+ function createTraceStore(dir) {
1375
+ async function ensureDir() {
1376
+ await (0, import_promises.mkdir)(dir, { recursive: true });
1377
+ }
1378
+ async function loadAll() {
1379
+ await ensureDir();
1380
+ let files;
1381
+ try {
1382
+ files = await (0, import_promises.readdir)(dir);
1383
+ } catch {
1384
+ return [];
1385
+ }
1386
+ const graphs = [];
1387
+ for (const file of files) {
1388
+ if (!file.endsWith(".json")) continue;
1389
+ try {
1390
+ const content = await (0, import_promises.readFile)((0, import_path.join)(dir, file), "utf-8");
1391
+ const graph = loadGraph(content);
1392
+ graphs.push(graph);
1393
+ } catch {
1394
+ }
1395
+ }
1396
+ return graphs;
1397
+ }
1398
+ return {
1399
+ async save(graph) {
1400
+ await ensureDir();
1401
+ const json = graphToJson(graph);
1402
+ const filePath = (0, import_path.join)(dir, `${graph.id}.json`);
1403
+ await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
1404
+ return filePath;
1405
+ },
1406
+ async get(graphId) {
1407
+ await ensureDir();
1408
+ const filePath = (0, import_path.join)(dir, `${graphId}.json`);
1409
+ try {
1410
+ const content = await (0, import_promises.readFile)(filePath, "utf-8");
1411
+ return loadGraph(content);
1412
+ } catch {
1413
+ }
1414
+ const all = await loadAll();
1415
+ return all.find((g) => g.id === graphId) ?? null;
1416
+ },
1417
+ async list(opts) {
1418
+ let graphs = await loadAll();
1419
+ if (opts?.status) {
1420
+ graphs = graphs.filter((g) => g.status === opts.status);
1421
+ }
1422
+ graphs.sort((a, b) => b.startTime - a.startTime);
1423
+ if (opts?.limit && opts.limit > 0) {
1424
+ graphs = graphs.slice(0, opts.limit);
1425
+ }
1426
+ return graphs;
1427
+ },
1428
+ async getStuckSpans() {
1429
+ const graphs = await loadAll();
1430
+ const stuck = [];
1431
+ for (const graph of graphs) {
1432
+ for (const node of graph.nodes.values()) {
1433
+ if (node.status === "running" || node.status === "hung" || node.status === "timeout") {
1434
+ stuck.push(node);
1435
+ }
1436
+ }
1437
+ }
1438
+ return stuck;
1439
+ },
1440
+ async getReasoningLoops(threshold = 25) {
1441
+ const graphs = await loadAll();
1442
+ const results = [];
1443
+ for (const graph of graphs) {
1444
+ const loops = findLoopsInGraph(graph, threshold);
1445
+ if (loops.length > 0) {
1446
+ results.push({ graphId: graph.id, nodes: loops });
1447
+ }
1448
+ }
1449
+ return results;
1450
+ }
1451
+ };
1452
+ }
1453
+ function findLoopsInGraph(graph, threshold) {
1454
+ const loopNodes = [];
1455
+ function walk(nodeId, consecutiveCount, consecutiveType) {
1456
+ const node = graph.nodes.get(nodeId);
1457
+ if (!node) return;
1458
+ const newCount = node.type === consecutiveType ? consecutiveCount + 1 : 1;
1459
+ if (newCount > threshold) {
1460
+ loopNodes.push(node);
1461
+ }
1462
+ for (const childId of node.children) {
1463
+ walk(childId, newCount, node.type);
1464
+ }
1465
+ }
1466
+ walk(graph.rootNodeId, 0, null);
1467
+ return loopNodes;
1468
+ }
1469
+
1470
+ // src/visualize.ts
1471
+ var STATUS_ICONS = {
1472
+ completed: "\u2713",
1473
+ failed: "\u2717",
1474
+ running: "\u231B",
1475
+ hung: "\u231B",
1476
+ timeout: "\u231B"
1477
+ };
1478
+ function formatDuration(ms) {
1479
+ if (ms < 1e3) return `${ms}ms`;
1480
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
1481
+ if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
1482
+ return `${(ms / 36e5).toFixed(1)}h`;
1483
+ }
1484
+ function nodeDuration(node, graphEndTime) {
1485
+ const end = node.endTime ?? graphEndTime;
1486
+ return formatDuration(end - node.startTime);
1487
+ }
1488
+ function getGenAiInfo(node) {
1489
+ const parts = [];
1490
+ const meta = node.metadata;
1491
+ if (meta["gen_ai.request.model"]) {
1492
+ parts.push(String(meta["gen_ai.request.model"]));
1493
+ }
1494
+ const tokens = meta["gen_ai.usage.prompt_tokens"] ?? meta["gen_ai.usage.completion_tokens"];
1495
+ if (tokens !== void 0) {
1496
+ const prompt = meta["gen_ai.usage.prompt_tokens"] ?? 0;
1497
+ const completion = meta["gen_ai.usage.completion_tokens"] ?? 0;
1498
+ if (prompt || completion) {
1499
+ parts.push(`${prompt + completion} tok`);
1500
+ }
1501
+ }
1502
+ return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
1503
+ }
1504
+ function hasViolation(node, graph) {
1505
+ return graph.events.some(
1506
+ (e) => e.nodeId === node.id && e.eventType === "custom" && e.data.guardViolation !== void 0
1507
+ );
1508
+ }
1509
+ function toAsciiTree(graph) {
1510
+ if (graph.nodes.size === 0) return "(empty graph)";
1511
+ const now = Date.now();
1512
+ const endTime = graph.endTime ?? now;
1513
+ const lines = [];
1514
+ function renderNode(nodeId, prefix, isLast, isRoot) {
1515
+ const node = graph.nodes.get(nodeId);
1516
+ if (!node) return;
1517
+ const icon = STATUS_ICONS[node.status];
1518
+ const duration = nodeDuration(node, endTime);
1519
+ const genAi = getGenAiInfo(node);
1520
+ const violation = hasViolation(node, graph) ? " \u26A0" : "";
1521
+ const errorInfo = node.status === "failed" && node.metadata.error ? ` \u2014 ${node.metadata.error}` : "";
1522
+ const timeoutInfo = node.status === "timeout" ? " [TIMEOUT]" : "";
1523
+ const hungInfo = node.status === "hung" ? " [HUNG]" : "";
1524
+ const connector = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1525
+ const line = `${prefix}${connector}${icon} ${node.name} (${node.type}) ${duration}${genAi}${violation}${timeoutInfo}${hungInfo}${errorInfo}`;
1526
+ lines.push(line);
1527
+ const children = getChildren(graph, nodeId);
1528
+ const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
1529
+ for (let i = 0; i < children.length; i++) {
1530
+ renderNode(children[i].id, childPrefix, i === children.length - 1, false);
1531
+ }
1532
+ }
1533
+ renderNode(graph.rootNodeId, "", true, true);
1534
+ return lines.join("\n");
1535
+ }
1536
+ function toTimeline(graph) {
1537
+ if (graph.nodes.size === 0) return "(empty graph)";
1538
+ const now = Date.now();
1539
+ const graphStart = graph.startTime;
1540
+ const graphEnd = graph.endTime ?? now;
1541
+ const totalDuration = graphEnd - graphStart;
1542
+ if (totalDuration <= 0) return "(zero duration)";
1543
+ const barWidth = 60;
1544
+ const lines = [];
1545
+ const scaleLabels = [];
1546
+ const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
1547
+ for (let i = 0; i <= tickCount; i++) {
1548
+ const t = totalDuration * i / tickCount;
1549
+ scaleLabels.push(formatDuration(t));
1550
+ }
1551
+ let header = "";
1552
+ for (let i = 0; i < scaleLabels.length; i++) {
1553
+ const pos = Math.round(barWidth * i / tickCount);
1554
+ while (header.length < pos) header += " ";
1555
+ header += scaleLabels[i];
1556
+ }
1557
+ lines.push(header);
1558
+ let tickLine = "";
1559
+ for (let i = 0; i < barWidth; i++) {
1560
+ const tickPos = tickCount > 0 ? i * tickCount / barWidth : 0;
1561
+ if (Number.isInteger(Math.round(tickPos * 100) / 100) && Math.abs(tickPos - Math.round(tickPos)) < 0.01) {
1562
+ tickLine += "\u253C";
1563
+ } else {
1564
+ tickLine += "\u2500";
1565
+ }
1566
+ }
1567
+ lines.push(tickLine);
1568
+ const orderedNodes = [];
1569
+ function collectNodes(nodeId) {
1570
+ const node = graph.nodes.get(nodeId);
1571
+ if (!node) return;
1572
+ orderedNodes.push(node);
1573
+ const children = getChildren(graph, nodeId);
1574
+ for (const child of children) {
1575
+ collectNodes(child.id);
1576
+ }
1577
+ }
1578
+ collectNodes(graph.rootNodeId);
1579
+ for (const node of orderedNodes) {
1580
+ const nodeStart = node.startTime - graphStart;
1581
+ const nodeEnd = (node.endTime ?? now) - graphStart;
1582
+ const startCol = Math.round(nodeStart / totalDuration * barWidth);
1583
+ const endCol = Math.max(startCol + 1, Math.round(nodeEnd / totalDuration * barWidth));
1584
+ let bar = "";
1585
+ for (let i = 0; i < barWidth; i++) {
1586
+ if (i >= startCol && i < endCol) {
1587
+ bar += "\u2588";
1588
+ } else {
1589
+ bar += " ";
1590
+ }
1591
+ }
1592
+ const icon = STATUS_ICONS[node.status];
1593
+ const duration = nodeDuration(node, graphEnd);
1594
+ const violation = hasViolation(node, graph) ? " \u26A0" : "";
1595
+ lines.push(`${bar} ${icon} ${node.name} (${duration})${violation}`);
1596
+ }
1597
+ return lines.join("\n");
1598
+ }
1599
+
1600
+ // src/trace-cli.ts
1601
+ function getTracesDir(argv) {
1602
+ const idx = argv.indexOf("--traces-dir");
1603
+ if (idx !== -1 && argv[idx + 1]) {
1604
+ return (0, import_path2.resolve)(argv[idx + 1]);
1605
+ }
1606
+ return (0, import_path2.resolve)("./traces");
1607
+ }
1608
+ function getFlag(argv, name) {
1609
+ const idx = argv.indexOf(name);
1610
+ if (idx !== -1 && argv[idx + 1]) {
1611
+ return argv[idx + 1];
1612
+ }
1613
+ return void 0;
1614
+ }
1615
+ function printTraceHelp() {
1616
+ console.log(
1617
+ `
1618
+ AgentFlow Trace \u2014 inspect saved execution traces.
1619
+
1620
+ Usage:
1621
+ agentflow trace <command> [options]
1622
+
1623
+ Commands:
1624
+ list [--status <status>] [--limit <n>] List saved traces
1625
+ show <graph-id> Show trace as ASCII tree
1626
+ timeline <graph-id> Show trace as timeline waterfall
1627
+ stuck Show all stuck/hung/timeout spans
1628
+ loops [--threshold <n>] Detect reasoning loops
1629
+
1630
+ Options:
1631
+ --traces-dir <path> Directory containing trace files (default: ./traces)
1632
+
1633
+ Examples:
1634
+ agentflow trace list --status failed --limit 10
1635
+ agentflow trace show abc-123
1636
+ agentflow trace timeline abc-123
1637
+ agentflow trace stuck
1638
+ agentflow trace loops --threshold 10
1639
+ `.trim()
1640
+ );
1641
+ }
1642
+ async function traceList(argv) {
1643
+ const dir = getTracesDir(argv);
1644
+ const store = createTraceStore(dir);
1645
+ const status = getFlag(argv, "--status");
1646
+ const limitStr = getFlag(argv, "--limit");
1647
+ const limit = limitStr ? Number.parseInt(limitStr, 10) : void 0;
1648
+ const graphs = await store.list({ status, limit });
1649
+ if (graphs.length === 0) {
1650
+ console.log("No traces found.");
1651
+ return;
1652
+ }
1653
+ console.log(`Found ${graphs.length} trace(s) in ${dir}:
1654
+ `);
1655
+ for (const g of graphs) {
1656
+ const duration = g.endTime ? `${((g.endTime - g.startTime) / 1e3).toFixed(1)}s` : "running";
1657
+ const date = new Date(g.startTime).toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
1658
+ const icon = g.status === "completed" ? "\u2713" : g.status === "failed" ? "\u2717" : "\u231B";
1659
+ console.log(
1660
+ ` ${icon} ${g.id} ${g.status.padEnd(10)} ${duration.padEnd(8)} ${date} ${g.agentId}`
1661
+ );
1662
+ }
1663
+ }
1664
+ async function traceShow(argv) {
1665
+ const dir = getTracesDir(argv);
1666
+ const store = createTraceStore(dir);
1667
+ const showIdx = argv.indexOf("show");
1668
+ const graphId = showIdx !== -1 ? argv[showIdx + 1] : void 0;
1669
+ if (!graphId || graphId.startsWith("--")) {
1670
+ console.error("Usage: agentflow trace show <graph-id>");
1671
+ process.exit(1);
1672
+ }
1673
+ let graph = await store.get(graphId);
1674
+ if (!graph) {
1675
+ const { readFile: readFile2 } = await import("fs/promises");
1676
+ const { join: join5 } = await import("path");
1677
+ const fname = graphId.endsWith(".json") ? graphId : `${graphId}.json`;
1678
+ try {
1679
+ const { loadGraph: loadGraph2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
1680
+ const content = await readFile2(join5(dir, fname), "utf-8");
1681
+ graph = loadGraph2(content);
1682
+ } catch {
1683
+ }
1684
+ }
1685
+ if (!graph) {
1686
+ console.error(`Trace "${graphId}" not found in ${dir}`);
1687
+ process.exit(1);
1688
+ }
1689
+ console.log(`Trace: ${graph.id} (${graph.status})
1690
+ `);
1691
+ console.log(toAsciiTree(graph));
1692
+ }
1693
+ async function traceTimeline(argv) {
1694
+ const dir = getTracesDir(argv);
1695
+ const store = createTraceStore(dir);
1696
+ const timelineIdx = argv.indexOf("timeline");
1697
+ const graphId = timelineIdx !== -1 ? argv[timelineIdx + 1] : void 0;
1698
+ if (!graphId || graphId.startsWith("--")) {
1699
+ console.error("Usage: agentflow trace timeline <graph-id>");
1700
+ process.exit(1);
1701
+ }
1702
+ let graph = await store.get(graphId);
1703
+ if (!graph) {
1704
+ const { readFile: readFile2 } = await import("fs/promises");
1705
+ const { join: join5 } = await import("path");
1706
+ const fname = graphId.endsWith(".json") ? graphId : `${graphId}.json`;
1707
+ try {
1708
+ const { loadGraph: loadGraph2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
1709
+ const content = await readFile2(join5(dir, fname), "utf-8");
1710
+ graph = loadGraph2(content);
1711
+ } catch {
1712
+ }
1713
+ }
1714
+ if (!graph) {
1715
+ console.error(`Trace "${graphId}" not found in ${dir}`);
1716
+ process.exit(1);
1717
+ }
1718
+ console.log(`Trace: ${graph.id} (${graph.status})
1719
+ `);
1720
+ console.log(toTimeline(graph));
1721
+ }
1722
+ async function traceStuck(argv) {
1723
+ const dir = getTracesDir(argv);
1724
+ const store = createTraceStore(dir);
1725
+ const stuck = await store.getStuckSpans();
1726
+ if (stuck.length === 0) {
1727
+ console.log("No stuck spans found.");
1728
+ return;
1729
+ }
1730
+ console.log(`Found ${stuck.length} stuck span(s):
1731
+ `);
1732
+ for (const node of stuck) {
1733
+ const elapsed = Date.now() - node.startTime;
1734
+ const icon = node.status === "timeout" ? "\u231B" : node.status === "hung" ? "\u231B" : "\u231B";
1735
+ console.log(
1736
+ ` ${icon} ${node.id} ${node.type.padEnd(10)} ${node.name.padEnd(20)} ${node.status.padEnd(8)} ${(elapsed / 1e3).toFixed(0)}s`
1737
+ );
1738
+ }
1739
+ }
1740
+ async function traceLoops(argv) {
1741
+ const dir = getTracesDir(argv);
1742
+ const store = createTraceStore(dir);
1743
+ const thresholdStr = getFlag(argv, "--threshold");
1744
+ const threshold = thresholdStr ? Number.parseInt(thresholdStr, 10) : void 0;
1745
+ const loops = await store.getReasoningLoops(threshold);
1746
+ if (loops.length === 0) {
1747
+ console.log("No reasoning loops detected.");
1748
+ return;
1749
+ }
1750
+ console.log(`Found reasoning loops in ${loops.length} trace(s):
1751
+ `);
1752
+ for (const { graphId, nodes } of loops) {
1753
+ console.log(` Graph: ${graphId}`);
1754
+ for (const node of nodes) {
1755
+ console.log(` - ${node.id} (${node.type}: ${node.name})`);
1756
+ }
1757
+ console.log("");
1758
+ }
1759
+ }
1760
+ async function handleTrace(argv) {
1761
+ if (argv.includes("--help") || argv.includes("-h")) {
1762
+ printTraceHelp();
1763
+ return;
1764
+ }
1765
+ const traceIdx = argv.indexOf("trace");
1766
+ const subcommand = traceIdx !== -1 ? argv[traceIdx + 1] : void 0;
1767
+ switch (subcommand) {
1768
+ case "list":
1769
+ await traceList(argv);
1770
+ break;
1771
+ case "show":
1772
+ await traceShow(argv);
1773
+ break;
1774
+ case "timeline":
1775
+ await traceTimeline(argv);
1776
+ break;
1777
+ case "stuck":
1778
+ await traceStuck(argv);
1779
+ break;
1780
+ case "loops":
1781
+ await traceLoops(argv);
1782
+ break;
1783
+ default:
1784
+ printTraceHelp();
1785
+ break;
1786
+ }
1218
1787
  }
1219
1788
 
1220
1789
  // src/watch.ts
1221
1790
  var import_node_fs4 = require("fs");
1222
- var import_node_path3 = require("path");
1223
1791
  var import_node_os = require("os");
1792
+ var import_node_path3 = require("path");
1793
+
1794
+ // src/watch-alerts.ts
1795
+ var import_node_child_process2 = require("child_process");
1796
+ var import_node_http = require("http");
1797
+ var import_node_https = require("https");
1798
+ function formatAlertMessage(payload) {
1799
+ const time = new Date(payload.timestamp).toISOString();
1800
+ const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1801
+ return [
1802
+ `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1803
+ ` Status: ${arrow}`,
1804
+ payload.detail ? ` Detail: ${payload.detail}` : null,
1805
+ ` File: ${payload.file}`,
1806
+ ` Time: ${time}`
1807
+ ].filter(Boolean).join("\n");
1808
+ }
1809
+ function formatTelegram(payload) {
1810
+ const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1811
+ const time = new Date(payload.timestamp).toLocaleTimeString();
1812
+ return [
1813
+ `${icon} *AgentFlow Alert*`,
1814
+ `*${payload.condition}*: \`${payload.agentId}\``,
1815
+ `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1816
+ payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1817
+ `Time: ${time}`
1818
+ ].filter(Boolean).join("\n");
1819
+ }
1820
+ async function sendAlert(payload, channel) {
1821
+ try {
1822
+ switch (channel.type) {
1823
+ case "stdout":
1824
+ sendStdout(payload);
1825
+ break;
1826
+ case "telegram":
1827
+ await sendTelegram(payload, channel.botToken, channel.chatId);
1828
+ break;
1829
+ case "webhook":
1830
+ await sendWebhook(payload, channel.url);
1831
+ break;
1832
+ case "command":
1833
+ await sendCommand(payload, channel.cmd);
1834
+ break;
1835
+ }
1836
+ } catch (err) {
1837
+ const msg = err instanceof Error ? err.message : String(err);
1838
+ console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1839
+ }
1840
+ }
1841
+ function sendStdout(payload) {
1842
+ console.log(formatAlertMessage(payload));
1843
+ }
1844
+ function sendTelegram(payload, botToken, chatId) {
1845
+ const body = JSON.stringify({
1846
+ chat_id: chatId,
1847
+ text: formatTelegram(payload),
1848
+ parse_mode: "Markdown"
1849
+ });
1850
+ return new Promise((resolve6, reject) => {
1851
+ const req = (0, import_node_https.request)(
1852
+ `https://api.telegram.org/bot${botToken}/sendMessage`,
1853
+ {
1854
+ method: "POST",
1855
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
1856
+ },
1857
+ (res) => {
1858
+ res.resume();
1859
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve6();
1860
+ else reject(new Error(`Telegram API returned ${res.statusCode}`));
1861
+ }
1862
+ );
1863
+ req.on("error", reject);
1864
+ req.write(body);
1865
+ req.end();
1866
+ });
1867
+ }
1868
+ function sendWebhook(payload, url) {
1869
+ const body = JSON.stringify(payload);
1870
+ const isHttps = url.startsWith("https");
1871
+ const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1872
+ return new Promise((resolve6, reject) => {
1873
+ const req = doRequest(
1874
+ url,
1875
+ {
1876
+ method: "POST",
1877
+ headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
1878
+ },
1879
+ (res) => {
1880
+ res.resume();
1881
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve6();
1882
+ else reject(new Error(`Webhook returned ${res.statusCode}`));
1883
+ }
1884
+ );
1885
+ req.on("error", reject);
1886
+ req.setTimeout(1e4, () => {
1887
+ req.destroy(new Error("Webhook timeout"));
1888
+ });
1889
+ req.write(body);
1890
+ req.end();
1891
+ });
1892
+ }
1893
+ function sendCommand(payload, cmd) {
1894
+ return new Promise((resolve6, reject) => {
1895
+ const env = {
1896
+ ...process.env,
1897
+ AGENTFLOW_ALERT_AGENT: payload.agentId,
1898
+ AGENTFLOW_ALERT_CONDITION: payload.condition,
1899
+ AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1900
+ AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1901
+ AGENTFLOW_ALERT_DETAIL: payload.detail,
1902
+ AGENTFLOW_ALERT_FILE: payload.file,
1903
+ AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1904
+ };
1905
+ (0, import_node_child_process2.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1906
+ if (err) reject(err);
1907
+ else resolve6();
1908
+ });
1909
+ });
1910
+ }
1224
1911
 
1225
1912
  // src/watch-state.ts
1226
1913
  var import_node_fs3 = require("fs");
@@ -1286,7 +1973,9 @@ function detectTransitions(previous, currentRecords, config, now) {
1286
1973
  const hasError = config.alertConditions.some((c) => c.type === "error");
1287
1974
  const hasRecovery = config.alertConditions.some((c) => c.type === "recovery");
1288
1975
  const staleConditions = config.alertConditions.filter((c) => c.type === "stale");
1289
- const consecutiveConditions = config.alertConditions.filter((c) => c.type === "consecutive-errors");
1976
+ const consecutiveConditions = config.alertConditions.filter(
1977
+ (c) => c.type === "consecutive-errors"
1978
+ );
1290
1979
  const byAgent = /* @__PURE__ */ new Map();
1291
1980
  for (const r of currentRecords) {
1292
1981
  const existing = byAgent.get(r.id);
@@ -1310,14 +1999,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1310
1999
  for (const cond of consecutiveConditions) {
1311
2000
  if (newConsec === cond.threshold) {
1312
2001
  if (canAlert(prev, `consecutive-errors:${cond.threshold}`, config.cooldownMs, now)) {
1313
- alerts.push(makePayload(
1314
- agentId,
1315
- `consecutive-errors (${cond.threshold})`,
1316
- prevStatus,
1317
- currStatus,
1318
- { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
1319
- config.dirs
1320
- ));
2002
+ alerts.push(
2003
+ makePayload(
2004
+ agentId,
2005
+ `consecutive-errors (${cond.threshold})`,
2006
+ prevStatus,
2007
+ currStatus,
2008
+ { ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
2009
+ config.dirs
2010
+ )
2011
+ );
1321
2012
  }
1322
2013
  }
1323
2014
  }
@@ -1326,14 +2017,16 @@ function detectTransitions(previous, currentRecords, config, now) {
1326
2017
  if (sinceActive > cond.durationMs && record.lastActive > 0) {
1327
2018
  if (canAlert(prev, "stale", config.cooldownMs, now)) {
1328
2019
  const mins = Math.floor(sinceActive / 6e4);
1329
- alerts.push(makePayload(
1330
- agentId,
1331
- "stale",
1332
- prevStatus,
1333
- currStatus,
1334
- { ...record, detail: `No update for ${mins}m. ${record.detail}` },
1335
- config.dirs
1336
- ));
2020
+ alerts.push(
2021
+ makePayload(
2022
+ agentId,
2023
+ "stale",
2024
+ prevStatus,
2025
+ currStatus,
2026
+ { ...record, detail: `No update for ${mins}m. ${record.detail}` },
2027
+ config.dirs
2028
+ )
2029
+ );
1337
2030
  }
1338
2031
  }
1339
2032
  }
@@ -1346,14 +2039,19 @@ function detectTransitions(previous, currentRecords, config, now) {
1346
2039
  if (canAlert(prev, "stale-auto", config.cooldownMs, now)) {
1347
2040
  const mins = Math.floor(sinceActive / 6e4);
1348
2041
  const expectedMins = Math.floor(expectedInterval / 6e4);
1349
- alerts.push(makePayload(
1350
- agentId,
1351
- "stale (auto)",
1352
- prevStatus,
1353
- currStatus,
1354
- { ...record, detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}` },
1355
- config.dirs
1356
- ));
2042
+ alerts.push(
2043
+ makePayload(
2044
+ agentId,
2045
+ "stale (auto)",
2046
+ prevStatus,
2047
+ currStatus,
2048
+ {
2049
+ ...record,
2050
+ detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}`
2051
+ },
2052
+ config.dirs
2053
+ )
2054
+ );
1357
2055
  }
1358
2056
  }
1359
2057
  }
@@ -1412,118 +2110,6 @@ function makePayload(agentId, condition, previousStatus, currentStatus, record,
1412
2110
  };
1413
2111
  }
1414
2112
 
1415
- // src/watch-alerts.ts
1416
- var import_node_https = require("https");
1417
- var import_node_http = require("http");
1418
- var import_node_child_process2 = require("child_process");
1419
- function formatAlertMessage(payload) {
1420
- const time = new Date(payload.timestamp).toISOString();
1421
- const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
1422
- return [
1423
- `[ALERT] ${payload.condition}: "${payload.agentId}"`,
1424
- ` Status: ${arrow}`,
1425
- payload.detail ? ` Detail: ${payload.detail}` : null,
1426
- ` File: ${payload.file}`,
1427
- ` Time: ${time}`
1428
- ].filter(Boolean).join("\n");
1429
- }
1430
- function formatTelegram(payload) {
1431
- const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
1432
- const time = new Date(payload.timestamp).toLocaleTimeString();
1433
- return [
1434
- `${icon} *AgentFlow Alert*`,
1435
- `*${payload.condition}*: \`${payload.agentId}\``,
1436
- `Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
1437
- payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
1438
- `Time: ${time}`
1439
- ].filter(Boolean).join("\n");
1440
- }
1441
- async function sendAlert(payload, channel) {
1442
- try {
1443
- switch (channel.type) {
1444
- case "stdout":
1445
- sendStdout(payload);
1446
- break;
1447
- case "telegram":
1448
- await sendTelegram(payload, channel.botToken, channel.chatId);
1449
- break;
1450
- case "webhook":
1451
- await sendWebhook(payload, channel.url);
1452
- break;
1453
- case "command":
1454
- await sendCommand(payload, channel.cmd);
1455
- break;
1456
- }
1457
- } catch (err) {
1458
- const msg = err instanceof Error ? err.message : String(err);
1459
- console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
1460
- }
1461
- }
1462
- function sendStdout(payload) {
1463
- console.log(formatAlertMessage(payload));
1464
- }
1465
- function sendTelegram(payload, botToken, chatId) {
1466
- const body = JSON.stringify({
1467
- chat_id: chatId,
1468
- text: formatTelegram(payload),
1469
- parse_mode: "Markdown"
1470
- });
1471
- return new Promise((resolve5, reject) => {
1472
- const req = (0, import_node_https.request)(
1473
- `https://api.telegram.org/bot${botToken}/sendMessage`,
1474
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1475
- (res) => {
1476
- res.resume();
1477
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve5();
1478
- else reject(new Error(`Telegram API returned ${res.statusCode}`));
1479
- }
1480
- );
1481
- req.on("error", reject);
1482
- req.write(body);
1483
- req.end();
1484
- });
1485
- }
1486
- function sendWebhook(payload, url) {
1487
- const body = JSON.stringify(payload);
1488
- const isHttps = url.startsWith("https");
1489
- const doRequest = isHttps ? import_node_https.request : import_node_http.request;
1490
- return new Promise((resolve5, reject) => {
1491
- const req = doRequest(
1492
- url,
1493
- { method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } },
1494
- (res) => {
1495
- res.resume();
1496
- if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve5();
1497
- else reject(new Error(`Webhook returned ${res.statusCode}`));
1498
- }
1499
- );
1500
- req.on("error", reject);
1501
- req.setTimeout(1e4, () => {
1502
- req.destroy(new Error("Webhook timeout"));
1503
- });
1504
- req.write(body);
1505
- req.end();
1506
- });
1507
- }
1508
- function sendCommand(payload, cmd) {
1509
- return new Promise((resolve5, reject) => {
1510
- const env = {
1511
- ...process.env,
1512
- AGENTFLOW_ALERT_AGENT: payload.agentId,
1513
- AGENTFLOW_ALERT_CONDITION: payload.condition,
1514
- AGENTFLOW_ALERT_STATUS: payload.currentStatus,
1515
- AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
1516
- AGENTFLOW_ALERT_DETAIL: payload.detail,
1517
- AGENTFLOW_ALERT_FILE: payload.file,
1518
- AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
1519
- };
1520
- (0, import_node_child_process2.exec)(cmd, { env, timeout: 3e4 }, (err) => {
1521
- if (err) reject(err);
1522
- else resolve5();
1523
- });
1524
- });
1525
- }
1526
-
1527
2113
  // src/watch.ts
1528
2114
  function parseWatchArgs(argv) {
1529
2115
  const dirs = [];
@@ -1565,7 +2151,9 @@ function parseWatchArgs(argv) {
1565
2151
  if (botToken && chatId) {
1566
2152
  notifyChannels.push({ type: "telegram", botToken, chatId });
1567
2153
  } else {
1568
- console.error("Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars");
2154
+ console.error(
2155
+ "Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars"
2156
+ );
1569
2157
  }
1570
2158
  } else if (val.startsWith("webhook:")) {
1571
2159
  notifyChannels.push({ type: "webhook", url: val.slice(8) });
@@ -1617,7 +2205,8 @@ function parseWatchArgs(argv) {
1617
2205
  };
1618
2206
  }
1619
2207
  function printWatchUsage() {
1620
- console.log(`
2208
+ console.log(
2209
+ `
1621
2210
  AgentFlow Watch \u2014 headless alert system for agent infrastructure.
1622
2211
 
1623
2212
  Polls directories for JSON/JSONL files, detects failures and stale
@@ -1660,7 +2249,8 @@ Examples:
1660
2249
  agentflow watch ./data ./cron --notify telegram --poll 60
1661
2250
  agentflow watch ./traces --notify webhook:https://hooks.slack.com/... --alert-on consecutive-errors:3
1662
2251
  agentflow watch ./data --notify "command:curl -X POST https://my-pagerduty/alert"
1663
- `.trim());
2252
+ `.trim()
2253
+ );
1664
2254
  }
1665
2255
  function startWatch(argv) {
1666
2256
  const config = parseWatchArgs(argv);
@@ -1689,7 +2279,9 @@ agentflow watch started`);
1689
2279
  console.log(` Directories: ${valid.join(", ")}`);
1690
2280
  console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
1691
2281
  console.log(` Alert on: ${condLabels.join(", ")}`);
1692
- console.log(` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`);
2282
+ console.log(
2283
+ ` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
2284
+ );
1693
2285
  console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
1694
2286
  console.log(` State: ${config.stateFilePath}`);
1695
2287
  console.log(` Hostname: ${(0, import_node_os.hostname)()}`);
@@ -1715,9 +2307,13 @@ agentflow watch started`);
1715
2307
  if (pollCount % 10 === 0) {
1716
2308
  const agentCount = Object.keys(state.agents).length;
1717
2309
  const errorCount = Object.values(state.agents).filter((a) => a.lastStatus === "error").length;
1718
- const runningCount = Object.values(state.agents).filter((a) => a.lastStatus === "running").length;
2310
+ const runningCount = Object.values(state.agents).filter(
2311
+ (a) => a.lastStatus === "running"
2312
+ ).length;
1719
2313
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1720
- console.log(`[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`);
2314
+ console.log(
2315
+ `[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`
2316
+ );
1721
2317
  }
1722
2318
  }
1723
2319
  poll();
@@ -1735,7 +2331,8 @@ agentflow watch started`);
1735
2331
 
1736
2332
  // src/cli.ts
1737
2333
  function printHelp() {
1738
- console.log(`
2334
+ console.log(
2335
+ `
1739
2336
  AgentFlow CLI \u2014 execution tracing and live monitoring for AI agent systems.
1740
2337
 
1741
2338
  Usage:
@@ -1745,6 +2342,7 @@ Commands:
1745
2342
  run [options] -- <cmd> Wrap a command with automatic execution tracing
1746
2343
  live [dir...] [options] Real-time terminal monitor (auto-detects any JSON/JSONL)
1747
2344
  watch [dir...] [options] Headless alert system \u2014 detects failures, sends notifications
2345
+ trace <command> [options] Inspect saved execution traces (list, show, timeline, stuck, loops)
1748
2346
 
1749
2347
  Run \`agentflow <command> --help\` for command-specific options.
1750
2348
 
@@ -1754,7 +2352,8 @@ Examples:
1754
2352
  agentflow live ./traces ./cron ./workers -R
1755
2353
  agentflow watch ./data --alert-on error --notify telegram
1756
2354
  agentflow watch ./data ./cron --alert-on stale:15m --notify webhook:https://...
1757
- `.trim());
2355
+ `.trim()
2356
+ );
1758
2357
  }
1759
2358
  function parseRunArgs(argv) {
1760
2359
  const result = {
@@ -1815,7 +2414,8 @@ function parseRunArgs(argv) {
1815
2414
  return result;
1816
2415
  }
1817
2416
  function printRunUsage() {
1818
- console.log(`
2417
+ console.log(
2418
+ `
1819
2419
  AgentFlow Run \u2014 wrap any command with automatic execution tracing.
1820
2420
 
1821
2421
  Usage:
@@ -1833,7 +2433,8 @@ Examples:
1833
2433
  agentflow run -- python -m myagent process
1834
2434
  agentflow run --watch-dir ./data -- python worker.py
1835
2435
  agentflow run --traces-dir ./my-traces --agent-id recon -- node agent.js
1836
- `.trim());
2436
+ `.trim()
2437
+ );
1837
2438
  }
1838
2439
  async function runCommand(argv) {
1839
2440
  if (argv.includes("--help") || argv.includes("-h")) {
@@ -1842,7 +2443,9 @@ async function runCommand(argv) {
1842
2443
  }
1843
2444
  const parsed = parseRunArgs(argv);
1844
2445
  if (parsed.command.length === 0) {
1845
- console.error("Error: No command specified. Use -- to separate agentflow flags from the command.");
2446
+ console.error(
2447
+ "Error: No command specified. Use -- to separate agentflow flags from the command."
2448
+ );
1846
2449
  console.error("Example: agentflow run -- python -m myagent process");
1847
2450
  process.exit(1);
1848
2451
  }
@@ -1866,15 +2469,17 @@ async function runCommand(argv) {
1866
2469
  try {
1867
2470
  const result = await runTraced(config);
1868
2471
  console.log("");
1869
- console.log(`\u2705 Command completed (exit code ${result.exitCode}, ${result.duration.toFixed(1)}s)`);
2472
+ console.log(
2473
+ `\u2705 Command completed (exit code ${result.exitCode}, ${result.duration.toFixed(1)}s)`
2474
+ );
1870
2475
  if (result.tracePaths.length > 0) {
1871
2476
  console.log("\u{1F4DD} Traces saved:");
1872
2477
  const orchPath = result.tracePaths[0];
1873
- const orchName = (0, import_path.basename)(orchPath, ".json").split("-")[0] ?? "orchestrator";
2478
+ const orchName = (0, import_path3.basename)(orchPath, ".json").split("-")[0] ?? "orchestrator";
1874
2479
  console.log(` ${orchName.padEnd(14)} \u2192 ${orchPath}`);
1875
2480
  for (let i = 1; i < result.tracePaths.length; i++) {
1876
2481
  const tPath = result.tracePaths[i];
1877
- const name = (0, import_path.basename)(tPath, ".json").replace(/-\d{4}-.*$/, "");
2482
+ const name = (0, import_path3.basename)(tPath, ".json").replace(/-\d{4}-.*$/, "");
1878
2483
  const isLast = i === result.tracePaths.length - 1;
1879
2484
  const prefix = isLast ? "\u2514\u2500" : "\u251C\u2500";
1880
2485
  console.log(` ${prefix} ${name.padEnd(12)} \u2192 ${tPath} (state changed)`);
@@ -1891,7 +2496,7 @@ async function runCommand(argv) {
1891
2496
  }
1892
2497
  async function main() {
1893
2498
  const argv = process.argv.slice(2);
1894
- const knownCommands = ["run", "live", "watch"];
2499
+ const knownCommands = ["run", "live", "watch", "trace"];
1895
2500
  if (argv.length === 0 || !knownCommands.includes(argv[0]) && (argv.includes("--help") || argv.includes("-h"))) {
1896
2501
  printHelp();
1897
2502
  process.exit(0);
@@ -1907,6 +2512,9 @@ async function main() {
1907
2512
  case "watch":
1908
2513
  startWatch(argv);
1909
2514
  break;
2515
+ case "trace":
2516
+ await handleTrace(argv);
2517
+ break;
1910
2518
  default:
1911
2519
  if (!subcommand?.startsWith("-")) {
1912
2520
  startLive(["live", ...argv]);