agentflow-core 0.7.0 → 0.8.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/README.md +99 -0
- package/dist/{chunk-DY7YHFIB.js → chunk-BYWLDTZK.js} +2 -1
- package/dist/{chunk-5PRHVYYD.js → chunk-NVFWBTAZ.js} +198 -89
- package/dist/cli.cjs +225 -112
- package/dist/cli.js +12 -8
- package/dist/index.cjs +1657 -166
- package/dist/index.d.cts +917 -37
- package/dist/index.d.ts +917 -37
- package/dist/index.js +1381 -10
- package/dist/{loader-LYRR6LMM.js → loader-JMFEFI3Q.js} +1 -1
- package/package.json +7 -3
package/dist/index.cjs
CHANGED
|
@@ -21,12 +21,30 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
auditProcesses: () => auditProcesses,
|
|
24
|
+
buildAgentSummaryPrompt: () => buildAgentSummaryPrompt,
|
|
25
|
+
buildAnomalyExplanationPrompt: () => buildAnomalyExplanationPrompt,
|
|
26
|
+
buildFailureAnalysisPrompt: () => buildFailureAnalysisPrompt,
|
|
27
|
+
buildFixSuggestionPrompt: () => buildFixSuggestionPrompt,
|
|
28
|
+
checkConformance: () => checkConformance,
|
|
24
29
|
checkGuards: () => checkGuards,
|
|
30
|
+
createEventEmitter: () => createEventEmitter,
|
|
31
|
+
createExecutionEvent: () => createExecutionEvent,
|
|
25
32
|
createGraphBuilder: () => createGraphBuilder,
|
|
33
|
+
createInsightEngine: () => createInsightEngine,
|
|
34
|
+
createJsonEventWriter: () => createJsonEventWriter,
|
|
35
|
+
createKnowledgeStore: () => createKnowledgeStore,
|
|
36
|
+
createPatternEvent: () => createPatternEvent,
|
|
37
|
+
createPolicySource: () => createPolicySource,
|
|
38
|
+
createSomaEventWriter: () => createSomaEventWriter,
|
|
26
39
|
createTraceStore: () => createTraceStore,
|
|
40
|
+
discoverAllProcessConfigs: () => discoverAllProcessConfigs,
|
|
41
|
+
discoverProcess: () => discoverProcess,
|
|
27
42
|
discoverProcessConfig: () => discoverProcessConfig,
|
|
43
|
+
findVariants: () => findVariants,
|
|
28
44
|
findWaitingOn: () => findWaitingOn,
|
|
29
45
|
formatAuditReport: () => formatAuditReport,
|
|
46
|
+
formatReceipt: () => formatReceipt,
|
|
47
|
+
getBottlenecks: () => getBottlenecks,
|
|
30
48
|
getChildren: () => getChildren,
|
|
31
49
|
getCriticalPath: () => getCriticalPath,
|
|
32
50
|
getDepth: () => getDepth,
|
|
@@ -35,6 +53,7 @@ __export(index_exports, {
|
|
|
35
53
|
getHungNodes: () => getHungNodes,
|
|
36
54
|
getNode: () => getNode,
|
|
37
55
|
getParent: () => getParent,
|
|
56
|
+
getPathSignature: () => getPathSignature,
|
|
38
57
|
getStats: () => getStats,
|
|
39
58
|
getSubtree: () => getSubtree,
|
|
40
59
|
getTraceTree: () => getTraceTree,
|
|
@@ -46,13 +65,327 @@ __export(index_exports, {
|
|
|
46
65
|
startWatch: () => startWatch,
|
|
47
66
|
stitchTrace: () => stitchTrace,
|
|
48
67
|
toAsciiTree: () => toAsciiTree,
|
|
68
|
+
toReceipt: () => toReceipt,
|
|
49
69
|
toTimeline: () => toTimeline,
|
|
50
70
|
withGuards: () => withGuards
|
|
51
71
|
});
|
|
52
72
|
module.exports = __toCommonJS(index_exports);
|
|
53
73
|
|
|
74
|
+
// src/process-mining.ts
|
|
75
|
+
function getPathSignature(graph) {
|
|
76
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
77
|
+
if (!root) return "";
|
|
78
|
+
const parts = [];
|
|
79
|
+
function walk(node) {
|
|
80
|
+
parts.push(`${node.type}:${node.name}`);
|
|
81
|
+
const childNodes = [];
|
|
82
|
+
for (const childId of node.children) {
|
|
83
|
+
const child = graph.nodes.get(childId);
|
|
84
|
+
if (child) childNodes.push(child);
|
|
85
|
+
}
|
|
86
|
+
childNodes.sort((a, b) => {
|
|
87
|
+
const keyA = `${a.type}:${a.name}`;
|
|
88
|
+
const keyB = `${b.type}:${b.name}`;
|
|
89
|
+
return keyA.localeCompare(keyB);
|
|
90
|
+
});
|
|
91
|
+
for (const child of childNodes) {
|
|
92
|
+
walk(child);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
walk(root);
|
|
96
|
+
return parts.join("\u2192");
|
|
97
|
+
}
|
|
98
|
+
function stepKey(node) {
|
|
99
|
+
return `${node.type}:${node.name}`;
|
|
100
|
+
}
|
|
101
|
+
function discoverProcess(graphs) {
|
|
102
|
+
if (graphs.length === 0) {
|
|
103
|
+
throw new Error("discoverProcess requires at least one graph");
|
|
104
|
+
}
|
|
105
|
+
const steps = /* @__PURE__ */ new Set();
|
|
106
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
107
|
+
const outgoingCounts = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const graph of graphs) {
|
|
109
|
+
for (const node of graph.nodes.values()) {
|
|
110
|
+
const parentKey = stepKey(node);
|
|
111
|
+
steps.add(parentKey);
|
|
112
|
+
for (const childId of node.children) {
|
|
113
|
+
const child = graph.nodes.get(childId);
|
|
114
|
+
if (!child) continue;
|
|
115
|
+
const childKey = stepKey(child);
|
|
116
|
+
const tKey = `${parentKey}\0${childKey}`;
|
|
117
|
+
transitionCounts.set(tKey, (transitionCounts.get(tKey) ?? 0) + 1);
|
|
118
|
+
outgoingCounts.set(parentKey, (outgoingCounts.get(parentKey) ?? 0) + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const transitions = [];
|
|
123
|
+
for (const [tKey, count] of transitionCounts) {
|
|
124
|
+
const [from, to] = tKey.split("\0");
|
|
125
|
+
const outgoing = outgoingCounts.get(from) ?? count;
|
|
126
|
+
transitions.push({
|
|
127
|
+
from,
|
|
128
|
+
to,
|
|
129
|
+
count,
|
|
130
|
+
probability: count / outgoing
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
transitions.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
134
|
+
return {
|
|
135
|
+
steps: [...steps].sort(),
|
|
136
|
+
transitions,
|
|
137
|
+
totalGraphs: graphs.length,
|
|
138
|
+
agentId: graphs[0]?.agentId ?? ""
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function findVariants(graphs) {
|
|
142
|
+
if (graphs.length === 0) return [];
|
|
143
|
+
const groups = /* @__PURE__ */ new Map();
|
|
144
|
+
for (const graph of graphs) {
|
|
145
|
+
const sig = getPathSignature(graph);
|
|
146
|
+
const group = groups.get(sig) ?? [];
|
|
147
|
+
group.push(graph);
|
|
148
|
+
groups.set(sig, group);
|
|
149
|
+
}
|
|
150
|
+
const total = graphs.length;
|
|
151
|
+
const variants = [];
|
|
152
|
+
for (const [pathSignature, groupGraphs] of groups) {
|
|
153
|
+
variants.push({
|
|
154
|
+
pathSignature,
|
|
155
|
+
count: groupGraphs.length,
|
|
156
|
+
percentage: groupGraphs.length / total * 100,
|
|
157
|
+
graphIds: groupGraphs.map((g) => g.id),
|
|
158
|
+
exampleGraph: groupGraphs[0]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
variants.sort((a, b) => {
|
|
162
|
+
const freqDiff = b.count - a.count;
|
|
163
|
+
if (freqDiff !== 0) return freqDiff;
|
|
164
|
+
return a.pathSignature.localeCompare(b.pathSignature);
|
|
165
|
+
});
|
|
166
|
+
return variants;
|
|
167
|
+
}
|
|
168
|
+
function percentile(sorted, p) {
|
|
169
|
+
if (sorted.length === 0) return 0;
|
|
170
|
+
if (sorted.length === 1) return sorted[0] ?? 0;
|
|
171
|
+
const index = p / 100 * (sorted.length - 1);
|
|
172
|
+
const lower = Math.floor(index);
|
|
173
|
+
const upper = Math.ceil(index);
|
|
174
|
+
if (lower === upper) return sorted[lower] ?? 0;
|
|
175
|
+
const weight = index - lower;
|
|
176
|
+
return (sorted[lower] ?? 0) * (1 - weight) + (sorted[upper] ?? 0) * weight;
|
|
177
|
+
}
|
|
178
|
+
function getBottlenecks(graphs) {
|
|
179
|
+
if (graphs.length === 0) return [];
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
const stats = /* @__PURE__ */ new Map();
|
|
182
|
+
for (const graph of graphs) {
|
|
183
|
+
for (const node of graph.nodes.values()) {
|
|
184
|
+
const key = `${node.type}:${node.name}`;
|
|
185
|
+
const entry = stats.get(key) ?? { durations: [], nodeType: node.type, nodeName: node.name };
|
|
186
|
+
const end = node.endTime ?? now;
|
|
187
|
+
entry.durations.push(end - node.startTime);
|
|
188
|
+
stats.set(key, entry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const total = graphs.length;
|
|
192
|
+
const bottlenecks = [];
|
|
193
|
+
for (const [, entry] of stats) {
|
|
194
|
+
const sorted = [...entry.durations].sort((a, b) => a - b);
|
|
195
|
+
bottlenecks.push({
|
|
196
|
+
nodeName: entry.nodeName,
|
|
197
|
+
nodeType: entry.nodeType,
|
|
198
|
+
occurrences: sorted.length,
|
|
199
|
+
durations: {
|
|
200
|
+
median: percentile(sorted, 50),
|
|
201
|
+
p95: percentile(sorted, 95),
|
|
202
|
+
p99: percentile(sorted, 99),
|
|
203
|
+
min: sorted[0] ?? 0,
|
|
204
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
205
|
+
},
|
|
206
|
+
percentOfGraphs: sorted.length / total * 100
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
bottlenecks.sort((a, b) => b.durations.p95 - a.durations.p95);
|
|
210
|
+
return bottlenecks;
|
|
211
|
+
}
|
|
212
|
+
function extractGraphTransitions(graph) {
|
|
213
|
+
const transitions = /* @__PURE__ */ new Set();
|
|
214
|
+
for (const node of graph.nodes.values()) {
|
|
215
|
+
const parentKey = stepKey(node);
|
|
216
|
+
for (const childId of node.children) {
|
|
217
|
+
const child = graph.nodes.get(childId);
|
|
218
|
+
if (!child) continue;
|
|
219
|
+
transitions.add(`${parentKey}\0${stepKey(child)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return transitions;
|
|
223
|
+
}
|
|
224
|
+
function checkConformance(graph, model) {
|
|
225
|
+
const graphTransitions = extractGraphTransitions(graph);
|
|
226
|
+
const deviations = [];
|
|
227
|
+
const modelLookup = /* @__PURE__ */ new Map();
|
|
228
|
+
for (const t of model.transitions) {
|
|
229
|
+
modelLookup.set(`${t.from}\0${t.to}`, t);
|
|
230
|
+
}
|
|
231
|
+
let totalChecks = 0;
|
|
232
|
+
let deviationCount = 0;
|
|
233
|
+
for (const tKey of graphTransitions) {
|
|
234
|
+
totalChecks++;
|
|
235
|
+
const [from, to] = tKey.split("\0");
|
|
236
|
+
const modelTransition = modelLookup.get(tKey);
|
|
237
|
+
if (!modelTransition) {
|
|
238
|
+
deviationCount++;
|
|
239
|
+
deviations.push({
|
|
240
|
+
type: "unexpected-transition",
|
|
241
|
+
from,
|
|
242
|
+
to,
|
|
243
|
+
message: `Unexpected transition ${from} \u2192 ${to} (not in process model)`
|
|
244
|
+
});
|
|
245
|
+
} else if (modelTransition.probability < 0.1) {
|
|
246
|
+
deviationCount++;
|
|
247
|
+
deviations.push({
|
|
248
|
+
type: "low-frequency-path",
|
|
249
|
+
from,
|
|
250
|
+
to,
|
|
251
|
+
message: `Low-frequency path ${from} \u2192 ${to} (model probability: ${(modelTransition.probability * 100).toFixed(1)}%)`,
|
|
252
|
+
modelProbability: modelTransition.probability
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const graphSteps = /* @__PURE__ */ new Set();
|
|
257
|
+
for (const node of graph.nodes.values()) {
|
|
258
|
+
graphSteps.add(stepKey(node));
|
|
259
|
+
}
|
|
260
|
+
for (const t of model.transitions) {
|
|
261
|
+
if (t.probability > 0.5) {
|
|
262
|
+
const tKey = `${t.from}\0${t.to}`;
|
|
263
|
+
if (graphSteps.has(t.from) && !graphTransitions.has(tKey)) {
|
|
264
|
+
totalChecks++;
|
|
265
|
+
deviationCount++;
|
|
266
|
+
deviations.push({
|
|
267
|
+
type: "missing-transition",
|
|
268
|
+
from: t.from,
|
|
269
|
+
to: t.to,
|
|
270
|
+
message: `Missing expected transition ${t.from} \u2192 ${t.to} (model probability: ${(t.probability * 100).toFixed(1)}%)`,
|
|
271
|
+
modelProbability: t.probability
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const conformanceScore = totalChecks === 0 ? 1 : (totalChecks - deviationCount) / totalChecks;
|
|
277
|
+
return {
|
|
278
|
+
conformanceScore,
|
|
279
|
+
isConforming: deviations.length === 0,
|
|
280
|
+
deviations
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/event-emitter.ts
|
|
285
|
+
var SCHEMA_VERSION = 1;
|
|
286
|
+
function createExecutionEvent(graph, options) {
|
|
287
|
+
const duration = graph.endTime !== null ? graph.endTime - graph.startTime : Date.now() - graph.startTime;
|
|
288
|
+
let failurePoint;
|
|
289
|
+
if (graph.status === "failed") {
|
|
290
|
+
let candidate;
|
|
291
|
+
for (const node of graph.nodes.values()) {
|
|
292
|
+
if (node.status === "failed" || node.status === "timeout") {
|
|
293
|
+
const errorMeta = node.metadata.error;
|
|
294
|
+
const fp = {
|
|
295
|
+
nodeId: node.id,
|
|
296
|
+
nodeName: node.name,
|
|
297
|
+
nodeType: node.type,
|
|
298
|
+
error: typeof errorMeta === "string" ? errorMeta : void 0
|
|
299
|
+
};
|
|
300
|
+
if (node.id !== graph.rootNodeId) {
|
|
301
|
+
failurePoint = fp;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (!candidate) candidate = fp;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!failurePoint) failurePoint = candidate;
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
eventType: graph.status === "failed" ? "execution.failed" : "execution.completed",
|
|
311
|
+
graphId: graph.id,
|
|
312
|
+
agentId: graph.agentId,
|
|
313
|
+
timestamp: Date.now(),
|
|
314
|
+
schemaVersion: SCHEMA_VERSION,
|
|
315
|
+
status: graph.status,
|
|
316
|
+
duration,
|
|
317
|
+
nodeCount: graph.nodes.size,
|
|
318
|
+
pathSignature: getPathSignature(graph),
|
|
319
|
+
...failurePoint ? { failurePoint } : {},
|
|
320
|
+
...options?.processContext ? { processContext: options.processContext } : {},
|
|
321
|
+
...options?.semantic ? { semantic: options.semantic } : {},
|
|
322
|
+
violations: options?.violations ?? []
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function createPatternEvent(agentId, model, variants, bottlenecks) {
|
|
326
|
+
return {
|
|
327
|
+
eventType: "pattern.discovered",
|
|
328
|
+
agentId,
|
|
329
|
+
timestamp: Date.now(),
|
|
330
|
+
schemaVersion: SCHEMA_VERSION,
|
|
331
|
+
pattern: {
|
|
332
|
+
totalGraphs: model.totalGraphs,
|
|
333
|
+
variantCount: variants.length,
|
|
334
|
+
topVariants: variants.slice(0, 5).map((v) => ({
|
|
335
|
+
pathSignature: v.pathSignature,
|
|
336
|
+
count: v.count,
|
|
337
|
+
percentage: v.percentage
|
|
338
|
+
})),
|
|
339
|
+
topBottlenecks: bottlenecks.slice(0, 5).map((b) => ({
|
|
340
|
+
nodeName: b.nodeName,
|
|
341
|
+
nodeType: b.nodeType,
|
|
342
|
+
p95: b.durations.p95
|
|
343
|
+
})),
|
|
344
|
+
processModel: model
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function createEventEmitter(config) {
|
|
349
|
+
const writers = config?.writers ?? [];
|
|
350
|
+
const knowledgeStore = config?.knowledgeStore;
|
|
351
|
+
const onError = config?.onError ?? (() => {
|
|
352
|
+
});
|
|
353
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
354
|
+
return {
|
|
355
|
+
async emit(event) {
|
|
356
|
+
if (knowledgeStore) {
|
|
357
|
+
try {
|
|
358
|
+
knowledgeStore.append(event);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
onError(err);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const writer of writers) {
|
|
364
|
+
try {
|
|
365
|
+
await writer.writeEvent(event);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
onError(err);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (const listener of subscribers) {
|
|
371
|
+
try {
|
|
372
|
+
listener(event);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
onError(err);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
subscribe(listener) {
|
|
379
|
+
subscribers.add(listener);
|
|
380
|
+
return () => {
|
|
381
|
+
subscribers.delete(listener);
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
54
387
|
// src/graph-builder.ts
|
|
55
|
-
var
|
|
388
|
+
var import_node_crypto = require("crypto");
|
|
56
389
|
function deepFreeze(obj) {
|
|
57
390
|
if (obj === null || typeof obj !== "object") return obj;
|
|
58
391
|
if (obj instanceof Map) {
|
|
@@ -83,8 +416,8 @@ function createGraphBuilder(config) {
|
|
|
83
416
|
const generateId = config?.idGenerator ?? createCounterIdGenerator();
|
|
84
417
|
const agentId = config?.agentId ?? "unknown";
|
|
85
418
|
const trigger = config?.trigger ?? "manual";
|
|
86
|
-
const spanId = (0,
|
|
87
|
-
const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? (0,
|
|
419
|
+
const spanId = (0, import_node_crypto.randomUUID)();
|
|
420
|
+
const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? (0, import_node_crypto.randomUUID)();
|
|
88
421
|
const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
|
|
89
422
|
const graphId = generateId();
|
|
90
423
|
const startTime = Date.now();
|
|
@@ -308,13 +641,13 @@ function getHungNodes(graph) {
|
|
|
308
641
|
function getCriticalPath(graph) {
|
|
309
642
|
const root = graph.nodes.get(graph.rootNodeId);
|
|
310
643
|
if (!root) return [];
|
|
311
|
-
function
|
|
644
|
+
function nodeDuration3(node) {
|
|
312
645
|
const end = node.endTime ?? Date.now();
|
|
313
646
|
return end - node.startTime;
|
|
314
647
|
}
|
|
315
648
|
function dfs(node) {
|
|
316
649
|
if (node.children.length === 0) {
|
|
317
|
-
return { duration:
|
|
650
|
+
return { duration: nodeDuration3(node), path: [node] };
|
|
318
651
|
}
|
|
319
652
|
let bestChild = { duration: -1, path: [] };
|
|
320
653
|
for (const childId of node.children) {
|
|
@@ -326,7 +659,7 @@ function getCriticalPath(graph) {
|
|
|
326
659
|
}
|
|
327
660
|
}
|
|
328
661
|
return {
|
|
329
|
-
duration:
|
|
662
|
+
duration: nodeDuration3(node) + bestChild.duration,
|
|
330
663
|
path: [node, ...bestChild.path]
|
|
331
664
|
};
|
|
332
665
|
}
|
|
@@ -429,7 +762,7 @@ function groupByTraceId(graphs) {
|
|
|
429
762
|
}
|
|
430
763
|
function stitchTrace(graphs) {
|
|
431
764
|
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
432
|
-
const traceId = graphs[0]
|
|
765
|
+
const traceId = graphs[0]?.traceId ?? "";
|
|
433
766
|
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
434
767
|
const childMap = /* @__PURE__ */ new Map();
|
|
435
768
|
let rootGraph = null;
|
|
@@ -481,6 +814,10 @@ function getTraceTree(trace) {
|
|
|
481
814
|
}
|
|
482
815
|
|
|
483
816
|
// src/guards.ts
|
|
817
|
+
function explainMessage(explanation) {
|
|
818
|
+
const sourceLabel = explanation.source === "static" ? "static config" : explanation.source === "soma-policy" ? `soma-policy${explanation.evidence ? ` (${explanation.evidence})` : ""}` : explanation.source === "assertion" ? "assertion" : "adaptive";
|
|
819
|
+
return `This run was stopped because ${explanation.rule} reached ${explanation.actual}, which exceeds the limit of ${explanation.threshold}. Source: ${sourceLabel}.`;
|
|
820
|
+
}
|
|
484
821
|
var DEFAULT_TIMEOUTS = {
|
|
485
822
|
tool: 3e4,
|
|
486
823
|
// 30s
|
|
@@ -507,22 +844,36 @@ function checkGuards(graph, config) {
|
|
|
507
844
|
const timeoutThreshold = timeouts[node.type];
|
|
508
845
|
const elapsed = now - node.startTime;
|
|
509
846
|
if (elapsed > timeoutThreshold) {
|
|
847
|
+
const explanation = {
|
|
848
|
+
rule: "timeout",
|
|
849
|
+
threshold: timeoutThreshold,
|
|
850
|
+
actual: elapsed,
|
|
851
|
+
source: "static"
|
|
852
|
+
};
|
|
510
853
|
violations.push({
|
|
511
854
|
type: "timeout",
|
|
512
855
|
nodeId: node.id,
|
|
513
|
-
message:
|
|
514
|
-
timestamp: now
|
|
856
|
+
message: explainMessage(explanation),
|
|
857
|
+
timestamp: now,
|
|
858
|
+
explanation
|
|
515
859
|
});
|
|
516
860
|
}
|
|
517
861
|
}
|
|
518
862
|
}
|
|
519
863
|
const depth = getDepth(graph);
|
|
520
864
|
if (depth > maxDepth) {
|
|
865
|
+
const explanation = {
|
|
866
|
+
rule: "max-depth",
|
|
867
|
+
threshold: maxDepth,
|
|
868
|
+
actual: depth,
|
|
869
|
+
source: "static"
|
|
870
|
+
};
|
|
521
871
|
violations.push({
|
|
522
872
|
type: "spawn-explosion",
|
|
523
873
|
nodeId: graph.rootNodeId,
|
|
524
|
-
message:
|
|
525
|
-
timestamp: now
|
|
874
|
+
message: explainMessage(explanation),
|
|
875
|
+
timestamp: now,
|
|
876
|
+
explanation
|
|
526
877
|
});
|
|
527
878
|
}
|
|
528
879
|
let agentCount = 0;
|
|
@@ -532,14 +883,26 @@ function checkGuards(graph, config) {
|
|
|
532
883
|
}
|
|
533
884
|
}
|
|
534
885
|
if (agentCount > maxAgentSpawns) {
|
|
886
|
+
const explanation = {
|
|
887
|
+
rule: "max-agent-spawns",
|
|
888
|
+
threshold: maxAgentSpawns,
|
|
889
|
+
actual: agentCount,
|
|
890
|
+
source: "static"
|
|
891
|
+
};
|
|
535
892
|
violations.push({
|
|
536
893
|
type: "spawn-explosion",
|
|
537
894
|
nodeId: graph.rootNodeId,
|
|
538
|
-
message:
|
|
539
|
-
timestamp: now
|
|
895
|
+
message: explainMessage(explanation),
|
|
896
|
+
timestamp: now,
|
|
897
|
+
explanation
|
|
540
898
|
});
|
|
541
899
|
}
|
|
542
900
|
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
901
|
+
if (config?.policySource) {
|
|
902
|
+
violations.push(
|
|
903
|
+
...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now)
|
|
904
|
+
);
|
|
905
|
+
}
|
|
543
906
|
return violations;
|
|
544
907
|
}
|
|
545
908
|
function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
@@ -559,11 +922,18 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
559
922
|
}
|
|
560
923
|
if (newCount > maxSteps && !reported.has(newType)) {
|
|
561
924
|
reported.add(newType);
|
|
925
|
+
const explanation = {
|
|
926
|
+
rule: "max-reasoning-steps",
|
|
927
|
+
threshold: maxSteps,
|
|
928
|
+
actual: newCount,
|
|
929
|
+
source: "static"
|
|
930
|
+
};
|
|
562
931
|
violations.push({
|
|
563
932
|
type: "reasoning-loop",
|
|
564
933
|
nodeId: node.id,
|
|
565
|
-
message:
|
|
566
|
-
timestamp
|
|
934
|
+
message: explainMessage(explanation),
|
|
935
|
+
timestamp,
|
|
936
|
+
explanation
|
|
567
937
|
});
|
|
568
938
|
}
|
|
569
939
|
const children = getChildren(graph, nodeId);
|
|
@@ -574,6 +944,61 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
574
944
|
walk(graph.rootNodeId, 0, null);
|
|
575
945
|
return violations;
|
|
576
946
|
}
|
|
947
|
+
function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
948
|
+
const violations = [];
|
|
949
|
+
const maxFailureRate = thresholds?.maxFailureRate ?? 0.5;
|
|
950
|
+
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
951
|
+
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
952
|
+
if (failureRate > maxFailureRate) {
|
|
953
|
+
const explanation = {
|
|
954
|
+
rule: "max-failure-rate",
|
|
955
|
+
threshold: maxFailureRate,
|
|
956
|
+
actual: failureRate,
|
|
957
|
+
source: "soma-policy"
|
|
958
|
+
};
|
|
959
|
+
violations.push({
|
|
960
|
+
type: "high-failure-rate",
|
|
961
|
+
nodeId: graph.rootNodeId,
|
|
962
|
+
message: explainMessage(explanation),
|
|
963
|
+
timestamp,
|
|
964
|
+
explanation
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
968
|
+
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
969
|
+
const explanation = {
|
|
970
|
+
rule: "min-conformance",
|
|
971
|
+
threshold: minConformance,
|
|
972
|
+
actual: conformanceScore,
|
|
973
|
+
source: "soma-policy"
|
|
974
|
+
};
|
|
975
|
+
violations.push({
|
|
976
|
+
type: "conformance-drift",
|
|
977
|
+
nodeId: graph.rootNodeId,
|
|
978
|
+
message: explainMessage(explanation),
|
|
979
|
+
timestamp,
|
|
980
|
+
explanation
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
for (const node of graph.nodes.values()) {
|
|
984
|
+
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
985
|
+
const explanation = {
|
|
986
|
+
rule: "known-bottleneck",
|
|
987
|
+
threshold: "flagged",
|
|
988
|
+
actual: "running",
|
|
989
|
+
source: "soma-policy"
|
|
990
|
+
};
|
|
991
|
+
violations.push({
|
|
992
|
+
type: "known-bottleneck",
|
|
993
|
+
nodeId: node.id,
|
|
994
|
+
message: explainMessage(explanation),
|
|
995
|
+
timestamp,
|
|
996
|
+
explanation
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return violations;
|
|
1001
|
+
}
|
|
577
1002
|
function withGuards(builder, config) {
|
|
578
1003
|
const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
|
|
579
1004
|
const onViolation = config?.onViolation ?? "warn";
|
|
@@ -638,9 +1063,639 @@ function withGuards(builder, config) {
|
|
|
638
1063
|
};
|
|
639
1064
|
}
|
|
640
1065
|
|
|
641
|
-
// src/
|
|
1066
|
+
// src/prompt-builder.ts
|
|
1067
|
+
var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
|
|
1068
|
+
function fmtDuration(ms) {
|
|
1069
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1070
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
1071
|
+
}
|
|
1072
|
+
function fmtTime(ts) {
|
|
1073
|
+
return new Date(ts).toISOString();
|
|
1074
|
+
}
|
|
1075
|
+
function durationStats(durations) {
|
|
1076
|
+
if (durations.length === 0) return { avg: 0, p50: 0, p95: 0, min: 0, max: 0 };
|
|
1077
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
1078
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
1079
|
+
return {
|
|
1080
|
+
avg: Math.round(sum / sorted.length),
|
|
1081
|
+
p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
|
|
1082
|
+
p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
|
|
1083
|
+
min: sorted[0] ?? 0,
|
|
1084
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function buildFailureAnalysisPrompt(events, profile) {
|
|
1088
|
+
const stats = durationStats(profile.recentDurations);
|
|
1089
|
+
const failureDetails = events.map((e, i) => {
|
|
1090
|
+
const lines = [
|
|
1091
|
+
`Failure ${i + 1}:`,
|
|
1092
|
+
` Time: ${fmtTime(e.timestamp)}`,
|
|
1093
|
+
` Duration: ${fmtDuration(e.duration)}`,
|
|
1094
|
+
` Path: ${e.pathSignature}`
|
|
1095
|
+
];
|
|
1096
|
+
if (e.failurePoint) {
|
|
1097
|
+
lines.push(` Failed at: ${e.failurePoint.nodeName} (${e.failurePoint.nodeType})`);
|
|
1098
|
+
if (e.failurePoint.error) lines.push(` Error: ${e.failurePoint.error}`);
|
|
1099
|
+
}
|
|
1100
|
+
if (e.violations.length > 0) {
|
|
1101
|
+
lines.push(` Violations: ${e.violations.map((v) => v.message).join("; ")}`);
|
|
1102
|
+
}
|
|
1103
|
+
return lines.join("\n");
|
|
1104
|
+
}).join("\n\n");
|
|
1105
|
+
return `${ROLE}
|
|
1106
|
+
|
|
1107
|
+
## Agent Profile
|
|
1108
|
+
- Agent: ${profile.agentId}
|
|
1109
|
+
- Total runs: ${profile.totalRuns}
|
|
1110
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}% (${profile.failureCount} failures / ${profile.totalRuns} total)
|
|
1111
|
+
- Avg duration: ${fmtDuration(stats.avg)} (p50: ${fmtDuration(stats.p50)}, p95: ${fmtDuration(stats.p95)})
|
|
1112
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1113
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
1114
|
+
|
|
1115
|
+
## Recent Failures (${events.length})
|
|
1116
|
+
|
|
1117
|
+
${failureDetails}
|
|
1118
|
+
|
|
1119
|
+
## Question
|
|
1120
|
+
Analyze these failures. What patterns do you see? What is the most likely root cause? Are these related or independent failures?`;
|
|
1121
|
+
}
|
|
1122
|
+
function buildAnomalyExplanationPrompt(event, profile) {
|
|
1123
|
+
const stats = durationStats(profile.recentDurations);
|
|
1124
|
+
const eventDetails = [
|
|
1125
|
+
`Time: ${fmtTime(event.timestamp)}`,
|
|
1126
|
+
`Status: ${event.status}`,
|
|
1127
|
+
`Duration: ${fmtDuration(event.duration)}`,
|
|
1128
|
+
`Path: ${event.pathSignature}`,
|
|
1129
|
+
`Node count: ${event.nodeCount}`
|
|
1130
|
+
];
|
|
1131
|
+
if (event.processContext) {
|
|
1132
|
+
eventDetails.push(`Conformance score: ${event.processContext.conformanceScore}`);
|
|
1133
|
+
eventDetails.push(`Is anomaly: ${event.processContext.isAnomaly}`);
|
|
1134
|
+
eventDetails.push(`Variant: ${event.processContext.variant}`);
|
|
1135
|
+
}
|
|
1136
|
+
if (event.failurePoint) {
|
|
1137
|
+
eventDetails.push(`Failed at: ${event.failurePoint.nodeName} (${event.failurePoint.nodeType})`);
|
|
1138
|
+
if (event.failurePoint.error) eventDetails.push(`Error: ${event.failurePoint.error}`);
|
|
1139
|
+
}
|
|
1140
|
+
if (event.violations.length > 0) {
|
|
1141
|
+
eventDetails.push(`Violations: ${event.violations.map((v) => v.message).join("; ")}`);
|
|
1142
|
+
}
|
|
1143
|
+
return `${ROLE}
|
|
1144
|
+
|
|
1145
|
+
## Agent Baseline (from profile)
|
|
1146
|
+
- Agent: ${profile.agentId}
|
|
1147
|
+
- Total runs: ${profile.totalRuns}
|
|
1148
|
+
- Typical failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
1149
|
+
- Typical duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}
|
|
1150
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
1151
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1152
|
+
|
|
1153
|
+
## Anomalous Execution
|
|
1154
|
+
${eventDetails.join("\n")}
|
|
1155
|
+
|
|
1156
|
+
## Question
|
|
1157
|
+
This execution has been flagged as anomalous. Explain what is unusual about it compared to the agent's typical behavior. What might have caused this deviation?`;
|
|
1158
|
+
}
|
|
1159
|
+
function buildAgentSummaryPrompt(profile, recentEvents, patterns) {
|
|
1160
|
+
const stats = durationStats(profile.recentDurations);
|
|
1161
|
+
const recentOutcomes = recentEvents.slice(0, 10).map((e) => ` ${fmtTime(e.timestamp)} \u2014 ${e.eventType} (${fmtDuration(e.duration)})`).join("\n");
|
|
1162
|
+
const patternSummary = patterns.length > 0 ? patterns.slice(0, 3).map((p) => {
|
|
1163
|
+
const lines = [
|
|
1164
|
+
` Variants: ${p.pattern.variantCount} across ${p.pattern.totalGraphs} executions`,
|
|
1165
|
+
` Top variant: ${p.pattern.topVariants[0]?.pathSignature ?? "N/A"} (${p.pattern.topVariants[0]?.percentage.toFixed(0) ?? 0}%)`
|
|
1166
|
+
];
|
|
1167
|
+
if (p.pattern.topBottlenecks.length > 0) {
|
|
1168
|
+
const topB = p.pattern.topBottlenecks[0];
|
|
1169
|
+
if (topB)
|
|
1170
|
+
lines.push(` Top bottleneck: ${topB.nodeName} (p95: ${fmtDuration(topB.p95)})`);
|
|
1171
|
+
}
|
|
1172
|
+
return lines.join("\n");
|
|
1173
|
+
}).join("\n\n") : " No patterns discovered yet.";
|
|
1174
|
+
const dataNote = recentEvents.length === 0 && patterns.length === 0 ? "\nNote: Limited data available. Summary is based only on the profile statistics.\n" : "";
|
|
1175
|
+
return `${ROLE}
|
|
1176
|
+
|
|
1177
|
+
## Agent Profile
|
|
1178
|
+
- Agent: ${profile.agentId}
|
|
1179
|
+
- Total runs: ${profile.totalRuns}
|
|
1180
|
+
- Success rate: ${((1 - profile.failureRate) * 100).toFixed(1)}% (${profile.successCount} successes, ${profile.failureCount} failures)
|
|
1181
|
+
- Duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}, range ${fmtDuration(stats.min)}\u2013${fmtDuration(stats.max)}
|
|
1182
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1183
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
1184
|
+
- Last pattern analysis: ${profile.lastPatternTimestamp ? fmtTime(profile.lastPatternTimestamp) : "never"}
|
|
1185
|
+
${dataNote}
|
|
1186
|
+
## Recent Executions (last ${recentEvents.slice(0, 10).length})
|
|
1187
|
+
${recentOutcomes || " No recent events."}
|
|
1188
|
+
|
|
1189
|
+
## Pattern Analysis
|
|
1190
|
+
${patternSummary}
|
|
1191
|
+
|
|
1192
|
+
## Question
|
|
1193
|
+
Provide a health summary for this agent. What are the key observations? Is the agent healthy, degrading, or in trouble? What should the operator pay attention to?`;
|
|
1194
|
+
}
|
|
1195
|
+
function buildFixSuggestionPrompt(events, profile, patterns) {
|
|
1196
|
+
const failureGroups = /* @__PURE__ */ new Map();
|
|
1197
|
+
for (const e of events) {
|
|
1198
|
+
const key = e.failurePoint?.error ?? e.pathSignature;
|
|
1199
|
+
const group = failureGroups.get(key) ?? [];
|
|
1200
|
+
group.push(e);
|
|
1201
|
+
failureGroups.set(key, group);
|
|
1202
|
+
}
|
|
1203
|
+
const failureGroupSummary = [...failureGroups.entries()].map(([key, group]) => {
|
|
1204
|
+
const latest = group[0];
|
|
1205
|
+
return ` "${key}" \u2014 ${group.length} occurrence(s), latest at ${latest ? fmtTime(latest.timestamp) : "unknown"}`;
|
|
1206
|
+
}).join("\n");
|
|
1207
|
+
const bottleneckDetails = patterns.flatMap((p) => p.pattern.topBottlenecks).map((b) => ` ${b.nodeName} (${b.nodeType}) \u2014 p95: ${fmtDuration(b.p95)}`);
|
|
1208
|
+
const uniqueBottlenecks = [...new Set(bottleneckDetails)].join("\n");
|
|
1209
|
+
const conformanceIssues = events.filter((e) => e.processContext && e.processContext.conformanceScore < 0.8).map(
|
|
1210
|
+
(e) => ` ${fmtTime(e.timestamp)}: conformance ${e.processContext?.conformanceScore}, variant "${e.processContext?.variant}"`
|
|
1211
|
+
).join("\n");
|
|
1212
|
+
return `${ROLE}
|
|
1213
|
+
|
|
1214
|
+
## Agent Profile
|
|
1215
|
+
- Agent: ${profile.agentId}
|
|
1216
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
1217
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1218
|
+
|
|
1219
|
+
## Failure Patterns (${events.length} failures)
|
|
1220
|
+
${failureGroupSummary || " No failures recorded."}
|
|
1221
|
+
|
|
1222
|
+
## Bottlenecks
|
|
1223
|
+
${uniqueBottlenecks || " No bottlenecks detected."}
|
|
1224
|
+
|
|
1225
|
+
## Conformance Issues
|
|
1226
|
+
${conformanceIssues || " No conformance issues."}
|
|
1227
|
+
|
|
1228
|
+
## Question
|
|
1229
|
+
Based on the failure patterns, bottlenecks, and conformance issues above, provide specific, actionable recommendations to improve this agent's reliability and performance. Prioritize by impact.`;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/insight-engine.ts
|
|
1233
|
+
var DEFAULT_CACHE_TTL_MS = 36e5;
|
|
1234
|
+
var SCHEMA_VERSION2 = 1;
|
|
1235
|
+
function simpleHash(input) {
|
|
1236
|
+
let hash = 0;
|
|
1237
|
+
for (let i = 0; i < input.length; i++) {
|
|
1238
|
+
const char = input.charCodeAt(i);
|
|
1239
|
+
hash = (hash << 5) - hash + char | 0;
|
|
1240
|
+
}
|
|
1241
|
+
return (hash >>> 0).toString(36);
|
|
1242
|
+
}
|
|
1243
|
+
function createInsightEngine(store, analysisFn, config) {
|
|
1244
|
+
const cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1245
|
+
function checkCache(agentId, insightType, dataHash) {
|
|
1246
|
+
const recent = store.getRecentInsights(agentId, { type: insightType, limit: 1 });
|
|
1247
|
+
if (recent.length === 0) return null;
|
|
1248
|
+
const cached = recent[0];
|
|
1249
|
+
if (!cached || cached.dataHash !== dataHash) return null;
|
|
1250
|
+
const age = Date.now() - cached.timestamp;
|
|
1251
|
+
if (age >= cacheTtlMs) return null;
|
|
1252
|
+
return cached;
|
|
1253
|
+
}
|
|
1254
|
+
function storeAndReturn(agentId, insightType, prompt, response, dataHash) {
|
|
1255
|
+
const event = {
|
|
1256
|
+
eventType: "insight.generated",
|
|
1257
|
+
agentId,
|
|
1258
|
+
timestamp: Date.now(),
|
|
1259
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
1260
|
+
insightType,
|
|
1261
|
+
prompt,
|
|
1262
|
+
response,
|
|
1263
|
+
dataHash
|
|
1264
|
+
};
|
|
1265
|
+
store.appendInsight(event);
|
|
1266
|
+
return {
|
|
1267
|
+
agentId,
|
|
1268
|
+
insightType,
|
|
1269
|
+
content: response,
|
|
1270
|
+
cached: false,
|
|
1271
|
+
timestamp: event.timestamp
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function shortCircuit(agentId, insightType, content, cached, timestamp) {
|
|
1275
|
+
return { agentId, insightType, content, cached, timestamp: timestamp ?? Date.now() };
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
async explainFailures(agentId) {
|
|
1279
|
+
const profile = store.getAgentProfile(agentId);
|
|
1280
|
+
if (!profile) {
|
|
1281
|
+
return shortCircuit(
|
|
1282
|
+
agentId,
|
|
1283
|
+
"failure-analysis",
|
|
1284
|
+
"No data available for this agent.",
|
|
1285
|
+
false
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
1289
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
1290
|
+
if (failures.length === 0) {
|
|
1291
|
+
return shortCircuit(
|
|
1292
|
+
agentId,
|
|
1293
|
+
"failure-analysis",
|
|
1294
|
+
"No recent failures found for this agent.",
|
|
1295
|
+
false
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile }));
|
|
1299
|
+
const cached = checkCache(agentId, "failure-analysis", dataHash);
|
|
1300
|
+
if (cached) {
|
|
1301
|
+
return shortCircuit(agentId, "failure-analysis", cached.response, true, cached.timestamp);
|
|
1302
|
+
}
|
|
1303
|
+
const prompt = buildFailureAnalysisPrompt(failures, profile);
|
|
1304
|
+
try {
|
|
1305
|
+
const response = await analysisFn(prompt);
|
|
1306
|
+
return storeAndReturn(agentId, "failure-analysis", prompt, response, dataHash);
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1309
|
+
return shortCircuit(agentId, "failure-analysis", `Analysis failed: ${message}`, false);
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
async explainAnomaly(agentId, event) {
|
|
1313
|
+
const profile = store.getAgentProfile(agentId);
|
|
1314
|
+
if (!profile) {
|
|
1315
|
+
return shortCircuit(
|
|
1316
|
+
agentId,
|
|
1317
|
+
"anomaly-explanation",
|
|
1318
|
+
"No data available for this agent.",
|
|
1319
|
+
false
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
const dataHash = simpleHash(JSON.stringify({ event, profile }));
|
|
1323
|
+
const cached = checkCache(agentId, "anomaly-explanation", dataHash);
|
|
1324
|
+
if (cached) {
|
|
1325
|
+
return shortCircuit(
|
|
1326
|
+
agentId,
|
|
1327
|
+
"anomaly-explanation",
|
|
1328
|
+
cached.response,
|
|
1329
|
+
true,
|
|
1330
|
+
cached.timestamp
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
const prompt = buildAnomalyExplanationPrompt(event, profile);
|
|
1334
|
+
try {
|
|
1335
|
+
const response = await analysisFn(prompt);
|
|
1336
|
+
return storeAndReturn(agentId, "anomaly-explanation", prompt, response, dataHash);
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1339
|
+
return shortCircuit(agentId, "anomaly-explanation", `Analysis failed: ${message}`, false);
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
async summarizeAgent(agentId) {
|
|
1343
|
+
const profile = store.getAgentProfile(agentId);
|
|
1344
|
+
if (!profile) {
|
|
1345
|
+
return shortCircuit(agentId, "agent-summary", "No data available for this agent.", false);
|
|
1346
|
+
}
|
|
1347
|
+
const recentEvents = store.getRecentEvents(agentId, { limit: 20 });
|
|
1348
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
1349
|
+
const dataHash = simpleHash(JSON.stringify({ profile, recentEvents, patterns }));
|
|
1350
|
+
const cached = checkCache(agentId, "agent-summary", dataHash);
|
|
1351
|
+
if (cached) {
|
|
1352
|
+
return shortCircuit(agentId, "agent-summary", cached.response, true, cached.timestamp);
|
|
1353
|
+
}
|
|
1354
|
+
const prompt = buildAgentSummaryPrompt(profile, recentEvents, patterns);
|
|
1355
|
+
try {
|
|
1356
|
+
const response = await analysisFn(prompt);
|
|
1357
|
+
return storeAndReturn(agentId, "agent-summary", prompt, response, dataHash);
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1360
|
+
return shortCircuit(agentId, "agent-summary", `Analysis failed: ${message}`, false);
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
async suggestFixes(agentId) {
|
|
1364
|
+
const profile = store.getAgentProfile(agentId);
|
|
1365
|
+
if (!profile) {
|
|
1366
|
+
return shortCircuit(agentId, "fix-suggestion", "No data available for this agent.", false);
|
|
1367
|
+
}
|
|
1368
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
1369
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
1370
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
1371
|
+
if (failures.length === 0 && profile.knownBottlenecks.length === 0) {
|
|
1372
|
+
return shortCircuit(
|
|
1373
|
+
agentId,
|
|
1374
|
+
"fix-suggestion",
|
|
1375
|
+
"Agent is healthy \u2014 no failures or bottlenecks detected.",
|
|
1376
|
+
false
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile, patterns }));
|
|
1380
|
+
const cached = checkCache(agentId, "fix-suggestion", dataHash);
|
|
1381
|
+
if (cached) {
|
|
1382
|
+
return shortCircuit(agentId, "fix-suggestion", cached.response, true, cached.timestamp);
|
|
1383
|
+
}
|
|
1384
|
+
const prompt = buildFixSuggestionPrompt(failures, profile, patterns);
|
|
1385
|
+
try {
|
|
1386
|
+
const response = await analysisFn(prompt);
|
|
1387
|
+
return storeAndReturn(agentId, "fix-suggestion", prompt, response, dataHash);
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1390
|
+
return shortCircuit(agentId, "fix-suggestion", `Analysis failed: ${message}`, false);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// src/json-event-writer.ts
|
|
1397
|
+
var import_node_fs = require("fs");
|
|
1398
|
+
var import_node_path = require("path");
|
|
1399
|
+
function createJsonEventWriter(config) {
|
|
1400
|
+
const { outputDir } = config;
|
|
1401
|
+
function ensureDir() {
|
|
1402
|
+
(0, import_node_fs.mkdirSync)(outputDir, { recursive: true });
|
|
1403
|
+
}
|
|
1404
|
+
function eventFileName(event) {
|
|
1405
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
1406
|
+
const agentId = "agentId" in event ? event.agentId : "unknown";
|
|
1407
|
+
return `${typePart}-${agentId}-${event.timestamp}.json`;
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
async write(_graph) {
|
|
1411
|
+
},
|
|
1412
|
+
async writeEvent(event) {
|
|
1413
|
+
ensureDir();
|
|
1414
|
+
const fileName = eventFileName(event);
|
|
1415
|
+
const filePath = (0, import_node_path.join)(outputDir, fileName);
|
|
1416
|
+
(0, import_node_fs.writeFileSync)(filePath, JSON.stringify(event, null, 2), "utf-8");
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/knowledge-store.ts
|
|
642
1422
|
var import_node_fs2 = require("fs");
|
|
643
1423
|
var import_node_path2 = require("path");
|
|
1424
|
+
var DEFAULT_BASE_DIR = ".agentflow/knowledge";
|
|
1425
|
+
var MAX_RECENT_DURATIONS = 100;
|
|
1426
|
+
var writeCounter = 0;
|
|
1427
|
+
function toDateDir(epochMs) {
|
|
1428
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1429
|
+
}
|
|
1430
|
+
function readJson(filePath) {
|
|
1431
|
+
try {
|
|
1432
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(filePath, "utf-8"));
|
|
1433
|
+
} catch {
|
|
1434
|
+
return null;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
function writeJsonAtomic(filePath, data) {
|
|
1438
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
1439
|
+
(0, import_node_fs2.writeFileSync)(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
1440
|
+
(0, import_node_fs2.renameSync)(tmpPath, filePath);
|
|
1441
|
+
}
|
|
1442
|
+
function emptyProfile(agentId) {
|
|
1443
|
+
return {
|
|
1444
|
+
agentId,
|
|
1445
|
+
totalRuns: 0,
|
|
1446
|
+
successCount: 0,
|
|
1447
|
+
failureCount: 0,
|
|
1448
|
+
failureRate: 0,
|
|
1449
|
+
recentDurations: [],
|
|
1450
|
+
lastConformanceScore: null,
|
|
1451
|
+
knownBottlenecks: [],
|
|
1452
|
+
lastPatternTimestamp: null,
|
|
1453
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
function mergeExecutionEvent(profile, event) {
|
|
1457
|
+
const totalRuns = profile.totalRuns + 1;
|
|
1458
|
+
const isFailure = event.eventType === "execution.failed";
|
|
1459
|
+
const successCount = profile.successCount + (isFailure ? 0 : 1);
|
|
1460
|
+
const failureCount = profile.failureCount + (isFailure ? 1 : 0);
|
|
1461
|
+
const durations = [...profile.recentDurations, event.duration];
|
|
1462
|
+
if (durations.length > MAX_RECENT_DURATIONS) {
|
|
1463
|
+
durations.shift();
|
|
1464
|
+
}
|
|
1465
|
+
const conformanceScore = event.processContext?.conformanceScore ?? profile.lastConformanceScore;
|
|
1466
|
+
return {
|
|
1467
|
+
agentId: profile.agentId,
|
|
1468
|
+
totalRuns,
|
|
1469
|
+
successCount,
|
|
1470
|
+
failureCount,
|
|
1471
|
+
failureRate: failureCount / totalRuns,
|
|
1472
|
+
recentDurations: durations,
|
|
1473
|
+
lastConformanceScore: conformanceScore,
|
|
1474
|
+
knownBottlenecks: profile.knownBottlenecks,
|
|
1475
|
+
lastPatternTimestamp: profile.lastPatternTimestamp,
|
|
1476
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function mergePatternEvent(profile, event) {
|
|
1480
|
+
const existingBottlenecks = new Set(profile.knownBottlenecks);
|
|
1481
|
+
for (const b of event.pattern.topBottlenecks) {
|
|
1482
|
+
existingBottlenecks.add(b.nodeName);
|
|
1483
|
+
}
|
|
1484
|
+
return {
|
|
1485
|
+
...profile,
|
|
1486
|
+
knownBottlenecks: [...existingBottlenecks],
|
|
1487
|
+
lastPatternTimestamp: event.timestamp,
|
|
1488
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
function createKnowledgeStore(config) {
|
|
1492
|
+
const baseDir = config?.baseDir ?? DEFAULT_BASE_DIR;
|
|
1493
|
+
const eventsDir = (0, import_node_path2.join)(baseDir, "events");
|
|
1494
|
+
const patternsDir = (0, import_node_path2.join)(baseDir, "patterns");
|
|
1495
|
+
const profilesDir = (0, import_node_path2.join)(baseDir, "profiles");
|
|
1496
|
+
const insightsDir = (0, import_node_path2.join)(baseDir, "insights");
|
|
1497
|
+
function ensureDir(dir) {
|
|
1498
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
1499
|
+
}
|
|
1500
|
+
function profilePath(agentId) {
|
|
1501
|
+
const safe = agentId.replace(/[/\\]/g, "_").replace(/\.\./g, "_");
|
|
1502
|
+
return (0, import_node_path2.join)(profilesDir, `${safe}.json`);
|
|
1503
|
+
}
|
|
1504
|
+
function appendExecutionEvent(event) {
|
|
1505
|
+
const dateDir = (0, import_node_path2.join)(eventsDir, event.agentId, toDateDir(event.timestamp));
|
|
1506
|
+
ensureDir(dateDir);
|
|
1507
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
1508
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1509
|
+
const fileName = `${typePart}-${event.timestamp}-${seq}.json`;
|
|
1510
|
+
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(dateDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1511
|
+
ensureDir(profilesDir);
|
|
1512
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1513
|
+
const updated = mergeExecutionEvent(existing, event);
|
|
1514
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1515
|
+
}
|
|
1516
|
+
function appendPatternEvent(event) {
|
|
1517
|
+
const agentPatternDir = (0, import_node_path2.join)(patternsDir, event.agentId);
|
|
1518
|
+
ensureDir(agentPatternDir);
|
|
1519
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1520
|
+
const fileName = `${event.timestamp}-${seq}.json`;
|
|
1521
|
+
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(agentPatternDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1522
|
+
ensureDir(profilesDir);
|
|
1523
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1524
|
+
const updated = mergePatternEvent(existing, event);
|
|
1525
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1526
|
+
}
|
|
1527
|
+
return {
|
|
1528
|
+
baseDir,
|
|
1529
|
+
append(event) {
|
|
1530
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1531
|
+
appendPatternEvent(event);
|
|
1532
|
+
} else {
|
|
1533
|
+
appendExecutionEvent(event);
|
|
1534
|
+
}
|
|
1535
|
+
},
|
|
1536
|
+
getRecentEvents(agentId, options) {
|
|
1537
|
+
const limit = options?.limit ?? 50;
|
|
1538
|
+
const since = options?.since ?? 0;
|
|
1539
|
+
const agentDir = (0, import_node_path2.join)(eventsDir, agentId);
|
|
1540
|
+
if (!(0, import_node_fs2.existsSync)(agentDir)) return [];
|
|
1541
|
+
const events = [];
|
|
1542
|
+
const dateDirs = (0, import_node_fs2.readdirSync)(agentDir).sort().reverse();
|
|
1543
|
+
for (const dateDir of dateDirs) {
|
|
1544
|
+
const fullDateDir = (0, import_node_path2.join)(agentDir, dateDir);
|
|
1545
|
+
let files;
|
|
1546
|
+
try {
|
|
1547
|
+
files = (0, import_node_fs2.readdirSync)(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1548
|
+
} catch {
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
for (const file of files) {
|
|
1552
|
+
const event = readJson((0, import_node_path2.join)(fullDateDir, file));
|
|
1553
|
+
if (event && event.timestamp > since) {
|
|
1554
|
+
events.push(event);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (events.length >= limit * 2) break;
|
|
1558
|
+
}
|
|
1559
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1560
|
+
return events.slice(0, limit);
|
|
1561
|
+
},
|
|
1562
|
+
getAgentProfile(agentId) {
|
|
1563
|
+
return readJson(profilePath(agentId));
|
|
1564
|
+
},
|
|
1565
|
+
getPatternHistory(agentId, options) {
|
|
1566
|
+
const limit = options?.limit ?? 20;
|
|
1567
|
+
const agentPatternDir = (0, import_node_path2.join)(patternsDir, agentId);
|
|
1568
|
+
if (!(0, import_node_fs2.existsSync)(agentPatternDir)) return [];
|
|
1569
|
+
const files = (0, import_node_fs2.readdirSync)(agentPatternDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
1570
|
+
const events = [];
|
|
1571
|
+
for (const file of files.slice(0, limit)) {
|
|
1572
|
+
const event = readJson((0, import_node_path2.join)(agentPatternDir, file));
|
|
1573
|
+
if (event) events.push(event);
|
|
1574
|
+
}
|
|
1575
|
+
return events;
|
|
1576
|
+
},
|
|
1577
|
+
compact(options) {
|
|
1578
|
+
let removed = 0;
|
|
1579
|
+
if ((0, import_node_fs2.existsSync)(eventsDir)) {
|
|
1580
|
+
for (const agentId of (0, import_node_fs2.readdirSync)(eventsDir)) {
|
|
1581
|
+
const agentDir = (0, import_node_path2.join)(eventsDir, agentId);
|
|
1582
|
+
let dateDirs;
|
|
1583
|
+
try {
|
|
1584
|
+
dateDirs = (0, import_node_fs2.readdirSync)(agentDir);
|
|
1585
|
+
} catch {
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
for (const dateDir of dateDirs) {
|
|
1589
|
+
const fullDateDir = (0, import_node_path2.join)(agentDir, dateDir);
|
|
1590
|
+
let files;
|
|
1591
|
+
try {
|
|
1592
|
+
files = (0, import_node_fs2.readdirSync)(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1593
|
+
} catch {
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
for (const file of files) {
|
|
1597
|
+
const parts = file.replace(".json", "").split("-");
|
|
1598
|
+
const tsPart = parts[parts.length - 2];
|
|
1599
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1600
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1601
|
+
try {
|
|
1602
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(fullDateDir, file));
|
|
1603
|
+
removed++;
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
if ((0, import_node_fs2.readdirSync)(fullDateDir).length === 0) {
|
|
1610
|
+
(0, import_node_fs2.rmSync)(fullDateDir, { recursive: true });
|
|
1611
|
+
}
|
|
1612
|
+
} catch {
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if ((0, import_node_fs2.existsSync)(patternsDir)) {
|
|
1618
|
+
for (const agentId of (0, import_node_fs2.readdirSync)(patternsDir)) {
|
|
1619
|
+
const agentPatternDir = (0, import_node_path2.join)(patternsDir, agentId);
|
|
1620
|
+
let files;
|
|
1621
|
+
try {
|
|
1622
|
+
files = (0, import_node_fs2.readdirSync)(agentPatternDir).filter((f) => f.endsWith(".json"));
|
|
1623
|
+
} catch {
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
for (const file of files) {
|
|
1627
|
+
const ts = Number.parseInt(file.split("-")[0] ?? "", 10);
|
|
1628
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1629
|
+
try {
|
|
1630
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(agentPatternDir, file));
|
|
1631
|
+
removed++;
|
|
1632
|
+
} catch {
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if ((0, import_node_fs2.existsSync)(insightsDir)) {
|
|
1639
|
+
for (const agentId of (0, import_node_fs2.readdirSync)(insightsDir)) {
|
|
1640
|
+
const agentInsightDir = (0, import_node_path2.join)(insightsDir, agentId);
|
|
1641
|
+
let files;
|
|
1642
|
+
try {
|
|
1643
|
+
files = (0, import_node_fs2.readdirSync)(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1644
|
+
} catch {
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
for (const file of files) {
|
|
1648
|
+
const parts = file.replace(".json", "").split("-");
|
|
1649
|
+
const tsPart = parts[parts.length - 2];
|
|
1650
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1651
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1652
|
+
try {
|
|
1653
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(agentInsightDir, file));
|
|
1654
|
+
removed++;
|
|
1655
|
+
} catch {
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return { removed };
|
|
1662
|
+
},
|
|
1663
|
+
appendInsight(event) {
|
|
1664
|
+
const agentInsightDir = (0, import_node_path2.join)(insightsDir, event.agentId);
|
|
1665
|
+
ensureDir(agentInsightDir);
|
|
1666
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1667
|
+
const fileName = `${event.insightType}-${event.timestamp}-${seq}.json`;
|
|
1668
|
+
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(agentInsightDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1669
|
+
},
|
|
1670
|
+
getRecentInsights(agentId, options) {
|
|
1671
|
+
const limit = options?.limit ?? 10;
|
|
1672
|
+
const typeFilter = options?.type;
|
|
1673
|
+
const agentInsightDir = (0, import_node_path2.join)(insightsDir, agentId);
|
|
1674
|
+
if (!(0, import_node_fs2.existsSync)(agentInsightDir)) return [];
|
|
1675
|
+
const files = (0, import_node_fs2.readdirSync)(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1676
|
+
const events = [];
|
|
1677
|
+
for (const file of files) {
|
|
1678
|
+
const event = readJson((0, import_node_path2.join)(agentInsightDir, file));
|
|
1679
|
+
if (event) {
|
|
1680
|
+
if (typeFilter && event.insightType !== typeFilter) continue;
|
|
1681
|
+
events.push(event);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1685
|
+
return events.slice(0, limit);
|
|
1686
|
+
},
|
|
1687
|
+
// EventWriter interface
|
|
1688
|
+
async write(_graph) {
|
|
1689
|
+
},
|
|
1690
|
+
async writeEvent(event) {
|
|
1691
|
+
this.append(event);
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/live.ts
|
|
1697
|
+
var import_node_fs4 = require("fs");
|
|
1698
|
+
var import_node_path4 = require("path");
|
|
644
1699
|
|
|
645
1700
|
// src/loader.ts
|
|
646
1701
|
function toNodesMap(raw) {
|
|
@@ -669,7 +1724,8 @@ function loadGraph(input) {
|
|
|
669
1724
|
events: raw.events ?? [],
|
|
670
1725
|
traceId: raw.traceId,
|
|
671
1726
|
spanId: raw.spanId,
|
|
672
|
-
parentSpanId: raw.parentSpanId
|
|
1727
|
+
parentSpanId: raw.parentSpanId,
|
|
1728
|
+
metadata: raw.metadata ?? {}
|
|
673
1729
|
};
|
|
674
1730
|
}
|
|
675
1731
|
function graphToJson(graph) {
|
|
@@ -696,8 +1752,8 @@ function graphToJson(graph) {
|
|
|
696
1752
|
|
|
697
1753
|
// src/process-audit.ts
|
|
698
1754
|
var import_node_child_process = require("child_process");
|
|
699
|
-
var
|
|
700
|
-
var
|
|
1755
|
+
var import_node_fs3 = require("fs");
|
|
1756
|
+
var import_node_path3 = require("path");
|
|
701
1757
|
function isPidAlive(pid) {
|
|
702
1758
|
try {
|
|
703
1759
|
process.kill(pid, 0);
|
|
@@ -708,7 +1764,7 @@ function isPidAlive(pid) {
|
|
|
708
1764
|
}
|
|
709
1765
|
function pidMatchesName(pid, name) {
|
|
710
1766
|
try {
|
|
711
|
-
const cmdline = (0,
|
|
1767
|
+
const cmdline = (0, import_node_fs3.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
712
1768
|
return cmdline.includes(name);
|
|
713
1769
|
} catch {
|
|
714
1770
|
return false;
|
|
@@ -716,8 +1772,8 @@ function pidMatchesName(pid, name) {
|
|
|
716
1772
|
}
|
|
717
1773
|
function readPidFile(path) {
|
|
718
1774
|
try {
|
|
719
|
-
const pid = parseInt((0,
|
|
720
|
-
return isNaN(pid) ? null : pid;
|
|
1775
|
+
const pid = parseInt((0, import_node_fs3.readFileSync)(path, "utf8").trim(), 10);
|
|
1776
|
+
return Number.isNaN(pid) ? null : pid;
|
|
721
1777
|
} catch {
|
|
722
1778
|
return null;
|
|
723
1779
|
}
|
|
@@ -731,8 +1787,8 @@ function auditPidFile(config) {
|
|
|
731
1787
|
pid: null,
|
|
732
1788
|
alive: false,
|
|
733
1789
|
matchesProcess: false,
|
|
734
|
-
stale: !(0,
|
|
735
|
-
reason: (0,
|
|
1790
|
+
stale: !(0, import_node_fs3.existsSync)(config.pidFile),
|
|
1791
|
+
reason: (0, import_node_fs3.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
736
1792
|
};
|
|
737
1793
|
}
|
|
738
1794
|
const alive = isPidAlive(pid);
|
|
@@ -752,8 +1808,15 @@ function auditSystemd(config) {
|
|
|
752
1808
|
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
753
1809
|
const unit = config.systemdUnit;
|
|
754
1810
|
try {
|
|
755
|
-
const raw = (0, import_node_child_process.
|
|
756
|
-
|
|
1811
|
+
const raw = (0, import_node_child_process.execFileSync)(
|
|
1812
|
+
"systemctl",
|
|
1813
|
+
[
|
|
1814
|
+
"--user",
|
|
1815
|
+
"show",
|
|
1816
|
+
unit,
|
|
1817
|
+
"--property=ActiveState,SubState,MainPID,NRestarts,Result",
|
|
1818
|
+
"--no-pager"
|
|
1819
|
+
],
|
|
757
1820
|
{ encoding: "utf8", timeout: 5e3 }
|
|
758
1821
|
);
|
|
759
1822
|
const props = {};
|
|
@@ -761,11 +1824,11 @@ function auditSystemd(config) {
|
|
|
761
1824
|
const [k, ...v] = line.split("=");
|
|
762
1825
|
if (k) props[k.trim()] = v.join("=").trim();
|
|
763
1826
|
}
|
|
764
|
-
const activeState = props
|
|
765
|
-
const subState = props
|
|
766
|
-
const mainPid = parseInt(props
|
|
767
|
-
const restarts = parseInt(props
|
|
768
|
-
const result = props
|
|
1827
|
+
const activeState = props.ActiveState ?? "unknown";
|
|
1828
|
+
const subState = props.SubState ?? "unknown";
|
|
1829
|
+
const mainPid = parseInt(props.MainPID ?? "0", 10);
|
|
1830
|
+
const restarts = parseInt(props.NRestarts ?? "0", 10);
|
|
1831
|
+
const result = props.Result ?? "unknown";
|
|
769
1832
|
return {
|
|
770
1833
|
unit,
|
|
771
1834
|
activeState,
|
|
@@ -781,9 +1844,9 @@ function auditSystemd(config) {
|
|
|
781
1844
|
}
|
|
782
1845
|
}
|
|
783
1846
|
function auditWorkers(config) {
|
|
784
|
-
if (!config.workersFile || !(0,
|
|
1847
|
+
if (!config.workersFile || !(0, import_node_fs3.existsSync)(config.workersFile)) return null;
|
|
785
1848
|
try {
|
|
786
|
-
const data = JSON.parse((0,
|
|
1849
|
+
const data = JSON.parse((0, import_node_fs3.readFileSync)(config.workersFile, "utf8"));
|
|
787
1850
|
const orchPid = data.pid ?? null;
|
|
788
1851
|
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
789
1852
|
const workers = [];
|
|
@@ -811,17 +1874,17 @@ function auditWorkers(config) {
|
|
|
811
1874
|
}
|
|
812
1875
|
function readCmdline(pid) {
|
|
813
1876
|
try {
|
|
814
|
-
return (0,
|
|
1877
|
+
return (0, import_node_fs3.readFileSync)(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim();
|
|
815
1878
|
} catch {
|
|
816
1879
|
return "";
|
|
817
1880
|
}
|
|
818
1881
|
}
|
|
819
1882
|
function getOsProcesses(processName) {
|
|
820
1883
|
try {
|
|
821
|
-
const raw = (0, import_node_child_process.execSync)(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
);
|
|
1884
|
+
const raw = (0, import_node_child_process.execSync)(`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`, {
|
|
1885
|
+
encoding: "utf8",
|
|
1886
|
+
timeout: 5e3
|
|
1887
|
+
});
|
|
825
1888
|
const results = [];
|
|
826
1889
|
for (const line of raw.split("\n")) {
|
|
827
1890
|
if (!line.includes(processName)) continue;
|
|
@@ -829,7 +1892,7 @@ function getOsProcesses(processName) {
|
|
|
829
1892
|
const trimmed = line.trim();
|
|
830
1893
|
const parts = trimmed.split(/\s+/);
|
|
831
1894
|
const pid = parseInt(parts[0] ?? "0", 10);
|
|
832
|
-
if (isNaN(pid) || pid <= 0) continue;
|
|
1895
|
+
if (Number.isNaN(pid) || pid <= 0) continue;
|
|
833
1896
|
const cpu = parts[1] ?? "0";
|
|
834
1897
|
const mem = parts[2] ?? "0";
|
|
835
1898
|
const elapsed = parts[3] ?? "";
|
|
@@ -844,53 +1907,83 @@ function getOsProcesses(processName) {
|
|
|
844
1907
|
}
|
|
845
1908
|
}
|
|
846
1909
|
function discoverProcessConfig(dirs) {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1910
|
+
const configs = discoverAllProcessConfigs(dirs);
|
|
1911
|
+
return configs.length > 0 ? configs[0] ?? null : null;
|
|
1912
|
+
}
|
|
1913
|
+
function discoverAllProcessConfigs(dirs) {
|
|
1914
|
+
const configs = /* @__PURE__ */ new Map();
|
|
850
1915
|
for (const dir of dirs) {
|
|
851
|
-
if (!(0,
|
|
1916
|
+
if (!(0, import_node_fs3.existsSync)(dir)) continue;
|
|
852
1917
|
let entries;
|
|
853
1918
|
try {
|
|
854
|
-
entries = (0,
|
|
1919
|
+
entries = (0, import_node_fs3.readdirSync)(dir);
|
|
855
1920
|
} catch {
|
|
856
1921
|
continue;
|
|
857
1922
|
}
|
|
858
1923
|
for (const f of entries) {
|
|
859
|
-
const fp = (0,
|
|
1924
|
+
const fp = (0, import_node_path3.join)(dir, f);
|
|
860
1925
|
try {
|
|
861
|
-
if (!(0,
|
|
1926
|
+
if (!(0, import_node_fs3.statSync)(fp).isFile()) continue;
|
|
862
1927
|
} catch {
|
|
863
1928
|
continue;
|
|
864
1929
|
}
|
|
865
|
-
if (f.endsWith(".pid")
|
|
866
|
-
|
|
867
|
-
if (!
|
|
868
|
-
|
|
1930
|
+
if (f.endsWith(".pid")) {
|
|
1931
|
+
const name = (0, import_node_path3.basename)(f, ".pid");
|
|
1932
|
+
if (!configs.has(name)) {
|
|
1933
|
+
configs.set(name, { processName: name });
|
|
869
1934
|
}
|
|
1935
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
1936
|
+
if (!cfg.pidFile) cfg.pidFile = fp;
|
|
870
1937
|
}
|
|
871
|
-
if (
|
|
872
|
-
|
|
873
|
-
if (
|
|
874
|
-
|
|
1938
|
+
if (f === "workers.json" || f.endsWith("-workers.json")) {
|
|
1939
|
+
const name = f === "workers.json" ? "" : (0, import_node_path3.basename)(f, "-workers.json");
|
|
1940
|
+
if (name && !configs.has(name)) {
|
|
1941
|
+
configs.set(name, { processName: name });
|
|
1942
|
+
}
|
|
1943
|
+
if (name) {
|
|
1944
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
1945
|
+
if (!cfg.workersFile) cfg.workersFile = fp;
|
|
875
1946
|
}
|
|
876
1947
|
}
|
|
877
1948
|
}
|
|
878
1949
|
}
|
|
879
|
-
if (!processName && !pidFile && !workersFile) return null;
|
|
880
|
-
if (!processName) processName = "agent";
|
|
881
|
-
let systemdUnit;
|
|
882
1950
|
try {
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
{ encoding: "utf8", timeout: 3e3 }
|
|
1951
|
+
const raw = (0, import_node_child_process.execSync)(
|
|
1952
|
+
"systemctl --user list-units --type=service --all --no-legend --no-pager 2>/dev/null",
|
|
1953
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
887
1954
|
);
|
|
888
|
-
|
|
889
|
-
|
|
1955
|
+
for (const line of raw.trim().split("\n")) {
|
|
1956
|
+
if (!line.trim()) continue;
|
|
1957
|
+
const parts = line.trim().split(/\s+/);
|
|
1958
|
+
const unitName = parts[0] ?? "";
|
|
1959
|
+
const loadState = parts[1] ?? "";
|
|
1960
|
+
if (!unitName.endsWith(".service") || loadState !== "loaded") continue;
|
|
1961
|
+
const name = unitName.replace(".service", "");
|
|
1962
|
+
if (/^(dbus|gpg-agent|dirmngr|keyboxd|snapd\.|pk-|launchpadlib-)/.test(name)) continue;
|
|
1963
|
+
if (!configs.has(name)) {
|
|
1964
|
+
configs.set(name, { processName: name });
|
|
1965
|
+
}
|
|
1966
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
1967
|
+
cfg.systemdUnit = unitName;
|
|
890
1968
|
}
|
|
891
1969
|
} catch {
|
|
892
1970
|
}
|
|
893
|
-
|
|
1971
|
+
for (const cfg of configs.values()) {
|
|
1972
|
+
if (cfg.systemdUnit !== void 0) continue;
|
|
1973
|
+
try {
|
|
1974
|
+
const unitName = `${cfg.processName}.service`;
|
|
1975
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
1976
|
+
"systemctl",
|
|
1977
|
+
["--user", "show", unitName, "--property=LoadState", "--no-pager"],
|
|
1978
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
1979
|
+
);
|
|
1980
|
+
if (result.includes("LoadState=loaded")) {
|
|
1981
|
+
cfg.systemdUnit = unitName;
|
|
1982
|
+
}
|
|
1983
|
+
} catch {
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return [...configs.values()];
|
|
894
1987
|
}
|
|
895
1988
|
function auditProcesses(config) {
|
|
896
1989
|
const pidFile = auditPidFile(config);
|
|
@@ -906,16 +1999,45 @@ function auditProcesses(config) {
|
|
|
906
1999
|
}
|
|
907
2000
|
}
|
|
908
2001
|
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
2002
|
+
const childPids = /* @__PURE__ */ new Set();
|
|
2003
|
+
for (const knownPid of knownPids) {
|
|
2004
|
+
try {
|
|
2005
|
+
const childrenRaw = (0, import_node_fs3.readFileSync)(
|
|
2006
|
+
`/proc/${knownPid}/task/${knownPid}/children`,
|
|
2007
|
+
"utf8"
|
|
2008
|
+
).trim();
|
|
2009
|
+
if (childrenRaw) {
|
|
2010
|
+
for (const c of childrenRaw.split(/\s+/)) {
|
|
2011
|
+
const cp = parseInt(c, 10);
|
|
2012
|
+
if (!Number.isNaN(cp)) childPids.add(cp);
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
} catch {
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
for (const p of osProcesses) {
|
|
2019
|
+
if (knownPids.has(p.pid)) continue;
|
|
2020
|
+
try {
|
|
2021
|
+
const statusContent = (0, import_node_fs3.readFileSync)(`/proc/${p.pid}/status`, "utf8");
|
|
2022
|
+
const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
|
|
2023
|
+
if (ppidMatch) {
|
|
2024
|
+
const ppid = parseInt(ppidMatch[1] ?? "0", 10);
|
|
2025
|
+
if (knownPids.has(ppid)) childPids.add(p.pid);
|
|
2026
|
+
}
|
|
2027
|
+
} catch {
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
909
2030
|
const selfPid = process.pid;
|
|
910
2031
|
const selfPpid = process.ppid;
|
|
911
2032
|
const orphans = osProcesses.filter(
|
|
912
|
-
(p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
2033
|
+
(p) => !knownPids.has(p.pid) && !childPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
913
2034
|
);
|
|
914
2035
|
const problems = [];
|
|
915
2036
|
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
916
2037
|
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
917
2038
|
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
918
|
-
if (systemd && systemd.restarts > 10)
|
|
2039
|
+
if (systemd && systemd.restarts > 10)
|
|
2040
|
+
problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
919
2041
|
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
920
2042
|
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
921
2043
|
}
|
|
@@ -924,15 +2046,24 @@ function auditProcesses(config) {
|
|
|
924
2046
|
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
925
2047
|
}
|
|
926
2048
|
}
|
|
927
|
-
if (orphans.length > 0)
|
|
2049
|
+
if (orphans.length > 0)
|
|
2050
|
+
problems.push(
|
|
2051
|
+
`${orphans.length} orphan process(es) not tracked by PID file or workers registry`
|
|
2052
|
+
);
|
|
928
2053
|
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
929
2054
|
}
|
|
930
2055
|
function formatAuditReport(result) {
|
|
931
2056
|
const lines = [];
|
|
932
2057
|
lines.push("");
|
|
933
|
-
lines.push(
|
|
934
|
-
|
|
935
|
-
|
|
2058
|
+
lines.push(
|
|
2059
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"
|
|
2060
|
+
);
|
|
2061
|
+
lines.push(
|
|
2062
|
+
"\u2551 \u{1F50D} P R O C E S S A U D I T \u2551"
|
|
2063
|
+
);
|
|
2064
|
+
lines.push(
|
|
2065
|
+
"\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
2066
|
+
);
|
|
936
2067
|
if (result.pidFile) {
|
|
937
2068
|
const pf = result.pidFile;
|
|
938
2069
|
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
@@ -950,25 +2081,33 @@ function formatAuditReport(result) {
|
|
|
950
2081
|
}
|
|
951
2082
|
if (result.workers) {
|
|
952
2083
|
const w = result.workers;
|
|
953
|
-
lines.push(
|
|
954
|
-
|
|
2084
|
+
lines.push(
|
|
2085
|
+
`
|
|
2086
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`
|
|
2087
|
+
);
|
|
955
2088
|
for (const worker of w.workers) {
|
|
956
2089
|
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
957
|
-
lines.push(
|
|
2090
|
+
lines.push(
|
|
2091
|
+
` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`
|
|
2092
|
+
);
|
|
958
2093
|
}
|
|
959
2094
|
}
|
|
960
2095
|
if (result.osProcesses.length > 0) {
|
|
961
2096
|
lines.push(`
|
|
962
2097
|
OS Processes (${result.osProcesses.length} total)`);
|
|
963
2098
|
for (const p of result.osProcesses) {
|
|
964
|
-
lines.push(
|
|
2099
|
+
lines.push(
|
|
2100
|
+
` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed.padEnd(10)} ${p.command.substring(0, 50)}`
|
|
2101
|
+
);
|
|
965
2102
|
}
|
|
966
2103
|
}
|
|
967
2104
|
if (result.orphans.length > 0) {
|
|
968
2105
|
lines.push(`
|
|
969
2106
|
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
970
2107
|
for (const p of result.orphans) {
|
|
971
|
-
lines.push(
|
|
2108
|
+
lines.push(
|
|
2109
|
+
` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`
|
|
2110
|
+
);
|
|
972
2111
|
lines.push(` Started: ${p.started}`);
|
|
973
2112
|
lines.push(` Command: ${p.cmdline || p.command}`);
|
|
974
2113
|
}
|
|
@@ -1006,26 +2145,26 @@ function parseArgs(argv) {
|
|
|
1006
2145
|
if (args[0] === "live") args.shift();
|
|
1007
2146
|
let i = 0;
|
|
1008
2147
|
while (i < args.length) {
|
|
1009
|
-
const arg = args[i];
|
|
2148
|
+
const arg = args[i] ?? "";
|
|
1010
2149
|
if (arg === "--help" || arg === "-h") {
|
|
1011
2150
|
printUsage();
|
|
1012
2151
|
process.exit(0);
|
|
1013
2152
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
1014
2153
|
i++;
|
|
1015
2154
|
const v = parseInt(args[i] ?? "", 10);
|
|
1016
|
-
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
2155
|
+
if (!Number.isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
1017
2156
|
i++;
|
|
1018
2157
|
} else if (arg === "--recursive" || arg === "-R") {
|
|
1019
2158
|
config.recursive = true;
|
|
1020
2159
|
i++;
|
|
1021
2160
|
} else if (!arg.startsWith("-")) {
|
|
1022
|
-
config.dirs.push((0,
|
|
2161
|
+
config.dirs.push((0, import_node_path4.resolve)(arg));
|
|
1023
2162
|
i++;
|
|
1024
2163
|
} else {
|
|
1025
2164
|
i++;
|
|
1026
2165
|
}
|
|
1027
2166
|
}
|
|
1028
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
2167
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path4.resolve)("."));
|
|
1029
2168
|
return config;
|
|
1030
2169
|
}
|
|
1031
2170
|
function printUsage() {
|
|
@@ -1061,7 +2200,7 @@ function scanFiles(dirs, recursive) {
|
|
|
1061
2200
|
const seen = /* @__PURE__ */ new Set();
|
|
1062
2201
|
function scanDir(d, topLevel) {
|
|
1063
2202
|
try {
|
|
1064
|
-
const dirStat = (0,
|
|
2203
|
+
const dirStat = (0, import_node_fs4.statSync)(d);
|
|
1065
2204
|
const dirMtime = dirStat.mtime.getTime();
|
|
1066
2205
|
const cachedMtime = dirMtimeCache.get(d);
|
|
1067
2206
|
if (cachedMtime === dirMtime) {
|
|
@@ -1077,13 +2216,13 @@ function scanFiles(dirs, recursive) {
|
|
|
1077
2216
|
}
|
|
1078
2217
|
}
|
|
1079
2218
|
const dirResults = [];
|
|
1080
|
-
for (const f of (0,
|
|
2219
|
+
for (const f of (0, import_node_fs4.readdirSync)(d)) {
|
|
1081
2220
|
if (f.startsWith(".")) continue;
|
|
1082
|
-
const fp = (0,
|
|
2221
|
+
const fp = (0, import_node_path4.join)(d, f);
|
|
1083
2222
|
if (seen.has(fp)) continue;
|
|
1084
2223
|
let stat;
|
|
1085
2224
|
try {
|
|
1086
|
-
stat = (0,
|
|
2225
|
+
stat = (0, import_node_fs4.statSync)(fp);
|
|
1087
2226
|
} catch {
|
|
1088
2227
|
continue;
|
|
1089
2228
|
}
|
|
@@ -1094,12 +2233,22 @@ function scanFiles(dirs, recursive) {
|
|
|
1094
2233
|
if (!stat.isFile()) continue;
|
|
1095
2234
|
if (f.endsWith(".json")) {
|
|
1096
2235
|
seen.add(fp);
|
|
1097
|
-
const entry = {
|
|
2236
|
+
const entry = {
|
|
2237
|
+
filename: f,
|
|
2238
|
+
path: fp,
|
|
2239
|
+
mtime: stat.mtime.getTime(),
|
|
2240
|
+
ext: ".json"
|
|
2241
|
+
};
|
|
1098
2242
|
results.push(entry);
|
|
1099
2243
|
dirResults.push(entry);
|
|
1100
2244
|
} else if (f.endsWith(".jsonl")) {
|
|
1101
2245
|
seen.add(fp);
|
|
1102
|
-
const entry = {
|
|
2246
|
+
const entry = {
|
|
2247
|
+
filename: f,
|
|
2248
|
+
path: fp,
|
|
2249
|
+
mtime: stat.mtime.getTime(),
|
|
2250
|
+
ext: ".jsonl"
|
|
2251
|
+
};
|
|
1103
2252
|
results.push(entry);
|
|
1104
2253
|
dirResults.push(entry);
|
|
1105
2254
|
}
|
|
@@ -1115,13 +2264,13 @@ function scanFiles(dirs, recursive) {
|
|
|
1115
2264
|
}
|
|
1116
2265
|
function safeReadJson(fp) {
|
|
1117
2266
|
try {
|
|
1118
|
-
return JSON.parse((0,
|
|
2267
|
+
return JSON.parse((0, import_node_fs4.readFileSync)(fp, "utf8"));
|
|
1119
2268
|
} catch {
|
|
1120
2269
|
return null;
|
|
1121
2270
|
}
|
|
1122
2271
|
}
|
|
1123
2272
|
function nameFromFile(filename) {
|
|
1124
|
-
return (0,
|
|
2273
|
+
return (0, import_node_path4.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
1125
2274
|
}
|
|
1126
2275
|
function normalizeStatus(val) {
|
|
1127
2276
|
if (typeof val !== "string") return "unknown";
|
|
@@ -1161,7 +2310,7 @@ function findTimestamp(obj) {
|
|
|
1161
2310
|
if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
|
|
1162
2311
|
if (typeof val === "string") {
|
|
1163
2312
|
const d = Date.parse(val);
|
|
1164
|
-
if (!isNaN(d)) return d;
|
|
2313
|
+
if (!Number.isNaN(d)) return d;
|
|
1165
2314
|
}
|
|
1166
2315
|
}
|
|
1167
2316
|
return 0;
|
|
@@ -1193,7 +2342,7 @@ function extractDetail(obj) {
|
|
|
1193
2342
|
}
|
|
1194
2343
|
return parts.join(" | ") || "";
|
|
1195
2344
|
}
|
|
1196
|
-
function tryLoadTrace(
|
|
2345
|
+
function tryLoadTrace(_fp, raw) {
|
|
1197
2346
|
if (typeof raw !== "object" || raw === null) return null;
|
|
1198
2347
|
const obj = raw;
|
|
1199
2348
|
if (!("nodes" in obj)) return null;
|
|
@@ -1299,11 +2448,11 @@ function processJsonFile(file) {
|
|
|
1299
2448
|
}
|
|
1300
2449
|
function processJsonlFile(file) {
|
|
1301
2450
|
try {
|
|
1302
|
-
const content = (0,
|
|
2451
|
+
const content = (0, import_node_fs4.readFileSync)(file.path, "utf8").trim();
|
|
1303
2452
|
if (!content) return [];
|
|
1304
2453
|
const lines = content.split("\n");
|
|
1305
2454
|
const lineCount = lines.length;
|
|
1306
|
-
const lastObj = JSON.parse(lines[lines.length - 1]);
|
|
2455
|
+
const lastObj = JSON.parse(lines[lines.length - 1] ?? "{}");
|
|
1307
2456
|
const name = lastObj.jobId ?? lastObj.agentId ?? lastObj.name ?? lastObj.id ?? nameFromFile(file.filename);
|
|
1308
2457
|
if (lastObj.action !== void 0 || lastObj.jobId !== void 0) {
|
|
1309
2458
|
const status2 = findStatus(lastObj);
|
|
@@ -1399,7 +2548,7 @@ function processJsonlFile(file) {
|
|
|
1399
2548
|
}
|
|
1400
2549
|
const parts = [];
|
|
1401
2550
|
if (model) {
|
|
1402
|
-
const shortModel = model.includes("/") ? model.split("/").pop() : model;
|
|
2551
|
+
const shortModel = model.includes("/") ? model.split("/").pop() ?? model : model;
|
|
1403
2552
|
parts.push(shortModel.slice(0, 20));
|
|
1404
2553
|
}
|
|
1405
2554
|
if (toolCalls.length > 0) {
|
|
@@ -1444,7 +2593,8 @@ function writeLine(lines, text) {
|
|
|
1444
2593
|
}
|
|
1445
2594
|
function flushLines(lines) {
|
|
1446
2595
|
process.stdout.write("\x1B[H");
|
|
1447
|
-
process.stdout.write(lines.join("\n")
|
|
2596
|
+
process.stdout.write(`${lines.join("\n")}
|
|
2597
|
+
`);
|
|
1448
2598
|
process.stdout.write("\x1B[J");
|
|
1449
2599
|
}
|
|
1450
2600
|
var prevFileCount = 0;
|
|
@@ -1538,7 +2688,7 @@ function render(config) {
|
|
|
1538
2688
|
const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
|
|
1539
2689
|
groups.push({
|
|
1540
2690
|
name: groupName,
|
|
1541
|
-
source: records[0]
|
|
2691
|
+
source: records[0]?.source ?? "trace",
|
|
1542
2692
|
status,
|
|
1543
2693
|
lastTs,
|
|
1544
2694
|
detail: `${records.length} agents`,
|
|
@@ -1563,15 +2713,15 @@ function render(config) {
|
|
|
1563
2713
|
if (age > 36e5 || age < 0) continue;
|
|
1564
2714
|
const idx = 11 - Math.floor(age / 3e5);
|
|
1565
2715
|
if (idx >= 0 && idx < 12) {
|
|
1566
|
-
buckets[idx]
|
|
1567
|
-
if (r.status === "error") failBuckets[idx]
|
|
2716
|
+
buckets[idx] = (buckets[idx] ?? 0) + 1;
|
|
2717
|
+
if (r.status === "error") failBuckets[idx] = (failBuckets[idx] ?? 0) + 1;
|
|
1568
2718
|
}
|
|
1569
2719
|
}
|
|
1570
2720
|
const maxBucket = Math.max(...buckets, 1);
|
|
1571
2721
|
const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
1572
2722
|
const spark = buckets.map((v, i) => {
|
|
1573
2723
|
const level = Math.round(v / maxBucket * 8);
|
|
1574
|
-
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
2724
|
+
return ((failBuckets[i] ?? 0) > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
1575
2725
|
}).join("");
|
|
1576
2726
|
let auditResult = null;
|
|
1577
2727
|
if (now - lastAuditTime > 1e4) {
|
|
@@ -1584,8 +2734,10 @@ function render(config) {
|
|
|
1584
2734
|
cachedAuditResult = auditResult;
|
|
1585
2735
|
lastAuditTime = now;
|
|
1586
2736
|
} catch (err) {
|
|
1587
|
-
process.stderr.write(
|
|
1588
|
-
`
|
|
2737
|
+
process.stderr.write(
|
|
2738
|
+
`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
|
|
2739
|
+
`
|
|
2740
|
+
);
|
|
1589
2741
|
}
|
|
1590
2742
|
}
|
|
1591
2743
|
} else {
|
|
@@ -1642,7 +2794,7 @@ function render(config) {
|
|
|
1642
2794
|
return new Date(ts).toLocaleTimeString();
|
|
1643
2795
|
}
|
|
1644
2796
|
function truncate(s, max) {
|
|
1645
|
-
return s.length > max ? s.slice(0, max - 1)
|
|
2797
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
1646
2798
|
}
|
|
1647
2799
|
const termWidth = process.stdout.columns || 120;
|
|
1648
2800
|
const detailWidth = Math.max(20, termWidth - 60);
|
|
@@ -1683,7 +2835,8 @@ function render(config) {
|
|
|
1683
2835
|
if (ar.systemd) {
|
|
1684
2836
|
const si = ar.systemd.activeState === "active" ? `${C.green}\u25CF${C.reset}` : ar.systemd.crashLooping ? `${C.yellow}\u25CF${C.reset}` : ar.systemd.failed ? `${C.red}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
|
|
1685
2837
|
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1686
|
-
if (ar.systemd.restarts > 0)
|
|
2838
|
+
if (ar.systemd.restarts > 0)
|
|
2839
|
+
sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1687
2840
|
}
|
|
1688
2841
|
let pidLabel = "";
|
|
1689
2842
|
if (ar.pidFile?.pid) {
|
|
@@ -1692,7 +2845,10 @@ function render(config) {
|
|
|
1692
2845
|
}
|
|
1693
2846
|
writeLine(L, "");
|
|
1694
2847
|
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1695
|
-
writeLine(
|
|
2848
|
+
writeLine(
|
|
2849
|
+
L,
|
|
2850
|
+
` ${healthIcon} ${healthLabel}${pidLabel}${sysdLabel} ${C.bold}Procs${C.reset} ${C.dim}${ar.osProcesses.length}${C.reset} ${ar.orphans.length > 0 ? `${C.red}Orphans ${ar.orphans.length}${C.reset}` : `${C.dim}Orphans 0${C.reset}`}`
|
|
2851
|
+
);
|
|
1696
2852
|
if (workerParts.length > 0) {
|
|
1697
2853
|
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1698
2854
|
}
|
|
@@ -1704,7 +2860,10 @@ function render(config) {
|
|
|
1704
2860
|
if (ar.orphans.length > 0) {
|
|
1705
2861
|
for (const o of ar.orphans.slice(0, 5)) {
|
|
1706
2862
|
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1707
|
-
writeLine(
|
|
2863
|
+
writeLine(
|
|
2864
|
+
L,
|
|
2865
|
+
` ${C.red}?${C.reset} ${C.dim}pid=${o.pid} cpu=${o.cpu} mem=${o.mem} up=${o.elapsed}${C.reset} ${C.dim}${cmd}${C.reset}`
|
|
2866
|
+
);
|
|
1708
2867
|
}
|
|
1709
2868
|
if (ar.orphans.length > 5) {
|
|
1710
2869
|
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
@@ -1778,7 +2937,7 @@ function render(config) {
|
|
|
1778
2937
|
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
1779
2938
|
const tg = tree[i];
|
|
1780
2939
|
const depth = getDistDepth(dt, tg.spanId);
|
|
1781
|
-
const indent =
|
|
2940
|
+
const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
|
|
1782
2941
|
const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
1783
2942
|
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1784
2943
|
const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
@@ -1799,7 +2958,7 @@ function render(config) {
|
|
|
1799
2958
|
const t = new Date(r.lastActive).toLocaleTimeString();
|
|
1800
2959
|
const agent = truncate(r.id, 26).padEnd(26);
|
|
1801
2960
|
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1802
|
-
const ageStr = age < 60 ? age
|
|
2961
|
+
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
|
|
1803
2962
|
const det = truncate(r.detail, detailWidth);
|
|
1804
2963
|
writeLine(
|
|
1805
2964
|
L,
|
|
@@ -1813,7 +2972,7 @@ function render(config) {
|
|
|
1813
2972
|
for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
|
|
1814
2973
|
}
|
|
1815
2974
|
writeLine(L, "");
|
|
1816
|
-
const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
|
|
2975
|
+
const dirLabel = config.dirs.length === 1 ? config.dirs[0] ?? "" : `${config.dirs.length} directories`;
|
|
1817
2976
|
writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
|
|
1818
2977
|
writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
1819
2978
|
flushLines(L);
|
|
@@ -1829,13 +2988,13 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
1829
2988
|
}
|
|
1830
2989
|
function startLive(argv) {
|
|
1831
2990
|
const config = parseArgs(argv);
|
|
1832
|
-
const valid = config.dirs.filter((d) => (0,
|
|
2991
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs4.existsSync)(d));
|
|
1833
2992
|
if (valid.length === 0) {
|
|
1834
2993
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1835
2994
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1836
2995
|
process.exit(1);
|
|
1837
2996
|
}
|
|
1838
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
2997
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs4.existsSync)(d));
|
|
1839
2998
|
if (invalid.length > 0) {
|
|
1840
2999
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1841
3000
|
}
|
|
@@ -1847,7 +3006,7 @@ function startLive(argv) {
|
|
|
1847
3006
|
let debounce = null;
|
|
1848
3007
|
for (const dir of config.dirs) {
|
|
1849
3008
|
try {
|
|
1850
|
-
(0,
|
|
3009
|
+
(0, import_node_fs4.watch)(dir, { recursive: config.recursive }, () => {
|
|
1851
3010
|
if (debounce) clearTimeout(debounce);
|
|
1852
3011
|
debounce = setTimeout(() => render(config), 500);
|
|
1853
3012
|
});
|
|
@@ -1864,7 +3023,7 @@ function startLive(argv) {
|
|
|
1864
3023
|
};
|
|
1865
3024
|
process.on("SIGINT", () => {
|
|
1866
3025
|
cleanup();
|
|
1867
|
-
console.log(C.dim
|
|
3026
|
+
console.log(`${C.dim}Monitor stopped.${C.reset}`);
|
|
1868
3027
|
process.exit(0);
|
|
1869
3028
|
});
|
|
1870
3029
|
process.on("SIGTERM", () => {
|
|
@@ -1873,22 +3032,169 @@ function startLive(argv) {
|
|
|
1873
3032
|
});
|
|
1874
3033
|
}
|
|
1875
3034
|
|
|
3035
|
+
// src/policy-source.ts
|
|
3036
|
+
var import_node_fs5 = require("fs");
|
|
3037
|
+
var import_node_path5 = require("path");
|
|
3038
|
+
function createPolicySource(store) {
|
|
3039
|
+
return {
|
|
3040
|
+
recentFailureRate(agentId) {
|
|
3041
|
+
const profile = store.getAgentProfile(agentId);
|
|
3042
|
+
return profile?.failureRate ?? 0;
|
|
3043
|
+
},
|
|
3044
|
+
isKnownBottleneck(nodeName) {
|
|
3045
|
+
const profilesDir = (0, import_node_path5.join)(store.baseDir, "profiles");
|
|
3046
|
+
let agentIds;
|
|
3047
|
+
try {
|
|
3048
|
+
agentIds = (0, import_node_fs5.readdirSync)(profilesDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
3049
|
+
} catch {
|
|
3050
|
+
return false;
|
|
3051
|
+
}
|
|
3052
|
+
for (const agentId of agentIds) {
|
|
3053
|
+
const profile = store.getAgentProfile(agentId);
|
|
3054
|
+
if (profile?.knownBottlenecks.includes(nodeName)) {
|
|
3055
|
+
return true;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
return false;
|
|
3059
|
+
},
|
|
3060
|
+
lastConformanceScore(agentId) {
|
|
3061
|
+
const profile = store.getAgentProfile(agentId);
|
|
3062
|
+
return profile?.lastConformanceScore ?? null;
|
|
3063
|
+
},
|
|
3064
|
+
getAgentProfile(agentId) {
|
|
3065
|
+
return store.getAgentProfile(agentId);
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// src/receipts.ts
|
|
3071
|
+
function extractTokenCost(node) {
|
|
3072
|
+
const semantic = node.metadata?.semantic;
|
|
3073
|
+
if (semantic?.tokenCost !== void 0 && semantic.tokenCost !== null) {
|
|
3074
|
+
return semantic.tokenCost;
|
|
3075
|
+
}
|
|
3076
|
+
if (node.state?.tokenCost !== void 0 && node.state.tokenCost !== null) {
|
|
3077
|
+
return node.state.tokenCost;
|
|
3078
|
+
}
|
|
3079
|
+
return null;
|
|
3080
|
+
}
|
|
3081
|
+
function extractError(node) {
|
|
3082
|
+
if (node.state?.error !== void 0 && node.state.error !== null) {
|
|
3083
|
+
return String(node.state.error);
|
|
3084
|
+
}
|
|
3085
|
+
if (node.metadata?.error !== void 0 && node.metadata.error !== null) {
|
|
3086
|
+
return String(node.metadata.error);
|
|
3087
|
+
}
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3090
|
+
function nodeDuration(node) {
|
|
3091
|
+
if (node.endTime === null) return null;
|
|
3092
|
+
return node.endTime - node.startTime;
|
|
3093
|
+
}
|
|
3094
|
+
function toReceipt(graph) {
|
|
3095
|
+
const nodes = [...graph.nodes.values()];
|
|
3096
|
+
nodes.sort((a, b) => a.startTime - b.startTime);
|
|
3097
|
+
const steps = nodes.map((node) => ({
|
|
3098
|
+
nodeId: node.id,
|
|
3099
|
+
name: node.name,
|
|
3100
|
+
type: node.type,
|
|
3101
|
+
status: node.status,
|
|
3102
|
+
durationMs: nodeDuration(node),
|
|
3103
|
+
tokenCost: extractTokenCost(node),
|
|
3104
|
+
error: extractError(node)
|
|
3105
|
+
}));
|
|
3106
|
+
let succeeded = 0;
|
|
3107
|
+
let failed = 0;
|
|
3108
|
+
const skipped = 0;
|
|
3109
|
+
for (const node of nodes) {
|
|
3110
|
+
if (node.status === "completed") {
|
|
3111
|
+
succeeded++;
|
|
3112
|
+
} else if (node.status === "failed" || node.status === "hung" || node.status === "timeout") {
|
|
3113
|
+
failed++;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
const attempted = nodes.length;
|
|
3117
|
+
let totalTokenCost = null;
|
|
3118
|
+
for (const step of steps) {
|
|
3119
|
+
if (step.tokenCost !== null) {
|
|
3120
|
+
totalTokenCost = (totalTokenCost ?? 0) + step.tokenCost;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
const totalDurationMs = graph.endTime !== null ? graph.endTime - graph.startTime : null;
|
|
3124
|
+
return {
|
|
3125
|
+
runId: graph.id,
|
|
3126
|
+
agentId: graph.agentId,
|
|
3127
|
+
status: graph.status,
|
|
3128
|
+
startTime: graph.startTime,
|
|
3129
|
+
endTime: graph.endTime,
|
|
3130
|
+
totalDurationMs,
|
|
3131
|
+
totalTokenCost,
|
|
3132
|
+
steps,
|
|
3133
|
+
summary: { attempted, succeeded, failed, skipped }
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
function formatReceipt(receipt) {
|
|
3137
|
+
const lines = [];
|
|
3138
|
+
lines.push("=== Run Receipt ===");
|
|
3139
|
+
lines.push(`Run: ${receipt.runId}`);
|
|
3140
|
+
lines.push(`Agent: ${receipt.agentId}`);
|
|
3141
|
+
lines.push(`Status: ${receipt.status}`);
|
|
3142
|
+
lines.push(
|
|
3143
|
+
`Duration: ${receipt.totalDurationMs !== null ? `${receipt.totalDurationMs}ms` : "\u2014"}`
|
|
3144
|
+
);
|
|
3145
|
+
lines.push("");
|
|
3146
|
+
const s = receipt.summary;
|
|
3147
|
+
lines.push(
|
|
3148
|
+
`Summary: ${s.attempted} attempted, ${s.succeeded} succeeded, ${s.failed} failed, ${s.skipped} skipped`
|
|
3149
|
+
);
|
|
3150
|
+
lines.push("");
|
|
3151
|
+
const nameWidth = Math.max(4, ...receipt.steps.map((st) => st.name.length));
|
|
3152
|
+
const typeWidth = Math.max(4, ...receipt.steps.map((st) => st.type.length));
|
|
3153
|
+
const pad = (str, width) => str.padEnd(width);
|
|
3154
|
+
const padNum = (str, width) => str.padStart(width);
|
|
3155
|
+
const idxWidth = Math.max(2, String(receipt.steps.length).length);
|
|
3156
|
+
const statusWidth = 9;
|
|
3157
|
+
const durWidth = 10;
|
|
3158
|
+
const tokWidth = 8;
|
|
3159
|
+
lines.push(
|
|
3160
|
+
` ${padNum("#", idxWidth)} | ${pad("Step", nameWidth)} | ${pad("Type", typeWidth)} | ${pad("Status", statusWidth)} | ${padNum("Duration", durWidth)} | ${padNum("Tokens", tokWidth)}`
|
|
3161
|
+
);
|
|
3162
|
+
lines.push(
|
|
3163
|
+
`${"-".repeat(idxWidth + 1)}|${"-".repeat(nameWidth + 2)}|${"-".repeat(typeWidth + 2)}|${"-".repeat(statusWidth + 2)}|${"-".repeat(durWidth + 2)}|${"-".repeat(tokWidth + 2)}`
|
|
3164
|
+
);
|
|
3165
|
+
for (let i = 0; i < receipt.steps.length; i++) {
|
|
3166
|
+
const step = receipt.steps[i];
|
|
3167
|
+
const durStr = step.durationMs !== null ? `${step.durationMs}ms` : "\u2014";
|
|
3168
|
+
const tokStr = step.tokenCost !== null ? String(step.tokenCost) : "\u2014";
|
|
3169
|
+
lines.push(
|
|
3170
|
+
` ${padNum(String(i + 1), idxWidth)} | ${pad(step.name, nameWidth)} | ${pad(step.type, typeWidth)} | ${pad(step.status, statusWidth)} | ${padNum(durStr, durWidth)} | ${padNum(tokStr, tokWidth)}`
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3173
|
+
lines.push("");
|
|
3174
|
+
if (receipt.totalTokenCost !== null) {
|
|
3175
|
+
lines.push(`Total token cost: ${receipt.totalTokenCost}`);
|
|
3176
|
+
} else {
|
|
3177
|
+
lines.push("Total token cost: no cost data");
|
|
3178
|
+
}
|
|
3179
|
+
return lines.join("\n");
|
|
3180
|
+
}
|
|
3181
|
+
|
|
1876
3182
|
// src/runner.ts
|
|
1877
3183
|
var import_node_child_process2 = require("child_process");
|
|
1878
|
-
var
|
|
1879
|
-
var
|
|
3184
|
+
var import_node_fs6 = require("fs");
|
|
3185
|
+
var import_node_path6 = require("path");
|
|
1880
3186
|
function globToRegex(pattern) {
|
|
1881
3187
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1882
3188
|
return new RegExp(`^${escaped}$`);
|
|
1883
3189
|
}
|
|
1884
3190
|
function snapshotDir(dir, patterns) {
|
|
1885
3191
|
const result = /* @__PURE__ */ new Map();
|
|
1886
|
-
if (!(0,
|
|
1887
|
-
for (const entry of (0,
|
|
3192
|
+
if (!(0, import_node_fs6.existsSync)(dir)) return result;
|
|
3193
|
+
for (const entry of (0, import_node_fs6.readdirSync)(dir)) {
|
|
1888
3194
|
if (!patterns.some((re) => re.test(entry))) continue;
|
|
1889
|
-
const full = (0,
|
|
3195
|
+
const full = (0, import_node_path6.join)(dir, entry);
|
|
1890
3196
|
try {
|
|
1891
|
-
const stat = (0,
|
|
3197
|
+
const stat = (0, import_node_fs6.statSync)(full);
|
|
1892
3198
|
if (stat.isFile()) {
|
|
1893
3199
|
result.set(full, stat.mtimeMs);
|
|
1894
3200
|
}
|
|
@@ -1898,11 +3204,11 @@ function snapshotDir(dir, patterns) {
|
|
|
1898
3204
|
return result;
|
|
1899
3205
|
}
|
|
1900
3206
|
function agentIdFromFilename(filePath) {
|
|
1901
|
-
const base = (0,
|
|
3207
|
+
const base = (0, import_node_path6.basename)(filePath, ".json");
|
|
1902
3208
|
const cleaned = base.replace(/-state$/, "");
|
|
1903
3209
|
return `alfred-${cleaned}`;
|
|
1904
3210
|
}
|
|
1905
|
-
function deriveAgentId(
|
|
3211
|
+
function deriveAgentId(_command) {
|
|
1906
3212
|
return "orchestrator";
|
|
1907
3213
|
}
|
|
1908
3214
|
function fileTimestamp() {
|
|
@@ -1920,7 +3226,7 @@ async function runTraced(config) {
|
|
|
1920
3226
|
if (command.length === 0) {
|
|
1921
3227
|
throw new Error("runTraced: command must not be empty");
|
|
1922
3228
|
}
|
|
1923
|
-
const resolvedTracesDir = (0,
|
|
3229
|
+
const resolvedTracesDir = (0, import_node_path6.resolve)(tracesDir);
|
|
1924
3230
|
const patterns = watchPatterns.map(globToRegex);
|
|
1925
3231
|
const orchestrator = createGraphBuilder({ agentId, trigger });
|
|
1926
3232
|
const { traceId, spanId } = orchestrator.traceContext;
|
|
@@ -2003,23 +3309,27 @@ async function runTraced(config) {
|
|
|
2003
3309
|
childBuilder.endNode(childRootId);
|
|
2004
3310
|
allGraphs.push(childBuilder.build());
|
|
2005
3311
|
}
|
|
2006
|
-
if (!(0,
|
|
2007
|
-
(0,
|
|
3312
|
+
if (!(0, import_node_fs6.existsSync)(resolvedTracesDir)) {
|
|
3313
|
+
(0, import_node_fs6.mkdirSync)(resolvedTracesDir, { recursive: true });
|
|
2008
3314
|
}
|
|
2009
3315
|
const ts = fileTimestamp();
|
|
2010
3316
|
const tracePaths = [];
|
|
2011
3317
|
for (const graph of allGraphs) {
|
|
2012
3318
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
2013
|
-
const outPath = (0,
|
|
2014
|
-
const resolvedOut = (0,
|
|
2015
|
-
if (!resolvedOut.startsWith(resolvedTracesDir
|
|
2016
|
-
throw new Error(
|
|
3319
|
+
const outPath = (0, import_node_path6.join)(resolvedTracesDir, filename);
|
|
3320
|
+
const resolvedOut = (0, import_node_path6.resolve)(outPath);
|
|
3321
|
+
if (!resolvedOut.startsWith(`${resolvedTracesDir}/`) && resolvedOut !== resolvedTracesDir) {
|
|
3322
|
+
throw new Error(
|
|
3323
|
+
`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`
|
|
3324
|
+
);
|
|
2017
3325
|
}
|
|
2018
|
-
(0,
|
|
3326
|
+
(0, import_node_fs6.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
2019
3327
|
tracePaths.push(outPath);
|
|
2020
3328
|
}
|
|
2021
3329
|
if (tracePaths.length > 0) {
|
|
2022
|
-
console.log(
|
|
3330
|
+
console.log(
|
|
3331
|
+
`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`
|
|
3332
|
+
);
|
|
2023
3333
|
}
|
|
2024
3334
|
return {
|
|
2025
3335
|
exitCode,
|
|
@@ -2031,9 +3341,169 @@ async function runTraced(config) {
|
|
|
2031
3341
|
};
|
|
2032
3342
|
}
|
|
2033
3343
|
|
|
3344
|
+
// src/soma-event-writer.ts
|
|
3345
|
+
var import_node_fs7 = require("fs");
|
|
3346
|
+
var import_node_path7 = require("path");
|
|
3347
|
+
function compactIso(epochMs) {
|
|
3348
|
+
return new Date(epochMs).toISOString().slice(0, 19).replace(/:/g, "");
|
|
3349
|
+
}
|
|
3350
|
+
function isoDate(epochMs) {
|
|
3351
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
3352
|
+
}
|
|
3353
|
+
function renderFrontmatter(fields) {
|
|
3354
|
+
const lines = ["---"];
|
|
3355
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3356
|
+
if (value === void 0) continue;
|
|
3357
|
+
if (Array.isArray(value)) {
|
|
3358
|
+
lines.push(`${key}: [${value.map((v) => `'${v}'`).join(", ")}]`);
|
|
3359
|
+
} else if (typeof value === "string") {
|
|
3360
|
+
lines.push(`${key}: '${value}'`);
|
|
3361
|
+
} else {
|
|
3362
|
+
lines.push(`${key}: ${String(value)}`);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
lines.push("---");
|
|
3366
|
+
return lines.join("\n");
|
|
3367
|
+
}
|
|
3368
|
+
function formatDuration(ms) {
|
|
3369
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
3370
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3371
|
+
return `${(ms / 6e4).toFixed(1)}min`;
|
|
3372
|
+
}
|
|
3373
|
+
function executionEventToMarkdown(event) {
|
|
3374
|
+
const isCompleted = event.eventType === "execution.completed";
|
|
3375
|
+
const subtype = isCompleted ? "completed" : "failed";
|
|
3376
|
+
const tags = ["agentflow/execution", `agent/${event.agentId}`, `status/${subtype}`];
|
|
3377
|
+
if (event.processContext?.isAnomaly) {
|
|
3378
|
+
tags.push("agentflow/anomaly");
|
|
3379
|
+
}
|
|
3380
|
+
const frontmatter = {
|
|
3381
|
+
type: "execution",
|
|
3382
|
+
subtype,
|
|
3383
|
+
name: `Execution: ${event.agentId} \u2014 ${subtype}`,
|
|
3384
|
+
source: "agentflow",
|
|
3385
|
+
created: isoDate(event.timestamp),
|
|
3386
|
+
alfred_tags: tags,
|
|
3387
|
+
agentflow_graph_id: event.graphId,
|
|
3388
|
+
duration_ms: event.duration,
|
|
3389
|
+
node_count: event.nodeCount
|
|
3390
|
+
};
|
|
3391
|
+
if (event.processContext) {
|
|
3392
|
+
frontmatter.conformance_score = event.processContext.conformanceScore;
|
|
3393
|
+
frontmatter.is_anomaly = event.processContext.isAnomaly;
|
|
3394
|
+
}
|
|
3395
|
+
const body = [];
|
|
3396
|
+
body.push(`# Execution: ${event.agentId} \u2014 ${subtype}
|
|
3397
|
+
`);
|
|
3398
|
+
body.push(`**Duration:** ${formatDuration(event.duration)} `);
|
|
3399
|
+
body.push(`**Nodes:** ${event.nodeCount} `);
|
|
3400
|
+
body.push(`**Status:** ${event.status}
|
|
3401
|
+
`);
|
|
3402
|
+
if (event.pathSignature) {
|
|
3403
|
+
body.push(`## Path
|
|
3404
|
+
`);
|
|
3405
|
+
body.push(`\`${event.pathSignature}\`
|
|
3406
|
+
`);
|
|
3407
|
+
}
|
|
3408
|
+
if (!isCompleted && event.failurePoint) {
|
|
3409
|
+
const fp = event.failurePoint;
|
|
3410
|
+
body.push(`## Failure Point
|
|
3411
|
+
`);
|
|
3412
|
+
body.push(`**Node:** ${fp.nodeType}:${fp.nodeName} (\`${fp.nodeId}\`) `);
|
|
3413
|
+
if (fp.error) {
|
|
3414
|
+
body.push(`**Error:** ${fp.error}
|
|
3415
|
+
`);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
if (event.processContext) {
|
|
3419
|
+
body.push(`## Process Context
|
|
3420
|
+
`);
|
|
3421
|
+
body.push(`**Conformance:** ${(event.processContext.conformanceScore * 100).toFixed(0)}% `);
|
|
3422
|
+
body.push(`**Anomaly:** ${event.processContext.isAnomaly ? "yes" : "no"}
|
|
3423
|
+
`);
|
|
3424
|
+
}
|
|
3425
|
+
body.push(`## Related
|
|
3426
|
+
`);
|
|
3427
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
3428
|
+
return `${renderFrontmatter(frontmatter)}
|
|
3429
|
+
|
|
3430
|
+
${body.join("\n")}`;
|
|
3431
|
+
}
|
|
3432
|
+
function patternEventToMarkdown(event) {
|
|
3433
|
+
const { pattern } = event;
|
|
3434
|
+
const tags = ["agentflow/pattern", `agent/${event.agentId}`];
|
|
3435
|
+
const frontmatter = {
|
|
3436
|
+
type: "synthesis",
|
|
3437
|
+
subtype: "pattern-discovery",
|
|
3438
|
+
name: `Pattern: ${event.agentId} \u2014 ${pattern.variantCount} variants across ${pattern.totalGraphs} runs`,
|
|
3439
|
+
source: "agentflow",
|
|
3440
|
+
created: isoDate(event.timestamp),
|
|
3441
|
+
alfred_tags: tags,
|
|
3442
|
+
variant_count: pattern.variantCount,
|
|
3443
|
+
total_graphs: pattern.totalGraphs
|
|
3444
|
+
};
|
|
3445
|
+
const body = [];
|
|
3446
|
+
body.push(`# Pattern: ${event.agentId}
|
|
3447
|
+
`);
|
|
3448
|
+
body.push(`**Variants:** ${pattern.variantCount} `);
|
|
3449
|
+
body.push(`**Total Runs:** ${pattern.totalGraphs}
|
|
3450
|
+
`);
|
|
3451
|
+
if (pattern.topVariants.length > 0) {
|
|
3452
|
+
body.push(`## Top Variants
|
|
3453
|
+
`);
|
|
3454
|
+
body.push(`| Path | Count | % |`);
|
|
3455
|
+
body.push(`|------|-------|---|`);
|
|
3456
|
+
for (const v of pattern.topVariants) {
|
|
3457
|
+
const sig = v.pathSignature.length > 60 ? `${v.pathSignature.slice(0, 57)}...` : v.pathSignature;
|
|
3458
|
+
body.push(`| \`${sig}\` | ${v.count} | ${v.percentage.toFixed(1)}% |`);
|
|
3459
|
+
}
|
|
3460
|
+
body.push("");
|
|
3461
|
+
}
|
|
3462
|
+
if (pattern.topBottlenecks.length > 0) {
|
|
3463
|
+
body.push(`## Top Bottlenecks
|
|
3464
|
+
`);
|
|
3465
|
+
body.push(`| Node | Type | p95 |`);
|
|
3466
|
+
body.push(`|------|------|-----|`);
|
|
3467
|
+
for (const b of pattern.topBottlenecks) {
|
|
3468
|
+
body.push(`| ${b.nodeName} | ${b.nodeType} | ${formatDuration(b.p95)} |`);
|
|
3469
|
+
}
|
|
3470
|
+
body.push("");
|
|
3471
|
+
}
|
|
3472
|
+
body.push(`## Related
|
|
3473
|
+
`);
|
|
3474
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
3475
|
+
return `${renderFrontmatter(frontmatter)}
|
|
3476
|
+
|
|
3477
|
+
${body.join("\n")}`;
|
|
3478
|
+
}
|
|
3479
|
+
function createSomaEventWriter(config) {
|
|
3480
|
+
const { inboxDir } = config;
|
|
3481
|
+
function ensureDir() {
|
|
3482
|
+
(0, import_node_fs7.mkdirSync)(inboxDir, { recursive: true });
|
|
3483
|
+
}
|
|
3484
|
+
function eventFileName(event) {
|
|
3485
|
+
const agentId = event.agentId;
|
|
3486
|
+
const ts = compactIso(event.timestamp);
|
|
3487
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
3488
|
+
return `synthesis-${agentId}-${ts}.md`;
|
|
3489
|
+
}
|
|
3490
|
+
return `execution-${agentId}-${ts}.md`;
|
|
3491
|
+
}
|
|
3492
|
+
return {
|
|
3493
|
+
async write(_graph) {
|
|
3494
|
+
},
|
|
3495
|
+
async writeEvent(event) {
|
|
3496
|
+
ensureDir();
|
|
3497
|
+
const markdown = event.eventType === "pattern.discovered" || event.eventType === "pattern.updated" ? patternEventToMarkdown(event) : executionEventToMarkdown(event);
|
|
3498
|
+
const fileName = eventFileName(event);
|
|
3499
|
+
(0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(inboxDir, fileName), markdown, "utf-8");
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
|
|
2034
3504
|
// src/trace-store.ts
|
|
2035
3505
|
var import_promises = require("fs/promises");
|
|
2036
|
-
var
|
|
3506
|
+
var import_node_path8 = require("path");
|
|
2037
3507
|
function createTraceStore(dir) {
|
|
2038
3508
|
async function ensureDir() {
|
|
2039
3509
|
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
@@ -2050,7 +3520,7 @@ function createTraceStore(dir) {
|
|
|
2050
3520
|
for (const file of files) {
|
|
2051
3521
|
if (!file.endsWith(".json")) continue;
|
|
2052
3522
|
try {
|
|
2053
|
-
const content = await (0, import_promises.readFile)((0,
|
|
3523
|
+
const content = await (0, import_promises.readFile)((0, import_node_path8.join)(dir, file), "utf-8");
|
|
2054
3524
|
const graph = loadGraph(content);
|
|
2055
3525
|
graphs.push(graph);
|
|
2056
3526
|
} catch {
|
|
@@ -2062,10 +3532,10 @@ function createTraceStore(dir) {
|
|
|
2062
3532
|
async save(graph) {
|
|
2063
3533
|
await ensureDir();
|
|
2064
3534
|
const json = graphToJson(graph);
|
|
2065
|
-
const filePath = (0,
|
|
2066
|
-
const resolvedBase = (0,
|
|
2067
|
-
const resolvedPath = (0,
|
|
2068
|
-
if (!resolvedPath.startsWith(resolvedBase
|
|
3535
|
+
const filePath = (0, import_node_path8.join)(dir, `${graph.id}.json`);
|
|
3536
|
+
const resolvedBase = (0, import_node_path8.resolve)(dir);
|
|
3537
|
+
const resolvedPath = (0, import_node_path8.resolve)(filePath);
|
|
3538
|
+
if (!resolvedPath.startsWith(`${resolvedBase}/`) && resolvedPath !== resolvedBase) {
|
|
2069
3539
|
throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
|
|
2070
3540
|
}
|
|
2071
3541
|
await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
@@ -2073,7 +3543,7 @@ function createTraceStore(dir) {
|
|
|
2073
3543
|
},
|
|
2074
3544
|
async get(graphId) {
|
|
2075
3545
|
await ensureDir();
|
|
2076
|
-
const filePath = (0,
|
|
3546
|
+
const filePath = (0, import_node_path8.join)(dir, `${graphId}.json`);
|
|
2077
3547
|
try {
|
|
2078
3548
|
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
2079
3549
|
return loadGraph(content);
|
|
@@ -2143,15 +3613,15 @@ var STATUS_ICONS = {
|
|
|
2143
3613
|
hung: "\u231B",
|
|
2144
3614
|
timeout: "\u231B"
|
|
2145
3615
|
};
|
|
2146
|
-
function
|
|
3616
|
+
function formatDuration2(ms) {
|
|
2147
3617
|
if (ms < 1e3) return `${ms}ms`;
|
|
2148
3618
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
2149
3619
|
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
2150
3620
|
return `${(ms / 36e5).toFixed(1)}h`;
|
|
2151
3621
|
}
|
|
2152
|
-
function
|
|
3622
|
+
function nodeDuration2(node, graphEndTime) {
|
|
2153
3623
|
const end = node.endTime ?? graphEndTime;
|
|
2154
|
-
return
|
|
3624
|
+
return formatDuration2(end - node.startTime);
|
|
2155
3625
|
}
|
|
2156
3626
|
function getGenAiInfo(node) {
|
|
2157
3627
|
const parts = [];
|
|
@@ -2183,7 +3653,7 @@ function toAsciiTree(graph) {
|
|
|
2183
3653
|
const node = graph.nodes.get(nodeId);
|
|
2184
3654
|
if (!node) return;
|
|
2185
3655
|
const icon = STATUS_ICONS[node.status];
|
|
2186
|
-
const duration =
|
|
3656
|
+
const duration = nodeDuration2(node, endTime);
|
|
2187
3657
|
const genAi = getGenAiInfo(node);
|
|
2188
3658
|
const violation = hasViolation(node, graph) ? " \u26A0" : "";
|
|
2189
3659
|
const errorInfo = node.status === "failed" && node.metadata.error ? ` \u2014 ${node.metadata.error}` : "";
|
|
@@ -2195,7 +3665,8 @@ function toAsciiTree(graph) {
|
|
|
2195
3665
|
const children = getChildren(graph, nodeId);
|
|
2196
3666
|
const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
|
|
2197
3667
|
for (let i = 0; i < children.length; i++) {
|
|
2198
|
-
|
|
3668
|
+
const childId = children[i]?.id;
|
|
3669
|
+
if (childId) renderNode(childId, childPrefix, i === children.length - 1, false);
|
|
2199
3670
|
}
|
|
2200
3671
|
}
|
|
2201
3672
|
renderNode(graph.rootNodeId, "", true, true);
|
|
@@ -2214,7 +3685,7 @@ function toTimeline(graph) {
|
|
|
2214
3685
|
const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
|
|
2215
3686
|
for (let i = 0; i <= tickCount; i++) {
|
|
2216
3687
|
const t = totalDuration * i / tickCount;
|
|
2217
|
-
scaleLabels.push(
|
|
3688
|
+
scaleLabels.push(formatDuration2(t));
|
|
2218
3689
|
}
|
|
2219
3690
|
let header = "";
|
|
2220
3691
|
for (let i = 0; i < scaleLabels.length; i++) {
|
|
@@ -2258,7 +3729,7 @@ function toTimeline(graph) {
|
|
|
2258
3729
|
}
|
|
2259
3730
|
}
|
|
2260
3731
|
const icon = STATUS_ICONS[node.status];
|
|
2261
|
-
const duration =
|
|
3732
|
+
const duration = nodeDuration2(node, graphEnd);
|
|
2262
3733
|
const violation = hasViolation(node, graph) ? " \u26A0" : "";
|
|
2263
3734
|
lines.push(`${bar} ${icon} ${node.name} (${duration})${violation}`);
|
|
2264
3735
|
}
|
|
@@ -2266,9 +3737,9 @@ function toTimeline(graph) {
|
|
|
2266
3737
|
}
|
|
2267
3738
|
|
|
2268
3739
|
// src/watch.ts
|
|
2269
|
-
var
|
|
3740
|
+
var import_node_fs9 = require("fs");
|
|
2270
3741
|
var import_node_os = require("os");
|
|
2271
|
-
var
|
|
3742
|
+
var import_node_path9 = require("path");
|
|
2272
3743
|
|
|
2273
3744
|
// src/watch-alerts.ts
|
|
2274
3745
|
var import_node_child_process3 = require("child_process");
|
|
@@ -2389,15 +3860,15 @@ function sendCommand(payload, cmd) {
|
|
|
2389
3860
|
}
|
|
2390
3861
|
|
|
2391
3862
|
// src/watch-state.ts
|
|
2392
|
-
var
|
|
3863
|
+
var import_node_fs8 = require("fs");
|
|
2393
3864
|
function parseDuration(input) {
|
|
2394
3865
|
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
|
|
2395
3866
|
if (!match) {
|
|
2396
3867
|
const n = parseInt(input, 10);
|
|
2397
|
-
return isNaN(n) ? 0 : n * 1e3;
|
|
3868
|
+
return Number.isNaN(n) ? 0 : n * 1e3;
|
|
2398
3869
|
}
|
|
2399
|
-
const value = parseFloat(match[1]);
|
|
2400
|
-
switch (match[2]
|
|
3870
|
+
const value = parseFloat(match[1] ?? "0");
|
|
3871
|
+
switch (match[2]?.toLowerCase()) {
|
|
2401
3872
|
case "s":
|
|
2402
3873
|
return value * 1e3;
|
|
2403
3874
|
case "m":
|
|
@@ -2414,9 +3885,9 @@ function emptyState() {
|
|
|
2414
3885
|
return { version: 1, agents: {}, lastPollTime: 0 };
|
|
2415
3886
|
}
|
|
2416
3887
|
function loadWatchState(filePath) {
|
|
2417
|
-
if (!(0,
|
|
3888
|
+
if (!(0, import_node_fs8.existsSync)(filePath)) return emptyState();
|
|
2418
3889
|
try {
|
|
2419
|
-
const raw = JSON.parse((0,
|
|
3890
|
+
const raw = JSON.parse((0, import_node_fs8.readFileSync)(filePath, "utf8"));
|
|
2420
3891
|
if (raw.version !== 1 || typeof raw.agents !== "object") return emptyState();
|
|
2421
3892
|
return raw;
|
|
2422
3893
|
} catch {
|
|
@@ -2424,13 +3895,13 @@ function loadWatchState(filePath) {
|
|
|
2424
3895
|
}
|
|
2425
3896
|
}
|
|
2426
3897
|
function saveWatchState(filePath, state) {
|
|
2427
|
-
const tmp = filePath
|
|
3898
|
+
const tmp = `${filePath}.tmp`;
|
|
2428
3899
|
try {
|
|
2429
|
-
(0,
|
|
2430
|
-
(0,
|
|
3900
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
3901
|
+
(0, import_node_fs8.renameSync)(tmp, filePath);
|
|
2431
3902
|
} catch {
|
|
2432
3903
|
try {
|
|
2433
|
-
(0,
|
|
3904
|
+
(0, import_node_fs8.writeFileSync)(filePath, JSON.stringify(state, null, 2), "utf8");
|
|
2434
3905
|
} catch {
|
|
2435
3906
|
}
|
|
2436
3907
|
}
|
|
@@ -2440,12 +3911,12 @@ function estimateInterval(history) {
|
|
|
2440
3911
|
const sorted = [...history].sort((a, b) => a - b);
|
|
2441
3912
|
const deltas = [];
|
|
2442
3913
|
for (let i = 1; i < sorted.length; i++) {
|
|
2443
|
-
const d = sorted[i] - sorted[i - 1];
|
|
3914
|
+
const d = (sorted[i] ?? 0) - (sorted[i - 1] ?? 0);
|
|
2444
3915
|
if (d > 0) deltas.push(d);
|
|
2445
3916
|
}
|
|
2446
3917
|
if (deltas.length === 0) return 0;
|
|
2447
3918
|
deltas.sort((a, b) => a - b);
|
|
2448
|
-
return deltas[Math.floor(deltas.length / 2)];
|
|
3919
|
+
return deltas[Math.floor(deltas.length / 2)] ?? 0;
|
|
2449
3920
|
}
|
|
2450
3921
|
function detectTransitions(previous, currentRecords, config, now) {
|
|
2451
3922
|
const alerts = [];
|
|
@@ -2602,7 +4073,7 @@ function parseWatchArgs(argv) {
|
|
|
2602
4073
|
if (args[0] === "watch") args.shift();
|
|
2603
4074
|
let i = 0;
|
|
2604
4075
|
while (i < args.length) {
|
|
2605
|
-
const arg = args[i];
|
|
4076
|
+
const arg = args[i] ?? "";
|
|
2606
4077
|
if (arg === "--help" || arg === "-h") {
|
|
2607
4078
|
printWatchUsage();
|
|
2608
4079
|
process.exit(0);
|
|
@@ -2625,8 +4096,8 @@ function parseWatchArgs(argv) {
|
|
|
2625
4096
|
i++;
|
|
2626
4097
|
const val = args[i] ?? "";
|
|
2627
4098
|
if (val === "telegram") {
|
|
2628
|
-
const botToken = process.env
|
|
2629
|
-
const chatId = process.env
|
|
4099
|
+
const botToken = process.env.AGENTFLOW_TELEGRAM_BOT_TOKEN ?? "";
|
|
4100
|
+
const chatId = process.env.AGENTFLOW_TELEGRAM_CHAT_ID ?? "";
|
|
2630
4101
|
if (botToken && chatId) {
|
|
2631
4102
|
notifyChannels.push({ type: "telegram", botToken, chatId });
|
|
2632
4103
|
} else {
|
|
@@ -2643,7 +4114,7 @@ function parseWatchArgs(argv) {
|
|
|
2643
4114
|
} else if (arg === "--poll") {
|
|
2644
4115
|
i++;
|
|
2645
4116
|
const v = parseInt(args[i] ?? "", 10);
|
|
2646
|
-
if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
4117
|
+
if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2647
4118
|
i++;
|
|
2648
4119
|
} else if (arg === "--cooldown") {
|
|
2649
4120
|
i++;
|
|
@@ -2658,20 +4129,20 @@ function parseWatchArgs(argv) {
|
|
|
2658
4129
|
recursive = true;
|
|
2659
4130
|
i++;
|
|
2660
4131
|
} else if (!arg.startsWith("-")) {
|
|
2661
|
-
dirs.push((0,
|
|
4132
|
+
dirs.push((0, import_node_path9.resolve)(arg));
|
|
2662
4133
|
i++;
|
|
2663
4134
|
} else {
|
|
2664
4135
|
i++;
|
|
2665
4136
|
}
|
|
2666
4137
|
}
|
|
2667
|
-
if (dirs.length === 0) dirs.push((0,
|
|
4138
|
+
if (dirs.length === 0) dirs.push((0, import_node_path9.resolve)("."));
|
|
2668
4139
|
if (alertConditions.length === 0) {
|
|
2669
4140
|
alertConditions.push({ type: "error" });
|
|
2670
4141
|
alertConditions.push({ type: "recovery" });
|
|
2671
4142
|
}
|
|
2672
4143
|
notifyChannels.unshift({ type: "stdout" });
|
|
2673
4144
|
if (!stateFilePath) {
|
|
2674
|
-
stateFilePath = (0,
|
|
4145
|
+
stateFilePath = (0, import_node_path9.join)(dirs[0] ?? ".", ".agentflow-watch-state.json");
|
|
2675
4146
|
}
|
|
2676
4147
|
return {
|
|
2677
4148
|
dirs,
|
|
@@ -2679,7 +4150,7 @@ function parseWatchArgs(argv) {
|
|
|
2679
4150
|
pollIntervalMs,
|
|
2680
4151
|
alertConditions,
|
|
2681
4152
|
notifyChannels,
|
|
2682
|
-
stateFilePath: (0,
|
|
4153
|
+
stateFilePath: (0, import_node_path9.resolve)(stateFilePath),
|
|
2683
4154
|
cooldownMs
|
|
2684
4155
|
};
|
|
2685
4156
|
}
|
|
@@ -2733,12 +4204,12 @@ Examples:
|
|
|
2733
4204
|
}
|
|
2734
4205
|
function startWatch(argv) {
|
|
2735
4206
|
const config = parseWatchArgs(argv);
|
|
2736
|
-
const valid = config.dirs.filter((d) => (0,
|
|
4207
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs9.existsSync)(d));
|
|
2737
4208
|
if (valid.length === 0) {
|
|
2738
4209
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
2739
4210
|
process.exit(1);
|
|
2740
4211
|
}
|
|
2741
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
4212
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs9.existsSync)(d));
|
|
2742
4213
|
if (invalid.length > 0) {
|
|
2743
4214
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
2744
4215
|
}
|
|
@@ -2759,7 +4230,7 @@ agentflow watch started`);
|
|
|
2759
4230
|
console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
|
|
2760
4231
|
console.log(` Alert on: ${condLabels.join(", ")}`);
|
|
2761
4232
|
console.log(
|
|
2762
|
-
` Notify: stdout${channelLabels.length > 0 ?
|
|
4233
|
+
` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
|
|
2763
4234
|
);
|
|
2764
4235
|
console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
|
|
2765
4236
|
console.log(` State: ${config.stateFilePath}`);
|
|
@@ -2818,12 +4289,30 @@ agentflow watch started`);
|
|
|
2818
4289
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2819
4290
|
0 && (module.exports = {
|
|
2820
4291
|
auditProcesses,
|
|
4292
|
+
buildAgentSummaryPrompt,
|
|
4293
|
+
buildAnomalyExplanationPrompt,
|
|
4294
|
+
buildFailureAnalysisPrompt,
|
|
4295
|
+
buildFixSuggestionPrompt,
|
|
4296
|
+
checkConformance,
|
|
2821
4297
|
checkGuards,
|
|
4298
|
+
createEventEmitter,
|
|
4299
|
+
createExecutionEvent,
|
|
2822
4300
|
createGraphBuilder,
|
|
4301
|
+
createInsightEngine,
|
|
4302
|
+
createJsonEventWriter,
|
|
4303
|
+
createKnowledgeStore,
|
|
4304
|
+
createPatternEvent,
|
|
4305
|
+
createPolicySource,
|
|
4306
|
+
createSomaEventWriter,
|
|
2823
4307
|
createTraceStore,
|
|
4308
|
+
discoverAllProcessConfigs,
|
|
4309
|
+
discoverProcess,
|
|
2824
4310
|
discoverProcessConfig,
|
|
4311
|
+
findVariants,
|
|
2825
4312
|
findWaitingOn,
|
|
2826
4313
|
formatAuditReport,
|
|
4314
|
+
formatReceipt,
|
|
4315
|
+
getBottlenecks,
|
|
2827
4316
|
getChildren,
|
|
2828
4317
|
getCriticalPath,
|
|
2829
4318
|
getDepth,
|
|
@@ -2832,6 +4321,7 @@ agentflow watch started`);
|
|
|
2832
4321
|
getHungNodes,
|
|
2833
4322
|
getNode,
|
|
2834
4323
|
getParent,
|
|
4324
|
+
getPathSignature,
|
|
2835
4325
|
getStats,
|
|
2836
4326
|
getSubtree,
|
|
2837
4327
|
getTraceTree,
|
|
@@ -2843,6 +4333,7 @@ agentflow watch started`);
|
|
|
2843
4333
|
startWatch,
|
|
2844
4334
|
stitchTrace,
|
|
2845
4335
|
toAsciiTree,
|
|
4336
|
+
toReceipt,
|
|
2846
4337
|
toTimeline,
|
|
2847
4338
|
withGuards
|
|
2848
4339
|
});
|