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/cli.cjs
CHANGED
|
@@ -1,241 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
for (
|
|
19
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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,31 @@ 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/
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
var import_node_child_process = require("child_process");
|
|
102
|
+
|
|
103
|
+
// src/graph-query.ts
|
|
104
|
+
function getChildren(graph, nodeId) {
|
|
105
|
+
const node = graph.nodes.get(nodeId);
|
|
106
|
+
if (!node) return [];
|
|
107
|
+
const result = [];
|
|
108
|
+
for (const childId of node.children) {
|
|
109
|
+
const child = graph.nodes.get(childId);
|
|
110
|
+
if (child) result.push(child);
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
294
113
|
}
|
|
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
114
|
function getFailures(graph) {
|
|
444
115
|
const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
|
|
445
116
|
return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
|
|
@@ -573,6 +244,7 @@ function getTraceTree(trace) {
|
|
|
573
244
|
}
|
|
574
245
|
|
|
575
246
|
// src/live.ts
|
|
247
|
+
init_loader();
|
|
576
248
|
var C = {
|
|
577
249
|
reset: "\x1B[0m",
|
|
578
250
|
bold: "\x1B[1m",
|
|
@@ -605,17 +277,18 @@ function parseArgs(argv) {
|
|
|
605
277
|
config.recursive = true;
|
|
606
278
|
i++;
|
|
607
279
|
} else if (!arg.startsWith("-")) {
|
|
608
|
-
config.dirs.push((0,
|
|
280
|
+
config.dirs.push((0, import_node_path.resolve)(arg));
|
|
609
281
|
i++;
|
|
610
282
|
} else {
|
|
611
283
|
i++;
|
|
612
284
|
}
|
|
613
285
|
}
|
|
614
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
286
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path.resolve)("."));
|
|
615
287
|
return config;
|
|
616
288
|
}
|
|
617
289
|
function printUsage() {
|
|
618
|
-
console.log(
|
|
290
|
+
console.log(
|
|
291
|
+
`
|
|
619
292
|
AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
|
|
620
293
|
|
|
621
294
|
Auto-detects agent traces, state files, job schedulers, and session logs
|
|
@@ -636,20 +309,21 @@ Examples:
|
|
|
636
309
|
agentflow live ./data
|
|
637
310
|
agentflow live ./traces ./cron ./workers --refresh 5
|
|
638
311
|
agentflow live /var/lib/myagent -R
|
|
639
|
-
`.trim()
|
|
312
|
+
`.trim()
|
|
313
|
+
);
|
|
640
314
|
}
|
|
641
315
|
function scanFiles(dirs, recursive) {
|
|
642
316
|
const results = [];
|
|
643
317
|
const seen = /* @__PURE__ */ new Set();
|
|
644
318
|
function scanDir(d, topLevel) {
|
|
645
319
|
try {
|
|
646
|
-
for (const f of (0,
|
|
320
|
+
for (const f of (0, import_node_fs.readdirSync)(d)) {
|
|
647
321
|
if (f.startsWith(".")) continue;
|
|
648
|
-
const fp = (0,
|
|
322
|
+
const fp = (0, import_node_path.join)(d, f);
|
|
649
323
|
if (seen.has(fp)) continue;
|
|
650
324
|
let stat;
|
|
651
325
|
try {
|
|
652
|
-
stat = (0,
|
|
326
|
+
stat = (0, import_node_fs.statSync)(fp);
|
|
653
327
|
} catch {
|
|
654
328
|
continue;
|
|
655
329
|
}
|
|
@@ -675,20 +349,22 @@ function scanFiles(dirs, recursive) {
|
|
|
675
349
|
}
|
|
676
350
|
function safeReadJson(fp) {
|
|
677
351
|
try {
|
|
678
|
-
return JSON.parse((0,
|
|
352
|
+
return JSON.parse((0, import_node_fs.readFileSync)(fp, "utf8"));
|
|
679
353
|
} catch {
|
|
680
354
|
return null;
|
|
681
355
|
}
|
|
682
356
|
}
|
|
683
357
|
function nameFromFile(filename) {
|
|
684
|
-
return (0,
|
|
358
|
+
return (0, import_node_path.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
685
359
|
}
|
|
686
360
|
function normalizeStatus(val) {
|
|
687
361
|
if (typeof val !== "string") return "unknown";
|
|
688
362
|
const s = val.toLowerCase();
|
|
689
363
|
if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
|
|
690
|
-
if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s))
|
|
691
|
-
|
|
364
|
+
if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s))
|
|
365
|
+
return "error";
|
|
366
|
+
if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s))
|
|
367
|
+
return "running";
|
|
692
368
|
return "unknown";
|
|
693
369
|
}
|
|
694
370
|
function findStatus(obj) {
|
|
@@ -704,7 +380,17 @@ function findStatus(obj) {
|
|
|
704
380
|
return "unknown";
|
|
705
381
|
}
|
|
706
382
|
function findTimestamp(obj) {
|
|
707
|
-
for (const key of [
|
|
383
|
+
for (const key of [
|
|
384
|
+
"ts",
|
|
385
|
+
"timestamp",
|
|
386
|
+
"lastRunAtMs",
|
|
387
|
+
"last_run",
|
|
388
|
+
"lastExecution",
|
|
389
|
+
"updated_at",
|
|
390
|
+
"started_at",
|
|
391
|
+
"endTime",
|
|
392
|
+
"startTime"
|
|
393
|
+
]) {
|
|
708
394
|
const val = obj[key];
|
|
709
395
|
if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
|
|
710
396
|
if (typeof val === "string") {
|
|
@@ -716,7 +402,16 @@ function findTimestamp(obj) {
|
|
|
716
402
|
}
|
|
717
403
|
function extractDetail(obj) {
|
|
718
404
|
const parts = [];
|
|
719
|
-
for (const key of [
|
|
405
|
+
for (const key of [
|
|
406
|
+
"summary",
|
|
407
|
+
"message",
|
|
408
|
+
"description",
|
|
409
|
+
"lastError",
|
|
410
|
+
"error",
|
|
411
|
+
"name",
|
|
412
|
+
"jobId",
|
|
413
|
+
"id"
|
|
414
|
+
]) {
|
|
720
415
|
const val = obj[key];
|
|
721
416
|
if (typeof val === "string" && val.length > 0 && val.length < 200) {
|
|
722
417
|
parts.push(val.slice(0, 80));
|
|
@@ -776,7 +471,14 @@ function processJsonFile(file) {
|
|
|
776
471
|
const status2 = findStatus(state);
|
|
777
472
|
const ts2 = findTimestamp(state) || file.mtime;
|
|
778
473
|
const detail2 = extractDetail(state);
|
|
779
|
-
records.push({
|
|
474
|
+
records.push({
|
|
475
|
+
id: String(name),
|
|
476
|
+
source: "jobs",
|
|
477
|
+
status: status2,
|
|
478
|
+
lastActive: ts2,
|
|
479
|
+
detail: detail2,
|
|
480
|
+
file: file.filename
|
|
481
|
+
});
|
|
780
482
|
}
|
|
781
483
|
return records;
|
|
782
484
|
}
|
|
@@ -790,8 +492,26 @@ function processJsonFile(file) {
|
|
|
790
492
|
const status2 = findStatus(w);
|
|
791
493
|
const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
|
|
792
494
|
const pid = w.pid;
|
|
793
|
-
|
|
794
|
-
|
|
495
|
+
let validatedStatus = status2;
|
|
496
|
+
let pidAlive = true;
|
|
497
|
+
if (pid && (status2 === "running" || status2 === "ok")) {
|
|
498
|
+
try {
|
|
499
|
+
(0, import_node_child_process.execSync)(`kill -0 ${pid} 2>/dev/null`, { stdio: "ignore" });
|
|
500
|
+
} catch {
|
|
501
|
+
pidAlive = false;
|
|
502
|
+
validatedStatus = "error";
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const pidLabel = pid ? pidAlive ? `pid: ${pid}` : `pid: ${pid} (dead)` : "";
|
|
506
|
+
const detail2 = pidLabel || extractDetail(w);
|
|
507
|
+
records.push({
|
|
508
|
+
id: name,
|
|
509
|
+
source: "workers",
|
|
510
|
+
status: validatedStatus,
|
|
511
|
+
lastActive: ts2,
|
|
512
|
+
detail: detail2,
|
|
513
|
+
file: file.filename
|
|
514
|
+
});
|
|
795
515
|
}
|
|
796
516
|
return records;
|
|
797
517
|
}
|
|
@@ -799,12 +519,19 @@ function processJsonFile(file) {
|
|
|
799
519
|
const status = findStatus(obj);
|
|
800
520
|
const ts = findTimestamp(obj) || file.mtime;
|
|
801
521
|
const detail = extractDetail(obj);
|
|
802
|
-
records.push({
|
|
522
|
+
records.push({
|
|
523
|
+
id: nameFromFile(file.filename),
|
|
524
|
+
source: "state",
|
|
525
|
+
status,
|
|
526
|
+
lastActive: ts,
|
|
527
|
+
detail,
|
|
528
|
+
file: file.filename
|
|
529
|
+
});
|
|
803
530
|
return records;
|
|
804
531
|
}
|
|
805
532
|
function processJsonlFile(file) {
|
|
806
533
|
try {
|
|
807
|
-
const content = (0,
|
|
534
|
+
const content = (0, import_node_fs.readFileSync)(file.path, "utf8").trim();
|
|
808
535
|
if (!content) return [];
|
|
809
536
|
const lines = content.split("\n");
|
|
810
537
|
const lineCount = lines.length;
|
|
@@ -815,13 +542,22 @@ function processJsonlFile(file) {
|
|
|
815
542
|
const ts2 = findTimestamp(lastObj) || file.mtime;
|
|
816
543
|
const action = lastObj.action;
|
|
817
544
|
const detail2 = action ? `${action} (${lineCount} entries)` : `${lineCount} entries`;
|
|
818
|
-
return [
|
|
545
|
+
return [
|
|
546
|
+
{
|
|
547
|
+
id: String(name),
|
|
548
|
+
source: "session",
|
|
549
|
+
status: status2,
|
|
550
|
+
lastActive: ts2,
|
|
551
|
+
detail: detail2,
|
|
552
|
+
file: file.filename
|
|
553
|
+
}
|
|
554
|
+
];
|
|
819
555
|
}
|
|
820
556
|
const tail = lines.slice(Math.max(0, lineCount - 30));
|
|
821
557
|
let model = "";
|
|
822
558
|
let totalTokens = 0;
|
|
823
559
|
let totalCost = 0;
|
|
824
|
-
|
|
560
|
+
const toolCalls = [];
|
|
825
561
|
let lastUserMsg = "";
|
|
826
562
|
let lastAssistantMsg = "";
|
|
827
563
|
let errorCount = 0;
|
|
@@ -920,7 +656,16 @@ function processJsonlFile(file) {
|
|
|
920
656
|
const status = errorCount > lineCount / 4 ? "error" : lastRole === "assistant" ? "ok" : "running";
|
|
921
657
|
const ts = findTimestamp(lastObj) || file.mtime;
|
|
922
658
|
const sessionName = sessionId ? sessionId.slice(0, 8) : nameFromFile(file.filename);
|
|
923
|
-
return [
|
|
659
|
+
return [
|
|
660
|
+
{
|
|
661
|
+
id: String(name !== "unknown" ? name : sessionName),
|
|
662
|
+
source: "session",
|
|
663
|
+
status,
|
|
664
|
+
lastActive: ts,
|
|
665
|
+
detail,
|
|
666
|
+
file: file.filename
|
|
667
|
+
}
|
|
668
|
+
];
|
|
924
669
|
} catch {
|
|
925
670
|
return [];
|
|
926
671
|
}
|
|
@@ -953,8 +698,30 @@ function render(config) {
|
|
|
953
698
|
if (r.traceData) allTraces.push(r.traceData);
|
|
954
699
|
}
|
|
955
700
|
}
|
|
956
|
-
const
|
|
701
|
+
const deduped = [];
|
|
702
|
+
const seenAgents = /* @__PURE__ */ new Map();
|
|
957
703
|
for (const r of allRecords) {
|
|
704
|
+
if (r.source === "jobs" || r.source === "workers" || r.source === "state") {
|
|
705
|
+
deduped.push(r);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
const key = `${r.source}:${r.id}`;
|
|
709
|
+
const existing = seenAgents.get(key);
|
|
710
|
+
if (!existing || r.lastActive > existing.lastActive) {
|
|
711
|
+
seenAgents.set(key, r);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
for (const r of seenAgents.values()) deduped.push(r);
|
|
715
|
+
const STALE_THRESHOLD_MS = 2 * 60 * 60 * 1e3;
|
|
716
|
+
const now = Date.now();
|
|
717
|
+
const filtered = deduped.filter((r) => {
|
|
718
|
+
if (r.source === "jobs" || r.source === "workers") return true;
|
|
719
|
+
if (r.status === "running") return true;
|
|
720
|
+
return now - r.lastActive < STALE_THRESHOLD_MS;
|
|
721
|
+
});
|
|
722
|
+
const activeRecords = filtered;
|
|
723
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
724
|
+
for (const r of activeRecords) {
|
|
958
725
|
const arr = byFile.get(r.file) ?? [];
|
|
959
726
|
arr.push(r);
|
|
960
727
|
byFile.set(r.file, arr);
|
|
@@ -1001,15 +768,14 @@ function render(config) {
|
|
|
1001
768
|
}
|
|
1002
769
|
}
|
|
1003
770
|
groups.sort((a, b) => b.lastTs - a.lastTs);
|
|
1004
|
-
const totExec =
|
|
1005
|
-
const totFail =
|
|
1006
|
-
const totRunning =
|
|
1007
|
-
const uniqueAgents = new Set(
|
|
771
|
+
const totExec = activeRecords.length;
|
|
772
|
+
const totFail = activeRecords.filter((r) => r.status === "error").length;
|
|
773
|
+
const totRunning = activeRecords.filter((r) => r.status === "running").length;
|
|
774
|
+
const uniqueAgents = new Set(activeRecords.map((r) => r.id)).size;
|
|
1008
775
|
const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
|
|
1009
|
-
const now = Date.now();
|
|
1010
776
|
const buckets = new Array(12).fill(0);
|
|
1011
777
|
const failBuckets = new Array(12).fill(0);
|
|
1012
|
-
for (const r of
|
|
778
|
+
for (const r of activeRecords) {
|
|
1013
779
|
const age = now - r.lastActive;
|
|
1014
780
|
if (age > 36e5 || age < 0) continue;
|
|
1015
781
|
const idx = 11 - Math.floor(age / 3e5);
|
|
@@ -1052,7 +818,8 @@ function render(config) {
|
|
|
1052
818
|
if (g.fail > 0 && g.ok === 0 && g.running === 0) return `${C.red}error${C.reset}`;
|
|
1053
819
|
if (g.running > 0) return `${C.green}running${C.reset}`;
|
|
1054
820
|
if (g.fail > 0) return `${C.yellow}${g.ok}ok/${g.fail}err${C.reset}`;
|
|
1055
|
-
if (g.ok > 0)
|
|
821
|
+
if (g.ok > 0)
|
|
822
|
+
return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
|
|
1056
823
|
return `${C.dim}idle${C.reset}`;
|
|
1057
824
|
}
|
|
1058
825
|
function sourceTag(s) {
|
|
@@ -1084,18 +851,30 @@ function render(config) {
|
|
|
1084
851
|
}
|
|
1085
852
|
const L = [];
|
|
1086
853
|
writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
|
|
1087
|
-
writeLine(
|
|
854
|
+
writeLine(
|
|
855
|
+
L,
|
|
856
|
+
`${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}`
|
|
857
|
+
);
|
|
1088
858
|
const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
|
|
1089
859
|
const pad1 = Math.max(0, 64 - metaLine.length);
|
|
1090
|
-
writeLine(
|
|
860
|
+
writeLine(
|
|
861
|
+
L,
|
|
862
|
+
`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`
|
|
863
|
+
);
|
|
1091
864
|
writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
|
|
1092
865
|
const sc = totFail === 0 ? C.green : C.yellow;
|
|
1093
866
|
writeLine(L, "");
|
|
1094
|
-
writeLine(
|
|
867
|
+
writeLine(
|
|
868
|
+
L,
|
|
869
|
+
` ${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}`
|
|
870
|
+
);
|
|
1095
871
|
writeLine(L, "");
|
|
1096
872
|
writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
|
|
1097
873
|
writeLine(L, "");
|
|
1098
|
-
writeLine(
|
|
874
|
+
writeLine(
|
|
875
|
+
L,
|
|
876
|
+
` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`
|
|
877
|
+
);
|
|
1099
878
|
let lineCount = 0;
|
|
1100
879
|
for (const g of groups) {
|
|
1101
880
|
if (lineCount > 35) break;
|
|
@@ -1106,13 +885,19 @@ function render(config) {
|
|
|
1106
885
|
const name = truncate(g.name, 26).padEnd(26);
|
|
1107
886
|
const st = statusText(g);
|
|
1108
887
|
const det = truncate(g.detail, detailWidth);
|
|
1109
|
-
writeLine(
|
|
888
|
+
writeLine(
|
|
889
|
+
L,
|
|
890
|
+
` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`
|
|
891
|
+
);
|
|
1110
892
|
lineCount++;
|
|
1111
893
|
} else {
|
|
1112
894
|
const name = truncate(g.name, 24).padEnd(24);
|
|
1113
895
|
const st = statusText(g);
|
|
1114
896
|
const tag = sourceTag(g.source);
|
|
1115
|
-
writeLine(
|
|
897
|
+
writeLine(
|
|
898
|
+
L,
|
|
899
|
+
` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`
|
|
900
|
+
);
|
|
1116
901
|
lineCount++;
|
|
1117
902
|
const kids = g.children.slice(0, 12);
|
|
1118
903
|
for (let i = 0; i < kids.length; i++) {
|
|
@@ -1124,7 +909,10 @@ function render(config) {
|
|
|
1124
909
|
const cName = truncate(child.id, 22).padEnd(22);
|
|
1125
910
|
const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
|
|
1126
911
|
const cDet = truncate(child.detail, detailWidth - 5);
|
|
1127
|
-
writeLine(
|
|
912
|
+
writeLine(
|
|
913
|
+
L,
|
|
914
|
+
` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`
|
|
915
|
+
);
|
|
1128
916
|
lineCount++;
|
|
1129
917
|
}
|
|
1130
918
|
if (g.children.length > 12) {
|
|
@@ -1141,7 +929,10 @@ function render(config) {
|
|
|
1141
929
|
const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
1142
930
|
const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
|
|
1143
931
|
const tid = dt.traceId.slice(0, 8);
|
|
1144
|
-
writeLine(
|
|
932
|
+
writeLine(
|
|
933
|
+
L,
|
|
934
|
+
` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`
|
|
935
|
+
);
|
|
1145
936
|
const tree = getTraceTree(dt);
|
|
1146
937
|
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
1147
938
|
const tg = tree[i];
|
|
@@ -1151,11 +942,14 @@ function render(config) {
|
|
|
1151
942
|
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1152
943
|
const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
1153
944
|
const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
|
|
1154
|
-
writeLine(
|
|
945
|
+
writeLine(
|
|
946
|
+
L,
|
|
947
|
+
`${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`
|
|
948
|
+
);
|
|
1155
949
|
}
|
|
1156
950
|
}
|
|
1157
951
|
}
|
|
1158
|
-
const recentRecords =
|
|
952
|
+
const recentRecords = activeRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 6);
|
|
1159
953
|
if (recentRecords.length > 0) {
|
|
1160
954
|
writeLine(L, "");
|
|
1161
955
|
writeLine(L, ` ${C.bold}${C.under}Recent Activity${C.reset}`);
|
|
@@ -1166,7 +960,10 @@ function render(config) {
|
|
|
1166
960
|
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1167
961
|
const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
|
|
1168
962
|
const det = truncate(r.detail, detailWidth);
|
|
1169
|
-
writeLine(
|
|
963
|
+
writeLine(
|
|
964
|
+
L,
|
|
965
|
+
` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`
|
|
966
|
+
);
|
|
1170
967
|
}
|
|
1171
968
|
}
|
|
1172
969
|
if (files.length === 0) {
|
|
@@ -1188,13 +985,13 @@ function getDistDepth(dt, spanId) {
|
|
|
1188
985
|
}
|
|
1189
986
|
function startLive(argv) {
|
|
1190
987
|
const config = parseArgs(argv);
|
|
1191
|
-
const valid = config.dirs.filter((d) => (0,
|
|
988
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs.existsSync)(d));
|
|
1192
989
|
if (valid.length === 0) {
|
|
1193
990
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1194
991
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1195
992
|
process.exit(1);
|
|
1196
993
|
}
|
|
1197
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
994
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs.existsSync)(d));
|
|
1198
995
|
if (invalid.length > 0) {
|
|
1199
996
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1200
997
|
}
|
|
@@ -1203,24 +1000,947 @@ function startLive(argv) {
|
|
|
1203
1000
|
let debounce = null;
|
|
1204
1001
|
for (const dir of config.dirs) {
|
|
1205
1002
|
try {
|
|
1206
|
-
(0,
|
|
1003
|
+
(0, import_node_fs.watch)(dir, { recursive: config.recursive }, () => {
|
|
1207
1004
|
if (debounce) clearTimeout(debounce);
|
|
1208
1005
|
debounce = setTimeout(() => render(config), 500);
|
|
1209
1006
|
});
|
|
1210
1007
|
} catch {
|
|
1211
1008
|
}
|
|
1212
1009
|
}
|
|
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
|
-
});
|
|
1010
|
+
setInterval(() => render(config), config.refreshMs);
|
|
1011
|
+
process.on("SIGINT", () => {
|
|
1012
|
+
console.log("\n" + C.dim + "Monitor stopped." + C.reset);
|
|
1013
|
+
process.exit(0);
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/runner.ts
|
|
1018
|
+
var import_node_child_process2 = require("child_process");
|
|
1019
|
+
var import_node_fs2 = require("fs");
|
|
1020
|
+
var import_node_path2 = require("path");
|
|
1021
|
+
|
|
1022
|
+
// src/graph-builder.ts
|
|
1023
|
+
var import_crypto = require("crypto");
|
|
1024
|
+
function deepFreeze(obj) {
|
|
1025
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
1026
|
+
if (obj instanceof Map) {
|
|
1027
|
+
Object.freeze(obj);
|
|
1028
|
+
for (const value of obj.values()) {
|
|
1029
|
+
deepFreeze(value);
|
|
1030
|
+
}
|
|
1031
|
+
return obj;
|
|
1032
|
+
}
|
|
1033
|
+
Object.freeze(obj);
|
|
1034
|
+
const record = obj;
|
|
1035
|
+
for (const key of Object.keys(record)) {
|
|
1036
|
+
const value = record[key];
|
|
1037
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
1038
|
+
deepFreeze(value);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return obj;
|
|
1042
|
+
}
|
|
1043
|
+
function createCounterIdGenerator() {
|
|
1044
|
+
let counter = 0;
|
|
1045
|
+
return () => {
|
|
1046
|
+
counter++;
|
|
1047
|
+
return `node_${String(counter).padStart(3, "0")}`;
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function createGraphBuilder(config) {
|
|
1051
|
+
const generateId = config?.idGenerator ?? createCounterIdGenerator();
|
|
1052
|
+
const agentId = config?.agentId ?? "unknown";
|
|
1053
|
+
const trigger = config?.trigger ?? "manual";
|
|
1054
|
+
const spanId = (0, import_crypto.randomUUID)();
|
|
1055
|
+
const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? (0, import_crypto.randomUUID)();
|
|
1056
|
+
const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
|
|
1057
|
+
const graphId = generateId();
|
|
1058
|
+
const startTime = Date.now();
|
|
1059
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1060
|
+
const edges = [];
|
|
1061
|
+
const events = [];
|
|
1062
|
+
const parentStack = [];
|
|
1063
|
+
let rootNodeId = null;
|
|
1064
|
+
let built = false;
|
|
1065
|
+
function assertNotBuilt() {
|
|
1066
|
+
if (built) {
|
|
1067
|
+
throw new Error("GraphBuilder: cannot mutate after build() has been called");
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
function getNode(nodeId) {
|
|
1071
|
+
const node = nodes.get(nodeId);
|
|
1072
|
+
if (!node) {
|
|
1073
|
+
throw new Error(`GraphBuilder: node "${nodeId}" does not exist`);
|
|
1074
|
+
}
|
|
1075
|
+
return node;
|
|
1076
|
+
}
|
|
1077
|
+
function recordEvent(nodeId, eventType, data = {}) {
|
|
1078
|
+
events.push({
|
|
1079
|
+
timestamp: Date.now(),
|
|
1080
|
+
eventType,
|
|
1081
|
+
nodeId,
|
|
1082
|
+
data
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
function buildGraph() {
|
|
1086
|
+
if (rootNodeId === null) {
|
|
1087
|
+
throw new Error("GraphBuilder: cannot build a graph with no nodes");
|
|
1088
|
+
}
|
|
1089
|
+
let graphStatus = "completed";
|
|
1090
|
+
for (const node of nodes.values()) {
|
|
1091
|
+
if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
|
|
1092
|
+
graphStatus = "failed";
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
1095
|
+
if (node.status === "running") {
|
|
1096
|
+
graphStatus = "running";
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
const endTime = graphStatus === "running" ? null : Date.now();
|
|
1100
|
+
const frozenNodes = new Map(
|
|
1101
|
+
[...nodes.entries()].map(([id, mNode]) => [
|
|
1102
|
+
id,
|
|
1103
|
+
{
|
|
1104
|
+
id: mNode.id,
|
|
1105
|
+
type: mNode.type,
|
|
1106
|
+
name: mNode.name,
|
|
1107
|
+
startTime: mNode.startTime,
|
|
1108
|
+
endTime: mNode.endTime,
|
|
1109
|
+
status: mNode.status,
|
|
1110
|
+
parentId: mNode.parentId,
|
|
1111
|
+
children: [...mNode.children],
|
|
1112
|
+
metadata: { ...mNode.metadata },
|
|
1113
|
+
state: { ...mNode.state }
|
|
1114
|
+
}
|
|
1115
|
+
])
|
|
1116
|
+
);
|
|
1117
|
+
const graph = {
|
|
1118
|
+
id: graphId,
|
|
1119
|
+
rootNodeId,
|
|
1120
|
+
nodes: frozenNodes,
|
|
1121
|
+
edges: [...edges],
|
|
1122
|
+
startTime,
|
|
1123
|
+
endTime,
|
|
1124
|
+
status: graphStatus,
|
|
1125
|
+
trigger,
|
|
1126
|
+
agentId,
|
|
1127
|
+
events: [...events],
|
|
1128
|
+
traceId,
|
|
1129
|
+
spanId,
|
|
1130
|
+
parentSpanId
|
|
1131
|
+
};
|
|
1132
|
+
return deepFreeze(graph);
|
|
1133
|
+
}
|
|
1134
|
+
const builder = {
|
|
1135
|
+
get graphId() {
|
|
1136
|
+
return graphId;
|
|
1137
|
+
},
|
|
1138
|
+
get traceContext() {
|
|
1139
|
+
return { traceId, spanId };
|
|
1140
|
+
},
|
|
1141
|
+
startNode(opts) {
|
|
1142
|
+
assertNotBuilt();
|
|
1143
|
+
const id = generateId();
|
|
1144
|
+
const parentId = opts.parentId ?? parentStack[parentStack.length - 1] ?? null;
|
|
1145
|
+
if (parentId !== null && !nodes.has(parentId)) {
|
|
1146
|
+
throw new Error(`GraphBuilder: parent node "${parentId}" does not exist`);
|
|
1147
|
+
}
|
|
1148
|
+
const node = {
|
|
1149
|
+
id,
|
|
1150
|
+
type: opts.type,
|
|
1151
|
+
name: opts.name,
|
|
1152
|
+
startTime: Date.now(),
|
|
1153
|
+
endTime: null,
|
|
1154
|
+
status: "running",
|
|
1155
|
+
parentId,
|
|
1156
|
+
children: [],
|
|
1157
|
+
metadata: opts.metadata ? { ...opts.metadata } : {},
|
|
1158
|
+
state: {}
|
|
1159
|
+
};
|
|
1160
|
+
nodes.set(id, node);
|
|
1161
|
+
if (parentId !== null) {
|
|
1162
|
+
const parent = nodes.get(parentId);
|
|
1163
|
+
if (parent) {
|
|
1164
|
+
parent.children.push(id);
|
|
1165
|
+
}
|
|
1166
|
+
edges.push({ from: parentId, to: id, type: "spawned" });
|
|
1167
|
+
}
|
|
1168
|
+
if (rootNodeId === null) {
|
|
1169
|
+
rootNodeId = id;
|
|
1170
|
+
}
|
|
1171
|
+
recordEvent(id, "agent_start", { type: opts.type, name: opts.name });
|
|
1172
|
+
return id;
|
|
1173
|
+
},
|
|
1174
|
+
endNode(nodeId, status = "completed") {
|
|
1175
|
+
assertNotBuilt();
|
|
1176
|
+
const node = getNode(nodeId);
|
|
1177
|
+
if (node.endTime !== null) {
|
|
1178
|
+
throw new Error(
|
|
1179
|
+
`GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
node.endTime = Date.now();
|
|
1183
|
+
node.status = status;
|
|
1184
|
+
recordEvent(nodeId, "agent_end", { status });
|
|
1185
|
+
},
|
|
1186
|
+
failNode(nodeId, error) {
|
|
1187
|
+
assertNotBuilt();
|
|
1188
|
+
const node = getNode(nodeId);
|
|
1189
|
+
if (node.endTime !== null) {
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
`GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
1195
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
1196
|
+
node.endTime = Date.now();
|
|
1197
|
+
node.status = "failed";
|
|
1198
|
+
node.metadata.error = errorMessage;
|
|
1199
|
+
if (errorStack) {
|
|
1200
|
+
node.metadata.errorStack = errorStack;
|
|
1201
|
+
}
|
|
1202
|
+
recordEvent(nodeId, "tool_error", { error: errorMessage });
|
|
1203
|
+
},
|
|
1204
|
+
addEdge(from, to, type) {
|
|
1205
|
+
assertNotBuilt();
|
|
1206
|
+
getNode(from);
|
|
1207
|
+
getNode(to);
|
|
1208
|
+
edges.push({ from, to, type });
|
|
1209
|
+
recordEvent(from, "custom", { to, type, action: "edge_add" });
|
|
1210
|
+
},
|
|
1211
|
+
pushEvent(event) {
|
|
1212
|
+
assertNotBuilt();
|
|
1213
|
+
getNode(event.nodeId);
|
|
1214
|
+
events.push({
|
|
1215
|
+
...event,
|
|
1216
|
+
timestamp: Date.now()
|
|
1217
|
+
});
|
|
1218
|
+
},
|
|
1219
|
+
updateState(nodeId, state) {
|
|
1220
|
+
assertNotBuilt();
|
|
1221
|
+
const node = getNode(nodeId);
|
|
1222
|
+
Object.assign(node.state, state);
|
|
1223
|
+
recordEvent(nodeId, "custom", { action: "state_update", ...state });
|
|
1224
|
+
},
|
|
1225
|
+
withParent(parentId, fn) {
|
|
1226
|
+
assertNotBuilt();
|
|
1227
|
+
getNode(parentId);
|
|
1228
|
+
parentStack.push(parentId);
|
|
1229
|
+
try {
|
|
1230
|
+
return fn();
|
|
1231
|
+
} finally {
|
|
1232
|
+
parentStack.pop();
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
getSnapshot() {
|
|
1236
|
+
return buildGraph();
|
|
1237
|
+
},
|
|
1238
|
+
build() {
|
|
1239
|
+
assertNotBuilt();
|
|
1240
|
+
const graph = buildGraph();
|
|
1241
|
+
built = true;
|
|
1242
|
+
return graph;
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
return builder;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/runner.ts
|
|
1249
|
+
init_loader();
|
|
1250
|
+
function globToRegex(pattern) {
|
|
1251
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1252
|
+
return new RegExp(`^${escaped}$`);
|
|
1253
|
+
}
|
|
1254
|
+
function snapshotDir(dir, patterns) {
|
|
1255
|
+
const result = /* @__PURE__ */ new Map();
|
|
1256
|
+
if (!(0, import_node_fs2.existsSync)(dir)) return result;
|
|
1257
|
+
for (const entry of (0, import_node_fs2.readdirSync)(dir)) {
|
|
1258
|
+
if (!patterns.some((re) => re.test(entry))) continue;
|
|
1259
|
+
const full = (0, import_node_path2.join)(dir, entry);
|
|
1260
|
+
try {
|
|
1261
|
+
const stat = (0, import_node_fs2.statSync)(full);
|
|
1262
|
+
if (stat.isFile()) {
|
|
1263
|
+
result.set(full, stat.mtimeMs);
|
|
1264
|
+
}
|
|
1265
|
+
} catch {
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return result;
|
|
1269
|
+
}
|
|
1270
|
+
function agentIdFromFilename(filePath) {
|
|
1271
|
+
const base = (0, import_node_path2.basename)(filePath, ".json");
|
|
1272
|
+
const cleaned = base.replace(/-state$/, "");
|
|
1273
|
+
return `alfred-${cleaned}`;
|
|
1274
|
+
}
|
|
1275
|
+
function deriveAgentId(command) {
|
|
1276
|
+
return "orchestrator";
|
|
1277
|
+
}
|
|
1278
|
+
function fileTimestamp() {
|
|
1279
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
|
|
1280
|
+
}
|
|
1281
|
+
async function runTraced(config) {
|
|
1282
|
+
const {
|
|
1283
|
+
command,
|
|
1284
|
+
agentId = deriveAgentId(command),
|
|
1285
|
+
trigger = "cli",
|
|
1286
|
+
tracesDir = "./traces",
|
|
1287
|
+
watchDirs = [],
|
|
1288
|
+
watchPatterns = ["*.json"]
|
|
1289
|
+
} = config;
|
|
1290
|
+
if (command.length === 0) {
|
|
1291
|
+
throw new Error("runTraced: command must not be empty");
|
|
1292
|
+
}
|
|
1293
|
+
const resolvedTracesDir = (0, import_node_path2.resolve)(tracesDir);
|
|
1294
|
+
const patterns = watchPatterns.map(globToRegex);
|
|
1295
|
+
const orchestrator = createGraphBuilder({ agentId, trigger });
|
|
1296
|
+
const { traceId, spanId } = orchestrator.traceContext;
|
|
1297
|
+
const beforeSnapshots = /* @__PURE__ */ new Map();
|
|
1298
|
+
for (const dir of watchDirs) {
|
|
1299
|
+
beforeSnapshots.set(dir, snapshotDir(dir, patterns));
|
|
1300
|
+
}
|
|
1301
|
+
const rootId = orchestrator.startNode({ type: "agent", name: agentId });
|
|
1302
|
+
const dispatchId = orchestrator.startNode({
|
|
1303
|
+
type: "tool",
|
|
1304
|
+
name: "dispatch-command",
|
|
1305
|
+
parentId: rootId
|
|
1306
|
+
});
|
|
1307
|
+
orchestrator.updateState(dispatchId, { command: command.join(" ") });
|
|
1308
|
+
const monitorId = orchestrator.startNode({
|
|
1309
|
+
type: "tool",
|
|
1310
|
+
name: "state-monitor",
|
|
1311
|
+
parentId: rootId
|
|
1312
|
+
});
|
|
1313
|
+
orchestrator.updateState(monitorId, {
|
|
1314
|
+
watchDirs,
|
|
1315
|
+
watchPatterns
|
|
1316
|
+
});
|
|
1317
|
+
const startMs = Date.now();
|
|
1318
|
+
const execCmd = command[0] ?? "";
|
|
1319
|
+
const execArgs = command.slice(1);
|
|
1320
|
+
process.env.AGENTFLOW_TRACE_ID = traceId;
|
|
1321
|
+
process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
|
|
1322
|
+
const result = (0, import_node_child_process2.spawnSync)(execCmd, execArgs, { stdio: "inherit" });
|
|
1323
|
+
delete process.env.AGENTFLOW_TRACE_ID;
|
|
1324
|
+
delete process.env.AGENTFLOW_PARENT_SPAN_ID;
|
|
1325
|
+
const exitCode = result.status ?? 1;
|
|
1326
|
+
const duration = (Date.now() - startMs) / 1e3;
|
|
1327
|
+
const stateChanges = [];
|
|
1328
|
+
for (const dir of watchDirs) {
|
|
1329
|
+
const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
|
|
1330
|
+
const after = snapshotDir(dir, patterns);
|
|
1331
|
+
for (const [filePath, mtime] of after) {
|
|
1332
|
+
const prevMtime = before.get(filePath);
|
|
1333
|
+
if (prevMtime === void 0 || mtime > prevMtime) {
|
|
1334
|
+
stateChanges.push(filePath);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
orchestrator.updateState(monitorId, { stateChanges });
|
|
1339
|
+
orchestrator.endNode(monitorId);
|
|
1340
|
+
if (exitCode === 0) {
|
|
1341
|
+
orchestrator.endNode(dispatchId);
|
|
1342
|
+
} else {
|
|
1343
|
+
orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
|
|
1344
|
+
}
|
|
1345
|
+
orchestrator.updateState(rootId, {
|
|
1346
|
+
exitCode,
|
|
1347
|
+
duration,
|
|
1348
|
+
stateChanges
|
|
1349
|
+
});
|
|
1350
|
+
if (exitCode === 0) {
|
|
1351
|
+
orchestrator.endNode(rootId);
|
|
1352
|
+
} else {
|
|
1353
|
+
orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
|
|
1354
|
+
}
|
|
1355
|
+
const orchestratorGraph = orchestrator.build();
|
|
1356
|
+
const allGraphs = [orchestratorGraph];
|
|
1357
|
+
for (const filePath of stateChanges) {
|
|
1358
|
+
const childAgentId = agentIdFromFilename(filePath);
|
|
1359
|
+
const childBuilder = createGraphBuilder({
|
|
1360
|
+
agentId: childAgentId,
|
|
1361
|
+
trigger: "state-change",
|
|
1362
|
+
traceId,
|
|
1363
|
+
parentSpanId: spanId
|
|
1364
|
+
});
|
|
1365
|
+
const childRootId = childBuilder.startNode({
|
|
1366
|
+
type: "agent",
|
|
1367
|
+
name: childAgentId
|
|
1368
|
+
});
|
|
1369
|
+
childBuilder.updateState(childRootId, {
|
|
1370
|
+
stateFile: filePath,
|
|
1371
|
+
detectedBy: "runner-state-monitor"
|
|
1372
|
+
});
|
|
1373
|
+
childBuilder.endNode(childRootId);
|
|
1374
|
+
allGraphs.push(childBuilder.build());
|
|
1375
|
+
}
|
|
1376
|
+
if (!(0, import_node_fs2.existsSync)(resolvedTracesDir)) {
|
|
1377
|
+
(0, import_node_fs2.mkdirSync)(resolvedTracesDir, { recursive: true });
|
|
1378
|
+
}
|
|
1379
|
+
const ts = fileTimestamp();
|
|
1380
|
+
const tracePaths = [];
|
|
1381
|
+
for (const graph of allGraphs) {
|
|
1382
|
+
const filename = `${graph.agentId}-${ts}.json`;
|
|
1383
|
+
const outPath = (0, import_node_path2.join)(resolvedTracesDir, filename);
|
|
1384
|
+
(0, import_node_fs2.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
1385
|
+
tracePaths.push(outPath);
|
|
1386
|
+
}
|
|
1387
|
+
if (tracePaths.length > 0) {
|
|
1388
|
+
console.log(`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`);
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
exitCode,
|
|
1392
|
+
traceId,
|
|
1393
|
+
spanId,
|
|
1394
|
+
tracePaths,
|
|
1395
|
+
stateChanges,
|
|
1396
|
+
duration
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/trace-cli.ts
|
|
1401
|
+
var import_path2 = require("path");
|
|
1402
|
+
|
|
1403
|
+
// src/trace-store.ts
|
|
1404
|
+
var import_promises = require("fs/promises");
|
|
1405
|
+
var import_path = require("path");
|
|
1406
|
+
init_loader();
|
|
1407
|
+
function createTraceStore(dir) {
|
|
1408
|
+
async function ensureDir() {
|
|
1409
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
1410
|
+
}
|
|
1411
|
+
async function loadAll() {
|
|
1412
|
+
await ensureDir();
|
|
1413
|
+
let files;
|
|
1414
|
+
try {
|
|
1415
|
+
files = await (0, import_promises.readdir)(dir);
|
|
1416
|
+
} catch {
|
|
1417
|
+
return [];
|
|
1418
|
+
}
|
|
1419
|
+
const graphs = [];
|
|
1420
|
+
for (const file of files) {
|
|
1421
|
+
if (!file.endsWith(".json")) continue;
|
|
1422
|
+
try {
|
|
1423
|
+
const content = await (0, import_promises.readFile)((0, import_path.join)(dir, file), "utf-8");
|
|
1424
|
+
const graph = loadGraph(content);
|
|
1425
|
+
graphs.push(graph);
|
|
1426
|
+
} catch {
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return graphs;
|
|
1430
|
+
}
|
|
1431
|
+
return {
|
|
1432
|
+
async save(graph) {
|
|
1433
|
+
await ensureDir();
|
|
1434
|
+
const json = graphToJson(graph);
|
|
1435
|
+
const filePath = (0, import_path.join)(dir, `${graph.id}.json`);
|
|
1436
|
+
await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
1437
|
+
return filePath;
|
|
1438
|
+
},
|
|
1439
|
+
async get(graphId) {
|
|
1440
|
+
await ensureDir();
|
|
1441
|
+
const filePath = (0, import_path.join)(dir, `${graphId}.json`);
|
|
1442
|
+
try {
|
|
1443
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
1444
|
+
return loadGraph(content);
|
|
1445
|
+
} catch {
|
|
1446
|
+
}
|
|
1447
|
+
const all = await loadAll();
|
|
1448
|
+
return all.find((g) => g.id === graphId) ?? null;
|
|
1449
|
+
},
|
|
1450
|
+
async list(opts) {
|
|
1451
|
+
let graphs = await loadAll();
|
|
1452
|
+
if (opts?.status) {
|
|
1453
|
+
graphs = graphs.filter((g) => g.status === opts.status);
|
|
1454
|
+
}
|
|
1455
|
+
graphs.sort((a, b) => b.startTime - a.startTime);
|
|
1456
|
+
if (opts?.limit && opts.limit > 0) {
|
|
1457
|
+
graphs = graphs.slice(0, opts.limit);
|
|
1458
|
+
}
|
|
1459
|
+
return graphs;
|
|
1460
|
+
},
|
|
1461
|
+
async getStuckSpans() {
|
|
1462
|
+
const graphs = await loadAll();
|
|
1463
|
+
const stuck = [];
|
|
1464
|
+
for (const graph of graphs) {
|
|
1465
|
+
for (const node of graph.nodes.values()) {
|
|
1466
|
+
if (node.status === "running" || node.status === "hung" || node.status === "timeout") {
|
|
1467
|
+
stuck.push(node);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
return stuck;
|
|
1472
|
+
},
|
|
1473
|
+
async getReasoningLoops(threshold = 25) {
|
|
1474
|
+
const graphs = await loadAll();
|
|
1475
|
+
const results = [];
|
|
1476
|
+
for (const graph of graphs) {
|
|
1477
|
+
const loops = findLoopsInGraph(graph, threshold);
|
|
1478
|
+
if (loops.length > 0) {
|
|
1479
|
+
results.push({ graphId: graph.id, nodes: loops });
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return results;
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
function findLoopsInGraph(graph, threshold) {
|
|
1487
|
+
const loopNodes = [];
|
|
1488
|
+
function walk(nodeId, consecutiveCount, consecutiveType) {
|
|
1489
|
+
const node = graph.nodes.get(nodeId);
|
|
1490
|
+
if (!node) return;
|
|
1491
|
+
const newCount = node.type === consecutiveType ? consecutiveCount + 1 : 1;
|
|
1492
|
+
if (newCount > threshold) {
|
|
1493
|
+
loopNodes.push(node);
|
|
1494
|
+
}
|
|
1495
|
+
for (const childId of node.children) {
|
|
1496
|
+
walk(childId, newCount, node.type);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
walk(graph.rootNodeId, 0, null);
|
|
1500
|
+
return loopNodes;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// src/visualize.ts
|
|
1504
|
+
var STATUS_ICONS = {
|
|
1505
|
+
completed: "\u2713",
|
|
1506
|
+
failed: "\u2717",
|
|
1507
|
+
running: "\u231B",
|
|
1508
|
+
hung: "\u231B",
|
|
1509
|
+
timeout: "\u231B"
|
|
1510
|
+
};
|
|
1511
|
+
function formatDuration(ms) {
|
|
1512
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1513
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1514
|
+
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
1515
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
1516
|
+
}
|
|
1517
|
+
function nodeDuration(node, graphEndTime) {
|
|
1518
|
+
const end = node.endTime ?? graphEndTime;
|
|
1519
|
+
return formatDuration(end - node.startTime);
|
|
1520
|
+
}
|
|
1521
|
+
function getGenAiInfo(node) {
|
|
1522
|
+
const parts = [];
|
|
1523
|
+
const meta = node.metadata;
|
|
1524
|
+
if (meta["gen_ai.request.model"]) {
|
|
1525
|
+
parts.push(String(meta["gen_ai.request.model"]));
|
|
1526
|
+
}
|
|
1527
|
+
const tokens = meta["gen_ai.usage.prompt_tokens"] ?? meta["gen_ai.usage.completion_tokens"];
|
|
1528
|
+
if (tokens !== void 0) {
|
|
1529
|
+
const prompt = meta["gen_ai.usage.prompt_tokens"] ?? 0;
|
|
1530
|
+
const completion = meta["gen_ai.usage.completion_tokens"] ?? 0;
|
|
1531
|
+
if (prompt || completion) {
|
|
1532
|
+
parts.push(`${prompt + completion} tok`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
return parts.length > 0 ? ` [${parts.join(", ")}]` : "";
|
|
1536
|
+
}
|
|
1537
|
+
function hasViolation(node, graph) {
|
|
1538
|
+
return graph.events.some(
|
|
1539
|
+
(e) => e.nodeId === node.id && e.eventType === "custom" && e.data.guardViolation !== void 0
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
function toAsciiTree(graph) {
|
|
1543
|
+
if (graph.nodes.size === 0) return "(empty graph)";
|
|
1544
|
+
const now = Date.now();
|
|
1545
|
+
const endTime = graph.endTime ?? now;
|
|
1546
|
+
const lines = [];
|
|
1547
|
+
function renderNode(nodeId, prefix, isLast, isRoot) {
|
|
1548
|
+
const node = graph.nodes.get(nodeId);
|
|
1549
|
+
if (!node) return;
|
|
1550
|
+
const icon = STATUS_ICONS[node.status];
|
|
1551
|
+
const duration = nodeDuration(node, endTime);
|
|
1552
|
+
const genAi = getGenAiInfo(node);
|
|
1553
|
+
const violation = hasViolation(node, graph) ? " \u26A0" : "";
|
|
1554
|
+
const errorInfo = node.status === "failed" && node.metadata.error ? ` \u2014 ${node.metadata.error}` : "";
|
|
1555
|
+
const timeoutInfo = node.status === "timeout" ? " [TIMEOUT]" : "";
|
|
1556
|
+
const hungInfo = node.status === "hung" ? " [HUNG]" : "";
|
|
1557
|
+
const connector = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1558
|
+
const line = `${prefix}${connector}${icon} ${node.name} (${node.type}) ${duration}${genAi}${violation}${timeoutInfo}${hungInfo}${errorInfo}`;
|
|
1559
|
+
lines.push(line);
|
|
1560
|
+
const children = getChildren(graph, nodeId);
|
|
1561
|
+
const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
|
|
1562
|
+
for (let i = 0; i < children.length; i++) {
|
|
1563
|
+
renderNode(children[i].id, childPrefix, i === children.length - 1, false);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
renderNode(graph.rootNodeId, "", true, true);
|
|
1567
|
+
return lines.join("\n");
|
|
1568
|
+
}
|
|
1569
|
+
function toTimeline(graph) {
|
|
1570
|
+
if (graph.nodes.size === 0) return "(empty graph)";
|
|
1571
|
+
const now = Date.now();
|
|
1572
|
+
const graphStart = graph.startTime;
|
|
1573
|
+
const graphEnd = graph.endTime ?? now;
|
|
1574
|
+
const totalDuration = graphEnd - graphStart;
|
|
1575
|
+
if (totalDuration <= 0) return "(zero duration)";
|
|
1576
|
+
const barWidth = 60;
|
|
1577
|
+
const lines = [];
|
|
1578
|
+
const scaleLabels = [];
|
|
1579
|
+
const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
|
|
1580
|
+
for (let i = 0; i <= tickCount; i++) {
|
|
1581
|
+
const t = totalDuration * i / tickCount;
|
|
1582
|
+
scaleLabels.push(formatDuration(t));
|
|
1583
|
+
}
|
|
1584
|
+
let header = "";
|
|
1585
|
+
for (let i = 0; i < scaleLabels.length; i++) {
|
|
1586
|
+
const pos = Math.round(barWidth * i / tickCount);
|
|
1587
|
+
while (header.length < pos) header += " ";
|
|
1588
|
+
header += scaleLabels[i];
|
|
1589
|
+
}
|
|
1590
|
+
lines.push(header);
|
|
1591
|
+
let tickLine = "";
|
|
1592
|
+
for (let i = 0; i < barWidth; i++) {
|
|
1593
|
+
const tickPos = tickCount > 0 ? i * tickCount / barWidth : 0;
|
|
1594
|
+
if (Number.isInteger(Math.round(tickPos * 100) / 100) && Math.abs(tickPos - Math.round(tickPos)) < 0.01) {
|
|
1595
|
+
tickLine += "\u253C";
|
|
1596
|
+
} else {
|
|
1597
|
+
tickLine += "\u2500";
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
lines.push(tickLine);
|
|
1601
|
+
const orderedNodes = [];
|
|
1602
|
+
function collectNodes(nodeId) {
|
|
1603
|
+
const node = graph.nodes.get(nodeId);
|
|
1604
|
+
if (!node) return;
|
|
1605
|
+
orderedNodes.push(node);
|
|
1606
|
+
const children = getChildren(graph, nodeId);
|
|
1607
|
+
for (const child of children) {
|
|
1608
|
+
collectNodes(child.id);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
collectNodes(graph.rootNodeId);
|
|
1612
|
+
for (const node of orderedNodes) {
|
|
1613
|
+
const nodeStart = node.startTime - graphStart;
|
|
1614
|
+
const nodeEnd = (node.endTime ?? now) - graphStart;
|
|
1615
|
+
const startCol = Math.round(nodeStart / totalDuration * barWidth);
|
|
1616
|
+
const endCol = Math.max(startCol + 1, Math.round(nodeEnd / totalDuration * barWidth));
|
|
1617
|
+
let bar = "";
|
|
1618
|
+
for (let i = 0; i < barWidth; i++) {
|
|
1619
|
+
if (i >= startCol && i < endCol) {
|
|
1620
|
+
bar += "\u2588";
|
|
1621
|
+
} else {
|
|
1622
|
+
bar += " ";
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
const icon = STATUS_ICONS[node.status];
|
|
1626
|
+
const duration = nodeDuration(node, graphEnd);
|
|
1627
|
+
const violation = hasViolation(node, graph) ? " \u26A0" : "";
|
|
1628
|
+
lines.push(`${bar} ${icon} ${node.name} (${duration})${violation}`);
|
|
1629
|
+
}
|
|
1630
|
+
return lines.join("\n");
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/trace-cli.ts
|
|
1634
|
+
function getTracesDir(argv) {
|
|
1635
|
+
const idx = argv.indexOf("--traces-dir");
|
|
1636
|
+
if (idx !== -1 && argv[idx + 1]) {
|
|
1637
|
+
return (0, import_path2.resolve)(argv[idx + 1]);
|
|
1638
|
+
}
|
|
1639
|
+
return (0, import_path2.resolve)("./traces");
|
|
1640
|
+
}
|
|
1641
|
+
function getFlag(argv, name) {
|
|
1642
|
+
const idx = argv.indexOf(name);
|
|
1643
|
+
if (idx !== -1 && argv[idx + 1]) {
|
|
1644
|
+
return argv[idx + 1];
|
|
1645
|
+
}
|
|
1646
|
+
return void 0;
|
|
1647
|
+
}
|
|
1648
|
+
function printTraceHelp() {
|
|
1649
|
+
console.log(
|
|
1650
|
+
`
|
|
1651
|
+
AgentFlow Trace \u2014 inspect saved execution traces.
|
|
1652
|
+
|
|
1653
|
+
Usage:
|
|
1654
|
+
agentflow trace <command> [options]
|
|
1655
|
+
|
|
1656
|
+
Commands:
|
|
1657
|
+
list [--status <status>] [--limit <n>] List saved traces
|
|
1658
|
+
show <graph-id> Show trace as ASCII tree
|
|
1659
|
+
timeline <graph-id> Show trace as timeline waterfall
|
|
1660
|
+
stuck Show all stuck/hung/timeout spans
|
|
1661
|
+
loops [--threshold <n>] Detect reasoning loops
|
|
1662
|
+
|
|
1663
|
+
Options:
|
|
1664
|
+
--traces-dir <path> Directory containing trace files (default: ./traces)
|
|
1665
|
+
|
|
1666
|
+
Examples:
|
|
1667
|
+
agentflow trace list --status failed --limit 10
|
|
1668
|
+
agentflow trace show abc-123
|
|
1669
|
+
agentflow trace timeline abc-123
|
|
1670
|
+
agentflow trace stuck
|
|
1671
|
+
agentflow trace loops --threshold 10
|
|
1672
|
+
`.trim()
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
async function traceList(argv) {
|
|
1676
|
+
const dir = getTracesDir(argv);
|
|
1677
|
+
const store = createTraceStore(dir);
|
|
1678
|
+
const status = getFlag(argv, "--status");
|
|
1679
|
+
const limitStr = getFlag(argv, "--limit");
|
|
1680
|
+
const limit = limitStr ? Number.parseInt(limitStr, 10) : void 0;
|
|
1681
|
+
const graphs = await store.list({ status, limit });
|
|
1682
|
+
if (graphs.length === 0) {
|
|
1683
|
+
console.log("No traces found.");
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
console.log(`Found ${graphs.length} trace(s) in ${dir}:
|
|
1687
|
+
`);
|
|
1688
|
+
for (const g of graphs) {
|
|
1689
|
+
const duration = g.endTime ? `${((g.endTime - g.startTime) / 1e3).toFixed(1)}s` : "running";
|
|
1690
|
+
const date = new Date(g.startTime).toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
|
|
1691
|
+
const icon = g.status === "completed" ? "\u2713" : g.status === "failed" ? "\u2717" : "\u231B";
|
|
1692
|
+
console.log(
|
|
1693
|
+
` ${icon} ${g.id} ${g.status.padEnd(10)} ${duration.padEnd(8)} ${date} ${g.agentId}`
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
async function traceShow(argv) {
|
|
1698
|
+
const dir = getTracesDir(argv);
|
|
1699
|
+
const store = createTraceStore(dir);
|
|
1700
|
+
const showIdx = argv.indexOf("show");
|
|
1701
|
+
const graphId = showIdx !== -1 ? argv[showIdx + 1] : void 0;
|
|
1702
|
+
if (!graphId || graphId.startsWith("--")) {
|
|
1703
|
+
console.error("Usage: agentflow trace show <graph-id>");
|
|
1704
|
+
process.exit(1);
|
|
1705
|
+
}
|
|
1706
|
+
let graph = await store.get(graphId);
|
|
1707
|
+
if (!graph) {
|
|
1708
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
1709
|
+
const { join: join5 } = await import("path");
|
|
1710
|
+
const fname = graphId.endsWith(".json") ? graphId : `${graphId}.json`;
|
|
1711
|
+
try {
|
|
1712
|
+
const { loadGraph: loadGraph2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
|
|
1713
|
+
const content = await readFile2(join5(dir, fname), "utf-8");
|
|
1714
|
+
graph = loadGraph2(content);
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (!graph) {
|
|
1719
|
+
console.error(`Trace "${graphId}" not found in ${dir}`);
|
|
1720
|
+
process.exit(1);
|
|
1721
|
+
}
|
|
1722
|
+
console.log(`Trace: ${graph.id} (${graph.status})
|
|
1723
|
+
`);
|
|
1724
|
+
console.log(toAsciiTree(graph));
|
|
1725
|
+
}
|
|
1726
|
+
async function traceTimeline(argv) {
|
|
1727
|
+
const dir = getTracesDir(argv);
|
|
1728
|
+
const store = createTraceStore(dir);
|
|
1729
|
+
const timelineIdx = argv.indexOf("timeline");
|
|
1730
|
+
const graphId = timelineIdx !== -1 ? argv[timelineIdx + 1] : void 0;
|
|
1731
|
+
if (!graphId || graphId.startsWith("--")) {
|
|
1732
|
+
console.error("Usage: agentflow trace timeline <graph-id>");
|
|
1733
|
+
process.exit(1);
|
|
1734
|
+
}
|
|
1735
|
+
let graph = await store.get(graphId);
|
|
1736
|
+
if (!graph) {
|
|
1737
|
+
const { readFile: readFile2 } = await import("fs/promises");
|
|
1738
|
+
const { join: join5 } = await import("path");
|
|
1739
|
+
const fname = graphId.endsWith(".json") ? graphId : `${graphId}.json`;
|
|
1740
|
+
try {
|
|
1741
|
+
const { loadGraph: loadGraph2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
|
|
1742
|
+
const content = await readFile2(join5(dir, fname), "utf-8");
|
|
1743
|
+
graph = loadGraph2(content);
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
if (!graph) {
|
|
1748
|
+
console.error(`Trace "${graphId}" not found in ${dir}`);
|
|
1749
|
+
process.exit(1);
|
|
1750
|
+
}
|
|
1751
|
+
console.log(`Trace: ${graph.id} (${graph.status})
|
|
1752
|
+
`);
|
|
1753
|
+
console.log(toTimeline(graph));
|
|
1754
|
+
}
|
|
1755
|
+
async function traceStuck(argv) {
|
|
1756
|
+
const dir = getTracesDir(argv);
|
|
1757
|
+
const store = createTraceStore(dir);
|
|
1758
|
+
const stuck = await store.getStuckSpans();
|
|
1759
|
+
if (stuck.length === 0) {
|
|
1760
|
+
console.log("No stuck spans found.");
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
console.log(`Found ${stuck.length} stuck span(s):
|
|
1764
|
+
`);
|
|
1765
|
+
for (const node of stuck) {
|
|
1766
|
+
const elapsed = Date.now() - node.startTime;
|
|
1767
|
+
const icon = node.status === "timeout" ? "\u231B" : node.status === "hung" ? "\u231B" : "\u231B";
|
|
1768
|
+
console.log(
|
|
1769
|
+
` ${icon} ${node.id} ${node.type.padEnd(10)} ${node.name.padEnd(20)} ${node.status.padEnd(8)} ${(elapsed / 1e3).toFixed(0)}s`
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
async function traceLoops(argv) {
|
|
1774
|
+
const dir = getTracesDir(argv);
|
|
1775
|
+
const store = createTraceStore(dir);
|
|
1776
|
+
const thresholdStr = getFlag(argv, "--threshold");
|
|
1777
|
+
const threshold = thresholdStr ? Number.parseInt(thresholdStr, 10) : void 0;
|
|
1778
|
+
const loops = await store.getReasoningLoops(threshold);
|
|
1779
|
+
if (loops.length === 0) {
|
|
1780
|
+
console.log("No reasoning loops detected.");
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
console.log(`Found reasoning loops in ${loops.length} trace(s):
|
|
1784
|
+
`);
|
|
1785
|
+
for (const { graphId, nodes } of loops) {
|
|
1786
|
+
console.log(` Graph: ${graphId}`);
|
|
1787
|
+
for (const node of nodes) {
|
|
1788
|
+
console.log(` - ${node.id} (${node.type}: ${node.name})`);
|
|
1789
|
+
}
|
|
1790
|
+
console.log("");
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
async function handleTrace(argv) {
|
|
1794
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
1795
|
+
printTraceHelp();
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
const traceIdx = argv.indexOf("trace");
|
|
1799
|
+
const subcommand = traceIdx !== -1 ? argv[traceIdx + 1] : void 0;
|
|
1800
|
+
switch (subcommand) {
|
|
1801
|
+
case "list":
|
|
1802
|
+
await traceList(argv);
|
|
1803
|
+
break;
|
|
1804
|
+
case "show":
|
|
1805
|
+
await traceShow(argv);
|
|
1806
|
+
break;
|
|
1807
|
+
case "timeline":
|
|
1808
|
+
await traceTimeline(argv);
|
|
1809
|
+
break;
|
|
1810
|
+
case "stuck":
|
|
1811
|
+
await traceStuck(argv);
|
|
1812
|
+
break;
|
|
1813
|
+
case "loops":
|
|
1814
|
+
await traceLoops(argv);
|
|
1815
|
+
break;
|
|
1816
|
+
default:
|
|
1817
|
+
printTraceHelp();
|
|
1818
|
+
break;
|
|
1819
|
+
}
|
|
1218
1820
|
}
|
|
1219
1821
|
|
|
1220
1822
|
// src/watch.ts
|
|
1221
1823
|
var import_node_fs4 = require("fs");
|
|
1222
|
-
var import_node_path3 = require("path");
|
|
1223
1824
|
var import_node_os = require("os");
|
|
1825
|
+
var import_node_path3 = require("path");
|
|
1826
|
+
|
|
1827
|
+
// src/watch-alerts.ts
|
|
1828
|
+
var import_node_child_process3 = require("child_process");
|
|
1829
|
+
var import_node_http = require("http");
|
|
1830
|
+
var import_node_https = require("https");
|
|
1831
|
+
function formatAlertMessage(payload) {
|
|
1832
|
+
const time = new Date(payload.timestamp).toISOString();
|
|
1833
|
+
const arrow = `${payload.previousStatus} \u2192 ${payload.currentStatus}`;
|
|
1834
|
+
return [
|
|
1835
|
+
`[ALERT] ${payload.condition}: "${payload.agentId}"`,
|
|
1836
|
+
` Status: ${arrow}`,
|
|
1837
|
+
payload.detail ? ` Detail: ${payload.detail}` : null,
|
|
1838
|
+
` File: ${payload.file}`,
|
|
1839
|
+
` Time: ${time}`
|
|
1840
|
+
].filter(Boolean).join("\n");
|
|
1841
|
+
}
|
|
1842
|
+
function formatTelegram(payload) {
|
|
1843
|
+
const icon = payload.condition === "recovery" ? "\u2705" : "\u26A0\uFE0F";
|
|
1844
|
+
const time = new Date(payload.timestamp).toLocaleTimeString();
|
|
1845
|
+
return [
|
|
1846
|
+
`${icon} *AgentFlow Alert*`,
|
|
1847
|
+
`*${payload.condition}*: \`${payload.agentId}\``,
|
|
1848
|
+
`Status: ${payload.previousStatus} \u2192 ${payload.currentStatus}`,
|
|
1849
|
+
payload.detail ? `Detail: ${payload.detail.slice(0, 200)}` : null,
|
|
1850
|
+
`Time: ${time}`
|
|
1851
|
+
].filter(Boolean).join("\n");
|
|
1852
|
+
}
|
|
1853
|
+
async function sendAlert(payload, channel) {
|
|
1854
|
+
try {
|
|
1855
|
+
switch (channel.type) {
|
|
1856
|
+
case "stdout":
|
|
1857
|
+
sendStdout(payload);
|
|
1858
|
+
break;
|
|
1859
|
+
case "telegram":
|
|
1860
|
+
await sendTelegram(payload, channel.botToken, channel.chatId);
|
|
1861
|
+
break;
|
|
1862
|
+
case "webhook":
|
|
1863
|
+
await sendWebhook(payload, channel.url);
|
|
1864
|
+
break;
|
|
1865
|
+
case "command":
|
|
1866
|
+
await sendCommand(payload, channel.cmd);
|
|
1867
|
+
break;
|
|
1868
|
+
}
|
|
1869
|
+
} catch (err) {
|
|
1870
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1871
|
+
console.error(`[agentflow] Failed to send ${channel.type} alert: ${msg}`);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
function sendStdout(payload) {
|
|
1875
|
+
console.log(formatAlertMessage(payload));
|
|
1876
|
+
}
|
|
1877
|
+
function sendTelegram(payload, botToken, chatId) {
|
|
1878
|
+
const body = JSON.stringify({
|
|
1879
|
+
chat_id: chatId,
|
|
1880
|
+
text: formatTelegram(payload),
|
|
1881
|
+
parse_mode: "Markdown"
|
|
1882
|
+
});
|
|
1883
|
+
return new Promise((resolve6, reject) => {
|
|
1884
|
+
const req = (0, import_node_https.request)(
|
|
1885
|
+
`https://api.telegram.org/bot${botToken}/sendMessage`,
|
|
1886
|
+
{
|
|
1887
|
+
method: "POST",
|
|
1888
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
|
|
1889
|
+
},
|
|
1890
|
+
(res) => {
|
|
1891
|
+
res.resume();
|
|
1892
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve6();
|
|
1893
|
+
else reject(new Error(`Telegram API returned ${res.statusCode}`));
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
req.on("error", reject);
|
|
1897
|
+
req.write(body);
|
|
1898
|
+
req.end();
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
function sendWebhook(payload, url) {
|
|
1902
|
+
const body = JSON.stringify(payload);
|
|
1903
|
+
const isHttps = url.startsWith("https");
|
|
1904
|
+
const doRequest = isHttps ? import_node_https.request : import_node_http.request;
|
|
1905
|
+
return new Promise((resolve6, reject) => {
|
|
1906
|
+
const req = doRequest(
|
|
1907
|
+
url,
|
|
1908
|
+
{
|
|
1909
|
+
method: "POST",
|
|
1910
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }
|
|
1911
|
+
},
|
|
1912
|
+
(res) => {
|
|
1913
|
+
res.resume();
|
|
1914
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) resolve6();
|
|
1915
|
+
else reject(new Error(`Webhook returned ${res.statusCode}`));
|
|
1916
|
+
}
|
|
1917
|
+
);
|
|
1918
|
+
req.on("error", reject);
|
|
1919
|
+
req.setTimeout(1e4, () => {
|
|
1920
|
+
req.destroy(new Error("Webhook timeout"));
|
|
1921
|
+
});
|
|
1922
|
+
req.write(body);
|
|
1923
|
+
req.end();
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
function sendCommand(payload, cmd) {
|
|
1927
|
+
return new Promise((resolve6, reject) => {
|
|
1928
|
+
const env = {
|
|
1929
|
+
...process.env,
|
|
1930
|
+
AGENTFLOW_ALERT_AGENT: payload.agentId,
|
|
1931
|
+
AGENTFLOW_ALERT_CONDITION: payload.condition,
|
|
1932
|
+
AGENTFLOW_ALERT_STATUS: payload.currentStatus,
|
|
1933
|
+
AGENTFLOW_ALERT_PREVIOUS_STATUS: payload.previousStatus,
|
|
1934
|
+
AGENTFLOW_ALERT_DETAIL: payload.detail,
|
|
1935
|
+
AGENTFLOW_ALERT_FILE: payload.file,
|
|
1936
|
+
AGENTFLOW_ALERT_TIMESTAMP: String(payload.timestamp)
|
|
1937
|
+
};
|
|
1938
|
+
(0, import_node_child_process3.exec)(cmd, { env, timeout: 3e4 }, (err) => {
|
|
1939
|
+
if (err) reject(err);
|
|
1940
|
+
else resolve6();
|
|
1941
|
+
});
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1224
1944
|
|
|
1225
1945
|
// src/watch-state.ts
|
|
1226
1946
|
var import_node_fs3 = require("fs");
|
|
@@ -1286,7 +2006,9 @@ function detectTransitions(previous, currentRecords, config, now) {
|
|
|
1286
2006
|
const hasError = config.alertConditions.some((c) => c.type === "error");
|
|
1287
2007
|
const hasRecovery = config.alertConditions.some((c) => c.type === "recovery");
|
|
1288
2008
|
const staleConditions = config.alertConditions.filter((c) => c.type === "stale");
|
|
1289
|
-
const consecutiveConditions = config.alertConditions.filter(
|
|
2009
|
+
const consecutiveConditions = config.alertConditions.filter(
|
|
2010
|
+
(c) => c.type === "consecutive-errors"
|
|
2011
|
+
);
|
|
1290
2012
|
const byAgent = /* @__PURE__ */ new Map();
|
|
1291
2013
|
for (const r of currentRecords) {
|
|
1292
2014
|
const existing = byAgent.get(r.id);
|
|
@@ -1310,14 +2032,16 @@ function detectTransitions(previous, currentRecords, config, now) {
|
|
|
1310
2032
|
for (const cond of consecutiveConditions) {
|
|
1311
2033
|
if (newConsec === cond.threshold) {
|
|
1312
2034
|
if (canAlert(prev, `consecutive-errors:${cond.threshold}`, config.cooldownMs, now)) {
|
|
1313
|
-
alerts.push(
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
2035
|
+
alerts.push(
|
|
2036
|
+
makePayload(
|
|
2037
|
+
agentId,
|
|
2038
|
+
`consecutive-errors (${cond.threshold})`,
|
|
2039
|
+
prevStatus,
|
|
2040
|
+
currStatus,
|
|
2041
|
+
{ ...record, detail: `${newConsec} consecutive errors. ${record.detail}` },
|
|
2042
|
+
config.dirs
|
|
2043
|
+
)
|
|
2044
|
+
);
|
|
1321
2045
|
}
|
|
1322
2046
|
}
|
|
1323
2047
|
}
|
|
@@ -1326,14 +2050,16 @@ function detectTransitions(previous, currentRecords, config, now) {
|
|
|
1326
2050
|
if (sinceActive > cond.durationMs && record.lastActive > 0) {
|
|
1327
2051
|
if (canAlert(prev, "stale", config.cooldownMs, now)) {
|
|
1328
2052
|
const mins = Math.floor(sinceActive / 6e4);
|
|
1329
|
-
alerts.push(
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
2053
|
+
alerts.push(
|
|
2054
|
+
makePayload(
|
|
2055
|
+
agentId,
|
|
2056
|
+
"stale",
|
|
2057
|
+
prevStatus,
|
|
2058
|
+
currStatus,
|
|
2059
|
+
{ ...record, detail: `No update for ${mins}m. ${record.detail}` },
|
|
2060
|
+
config.dirs
|
|
2061
|
+
)
|
|
2062
|
+
);
|
|
1337
2063
|
}
|
|
1338
2064
|
}
|
|
1339
2065
|
}
|
|
@@ -1346,14 +2072,19 @@ function detectTransitions(previous, currentRecords, config, now) {
|
|
|
1346
2072
|
if (canAlert(prev, "stale-auto", config.cooldownMs, now)) {
|
|
1347
2073
|
const mins = Math.floor(sinceActive / 6e4);
|
|
1348
2074
|
const expectedMins = Math.floor(expectedInterval / 6e4);
|
|
1349
|
-
alerts.push(
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
2075
|
+
alerts.push(
|
|
2076
|
+
makePayload(
|
|
2077
|
+
agentId,
|
|
2078
|
+
"stale (auto)",
|
|
2079
|
+
prevStatus,
|
|
2080
|
+
currStatus,
|
|
2081
|
+
{
|
|
2082
|
+
...record,
|
|
2083
|
+
detail: `No update for ${mins}m (expected every ~${expectedMins}m). ${record.detail}`
|
|
2084
|
+
},
|
|
2085
|
+
config.dirs
|
|
2086
|
+
)
|
|
2087
|
+
);
|
|
1357
2088
|
}
|
|
1358
2089
|
}
|
|
1359
2090
|
}
|
|
@@ -1412,118 +2143,6 @@ function makePayload(agentId, condition, previousStatus, currentStatus, record,
|
|
|
1412
2143
|
};
|
|
1413
2144
|
}
|
|
1414
2145
|
|
|
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
2146
|
// src/watch.ts
|
|
1528
2147
|
function parseWatchArgs(argv) {
|
|
1529
2148
|
const dirs = [];
|
|
@@ -1565,7 +2184,9 @@ function parseWatchArgs(argv) {
|
|
|
1565
2184
|
if (botToken && chatId) {
|
|
1566
2185
|
notifyChannels.push({ type: "telegram", botToken, chatId });
|
|
1567
2186
|
} else {
|
|
1568
|
-
console.error(
|
|
2187
|
+
console.error(
|
|
2188
|
+
"Warning: --notify telegram requires AGENTFLOW_TELEGRAM_BOT_TOKEN and AGENTFLOW_TELEGRAM_CHAT_ID env vars"
|
|
2189
|
+
);
|
|
1569
2190
|
}
|
|
1570
2191
|
} else if (val.startsWith("webhook:")) {
|
|
1571
2192
|
notifyChannels.push({ type: "webhook", url: val.slice(8) });
|
|
@@ -1617,7 +2238,8 @@ function parseWatchArgs(argv) {
|
|
|
1617
2238
|
};
|
|
1618
2239
|
}
|
|
1619
2240
|
function printWatchUsage() {
|
|
1620
|
-
console.log(
|
|
2241
|
+
console.log(
|
|
2242
|
+
`
|
|
1621
2243
|
AgentFlow Watch \u2014 headless alert system for agent infrastructure.
|
|
1622
2244
|
|
|
1623
2245
|
Polls directories for JSON/JSONL files, detects failures and stale
|
|
@@ -1660,7 +2282,8 @@ Examples:
|
|
|
1660
2282
|
agentflow watch ./data ./cron --notify telegram --poll 60
|
|
1661
2283
|
agentflow watch ./traces --notify webhook:https://hooks.slack.com/... --alert-on consecutive-errors:3
|
|
1662
2284
|
agentflow watch ./data --notify "command:curl -X POST https://my-pagerduty/alert"
|
|
1663
|
-
`.trim()
|
|
2285
|
+
`.trim()
|
|
2286
|
+
);
|
|
1664
2287
|
}
|
|
1665
2288
|
function startWatch(argv) {
|
|
1666
2289
|
const config = parseWatchArgs(argv);
|
|
@@ -1689,7 +2312,9 @@ agentflow watch started`);
|
|
|
1689
2312
|
console.log(` Directories: ${valid.join(", ")}`);
|
|
1690
2313
|
console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
|
|
1691
2314
|
console.log(` Alert on: ${condLabels.join(", ")}`);
|
|
1692
|
-
console.log(
|
|
2315
|
+
console.log(
|
|
2316
|
+
` Notify: stdout${channelLabels.length > 0 ? ", " + channelLabels.join(", ") : ""}`
|
|
2317
|
+
);
|
|
1693
2318
|
console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
|
|
1694
2319
|
console.log(` State: ${config.stateFilePath}`);
|
|
1695
2320
|
console.log(` Hostname: ${(0, import_node_os.hostname)()}`);
|
|
@@ -1715,9 +2340,13 @@ agentflow watch started`);
|
|
|
1715
2340
|
if (pollCount % 10 === 0) {
|
|
1716
2341
|
const agentCount = Object.keys(state.agents).length;
|
|
1717
2342
|
const errorCount = Object.values(state.agents).filter((a) => a.lastStatus === "error").length;
|
|
1718
|
-
const runningCount = Object.values(state.agents).filter(
|
|
2343
|
+
const runningCount = Object.values(state.agents).filter(
|
|
2344
|
+
(a) => a.lastStatus === "running"
|
|
2345
|
+
).length;
|
|
1719
2346
|
const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
1720
|
-
console.log(
|
|
2347
|
+
console.log(
|
|
2348
|
+
`[${time}] heartbeat: ${agentCount} agents, ${runningCount} running, ${errorCount} errors, ${files.length} files`
|
|
2349
|
+
);
|
|
1721
2350
|
}
|
|
1722
2351
|
}
|
|
1723
2352
|
poll();
|
|
@@ -1735,7 +2364,8 @@ agentflow watch started`);
|
|
|
1735
2364
|
|
|
1736
2365
|
// src/cli.ts
|
|
1737
2366
|
function printHelp() {
|
|
1738
|
-
console.log(
|
|
2367
|
+
console.log(
|
|
2368
|
+
`
|
|
1739
2369
|
AgentFlow CLI \u2014 execution tracing and live monitoring for AI agent systems.
|
|
1740
2370
|
|
|
1741
2371
|
Usage:
|
|
@@ -1745,6 +2375,7 @@ Commands:
|
|
|
1745
2375
|
run [options] -- <cmd> Wrap a command with automatic execution tracing
|
|
1746
2376
|
live [dir...] [options] Real-time terminal monitor (auto-detects any JSON/JSONL)
|
|
1747
2377
|
watch [dir...] [options] Headless alert system \u2014 detects failures, sends notifications
|
|
2378
|
+
trace <command> [options] Inspect saved execution traces (list, show, timeline, stuck, loops)
|
|
1748
2379
|
|
|
1749
2380
|
Run \`agentflow <command> --help\` for command-specific options.
|
|
1750
2381
|
|
|
@@ -1754,7 +2385,8 @@ Examples:
|
|
|
1754
2385
|
agentflow live ./traces ./cron ./workers -R
|
|
1755
2386
|
agentflow watch ./data --alert-on error --notify telegram
|
|
1756
2387
|
agentflow watch ./data ./cron --alert-on stale:15m --notify webhook:https://...
|
|
1757
|
-
`.trim()
|
|
2388
|
+
`.trim()
|
|
2389
|
+
);
|
|
1758
2390
|
}
|
|
1759
2391
|
function parseRunArgs(argv) {
|
|
1760
2392
|
const result = {
|
|
@@ -1815,7 +2447,8 @@ function parseRunArgs(argv) {
|
|
|
1815
2447
|
return result;
|
|
1816
2448
|
}
|
|
1817
2449
|
function printRunUsage() {
|
|
1818
|
-
console.log(
|
|
2450
|
+
console.log(
|
|
2451
|
+
`
|
|
1819
2452
|
AgentFlow Run \u2014 wrap any command with automatic execution tracing.
|
|
1820
2453
|
|
|
1821
2454
|
Usage:
|
|
@@ -1833,7 +2466,8 @@ Examples:
|
|
|
1833
2466
|
agentflow run -- python -m myagent process
|
|
1834
2467
|
agentflow run --watch-dir ./data -- python worker.py
|
|
1835
2468
|
agentflow run --traces-dir ./my-traces --agent-id recon -- node agent.js
|
|
1836
|
-
`.trim()
|
|
2469
|
+
`.trim()
|
|
2470
|
+
);
|
|
1837
2471
|
}
|
|
1838
2472
|
async function runCommand(argv) {
|
|
1839
2473
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
@@ -1842,7 +2476,9 @@ async function runCommand(argv) {
|
|
|
1842
2476
|
}
|
|
1843
2477
|
const parsed = parseRunArgs(argv);
|
|
1844
2478
|
if (parsed.command.length === 0) {
|
|
1845
|
-
console.error(
|
|
2479
|
+
console.error(
|
|
2480
|
+
"Error: No command specified. Use -- to separate agentflow flags from the command."
|
|
2481
|
+
);
|
|
1846
2482
|
console.error("Example: agentflow run -- python -m myagent process");
|
|
1847
2483
|
process.exit(1);
|
|
1848
2484
|
}
|
|
@@ -1866,15 +2502,17 @@ async function runCommand(argv) {
|
|
|
1866
2502
|
try {
|
|
1867
2503
|
const result = await runTraced(config);
|
|
1868
2504
|
console.log("");
|
|
1869
|
-
console.log(
|
|
2505
|
+
console.log(
|
|
2506
|
+
`\u2705 Command completed (exit code ${result.exitCode}, ${result.duration.toFixed(1)}s)`
|
|
2507
|
+
);
|
|
1870
2508
|
if (result.tracePaths.length > 0) {
|
|
1871
2509
|
console.log("\u{1F4DD} Traces saved:");
|
|
1872
2510
|
const orchPath = result.tracePaths[0];
|
|
1873
|
-
const orchName = (0,
|
|
2511
|
+
const orchName = (0, import_path3.basename)(orchPath, ".json").split("-")[0] ?? "orchestrator";
|
|
1874
2512
|
console.log(` ${orchName.padEnd(14)} \u2192 ${orchPath}`);
|
|
1875
2513
|
for (let i = 1; i < result.tracePaths.length; i++) {
|
|
1876
2514
|
const tPath = result.tracePaths[i];
|
|
1877
|
-
const name = (0,
|
|
2515
|
+
const name = (0, import_path3.basename)(tPath, ".json").replace(/-\d{4}-.*$/, "");
|
|
1878
2516
|
const isLast = i === result.tracePaths.length - 1;
|
|
1879
2517
|
const prefix = isLast ? "\u2514\u2500" : "\u251C\u2500";
|
|
1880
2518
|
console.log(` ${prefix} ${name.padEnd(12)} \u2192 ${tPath} (state changed)`);
|
|
@@ -1891,7 +2529,7 @@ async function runCommand(argv) {
|
|
|
1891
2529
|
}
|
|
1892
2530
|
async function main() {
|
|
1893
2531
|
const argv = process.argv.slice(2);
|
|
1894
|
-
const knownCommands = ["run", "live", "watch"];
|
|
2532
|
+
const knownCommands = ["run", "live", "watch", "trace"];
|
|
1895
2533
|
if (argv.length === 0 || !knownCommands.includes(argv[0]) && (argv.includes("--help") || argv.includes("-h"))) {
|
|
1896
2534
|
printHelp();
|
|
1897
2535
|
process.exit(0);
|
|
@@ -1907,6 +2545,9 @@ async function main() {
|
|
|
1907
2545
|
case "watch":
|
|
1908
2546
|
startWatch(argv);
|
|
1909
2547
|
break;
|
|
2548
|
+
case "trace":
|
|
2549
|
+
await handleTrace(argv);
|
|
2550
|
+
break;
|
|
1910
2551
|
default:
|
|
1911
2552
|
if (!subcommand?.startsWith("-")) {
|
|
1912
2553
|
startLive(["live", ...argv]);
|