agentflow-core 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,212 +1,24 @@
1
1
  import {
2
2
  createGraphBuilder,
3
+ findWaitingOn,
4
+ getChildren,
5
+ getCriticalPath,
6
+ getDepth,
7
+ getDuration,
8
+ getFailures,
9
+ getHungNodes,
10
+ getNode,
11
+ getParent,
12
+ getStats,
13
+ getSubtree,
14
+ getTraceTree,
3
15
  graphToJson,
16
+ groupByTraceId,
4
17
  loadGraph,
5
- runTraced
6
- } from "./chunk-TT5DLU73.js";
7
-
8
- // src/graph-stitch.ts
9
- function groupByTraceId(graphs) {
10
- const groups = /* @__PURE__ */ new Map();
11
- for (const g of graphs) {
12
- if (!g.traceId) continue;
13
- const arr = groups.get(g.traceId) ?? [];
14
- arr.push(g);
15
- groups.set(g.traceId, arr);
16
- }
17
- return groups;
18
- }
19
- function stitchTrace(graphs) {
20
- if (graphs.length === 0) throw new Error("No graphs to stitch");
21
- const traceId = graphs[0].traceId ?? "";
22
- const graphsBySpan = /* @__PURE__ */ new Map();
23
- const childMap = /* @__PURE__ */ new Map();
24
- let rootGraph = null;
25
- for (const g of graphs) {
26
- if (g.spanId) graphsBySpan.set(g.spanId, g);
27
- if (!g.parentSpanId) {
28
- if (!rootGraph || g.startTime < rootGraph.startTime) rootGraph = g;
29
- }
30
- if (g.parentSpanId) {
31
- const siblings = childMap.get(g.parentSpanId) ?? [];
32
- if (g.spanId) siblings.push(g.spanId);
33
- childMap.set(g.parentSpanId, siblings);
34
- }
35
- }
36
- if (!rootGraph) rootGraph = graphs[0];
37
- let status = "completed";
38
- let endTime = 0;
39
- let startTime = Infinity;
40
- for (const g of graphs) {
41
- startTime = Math.min(startTime, g.startTime);
42
- if (g.status === "failed") status = "failed";
43
- else if (g.status === "running" && status !== "failed") status = "running";
44
- if (g.endTime === null) endTime = null;
45
- else if (endTime !== null) endTime = Math.max(endTime, g.endTime);
46
- }
47
- const frozenChildMap = /* @__PURE__ */ new Map();
48
- for (const [k, v] of childMap) frozenChildMap.set(k, Object.freeze([...v]));
49
- return Object.freeze({
50
- traceId,
51
- graphs: graphsBySpan,
52
- rootGraph,
53
- childMap: frozenChildMap,
54
- startTime,
55
- endTime,
56
- status
57
- });
58
- }
59
- function getTraceTree(trace) {
60
- const result = [];
61
- function walk(spanId) {
62
- const graph = trace.graphs.get(spanId);
63
- if (graph) result.push(graph);
64
- const children = trace.childMap.get(spanId) ?? [];
65
- for (const childSpan of children) walk(childSpan);
66
- }
67
- if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
68
- else result.push(trace.rootGraph);
69
- return result;
70
- }
71
-
72
- // src/graph-query.ts
73
- function getNode(graph, nodeId) {
74
- return graph.nodes.get(nodeId);
75
- }
76
- function getChildren(graph, nodeId) {
77
- const node = graph.nodes.get(nodeId);
78
- if (!node) return [];
79
- const result = [];
80
- for (const childId of node.children) {
81
- const child = graph.nodes.get(childId);
82
- if (child) result.push(child);
83
- }
84
- return result;
85
- }
86
- function getParent(graph, nodeId) {
87
- const node = graph.nodes.get(nodeId);
88
- if (!node || node.parentId === null) return void 0;
89
- return graph.nodes.get(node.parentId);
90
- }
91
- function getFailures(graph) {
92
- const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
93
- return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
94
- }
95
- function getHungNodes(graph) {
96
- return [...graph.nodes.values()].filter(
97
- (node) => node.status === "running" && node.endTime === null
98
- );
99
- }
100
- function getCriticalPath(graph) {
101
- const root = graph.nodes.get(graph.rootNodeId);
102
- if (!root) return [];
103
- function nodeDuration(node) {
104
- const end = node.endTime ?? Date.now();
105
- return end - node.startTime;
106
- }
107
- function dfs(node) {
108
- if (node.children.length === 0) {
109
- return { duration: nodeDuration(node), path: [node] };
110
- }
111
- let bestChild = { duration: -1, path: [] };
112
- for (const childId of node.children) {
113
- const child = graph.nodes.get(childId);
114
- if (!child) continue;
115
- const result = dfs(child);
116
- if (result.duration > bestChild.duration) {
117
- bestChild = result;
118
- }
119
- }
120
- return {
121
- duration: nodeDuration(node) + bestChild.duration,
122
- path: [node, ...bestChild.path]
123
- };
124
- }
125
- return dfs(root).path;
126
- }
127
- function findWaitingOn(graph, nodeId) {
128
- const results = [];
129
- for (const edge of graph.edges) {
130
- if (edge.from === nodeId && edge.type === "waited_on") {
131
- const node = graph.nodes.get(edge.to);
132
- if (node) results.push(node);
133
- }
134
- }
135
- return results;
136
- }
137
- function getSubtree(graph, nodeId) {
138
- const startNode = graph.nodes.get(nodeId);
139
- if (!startNode) return [];
140
- const result = [];
141
- const queue = [...startNode.children];
142
- while (queue.length > 0) {
143
- const currentId = queue.shift();
144
- if (currentId === void 0) break;
145
- const current = graph.nodes.get(currentId);
146
- if (!current) continue;
147
- result.push(current);
148
- queue.push(...current.children);
149
- }
150
- return result;
151
- }
152
- function getDuration(graph) {
153
- const end = graph.endTime ?? Date.now();
154
- return end - graph.startTime;
155
- }
156
- function getDepth(graph) {
157
- const root = graph.nodes.get(graph.rootNodeId);
158
- if (!root) return -1;
159
- function dfs(node, depth) {
160
- if (node.children.length === 0) return depth;
161
- let maxDepth = depth;
162
- for (const childId of node.children) {
163
- const child = graph.nodes.get(childId);
164
- if (!child) continue;
165
- const childDepth = dfs(child, depth + 1);
166
- if (childDepth > maxDepth) maxDepth = childDepth;
167
- }
168
- return maxDepth;
169
- }
170
- return dfs(root, 0);
171
- }
172
- function getStats(graph) {
173
- const byStatus = {
174
- running: 0,
175
- completed: 0,
176
- failed: 0,
177
- hung: 0,
178
- timeout: 0
179
- };
180
- const byType = {
181
- agent: 0,
182
- tool: 0,
183
- subagent: 0,
184
- wait: 0,
185
- decision: 0,
186
- custom: 0
187
- };
188
- let failureCount = 0;
189
- let hungCount = 0;
190
- for (const node of graph.nodes.values()) {
191
- byStatus[node.status]++;
192
- byType[node.type]++;
193
- if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
194
- failureCount++;
195
- }
196
- if (node.status === "running" && node.endTime === null) {
197
- hungCount++;
198
- }
199
- }
200
- return {
201
- totalNodes: graph.nodes.size,
202
- byStatus,
203
- byType,
204
- depth: getDepth(graph),
205
- duration: getDuration(graph),
206
- failureCount,
207
- hungCount
208
- };
209
- }
18
+ runTraced,
19
+ startLive,
20
+ stitchTrace
21
+ } from "./chunk-FJVQYJFB.js";
210
22
  export {
211
23
  createGraphBuilder,
212
24
  findWaitingOn,
@@ -225,5 +37,6 @@ export {
225
37
  groupByTraceId,
226
38
  loadGraph,
227
39
  runTraced,
40
+ startLive,
228
41
  stitchTrace
229
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentflow-core",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Universal execution tracing for AI agent systems",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,435 +0,0 @@
1
- // src/graph-builder.ts
2
- import { randomUUID } from "crypto";
3
- function deepFreeze(obj) {
4
- if (obj === null || typeof obj !== "object") return obj;
5
- if (obj instanceof Map) {
6
- Object.freeze(obj);
7
- for (const value of obj.values()) {
8
- deepFreeze(value);
9
- }
10
- return obj;
11
- }
12
- Object.freeze(obj);
13
- const record = obj;
14
- for (const key of Object.keys(record)) {
15
- const value = record[key];
16
- if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
17
- deepFreeze(value);
18
- }
19
- }
20
- return obj;
21
- }
22
- function createCounterIdGenerator() {
23
- let counter = 0;
24
- return () => {
25
- counter++;
26
- return `node_${String(counter).padStart(3, "0")}`;
27
- };
28
- }
29
- function createGraphBuilder(config) {
30
- const generateId = config?.idGenerator ?? createCounterIdGenerator();
31
- const agentId = config?.agentId ?? "unknown";
32
- const trigger = config?.trigger ?? "manual";
33
- const spanId = randomUUID();
34
- const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? randomUUID();
35
- const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
36
- const graphId = generateId();
37
- const startTime = Date.now();
38
- const nodes = /* @__PURE__ */ new Map();
39
- const edges = [];
40
- const events = [];
41
- const parentStack = [];
42
- let rootNodeId = null;
43
- let built = false;
44
- function assertNotBuilt() {
45
- if (built) {
46
- throw new Error("GraphBuilder: cannot mutate after build() has been called");
47
- }
48
- }
49
- function getNode(nodeId) {
50
- const node = nodes.get(nodeId);
51
- if (!node) {
52
- throw new Error(`GraphBuilder: node "${nodeId}" does not exist`);
53
- }
54
- return node;
55
- }
56
- function recordEvent(nodeId, eventType, data = {}) {
57
- events.push({
58
- timestamp: Date.now(),
59
- eventType,
60
- nodeId,
61
- data
62
- });
63
- }
64
- function buildGraph() {
65
- if (rootNodeId === null) {
66
- throw new Error("GraphBuilder: cannot build a graph with no nodes");
67
- }
68
- let graphStatus = "completed";
69
- for (const node of nodes.values()) {
70
- if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
71
- graphStatus = "failed";
72
- break;
73
- }
74
- if (node.status === "running") {
75
- graphStatus = "running";
76
- }
77
- }
78
- const endTime = graphStatus === "running" ? null : Date.now();
79
- const frozenNodes = new Map(
80
- [...nodes.entries()].map(([id, mNode]) => [
81
- id,
82
- {
83
- id: mNode.id,
84
- type: mNode.type,
85
- name: mNode.name,
86
- startTime: mNode.startTime,
87
- endTime: mNode.endTime,
88
- status: mNode.status,
89
- parentId: mNode.parentId,
90
- children: [...mNode.children],
91
- metadata: { ...mNode.metadata },
92
- state: { ...mNode.state }
93
- }
94
- ])
95
- );
96
- const graph = {
97
- id: graphId,
98
- rootNodeId,
99
- nodes: frozenNodes,
100
- edges: [...edges],
101
- startTime,
102
- endTime,
103
- status: graphStatus,
104
- trigger,
105
- agentId,
106
- events: [...events],
107
- traceId,
108
- spanId,
109
- parentSpanId
110
- };
111
- return deepFreeze(graph);
112
- }
113
- const builder = {
114
- get graphId() {
115
- return graphId;
116
- },
117
- get traceContext() {
118
- return { traceId, spanId };
119
- },
120
- startNode(opts) {
121
- assertNotBuilt();
122
- const id = generateId();
123
- const parentId = opts.parentId ?? parentStack[parentStack.length - 1] ?? null;
124
- if (parentId !== null && !nodes.has(parentId)) {
125
- throw new Error(`GraphBuilder: parent node "${parentId}" does not exist`);
126
- }
127
- const node = {
128
- id,
129
- type: opts.type,
130
- name: opts.name,
131
- startTime: Date.now(),
132
- endTime: null,
133
- status: "running",
134
- parentId,
135
- children: [],
136
- metadata: opts.metadata ? { ...opts.metadata } : {},
137
- state: {}
138
- };
139
- nodes.set(id, node);
140
- if (parentId !== null) {
141
- const parent = nodes.get(parentId);
142
- if (parent) {
143
- parent.children.push(id);
144
- }
145
- edges.push({ from: parentId, to: id, type: "spawned" });
146
- }
147
- if (rootNodeId === null) {
148
- rootNodeId = id;
149
- }
150
- recordEvent(id, "agent_start", { type: opts.type, name: opts.name });
151
- return id;
152
- },
153
- endNode(nodeId, status = "completed") {
154
- assertNotBuilt();
155
- const node = getNode(nodeId);
156
- if (node.endTime !== null) {
157
- throw new Error(
158
- `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
159
- );
160
- }
161
- node.endTime = Date.now();
162
- node.status = status;
163
- recordEvent(nodeId, "agent_end", { status });
164
- },
165
- failNode(nodeId, error) {
166
- assertNotBuilt();
167
- const node = getNode(nodeId);
168
- if (node.endTime !== null) {
169
- throw new Error(
170
- `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
171
- );
172
- }
173
- const errorMessage = error instanceof Error ? error.message : error;
174
- const errorStack = error instanceof Error ? error.stack : void 0;
175
- node.endTime = Date.now();
176
- node.status = "failed";
177
- node.metadata.error = errorMessage;
178
- if (errorStack) {
179
- node.metadata.errorStack = errorStack;
180
- }
181
- recordEvent(nodeId, "tool_error", { error: errorMessage });
182
- },
183
- addEdge(from, to, type) {
184
- assertNotBuilt();
185
- getNode(from);
186
- getNode(to);
187
- edges.push({ from, to, type });
188
- recordEvent(from, "custom", { to, type, action: "edge_add" });
189
- },
190
- pushEvent(event) {
191
- assertNotBuilt();
192
- getNode(event.nodeId);
193
- events.push({
194
- ...event,
195
- timestamp: Date.now()
196
- });
197
- },
198
- updateState(nodeId, state) {
199
- assertNotBuilt();
200
- const node = getNode(nodeId);
201
- Object.assign(node.state, state);
202
- recordEvent(nodeId, "custom", { action: "state_update", ...state });
203
- },
204
- withParent(parentId, fn) {
205
- assertNotBuilt();
206
- getNode(parentId);
207
- parentStack.push(parentId);
208
- try {
209
- return fn();
210
- } finally {
211
- parentStack.pop();
212
- }
213
- },
214
- getSnapshot() {
215
- return buildGraph();
216
- },
217
- build() {
218
- assertNotBuilt();
219
- const graph = buildGraph();
220
- built = true;
221
- return graph;
222
- }
223
- };
224
- return builder;
225
- }
226
-
227
- // src/loader.ts
228
- function toNodesMap(raw) {
229
- if (raw instanceof Map) return raw;
230
- if (Array.isArray(raw)) {
231
- return new Map(raw);
232
- }
233
- if (raw !== null && typeof raw === "object") {
234
- return new Map(Object.entries(raw));
235
- }
236
- return /* @__PURE__ */ new Map();
237
- }
238
- function loadGraph(input) {
239
- const raw = typeof input === "string" ? JSON.parse(input) : input;
240
- const nodes = toNodesMap(raw.nodes);
241
- return {
242
- id: raw.id ?? "",
243
- rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
244
- nodes,
245
- edges: raw.edges ?? [],
246
- startTime: raw.startTime ?? 0,
247
- endTime: raw.endTime ?? null,
248
- status: raw.status ?? "completed",
249
- trigger: raw.trigger ?? "unknown",
250
- agentId: raw.agentId ?? "unknown",
251
- events: raw.events ?? [],
252
- traceId: raw.traceId,
253
- spanId: raw.spanId,
254
- parentSpanId: raw.parentSpanId
255
- };
256
- }
257
- function graphToJson(graph) {
258
- const nodesObj = {};
259
- for (const [id, node] of graph.nodes) {
260
- nodesObj[id] = node;
261
- }
262
- return {
263
- id: graph.id,
264
- rootNodeId: graph.rootNodeId,
265
- nodes: nodesObj,
266
- edges: graph.edges,
267
- startTime: graph.startTime,
268
- endTime: graph.endTime,
269
- status: graph.status,
270
- trigger: graph.trigger,
271
- agentId: graph.agentId,
272
- events: graph.events,
273
- traceId: graph.traceId,
274
- spanId: graph.spanId,
275
- parentSpanId: graph.parentSpanId
276
- };
277
- }
278
-
279
- // src/runner.ts
280
- import { spawnSync } from "child_process";
281
- import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "fs";
282
- import { basename, join, resolve } from "path";
283
- function globToRegex(pattern) {
284
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
285
- return new RegExp(`^${escaped}$`);
286
- }
287
- function snapshotDir(dir, patterns) {
288
- const result = /* @__PURE__ */ new Map();
289
- if (!existsSync(dir)) return result;
290
- for (const entry of readdirSync(dir)) {
291
- if (!patterns.some((re) => re.test(entry))) continue;
292
- const full = join(dir, entry);
293
- try {
294
- const stat = statSync(full);
295
- if (stat.isFile()) {
296
- result.set(full, stat.mtimeMs);
297
- }
298
- } catch {
299
- }
300
- }
301
- return result;
302
- }
303
- function agentIdFromFilename(filePath) {
304
- const base = basename(filePath, ".json");
305
- const cleaned = base.replace(/-state$/, "");
306
- return `alfred-${cleaned}`;
307
- }
308
- function deriveAgentId(command) {
309
- return "orchestrator";
310
- }
311
- function fileTimestamp() {
312
- return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
313
- }
314
- async function runTraced(config) {
315
- const {
316
- command,
317
- agentId = deriveAgentId(command),
318
- trigger = "cli",
319
- tracesDir = "./traces",
320
- watchDirs = [],
321
- watchPatterns = ["*.json"]
322
- } = config;
323
- if (command.length === 0) {
324
- throw new Error("runTraced: command must not be empty");
325
- }
326
- const resolvedTracesDir = resolve(tracesDir);
327
- const patterns = watchPatterns.map(globToRegex);
328
- const orchestrator = createGraphBuilder({ agentId, trigger });
329
- const { traceId, spanId } = orchestrator.traceContext;
330
- const beforeSnapshots = /* @__PURE__ */ new Map();
331
- for (const dir of watchDirs) {
332
- beforeSnapshots.set(dir, snapshotDir(dir, patterns));
333
- }
334
- const rootId = orchestrator.startNode({ type: "agent", name: agentId });
335
- const dispatchId = orchestrator.startNode({
336
- type: "tool",
337
- name: "dispatch-command",
338
- parentId: rootId
339
- });
340
- orchestrator.updateState(dispatchId, { command: command.join(" ") });
341
- const monitorId = orchestrator.startNode({
342
- type: "tool",
343
- name: "state-monitor",
344
- parentId: rootId
345
- });
346
- orchestrator.updateState(monitorId, {
347
- watchDirs,
348
- watchPatterns
349
- });
350
- const startMs = Date.now();
351
- const execCmd = command[0] ?? "";
352
- const execArgs = command.slice(1);
353
- process.env.AGENTFLOW_TRACE_ID = traceId;
354
- process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
355
- const result = spawnSync(execCmd, execArgs, { stdio: "inherit" });
356
- delete process.env.AGENTFLOW_TRACE_ID;
357
- delete process.env.AGENTFLOW_PARENT_SPAN_ID;
358
- const exitCode = result.status ?? 1;
359
- const duration = (Date.now() - startMs) / 1e3;
360
- const stateChanges = [];
361
- for (const dir of watchDirs) {
362
- const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
363
- const after = snapshotDir(dir, patterns);
364
- for (const [filePath, mtime] of after) {
365
- const prevMtime = before.get(filePath);
366
- if (prevMtime === void 0 || mtime > prevMtime) {
367
- stateChanges.push(filePath);
368
- }
369
- }
370
- }
371
- orchestrator.updateState(monitorId, { stateChanges });
372
- orchestrator.endNode(monitorId);
373
- if (exitCode === 0) {
374
- orchestrator.endNode(dispatchId);
375
- } else {
376
- orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
377
- }
378
- orchestrator.updateState(rootId, {
379
- exitCode,
380
- duration,
381
- stateChanges
382
- });
383
- if (exitCode === 0) {
384
- orchestrator.endNode(rootId);
385
- } else {
386
- orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
387
- }
388
- const orchestratorGraph = orchestrator.build();
389
- const allGraphs = [orchestratorGraph];
390
- for (const filePath of stateChanges) {
391
- const childAgentId = agentIdFromFilename(filePath);
392
- const childBuilder = createGraphBuilder({
393
- agentId: childAgentId,
394
- trigger: "state-change",
395
- traceId,
396
- parentSpanId: spanId
397
- });
398
- const childRootId = childBuilder.startNode({
399
- type: "agent",
400
- name: childAgentId
401
- });
402
- childBuilder.updateState(childRootId, {
403
- stateFile: filePath,
404
- detectedBy: "runner-state-monitor"
405
- });
406
- childBuilder.endNode(childRootId);
407
- allGraphs.push(childBuilder.build());
408
- }
409
- if (!existsSync(resolvedTracesDir)) {
410
- mkdirSync(resolvedTracesDir, { recursive: true });
411
- }
412
- const ts = fileTimestamp();
413
- const tracePaths = [];
414
- for (const graph of allGraphs) {
415
- const filename = `${graph.agentId}-${ts}.json`;
416
- const outPath = join(resolvedTracesDir, filename);
417
- writeFileSync(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
418
- tracePaths.push(outPath);
419
- }
420
- return {
421
- exitCode,
422
- traceId,
423
- spanId,
424
- tracePaths,
425
- stateChanges,
426
- duration
427
- };
428
- }
429
-
430
- export {
431
- createGraphBuilder,
432
- loadGraph,
433
- graphToJson,
434
- runTraced
435
- };