agentflow-core 0.6.3 → 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-R5SNE2HB.js → chunk-6X5HU5LB.js} +139 -57
- package/dist/cli.cjs +166 -80
- package/dist/cli.js +7 -3
- package/dist/index.cjs +1421 -124
- 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,13 +2698,14 @@ 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;
|
|
1451
2706
|
var newExecCount = 0;
|
|
1452
2707
|
var sessionStart = Date.now();
|
|
1453
|
-
var
|
|
2708
|
+
var liveRunning = false;
|
|
1454
2709
|
var cachedAuditConfig = null;
|
|
1455
2710
|
var cachedAuditResult = null;
|
|
1456
2711
|
var lastAuditTime = 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,14 +2899,10 @@ 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);
|
|
1649
|
-
if (firstRender) {
|
|
1650
|
-
process.stdout.write("\x1B[2J");
|
|
1651
|
-
firstRender = false;
|
|
1652
|
-
}
|
|
1653
2906
|
const L = [];
|
|
1654
2907
|
writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
|
|
1655
2908
|
writeLine(
|
|
@@ -1687,7 +2940,8 @@ function render(config) {
|
|
|
1687
2940
|
if (ar.systemd) {
|
|
1688
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}`;
|
|
1689
2942
|
sysdLabel = ` ${C.bold}Systemd${C.reset} ${si} ${ar.systemd.activeState}`;
|
|
1690
|
-
if (ar.systemd.restarts > 0)
|
|
2943
|
+
if (ar.systemd.restarts > 0)
|
|
2944
|
+
sysdLabel += ` ${C.dim}(${ar.systemd.restarts} restarts)${C.reset}`;
|
|
1691
2945
|
}
|
|
1692
2946
|
let pidLabel = "";
|
|
1693
2947
|
if (ar.pidFile?.pid) {
|
|
@@ -1696,7 +2950,10 @@ function render(config) {
|
|
|
1696
2950
|
}
|
|
1697
2951
|
writeLine(L, "");
|
|
1698
2952
|
writeLine(L, ` ${C.bold}${C.under}Process Health${C.reset}`);
|
|
1699
|
-
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
|
+
);
|
|
1700
2957
|
if (workerParts.length > 0) {
|
|
1701
2958
|
writeLine(L, ` ${C.dim}Workers${C.reset} ${workerParts.join(" ")}`);
|
|
1702
2959
|
}
|
|
@@ -1708,7 +2965,10 @@ function render(config) {
|
|
|
1708
2965
|
if (ar.orphans.length > 0) {
|
|
1709
2966
|
for (const o of ar.orphans.slice(0, 5)) {
|
|
1710
2967
|
const cmd = (o.cmdline || o.command).substring(0, detailWidth);
|
|
1711
|
-
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
|
+
);
|
|
1712
2972
|
}
|
|
1713
2973
|
if (ar.orphans.length > 5) {
|
|
1714
2974
|
writeLine(L, ` ${C.dim}... +${ar.orphans.length - 5} more orphans${C.reset}`);
|
|
@@ -1782,7 +3042,7 @@ function render(config) {
|
|
|
1782
3042
|
for (let i = 0; i < Math.min(tree.length, 6); i++) {
|
|
1783
3043
|
const tg = tree[i];
|
|
1784
3044
|
const depth = getDistDepth(dt, tg.spanId);
|
|
1785
|
-
const indent =
|
|
3045
|
+
const indent = ` ${"\u2502 ".repeat(Math.max(0, depth - 1))}`;
|
|
1786
3046
|
const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
|
|
1787
3047
|
const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1788
3048
|
const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
|
|
@@ -1803,7 +3063,7 @@ function render(config) {
|
|
|
1803
3063
|
const t = new Date(r.lastActive).toLocaleTimeString();
|
|
1804
3064
|
const agent = truncate(r.id, 26).padEnd(26);
|
|
1805
3065
|
const age = Math.floor((Date.now() - r.lastActive) / 1e3);
|
|
1806
|
-
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`;
|
|
1807
3067
|
const det = truncate(r.detail, detailWidth);
|
|
1808
3068
|
writeLine(
|
|
1809
3069
|
L,
|
|
@@ -1833,22 +3093,25 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
1833
3093
|
}
|
|
1834
3094
|
function startLive(argv) {
|
|
1835
3095
|
const config = parseArgs(argv);
|
|
1836
|
-
const valid = config.dirs.filter((d) => (0,
|
|
3096
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs6.existsSync)(d));
|
|
1837
3097
|
if (valid.length === 0) {
|
|
1838
3098
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
1839
3099
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
1840
3100
|
process.exit(1);
|
|
1841
3101
|
}
|
|
1842
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
3102
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs6.existsSync)(d));
|
|
1843
3103
|
if (invalid.length > 0) {
|
|
1844
3104
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
1845
3105
|
}
|
|
1846
3106
|
config.dirs = valid;
|
|
3107
|
+
process.stdout.write("\x1B[?1049h");
|
|
3108
|
+
process.stdout.write("\x1B[?25l");
|
|
3109
|
+
liveRunning = true;
|
|
1847
3110
|
render(config);
|
|
1848
3111
|
let debounce = null;
|
|
1849
3112
|
for (const dir of config.dirs) {
|
|
1850
3113
|
try {
|
|
1851
|
-
(0,
|
|
3114
|
+
(0, import_node_fs6.watch)(dir, { recursive: config.recursive }, () => {
|
|
1852
3115
|
if (debounce) clearTimeout(debounce);
|
|
1853
3116
|
debounce = setTimeout(() => render(config), 500);
|
|
1854
3117
|
});
|
|
@@ -1856,28 +3119,40 @@ function startLive(argv) {
|
|
|
1856
3119
|
}
|
|
1857
3120
|
}
|
|
1858
3121
|
setInterval(() => render(config), config.refreshMs);
|
|
3122
|
+
const cleanup = () => {
|
|
3123
|
+
if (liveRunning) {
|
|
3124
|
+
liveRunning = false;
|
|
3125
|
+
process.stdout.write("\x1B[?25h");
|
|
3126
|
+
process.stdout.write("\x1B[?1049l");
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
1859
3129
|
process.on("SIGINT", () => {
|
|
1860
|
-
|
|
3130
|
+
cleanup();
|
|
3131
|
+
console.log(`${C.dim}Monitor stopped.${C.reset}`);
|
|
3132
|
+
process.exit(0);
|
|
3133
|
+
});
|
|
3134
|
+
process.on("SIGTERM", () => {
|
|
3135
|
+
cleanup();
|
|
1861
3136
|
process.exit(0);
|
|
1862
3137
|
});
|
|
1863
3138
|
}
|
|
1864
3139
|
|
|
1865
3140
|
// src/runner.ts
|
|
1866
3141
|
var import_node_child_process2 = require("child_process");
|
|
1867
|
-
var
|
|
1868
|
-
var
|
|
3142
|
+
var import_node_fs7 = require("fs");
|
|
3143
|
+
var import_node_path7 = require("path");
|
|
1869
3144
|
function globToRegex(pattern) {
|
|
1870
3145
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1871
3146
|
return new RegExp(`^${escaped}$`);
|
|
1872
3147
|
}
|
|
1873
3148
|
function snapshotDir(dir, patterns) {
|
|
1874
3149
|
const result = /* @__PURE__ */ new Map();
|
|
1875
|
-
if (!(0,
|
|
1876
|
-
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)) {
|
|
1877
3152
|
if (!patterns.some((re) => re.test(entry))) continue;
|
|
1878
|
-
const full = (0,
|
|
3153
|
+
const full = (0, import_node_path7.join)(dir, entry);
|
|
1879
3154
|
try {
|
|
1880
|
-
const stat = (0,
|
|
3155
|
+
const stat = (0, import_node_fs7.statSync)(full);
|
|
1881
3156
|
if (stat.isFile()) {
|
|
1882
3157
|
result.set(full, stat.mtimeMs);
|
|
1883
3158
|
}
|
|
@@ -1887,11 +3162,11 @@ function snapshotDir(dir, patterns) {
|
|
|
1887
3162
|
return result;
|
|
1888
3163
|
}
|
|
1889
3164
|
function agentIdFromFilename(filePath) {
|
|
1890
|
-
const base = (0,
|
|
3165
|
+
const base = (0, import_node_path7.basename)(filePath, ".json");
|
|
1891
3166
|
const cleaned = base.replace(/-state$/, "");
|
|
1892
3167
|
return `alfred-${cleaned}`;
|
|
1893
3168
|
}
|
|
1894
|
-
function deriveAgentId(
|
|
3169
|
+
function deriveAgentId(_command) {
|
|
1895
3170
|
return "orchestrator";
|
|
1896
3171
|
}
|
|
1897
3172
|
function fileTimestamp() {
|
|
@@ -1909,7 +3184,7 @@ async function runTraced(config) {
|
|
|
1909
3184
|
if (command.length === 0) {
|
|
1910
3185
|
throw new Error("runTraced: command must not be empty");
|
|
1911
3186
|
}
|
|
1912
|
-
const resolvedTracesDir = (0,
|
|
3187
|
+
const resolvedTracesDir = (0, import_node_path7.resolve)(tracesDir);
|
|
1913
3188
|
const patterns = watchPatterns.map(globToRegex);
|
|
1914
3189
|
const orchestrator = createGraphBuilder({ agentId, trigger });
|
|
1915
3190
|
const { traceId, spanId } = orchestrator.traceContext;
|
|
@@ -1992,23 +3267,27 @@ async function runTraced(config) {
|
|
|
1992
3267
|
childBuilder.endNode(childRootId);
|
|
1993
3268
|
allGraphs.push(childBuilder.build());
|
|
1994
3269
|
}
|
|
1995
|
-
if (!(0,
|
|
1996
|
-
(0,
|
|
3270
|
+
if (!(0, import_node_fs7.existsSync)(resolvedTracesDir)) {
|
|
3271
|
+
(0, import_node_fs7.mkdirSync)(resolvedTracesDir, { recursive: true });
|
|
1997
3272
|
}
|
|
1998
3273
|
const ts = fileTimestamp();
|
|
1999
3274
|
const tracePaths = [];
|
|
2000
3275
|
for (const graph of allGraphs) {
|
|
2001
3276
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
2002
|
-
const outPath = (0,
|
|
2003
|
-
const resolvedOut = (0,
|
|
2004
|
-
if (!resolvedOut.startsWith(resolvedTracesDir
|
|
2005
|
-
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
|
+
);
|
|
2006
3283
|
}
|
|
2007
|
-
(0,
|
|
3284
|
+
(0, import_node_fs7.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
2008
3285
|
tracePaths.push(outPath);
|
|
2009
3286
|
}
|
|
2010
3287
|
if (tracePaths.length > 0) {
|
|
2011
|
-
console.log(
|
|
3288
|
+
console.log(
|
|
3289
|
+
`\u{1F50D} Run "agentflow trace show ${orchestratorGraph.id} --traces-dir ${resolvedTracesDir}" to inspect`
|
|
3290
|
+
);
|
|
2012
3291
|
}
|
|
2013
3292
|
return {
|
|
2014
3293
|
exitCode,
|
|
@@ -2022,7 +3301,7 @@ async function runTraced(config) {
|
|
|
2022
3301
|
|
|
2023
3302
|
// src/trace-store.ts
|
|
2024
3303
|
var import_promises = require("fs/promises");
|
|
2025
|
-
var
|
|
3304
|
+
var import_node_path8 = require("path");
|
|
2026
3305
|
function createTraceStore(dir) {
|
|
2027
3306
|
async function ensureDir() {
|
|
2028
3307
|
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
@@ -2039,7 +3318,7 @@ function createTraceStore(dir) {
|
|
|
2039
3318
|
for (const file of files) {
|
|
2040
3319
|
if (!file.endsWith(".json")) continue;
|
|
2041
3320
|
try {
|
|
2042
|
-
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");
|
|
2043
3322
|
const graph = loadGraph(content);
|
|
2044
3323
|
graphs.push(graph);
|
|
2045
3324
|
} catch {
|
|
@@ -2051,10 +3330,10 @@ function createTraceStore(dir) {
|
|
|
2051
3330
|
async save(graph) {
|
|
2052
3331
|
await ensureDir();
|
|
2053
3332
|
const json = graphToJson(graph);
|
|
2054
|
-
const filePath = (0,
|
|
2055
|
-
const resolvedBase = (0,
|
|
2056
|
-
const resolvedPath = (0,
|
|
2057
|
-
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) {
|
|
2058
3337
|
throw new Error(`Path traversal detected: "${graph.id}" escapes base directory`);
|
|
2059
3338
|
}
|
|
2060
3339
|
await (0, import_promises.writeFile)(filePath, JSON.stringify(json, null, 2), "utf-8");
|
|
@@ -2062,7 +3341,7 @@ function createTraceStore(dir) {
|
|
|
2062
3341
|
},
|
|
2063
3342
|
async get(graphId) {
|
|
2064
3343
|
await ensureDir();
|
|
2065
|
-
const filePath = (0,
|
|
3344
|
+
const filePath = (0, import_node_path8.join)(dir, `${graphId}.json`);
|
|
2066
3345
|
try {
|
|
2067
3346
|
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
2068
3347
|
return loadGraph(content);
|
|
@@ -2132,7 +3411,7 @@ var STATUS_ICONS = {
|
|
|
2132
3411
|
hung: "\u231B",
|
|
2133
3412
|
timeout: "\u231B"
|
|
2134
3413
|
};
|
|
2135
|
-
function
|
|
3414
|
+
function formatDuration2(ms) {
|
|
2136
3415
|
if (ms < 1e3) return `${ms}ms`;
|
|
2137
3416
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
2138
3417
|
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
@@ -2140,7 +3419,7 @@ function formatDuration(ms) {
|
|
|
2140
3419
|
}
|
|
2141
3420
|
function nodeDuration(node, graphEndTime) {
|
|
2142
3421
|
const end = node.endTime ?? graphEndTime;
|
|
2143
|
-
return
|
|
3422
|
+
return formatDuration2(end - node.startTime);
|
|
2144
3423
|
}
|
|
2145
3424
|
function getGenAiInfo(node) {
|
|
2146
3425
|
const parts = [];
|
|
@@ -2184,7 +3463,8 @@ function toAsciiTree(graph) {
|
|
|
2184
3463
|
const children = getChildren(graph, nodeId);
|
|
2185
3464
|
const childPrefix = isRoot ? "" : prefix + (isLast ? " " : "\u2502 ");
|
|
2186
3465
|
for (let i = 0; i < children.length; i++) {
|
|
2187
|
-
|
|
3466
|
+
const childId = children[i]?.id;
|
|
3467
|
+
if (childId) renderNode(childId, childPrefix, i === children.length - 1, false);
|
|
2188
3468
|
}
|
|
2189
3469
|
}
|
|
2190
3470
|
renderNode(graph.rootNodeId, "", true, true);
|
|
@@ -2203,7 +3483,7 @@ function toTimeline(graph) {
|
|
|
2203
3483
|
const tickCount = Math.min(5, Math.max(2, Math.floor(barWidth / 10)));
|
|
2204
3484
|
for (let i = 0; i <= tickCount; i++) {
|
|
2205
3485
|
const t = totalDuration * i / tickCount;
|
|
2206
|
-
scaleLabels.push(
|
|
3486
|
+
scaleLabels.push(formatDuration2(t));
|
|
2207
3487
|
}
|
|
2208
3488
|
let header = "";
|
|
2209
3489
|
for (let i = 0; i < scaleLabels.length; i++) {
|
|
@@ -2255,9 +3535,9 @@ function toTimeline(graph) {
|
|
|
2255
3535
|
}
|
|
2256
3536
|
|
|
2257
3537
|
// src/watch.ts
|
|
2258
|
-
var
|
|
3538
|
+
var import_node_fs9 = require("fs");
|
|
2259
3539
|
var import_node_os = require("os");
|
|
2260
|
-
var
|
|
3540
|
+
var import_node_path9 = require("path");
|
|
2261
3541
|
|
|
2262
3542
|
// src/watch-alerts.ts
|
|
2263
3543
|
var import_node_child_process3 = require("child_process");
|
|
@@ -2378,15 +3658,15 @@ function sendCommand(payload, cmd) {
|
|
|
2378
3658
|
}
|
|
2379
3659
|
|
|
2380
3660
|
// src/watch-state.ts
|
|
2381
|
-
var
|
|
3661
|
+
var import_node_fs8 = require("fs");
|
|
2382
3662
|
function parseDuration(input) {
|
|
2383
3663
|
const match = input.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)$/i);
|
|
2384
3664
|
if (!match) {
|
|
2385
3665
|
const n = parseInt(input, 10);
|
|
2386
|
-
return isNaN(n) ? 0 : n * 1e3;
|
|
3666
|
+
return Number.isNaN(n) ? 0 : n * 1e3;
|
|
2387
3667
|
}
|
|
2388
3668
|
const value = parseFloat(match[1]);
|
|
2389
|
-
switch (match[2]
|
|
3669
|
+
switch (match[2]?.toLowerCase()) {
|
|
2390
3670
|
case "s":
|
|
2391
3671
|
return value * 1e3;
|
|
2392
3672
|
case "m":
|
|
@@ -2403,9 +3683,9 @@ function emptyState() {
|
|
|
2403
3683
|
return { version: 1, agents: {}, lastPollTime: 0 };
|
|
2404
3684
|
}
|
|
2405
3685
|
function loadWatchState(filePath) {
|
|
2406
|
-
if (!(0,
|
|
3686
|
+
if (!(0, import_node_fs8.existsSync)(filePath)) return emptyState();
|
|
2407
3687
|
try {
|
|
2408
|
-
const raw = JSON.parse((0,
|
|
3688
|
+
const raw = JSON.parse((0, import_node_fs8.readFileSync)(filePath, "utf8"));
|
|
2409
3689
|
if (raw.version !== 1 || typeof raw.agents !== "object") return emptyState();
|
|
2410
3690
|
return raw;
|
|
2411
3691
|
} catch {
|
|
@@ -2413,13 +3693,13 @@ function loadWatchState(filePath) {
|
|
|
2413
3693
|
}
|
|
2414
3694
|
}
|
|
2415
3695
|
function saveWatchState(filePath, state) {
|
|
2416
|
-
const tmp = filePath
|
|
3696
|
+
const tmp = `${filePath}.tmp`;
|
|
2417
3697
|
try {
|
|
2418
|
-
(0,
|
|
2419
|
-
(0,
|
|
3698
|
+
(0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
3699
|
+
(0, import_node_fs8.renameSync)(tmp, filePath);
|
|
2420
3700
|
} catch {
|
|
2421
3701
|
try {
|
|
2422
|
-
(0,
|
|
3702
|
+
(0, import_node_fs8.writeFileSync)(filePath, JSON.stringify(state, null, 2), "utf8");
|
|
2423
3703
|
} catch {
|
|
2424
3704
|
}
|
|
2425
3705
|
}
|
|
@@ -2614,8 +3894,8 @@ function parseWatchArgs(argv) {
|
|
|
2614
3894
|
i++;
|
|
2615
3895
|
const val = args[i] ?? "";
|
|
2616
3896
|
if (val === "telegram") {
|
|
2617
|
-
const botToken = process.env
|
|
2618
|
-
const chatId = process.env
|
|
3897
|
+
const botToken = process.env.AGENTFLOW_TELEGRAM_BOT_TOKEN ?? "";
|
|
3898
|
+
const chatId = process.env.AGENTFLOW_TELEGRAM_CHAT_ID ?? "";
|
|
2619
3899
|
if (botToken && chatId) {
|
|
2620
3900
|
notifyChannels.push({ type: "telegram", botToken, chatId });
|
|
2621
3901
|
} else {
|
|
@@ -2632,7 +3912,7 @@ function parseWatchArgs(argv) {
|
|
|
2632
3912
|
} else if (arg === "--poll") {
|
|
2633
3913
|
i++;
|
|
2634
3914
|
const v = parseInt(args[i] ?? "", 10);
|
|
2635
|
-
if (!isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
3915
|
+
if (!Number.isNaN(v) && v > 0) pollIntervalMs = v * 1e3;
|
|
2636
3916
|
i++;
|
|
2637
3917
|
} else if (arg === "--cooldown") {
|
|
2638
3918
|
i++;
|
|
@@ -2647,20 +3927,20 @@ function parseWatchArgs(argv) {
|
|
|
2647
3927
|
recursive = true;
|
|
2648
3928
|
i++;
|
|
2649
3929
|
} else if (!arg.startsWith("-")) {
|
|
2650
|
-
dirs.push((0,
|
|
3930
|
+
dirs.push((0, import_node_path9.resolve)(arg));
|
|
2651
3931
|
i++;
|
|
2652
3932
|
} else {
|
|
2653
3933
|
i++;
|
|
2654
3934
|
}
|
|
2655
3935
|
}
|
|
2656
|
-
if (dirs.length === 0) dirs.push((0,
|
|
3936
|
+
if (dirs.length === 0) dirs.push((0, import_node_path9.resolve)("."));
|
|
2657
3937
|
if (alertConditions.length === 0) {
|
|
2658
3938
|
alertConditions.push({ type: "error" });
|
|
2659
3939
|
alertConditions.push({ type: "recovery" });
|
|
2660
3940
|
}
|
|
2661
3941
|
notifyChannels.unshift({ type: "stdout" });
|
|
2662
3942
|
if (!stateFilePath) {
|
|
2663
|
-
stateFilePath = (0,
|
|
3943
|
+
stateFilePath = (0, import_node_path9.join)(dirs[0], ".agentflow-watch-state.json");
|
|
2664
3944
|
}
|
|
2665
3945
|
return {
|
|
2666
3946
|
dirs,
|
|
@@ -2668,7 +3948,7 @@ function parseWatchArgs(argv) {
|
|
|
2668
3948
|
pollIntervalMs,
|
|
2669
3949
|
alertConditions,
|
|
2670
3950
|
notifyChannels,
|
|
2671
|
-
stateFilePath: (0,
|
|
3951
|
+
stateFilePath: (0, import_node_path9.resolve)(stateFilePath),
|
|
2672
3952
|
cooldownMs
|
|
2673
3953
|
};
|
|
2674
3954
|
}
|
|
@@ -2722,12 +4002,12 @@ Examples:
|
|
|
2722
4002
|
}
|
|
2723
4003
|
function startWatch(argv) {
|
|
2724
4004
|
const config = parseWatchArgs(argv);
|
|
2725
|
-
const valid = config.dirs.filter((d) => (0,
|
|
4005
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs9.existsSync)(d));
|
|
2726
4006
|
if (valid.length === 0) {
|
|
2727
4007
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
2728
4008
|
process.exit(1);
|
|
2729
4009
|
}
|
|
2730
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
4010
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs9.existsSync)(d));
|
|
2731
4011
|
if (invalid.length > 0) {
|
|
2732
4012
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
2733
4013
|
}
|
|
@@ -2748,7 +4028,7 @@ agentflow watch started`);
|
|
|
2748
4028
|
console.log(` Poll: ${config.pollIntervalMs / 1e3}s`);
|
|
2749
4029
|
console.log(` Alert on: ${condLabels.join(", ")}`);
|
|
2750
4030
|
console.log(
|
|
2751
|
-
` Notify: stdout${channelLabels.length > 0 ?
|
|
4031
|
+
` Notify: stdout${channelLabels.length > 0 ? `, ${channelLabels.join(", ")}` : ""}`
|
|
2752
4032
|
);
|
|
2753
4033
|
console.log(` Cooldown: ${Math.floor(config.cooldownMs / 6e4)}m`);
|
|
2754
4034
|
console.log(` State: ${config.stateFilePath}`);
|
|
@@ -2807,12 +4087,28 @@ agentflow watch started`);
|
|
|
2807
4087
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2808
4088
|
0 && (module.exports = {
|
|
2809
4089
|
auditProcesses,
|
|
4090
|
+
buildAgentSummaryPrompt,
|
|
4091
|
+
buildAnomalyExplanationPrompt,
|
|
4092
|
+
buildFailureAnalysisPrompt,
|
|
4093
|
+
buildFixSuggestionPrompt,
|
|
4094
|
+
checkConformance,
|
|
2810
4095
|
checkGuards,
|
|
4096
|
+
createEventEmitter,
|
|
4097
|
+
createExecutionEvent,
|
|
2811
4098
|
createGraphBuilder,
|
|
4099
|
+
createInsightEngine,
|
|
4100
|
+
createJsonEventWriter,
|
|
4101
|
+
createKnowledgeStore,
|
|
4102
|
+
createPatternEvent,
|
|
4103
|
+
createPolicySource,
|
|
4104
|
+
createSomaEventWriter,
|
|
2812
4105
|
createTraceStore,
|
|
4106
|
+
discoverProcess,
|
|
2813
4107
|
discoverProcessConfig,
|
|
4108
|
+
findVariants,
|
|
2814
4109
|
findWaitingOn,
|
|
2815
4110
|
formatAuditReport,
|
|
4111
|
+
getBottlenecks,
|
|
2816
4112
|
getChildren,
|
|
2817
4113
|
getCriticalPath,
|
|
2818
4114
|
getDepth,
|
|
@@ -2821,6 +4117,7 @@ agentflow watch started`);
|
|
|
2821
4117
|
getHungNodes,
|
|
2822
4118
|
getNode,
|
|
2823
4119
|
getParent,
|
|
4120
|
+
getPathSignature,
|
|
2824
4121
|
getStats,
|
|
2825
4122
|
getSubtree,
|
|
2826
4123
|
getTraceTree,
|