agentflow-core 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5PRHVYYD.js → chunk-6X5HU5LB.js} +123 -52
- package/dist/cli.cjs +150 -75
- package/dist/cli.js +7 -3
- package/dist/index.cjs +1405 -119
- package/dist/index.d.cts +710 -37
- package/dist/index.d.ts +710 -37
- package/dist/index.js +1207 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,12 +21,28 @@ 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
|
+
discoverProcess: () => discoverProcess,
|
|
27
41
|
discoverProcessConfig: () => discoverProcessConfig,
|
|
42
|
+
findVariants: () => findVariants,
|
|
28
43
|
findWaitingOn: () => findWaitingOn,
|
|
29
44
|
formatAuditReport: () => formatAuditReport,
|
|
45
|
+
getBottlenecks: () => getBottlenecks,
|
|
30
46
|
getChildren: () => getChildren,
|
|
31
47
|
getCriticalPath: () => getCriticalPath,
|
|
32
48
|
getDepth: () => getDepth,
|
|
@@ -35,6 +51,7 @@ __export(index_exports, {
|
|
|
35
51
|
getHungNodes: () => getHungNodes,
|
|
36
52
|
getNode: () => getNode,
|
|
37
53
|
getParent: () => getParent,
|
|
54
|
+
getPathSignature: () => getPathSignature,
|
|
38
55
|
getStats: () => getStats,
|
|
39
56
|
getSubtree: () => getSubtree,
|
|
40
57
|
getTraceTree: () => getTraceTree,
|
|
@@ -51,8 +68,321 @@ __export(index_exports, {
|
|
|
51
68
|
});
|
|
52
69
|
module.exports = __toCommonJS(index_exports);
|
|
53
70
|
|
|
71
|
+
// src/process-mining.ts
|
|
72
|
+
function getPathSignature(graph) {
|
|
73
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
74
|
+
if (!root) return "";
|
|
75
|
+
const parts = [];
|
|
76
|
+
function walk(node) {
|
|
77
|
+
parts.push(`${node.type}:${node.name}`);
|
|
78
|
+
const childNodes = [];
|
|
79
|
+
for (const childId of node.children) {
|
|
80
|
+
const child = graph.nodes.get(childId);
|
|
81
|
+
if (child) childNodes.push(child);
|
|
82
|
+
}
|
|
83
|
+
childNodes.sort((a, b) => {
|
|
84
|
+
const keyA = `${a.type}:${a.name}`;
|
|
85
|
+
const keyB = `${b.type}:${b.name}`;
|
|
86
|
+
return keyA.localeCompare(keyB);
|
|
87
|
+
});
|
|
88
|
+
for (const child of childNodes) {
|
|
89
|
+
walk(child);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
walk(root);
|
|
93
|
+
return parts.join("\u2192");
|
|
94
|
+
}
|
|
95
|
+
function stepKey(node) {
|
|
96
|
+
return `${node.type}:${node.name}`;
|
|
97
|
+
}
|
|
98
|
+
function discoverProcess(graphs) {
|
|
99
|
+
if (graphs.length === 0) {
|
|
100
|
+
throw new Error("discoverProcess requires at least one graph");
|
|
101
|
+
}
|
|
102
|
+
const steps = /* @__PURE__ */ new Set();
|
|
103
|
+
const transitionCounts = /* @__PURE__ */ new Map();
|
|
104
|
+
const outgoingCounts = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const graph of graphs) {
|
|
106
|
+
for (const node of graph.nodes.values()) {
|
|
107
|
+
const parentKey = stepKey(node);
|
|
108
|
+
steps.add(parentKey);
|
|
109
|
+
for (const childId of node.children) {
|
|
110
|
+
const child = graph.nodes.get(childId);
|
|
111
|
+
if (!child) continue;
|
|
112
|
+
const childKey = stepKey(child);
|
|
113
|
+
const tKey = `${parentKey}\0${childKey}`;
|
|
114
|
+
transitionCounts.set(tKey, (transitionCounts.get(tKey) ?? 0) + 1);
|
|
115
|
+
outgoingCounts.set(parentKey, (outgoingCounts.get(parentKey) ?? 0) + 1);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const transitions = [];
|
|
120
|
+
for (const [tKey, count] of transitionCounts) {
|
|
121
|
+
const [from, to] = tKey.split("\0");
|
|
122
|
+
const outgoing = outgoingCounts.get(from) ?? count;
|
|
123
|
+
transitions.push({
|
|
124
|
+
from,
|
|
125
|
+
to,
|
|
126
|
+
count,
|
|
127
|
+
probability: count / outgoing
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
transitions.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
131
|
+
return {
|
|
132
|
+
steps: [...steps].sort(),
|
|
133
|
+
transitions,
|
|
134
|
+
totalGraphs: graphs.length,
|
|
135
|
+
agentId: graphs[0].agentId
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function findVariants(graphs) {
|
|
139
|
+
if (graphs.length === 0) return [];
|
|
140
|
+
const groups = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const graph of graphs) {
|
|
142
|
+
const sig = getPathSignature(graph);
|
|
143
|
+
const group = groups.get(sig) ?? [];
|
|
144
|
+
group.push(graph);
|
|
145
|
+
groups.set(sig, group);
|
|
146
|
+
}
|
|
147
|
+
const total = graphs.length;
|
|
148
|
+
const variants = [];
|
|
149
|
+
for (const [pathSignature, groupGraphs] of groups) {
|
|
150
|
+
variants.push({
|
|
151
|
+
pathSignature,
|
|
152
|
+
count: groupGraphs.length,
|
|
153
|
+
percentage: groupGraphs.length / total * 100,
|
|
154
|
+
graphIds: groupGraphs.map((g) => g.id),
|
|
155
|
+
exampleGraph: groupGraphs[0]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
variants.sort((a, b) => {
|
|
159
|
+
const freqDiff = b.count - a.count;
|
|
160
|
+
if (freqDiff !== 0) return freqDiff;
|
|
161
|
+
return a.pathSignature.localeCompare(b.pathSignature);
|
|
162
|
+
});
|
|
163
|
+
return variants;
|
|
164
|
+
}
|
|
165
|
+
function percentile(sorted, p) {
|
|
166
|
+
if (sorted.length === 0) return 0;
|
|
167
|
+
if (sorted.length === 1) return sorted[0];
|
|
168
|
+
const index = p / 100 * (sorted.length - 1);
|
|
169
|
+
const lower = Math.floor(index);
|
|
170
|
+
const upper = Math.ceil(index);
|
|
171
|
+
if (lower === upper) return sorted[lower];
|
|
172
|
+
const weight = index - lower;
|
|
173
|
+
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
174
|
+
}
|
|
175
|
+
function getBottlenecks(graphs) {
|
|
176
|
+
if (graphs.length === 0) return [];
|
|
177
|
+
const now = Date.now();
|
|
178
|
+
const stats = /* @__PURE__ */ new Map();
|
|
179
|
+
for (const graph of graphs) {
|
|
180
|
+
for (const node of graph.nodes.values()) {
|
|
181
|
+
const key = `${node.type}:${node.name}`;
|
|
182
|
+
const entry = stats.get(key) ?? { durations: [], nodeType: node.type, nodeName: node.name };
|
|
183
|
+
const end = node.endTime ?? now;
|
|
184
|
+
entry.durations.push(end - node.startTime);
|
|
185
|
+
stats.set(key, entry);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const total = graphs.length;
|
|
189
|
+
const bottlenecks = [];
|
|
190
|
+
for (const [, entry] of stats) {
|
|
191
|
+
const sorted = [...entry.durations].sort((a, b) => a - b);
|
|
192
|
+
bottlenecks.push({
|
|
193
|
+
nodeName: entry.nodeName,
|
|
194
|
+
nodeType: entry.nodeType,
|
|
195
|
+
occurrences: sorted.length,
|
|
196
|
+
durations: {
|
|
197
|
+
median: percentile(sorted, 50),
|
|
198
|
+
p95: percentile(sorted, 95),
|
|
199
|
+
p99: percentile(sorted, 99),
|
|
200
|
+
min: sorted[0],
|
|
201
|
+
max: sorted[sorted.length - 1]
|
|
202
|
+
},
|
|
203
|
+
percentOfGraphs: sorted.length / total * 100
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
bottlenecks.sort((a, b) => b.durations.p95 - a.durations.p95);
|
|
207
|
+
return bottlenecks;
|
|
208
|
+
}
|
|
209
|
+
function extractGraphTransitions(graph) {
|
|
210
|
+
const transitions = /* @__PURE__ */ new Set();
|
|
211
|
+
for (const node of graph.nodes.values()) {
|
|
212
|
+
const parentKey = stepKey(node);
|
|
213
|
+
for (const childId of node.children) {
|
|
214
|
+
const child = graph.nodes.get(childId);
|
|
215
|
+
if (!child) continue;
|
|
216
|
+
transitions.add(`${parentKey}\0${stepKey(child)}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return transitions;
|
|
220
|
+
}
|
|
221
|
+
function checkConformance(graph, model) {
|
|
222
|
+
const graphTransitions = extractGraphTransitions(graph);
|
|
223
|
+
const deviations = [];
|
|
224
|
+
const modelLookup = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const t of model.transitions) {
|
|
226
|
+
modelLookup.set(`${t.from}\0${t.to}`, t);
|
|
227
|
+
}
|
|
228
|
+
let totalChecks = 0;
|
|
229
|
+
let deviationCount = 0;
|
|
230
|
+
for (const tKey of graphTransitions) {
|
|
231
|
+
totalChecks++;
|
|
232
|
+
const [from, to] = tKey.split("\0");
|
|
233
|
+
const modelTransition = modelLookup.get(tKey);
|
|
234
|
+
if (!modelTransition) {
|
|
235
|
+
deviationCount++;
|
|
236
|
+
deviations.push({
|
|
237
|
+
type: "unexpected-transition",
|
|
238
|
+
from,
|
|
239
|
+
to,
|
|
240
|
+
message: `Unexpected transition ${from} \u2192 ${to} (not in process model)`
|
|
241
|
+
});
|
|
242
|
+
} else if (modelTransition.probability < 0.1) {
|
|
243
|
+
deviationCount++;
|
|
244
|
+
deviations.push({
|
|
245
|
+
type: "low-frequency-path",
|
|
246
|
+
from,
|
|
247
|
+
to,
|
|
248
|
+
message: `Low-frequency path ${from} \u2192 ${to} (model probability: ${(modelTransition.probability * 100).toFixed(1)}%)`,
|
|
249
|
+
modelProbability: modelTransition.probability
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const graphSteps = /* @__PURE__ */ new Set();
|
|
254
|
+
for (const node of graph.nodes.values()) {
|
|
255
|
+
graphSteps.add(stepKey(node));
|
|
256
|
+
}
|
|
257
|
+
for (const t of model.transitions) {
|
|
258
|
+
if (t.probability > 0.5) {
|
|
259
|
+
const tKey = `${t.from}\0${t.to}`;
|
|
260
|
+
if (graphSteps.has(t.from) && !graphTransitions.has(tKey)) {
|
|
261
|
+
totalChecks++;
|
|
262
|
+
deviationCount++;
|
|
263
|
+
deviations.push({
|
|
264
|
+
type: "missing-transition",
|
|
265
|
+
from: t.from,
|
|
266
|
+
to: t.to,
|
|
267
|
+
message: `Missing expected transition ${t.from} \u2192 ${t.to} (model probability: ${(t.probability * 100).toFixed(1)}%)`,
|
|
268
|
+
modelProbability: t.probability
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const conformanceScore = totalChecks === 0 ? 1 : (totalChecks - deviationCount) / totalChecks;
|
|
274
|
+
return {
|
|
275
|
+
conformanceScore,
|
|
276
|
+
isConforming: deviations.length === 0,
|
|
277
|
+
deviations
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/event-emitter.ts
|
|
282
|
+
var SCHEMA_VERSION = 1;
|
|
283
|
+
function createExecutionEvent(graph, options) {
|
|
284
|
+
const duration = graph.endTime !== null ? graph.endTime - graph.startTime : Date.now() - graph.startTime;
|
|
285
|
+
let failurePoint;
|
|
286
|
+
if (graph.status === "failed") {
|
|
287
|
+
let candidate;
|
|
288
|
+
for (const node of graph.nodes.values()) {
|
|
289
|
+
if (node.status === "failed" || node.status === "timeout") {
|
|
290
|
+
const errorMeta = node.metadata.error;
|
|
291
|
+
const fp = {
|
|
292
|
+
nodeId: node.id,
|
|
293
|
+
nodeName: node.name,
|
|
294
|
+
nodeType: node.type,
|
|
295
|
+
error: typeof errorMeta === "string" ? errorMeta : void 0
|
|
296
|
+
};
|
|
297
|
+
if (node.id !== graph.rootNodeId) {
|
|
298
|
+
failurePoint = fp;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
if (!candidate) candidate = fp;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (!failurePoint) failurePoint = candidate;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
eventType: graph.status === "failed" ? "execution.failed" : "execution.completed",
|
|
308
|
+
graphId: graph.id,
|
|
309
|
+
agentId: graph.agentId,
|
|
310
|
+
timestamp: Date.now(),
|
|
311
|
+
schemaVersion: SCHEMA_VERSION,
|
|
312
|
+
status: graph.status,
|
|
313
|
+
duration,
|
|
314
|
+
nodeCount: graph.nodes.size,
|
|
315
|
+
pathSignature: getPathSignature(graph),
|
|
316
|
+
...failurePoint ? { failurePoint } : {},
|
|
317
|
+
...options?.processContext ? { processContext: options.processContext } : {},
|
|
318
|
+
...options?.semantic ? { semantic: options.semantic } : {},
|
|
319
|
+
violations: options?.violations ?? []
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function createPatternEvent(agentId, model, variants, bottlenecks) {
|
|
323
|
+
return {
|
|
324
|
+
eventType: "pattern.discovered",
|
|
325
|
+
agentId,
|
|
326
|
+
timestamp: Date.now(),
|
|
327
|
+
schemaVersion: SCHEMA_VERSION,
|
|
328
|
+
pattern: {
|
|
329
|
+
totalGraphs: model.totalGraphs,
|
|
330
|
+
variantCount: variants.length,
|
|
331
|
+
topVariants: variants.slice(0, 5).map((v) => ({
|
|
332
|
+
pathSignature: v.pathSignature,
|
|
333
|
+
count: v.count,
|
|
334
|
+
percentage: v.percentage
|
|
335
|
+
})),
|
|
336
|
+
topBottlenecks: bottlenecks.slice(0, 5).map((b) => ({
|
|
337
|
+
nodeName: b.nodeName,
|
|
338
|
+
nodeType: b.nodeType,
|
|
339
|
+
p95: b.durations.p95
|
|
340
|
+
})),
|
|
341
|
+
processModel: model
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function createEventEmitter(config) {
|
|
346
|
+
const writers = config?.writers ?? [];
|
|
347
|
+
const knowledgeStore = config?.knowledgeStore;
|
|
348
|
+
const onError = config?.onError ?? (() => {
|
|
349
|
+
});
|
|
350
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
351
|
+
return {
|
|
352
|
+
async emit(event) {
|
|
353
|
+
if (knowledgeStore) {
|
|
354
|
+
try {
|
|
355
|
+
knowledgeStore.append(event);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
onError(err);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const writer of writers) {
|
|
361
|
+
try {
|
|
362
|
+
await writer.writeEvent(event);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
onError(err);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
for (const listener of subscribers) {
|
|
368
|
+
try {
|
|
369
|
+
listener(event);
|
|
370
|
+
} catch (err) {
|
|
371
|
+
onError(err);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
subscribe(listener) {
|
|
376
|
+
subscribers.add(listener);
|
|
377
|
+
return () => {
|
|
378
|
+
subscribers.delete(listener);
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
54
384
|
// src/graph-builder.ts
|
|
55
|
-
var
|
|
385
|
+
var import_node_crypto = require("crypto");
|
|
56
386
|
function deepFreeze(obj) {
|
|
57
387
|
if (obj === null || typeof obj !== "object") return obj;
|
|
58
388
|
if (obj instanceof Map) {
|
|
@@ -83,8 +413,8 @@ function createGraphBuilder(config) {
|
|
|
83
413
|
const generateId = config?.idGenerator ?? createCounterIdGenerator();
|
|
84
414
|
const agentId = config?.agentId ?? "unknown";
|
|
85
415
|
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,
|
|
416
|
+
const spanId = (0, import_node_crypto.randomUUID)();
|
|
417
|
+
const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? (0, import_node_crypto.randomUUID)();
|
|
88
418
|
const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
|
|
89
419
|
const graphId = generateId();
|
|
90
420
|
const startTime = Date.now();
|
|
@@ -277,6 +607,336 @@ function createGraphBuilder(config) {
|
|
|
277
607
|
return builder;
|
|
278
608
|
}
|
|
279
609
|
|
|
610
|
+
// src/prompt-builder.ts
|
|
611
|
+
var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
|
|
612
|
+
function fmtDuration(ms) {
|
|
613
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
614
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
615
|
+
}
|
|
616
|
+
function fmtTime(ts) {
|
|
617
|
+
return new Date(ts).toISOString();
|
|
618
|
+
}
|
|
619
|
+
function durationStats(durations) {
|
|
620
|
+
if (durations.length === 0) return { avg: 0, p50: 0, p95: 0, min: 0, max: 0 };
|
|
621
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
622
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
623
|
+
return {
|
|
624
|
+
avg: Math.round(sum / sorted.length),
|
|
625
|
+
p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
|
|
626
|
+
p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
|
|
627
|
+
min: sorted[0] ?? 0,
|
|
628
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function buildFailureAnalysisPrompt(events, profile) {
|
|
632
|
+
const stats = durationStats(profile.recentDurations);
|
|
633
|
+
const failureDetails = events.map((e, i) => {
|
|
634
|
+
const lines = [
|
|
635
|
+
`Failure ${i + 1}:`,
|
|
636
|
+
` Time: ${fmtTime(e.timestamp)}`,
|
|
637
|
+
` Duration: ${fmtDuration(e.duration)}`,
|
|
638
|
+
` Path: ${e.pathSignature}`
|
|
639
|
+
];
|
|
640
|
+
if (e.failurePoint) {
|
|
641
|
+
lines.push(` Failed at: ${e.failurePoint.nodeName} (${e.failurePoint.nodeType})`);
|
|
642
|
+
if (e.failurePoint.error) lines.push(` Error: ${e.failurePoint.error}`);
|
|
643
|
+
}
|
|
644
|
+
if (e.violations.length > 0) {
|
|
645
|
+
lines.push(` Violations: ${e.violations.map((v) => v.message).join("; ")}`);
|
|
646
|
+
}
|
|
647
|
+
return lines.join("\n");
|
|
648
|
+
}).join("\n\n");
|
|
649
|
+
return `${ROLE}
|
|
650
|
+
|
|
651
|
+
## Agent Profile
|
|
652
|
+
- Agent: ${profile.agentId}
|
|
653
|
+
- Total runs: ${profile.totalRuns}
|
|
654
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}% (${profile.failureCount} failures / ${profile.totalRuns} total)
|
|
655
|
+
- Avg duration: ${fmtDuration(stats.avg)} (p50: ${fmtDuration(stats.p50)}, p95: ${fmtDuration(stats.p95)})
|
|
656
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
657
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
658
|
+
|
|
659
|
+
## Recent Failures (${events.length})
|
|
660
|
+
|
|
661
|
+
${failureDetails}
|
|
662
|
+
|
|
663
|
+
## Question
|
|
664
|
+
Analyze these failures. What patterns do you see? What is the most likely root cause? Are these related or independent failures?`;
|
|
665
|
+
}
|
|
666
|
+
function buildAnomalyExplanationPrompt(event, profile) {
|
|
667
|
+
const stats = durationStats(profile.recentDurations);
|
|
668
|
+
const eventDetails = [
|
|
669
|
+
`Time: ${fmtTime(event.timestamp)}`,
|
|
670
|
+
`Status: ${event.status}`,
|
|
671
|
+
`Duration: ${fmtDuration(event.duration)}`,
|
|
672
|
+
`Path: ${event.pathSignature}`,
|
|
673
|
+
`Node count: ${event.nodeCount}`
|
|
674
|
+
];
|
|
675
|
+
if (event.processContext) {
|
|
676
|
+
eventDetails.push(`Conformance score: ${event.processContext.conformanceScore}`);
|
|
677
|
+
eventDetails.push(`Is anomaly: ${event.processContext.isAnomaly}`);
|
|
678
|
+
eventDetails.push(`Variant: ${event.processContext.variant}`);
|
|
679
|
+
}
|
|
680
|
+
if (event.failurePoint) {
|
|
681
|
+
eventDetails.push(`Failed at: ${event.failurePoint.nodeName} (${event.failurePoint.nodeType})`);
|
|
682
|
+
if (event.failurePoint.error) eventDetails.push(`Error: ${event.failurePoint.error}`);
|
|
683
|
+
}
|
|
684
|
+
if (event.violations.length > 0) {
|
|
685
|
+
eventDetails.push(`Violations: ${event.violations.map((v) => v.message).join("; ")}`);
|
|
686
|
+
}
|
|
687
|
+
return `${ROLE}
|
|
688
|
+
|
|
689
|
+
## Agent Baseline (from profile)
|
|
690
|
+
- Agent: ${profile.agentId}
|
|
691
|
+
- Total runs: ${profile.totalRuns}
|
|
692
|
+
- Typical failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
693
|
+
- Typical duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}
|
|
694
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
695
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
696
|
+
|
|
697
|
+
## Anomalous Execution
|
|
698
|
+
${eventDetails.join("\n")}
|
|
699
|
+
|
|
700
|
+
## Question
|
|
701
|
+
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?`;
|
|
702
|
+
}
|
|
703
|
+
function buildAgentSummaryPrompt(profile, recentEvents, patterns) {
|
|
704
|
+
const stats = durationStats(profile.recentDurations);
|
|
705
|
+
const recentOutcomes = recentEvents.slice(0, 10).map((e) => ` ${fmtTime(e.timestamp)} \u2014 ${e.eventType} (${fmtDuration(e.duration)})`).join("\n");
|
|
706
|
+
const patternSummary = patterns.length > 0 ? patterns.slice(0, 3).map((p) => {
|
|
707
|
+
const lines = [
|
|
708
|
+
` Variants: ${p.pattern.variantCount} across ${p.pattern.totalGraphs} executions`,
|
|
709
|
+
` Top variant: ${p.pattern.topVariants[0]?.pathSignature ?? "N/A"} (${p.pattern.topVariants[0]?.percentage.toFixed(0) ?? 0}%)`
|
|
710
|
+
];
|
|
711
|
+
if (p.pattern.topBottlenecks.length > 0) {
|
|
712
|
+
const topB = p.pattern.topBottlenecks[0];
|
|
713
|
+
if (topB)
|
|
714
|
+
lines.push(` Top bottleneck: ${topB.nodeName} (p95: ${fmtDuration(topB.p95)})`);
|
|
715
|
+
}
|
|
716
|
+
return lines.join("\n");
|
|
717
|
+
}).join("\n\n") : " No patterns discovered yet.";
|
|
718
|
+
const dataNote = recentEvents.length === 0 && patterns.length === 0 ? "\nNote: Limited data available. Summary is based only on the profile statistics.\n" : "";
|
|
719
|
+
return `${ROLE}
|
|
720
|
+
|
|
721
|
+
## Agent Profile
|
|
722
|
+
- Agent: ${profile.agentId}
|
|
723
|
+
- Total runs: ${profile.totalRuns}
|
|
724
|
+
- Success rate: ${((1 - profile.failureRate) * 100).toFixed(1)}% (${profile.successCount} successes, ${profile.failureCount} failures)
|
|
725
|
+
- Duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}, range ${fmtDuration(stats.min)}\u2013${fmtDuration(stats.max)}
|
|
726
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
727
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
728
|
+
- Last pattern analysis: ${profile.lastPatternTimestamp ? fmtTime(profile.lastPatternTimestamp) : "never"}
|
|
729
|
+
${dataNote}
|
|
730
|
+
## Recent Executions (last ${recentEvents.slice(0, 10).length})
|
|
731
|
+
${recentOutcomes || " No recent events."}
|
|
732
|
+
|
|
733
|
+
## Pattern Analysis
|
|
734
|
+
${patternSummary}
|
|
735
|
+
|
|
736
|
+
## Question
|
|
737
|
+
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?`;
|
|
738
|
+
}
|
|
739
|
+
function buildFixSuggestionPrompt(events, profile, patterns) {
|
|
740
|
+
const failureGroups = /* @__PURE__ */ new Map();
|
|
741
|
+
for (const e of events) {
|
|
742
|
+
const key = e.failurePoint?.error ?? e.pathSignature;
|
|
743
|
+
const group = failureGroups.get(key) ?? [];
|
|
744
|
+
group.push(e);
|
|
745
|
+
failureGroups.set(key, group);
|
|
746
|
+
}
|
|
747
|
+
const failureGroupSummary = [...failureGroups.entries()].map(([key, group]) => {
|
|
748
|
+
const latest = group[0];
|
|
749
|
+
return ` "${key}" \u2014 ${group.length} occurrence(s), latest at ${latest ? fmtTime(latest.timestamp) : "unknown"}`;
|
|
750
|
+
}).join("\n");
|
|
751
|
+
const bottleneckDetails = patterns.flatMap((p) => p.pattern.topBottlenecks).map((b) => ` ${b.nodeName} (${b.nodeType}) \u2014 p95: ${fmtDuration(b.p95)}`);
|
|
752
|
+
const uniqueBottlenecks = [...new Set(bottleneckDetails)].join("\n");
|
|
753
|
+
const conformanceIssues = events.filter((e) => e.processContext && e.processContext.conformanceScore < 0.8).map(
|
|
754
|
+
(e) => ` ${fmtTime(e.timestamp)}: conformance ${e.processContext?.conformanceScore}, variant "${e.processContext?.variant}"`
|
|
755
|
+
).join("\n");
|
|
756
|
+
return `${ROLE}
|
|
757
|
+
|
|
758
|
+
## Agent Profile
|
|
759
|
+
- Agent: ${profile.agentId}
|
|
760
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
761
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
762
|
+
|
|
763
|
+
## Failure Patterns (${events.length} failures)
|
|
764
|
+
${failureGroupSummary || " No failures recorded."}
|
|
765
|
+
|
|
766
|
+
## Bottlenecks
|
|
767
|
+
${uniqueBottlenecks || " No bottlenecks detected."}
|
|
768
|
+
|
|
769
|
+
## Conformance Issues
|
|
770
|
+
${conformanceIssues || " No conformance issues."}
|
|
771
|
+
|
|
772
|
+
## Question
|
|
773
|
+
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.`;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/insight-engine.ts
|
|
777
|
+
var DEFAULT_CACHE_TTL_MS = 36e5;
|
|
778
|
+
var SCHEMA_VERSION2 = 1;
|
|
779
|
+
function simpleHash(input) {
|
|
780
|
+
let hash = 0;
|
|
781
|
+
for (let i = 0; i < input.length; i++) {
|
|
782
|
+
const char = input.charCodeAt(i);
|
|
783
|
+
hash = (hash << 5) - hash + char | 0;
|
|
784
|
+
}
|
|
785
|
+
return (hash >>> 0).toString(36);
|
|
786
|
+
}
|
|
787
|
+
function createInsightEngine(store, analysisFn, config) {
|
|
788
|
+
const cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
789
|
+
function checkCache(agentId, insightType, dataHash) {
|
|
790
|
+
const recent = store.getRecentInsights(agentId, { type: insightType, limit: 1 });
|
|
791
|
+
if (recent.length === 0) return null;
|
|
792
|
+
const cached = recent[0];
|
|
793
|
+
if (!cached || cached.dataHash !== dataHash) return null;
|
|
794
|
+
const age = Date.now() - cached.timestamp;
|
|
795
|
+
if (age >= cacheTtlMs) return null;
|
|
796
|
+
return cached;
|
|
797
|
+
}
|
|
798
|
+
function storeAndReturn(agentId, insightType, prompt, response, dataHash) {
|
|
799
|
+
const event = {
|
|
800
|
+
eventType: "insight.generated",
|
|
801
|
+
agentId,
|
|
802
|
+
timestamp: Date.now(),
|
|
803
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
804
|
+
insightType,
|
|
805
|
+
prompt,
|
|
806
|
+
response,
|
|
807
|
+
dataHash
|
|
808
|
+
};
|
|
809
|
+
store.appendInsight(event);
|
|
810
|
+
return {
|
|
811
|
+
agentId,
|
|
812
|
+
insightType,
|
|
813
|
+
content: response,
|
|
814
|
+
cached: false,
|
|
815
|
+
timestamp: event.timestamp
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function shortCircuit(agentId, insightType, content, cached, timestamp) {
|
|
819
|
+
return { agentId, insightType, content, cached, timestamp: timestamp ?? Date.now() };
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
async explainFailures(agentId) {
|
|
823
|
+
const profile = store.getAgentProfile(agentId);
|
|
824
|
+
if (!profile) {
|
|
825
|
+
return shortCircuit(
|
|
826
|
+
agentId,
|
|
827
|
+
"failure-analysis",
|
|
828
|
+
"No data available for this agent.",
|
|
829
|
+
false
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
833
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
834
|
+
if (failures.length === 0) {
|
|
835
|
+
return shortCircuit(
|
|
836
|
+
agentId,
|
|
837
|
+
"failure-analysis",
|
|
838
|
+
"No recent failures found for this agent.",
|
|
839
|
+
false
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile }));
|
|
843
|
+
const cached = checkCache(agentId, "failure-analysis", dataHash);
|
|
844
|
+
if (cached) {
|
|
845
|
+
return shortCircuit(agentId, "failure-analysis", cached.response, true, cached.timestamp);
|
|
846
|
+
}
|
|
847
|
+
const prompt = buildFailureAnalysisPrompt(failures, profile);
|
|
848
|
+
try {
|
|
849
|
+
const response = await analysisFn(prompt);
|
|
850
|
+
return storeAndReturn(agentId, "failure-analysis", prompt, response, dataHash);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
853
|
+
return shortCircuit(agentId, "failure-analysis", `Analysis failed: ${message}`, false);
|
|
854
|
+
}
|
|
855
|
+
},
|
|
856
|
+
async explainAnomaly(agentId, event) {
|
|
857
|
+
const profile = store.getAgentProfile(agentId);
|
|
858
|
+
if (!profile) {
|
|
859
|
+
return shortCircuit(
|
|
860
|
+
agentId,
|
|
861
|
+
"anomaly-explanation",
|
|
862
|
+
"No data available for this agent.",
|
|
863
|
+
false
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const dataHash = simpleHash(JSON.stringify({ event, profile }));
|
|
867
|
+
const cached = checkCache(agentId, "anomaly-explanation", dataHash);
|
|
868
|
+
if (cached) {
|
|
869
|
+
return shortCircuit(
|
|
870
|
+
agentId,
|
|
871
|
+
"anomaly-explanation",
|
|
872
|
+
cached.response,
|
|
873
|
+
true,
|
|
874
|
+
cached.timestamp
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
const prompt = buildAnomalyExplanationPrompt(event, profile);
|
|
878
|
+
try {
|
|
879
|
+
const response = await analysisFn(prompt);
|
|
880
|
+
return storeAndReturn(agentId, "anomaly-explanation", prompt, response, dataHash);
|
|
881
|
+
} catch (err) {
|
|
882
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
883
|
+
return shortCircuit(agentId, "anomaly-explanation", `Analysis failed: ${message}`, false);
|
|
884
|
+
}
|
|
885
|
+
},
|
|
886
|
+
async summarizeAgent(agentId) {
|
|
887
|
+
const profile = store.getAgentProfile(agentId);
|
|
888
|
+
if (!profile) {
|
|
889
|
+
return shortCircuit(agentId, "agent-summary", "No data available for this agent.", false);
|
|
890
|
+
}
|
|
891
|
+
const recentEvents = store.getRecentEvents(agentId, { limit: 20 });
|
|
892
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
893
|
+
const dataHash = simpleHash(JSON.stringify({ profile, recentEvents, patterns }));
|
|
894
|
+
const cached = checkCache(agentId, "agent-summary", dataHash);
|
|
895
|
+
if (cached) {
|
|
896
|
+
return shortCircuit(agentId, "agent-summary", cached.response, true, cached.timestamp);
|
|
897
|
+
}
|
|
898
|
+
const prompt = buildAgentSummaryPrompt(profile, recentEvents, patterns);
|
|
899
|
+
try {
|
|
900
|
+
const response = await analysisFn(prompt);
|
|
901
|
+
return storeAndReturn(agentId, "agent-summary", prompt, response, dataHash);
|
|
902
|
+
} catch (err) {
|
|
903
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
904
|
+
return shortCircuit(agentId, "agent-summary", `Analysis failed: ${message}`, false);
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
async suggestFixes(agentId) {
|
|
908
|
+
const profile = store.getAgentProfile(agentId);
|
|
909
|
+
if (!profile) {
|
|
910
|
+
return shortCircuit(agentId, "fix-suggestion", "No data available for this agent.", false);
|
|
911
|
+
}
|
|
912
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
913
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
914
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
915
|
+
if (failures.length === 0 && profile.knownBottlenecks.length === 0) {
|
|
916
|
+
return shortCircuit(
|
|
917
|
+
agentId,
|
|
918
|
+
"fix-suggestion",
|
|
919
|
+
"Agent is healthy \u2014 no failures or bottlenecks detected.",
|
|
920
|
+
false
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile, patterns }));
|
|
924
|
+
const cached = checkCache(agentId, "fix-suggestion", dataHash);
|
|
925
|
+
if (cached) {
|
|
926
|
+
return shortCircuit(agentId, "fix-suggestion", cached.response, true, cached.timestamp);
|
|
927
|
+
}
|
|
928
|
+
const prompt = buildFixSuggestionPrompt(failures, profile, patterns);
|
|
929
|
+
try {
|
|
930
|
+
const response = await analysisFn(prompt);
|
|
931
|
+
return storeAndReturn(agentId, "fix-suggestion", prompt, response, dataHash);
|
|
932
|
+
} catch (err) {
|
|
933
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
934
|
+
return shortCircuit(agentId, "fix-suggestion", `Analysis failed: ${message}`, false);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
280
940
|
// src/graph-query.ts
|
|
281
941
|
function getNode(graph, nodeId) {
|
|
282
942
|
return graph.nodes.get(nodeId);
|
|
@@ -429,7 +1089,7 @@ function groupByTraceId(graphs) {
|
|
|
429
1089
|
}
|
|
430
1090
|
function stitchTrace(graphs) {
|
|
431
1091
|
if (graphs.length === 0) throw new Error("No graphs to stitch");
|
|
432
|
-
const traceId = graphs[0]
|
|
1092
|
+
const traceId = graphs[0]?.traceId ?? "";
|
|
433
1093
|
const graphsBySpan = /* @__PURE__ */ new Map();
|
|
434
1094
|
const childMap = /* @__PURE__ */ new Map();
|
|
435
1095
|
let rootGraph = null;
|
|
@@ -540,6 +1200,9 @@ function checkGuards(graph, config) {
|
|
|
540
1200
|
});
|
|
541
1201
|
}
|
|
542
1202
|
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
1203
|
+
if (config?.policySource) {
|
|
1204
|
+
violations.push(...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now));
|
|
1205
|
+
}
|
|
543
1206
|
return violations;
|
|
544
1207
|
}
|
|
545
1208
|
function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
@@ -574,6 +1237,40 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
574
1237
|
walk(graph.rootNodeId, 0, null);
|
|
575
1238
|
return violations;
|
|
576
1239
|
}
|
|
1240
|
+
function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
1241
|
+
const violations = [];
|
|
1242
|
+
const maxFailureRate = thresholds?.maxFailureRate ?? 0.5;
|
|
1243
|
+
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
1244
|
+
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
1245
|
+
if (failureRate > maxFailureRate) {
|
|
1246
|
+
violations.push({
|
|
1247
|
+
type: "high-failure-rate",
|
|
1248
|
+
nodeId: graph.rootNodeId,
|
|
1249
|
+
message: `Agent ${graph.agentId} has a recent failure rate of ${(failureRate * 100).toFixed(0)}% (threshold: ${(maxFailureRate * 100).toFixed(0)}%)`,
|
|
1250
|
+
timestamp
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
1254
|
+
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
1255
|
+
violations.push({
|
|
1256
|
+
type: "conformance-drift",
|
|
1257
|
+
nodeId: graph.rootNodeId,
|
|
1258
|
+
message: `Agent ${graph.agentId} conformance score ${(conformanceScore * 100).toFixed(0)}% is below threshold ${(minConformance * 100).toFixed(0)}%`,
|
|
1259
|
+
timestamp
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
for (const node of graph.nodes.values()) {
|
|
1263
|
+
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
1264
|
+
violations.push({
|
|
1265
|
+
type: "known-bottleneck",
|
|
1266
|
+
nodeId: node.id,
|
|
1267
|
+
message: `Node ${node.name} (${node.type}) is a known bottleneck`,
|
|
1268
|
+
timestamp
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
return violations;
|
|
1273
|
+
}
|
|
577
1274
|
function withGuards(builder, config) {
|
|
578
1275
|
const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
|
|
579
1276
|
const onViolation = config?.onViolation ?? "warn";
|
|
@@ -638,9 +1335,510 @@ function withGuards(builder, config) {
|
|
|
638
1335
|
};
|
|
639
1336
|
}
|
|
640
1337
|
|
|
641
|
-
// src/
|
|
1338
|
+
// src/json-event-writer.ts
|
|
1339
|
+
var import_node_fs = require("fs");
|
|
1340
|
+
var import_node_path = require("path");
|
|
1341
|
+
function createJsonEventWriter(config) {
|
|
1342
|
+
const { outputDir } = config;
|
|
1343
|
+
function ensureDir() {
|
|
1344
|
+
(0, import_node_fs.mkdirSync)(outputDir, { recursive: true });
|
|
1345
|
+
}
|
|
1346
|
+
function eventFileName(event) {
|
|
1347
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
1348
|
+
const agentId = "agentId" in event ? event.agentId : "unknown";
|
|
1349
|
+
return `${typePart}-${agentId}-${event.timestamp}.json`;
|
|
1350
|
+
}
|
|
1351
|
+
return {
|
|
1352
|
+
async write(_graph) {
|
|
1353
|
+
},
|
|
1354
|
+
async writeEvent(event) {
|
|
1355
|
+
ensureDir();
|
|
1356
|
+
const fileName = eventFileName(event);
|
|
1357
|
+
const filePath = (0, import_node_path.join)(outputDir, fileName);
|
|
1358
|
+
(0, import_node_fs.writeFileSync)(filePath, JSON.stringify(event, null, 2), "utf-8");
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/knowledge-store.ts
|
|
642
1364
|
var import_node_fs2 = require("fs");
|
|
643
1365
|
var import_node_path2 = require("path");
|
|
1366
|
+
var DEFAULT_BASE_DIR = ".agentflow/knowledge";
|
|
1367
|
+
var MAX_RECENT_DURATIONS = 100;
|
|
1368
|
+
var writeCounter = 0;
|
|
1369
|
+
function toDateDir(epochMs) {
|
|
1370
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1371
|
+
}
|
|
1372
|
+
function readJson(filePath) {
|
|
1373
|
+
try {
|
|
1374
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(filePath, "utf-8"));
|
|
1375
|
+
} catch {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function writeJsonAtomic(filePath, data) {
|
|
1380
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}`;
|
|
1381
|
+
(0, import_node_fs2.writeFileSync)(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
1382
|
+
(0, import_node_fs2.renameSync)(tmpPath, filePath);
|
|
1383
|
+
}
|
|
1384
|
+
function emptyProfile(agentId) {
|
|
1385
|
+
return {
|
|
1386
|
+
agentId,
|
|
1387
|
+
totalRuns: 0,
|
|
1388
|
+
successCount: 0,
|
|
1389
|
+
failureCount: 0,
|
|
1390
|
+
failureRate: 0,
|
|
1391
|
+
recentDurations: [],
|
|
1392
|
+
lastConformanceScore: null,
|
|
1393
|
+
knownBottlenecks: [],
|
|
1394
|
+
lastPatternTimestamp: null,
|
|
1395
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
function mergeExecutionEvent(profile, event) {
|
|
1399
|
+
const totalRuns = profile.totalRuns + 1;
|
|
1400
|
+
const isFailure = event.eventType === "execution.failed";
|
|
1401
|
+
const successCount = profile.successCount + (isFailure ? 0 : 1);
|
|
1402
|
+
const failureCount = profile.failureCount + (isFailure ? 1 : 0);
|
|
1403
|
+
const durations = [...profile.recentDurations, event.duration];
|
|
1404
|
+
if (durations.length > MAX_RECENT_DURATIONS) {
|
|
1405
|
+
durations.shift();
|
|
1406
|
+
}
|
|
1407
|
+
const conformanceScore = event.processContext?.conformanceScore ?? profile.lastConformanceScore;
|
|
1408
|
+
return {
|
|
1409
|
+
agentId: profile.agentId,
|
|
1410
|
+
totalRuns,
|
|
1411
|
+
successCount,
|
|
1412
|
+
failureCount,
|
|
1413
|
+
failureRate: failureCount / totalRuns,
|
|
1414
|
+
recentDurations: durations,
|
|
1415
|
+
lastConformanceScore: conformanceScore,
|
|
1416
|
+
knownBottlenecks: profile.knownBottlenecks,
|
|
1417
|
+
lastPatternTimestamp: profile.lastPatternTimestamp,
|
|
1418
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
function mergePatternEvent(profile, event) {
|
|
1422
|
+
const existingBottlenecks = new Set(profile.knownBottlenecks);
|
|
1423
|
+
for (const b of event.pattern.topBottlenecks) {
|
|
1424
|
+
existingBottlenecks.add(b.nodeName);
|
|
1425
|
+
}
|
|
1426
|
+
return {
|
|
1427
|
+
...profile,
|
|
1428
|
+
knownBottlenecks: [...existingBottlenecks],
|
|
1429
|
+
lastPatternTimestamp: event.timestamp,
|
|
1430
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function createKnowledgeStore(config) {
|
|
1434
|
+
const baseDir = config?.baseDir ?? DEFAULT_BASE_DIR;
|
|
1435
|
+
const eventsDir = (0, import_node_path2.join)(baseDir, "events");
|
|
1436
|
+
const patternsDir = (0, import_node_path2.join)(baseDir, "patterns");
|
|
1437
|
+
const profilesDir = (0, import_node_path2.join)(baseDir, "profiles");
|
|
1438
|
+
const insightsDir = (0, import_node_path2.join)(baseDir, "insights");
|
|
1439
|
+
function ensureDir(dir) {
|
|
1440
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
1441
|
+
}
|
|
1442
|
+
function profilePath(agentId) {
|
|
1443
|
+
return (0, import_node_path2.join)(profilesDir, `${agentId}.json`);
|
|
1444
|
+
}
|
|
1445
|
+
function appendExecutionEvent(event) {
|
|
1446
|
+
const dateDir = (0, import_node_path2.join)(eventsDir, event.agentId, toDateDir(event.timestamp));
|
|
1447
|
+
ensureDir(dateDir);
|
|
1448
|
+
const typePart = event.eventType.replace(/\./g, "-");
|
|
1449
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1450
|
+
const fileName = `${typePart}-${event.timestamp}-${seq}.json`;
|
|
1451
|
+
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(dateDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1452
|
+
ensureDir(profilesDir);
|
|
1453
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1454
|
+
const updated = mergeExecutionEvent(existing, event);
|
|
1455
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1456
|
+
}
|
|
1457
|
+
function appendPatternEvent(event) {
|
|
1458
|
+
const agentPatternDir = (0, import_node_path2.join)(patternsDir, event.agentId);
|
|
1459
|
+
ensureDir(agentPatternDir);
|
|
1460
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1461
|
+
const fileName = `${event.timestamp}-${seq}.json`;
|
|
1462
|
+
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(agentPatternDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1463
|
+
ensureDir(profilesDir);
|
|
1464
|
+
const existing = readJson(profilePath(event.agentId)) ?? emptyProfile(event.agentId);
|
|
1465
|
+
const updated = mergePatternEvent(existing, event);
|
|
1466
|
+
writeJsonAtomic(profilePath(event.agentId), updated);
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
baseDir,
|
|
1470
|
+
append(event) {
|
|
1471
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1472
|
+
appendPatternEvent(event);
|
|
1473
|
+
} else {
|
|
1474
|
+
appendExecutionEvent(event);
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
getRecentEvents(agentId, options) {
|
|
1478
|
+
const limit = options?.limit ?? 50;
|
|
1479
|
+
const since = options?.since ?? 0;
|
|
1480
|
+
const agentDir = (0, import_node_path2.join)(eventsDir, agentId);
|
|
1481
|
+
if (!(0, import_node_fs2.existsSync)(agentDir)) return [];
|
|
1482
|
+
const events = [];
|
|
1483
|
+
const dateDirs = (0, import_node_fs2.readdirSync)(agentDir).sort().reverse();
|
|
1484
|
+
for (const dateDir of dateDirs) {
|
|
1485
|
+
const fullDateDir = (0, import_node_path2.join)(agentDir, dateDir);
|
|
1486
|
+
let files;
|
|
1487
|
+
try {
|
|
1488
|
+
files = (0, import_node_fs2.readdirSync)(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1489
|
+
} catch {
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
for (const file of files) {
|
|
1493
|
+
const event = readJson((0, import_node_path2.join)(fullDateDir, file));
|
|
1494
|
+
if (event && event.timestamp > since) {
|
|
1495
|
+
events.push(event);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
if (events.length >= limit * 2) break;
|
|
1499
|
+
}
|
|
1500
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1501
|
+
return events.slice(0, limit);
|
|
1502
|
+
},
|
|
1503
|
+
getAgentProfile(agentId) {
|
|
1504
|
+
return readJson(profilePath(agentId));
|
|
1505
|
+
},
|
|
1506
|
+
getPatternHistory(agentId, options) {
|
|
1507
|
+
const limit = options?.limit ?? 20;
|
|
1508
|
+
const agentPatternDir = (0, import_node_path2.join)(patternsDir, agentId);
|
|
1509
|
+
if (!(0, import_node_fs2.existsSync)(agentPatternDir)) return [];
|
|
1510
|
+
const files = (0, import_node_fs2.readdirSync)(agentPatternDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
1511
|
+
const events = [];
|
|
1512
|
+
for (const file of files.slice(0, limit)) {
|
|
1513
|
+
const event = readJson((0, import_node_path2.join)(agentPatternDir, file));
|
|
1514
|
+
if (event) events.push(event);
|
|
1515
|
+
}
|
|
1516
|
+
return events;
|
|
1517
|
+
},
|
|
1518
|
+
compact(options) {
|
|
1519
|
+
let removed = 0;
|
|
1520
|
+
if ((0, import_node_fs2.existsSync)(eventsDir)) {
|
|
1521
|
+
for (const agentId of (0, import_node_fs2.readdirSync)(eventsDir)) {
|
|
1522
|
+
const agentDir = (0, import_node_path2.join)(eventsDir, agentId);
|
|
1523
|
+
let dateDirs;
|
|
1524
|
+
try {
|
|
1525
|
+
dateDirs = (0, import_node_fs2.readdirSync)(agentDir);
|
|
1526
|
+
} catch {
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
for (const dateDir of dateDirs) {
|
|
1530
|
+
const fullDateDir = (0, import_node_path2.join)(agentDir, dateDir);
|
|
1531
|
+
let files;
|
|
1532
|
+
try {
|
|
1533
|
+
files = (0, import_node_fs2.readdirSync)(fullDateDir).filter((f) => f.endsWith(".json"));
|
|
1534
|
+
} catch {
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
for (const file of files) {
|
|
1538
|
+
const parts = file.replace(".json", "").split("-");
|
|
1539
|
+
const tsPart = parts[parts.length - 2];
|
|
1540
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1541
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1542
|
+
try {
|
|
1543
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(fullDateDir, file));
|
|
1544
|
+
removed++;
|
|
1545
|
+
} catch {
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
try {
|
|
1550
|
+
if ((0, import_node_fs2.readdirSync)(fullDateDir).length === 0) {
|
|
1551
|
+
(0, import_node_fs2.rmSync)(fullDateDir, { recursive: true });
|
|
1552
|
+
}
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
if ((0, import_node_fs2.existsSync)(patternsDir)) {
|
|
1559
|
+
for (const agentId of (0, import_node_fs2.readdirSync)(patternsDir)) {
|
|
1560
|
+
const agentPatternDir = (0, import_node_path2.join)(patternsDir, agentId);
|
|
1561
|
+
let files;
|
|
1562
|
+
try {
|
|
1563
|
+
files = (0, import_node_fs2.readdirSync)(agentPatternDir).filter((f) => f.endsWith(".json"));
|
|
1564
|
+
} catch {
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
for (const file of files) {
|
|
1568
|
+
const ts = Number.parseInt(file.split("-")[0], 10);
|
|
1569
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1570
|
+
try {
|
|
1571
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(agentPatternDir, file));
|
|
1572
|
+
removed++;
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if ((0, import_node_fs2.existsSync)(insightsDir)) {
|
|
1580
|
+
for (const agentId of (0, import_node_fs2.readdirSync)(insightsDir)) {
|
|
1581
|
+
const agentInsightDir = (0, import_node_path2.join)(insightsDir, agentId);
|
|
1582
|
+
let files;
|
|
1583
|
+
try {
|
|
1584
|
+
files = (0, import_node_fs2.readdirSync)(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1585
|
+
} catch {
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
for (const file of files) {
|
|
1589
|
+
const parts = file.replace(".json", "").split("-");
|
|
1590
|
+
const tsPart = parts[parts.length - 2];
|
|
1591
|
+
const ts = tsPart ? Number.parseInt(tsPart, 10) : 0;
|
|
1592
|
+
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1593
|
+
try {
|
|
1594
|
+
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(agentInsightDir, file));
|
|
1595
|
+
removed++;
|
|
1596
|
+
} catch {
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return { removed };
|
|
1603
|
+
},
|
|
1604
|
+
appendInsight(event) {
|
|
1605
|
+
const agentInsightDir = (0, import_node_path2.join)(insightsDir, event.agentId);
|
|
1606
|
+
ensureDir(agentInsightDir);
|
|
1607
|
+
const seq = String(writeCounter++).padStart(4, "0");
|
|
1608
|
+
const fileName = `${event.insightType}-${event.timestamp}-${seq}.json`;
|
|
1609
|
+
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(agentInsightDir, fileName), JSON.stringify(event, null, 2), "utf-8");
|
|
1610
|
+
},
|
|
1611
|
+
getRecentInsights(agentId, options) {
|
|
1612
|
+
const limit = options?.limit ?? 10;
|
|
1613
|
+
const typeFilter = options?.type;
|
|
1614
|
+
const agentInsightDir = (0, import_node_path2.join)(insightsDir, agentId);
|
|
1615
|
+
if (!(0, import_node_fs2.existsSync)(agentInsightDir)) return [];
|
|
1616
|
+
const files = (0, import_node_fs2.readdirSync)(agentInsightDir).filter((f) => f.endsWith(".json"));
|
|
1617
|
+
const events = [];
|
|
1618
|
+
for (const file of files) {
|
|
1619
|
+
const event = readJson((0, import_node_path2.join)(agentInsightDir, file));
|
|
1620
|
+
if (event) {
|
|
1621
|
+
if (typeFilter && event.insightType !== typeFilter) continue;
|
|
1622
|
+
events.push(event);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1626
|
+
return events.slice(0, limit);
|
|
1627
|
+
},
|
|
1628
|
+
// EventWriter interface
|
|
1629
|
+
async write(_graph) {
|
|
1630
|
+
},
|
|
1631
|
+
async writeEvent(event) {
|
|
1632
|
+
this.append(event);
|
|
1633
|
+
}
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/policy-source.ts
|
|
1638
|
+
var import_node_fs3 = require("fs");
|
|
1639
|
+
var import_node_path3 = require("path");
|
|
1640
|
+
function createPolicySource(store) {
|
|
1641
|
+
return {
|
|
1642
|
+
recentFailureRate(agentId) {
|
|
1643
|
+
const profile = store.getAgentProfile(agentId);
|
|
1644
|
+
return profile?.failureRate ?? 0;
|
|
1645
|
+
},
|
|
1646
|
+
isKnownBottleneck(nodeName) {
|
|
1647
|
+
const profilesDir = (0, import_node_path3.join)(store.baseDir, "profiles");
|
|
1648
|
+
let agentIds;
|
|
1649
|
+
try {
|
|
1650
|
+
agentIds = (0, import_node_fs3.readdirSync)(profilesDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
1651
|
+
} catch {
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
for (const agentId of agentIds) {
|
|
1655
|
+
const profile = store.getAgentProfile(agentId);
|
|
1656
|
+
if (profile && profile.knownBottlenecks.includes(nodeName)) {
|
|
1657
|
+
return true;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
return false;
|
|
1661
|
+
},
|
|
1662
|
+
lastConformanceScore(agentId) {
|
|
1663
|
+
const profile = store.getAgentProfile(agentId);
|
|
1664
|
+
return profile?.lastConformanceScore ?? null;
|
|
1665
|
+
},
|
|
1666
|
+
getAgentProfile(agentId) {
|
|
1667
|
+
return store.getAgentProfile(agentId);
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// src/soma-event-writer.ts
|
|
1673
|
+
var import_node_fs4 = require("fs");
|
|
1674
|
+
var import_node_path4 = require("path");
|
|
1675
|
+
function compactIso(epochMs) {
|
|
1676
|
+
return new Date(epochMs).toISOString().slice(0, 19).replace(/:/g, "");
|
|
1677
|
+
}
|
|
1678
|
+
function isoDate(epochMs) {
|
|
1679
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1680
|
+
}
|
|
1681
|
+
function renderFrontmatter(fields) {
|
|
1682
|
+
const lines = ["---"];
|
|
1683
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
1684
|
+
if (value === void 0) continue;
|
|
1685
|
+
if (Array.isArray(value)) {
|
|
1686
|
+
lines.push(`${key}: [${value.map((v) => `'${v}'`).join(", ")}]`);
|
|
1687
|
+
} else if (typeof value === "string") {
|
|
1688
|
+
lines.push(`${key}: '${value}'`);
|
|
1689
|
+
} else {
|
|
1690
|
+
lines.push(`${key}: ${String(value)}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
lines.push("---");
|
|
1694
|
+
return lines.join("\n");
|
|
1695
|
+
}
|
|
1696
|
+
function formatDuration(ms) {
|
|
1697
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
1698
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
1699
|
+
return `${(ms / 6e4).toFixed(1)}min`;
|
|
1700
|
+
}
|
|
1701
|
+
function executionEventToMarkdown(event) {
|
|
1702
|
+
const isCompleted = event.eventType === "execution.completed";
|
|
1703
|
+
const subtype = isCompleted ? "completed" : "failed";
|
|
1704
|
+
const tags = [
|
|
1705
|
+
"agentflow/execution",
|
|
1706
|
+
`agent/${event.agentId}`,
|
|
1707
|
+
`status/${subtype}`
|
|
1708
|
+
];
|
|
1709
|
+
if (event.processContext?.isAnomaly) {
|
|
1710
|
+
tags.push("agentflow/anomaly");
|
|
1711
|
+
}
|
|
1712
|
+
const frontmatter = {
|
|
1713
|
+
type: "execution",
|
|
1714
|
+
subtype,
|
|
1715
|
+
name: `Execution: ${event.agentId} \u2014 ${subtype}`,
|
|
1716
|
+
source: "agentflow",
|
|
1717
|
+
created: isoDate(event.timestamp),
|
|
1718
|
+
alfred_tags: tags,
|
|
1719
|
+
agentflow_graph_id: event.graphId,
|
|
1720
|
+
duration_ms: event.duration,
|
|
1721
|
+
node_count: event.nodeCount
|
|
1722
|
+
};
|
|
1723
|
+
if (event.processContext) {
|
|
1724
|
+
frontmatter.conformance_score = event.processContext.conformanceScore;
|
|
1725
|
+
frontmatter.is_anomaly = event.processContext.isAnomaly;
|
|
1726
|
+
}
|
|
1727
|
+
const body = [];
|
|
1728
|
+
body.push(`# Execution: ${event.agentId} \u2014 ${subtype}
|
|
1729
|
+
`);
|
|
1730
|
+
body.push(`**Duration:** ${formatDuration(event.duration)} `);
|
|
1731
|
+
body.push(`**Nodes:** ${event.nodeCount} `);
|
|
1732
|
+
body.push(`**Status:** ${event.status}
|
|
1733
|
+
`);
|
|
1734
|
+
if (event.pathSignature) {
|
|
1735
|
+
body.push(`## Path
|
|
1736
|
+
`);
|
|
1737
|
+
body.push(`\`${event.pathSignature}\`
|
|
1738
|
+
`);
|
|
1739
|
+
}
|
|
1740
|
+
if (!isCompleted && event.failurePoint) {
|
|
1741
|
+
const fp = event.failurePoint;
|
|
1742
|
+
body.push(`## Failure Point
|
|
1743
|
+
`);
|
|
1744
|
+
body.push(`**Node:** ${fp.nodeType}:${fp.nodeName} (\`${fp.nodeId}\`) `);
|
|
1745
|
+
if (fp.error) {
|
|
1746
|
+
body.push(`**Error:** ${fp.error}
|
|
1747
|
+
`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (event.processContext) {
|
|
1751
|
+
body.push(`## Process Context
|
|
1752
|
+
`);
|
|
1753
|
+
body.push(`**Conformance:** ${(event.processContext.conformanceScore * 100).toFixed(0)}% `);
|
|
1754
|
+
body.push(`**Anomaly:** ${event.processContext.isAnomaly ? "yes" : "no"}
|
|
1755
|
+
`);
|
|
1756
|
+
}
|
|
1757
|
+
body.push(`## Related
|
|
1758
|
+
`);
|
|
1759
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
1760
|
+
return `${renderFrontmatter(frontmatter)}
|
|
1761
|
+
|
|
1762
|
+
${body.join("\n")}`;
|
|
1763
|
+
}
|
|
1764
|
+
function patternEventToMarkdown(event) {
|
|
1765
|
+
const { pattern } = event;
|
|
1766
|
+
const tags = [
|
|
1767
|
+
"agentflow/pattern",
|
|
1768
|
+
`agent/${event.agentId}`
|
|
1769
|
+
];
|
|
1770
|
+
const frontmatter = {
|
|
1771
|
+
type: "synthesis",
|
|
1772
|
+
subtype: "pattern-discovery",
|
|
1773
|
+
name: `Pattern: ${event.agentId} \u2014 ${pattern.variantCount} variants across ${pattern.totalGraphs} runs`,
|
|
1774
|
+
source: "agentflow",
|
|
1775
|
+
created: isoDate(event.timestamp),
|
|
1776
|
+
alfred_tags: tags,
|
|
1777
|
+
variant_count: pattern.variantCount,
|
|
1778
|
+
total_graphs: pattern.totalGraphs
|
|
1779
|
+
};
|
|
1780
|
+
const body = [];
|
|
1781
|
+
body.push(`# Pattern: ${event.agentId}
|
|
1782
|
+
`);
|
|
1783
|
+
body.push(`**Variants:** ${pattern.variantCount} `);
|
|
1784
|
+
body.push(`**Total Runs:** ${pattern.totalGraphs}
|
|
1785
|
+
`);
|
|
1786
|
+
if (pattern.topVariants.length > 0) {
|
|
1787
|
+
body.push(`## Top Variants
|
|
1788
|
+
`);
|
|
1789
|
+
body.push(`| Path | Count | % |`);
|
|
1790
|
+
body.push(`|------|-------|---|`);
|
|
1791
|
+
for (const v of pattern.topVariants) {
|
|
1792
|
+
const sig = v.pathSignature.length > 60 ? `${v.pathSignature.slice(0, 57)}...` : v.pathSignature;
|
|
1793
|
+
body.push(`| \`${sig}\` | ${v.count} | ${v.percentage.toFixed(1)}% |`);
|
|
1794
|
+
}
|
|
1795
|
+
body.push("");
|
|
1796
|
+
}
|
|
1797
|
+
if (pattern.topBottlenecks.length > 0) {
|
|
1798
|
+
body.push(`## Top Bottlenecks
|
|
1799
|
+
`);
|
|
1800
|
+
body.push(`| Node | Type | p95 |`);
|
|
1801
|
+
body.push(`|------|------|-----|`);
|
|
1802
|
+
for (const b of pattern.topBottlenecks) {
|
|
1803
|
+
body.push(`| ${b.nodeName} | ${b.nodeType} | ${formatDuration(b.p95)} |`);
|
|
1804
|
+
}
|
|
1805
|
+
body.push("");
|
|
1806
|
+
}
|
|
1807
|
+
body.push(`## Related
|
|
1808
|
+
`);
|
|
1809
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
1810
|
+
return `${renderFrontmatter(frontmatter)}
|
|
1811
|
+
|
|
1812
|
+
${body.join("\n")}`;
|
|
1813
|
+
}
|
|
1814
|
+
function createSomaEventWriter(config) {
|
|
1815
|
+
const { inboxDir } = config;
|
|
1816
|
+
function ensureDir() {
|
|
1817
|
+
(0, import_node_fs4.mkdirSync)(inboxDir, { recursive: true });
|
|
1818
|
+
}
|
|
1819
|
+
function eventFileName(event) {
|
|
1820
|
+
const agentId = event.agentId;
|
|
1821
|
+
const ts = compactIso(event.timestamp);
|
|
1822
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
1823
|
+
return `synthesis-${agentId}-${ts}.md`;
|
|
1824
|
+
}
|
|
1825
|
+
return `execution-${agentId}-${ts}.md`;
|
|
1826
|
+
}
|
|
1827
|
+
return {
|
|
1828
|
+
async write(_graph) {
|
|
1829
|
+
},
|
|
1830
|
+
async writeEvent(event) {
|
|
1831
|
+
ensureDir();
|
|
1832
|
+
const markdown = event.eventType === "pattern.discovered" || event.eventType === "pattern.updated" ? patternEventToMarkdown(event) : executionEventToMarkdown(event);
|
|
1833
|
+
const fileName = eventFileName(event);
|
|
1834
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(inboxDir, fileName), markdown, "utf-8");
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// src/live.ts
|
|
1840
|
+
var import_node_fs6 = require("fs");
|
|
1841
|
+
var import_node_path6 = require("path");
|
|
644
1842
|
|
|
645
1843
|
// src/loader.ts
|
|
646
1844
|
function toNodesMap(raw) {
|
|
@@ -696,8 +1894,8 @@ function graphToJson(graph) {
|
|
|
696
1894
|
|
|
697
1895
|
// src/process-audit.ts
|
|
698
1896
|
var import_node_child_process = require("child_process");
|
|
699
|
-
var
|
|
700
|
-
var
|
|
1897
|
+
var import_node_fs5 = require("fs");
|
|
1898
|
+
var import_node_path5 = require("path");
|
|
701
1899
|
function isPidAlive(pid) {
|
|
702
1900
|
try {
|
|
703
1901
|
process.kill(pid, 0);
|
|
@@ -708,7 +1906,7 @@ function isPidAlive(pid) {
|
|
|
708
1906
|
}
|
|
709
1907
|
function pidMatchesName(pid, name) {
|
|
710
1908
|
try {
|
|
711
|
-
const cmdline = (0,
|
|
1909
|
+
const cmdline = (0, import_node_fs5.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
712
1910
|
return cmdline.includes(name);
|
|
713
1911
|
} catch {
|
|
714
1912
|
return false;
|
|
@@ -716,8 +1914,8 @@ function pidMatchesName(pid, name) {
|
|
|
716
1914
|
}
|
|
717
1915
|
function readPidFile(path) {
|
|
718
1916
|
try {
|
|
719
|
-
const pid = parseInt((0,
|
|
720
|
-
return isNaN(pid) ? null : pid;
|
|
1917
|
+
const pid = parseInt((0, import_node_fs5.readFileSync)(path, "utf8").trim(), 10);
|
|
1918
|
+
return Number.isNaN(pid) ? null : pid;
|
|
721
1919
|
} catch {
|
|
722
1920
|
return null;
|
|
723
1921
|
}
|
|
@@ -731,8 +1929,8 @@ function auditPidFile(config) {
|
|
|
731
1929
|
pid: null,
|
|
732
1930
|
alive: false,
|
|
733
1931
|
matchesProcess: false,
|
|
734
|
-
stale: !(0,
|
|
735
|
-
reason: (0,
|
|
1932
|
+
stale: !(0, import_node_fs5.existsSync)(config.pidFile),
|
|
1933
|
+
reason: (0, import_node_fs5.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
736
1934
|
};
|
|
737
1935
|
}
|
|
738
1936
|
const alive = isPidAlive(pid);
|
|
@@ -761,11 +1959,11 @@ function auditSystemd(config) {
|
|
|
761
1959
|
const [k, ...v] = line.split("=");
|
|
762
1960
|
if (k) props[k.trim()] = v.join("=").trim();
|
|
763
1961
|
}
|
|
764
|
-
const activeState = props
|
|
765
|
-
const subState = props
|
|
766
|
-
const mainPid = parseInt(props
|
|
767
|
-
const restarts = parseInt(props
|
|
768
|
-
const result = props
|
|
1962
|
+
const activeState = props.ActiveState ?? "unknown";
|
|
1963
|
+
const subState = props.SubState ?? "unknown";
|
|
1964
|
+
const mainPid = parseInt(props.MainPID ?? "0", 10);
|
|
1965
|
+
const restarts = parseInt(props.NRestarts ?? "0", 10);
|
|
1966
|
+
const result = props.Result ?? "unknown";
|
|
769
1967
|
return {
|
|
770
1968
|
unit,
|
|
771
1969
|
activeState,
|
|
@@ -781,9 +1979,9 @@ function auditSystemd(config) {
|
|
|
781
1979
|
}
|
|
782
1980
|
}
|
|
783
1981
|
function auditWorkers(config) {
|
|
784
|
-
if (!config.workersFile || !(0,
|
|
1982
|
+
if (!config.workersFile || !(0, import_node_fs5.existsSync)(config.workersFile)) return null;
|
|
785
1983
|
try {
|
|
786
|
-
const data = JSON.parse((0,
|
|
1984
|
+
const data = JSON.parse((0, import_node_fs5.readFileSync)(config.workersFile, "utf8"));
|
|
787
1985
|
const orchPid = data.pid ?? null;
|
|
788
1986
|
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
789
1987
|
const workers = [];
|
|
@@ -811,17 +2009,17 @@ function auditWorkers(config) {
|
|
|
811
2009
|
}
|
|
812
2010
|
function readCmdline(pid) {
|
|
813
2011
|
try {
|
|
814
|
-
return (0,
|
|
2012
|
+
return (0, import_node_fs5.readFileSync)(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim();
|
|
815
2013
|
} catch {
|
|
816
2014
|
return "";
|
|
817
2015
|
}
|
|
818
2016
|
}
|
|
819
2017
|
function getOsProcesses(processName) {
|
|
820
2018
|
try {
|
|
821
|
-
const raw = (0, import_node_child_process.execSync)(
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
);
|
|
2019
|
+
const raw = (0, import_node_child_process.execSync)(`ps -eo pid,pcpu,pmem,etime,lstart,args --no-headers`, {
|
|
2020
|
+
encoding: "utf8",
|
|
2021
|
+
timeout: 5e3
|
|
2022
|
+
});
|
|
825
2023
|
const results = [];
|
|
826
2024
|
for (const line of raw.split("\n")) {
|
|
827
2025
|
if (!line.includes(processName)) continue;
|
|
@@ -829,7 +2027,7 @@ function getOsProcesses(processName) {
|
|
|
829
2027
|
const trimmed = line.trim();
|
|
830
2028
|
const parts = trimmed.split(/\s+/);
|
|
831
2029
|
const pid = parseInt(parts[0] ?? "0", 10);
|
|
832
|
-
if (isNaN(pid) || pid <= 0) continue;
|
|
2030
|
+
if (Number.isNaN(pid) || pid <= 0) continue;
|
|
833
2031
|
const cpu = parts[1] ?? "0";
|
|
834
2032
|
const mem = parts[2] ?? "0";
|
|
835
2033
|
const elapsed = parts[3] ?? "";
|
|
@@ -848,30 +2046,30 @@ function discoverProcessConfig(dirs) {
|
|
|
848
2046
|
let workersFile;
|
|
849
2047
|
let processName = "";
|
|
850
2048
|
for (const dir of dirs) {
|
|
851
|
-
if (!(0,
|
|
2049
|
+
if (!(0, import_node_fs5.existsSync)(dir)) continue;
|
|
852
2050
|
let entries;
|
|
853
2051
|
try {
|
|
854
|
-
entries = (0,
|
|
2052
|
+
entries = (0, import_node_fs5.readdirSync)(dir);
|
|
855
2053
|
} catch {
|
|
856
2054
|
continue;
|
|
857
2055
|
}
|
|
858
2056
|
for (const f of entries) {
|
|
859
|
-
const fp = (0,
|
|
2057
|
+
const fp = (0, import_node_path5.join)(dir, f);
|
|
860
2058
|
try {
|
|
861
|
-
if (!(0,
|
|
2059
|
+
if (!(0, import_node_fs5.statSync)(fp).isFile()) continue;
|
|
862
2060
|
} catch {
|
|
863
2061
|
continue;
|
|
864
2062
|
}
|
|
865
2063
|
if (f.endsWith(".pid") && !pidFile) {
|
|
866
2064
|
pidFile = fp;
|
|
867
2065
|
if (!processName) {
|
|
868
|
-
processName = (0,
|
|
2066
|
+
processName = (0, import_node_path5.basename)(f, ".pid");
|
|
869
2067
|
}
|
|
870
2068
|
}
|
|
871
2069
|
if ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
|
|
872
2070
|
workersFile = fp;
|
|
873
2071
|
if (!processName && f !== "workers.json") {
|
|
874
|
-
processName = (0,
|
|
2072
|
+
processName = (0, import_node_path5.basename)(f, "-workers.json");
|
|
875
2073
|
}
|
|
876
2074
|
}
|
|
877
2075
|
}
|
|
@@ -906,16 +2104,45 @@ function auditProcesses(config) {
|
|
|
906
2104
|
}
|
|
907
2105
|
}
|
|
908
2106
|
if (systemd?.mainPid) knownPids.add(systemd.mainPid);
|
|
2107
|
+
const childPids = /* @__PURE__ */ new Set();
|
|
2108
|
+
for (const knownPid of knownPids) {
|
|
2109
|
+
try {
|
|
2110
|
+
const childrenRaw = (0, import_node_fs5.readFileSync)(
|
|
2111
|
+
`/proc/${knownPid}/task/${knownPid}/children`,
|
|
2112
|
+
"utf8"
|
|
2113
|
+
).trim();
|
|
2114
|
+
if (childrenRaw) {
|
|
2115
|
+
for (const c of childrenRaw.split(/\s+/)) {
|
|
2116
|
+
const cp = parseInt(c, 10);
|
|
2117
|
+
if (!Number.isNaN(cp)) childPids.add(cp);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
} catch {
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
for (const p of osProcesses) {
|
|
2124
|
+
if (knownPids.has(p.pid)) continue;
|
|
2125
|
+
try {
|
|
2126
|
+
const statusContent = (0, import_node_fs5.readFileSync)(`/proc/${p.pid}/status`, "utf8");
|
|
2127
|
+
const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
|
|
2128
|
+
if (ppidMatch) {
|
|
2129
|
+
const ppid = parseInt(ppidMatch[1] ?? "0", 10);
|
|
2130
|
+
if (knownPids.has(ppid)) childPids.add(p.pid);
|
|
2131
|
+
}
|
|
2132
|
+
} catch {
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
909
2135
|
const selfPid = process.pid;
|
|
910
2136
|
const selfPpid = process.ppid;
|
|
911
2137
|
const orphans = osProcesses.filter(
|
|
912
|
-
(p) => !knownPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
2138
|
+
(p) => !knownPids.has(p.pid) && !childPids.has(p.pid) && p.pid !== selfPid && p.pid !== selfPpid
|
|
913
2139
|
);
|
|
914
2140
|
const problems = [];
|
|
915
2141
|
if (pidFile?.stale) problems.push(`Stale PID file: ${pidFile.reason}`);
|
|
916
2142
|
if (systemd?.crashLooping) problems.push("Systemd unit is crash-looping (auto-restart)");
|
|
917
2143
|
if (systemd?.failed) problems.push("Systemd unit has failed");
|
|
918
|
-
if (systemd && systemd.restarts > 10)
|
|
2144
|
+
if (systemd && systemd.restarts > 10)
|
|
2145
|
+
problems.push(`High systemd restart count: ${systemd.restarts}`);
|
|
919
2146
|
if (pidFile?.pid && systemd?.mainPid && pidFile.pid !== systemd.mainPid) {
|
|
920
2147
|
problems.push(`PID mismatch: file says ${pidFile.pid}, systemd says ${systemd.mainPid}`);
|
|
921
2148
|
}
|
|
@@ -924,15 +2151,24 @@ function auditProcesses(config) {
|
|
|
924
2151
|
if (w.stale) problems.push(`Worker "${w.name}" (pid ${w.pid}) declares running but is dead`);
|
|
925
2152
|
}
|
|
926
2153
|
}
|
|
927
|
-
if (orphans.length > 0)
|
|
2154
|
+
if (orphans.length > 0)
|
|
2155
|
+
problems.push(
|
|
2156
|
+
`${orphans.length} orphan process(es) not tracked by PID file or workers registry`
|
|
2157
|
+
);
|
|
928
2158
|
return { pidFile, systemd, workers, osProcesses, orphans, problems };
|
|
929
2159
|
}
|
|
930
2160
|
function formatAuditReport(result) {
|
|
931
2161
|
const lines = [];
|
|
932
2162
|
lines.push("");
|
|
933
|
-
lines.push(
|
|
934
|
-
|
|
935
|
-
|
|
2163
|
+
lines.push(
|
|
2164
|
+
"\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"
|
|
2165
|
+
);
|
|
2166
|
+
lines.push(
|
|
2167
|
+
"\u2551 \u{1F50D} P R O C E S S A U D I T \u2551"
|
|
2168
|
+
);
|
|
2169
|
+
lines.push(
|
|
2170
|
+
"\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"
|
|
2171
|
+
);
|
|
936
2172
|
if (result.pidFile) {
|
|
937
2173
|
const pf = result.pidFile;
|
|
938
2174
|
const icon = pf.pid && pf.alive && pf.matchesProcess ? "\u2705" : pf.stale ? "\u26A0\uFE0F " : "\u2139\uFE0F ";
|
|
@@ -950,25 +2186,33 @@ function formatAuditReport(result) {
|
|
|
950
2186
|
}
|
|
951
2187
|
if (result.workers) {
|
|
952
2188
|
const w = result.workers;
|
|
953
|
-
lines.push(
|
|
954
|
-
|
|
2189
|
+
lines.push(
|
|
2190
|
+
`
|
|
2191
|
+
Workers (orchestrator pid ${w.orchestratorPid ?? "unknown"} ${w.orchestratorAlive ? "\u2705" : "\u274C"})`
|
|
2192
|
+
);
|
|
955
2193
|
for (const worker of w.workers) {
|
|
956
2194
|
const icon = worker.declaredStatus === "running" && worker.alive ? "\u{1F7E2}" : worker.stale ? "\u{1F534} STALE" : "\u26AA";
|
|
957
|
-
lines.push(
|
|
2195
|
+
lines.push(
|
|
2196
|
+
` ${icon} ${worker.name.padEnd(14)} pid=${String(worker.pid ?? "-").padEnd(8)} status=${worker.declaredStatus}`
|
|
2197
|
+
);
|
|
958
2198
|
}
|
|
959
2199
|
}
|
|
960
2200
|
if (result.osProcesses.length > 0) {
|
|
961
2201
|
lines.push(`
|
|
962
2202
|
OS Processes (${result.osProcesses.length} total)`);
|
|
963
2203
|
for (const p of result.osProcesses) {
|
|
964
|
-
lines.push(
|
|
2204
|
+
lines.push(
|
|
2205
|
+
` 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)}`
|
|
2206
|
+
);
|
|
965
2207
|
}
|
|
966
2208
|
}
|
|
967
2209
|
if (result.orphans.length > 0) {
|
|
968
2210
|
lines.push(`
|
|
969
2211
|
\u26A0\uFE0F ${result.orphans.length} ORPHAN PROCESS(ES):`);
|
|
970
2212
|
for (const p of result.orphans) {
|
|
971
|
-
lines.push(
|
|
2213
|
+
lines.push(
|
|
2214
|
+
` PID ${String(p.pid).padEnd(8)} CPU=${p.cpu.padEnd(6)} MEM=${p.mem.padEnd(6)} Up=${p.elapsed}`
|
|
2215
|
+
);
|
|
972
2216
|
lines.push(` Started: ${p.started}`);
|
|
973
2217
|
lines.push(` Command: ${p.cmdline || p.command}`);
|
|
974
2218
|
}
|
|
@@ -1013,19 +2257,19 @@ function parseArgs(argv) {
|
|
|
1013
2257
|
} else if (arg === "--refresh" || arg === "-r") {
|
|
1014
2258
|
i++;
|
|
1015
2259
|
const v = parseInt(args[i] ?? "", 10);
|
|
1016
|
-
if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
2260
|
+
if (!Number.isNaN(v) && v > 0) config.refreshMs = v * 1e3;
|
|
1017
2261
|
i++;
|
|
1018
2262
|
} else if (arg === "--recursive" || arg === "-R") {
|
|
1019
2263
|
config.recursive = true;
|
|
1020
2264
|
i++;
|
|
1021
2265
|
} else if (!arg.startsWith("-")) {
|
|
1022
|
-
config.dirs.push((0,
|
|
2266
|
+
config.dirs.push((0, import_node_path6.resolve)(arg));
|
|
1023
2267
|
i++;
|
|
1024
2268
|
} else {
|
|
1025
2269
|
i++;
|
|
1026
2270
|
}
|
|
1027
2271
|
}
|
|
1028
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
2272
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path6.resolve)("."));
|
|
1029
2273
|
return config;
|
|
1030
2274
|
}
|
|
1031
2275
|
function printUsage() {
|
|
@@ -1061,7 +2305,7 @@ function scanFiles(dirs, recursive) {
|
|
|
1061
2305
|
const seen = /* @__PURE__ */ new Set();
|
|
1062
2306
|
function scanDir(d, topLevel) {
|
|
1063
2307
|
try {
|
|
1064
|
-
const dirStat = (0,
|
|
2308
|
+
const dirStat = (0, import_node_fs6.statSync)(d);
|
|
1065
2309
|
const dirMtime = dirStat.mtime.getTime();
|
|
1066
2310
|
const cachedMtime = dirMtimeCache.get(d);
|
|
1067
2311
|
if (cachedMtime === dirMtime) {
|
|
@@ -1077,13 +2321,13 @@ function scanFiles(dirs, recursive) {
|
|
|
1077
2321
|
}
|
|
1078
2322
|
}
|
|
1079
2323
|
const dirResults = [];
|
|
1080
|
-
for (const f of (0,
|
|
2324
|
+
for (const f of (0, import_node_fs6.readdirSync)(d)) {
|
|
1081
2325
|
if (f.startsWith(".")) continue;
|
|
1082
|
-
const fp = (0,
|
|
2326
|
+
const fp = (0, import_node_path6.join)(d, f);
|
|
1083
2327
|
if (seen.has(fp)) continue;
|
|
1084
2328
|
let stat;
|
|
1085
2329
|
try {
|
|
1086
|
-
stat = (0,
|
|
2330
|
+
stat = (0, import_node_fs6.statSync)(fp);
|
|
1087
2331
|
} catch {
|
|
1088
2332
|
continue;
|
|
1089
2333
|
}
|
|
@@ -1094,12 +2338,22 @@ function scanFiles(dirs, recursive) {
|
|
|
1094
2338
|
if (!stat.isFile()) continue;
|
|
1095
2339
|
if (f.endsWith(".json")) {
|
|
1096
2340
|
seen.add(fp);
|
|
1097
|
-
const entry = {
|
|
2341
|
+
const entry = {
|
|
2342
|
+
filename: f,
|
|
2343
|
+
path: fp,
|
|
2344
|
+
mtime: stat.mtime.getTime(),
|
|
2345
|
+
ext: ".json"
|
|
2346
|
+
};
|
|
1098
2347
|
results.push(entry);
|
|
1099
2348
|
dirResults.push(entry);
|
|
1100
2349
|
} else if (f.endsWith(".jsonl")) {
|
|
1101
2350
|
seen.add(fp);
|
|
1102
|
-
const entry = {
|
|
2351
|
+
const entry = {
|
|
2352
|
+
filename: f,
|
|
2353
|
+
path: fp,
|
|
2354
|
+
mtime: stat.mtime.getTime(),
|
|
2355
|
+
ext: ".jsonl"
|
|
2356
|
+
};
|
|
1103
2357
|
results.push(entry);
|
|
1104
2358
|
dirResults.push(entry);
|
|
1105
2359
|
}
|
|
@@ -1115,13 +2369,13 @@ function scanFiles(dirs, recursive) {
|
|
|
1115
2369
|
}
|
|
1116
2370
|
function safeReadJson(fp) {
|
|
1117
2371
|
try {
|
|
1118
|
-
return JSON.parse((0,
|
|
2372
|
+
return JSON.parse((0, import_node_fs6.readFileSync)(fp, "utf8"));
|
|
1119
2373
|
} catch {
|
|
1120
2374
|
return null;
|
|
1121
2375
|
}
|
|
1122
2376
|
}
|
|
1123
2377
|
function nameFromFile(filename) {
|
|
1124
|
-
return (0,
|
|
2378
|
+
return (0, import_node_path6.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
1125
2379
|
}
|
|
1126
2380
|
function normalizeStatus(val) {
|
|
1127
2381
|
if (typeof val !== "string") return "unknown";
|
|
@@ -1161,7 +2415,7 @@ function findTimestamp(obj) {
|
|
|
1161
2415
|
if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
|
|
1162
2416
|
if (typeof val === "string") {
|
|
1163
2417
|
const d = Date.parse(val);
|
|
1164
|
-
if (!isNaN(d)) return d;
|
|
2418
|
+
if (!Number.isNaN(d)) return d;
|
|
1165
2419
|
}
|
|
1166
2420
|
}
|
|
1167
2421
|
return 0;
|
|
@@ -1193,7 +2447,7 @@ function extractDetail(obj) {
|
|
|
1193
2447
|
}
|
|
1194
2448
|
return parts.join(" | ") || "";
|
|
1195
2449
|
}
|
|
1196
|
-
function tryLoadTrace(
|
|
2450
|
+
function tryLoadTrace(_fp, raw) {
|
|
1197
2451
|
if (typeof raw !== "object" || raw === null) return null;
|
|
1198
2452
|
const obj = raw;
|
|
1199
2453
|
if (!("nodes" in obj)) return null;
|
|
@@ -1299,7 +2553,7 @@ function processJsonFile(file) {
|
|
|
1299
2553
|
}
|
|
1300
2554
|
function processJsonlFile(file) {
|
|
1301
2555
|
try {
|
|
1302
|
-
const content = (0,
|
|
2556
|
+
const content = (0, import_node_fs6.readFileSync)(file.path, "utf8").trim();
|
|
1303
2557
|
if (!content) return [];
|
|
1304
2558
|
const lines = content.split("\n");
|
|
1305
2559
|
const lineCount = lines.length;
|
|
@@ -1444,7 +2698,8 @@ function writeLine(lines, text) {
|
|
|
1444
2698
|
}
|
|
1445
2699
|
function flushLines(lines) {
|
|
1446
2700
|
process.stdout.write("\x1B[H");
|
|
1447
|
-
process.stdout.write(lines.join("\n")
|
|
2701
|
+
process.stdout.write(`${lines.join("\n")}
|
|
2702
|
+
`);
|
|
1448
2703
|
process.stdout.write("\x1B[J");
|
|
1449
2704
|
}
|
|
1450
2705
|
var prevFileCount = 0;
|
|
@@ -1538,7 +2793,7 @@ function render(config) {
|
|
|
1538
2793
|
const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
|
|
1539
2794
|
groups.push({
|
|
1540
2795
|
name: groupName,
|
|
1541
|
-
source: records[0]
|
|
2796
|
+
source: records[0]?.source ?? "trace",
|
|
1542
2797
|
status,
|
|
1543
2798
|
lastTs,
|
|
1544
2799
|
detail: `${records.length} agents`,
|
|
@@ -1584,8 +2839,10 @@ function render(config) {
|
|
|
1584
2839
|
cachedAuditResult = auditResult;
|
|
1585
2840
|
lastAuditTime = now;
|
|
1586
2841
|
} catch (err) {
|
|
1587
|
-
process.stderr.write(
|
|
1588
|
-
`
|
|
2842
|
+
process.stderr.write(
|
|
2843
|
+
`[agentflow] process audit error: ${err instanceof Error ? err.message : err}
|
|
2844
|
+
`
|
|
2845
|
+
);
|
|
1589
2846
|
}
|
|
1590
2847
|
}
|
|
1591
2848
|
} else {
|
|
@@ -1642,7 +2899,7 @@ function render(config) {
|
|
|
1642
2899
|
return new Date(ts).toLocaleTimeString();
|
|
1643
2900
|
}
|
|
1644
2901
|
function truncate(s, max) {
|
|
1645
|
-
return s.length > max ? s.slice(0, max - 1)
|
|
2902
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
1646
2903
|
}
|
|
1647
2904
|
const termWidth = process.stdout.columns || 120;
|
|
1648
2905
|
const detailWidth = Math.max(20, termWidth - 60);
|
|
@@ -1683,7 +2940,8 @@ function render(config) {
|
|
|
1683
2940
|
if (ar.systemd) {
|
|
1684
2941
|
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
2942
|
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1686
|
-
if (ar.systemd.restarts > 0)
|
|
2943
|
+
if (ar.systemd.restarts > 0)
|
|
2944
|
+
sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1687
2945
|
}
|
|
1688
2946
|
let pidLabel = "";
|
|
1689
2947
|
if (ar.pidFile?.pid) {
|
|
@@ -1692,7 +2950,10 @@ function render(config) {
|
|
|
1692
2950
|
}
|
|
1693
2951
|
writeLine(L, "");
|
|
1694
2952
|
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1695
|
-
writeLine(
|
|
2953
|
+
writeLine(
|
|
2954
|
+
L,
|
|
2955
|
+
` ${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}`}`
|
|
2956
|
+
);
|
|
1696
2957
|
if (workerParts.length > 0) {
|
|
1697
2958
|
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1698
2959
|
}
|
|
@@ -1704,7 +2965,10 @@ function render(config) {
|
|
|
1704
2965
|
if (ar.orphans.length > 0) {
|
|
1705
2966
|
for (const o of ar.orphans.slice(0, 5)) {
|
|
1706
2967
|
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1707
|
-
writeLine(
|
|
2968
|
+
writeLine(
|
|
2969
|
+
L,
|
|
2970
|
+
` ${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}`
|
|
2971
|
+
);
|
|
1708
2972
|
}
|
|
1709
2973
|
if (ar.orphans.length > 5) {
|
|
1710
2974
|
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
@@ -1778,7 +3042,7 @@ function render(config) {
|
|
|
1778
3042
|
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
1779
3043
|
const tg = tree[i];
|
|
1780
3044
|
const depth = getDistDepth(dt, tg.spanId);
|
|
1781
|
-
const indent =
|
|
3045
|
+
const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
|
|
1782
3046
|
const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
1783
3047
|
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1784
3048
|
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 +3063,7 @@ function render(config) {
|
|
|
1799
3063
|
const t = new Date(r.lastActive).toLocaleTimeString();
|
|
1800
3064
|
const agent = truncate(r.id, 26).padEnd(26);
|
|
1801
3065
|
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1802
|
-
const ageStr = age < 60 ? age
|
|
3066
|
+
const ageStr = age < 60 ? `${age}s ago` : age < 3600 ? `${Math.floor(age / 60)}m ago` : `${Math.floor(age / 3600)}h ago`;
|
|
1803
3067
|
const det = truncate(r.detail, detailWidth);
|
|
1804
3068
|
writeLine(
|
|
1805
3069
|
L,
|
|
@@ -1829,13 +3093,13 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
1829
3093
|
}
|
|
1830
3094
|
function startLive(argv) {
|
|
1831
3095
|
const config = parseArgs(argv);
|
|
1832
|
-
const valid = config.dirs.filter((d) => (0,
|
|
3096
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs6.existsSync)(d));
|
|
1833
3097
|
if (valid.length === 0) {
|
|
1834
3098
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1835
3099
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1836
3100
|
process.exit(1);
|
|
1837
3101
|
}
|
|
1838
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
3102
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs6.existsSync)(d));
|
|
1839
3103
|
if (invalid.length > 0) {
|
|
1840
3104
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1841
3105
|
}
|
|
@@ -1847,7 +3111,7 @@ function startLive(argv) {
|
|
|
1847
3111
|
let debounce = null;
|
|
1848
3112
|
for (const dir of config.dirs) {
|
|
1849
3113
|
try {
|
|
1850
|
-
(0,
|
|
3114
|
+
(0, import_node_fs6.watch)(dir, { recursive: config.recursive }, () => {
|
|
1851
3115
|
if (debounce) clearTimeout(debounce);
|
|
1852
3116
|
debounce = setTimeout(() => render(config), 500);
|
|
1853
3117
|
});
|
|
@@ -1864,7 +3128,7 @@ function startLive(argv) {
|
|
|
1864
3128
|
};
|
|
1865
3129
|
process.on("SIGINT", () => {
|
|
1866
3130
|
cleanup();
|
|
1867
|
-
console.log(C.dim
|
|
3131
|
+
console.log(`${C.dim}Monitor stopped.${C.reset}`);
|
|
1868
3132
|
process.exit(0);
|
|
1869
3133
|
});
|
|
1870
3134
|
process.on("SIGTERM", () => {
|
|
@@ -1875,20 +3139,20 @@ function startLive(argv) {
|
|
|
1875
3139
|
|
|
1876
3140
|
// src/runner.ts
|
|
1877
3141
|
var import_node_child_process2 = require("child_process");
|
|
1878
|
-
var
|
|
1879
|
-
var
|
|
3142
|
+
var import_node_fs7 = require("fs");
|
|
3143
|
+
var import_node_path7 = require("path");
|
|
1880
3144
|
function globToRegex(pattern) {
|
|
1881
3145
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1882
3146
|
return new RegExp(`^${escaped}$`);
|
|
1883
3147
|
}
|
|
1884
3148
|
function snapshotDir(dir, patterns) {
|
|
1885
3149
|
const result = /* @__PURE__ */ new Map();
|
|
1886
|
-
if (!(0,
|
|
1887
|
-
for (const entry of (0,
|
|
3150
|
+
if (!(0, import_node_fs7.existsSync)(dir)) return result;
|
|
3151
|
+
for (const entry of (0, import_node_fs7.readdirSync)(dir)) {
|
|
1888
3152
|
if (!patterns.some((re) => re.test(entry))) continue;
|
|
1889
|
-
const full = (0,
|
|
3153
|
+
const full = (0, import_node_path7.join)(dir, entry);
|
|
1890
3154
|
try {
|
|
1891
|
-
const stat = (0,
|
|
3155
|
+
const stat = (0, import_node_fs7.statSync)(full);
|
|
1892
3156
|
if (stat.isFile()) {
|
|
1893
3157
|
result.set(full, stat.mtimeMs);
|
|
1894
3158
|
}
|
|
@@ -1898,11 +3162,11 @@ function snapshotDir(dir, patterns) {
|
|
|
1898
3162
|
return result;
|
|
1899
3163
|
}
|
|
1900
3164
|
function agentIdFromFilename(filePath) {
|
|
1901
|
-
const base = (0,
|
|
3165
|
+
const base = (0, import_node_path7.basename)(filePath, ".json");
|
|
1902
3166
|
const cleaned = base.replace(/-state$/, "");
|
|
1903
3167
|
return `alfred-${cleaned}`;
|
|
1904
3168
|
}
|
|
1905
|
-
function deriveAgentId(
|
|
3169
|
+
function deriveAgentId(_command) {
|
|
1906
3170
|
return "orchestrator";
|
|
1907
3171
|
}
|
|
1908
3172
|
function fileTimestamp() {
|
|
@@ -1920,7 +3184,7 @@ async function runTraced(config) {
|
|
|
1920
3184
|
if (command.length === 0) {
|
|
1921
3185
|
throw new Error("runTraced: command must not be empty");
|
|
1922
3186
|
}
|
|
1923
|
-
const resolvedTracesDir = (0,
|
|
3187
|
+
const resolvedTracesDir = (0, import_node_path7.resolve)(tracesDir);
|
|
1924
3188
|
const patterns = watchPatterns.map(globToRegex);
|
|
1925
3189
|
const orchestrator = createGraphBuilder({ agentId, trigger });
|
|
1926
3190
|
const { traceId, spanId } = orchestrator.traceContext;
|
|
@@ -2003,23 +3267,27 @@ async function runTraced(config) {
|
|
|
2003
3267
|
childBuilder.endNode(childRootId);
|
|
2004
3268
|
allGraphs.push(childBuilder.build());
|
|
2005
3269
|
}
|
|
2006
|
-
if (!(0,
|
|
2007
|
-
(0,
|
|
3270
|
+
if (!(0, import_node_fs7.existsSync)(resolvedTracesDir)) {
|
|
3271
|
+
(0, import_node_fs7.mkdirSync)(resolvedTracesDir, { recursive: true });
|
|
2008
3272
|
}
|
|
2009
3273
|
const ts = fileTimestamp();
|
|
2010
3274
|
const tracePaths = [];
|
|
2011
3275
|
for (const graph of allGraphs) {
|
|
2012
3276
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
2013
|
-
const outPath = (0,
|
|
2014
|
-
const resolvedOut = (0,
|
|
2015
|
-
if (!resolvedOut.startsWith(resolvedTracesDir
|
|
2016
|
-
throw new Error(
|
|
3277
|
+
const outPath = (0, import_node_path7.join)(resolvedTracesDir, filename);
|
|
3278
|
+
const resolvedOut = (0, import_node_path7.resolve)(outPath);
|
|
3279
|
+
if (!resolvedOut.startsWith(`${resolvedTracesDir}/`) && resolvedOut !== resolvedTracesDir) {
|
|
3280
|
+
throw new Error(
|
|
3281
|
+
`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`
|
|
3282
|
+
);
|
|
2017
3283
|
}
|
|
2018
|
-
(0,
|
|
3284
|
+
(0, import_node_fs7.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
2019
3285
|
tracePaths.push(outPath);
|
|
2020
3286
|
}
|
|
2021
3287
|
if (tracePaths.length > 0) {
|
|
2022
|
-
console.log(
|
|
3288
|
+
console.log(
|
|
3289
|
+
`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`
|
|
3290
|
+
);
|
|
2023
3291
|
}
|
|
2024
3292
|
return {
|
|
2025
3293
|
exitCode,
|
|
@@ -2033,7 +3301,7 @@ async function runTraced(config) {
|
|
|
2033
3301
|
|
|
2034
3302
|
// src/trace-store.ts
|
|
2035
3303
|
var import_promises = require("fs/promises");
|
|
2036
|
-
var
|
|
3304
|
+
var import_node_path8 = require("path");
|
|
2037
3305
|
function createTraceStore(dir) {
|
|
2038
3306
|
async function ensureDir() {
|
|
2039
3307
|
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
@@ -2050,7 +3318,7 @@ function createTraceStore(dir) {
|
|
|
2050
3318
|
for (const file of files) {
|
|
2051
3319
|
if (!file.endsWith(".json")) continue;
|
|
2052
3320
|
try {
|
|
2053
|
-
const content = await (0, import_promises.readFile)((0,
|
|
3321
|
+
const content = await (0, import_promises.readFile)((0, import_node_path8.join)(dir, file), "utf-8");
|
|
2054
3322
|
const graph = loadGraph(content);
|
|
2055
3323
|
graphs.push(graph);
|
|
2056
3324
|
} catch {
|
|
@@ -2062,10 +3330,10 @@ function createTraceStore(dir) {
|
|
|
2062
3330
|
async save(graph) {
|
|
2063
3331
|
await ensureDir();
|
|
2064
3332
|
const json = graphToJson(graph);
|
|
2065
|
-
const filePath = (0,
|
|
2066
|
-
const resolvedBase = (0,
|
|
2067
|
-
const resolvedPath = (0,
|
|
2068
|
-
if (!resolvedPath.startsWith(resolvedBase
|
|
3333
|
+
const filePath = (0, import_node_path8.join)(dir, `${graph.id}.json`);
|
|
3334
|
+
const resolvedBase = (0, import_node_path8.resolve)(dir);
|
|
3335
|
+
const resolvedPath = (0, import_node_path8.resolve)(filePath);
|
|
3336
|
+
if (!resolvedPath.startsWith(`${resolvedBase}/`) && resolvedPath !== resolvedBase) {
|
|
2069
3337
|
throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
|
|
2070
3338
|
}
|
|
2071
3339
|
await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
@@ -2073,7 +3341,7 @@ function createTraceStore(dir) {
|
|
|
2073
3341
|
},
|
|
2074
3342
|
async get(graphId) {
|
|
2075
3343
|
await ensureDir();
|
|
2076
|
-
const filePath = (0,
|
|
3344
|
+
const filePath = (0, import_node_path8.join)(dir, `${graphId}.json`);
|
|
2077
3345
|
try {
|
|
2078
3346
|
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
2079
3347
|
return loadGraph(content);
|
|
@@ -2143,7 +3411,7 @@ var STATUS_ICONS = {
|
|
|
2143
3411
|
hung: "\u231B",
|
|
2144
3412
|
timeout: "\u231B"
|
|
2145
3413
|
};
|
|
2146
|
-
function
|
|
3414
|
+
function formatDuration2(ms) {
|
|
2147
3415
|
if (ms < 1e3) return `${ms}ms`;
|
|
2148
3416
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
2149
3417
|
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
@@ -2151,7 +3419,7 @@ function formatDuration(ms) {
|
|
|
2151
3419
|
}
|
|
2152
3420
|
function nodeDuration(node, graphEndTime) {
|
|
2153
3421
|
const end = node.endTime ?? graphEndTime;
|
|
2154
|
-
return
|
|
3422
|
+
return formatDuration2(end - node.startTime);
|
|
2155
3423
|
}
|
|
2156
3424
|
function getGenAiInfo(node) {
|
|
2157
3425
|
const parts = [];
|
|
@@ -2195,7 +3463,8 @@ function toAsciiTree(graph) {
|
|
|
2195
3463
|
const children = getChildren(graph, nodeId);
|
|
2196
3464
|
const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
|
|
2197
3465
|
for (let i = 0; i < children.length; i++) {
|
|
2198
|
-
|
|
3466
|
+
const childId = children[i]?.id;
|
|
3467
|
+
if (childId) renderNode(childId, childPrefix, i === children.length - 1, false);
|
|
2199
3468
|
}
|
|
2200
3469
|
}
|
|
2201
3470
|
renderNode(graph.rootNodeId, "", true, true);
|
|
@@ -2214,7 +3483,7 @@ function toTimeline(graph) {
|
|
|
2214
3483
|
const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
|
|
2215
3484
|
for (let i = 0; i <= tickCount; i++) {
|
|
2216
3485
|
const t = totalDuration * i / tickCount;
|
|
2217
|
-
scaleLabels.push(
|
|
3486
|
+
scaleLabels.push(formatDuration2(t));
|
|
2218
3487
|
}
|
|
2219
3488
|
let header = "";
|
|
2220
3489
|
for (let i = 0; i < scaleLabels.length; i++) {
|
|
@@ -2266,9 +3535,9 @@ function toTimeline(graph) {
|
|
|
2266
3535
|
}
|
|
2267
3536
|
|
|
2268
3537
|
// src/watch.ts
|
|
2269
|
-
var
|
|
3538
|
+
var import_node_fs9 = require("fs");
|
|
2270
3539
|
var import_node_os = require("os");
|
|
2271
|
-
var
|
|
3540
|
+
var import_node_path9 = require("path");
|
|
2272
3541
|
|
|
2273
3542
|
// src/watch-alerts.ts
|
|
2274
3543
|
var import_node_child_process3 = require("child_process");
|
|
@@ -2389,15 +3658,15 @@ function sendCommand(payload, cmd) {
|
|
|
2389
3658
|
}
|
|
2390
3659
|
|
|
2391
3660
|
// src/watch-state.ts
|
|
2392
|
-
var
|
|
3661
|
+
var import_node_fs8 = require("fs");
|
|
2393
3662
|
function parseDuration(input) {
|
|
2394
3663
|
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
|
|
2395
3664
|
if (!match) {
|
|
2396
3665
|
const n = parseInt(input, 10);
|
|
2397
|
-
return isNaN(n) ? 0 : n * 1e3;
|
|
3666
|
+
return Number.isNaN(n) ? 0 : n * 1e3;
|
|
2398
3667
|
}
|
|
2399
3668
|
const value = parseFloat(match[1]);
|
|
2400
|
-
switch (match[2]
|
|
3669
|
+
switch (match[2]?.toLowerCase()) {
|
|
2401
3670
|
case "s":
|
|
2402
3671
|
return value * 1e3;
|
|
2403
3672
|
case "m":
|
|
@@ -2414,9 +3683,9 @@ function emptyState() {
|
|
|
2414
3683
|
return { version: 1, agents: {}, lastPollTime: 0 };
|
|
2415
3684
|
}
|
|
2416
3685
|
function loadWatchState(filePath) {
|
|
2417
|
-
if (!(0,
|
|
3686
|
+
if (!(0, import_node_fs8.existsSync)(filePath)) return emptyState();
|
|
2418
3687
|
try {
|
|
2419
|
-
const raw = JSON.parse((0,
|
|
3688
|
+
const raw = JSON.parse((0, import_node_fs8.readFileSync)(filePath, "utf8"));
|
|
2420
3689
|
if (raw.version !== 1 || typeof raw.agents !== "object") return emptyState();
|
|
2421
3690
|
return raw;
|
|
2422
3691
|
} catch {
|
|
@@ -2424,13 +3693,13 @@ function loadWatchState(filePath) {
|
|
|
2424
3693
|
}
|
|
2425
3694
|
}
|
|
2426
3695
|
function saveWatchState(filePath, state) {
|
|
2427
|
-
const tmp = filePath
|
|
3696
|
+
const tmp = `${filePath}.tmp`;
|
|
2428
3697
|
try {
|
|
2429
|
-
(0,
|
|
2430
|
-
(0,
|
|
3698
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
3699
|
+
(0, import_node_fs8.renameSync)(tmp, filePath);
|
|
2431
3700
|
} catch {
|
|
2432
3701
|
try {
|
|
2433
|
-
(0,
|
|
3702
|
+
(0, import_node_fs8.writeFileSync)(filePath, JSON.stringify(state, null, 2), "utf8");
|
|
2434
3703
|
} catch {
|
|
2435
3704
|
}
|
|
2436
3705
|
}
|
|
@@ -2625,8 +3894,8 @@ function parseWatchArgs(argv) {
|
|
|
2625
3894
|
i++;
|
|
2626
3895
|
const val = args[i] ?? "";
|
|
2627
3896
|
if (val === "telegram") {
|
|
2628
|
-
const botToken = process.env
|
|
2629
|
-
const chatId = process.env
|
|
3897
|
+
const botToken = process.env.AGENTFLOW_TELEGRAM_BOT_TOKEN ?? "";
|
|
3898
|
+
const chatId = process.env.AGENTFLOW_TELEGRAM_CHAT_ID ?? "";
|
|
2630
3899
|
if (botToken && chatId) {
|
|
2631
3900
|
notifyChannels.push({ type: "telegram", botToken, chatId });
|
|
2632
3901
|
} else {
|
|
@@ -2643,7 +3912,7 @@ function parseWatchArgs(argv) {
|
|
|
2643
3912
|
} else if (arg === "--poll") {
|
|
2644
3913
|
i++;
|
|
2645
3914
|
const v = parseInt(args[i] ?? "", 10);
|
|
2646
|
-
if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
3915
|
+
if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2647
3916
|
i++;
|
|
2648
3917
|
} else if (arg === "--cooldown") {
|
|
2649
3918
|
i++;
|
|
@@ -2658,20 +3927,20 @@ function parseWatchArgs(argv) {
|
|
|
2658
3927
|
recursive = true;
|
|
2659
3928
|
i++;
|
|
2660
3929
|
} else if (!arg.startsWith("-")) {
|
|
2661
|
-
dirs.push((0,
|
|
3930
|
+
dirs.push((0, import_node_path9.resolve)(arg));
|
|
2662
3931
|
i++;
|
|
2663
3932
|
} else {
|
|
2664
3933
|
i++;
|
|
2665
3934
|
}
|
|
2666
3935
|
}
|
|
2667
|
-
if (dirs.length === 0) dirs.push((0,
|
|
3936
|
+
if (dirs.length === 0) dirs.push((0, import_node_path9.resolve)("."));
|
|
2668
3937
|
if (alertConditions.length === 0) {
|
|
2669
3938
|
alertConditions.push({ type: "error" });
|
|
2670
3939
|
alertConditions.push({ type: "recovery" });
|
|
2671
3940
|
}
|
|
2672
3941
|
notifyChannels.unshift({ type: "stdout" });
|
|
2673
3942
|
if (!stateFilePath) {
|
|
2674
|
-
stateFilePath = (0,
|
|
3943
|
+
stateFilePath = (0, import_node_path9.join)(dirs[0], ".agentflow-watch-state.json");
|
|
2675
3944
|
}
|
|
2676
3945
|
return {
|
|
2677
3946
|
dirs,
|
|
@@ -2679,7 +3948,7 @@ function parseWatchArgs(argv) {
|
|
|
2679
3948
|
pollIntervalMs,
|
|
2680
3949
|
alertConditions,
|
|
2681
3950
|
notifyChannels,
|
|
2682
|
-
stateFilePath: (0,
|
|
3951
|
+
stateFilePath: (0, import_node_path9.resolve)(stateFilePath),
|
|
2683
3952
|
cooldownMs
|
|
2684
3953
|
};
|
|
2685
3954
|
}
|
|
@@ -2733,12 +4002,12 @@ Examples:
|
|
|
2733
4002
|
}
|
|
2734
4003
|
function startWatch(argv) {
|
|
2735
4004
|
const config = parseWatchArgs(argv);
|
|
2736
|
-
const valid = config.dirs.filter((d) => (0,
|
|
4005
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs9.existsSync)(d));
|
|
2737
4006
|
if (valid.length === 0) {
|
|
2738
4007
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
2739
4008
|
process.exit(1);
|
|
2740
4009
|
}
|
|
2741
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
4010
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs9.existsSync)(d));
|
|
2742
4011
|
if (invalid.length > 0) {
|
|
2743
4012
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
2744
4013
|
}
|
|
@@ -2759,7 +4028,7 @@ agentflow watch started`);
|
|
|
2759
4028
|
console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
|
|
2760
4029
|
console.log(` Alert on: ${condLabels.join(", ")}`);
|
|
2761
4030
|
console.log(
|
|
2762
|
-
` Notify: stdout${channelLabels.length > 0 ?
|
|
4031
|
+
` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
|
|
2763
4032
|
);
|
|
2764
4033
|
console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
|
|
2765
4034
|
console.log(` State: ${config.stateFilePath}`);
|
|
@@ -2818,12 +4087,28 @@ agentflow watch started`);
|
|
|
2818
4087
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2819
4088
|
0 && (module.exports = {
|
|
2820
4089
|
auditProcesses,
|
|
4090
|
+
buildAgentSummaryPrompt,
|
|
4091
|
+
buildAnomalyExplanationPrompt,
|
|
4092
|
+
buildFailureAnalysisPrompt,
|
|
4093
|
+
buildFixSuggestionPrompt,
|
|
4094
|
+
checkConformance,
|
|
2821
4095
|
checkGuards,
|
|
4096
|
+
createEventEmitter,
|
|
4097
|
+
createExecutionEvent,
|
|
2822
4098
|
createGraphBuilder,
|
|
4099
|
+
createInsightEngine,
|
|
4100
|
+
createJsonEventWriter,
|
|
4101
|
+
createKnowledgeStore,
|
|
4102
|
+
createPatternEvent,
|
|
4103
|
+
createPolicySource,
|
|
4104
|
+
createSomaEventWriter,
|
|
2823
4105
|
createTraceStore,
|
|
4106
|
+
discoverProcess,
|
|
2824
4107
|
discoverProcessConfig,
|
|
4108
|
+
findVariants,
|
|
2825
4109
|
findWaitingOn,
|
|
2826
4110
|
formatAuditReport,
|
|
4111
|
+
getBottlenecks,
|
|
2827
4112
|
getChildren,
|
|
2828
4113
|
getCriticalPath,
|
|
2829
4114
|
getDepth,
|
|
@@ -2832,6 +4117,7 @@ agentflow watch started`);
|
|
|
2832
4117
|
getHungNodes,
|
|
2833
4118
|
getNode,
|
|
2834
4119
|
getParent,
|
|
4120
|
+
getPathSignature,
|
|
2835
4121
|
getStats,
|
|
2836
4122
|
getSubtree,
|
|
2837
4123
|
getTraceTree,
|