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/chunk-DY7YHFIB.js +56 -0
- package/dist/{chunk-M5CGDXAO.js → chunk-T2BYYGA4.js} +1644 -1305
- package/dist/cli.cjs +1227 -586
- package/dist/cli.js +217 -9
- package/dist/index.cjs +971 -418
- package/dist/index.d.cts +296 -132
- package/dist/index.d.ts +296 -132
- package/dist/index.js +173 -5
- package/dist/loader-LYRR6LMM.js +8 -0
- package/package.json +1 -1
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
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
768
|
+
for (const f of (0, import_node_fs.readdirSync)(d)) {
|
|
754
769
|
if (f.startsWith(".")) continue;
|
|
755
|
-
const fp = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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))
|
|
798
|
-
|
|
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 [
|
|
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 [
|
|
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({
|
|
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
|
-
|
|
901
|
-
|
|
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({
|
|
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,
|
|
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 [
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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 =
|
|
1112
|
-
const totFail =
|
|
1113
|
-
const totRunning =
|
|
1114
|
-
const uniqueAgents = new Set(
|
|
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
|
|
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)
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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(
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
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(
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
});
|