agentflow-core 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/dist/{chunk-DY7YHFIB.js → chunk-BYWLDTZK.js} +2 -1
- package/dist/{chunk-6X5HU5LB.js → chunk-NVFWBTAZ.js} +75 -37
- package/dist/cli.cjs +76 -38
- package/dist/cli.js +6 -6
- package/dist/index.cjs +945 -740
- package/dist/index.d.cts +344 -137
- package/dist/index.d.ts +344 -137
- package/dist/index.js +380 -215
- package/dist/{loader-LYRR6LMM.js → loader-JMFEFI3Q.js} +1 -1
- package/package.json +7 -3
package/dist/index.cjs
CHANGED
|
@@ -37,11 +37,13 @@ __export(index_exports, {
|
|
|
37
37
|
createPolicySource: () => createPolicySource,
|
|
38
38
|
createSomaEventWriter: () => createSomaEventWriter,
|
|
39
39
|
createTraceStore: () => createTraceStore,
|
|
40
|
+
discoverAllProcessConfigs: () => discoverAllProcessConfigs,
|
|
40
41
|
discoverProcess: () => discoverProcess,
|
|
41
42
|
discoverProcessConfig: () => discoverProcessConfig,
|
|
42
43
|
findVariants: () => findVariants,
|
|
43
44
|
findWaitingOn: () => findWaitingOn,
|
|
44
45
|
formatAuditReport: () => formatAuditReport,
|
|
46
|
+
formatReceipt: () => formatReceipt,
|
|
45
47
|
getBottlenecks: () => getBottlenecks,
|
|
46
48
|
getChildren: () => getChildren,
|
|
47
49
|
getCriticalPath: () => getCriticalPath,
|
|
@@ -63,6 +65,7 @@ __export(index_exports, {
|
|
|
63
65
|
startWatch: () => startWatch,
|
|
64
66
|
stitchTrace: () => stitchTrace,
|
|
65
67
|
toAsciiTree: () => toAsciiTree,
|
|
68
|
+
toReceipt: () => toReceipt,
|
|
66
69
|
toTimeline: () => toTimeline,
|
|
67
70
|
withGuards: () => withGuards
|
|
68
71
|
});
|
|
@@ -132,7 +135,7 @@ function discoverProcess(graphs) {
|
|
|
132
135
|
steps: [...steps].sort(),
|
|
133
136
|
transitions,
|
|
134
137
|
totalGraphs: graphs.length,
|
|
135
|
-
agentId: graphs[0]
|
|
138
|
+
agentId: graphs[0]?.agentId ?? ""
|
|
136
139
|
};
|
|
137
140
|
}
|
|
138
141
|
function findVariants(graphs) {
|
|
@@ -164,13 +167,13 @@ function findVariants(graphs) {
|
|
|
164
167
|
}
|
|
165
168
|
function percentile(sorted, p) {
|
|
166
169
|
if (sorted.length === 0) return 0;
|
|
167
|
-
if (sorted.length === 1) return sorted[0];
|
|
170
|
+
if (sorted.length === 1) return sorted[0] ?? 0;
|
|
168
171
|
const index = p / 100 * (sorted.length - 1);
|
|
169
172
|
const lower = Math.floor(index);
|
|
170
173
|
const upper = Math.ceil(index);
|
|
171
|
-
if (lower === upper) return sorted[lower];
|
|
174
|
+
if (lower === upper) return sorted[lower] ?? 0;
|
|
172
175
|
const weight = index - lower;
|
|
173
|
-
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
176
|
+
return (sorted[lower] ?? 0) * (1 - weight) + (sorted[upper] ?? 0) * weight;
|
|
174
177
|
}
|
|
175
178
|
function getBottlenecks(graphs) {
|
|
176
179
|
if (graphs.length === 0) return [];
|
|
@@ -197,8 +200,8 @@ function getBottlenecks(graphs) {
|
|
|
197
200
|
median: percentile(sorted, 50),
|
|
198
201
|
p95: percentile(sorted, 95),
|
|
199
202
|
p99: percentile(sorted, 99),
|
|
200
|
-
min: sorted[0],
|
|
201
|
-
max: sorted[sorted.length - 1]
|
|
203
|
+
min: sorted[0] ?? 0,
|
|
204
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
202
205
|
},
|
|
203
206
|
percentOfGraphs: sorted.length / total * 100
|
|
204
207
|
});
|
|
@@ -607,435 +610,105 @@ function createGraphBuilder(config) {
|
|
|
607
610
|
return builder;
|
|
608
611
|
}
|
|
609
612
|
|
|
610
|
-
// src/
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
614
|
-
return `${(ms / 1e3).toFixed(1)}s`;
|
|
613
|
+
// src/graph-query.ts
|
|
614
|
+
function getNode(graph, nodeId) {
|
|
615
|
+
return graph.nodes.get(nodeId);
|
|
615
616
|
}
|
|
616
|
-
function
|
|
617
|
-
|
|
617
|
+
function getChildren(graph, nodeId) {
|
|
618
|
+
const node = graph.nodes.get(nodeId);
|
|
619
|
+
if (!node) return [];
|
|
620
|
+
const result = [];
|
|
621
|
+
for (const childId of node.children) {
|
|
622
|
+
const child = graph.nodes.get(childId);
|
|
623
|
+
if (child) result.push(child);
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
618
626
|
}
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
};
|
|
627
|
+
function getParent(graph, nodeId) {
|
|
628
|
+
const node = graph.nodes.get(nodeId);
|
|
629
|
+
if (!node || node.parentId === null) return void 0;
|
|
630
|
+
return graph.nodes.get(node.parentId);
|
|
630
631
|
}
|
|
631
|
-
function
|
|
632
|
-
const
|
|
633
|
-
|
|
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?`;
|
|
632
|
+
function getFailures(graph) {
|
|
633
|
+
const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
|
|
634
|
+
return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
|
|
665
635
|
}
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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}`);
|
|
636
|
+
function getHungNodes(graph) {
|
|
637
|
+
return [...graph.nodes.values()].filter(
|
|
638
|
+
(node) => node.status === "running" && node.endTime === null
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
function getCriticalPath(graph) {
|
|
642
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
643
|
+
if (!root) return [];
|
|
644
|
+
function nodeDuration3(node) {
|
|
645
|
+
const end = node.endTime ?? Date.now();
|
|
646
|
+
return end - node.startTime;
|
|
683
647
|
}
|
|
684
|
-
|
|
685
|
-
|
|
648
|
+
function dfs(node) {
|
|
649
|
+
if (node.children.length === 0) {
|
|
650
|
+
return { duration: nodeDuration3(node), path: [node] };
|
|
651
|
+
}
|
|
652
|
+
let bestChild = { duration: -1, path: [] };
|
|
653
|
+
for (const childId of node.children) {
|
|
654
|
+
const child = graph.nodes.get(childId);
|
|
655
|
+
if (!child) continue;
|
|
656
|
+
const result = dfs(child);
|
|
657
|
+
if (result.duration > bestChild.duration) {
|
|
658
|
+
bestChild = result;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
duration: nodeDuration3(node) + bestChild.duration,
|
|
663
|
+
path: [node, ...bestChild.path]
|
|
664
|
+
};
|
|
686
665
|
}
|
|
687
|
-
return
|
|
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?`;
|
|
666
|
+
return dfs(root).path;
|
|
702
667
|
}
|
|
703
|
-
function
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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)})`);
|
|
668
|
+
function findWaitingOn(graph, nodeId) {
|
|
669
|
+
const results = [];
|
|
670
|
+
for (const edge of graph.edges) {
|
|
671
|
+
if (edge.from === nodeId && edge.type === "waited_on") {
|
|
672
|
+
const node = graph.nodes.get(edge.to);
|
|
673
|
+
if (node) results.push(node);
|
|
715
674
|
}
|
|
716
|
-
|
|
717
|
-
|
|
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?`;
|
|
675
|
+
}
|
|
676
|
+
return results;
|
|
738
677
|
}
|
|
739
|
-
function
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
678
|
+
function getSubtree(graph, nodeId) {
|
|
679
|
+
const startNode = graph.nodes.get(nodeId);
|
|
680
|
+
if (!startNode) return [];
|
|
681
|
+
const result = [];
|
|
682
|
+
const queue = [...startNode.children];
|
|
683
|
+
while (queue.length > 0) {
|
|
684
|
+
const currentId = queue.shift();
|
|
685
|
+
if (currentId === void 0) break;
|
|
686
|
+
const current = graph.nodes.get(currentId);
|
|
687
|
+
if (!current) continue;
|
|
688
|
+
result.push(current);
|
|
689
|
+
queue.push(...current.children);
|
|
746
690
|
}
|
|
747
|
-
|
|
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.`;
|
|
691
|
+
return result;
|
|
774
692
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
function
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
693
|
+
function getDuration(graph) {
|
|
694
|
+
const end = graph.endTime ?? Date.now();
|
|
695
|
+
return end - graph.startTime;
|
|
696
|
+
}
|
|
697
|
+
function getDepth(graph) {
|
|
698
|
+
const root = graph.nodes.get(graph.rootNodeId);
|
|
699
|
+
if (!root) return -1;
|
|
700
|
+
function dfs(node, depth) {
|
|
701
|
+
if (node.children.length === 0) return depth;
|
|
702
|
+
let maxDepth = depth;
|
|
703
|
+
for (const childId of node.children) {
|
|
704
|
+
const child = graph.nodes.get(childId);
|
|
705
|
+
if (!child) continue;
|
|
706
|
+
const childDepth = dfs(child, depth + 1);
|
|
707
|
+
if (childDepth > maxDepth) maxDepth = childDepth;
|
|
708
|
+
}
|
|
709
|
+
return maxDepth;
|
|
784
710
|
}
|
|
785
|
-
return (
|
|
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
|
-
|
|
940
|
-
// src/graph-query.ts
|
|
941
|
-
function getNode(graph, nodeId) {
|
|
942
|
-
return graph.nodes.get(nodeId);
|
|
943
|
-
}
|
|
944
|
-
function getChildren(graph, nodeId) {
|
|
945
|
-
const node = graph.nodes.get(nodeId);
|
|
946
|
-
if (!node) return [];
|
|
947
|
-
const result = [];
|
|
948
|
-
for (const childId of node.children) {
|
|
949
|
-
const child = graph.nodes.get(childId);
|
|
950
|
-
if (child) result.push(child);
|
|
951
|
-
}
|
|
952
|
-
return result;
|
|
953
|
-
}
|
|
954
|
-
function getParent(graph, nodeId) {
|
|
955
|
-
const node = graph.nodes.get(nodeId);
|
|
956
|
-
if (!node || node.parentId === null) return void 0;
|
|
957
|
-
return graph.nodes.get(node.parentId);
|
|
958
|
-
}
|
|
959
|
-
function getFailures(graph) {
|
|
960
|
-
const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
|
|
961
|
-
return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
|
|
962
|
-
}
|
|
963
|
-
function getHungNodes(graph) {
|
|
964
|
-
return [...graph.nodes.values()].filter(
|
|
965
|
-
(node) => node.status === "running" && node.endTime === null
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
function getCriticalPath(graph) {
|
|
969
|
-
const root = graph.nodes.get(graph.rootNodeId);
|
|
970
|
-
if (!root) return [];
|
|
971
|
-
function nodeDuration2(node) {
|
|
972
|
-
const end = node.endTime ?? Date.now();
|
|
973
|
-
return end - node.startTime;
|
|
974
|
-
}
|
|
975
|
-
function dfs(node) {
|
|
976
|
-
if (node.children.length === 0) {
|
|
977
|
-
return { duration: nodeDuration2(node), path: [node] };
|
|
978
|
-
}
|
|
979
|
-
let bestChild = { duration: -1, path: [] };
|
|
980
|
-
for (const childId of node.children) {
|
|
981
|
-
const child = graph.nodes.get(childId);
|
|
982
|
-
if (!child) continue;
|
|
983
|
-
const result = dfs(child);
|
|
984
|
-
if (result.duration > bestChild.duration) {
|
|
985
|
-
bestChild = result;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return {
|
|
989
|
-
duration: nodeDuration2(node) + bestChild.duration,
|
|
990
|
-
path: [node, ...bestChild.path]
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
return dfs(root).path;
|
|
994
|
-
}
|
|
995
|
-
function findWaitingOn(graph, nodeId) {
|
|
996
|
-
const results = [];
|
|
997
|
-
for (const edge of graph.edges) {
|
|
998
|
-
if (edge.from === nodeId && edge.type === "waited_on") {
|
|
999
|
-
const node = graph.nodes.get(edge.to);
|
|
1000
|
-
if (node) results.push(node);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return results;
|
|
1004
|
-
}
|
|
1005
|
-
function getSubtree(graph, nodeId) {
|
|
1006
|
-
const startNode = graph.nodes.get(nodeId);
|
|
1007
|
-
if (!startNode) return [];
|
|
1008
|
-
const result = [];
|
|
1009
|
-
const queue = [...startNode.children];
|
|
1010
|
-
while (queue.length > 0) {
|
|
1011
|
-
const currentId = queue.shift();
|
|
1012
|
-
if (currentId === void 0) break;
|
|
1013
|
-
const current = graph.nodes.get(currentId);
|
|
1014
|
-
if (!current) continue;
|
|
1015
|
-
result.push(current);
|
|
1016
|
-
queue.push(...current.children);
|
|
1017
|
-
}
|
|
1018
|
-
return result;
|
|
1019
|
-
}
|
|
1020
|
-
function getDuration(graph) {
|
|
1021
|
-
const end = graph.endTime ?? Date.now();
|
|
1022
|
-
return end - graph.startTime;
|
|
1023
|
-
}
|
|
1024
|
-
function getDepth(graph) {
|
|
1025
|
-
const root = graph.nodes.get(graph.rootNodeId);
|
|
1026
|
-
if (!root) return -1;
|
|
1027
|
-
function dfs(node, depth) {
|
|
1028
|
-
if (node.children.length === 0) return depth;
|
|
1029
|
-
let maxDepth = depth;
|
|
1030
|
-
for (const childId of node.children) {
|
|
1031
|
-
const child = graph.nodes.get(childId);
|
|
1032
|
-
if (!child) continue;
|
|
1033
|
-
const childDepth = dfs(child, depth + 1);
|
|
1034
|
-
if (childDepth > maxDepth) maxDepth = childDepth;
|
|
1035
|
-
}
|
|
1036
|
-
return maxDepth;
|
|
1037
|
-
}
|
|
1038
|
-
return dfs(root, 0);
|
|
711
|
+
return dfs(root, 0);
|
|
1039
712
|
}
|
|
1040
713
|
function getStats(graph) {
|
|
1041
714
|
const byStatus = {
|
|
@@ -1141,6 +814,10 @@ function getTraceTree(trace) {
|
|
|
1141
814
|
}
|
|
1142
815
|
|
|
1143
816
|
// src/guards.ts
|
|
817
|
+
function explainMessage(explanation) {
|
|
818
|
+
const sourceLabel = explanation.source === "static" ? "static config" : explanation.source === "soma-policy" ? `soma-policy${explanation.evidence ? ` (${explanation.evidence})` : ""}` : explanation.source === "assertion" ? "assertion" : "adaptive";
|
|
819
|
+
return `This run was stopped because ${explanation.rule} reached ${explanation.actual}, which exceeds the limit of ${explanation.threshold}. Source: ${sourceLabel}.`;
|
|
820
|
+
}
|
|
1144
821
|
var DEFAULT_TIMEOUTS = {
|
|
1145
822
|
tool: 3e4,
|
|
1146
823
|
// 30s
|
|
@@ -1167,22 +844,36 @@ function checkGuards(graph, config) {
|
|
|
1167
844
|
const timeoutThreshold = timeouts[node.type];
|
|
1168
845
|
const elapsed = now - node.startTime;
|
|
1169
846
|
if (elapsed > timeoutThreshold) {
|
|
847
|
+
const explanation = {
|
|
848
|
+
rule: "timeout",
|
|
849
|
+
threshold: timeoutThreshold,
|
|
850
|
+
actual: elapsed,
|
|
851
|
+
source: "static"
|
|
852
|
+
};
|
|
1170
853
|
violations.push({
|
|
1171
854
|
type: "timeout",
|
|
1172
855
|
nodeId: node.id,
|
|
1173
|
-
message:
|
|
1174
|
-
timestamp: now
|
|
856
|
+
message: explainMessage(explanation),
|
|
857
|
+
timestamp: now,
|
|
858
|
+
explanation
|
|
1175
859
|
});
|
|
1176
860
|
}
|
|
1177
861
|
}
|
|
1178
862
|
}
|
|
1179
863
|
const depth = getDepth(graph);
|
|
1180
864
|
if (depth > maxDepth) {
|
|
865
|
+
const explanation = {
|
|
866
|
+
rule: "max-depth",
|
|
867
|
+
threshold: maxDepth,
|
|
868
|
+
actual: depth,
|
|
869
|
+
source: "static"
|
|
870
|
+
};
|
|
1181
871
|
violations.push({
|
|
1182
872
|
type: "spawn-explosion",
|
|
1183
873
|
nodeId: graph.rootNodeId,
|
|
1184
|
-
message:
|
|
1185
|
-
timestamp: now
|
|
874
|
+
message: explainMessage(explanation),
|
|
875
|
+
timestamp: now,
|
|
876
|
+
explanation
|
|
1186
877
|
});
|
|
1187
878
|
}
|
|
1188
879
|
let agentCount = 0;
|
|
@@ -1192,16 +883,25 @@ function checkGuards(graph, config) {
|
|
|
1192
883
|
}
|
|
1193
884
|
}
|
|
1194
885
|
if (agentCount > maxAgentSpawns) {
|
|
886
|
+
const explanation = {
|
|
887
|
+
rule: "max-agent-spawns",
|
|
888
|
+
threshold: maxAgentSpawns,
|
|
889
|
+
actual: agentCount,
|
|
890
|
+
source: "static"
|
|
891
|
+
};
|
|
1195
892
|
violations.push({
|
|
1196
893
|
type: "spawn-explosion",
|
|
1197
894
|
nodeId: graph.rootNodeId,
|
|
1198
|
-
message:
|
|
1199
|
-
timestamp: now
|
|
895
|
+
message: explainMessage(explanation),
|
|
896
|
+
timestamp: now,
|
|
897
|
+
explanation
|
|
1200
898
|
});
|
|
1201
899
|
}
|
|
1202
900
|
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
1203
901
|
if (config?.policySource) {
|
|
1204
|
-
violations.push(
|
|
902
|
+
violations.push(
|
|
903
|
+
...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now)
|
|
904
|
+
);
|
|
1205
905
|
}
|
|
1206
906
|
return violations;
|
|
1207
907
|
}
|
|
@@ -1222,11 +922,18 @@ function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
|
1222
922
|
}
|
|
1223
923
|
if (newCount > maxSteps && !reported.has(newType)) {
|
|
1224
924
|
reported.add(newType);
|
|
925
|
+
const explanation = {
|
|
926
|
+
rule: "max-reasoning-steps",
|
|
927
|
+
threshold: maxSteps,
|
|
928
|
+
actual: newCount,
|
|
929
|
+
source: "static"
|
|
930
|
+
};
|
|
1225
931
|
violations.push({
|
|
1226
932
|
type: "reasoning-loop",
|
|
1227
933
|
nodeId: node.id,
|
|
1228
|
-
message:
|
|
1229
|
-
timestamp
|
|
934
|
+
message: explainMessage(explanation),
|
|
935
|
+
timestamp,
|
|
936
|
+
explanation
|
|
1230
937
|
});
|
|
1231
938
|
}
|
|
1232
939
|
const children = getChildren(graph, nodeId);
|
|
@@ -1243,29 +950,50 @@ function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
|
1243
950
|
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
1244
951
|
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
1245
952
|
if (failureRate > maxFailureRate) {
|
|
953
|
+
const explanation = {
|
|
954
|
+
rule: "max-failure-rate",
|
|
955
|
+
threshold: maxFailureRate,
|
|
956
|
+
actual: failureRate,
|
|
957
|
+
source: "soma-policy"
|
|
958
|
+
};
|
|
1246
959
|
violations.push({
|
|
1247
960
|
type: "high-failure-rate",
|
|
1248
961
|
nodeId: graph.rootNodeId,
|
|
1249
|
-
message:
|
|
1250
|
-
timestamp
|
|
962
|
+
message: explainMessage(explanation),
|
|
963
|
+
timestamp,
|
|
964
|
+
explanation
|
|
1251
965
|
});
|
|
1252
966
|
}
|
|
1253
967
|
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
1254
968
|
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
969
|
+
const explanation = {
|
|
970
|
+
rule: "min-conformance",
|
|
971
|
+
threshold: minConformance,
|
|
972
|
+
actual: conformanceScore,
|
|
973
|
+
source: "soma-policy"
|
|
974
|
+
};
|
|
1255
975
|
violations.push({
|
|
1256
976
|
type: "conformance-drift",
|
|
1257
977
|
nodeId: graph.rootNodeId,
|
|
1258
|
-
message:
|
|
1259
|
-
timestamp
|
|
978
|
+
message: explainMessage(explanation),
|
|
979
|
+
timestamp,
|
|
980
|
+
explanation
|
|
1260
981
|
});
|
|
1261
982
|
}
|
|
1262
983
|
for (const node of graph.nodes.values()) {
|
|
1263
984
|
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
985
|
+
const explanation = {
|
|
986
|
+
rule: "known-bottleneck",
|
|
987
|
+
threshold: "flagged",
|
|
988
|
+
actual: "running",
|
|
989
|
+
source: "soma-policy"
|
|
990
|
+
};
|
|
1264
991
|
violations.push({
|
|
1265
992
|
type: "known-bottleneck",
|
|
1266
993
|
nodeId: node.id,
|
|
1267
|
-
message:
|
|
1268
|
-
timestamp
|
|
994
|
+
message: explainMessage(explanation),
|
|
995
|
+
timestamp,
|
|
996
|
+
explanation
|
|
1269
997
|
});
|
|
1270
998
|
}
|
|
1271
999
|
}
|
|
@@ -1315,22 +1043,352 @@ function withGuards(builder, config) {
|
|
|
1315
1043
|
const violations = checkGuards(snapshot, config);
|
|
1316
1044
|
handleViolations(violations);
|
|
1317
1045
|
},
|
|
1318
|
-
failNode: (nodeId, error) => {
|
|
1319
|
-
builder.failNode(nodeId, error);
|
|
1320
|
-
const snapshot = builder.getSnapshot();
|
|
1321
|
-
const violations = checkGuards(snapshot, config);
|
|
1322
|
-
handleViolations(violations);
|
|
1046
|
+
failNode: (nodeId, error) => {
|
|
1047
|
+
builder.failNode(nodeId, error);
|
|
1048
|
+
const snapshot = builder.getSnapshot();
|
|
1049
|
+
const violations = checkGuards(snapshot, config);
|
|
1050
|
+
handleViolations(violations);
|
|
1051
|
+
},
|
|
1052
|
+
addEdge: (from, to, type) => builder.addEdge(from, to, type),
|
|
1053
|
+
pushEvent: (event) => builder.pushEvent(event),
|
|
1054
|
+
updateState: (nodeId, state) => builder.updateState(nodeId, state),
|
|
1055
|
+
withParent: (parentId, fn) => builder.withParent(parentId, fn),
|
|
1056
|
+
getSnapshot: () => builder.getSnapshot(),
|
|
1057
|
+
build: () => {
|
|
1058
|
+
const snapshot = builder.getSnapshot();
|
|
1059
|
+
const violations = checkGuards(snapshot, config);
|
|
1060
|
+
handleViolations(violations);
|
|
1061
|
+
return builder.build();
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/prompt-builder.ts
|
|
1067
|
+
var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
|
|
1068
|
+
function fmtDuration(ms) {
|
|
1069
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
1070
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
1071
|
+
}
|
|
1072
|
+
function fmtTime(ts) {
|
|
1073
|
+
return new Date(ts).toISOString();
|
|
1074
|
+
}
|
|
1075
|
+
function durationStats(durations) {
|
|
1076
|
+
if (durations.length === 0) return { avg: 0, p50: 0, p95: 0, min: 0, max: 0 };
|
|
1077
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
1078
|
+
const sum = sorted.reduce((a, b) => a + b, 0);
|
|
1079
|
+
return {
|
|
1080
|
+
avg: Math.round(sum / sorted.length),
|
|
1081
|
+
p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
|
|
1082
|
+
p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
|
|
1083
|
+
min: sorted[0] ?? 0,
|
|
1084
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function buildFailureAnalysisPrompt(events, profile) {
|
|
1088
|
+
const stats = durationStats(profile.recentDurations);
|
|
1089
|
+
const failureDetails = events.map((e, i) => {
|
|
1090
|
+
const lines = [
|
|
1091
|
+
`Failure ${i + 1}:`,
|
|
1092
|
+
` Time: ${fmtTime(e.timestamp)}`,
|
|
1093
|
+
` Duration: ${fmtDuration(e.duration)}`,
|
|
1094
|
+
` Path: ${e.pathSignature}`
|
|
1095
|
+
];
|
|
1096
|
+
if (e.failurePoint) {
|
|
1097
|
+
lines.push(` Failed at: ${e.failurePoint.nodeName} (${e.failurePoint.nodeType})`);
|
|
1098
|
+
if (e.failurePoint.error) lines.push(` Error: ${e.failurePoint.error}`);
|
|
1099
|
+
}
|
|
1100
|
+
if (e.violations.length > 0) {
|
|
1101
|
+
lines.push(` Violations: ${e.violations.map((v) => v.message).join("; ")}`);
|
|
1102
|
+
}
|
|
1103
|
+
return lines.join("\n");
|
|
1104
|
+
}).join("\n\n");
|
|
1105
|
+
return `${ROLE}
|
|
1106
|
+
|
|
1107
|
+
## Agent Profile
|
|
1108
|
+
- Agent: ${profile.agentId}
|
|
1109
|
+
- Total runs: ${profile.totalRuns}
|
|
1110
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}% (${profile.failureCount} failures / ${profile.totalRuns} total)
|
|
1111
|
+
- Avg duration: ${fmtDuration(stats.avg)} (p50: ${fmtDuration(stats.p50)}, p95: ${fmtDuration(stats.p95)})
|
|
1112
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1113
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
1114
|
+
|
|
1115
|
+
## Recent Failures (${events.length})
|
|
1116
|
+
|
|
1117
|
+
${failureDetails}
|
|
1118
|
+
|
|
1119
|
+
## Question
|
|
1120
|
+
Analyze these failures. What patterns do you see? What is the most likely root cause? Are these related or independent failures?`;
|
|
1121
|
+
}
|
|
1122
|
+
function buildAnomalyExplanationPrompt(event, profile) {
|
|
1123
|
+
const stats = durationStats(profile.recentDurations);
|
|
1124
|
+
const eventDetails = [
|
|
1125
|
+
`Time: ${fmtTime(event.timestamp)}`,
|
|
1126
|
+
`Status: ${event.status}`,
|
|
1127
|
+
`Duration: ${fmtDuration(event.duration)}`,
|
|
1128
|
+
`Path: ${event.pathSignature}`,
|
|
1129
|
+
`Node count: ${event.nodeCount}`
|
|
1130
|
+
];
|
|
1131
|
+
if (event.processContext) {
|
|
1132
|
+
eventDetails.push(`Conformance score: ${event.processContext.conformanceScore}`);
|
|
1133
|
+
eventDetails.push(`Is anomaly: ${event.processContext.isAnomaly}`);
|
|
1134
|
+
eventDetails.push(`Variant: ${event.processContext.variant}`);
|
|
1135
|
+
}
|
|
1136
|
+
if (event.failurePoint) {
|
|
1137
|
+
eventDetails.push(`Failed at: ${event.failurePoint.nodeName} (${event.failurePoint.nodeType})`);
|
|
1138
|
+
if (event.failurePoint.error) eventDetails.push(`Error: ${event.failurePoint.error}`);
|
|
1139
|
+
}
|
|
1140
|
+
if (event.violations.length > 0) {
|
|
1141
|
+
eventDetails.push(`Violations: ${event.violations.map((v) => v.message).join("; ")}`);
|
|
1142
|
+
}
|
|
1143
|
+
return `${ROLE}
|
|
1144
|
+
|
|
1145
|
+
## Agent Baseline (from profile)
|
|
1146
|
+
- Agent: ${profile.agentId}
|
|
1147
|
+
- Total runs: ${profile.totalRuns}
|
|
1148
|
+
- Typical failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
1149
|
+
- Typical duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}
|
|
1150
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
1151
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1152
|
+
|
|
1153
|
+
## Anomalous Execution
|
|
1154
|
+
${eventDetails.join("\n")}
|
|
1155
|
+
|
|
1156
|
+
## Question
|
|
1157
|
+
This execution has been flagged as anomalous. Explain what is unusual about it compared to the agent's typical behavior. What might have caused this deviation?`;
|
|
1158
|
+
}
|
|
1159
|
+
function buildAgentSummaryPrompt(profile, recentEvents, patterns) {
|
|
1160
|
+
const stats = durationStats(profile.recentDurations);
|
|
1161
|
+
const recentOutcomes = recentEvents.slice(0, 10).map((e) => ` ${fmtTime(e.timestamp)} \u2014 ${e.eventType} (${fmtDuration(e.duration)})`).join("\n");
|
|
1162
|
+
const patternSummary = patterns.length > 0 ? patterns.slice(0, 3).map((p) => {
|
|
1163
|
+
const lines = [
|
|
1164
|
+
` Variants: ${p.pattern.variantCount} across ${p.pattern.totalGraphs} executions`,
|
|
1165
|
+
` Top variant: ${p.pattern.topVariants[0]?.pathSignature ?? "N/A"} (${p.pattern.topVariants[0]?.percentage.toFixed(0) ?? 0}%)`
|
|
1166
|
+
];
|
|
1167
|
+
if (p.pattern.topBottlenecks.length > 0) {
|
|
1168
|
+
const topB = p.pattern.topBottlenecks[0];
|
|
1169
|
+
if (topB)
|
|
1170
|
+
lines.push(` Top bottleneck: ${topB.nodeName} (p95: ${fmtDuration(topB.p95)})`);
|
|
1171
|
+
}
|
|
1172
|
+
return lines.join("\n");
|
|
1173
|
+
}).join("\n\n") : " No patterns discovered yet.";
|
|
1174
|
+
const dataNote = recentEvents.length === 0 && patterns.length === 0 ? "\nNote: Limited data available. Summary is based only on the profile statistics.\n" : "";
|
|
1175
|
+
return `${ROLE}
|
|
1176
|
+
|
|
1177
|
+
## Agent Profile
|
|
1178
|
+
- Agent: ${profile.agentId}
|
|
1179
|
+
- Total runs: ${profile.totalRuns}
|
|
1180
|
+
- Success rate: ${((1 - profile.failureRate) * 100).toFixed(1)}% (${profile.successCount} successes, ${profile.failureCount} failures)
|
|
1181
|
+
- Duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}, range ${fmtDuration(stats.min)}\u2013${fmtDuration(stats.max)}
|
|
1182
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1183
|
+
- Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
|
|
1184
|
+
- Last pattern analysis: ${profile.lastPatternTimestamp ? fmtTime(profile.lastPatternTimestamp) : "never"}
|
|
1185
|
+
${dataNote}
|
|
1186
|
+
## Recent Executions (last ${recentEvents.slice(0, 10).length})
|
|
1187
|
+
${recentOutcomes || " No recent events."}
|
|
1188
|
+
|
|
1189
|
+
## Pattern Analysis
|
|
1190
|
+
${patternSummary}
|
|
1191
|
+
|
|
1192
|
+
## Question
|
|
1193
|
+
Provide a health summary for this agent. What are the key observations? Is the agent healthy, degrading, or in trouble? What should the operator pay attention to?`;
|
|
1194
|
+
}
|
|
1195
|
+
function buildFixSuggestionPrompt(events, profile, patterns) {
|
|
1196
|
+
const failureGroups = /* @__PURE__ */ new Map();
|
|
1197
|
+
for (const e of events) {
|
|
1198
|
+
const key = e.failurePoint?.error ?? e.pathSignature;
|
|
1199
|
+
const group = failureGroups.get(key) ?? [];
|
|
1200
|
+
group.push(e);
|
|
1201
|
+
failureGroups.set(key, group);
|
|
1202
|
+
}
|
|
1203
|
+
const failureGroupSummary = [...failureGroups.entries()].map(([key, group]) => {
|
|
1204
|
+
const latest = group[0];
|
|
1205
|
+
return ` "${key}" \u2014 ${group.length} occurrence(s), latest at ${latest ? fmtTime(latest.timestamp) : "unknown"}`;
|
|
1206
|
+
}).join("\n");
|
|
1207
|
+
const bottleneckDetails = patterns.flatMap((p) => p.pattern.topBottlenecks).map((b) => ` ${b.nodeName} (${b.nodeType}) \u2014 p95: ${fmtDuration(b.p95)}`);
|
|
1208
|
+
const uniqueBottlenecks = [...new Set(bottleneckDetails)].join("\n");
|
|
1209
|
+
const conformanceIssues = events.filter((e) => e.processContext && e.processContext.conformanceScore < 0.8).map(
|
|
1210
|
+
(e) => ` ${fmtTime(e.timestamp)}: conformance ${e.processContext?.conformanceScore}, variant "${e.processContext?.variant}"`
|
|
1211
|
+
).join("\n");
|
|
1212
|
+
return `${ROLE}
|
|
1213
|
+
|
|
1214
|
+
## Agent Profile
|
|
1215
|
+
- Agent: ${profile.agentId}
|
|
1216
|
+
- Failure rate: ${(profile.failureRate * 100).toFixed(1)}%
|
|
1217
|
+
- Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
|
|
1218
|
+
|
|
1219
|
+
## Failure Patterns (${events.length} failures)
|
|
1220
|
+
${failureGroupSummary || " No failures recorded."}
|
|
1221
|
+
|
|
1222
|
+
## Bottlenecks
|
|
1223
|
+
${uniqueBottlenecks || " No bottlenecks detected."}
|
|
1224
|
+
|
|
1225
|
+
## Conformance Issues
|
|
1226
|
+
${conformanceIssues || " No conformance issues."}
|
|
1227
|
+
|
|
1228
|
+
## Question
|
|
1229
|
+
Based on the failure patterns, bottlenecks, and conformance issues above, provide specific, actionable recommendations to improve this agent's reliability and performance. Prioritize by impact.`;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/insight-engine.ts
|
|
1233
|
+
var DEFAULT_CACHE_TTL_MS = 36e5;
|
|
1234
|
+
var SCHEMA_VERSION2 = 1;
|
|
1235
|
+
function simpleHash(input) {
|
|
1236
|
+
let hash = 0;
|
|
1237
|
+
for (let i = 0; i < input.length; i++) {
|
|
1238
|
+
const char = input.charCodeAt(i);
|
|
1239
|
+
hash = (hash << 5) - hash + char | 0;
|
|
1240
|
+
}
|
|
1241
|
+
return (hash >>> 0).toString(36);
|
|
1242
|
+
}
|
|
1243
|
+
function createInsightEngine(store, analysisFn, config) {
|
|
1244
|
+
const cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
|
|
1245
|
+
function checkCache(agentId, insightType, dataHash) {
|
|
1246
|
+
const recent = store.getRecentInsights(agentId, { type: insightType, limit: 1 });
|
|
1247
|
+
if (recent.length === 0) return null;
|
|
1248
|
+
const cached = recent[0];
|
|
1249
|
+
if (!cached || cached.dataHash !== dataHash) return null;
|
|
1250
|
+
const age = Date.now() - cached.timestamp;
|
|
1251
|
+
if (age >= cacheTtlMs) return null;
|
|
1252
|
+
return cached;
|
|
1253
|
+
}
|
|
1254
|
+
function storeAndReturn(agentId, insightType, prompt, response, dataHash) {
|
|
1255
|
+
const event = {
|
|
1256
|
+
eventType: "insight.generated",
|
|
1257
|
+
agentId,
|
|
1258
|
+
timestamp: Date.now(),
|
|
1259
|
+
schemaVersion: SCHEMA_VERSION2,
|
|
1260
|
+
insightType,
|
|
1261
|
+
prompt,
|
|
1262
|
+
response,
|
|
1263
|
+
dataHash
|
|
1264
|
+
};
|
|
1265
|
+
store.appendInsight(event);
|
|
1266
|
+
return {
|
|
1267
|
+
agentId,
|
|
1268
|
+
insightType,
|
|
1269
|
+
content: response,
|
|
1270
|
+
cached: false,
|
|
1271
|
+
timestamp: event.timestamp
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function shortCircuit(agentId, insightType, content, cached, timestamp) {
|
|
1275
|
+
return { agentId, insightType, content, cached, timestamp: timestamp ?? Date.now() };
|
|
1276
|
+
}
|
|
1277
|
+
return {
|
|
1278
|
+
async explainFailures(agentId) {
|
|
1279
|
+
const profile = store.getAgentProfile(agentId);
|
|
1280
|
+
if (!profile) {
|
|
1281
|
+
return shortCircuit(
|
|
1282
|
+
agentId,
|
|
1283
|
+
"failure-analysis",
|
|
1284
|
+
"No data available for this agent.",
|
|
1285
|
+
false
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
1289
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
1290
|
+
if (failures.length === 0) {
|
|
1291
|
+
return shortCircuit(
|
|
1292
|
+
agentId,
|
|
1293
|
+
"failure-analysis",
|
|
1294
|
+
"No recent failures found for this agent.",
|
|
1295
|
+
false
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile }));
|
|
1299
|
+
const cached = checkCache(agentId, "failure-analysis", dataHash);
|
|
1300
|
+
if (cached) {
|
|
1301
|
+
return shortCircuit(agentId, "failure-analysis", cached.response, true, cached.timestamp);
|
|
1302
|
+
}
|
|
1303
|
+
const prompt = buildFailureAnalysisPrompt(failures, profile);
|
|
1304
|
+
try {
|
|
1305
|
+
const response = await analysisFn(prompt);
|
|
1306
|
+
return storeAndReturn(agentId, "failure-analysis", prompt, response, dataHash);
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1309
|
+
return shortCircuit(agentId, "failure-analysis", `Analysis failed: ${message}`, false);
|
|
1310
|
+
}
|
|
1311
|
+
},
|
|
1312
|
+
async explainAnomaly(agentId, event) {
|
|
1313
|
+
const profile = store.getAgentProfile(agentId);
|
|
1314
|
+
if (!profile) {
|
|
1315
|
+
return shortCircuit(
|
|
1316
|
+
agentId,
|
|
1317
|
+
"anomaly-explanation",
|
|
1318
|
+
"No data available for this agent.",
|
|
1319
|
+
false
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
const dataHash = simpleHash(JSON.stringify({ event, profile }));
|
|
1323
|
+
const cached = checkCache(agentId, "anomaly-explanation", dataHash);
|
|
1324
|
+
if (cached) {
|
|
1325
|
+
return shortCircuit(
|
|
1326
|
+
agentId,
|
|
1327
|
+
"anomaly-explanation",
|
|
1328
|
+
cached.response,
|
|
1329
|
+
true,
|
|
1330
|
+
cached.timestamp
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
const prompt = buildAnomalyExplanationPrompt(event, profile);
|
|
1334
|
+
try {
|
|
1335
|
+
const response = await analysisFn(prompt);
|
|
1336
|
+
return storeAndReturn(agentId, "anomaly-explanation", prompt, response, dataHash);
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1339
|
+
return shortCircuit(agentId, "anomaly-explanation", `Analysis failed: ${message}`, false);
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
async summarizeAgent(agentId) {
|
|
1343
|
+
const profile = store.getAgentProfile(agentId);
|
|
1344
|
+
if (!profile) {
|
|
1345
|
+
return shortCircuit(agentId, "agent-summary", "No data available for this agent.", false);
|
|
1346
|
+
}
|
|
1347
|
+
const recentEvents = store.getRecentEvents(agentId, { limit: 20 });
|
|
1348
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
1349
|
+
const dataHash = simpleHash(JSON.stringify({ profile, recentEvents, patterns }));
|
|
1350
|
+
const cached = checkCache(agentId, "agent-summary", dataHash);
|
|
1351
|
+
if (cached) {
|
|
1352
|
+
return shortCircuit(agentId, "agent-summary", cached.response, true, cached.timestamp);
|
|
1353
|
+
}
|
|
1354
|
+
const prompt = buildAgentSummaryPrompt(profile, recentEvents, patterns);
|
|
1355
|
+
try {
|
|
1356
|
+
const response = await analysisFn(prompt);
|
|
1357
|
+
return storeAndReturn(agentId, "agent-summary", prompt, response, dataHash);
|
|
1358
|
+
} catch (err) {
|
|
1359
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1360
|
+
return shortCircuit(agentId, "agent-summary", `Analysis failed: ${message}`, false);
|
|
1361
|
+
}
|
|
1323
1362
|
},
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
const
|
|
1331
|
-
const
|
|
1332
|
-
|
|
1333
|
-
|
|
1363
|
+
async suggestFixes(agentId) {
|
|
1364
|
+
const profile = store.getAgentProfile(agentId);
|
|
1365
|
+
if (!profile) {
|
|
1366
|
+
return shortCircuit(agentId, "fix-suggestion", "No data available for this agent.", false);
|
|
1367
|
+
}
|
|
1368
|
+
const events = store.getRecentEvents(agentId, { limit: 50 });
|
|
1369
|
+
const failures = events.filter((e) => e.eventType === "execution.failed");
|
|
1370
|
+
const patterns = store.getPatternHistory(agentId, { limit: 5 });
|
|
1371
|
+
if (failures.length === 0 && profile.knownBottlenecks.length === 0) {
|
|
1372
|
+
return shortCircuit(
|
|
1373
|
+
agentId,
|
|
1374
|
+
"fix-suggestion",
|
|
1375
|
+
"Agent is healthy \u2014 no failures or bottlenecks detected.",
|
|
1376
|
+
false
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
const dataHash = simpleHash(JSON.stringify({ failures, profile, patterns }));
|
|
1380
|
+
const cached = checkCache(agentId, "fix-suggestion", dataHash);
|
|
1381
|
+
if (cached) {
|
|
1382
|
+
return shortCircuit(agentId, "fix-suggestion", cached.response, true, cached.timestamp);
|
|
1383
|
+
}
|
|
1384
|
+
const prompt = buildFixSuggestionPrompt(failures, profile, patterns);
|
|
1385
|
+
try {
|
|
1386
|
+
const response = await analysisFn(prompt);
|
|
1387
|
+
return storeAndReturn(agentId, "fix-suggestion", prompt, response, dataHash);
|
|
1388
|
+
} catch (err) {
|
|
1389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1390
|
+
return shortCircuit(agentId, "fix-suggestion", `Analysis failed: ${message}`, false);
|
|
1391
|
+
}
|
|
1334
1392
|
}
|
|
1335
1393
|
};
|
|
1336
1394
|
}
|
|
@@ -1440,7 +1498,8 @@ function createKnowledgeStore(config) {
|
|
|
1440
1498
|
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
1441
1499
|
}
|
|
1442
1500
|
function profilePath(agentId) {
|
|
1443
|
-
|
|
1501
|
+
const safe = agentId.replace(/[/\\]/g, "_").replace(/\.\./g, "_");
|
|
1502
|
+
return (0, import_node_path2.join)(profilesDir, `${safe}.json`);
|
|
1444
1503
|
}
|
|
1445
1504
|
function appendExecutionEvent(event) {
|
|
1446
1505
|
const dateDir = (0, import_node_path2.join)(eventsDir, event.agentId, toDateDir(event.timestamp));
|
|
@@ -1565,7 +1624,7 @@ function createKnowledgeStore(config) {
|
|
|
1565
1624
|
continue;
|
|
1566
1625
|
}
|
|
1567
1626
|
for (const file of files) {
|
|
1568
|
-
const ts = Number.parseInt(file.split("-")[0], 10);
|
|
1627
|
+
const ts = Number.parseInt(file.split("-")[0] ?? "", 10);
|
|
1569
1628
|
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1570
1629
|
try {
|
|
1571
1630
|
(0, import_node_fs2.rmSync)((0, import_node_path2.join)(agentPatternDir, file));
|
|
@@ -1625,220 +1684,18 @@ function createKnowledgeStore(config) {
|
|
|
1625
1684
|
events.sort((a, b) => b.timestamp - a.timestamp);
|
|
1626
1685
|
return events.slice(0, limit);
|
|
1627
1686
|
},
|
|
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 {
|
|
1687
|
+
// EventWriter interface
|
|
1828
1688
|
async write(_graph) {
|
|
1829
1689
|
},
|
|
1830
1690
|
async writeEvent(event) {
|
|
1831
|
-
|
|
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");
|
|
1691
|
+
this.append(event);
|
|
1835
1692
|
}
|
|
1836
1693
|
};
|
|
1837
1694
|
}
|
|
1838
1695
|
|
|
1839
1696
|
// src/live.ts
|
|
1840
|
-
var
|
|
1841
|
-
var
|
|
1697
|
+
var import_node_fs4 = require("fs");
|
|
1698
|
+
var import_node_path4 = require("path");
|
|
1842
1699
|
|
|
1843
1700
|
// src/loader.ts
|
|
1844
1701
|
function toNodesMap(raw) {
|
|
@@ -1867,7 +1724,8 @@ function loadGraph(input) {
|
|
|
1867
1724
|
events: raw.events ?? [],
|
|
1868
1725
|
traceId: raw.traceId,
|
|
1869
1726
|
spanId: raw.spanId,
|
|
1870
|
-
parentSpanId: raw.parentSpanId
|
|
1727
|
+
parentSpanId: raw.parentSpanId,
|
|
1728
|
+
metadata: raw.metadata ?? {}
|
|
1871
1729
|
};
|
|
1872
1730
|
}
|
|
1873
1731
|
function graphToJson(graph) {
|
|
@@ -1894,8 +1752,8 @@ function graphToJson(graph) {
|
|
|
1894
1752
|
|
|
1895
1753
|
// src/process-audit.ts
|
|
1896
1754
|
var import_node_child_process = require("child_process");
|
|
1897
|
-
var
|
|
1898
|
-
var
|
|
1755
|
+
var import_node_fs3 = require("fs");
|
|
1756
|
+
var import_node_path3 = require("path");
|
|
1899
1757
|
function isPidAlive(pid) {
|
|
1900
1758
|
try {
|
|
1901
1759
|
process.kill(pid, 0);
|
|
@@ -1906,7 +1764,7 @@ function isPidAlive(pid) {
|
|
|
1906
1764
|
}
|
|
1907
1765
|
function pidMatchesName(pid, name) {
|
|
1908
1766
|
try {
|
|
1909
|
-
const cmdline = (0,
|
|
1767
|
+
const cmdline = (0, import_node_fs3.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
|
|
1910
1768
|
return cmdline.includes(name);
|
|
1911
1769
|
} catch {
|
|
1912
1770
|
return false;
|
|
@@ -1914,7 +1772,7 @@ function pidMatchesName(pid, name) {
|
|
|
1914
1772
|
}
|
|
1915
1773
|
function readPidFile(path) {
|
|
1916
1774
|
try {
|
|
1917
|
-
const pid = parseInt((0,
|
|
1775
|
+
const pid = parseInt((0, import_node_fs3.readFileSync)(path, "utf8").trim(), 10);
|
|
1918
1776
|
return Number.isNaN(pid) ? null : pid;
|
|
1919
1777
|
} catch {
|
|
1920
1778
|
return null;
|
|
@@ -1929,8 +1787,8 @@ function auditPidFile(config) {
|
|
|
1929
1787
|
pid: null,
|
|
1930
1788
|
alive: false,
|
|
1931
1789
|
matchesProcess: false,
|
|
1932
|
-
stale: !(0,
|
|
1933
|
-
reason: (0,
|
|
1790
|
+
stale: !(0, import_node_fs3.existsSync)(config.pidFile),
|
|
1791
|
+
reason: (0, import_node_fs3.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
|
|
1934
1792
|
};
|
|
1935
1793
|
}
|
|
1936
1794
|
const alive = isPidAlive(pid);
|
|
@@ -1950,8 +1808,15 @@ function auditSystemd(config) {
|
|
|
1950
1808
|
if (config.systemdUnit === null || config.systemdUnit === void 0) return null;
|
|
1951
1809
|
const unit = config.systemdUnit;
|
|
1952
1810
|
try {
|
|
1953
|
-
const raw = (0, import_node_child_process.
|
|
1954
|
-
|
|
1811
|
+
const raw = (0, import_node_child_process.execFileSync)(
|
|
1812
|
+
"systemctl",
|
|
1813
|
+
[
|
|
1814
|
+
"--user",
|
|
1815
|
+
"show",
|
|
1816
|
+
unit,
|
|
1817
|
+
"--property=ActiveState,SubState,MainPID,NRestarts,Result",
|
|
1818
|
+
"--no-pager"
|
|
1819
|
+
],
|
|
1955
1820
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1956
1821
|
);
|
|
1957
1822
|
const props = {};
|
|
@@ -1979,9 +1844,9 @@ function auditSystemd(config) {
|
|
|
1979
1844
|
}
|
|
1980
1845
|
}
|
|
1981
1846
|
function auditWorkers(config) {
|
|
1982
|
-
if (!config.workersFile || !(0,
|
|
1847
|
+
if (!config.workersFile || !(0, import_node_fs3.existsSync)(config.workersFile)) return null;
|
|
1983
1848
|
try {
|
|
1984
|
-
const data = JSON.parse((0,
|
|
1849
|
+
const data = JSON.parse((0, import_node_fs3.readFileSync)(config.workersFile, "utf8"));
|
|
1985
1850
|
const orchPid = data.pid ?? null;
|
|
1986
1851
|
const orchAlive = orchPid ? isPidAlive(orchPid) : false;
|
|
1987
1852
|
const workers = [];
|
|
@@ -2009,7 +1874,7 @@ function auditWorkers(config) {
|
|
|
2009
1874
|
}
|
|
2010
1875
|
function readCmdline(pid) {
|
|
2011
1876
|
try {
|
|
2012
|
-
return (0,
|
|
1877
|
+
return (0, import_node_fs3.readFileSync)(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim();
|
|
2013
1878
|
} catch {
|
|
2014
1879
|
return "";
|
|
2015
1880
|
}
|
|
@@ -2042,53 +1907,83 @@ function getOsProcesses(processName) {
|
|
|
2042
1907
|
}
|
|
2043
1908
|
}
|
|
2044
1909
|
function discoverProcessConfig(dirs) {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
1910
|
+
const configs = discoverAllProcessConfigs(dirs);
|
|
1911
|
+
return configs.length > 0 ? configs[0] ?? null : null;
|
|
1912
|
+
}
|
|
1913
|
+
function discoverAllProcessConfigs(dirs) {
|
|
1914
|
+
const configs = /* @__PURE__ */ new Map();
|
|
2048
1915
|
for (const dir of dirs) {
|
|
2049
|
-
if (!(0,
|
|
1916
|
+
if (!(0, import_node_fs3.existsSync)(dir)) continue;
|
|
2050
1917
|
let entries;
|
|
2051
1918
|
try {
|
|
2052
|
-
entries = (0,
|
|
1919
|
+
entries = (0, import_node_fs3.readdirSync)(dir);
|
|
2053
1920
|
} catch {
|
|
2054
1921
|
continue;
|
|
2055
1922
|
}
|
|
2056
1923
|
for (const f of entries) {
|
|
2057
|
-
const fp = (0,
|
|
1924
|
+
const fp = (0, import_node_path3.join)(dir, f);
|
|
2058
1925
|
try {
|
|
2059
|
-
if (!(0,
|
|
1926
|
+
if (!(0, import_node_fs3.statSync)(fp).isFile()) continue;
|
|
2060
1927
|
} catch {
|
|
2061
1928
|
continue;
|
|
2062
1929
|
}
|
|
2063
|
-
if (f.endsWith(".pid")
|
|
2064
|
-
|
|
2065
|
-
if (!
|
|
2066
|
-
|
|
1930
|
+
if (f.endsWith(".pid")) {
|
|
1931
|
+
const name = (0, import_node_path3.basename)(f, ".pid");
|
|
1932
|
+
if (!configs.has(name)) {
|
|
1933
|
+
configs.set(name, { processName: name });
|
|
2067
1934
|
}
|
|
1935
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
1936
|
+
if (!cfg.pidFile) cfg.pidFile = fp;
|
|
2068
1937
|
}
|
|
2069
|
-
if (
|
|
2070
|
-
|
|
2071
|
-
if (
|
|
2072
|
-
|
|
1938
|
+
if (f === "workers.json" || f.endsWith("-workers.json")) {
|
|
1939
|
+
const name = f === "workers.json" ? "" : (0, import_node_path3.basename)(f, "-workers.json");
|
|
1940
|
+
if (name && !configs.has(name)) {
|
|
1941
|
+
configs.set(name, { processName: name });
|
|
1942
|
+
}
|
|
1943
|
+
if (name) {
|
|
1944
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
1945
|
+
if (!cfg.workersFile) cfg.workersFile = fp;
|
|
2073
1946
|
}
|
|
2074
1947
|
}
|
|
2075
1948
|
}
|
|
2076
1949
|
}
|
|
2077
|
-
if (!processName && !pidFile && !workersFile) return null;
|
|
2078
|
-
if (!processName) processName = "agent";
|
|
2079
|
-
let systemdUnit;
|
|
2080
1950
|
try {
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
{ encoding: "utf8", timeout: 3e3 }
|
|
1951
|
+
const raw = (0, import_node_child_process.execSync)(
|
|
1952
|
+
"systemctl --user list-units --type=service --all --no-legend --no-pager 2>/dev/null",
|
|
1953
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
2085
1954
|
);
|
|
2086
|
-
|
|
2087
|
-
|
|
1955
|
+
for (const line of raw.trim().split("\n")) {
|
|
1956
|
+
if (!line.trim()) continue;
|
|
1957
|
+
const parts = line.trim().split(/\s+/);
|
|
1958
|
+
const unitName = parts[0] ?? "";
|
|
1959
|
+
const loadState = parts[1] ?? "";
|
|
1960
|
+
if (!unitName.endsWith(".service") || loadState !== "loaded") continue;
|
|
1961
|
+
const name = unitName.replace(".service", "");
|
|
1962
|
+
if (/^(dbus|gpg-agent|dirmngr|keyboxd|snapd\.|pk-|launchpadlib-)/.test(name)) continue;
|
|
1963
|
+
if (!configs.has(name)) {
|
|
1964
|
+
configs.set(name, { processName: name });
|
|
1965
|
+
}
|
|
1966
|
+
const cfg = configs.get(name) ?? { processName: name };
|
|
1967
|
+
cfg.systemdUnit = unitName;
|
|
2088
1968
|
}
|
|
2089
1969
|
} catch {
|
|
2090
1970
|
}
|
|
2091
|
-
|
|
1971
|
+
for (const cfg of configs.values()) {
|
|
1972
|
+
if (cfg.systemdUnit !== void 0) continue;
|
|
1973
|
+
try {
|
|
1974
|
+
const unitName = `${cfg.processName}.service`;
|
|
1975
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
1976
|
+
"systemctl",
|
|
1977
|
+
["--user", "show", unitName, "--property=LoadState", "--no-pager"],
|
|
1978
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
1979
|
+
);
|
|
1980
|
+
if (result.includes("LoadState=loaded")) {
|
|
1981
|
+
cfg.systemdUnit = unitName;
|
|
1982
|
+
}
|
|
1983
|
+
} catch {
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return [...configs.values()];
|
|
2092
1987
|
}
|
|
2093
1988
|
function auditProcesses(config) {
|
|
2094
1989
|
const pidFile = auditPidFile(config);
|
|
@@ -2107,7 +2002,7 @@ function auditProcesses(config) {
|
|
|
2107
2002
|
const childPids = /* @__PURE__ */ new Set();
|
|
2108
2003
|
for (const knownPid of knownPids) {
|
|
2109
2004
|
try {
|
|
2110
|
-
const childrenRaw = (0,
|
|
2005
|
+
const childrenRaw = (0, import_node_fs3.readFileSync)(
|
|
2111
2006
|
`/proc/${knownPid}/task/${knownPid}/children`,
|
|
2112
2007
|
"utf8"
|
|
2113
2008
|
).trim();
|
|
@@ -2123,7 +2018,7 @@ function auditProcesses(config) {
|
|
|
2123
2018
|
for (const p of osProcesses) {
|
|
2124
2019
|
if (knownPids.has(p.pid)) continue;
|
|
2125
2020
|
try {
|
|
2126
|
-
const statusContent = (0,
|
|
2021
|
+
const statusContent = (0, import_node_fs3.readFileSync)(`/proc/${p.pid}/status`, "utf8");
|
|
2127
2022
|
const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
|
|
2128
2023
|
if (ppidMatch) {
|
|
2129
2024
|
const ppid = parseInt(ppidMatch[1] ?? "0", 10);
|
|
@@ -2250,7 +2145,7 @@ function parseArgs(argv) {
|
|
|
2250
2145
|
if (args[0] === "live") args.shift();
|
|
2251
2146
|
let i = 0;
|
|
2252
2147
|
while (i < args.length) {
|
|
2253
|
-
const arg = args[i];
|
|
2148
|
+
const arg = args[i] ?? "";
|
|
2254
2149
|
if (arg === "--help" || arg === "-h") {
|
|
2255
2150
|
printUsage();
|
|
2256
2151
|
process.exit(0);
|
|
@@ -2263,13 +2158,13 @@ function parseArgs(argv) {
|
|
|
2263
2158
|
config.recursive = true;
|
|
2264
2159
|
i++;
|
|
2265
2160
|
} else if (!arg.startsWith("-")) {
|
|
2266
|
-
config.dirs.push((0,
|
|
2161
|
+
config.dirs.push((0, import_node_path4.resolve)(arg));
|
|
2267
2162
|
i++;
|
|
2268
2163
|
} else {
|
|
2269
2164
|
i++;
|
|
2270
2165
|
}
|
|
2271
2166
|
}
|
|
2272
|
-
if (config.dirs.length === 0) config.dirs.push((0,
|
|
2167
|
+
if (config.dirs.length === 0) config.dirs.push((0, import_node_path4.resolve)("."));
|
|
2273
2168
|
return config;
|
|
2274
2169
|
}
|
|
2275
2170
|
function printUsage() {
|
|
@@ -2305,7 +2200,7 @@ function scanFiles(dirs, recursive) {
|
|
|
2305
2200
|
const seen = /* @__PURE__ */ new Set();
|
|
2306
2201
|
function scanDir(d, topLevel) {
|
|
2307
2202
|
try {
|
|
2308
|
-
const dirStat = (0,
|
|
2203
|
+
const dirStat = (0, import_node_fs4.statSync)(d);
|
|
2309
2204
|
const dirMtime = dirStat.mtime.getTime();
|
|
2310
2205
|
const cachedMtime = dirMtimeCache.get(d);
|
|
2311
2206
|
if (cachedMtime === dirMtime) {
|
|
@@ -2321,13 +2216,13 @@ function scanFiles(dirs, recursive) {
|
|
|
2321
2216
|
}
|
|
2322
2217
|
}
|
|
2323
2218
|
const dirResults = [];
|
|
2324
|
-
for (const f of (0,
|
|
2219
|
+
for (const f of (0, import_node_fs4.readdirSync)(d)) {
|
|
2325
2220
|
if (f.startsWith(".")) continue;
|
|
2326
|
-
const fp = (0,
|
|
2221
|
+
const fp = (0, import_node_path4.join)(d, f);
|
|
2327
2222
|
if (seen.has(fp)) continue;
|
|
2328
2223
|
let stat;
|
|
2329
2224
|
try {
|
|
2330
|
-
stat = (0,
|
|
2225
|
+
stat = (0, import_node_fs4.statSync)(fp);
|
|
2331
2226
|
} catch {
|
|
2332
2227
|
continue;
|
|
2333
2228
|
}
|
|
@@ -2369,13 +2264,13 @@ function scanFiles(dirs, recursive) {
|
|
|
2369
2264
|
}
|
|
2370
2265
|
function safeReadJson(fp) {
|
|
2371
2266
|
try {
|
|
2372
|
-
return JSON.parse((0,
|
|
2267
|
+
return JSON.parse((0, import_node_fs4.readFileSync)(fp, "utf8"));
|
|
2373
2268
|
} catch {
|
|
2374
2269
|
return null;
|
|
2375
2270
|
}
|
|
2376
2271
|
}
|
|
2377
2272
|
function nameFromFile(filename) {
|
|
2378
|
-
return (0,
|
|
2273
|
+
return (0, import_node_path4.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
|
|
2379
2274
|
}
|
|
2380
2275
|
function normalizeStatus(val) {
|
|
2381
2276
|
if (typeof val !== "string") return "unknown";
|
|
@@ -2553,11 +2448,11 @@ function processJsonFile(file) {
|
|
|
2553
2448
|
}
|
|
2554
2449
|
function processJsonlFile(file) {
|
|
2555
2450
|
try {
|
|
2556
|
-
const content = (0,
|
|
2451
|
+
const content = (0, import_node_fs4.readFileSync)(file.path, "utf8").trim();
|
|
2557
2452
|
if (!content) return [];
|
|
2558
2453
|
const lines = content.split("\n");
|
|
2559
2454
|
const lineCount = lines.length;
|
|
2560
|
-
const lastObj = JSON.parse(lines[lines.length - 1]);
|
|
2455
|
+
const lastObj = JSON.parse(lines[lines.length - 1] ?? "{}");
|
|
2561
2456
|
const name = lastObj.jobId ?? lastObj.agentId ?? lastObj.name ?? lastObj.id ?? nameFromFile(file.filename);
|
|
2562
2457
|
if (lastObj.action !== void 0 || lastObj.jobId !== void 0) {
|
|
2563
2458
|
const status2 = findStatus(lastObj);
|
|
@@ -2653,7 +2548,7 @@ function processJsonlFile(file) {
|
|
|
2653
2548
|
}
|
|
2654
2549
|
const parts = [];
|
|
2655
2550
|
if (model) {
|
|
2656
|
-
const shortModel = model.includes("/") ? model.split("/").pop() : model;
|
|
2551
|
+
const shortModel = model.includes("/") ? model.split("/").pop() ?? model : model;
|
|
2657
2552
|
parts.push(shortModel.slice(0, 20));
|
|
2658
2553
|
}
|
|
2659
2554
|
if (toolCalls.length > 0) {
|
|
@@ -2818,15 +2713,15 @@ function render(config) {
|
|
|
2818
2713
|
if (age > 36e5 || age < 0) continue;
|
|
2819
2714
|
const idx = 11 - Math.floor(age / 3e5);
|
|
2820
2715
|
if (idx >= 0 && idx < 12) {
|
|
2821
|
-
buckets[idx]
|
|
2822
|
-
if (r.status === "error") failBuckets[idx]
|
|
2716
|
+
buckets[idx] = (buckets[idx] ?? 0) + 1;
|
|
2717
|
+
if (r.status === "error") failBuckets[idx] = (failBuckets[idx] ?? 0) + 1;
|
|
2823
2718
|
}
|
|
2824
2719
|
}
|
|
2825
2720
|
const maxBucket = Math.max(...buckets, 1);
|
|
2826
2721
|
const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
|
2827
2722
|
const spark = buckets.map((v, i) => {
|
|
2828
2723
|
const level = Math.round(v / maxBucket * 8);
|
|
2829
|
-
return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
2724
|
+
return ((failBuckets[i] ?? 0) > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
|
|
2830
2725
|
}).join("");
|
|
2831
2726
|
let auditResult = null;
|
|
2832
2727
|
if (now - lastAuditTime > 1e4) {
|
|
@@ -3077,7 +2972,7 @@ function render(config) {
|
|
|
3077
2972
|
for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
|
|
3078
2973
|
}
|
|
3079
2974
|
writeLine(L, "");
|
|
3080
|
-
const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
|
|
2975
|
+
const dirLabel = config.dirs.length === 1 ? config.dirs[0] ?? "" : `${config.dirs.length} directories`;
|
|
3081
2976
|
writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
|
|
3082
2977
|
writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
|
|
3083
2978
|
flushLines(L);
|
|
@@ -3093,13 +2988,13 @@ function getDistDepth(dt, spanId, visited) {
|
|
|
3093
2988
|
}
|
|
3094
2989
|
function startLive(argv) {
|
|
3095
2990
|
const config = parseArgs(argv);
|
|
3096
|
-
const valid = config.dirs.filter((d) => (0,
|
|
2991
|
+
const valid = config.dirs.filter((d) => (0, import_node_fs4.existsSync)(d));
|
|
3097
2992
|
if (valid.length === 0) {
|
|
3098
2993
|
console.error(`No valid directories found: ${config.dirs.join(", ")}`);
|
|
3099
2994
|
console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
|
|
3100
2995
|
process.exit(1);
|
|
3101
2996
|
}
|
|
3102
|
-
const invalid = config.dirs.filter((d) => !(0,
|
|
2997
|
+
const invalid = config.dirs.filter((d) => !(0, import_node_fs4.existsSync)(d));
|
|
3103
2998
|
if (invalid.length > 0) {
|
|
3104
2999
|
console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
|
|
3105
3000
|
}
|
|
@@ -3111,7 +3006,7 @@ function startLive(argv) {
|
|
|
3111
3006
|
let debounce = null;
|
|
3112
3007
|
for (const dir of config.dirs) {
|
|
3113
3008
|
try {
|
|
3114
|
-
(0,
|
|
3009
|
+
(0, import_node_fs4.watch)(dir, { recursive: config.recursive }, () => {
|
|
3115
3010
|
if (debounce) clearTimeout(debounce);
|
|
3116
3011
|
debounce = setTimeout(() => render(config), 500);
|
|
3117
3012
|
});
|
|
@@ -3137,22 +3032,169 @@ function startLive(argv) {
|
|
|
3137
3032
|
});
|
|
3138
3033
|
}
|
|
3139
3034
|
|
|
3035
|
+
// src/policy-source.ts
|
|
3036
|
+
var import_node_fs5 = require("fs");
|
|
3037
|
+
var import_node_path5 = require("path");
|
|
3038
|
+
function createPolicySource(store) {
|
|
3039
|
+
return {
|
|
3040
|
+
recentFailureRate(agentId) {
|
|
3041
|
+
const profile = store.getAgentProfile(agentId);
|
|
3042
|
+
return profile?.failureRate ?? 0;
|
|
3043
|
+
},
|
|
3044
|
+
isKnownBottleneck(nodeName) {
|
|
3045
|
+
const profilesDir = (0, import_node_path5.join)(store.baseDir, "profiles");
|
|
3046
|
+
let agentIds;
|
|
3047
|
+
try {
|
|
3048
|
+
agentIds = (0, import_node_fs5.readdirSync)(profilesDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
|
|
3049
|
+
} catch {
|
|
3050
|
+
return false;
|
|
3051
|
+
}
|
|
3052
|
+
for (const agentId of agentIds) {
|
|
3053
|
+
const profile = store.getAgentProfile(agentId);
|
|
3054
|
+
if (profile?.knownBottlenecks.includes(nodeName)) {
|
|
3055
|
+
return true;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
return false;
|
|
3059
|
+
},
|
|
3060
|
+
lastConformanceScore(agentId) {
|
|
3061
|
+
const profile = store.getAgentProfile(agentId);
|
|
3062
|
+
return profile?.lastConformanceScore ?? null;
|
|
3063
|
+
},
|
|
3064
|
+
getAgentProfile(agentId) {
|
|
3065
|
+
return store.getAgentProfile(agentId);
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// src/receipts.ts
|
|
3071
|
+
function extractTokenCost(node) {
|
|
3072
|
+
const semantic = node.metadata?.semantic;
|
|
3073
|
+
if (semantic?.tokenCost !== void 0 && semantic.tokenCost !== null) {
|
|
3074
|
+
return semantic.tokenCost;
|
|
3075
|
+
}
|
|
3076
|
+
if (node.state?.tokenCost !== void 0 && node.state.tokenCost !== null) {
|
|
3077
|
+
return node.state.tokenCost;
|
|
3078
|
+
}
|
|
3079
|
+
return null;
|
|
3080
|
+
}
|
|
3081
|
+
function extractError(node) {
|
|
3082
|
+
if (node.state?.error !== void 0 && node.state.error !== null) {
|
|
3083
|
+
return String(node.state.error);
|
|
3084
|
+
}
|
|
3085
|
+
if (node.metadata?.error !== void 0 && node.metadata.error !== null) {
|
|
3086
|
+
return String(node.metadata.error);
|
|
3087
|
+
}
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3090
|
+
function nodeDuration(node) {
|
|
3091
|
+
if (node.endTime === null) return null;
|
|
3092
|
+
return node.endTime - node.startTime;
|
|
3093
|
+
}
|
|
3094
|
+
function toReceipt(graph) {
|
|
3095
|
+
const nodes = [...graph.nodes.values()];
|
|
3096
|
+
nodes.sort((a, b) => a.startTime - b.startTime);
|
|
3097
|
+
const steps = nodes.map((node) => ({
|
|
3098
|
+
nodeId: node.id,
|
|
3099
|
+
name: node.name,
|
|
3100
|
+
type: node.type,
|
|
3101
|
+
status: node.status,
|
|
3102
|
+
durationMs: nodeDuration(node),
|
|
3103
|
+
tokenCost: extractTokenCost(node),
|
|
3104
|
+
error: extractError(node)
|
|
3105
|
+
}));
|
|
3106
|
+
let succeeded = 0;
|
|
3107
|
+
let failed = 0;
|
|
3108
|
+
const skipped = 0;
|
|
3109
|
+
for (const node of nodes) {
|
|
3110
|
+
if (node.status === "completed") {
|
|
3111
|
+
succeeded++;
|
|
3112
|
+
} else if (node.status === "failed" || node.status === "hung" || node.status === "timeout") {
|
|
3113
|
+
failed++;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
const attempted = nodes.length;
|
|
3117
|
+
let totalTokenCost = null;
|
|
3118
|
+
for (const step of steps) {
|
|
3119
|
+
if (step.tokenCost !== null) {
|
|
3120
|
+
totalTokenCost = (totalTokenCost ?? 0) + step.tokenCost;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
const totalDurationMs = graph.endTime !== null ? graph.endTime - graph.startTime : null;
|
|
3124
|
+
return {
|
|
3125
|
+
runId: graph.id,
|
|
3126
|
+
agentId: graph.agentId,
|
|
3127
|
+
status: graph.status,
|
|
3128
|
+
startTime: graph.startTime,
|
|
3129
|
+
endTime: graph.endTime,
|
|
3130
|
+
totalDurationMs,
|
|
3131
|
+
totalTokenCost,
|
|
3132
|
+
steps,
|
|
3133
|
+
summary: { attempted, succeeded, failed, skipped }
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
function formatReceipt(receipt) {
|
|
3137
|
+
const lines = [];
|
|
3138
|
+
lines.push("=== Run Receipt ===");
|
|
3139
|
+
lines.push(`Run: ${receipt.runId}`);
|
|
3140
|
+
lines.push(`Agent: ${receipt.agentId}`);
|
|
3141
|
+
lines.push(`Status: ${receipt.status}`);
|
|
3142
|
+
lines.push(
|
|
3143
|
+
`Duration: ${receipt.totalDurationMs !== null ? `${receipt.totalDurationMs}ms` : "\u2014"}`
|
|
3144
|
+
);
|
|
3145
|
+
lines.push("");
|
|
3146
|
+
const s = receipt.summary;
|
|
3147
|
+
lines.push(
|
|
3148
|
+
`Summary: ${s.attempted} attempted, ${s.succeeded} succeeded, ${s.failed} failed, ${s.skipped} skipped`
|
|
3149
|
+
);
|
|
3150
|
+
lines.push("");
|
|
3151
|
+
const nameWidth = Math.max(4, ...receipt.steps.map((st) => st.name.length));
|
|
3152
|
+
const typeWidth = Math.max(4, ...receipt.steps.map((st) => st.type.length));
|
|
3153
|
+
const pad = (str, width) => str.padEnd(width);
|
|
3154
|
+
const padNum = (str, width) => str.padStart(width);
|
|
3155
|
+
const idxWidth = Math.max(2, String(receipt.steps.length).length);
|
|
3156
|
+
const statusWidth = 9;
|
|
3157
|
+
const durWidth = 10;
|
|
3158
|
+
const tokWidth = 8;
|
|
3159
|
+
lines.push(
|
|
3160
|
+
` ${padNum("#", idxWidth)} | ${pad("Step", nameWidth)} | ${pad("Type", typeWidth)} | ${pad("Status", statusWidth)} | ${padNum("Duration", durWidth)} | ${padNum("Tokens", tokWidth)}`
|
|
3161
|
+
);
|
|
3162
|
+
lines.push(
|
|
3163
|
+
`${"-".repeat(idxWidth + 1)}|${"-".repeat(nameWidth + 2)}|${"-".repeat(typeWidth + 2)}|${"-".repeat(statusWidth + 2)}|${"-".repeat(durWidth + 2)}|${"-".repeat(tokWidth + 2)}`
|
|
3164
|
+
);
|
|
3165
|
+
for (let i = 0; i < receipt.steps.length; i++) {
|
|
3166
|
+
const step = receipt.steps[i];
|
|
3167
|
+
const durStr = step.durationMs !== null ? `${step.durationMs}ms` : "\u2014";
|
|
3168
|
+
const tokStr = step.tokenCost !== null ? String(step.tokenCost) : "\u2014";
|
|
3169
|
+
lines.push(
|
|
3170
|
+
` ${padNum(String(i + 1), idxWidth)} | ${pad(step.name, nameWidth)} | ${pad(step.type, typeWidth)} | ${pad(step.status, statusWidth)} | ${padNum(durStr, durWidth)} | ${padNum(tokStr, tokWidth)}`
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3173
|
+
lines.push("");
|
|
3174
|
+
if (receipt.totalTokenCost !== null) {
|
|
3175
|
+
lines.push(`Total token cost: ${receipt.totalTokenCost}`);
|
|
3176
|
+
} else {
|
|
3177
|
+
lines.push("Total token cost: no cost data");
|
|
3178
|
+
}
|
|
3179
|
+
return lines.join("\n");
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3140
3182
|
// src/runner.ts
|
|
3141
3183
|
var import_node_child_process2 = require("child_process");
|
|
3142
|
-
var
|
|
3143
|
-
var
|
|
3184
|
+
var import_node_fs6 = require("fs");
|
|
3185
|
+
var import_node_path6 = require("path");
|
|
3144
3186
|
function globToRegex(pattern) {
|
|
3145
3187
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
3146
3188
|
return new RegExp(`^${escaped}$`);
|
|
3147
3189
|
}
|
|
3148
3190
|
function snapshotDir(dir, patterns) {
|
|
3149
3191
|
const result = /* @__PURE__ */ new Map();
|
|
3150
|
-
if (!(0,
|
|
3151
|
-
for (const entry of (0,
|
|
3192
|
+
if (!(0, import_node_fs6.existsSync)(dir)) return result;
|
|
3193
|
+
for (const entry of (0, import_node_fs6.readdirSync)(dir)) {
|
|
3152
3194
|
if (!patterns.some((re) => re.test(entry))) continue;
|
|
3153
|
-
const full = (0,
|
|
3195
|
+
const full = (0, import_node_path6.join)(dir, entry);
|
|
3154
3196
|
try {
|
|
3155
|
-
const stat = (0,
|
|
3197
|
+
const stat = (0, import_node_fs6.statSync)(full);
|
|
3156
3198
|
if (stat.isFile()) {
|
|
3157
3199
|
result.set(full, stat.mtimeMs);
|
|
3158
3200
|
}
|
|
@@ -3162,7 +3204,7 @@ function snapshotDir(dir, patterns) {
|
|
|
3162
3204
|
return result;
|
|
3163
3205
|
}
|
|
3164
3206
|
function agentIdFromFilename(filePath) {
|
|
3165
|
-
const base = (0,
|
|
3207
|
+
const base = (0, import_node_path6.basename)(filePath, ".json");
|
|
3166
3208
|
const cleaned = base.replace(/-state$/, "");
|
|
3167
3209
|
return `alfred-${cleaned}`;
|
|
3168
3210
|
}
|
|
@@ -3184,7 +3226,7 @@ async function runTraced(config) {
|
|
|
3184
3226
|
if (command.length === 0) {
|
|
3185
3227
|
throw new Error("runTraced: command must not be empty");
|
|
3186
3228
|
}
|
|
3187
|
-
const resolvedTracesDir = (0,
|
|
3229
|
+
const resolvedTracesDir = (0, import_node_path6.resolve)(tracesDir);
|
|
3188
3230
|
const patterns = watchPatterns.map(globToRegex);
|
|
3189
3231
|
const orchestrator = createGraphBuilder({ agentId, trigger });
|
|
3190
3232
|
const { traceId, spanId } = orchestrator.traceContext;
|
|
@@ -3267,21 +3309,21 @@ async function runTraced(config) {
|
|
|
3267
3309
|
childBuilder.endNode(childRootId);
|
|
3268
3310
|
allGraphs.push(childBuilder.build());
|
|
3269
3311
|
}
|
|
3270
|
-
if (!(0,
|
|
3271
|
-
(0,
|
|
3312
|
+
if (!(0, import_node_fs6.existsSync)(resolvedTracesDir)) {
|
|
3313
|
+
(0, import_node_fs6.mkdirSync)(resolvedTracesDir, { recursive: true });
|
|
3272
3314
|
}
|
|
3273
3315
|
const ts = fileTimestamp();
|
|
3274
3316
|
const tracePaths = [];
|
|
3275
3317
|
for (const graph of allGraphs) {
|
|
3276
3318
|
const filename = `${graph.agentId}-${ts}.json`;
|
|
3277
|
-
const outPath = (0,
|
|
3278
|
-
const resolvedOut = (0,
|
|
3319
|
+
const outPath = (0, import_node_path6.join)(resolvedTracesDir, filename);
|
|
3320
|
+
const resolvedOut = (0, import_node_path6.resolve)(outPath);
|
|
3279
3321
|
if (!resolvedOut.startsWith(`${resolvedTracesDir}/`) && resolvedOut !== resolvedTracesDir) {
|
|
3280
3322
|
throw new Error(
|
|
3281
3323
|
`Path traversal detected: agentId "${graph.agentId}" escapes traces directory`
|
|
3282
3324
|
);
|
|
3283
3325
|
}
|
|
3284
|
-
(0,
|
|
3326
|
+
(0, import_node_fs6.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
|
|
3285
3327
|
tracePaths.push(outPath);
|
|
3286
3328
|
}
|
|
3287
3329
|
if (tracePaths.length > 0) {
|
|
@@ -3299,6 +3341,166 @@ async function runTraced(config) {
|
|
|
3299
3341
|
};
|
|
3300
3342
|
}
|
|
3301
3343
|
|
|
3344
|
+
// src/soma-event-writer.ts
|
|
3345
|
+
var import_node_fs7 = require("fs");
|
|
3346
|
+
var import_node_path7 = require("path");
|
|
3347
|
+
function compactIso(epochMs) {
|
|
3348
|
+
return new Date(epochMs).toISOString().slice(0, 19).replace(/:/g, "");
|
|
3349
|
+
}
|
|
3350
|
+
function isoDate(epochMs) {
|
|
3351
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
3352
|
+
}
|
|
3353
|
+
function renderFrontmatter(fields) {
|
|
3354
|
+
const lines = ["---"];
|
|
3355
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
3356
|
+
if (value === void 0) continue;
|
|
3357
|
+
if (Array.isArray(value)) {
|
|
3358
|
+
lines.push(`${key}: [${value.map((v) => `'${v}'`).join(", ")}]`);
|
|
3359
|
+
} else if (typeof value === "string") {
|
|
3360
|
+
lines.push(`${key}: '${value}'`);
|
|
3361
|
+
} else {
|
|
3362
|
+
lines.push(`${key}: ${String(value)}`);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
lines.push("---");
|
|
3366
|
+
return lines.join("\n");
|
|
3367
|
+
}
|
|
3368
|
+
function formatDuration(ms) {
|
|
3369
|
+
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
3370
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3371
|
+
return `${(ms / 6e4).toFixed(1)}min`;
|
|
3372
|
+
}
|
|
3373
|
+
function executionEventToMarkdown(event) {
|
|
3374
|
+
const isCompleted = event.eventType === "execution.completed";
|
|
3375
|
+
const subtype = isCompleted ? "completed" : "failed";
|
|
3376
|
+
const tags = ["agentflow/execution", `agent/${event.agentId}`, `status/${subtype}`];
|
|
3377
|
+
if (event.processContext?.isAnomaly) {
|
|
3378
|
+
tags.push("agentflow/anomaly");
|
|
3379
|
+
}
|
|
3380
|
+
const frontmatter = {
|
|
3381
|
+
type: "execution",
|
|
3382
|
+
subtype,
|
|
3383
|
+
name: `Execution: ${event.agentId} \u2014 ${subtype}`,
|
|
3384
|
+
source: "agentflow",
|
|
3385
|
+
created: isoDate(event.timestamp),
|
|
3386
|
+
alfred_tags: tags,
|
|
3387
|
+
agentflow_graph_id: event.graphId,
|
|
3388
|
+
duration_ms: event.duration,
|
|
3389
|
+
node_count: event.nodeCount
|
|
3390
|
+
};
|
|
3391
|
+
if (event.processContext) {
|
|
3392
|
+
frontmatter.conformance_score = event.processContext.conformanceScore;
|
|
3393
|
+
frontmatter.is_anomaly = event.processContext.isAnomaly;
|
|
3394
|
+
}
|
|
3395
|
+
const body = [];
|
|
3396
|
+
body.push(`# Execution: ${event.agentId} \u2014 ${subtype}
|
|
3397
|
+
`);
|
|
3398
|
+
body.push(`**Duration:** ${formatDuration(event.duration)} `);
|
|
3399
|
+
body.push(`**Nodes:** ${event.nodeCount} `);
|
|
3400
|
+
body.push(`**Status:** ${event.status}
|
|
3401
|
+
`);
|
|
3402
|
+
if (event.pathSignature) {
|
|
3403
|
+
body.push(`## Path
|
|
3404
|
+
`);
|
|
3405
|
+
body.push(`\`${event.pathSignature}\`
|
|
3406
|
+
`);
|
|
3407
|
+
}
|
|
3408
|
+
if (!isCompleted && event.failurePoint) {
|
|
3409
|
+
const fp = event.failurePoint;
|
|
3410
|
+
body.push(`## Failure Point
|
|
3411
|
+
`);
|
|
3412
|
+
body.push(`**Node:** ${fp.nodeType}:${fp.nodeName} (\`${fp.nodeId}\`) `);
|
|
3413
|
+
if (fp.error) {
|
|
3414
|
+
body.push(`**Error:** ${fp.error}
|
|
3415
|
+
`);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
if (event.processContext) {
|
|
3419
|
+
body.push(`## Process Context
|
|
3420
|
+
`);
|
|
3421
|
+
body.push(`**Conformance:** ${(event.processContext.conformanceScore * 100).toFixed(0)}% `);
|
|
3422
|
+
body.push(`**Anomaly:** ${event.processContext.isAnomaly ? "yes" : "no"}
|
|
3423
|
+
`);
|
|
3424
|
+
}
|
|
3425
|
+
body.push(`## Related
|
|
3426
|
+
`);
|
|
3427
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
3428
|
+
return `${renderFrontmatter(frontmatter)}
|
|
3429
|
+
|
|
3430
|
+
${body.join("\n")}`;
|
|
3431
|
+
}
|
|
3432
|
+
function patternEventToMarkdown(event) {
|
|
3433
|
+
const { pattern } = event;
|
|
3434
|
+
const tags = ["agentflow/pattern", `agent/${event.agentId}`];
|
|
3435
|
+
const frontmatter = {
|
|
3436
|
+
type: "synthesis",
|
|
3437
|
+
subtype: "pattern-discovery",
|
|
3438
|
+
name: `Pattern: ${event.agentId} \u2014 ${pattern.variantCount} variants across ${pattern.totalGraphs} runs`,
|
|
3439
|
+
source: "agentflow",
|
|
3440
|
+
created: isoDate(event.timestamp),
|
|
3441
|
+
alfred_tags: tags,
|
|
3442
|
+
variant_count: pattern.variantCount,
|
|
3443
|
+
total_graphs: pattern.totalGraphs
|
|
3444
|
+
};
|
|
3445
|
+
const body = [];
|
|
3446
|
+
body.push(`# Pattern: ${event.agentId}
|
|
3447
|
+
`);
|
|
3448
|
+
body.push(`**Variants:** ${pattern.variantCount} `);
|
|
3449
|
+
body.push(`**Total Runs:** ${pattern.totalGraphs}
|
|
3450
|
+
`);
|
|
3451
|
+
if (pattern.topVariants.length > 0) {
|
|
3452
|
+
body.push(`## Top Variants
|
|
3453
|
+
`);
|
|
3454
|
+
body.push(`| Path | Count | % |`);
|
|
3455
|
+
body.push(`|------|-------|---|`);
|
|
3456
|
+
for (const v of pattern.topVariants) {
|
|
3457
|
+
const sig = v.pathSignature.length > 60 ? `${v.pathSignature.slice(0, 57)}...` : v.pathSignature;
|
|
3458
|
+
body.push(`| \`${sig}\` | ${v.count} | ${v.percentage.toFixed(1)}% |`);
|
|
3459
|
+
}
|
|
3460
|
+
body.push("");
|
|
3461
|
+
}
|
|
3462
|
+
if (pattern.topBottlenecks.length > 0) {
|
|
3463
|
+
body.push(`## Top Bottlenecks
|
|
3464
|
+
`);
|
|
3465
|
+
body.push(`| Node | Type | p95 |`);
|
|
3466
|
+
body.push(`|------|------|-----|`);
|
|
3467
|
+
for (const b of pattern.topBottlenecks) {
|
|
3468
|
+
body.push(`| ${b.nodeName} | ${b.nodeType} | ${formatDuration(b.p95)} |`);
|
|
3469
|
+
}
|
|
3470
|
+
body.push("");
|
|
3471
|
+
}
|
|
3472
|
+
body.push(`## Related
|
|
3473
|
+
`);
|
|
3474
|
+
body.push(`- [[agent/${event.agentId}]]`);
|
|
3475
|
+
return `${renderFrontmatter(frontmatter)}
|
|
3476
|
+
|
|
3477
|
+
${body.join("\n")}`;
|
|
3478
|
+
}
|
|
3479
|
+
function createSomaEventWriter(config) {
|
|
3480
|
+
const { inboxDir } = config;
|
|
3481
|
+
function ensureDir() {
|
|
3482
|
+
(0, import_node_fs7.mkdirSync)(inboxDir, { recursive: true });
|
|
3483
|
+
}
|
|
3484
|
+
function eventFileName(event) {
|
|
3485
|
+
const agentId = event.agentId;
|
|
3486
|
+
const ts = compactIso(event.timestamp);
|
|
3487
|
+
if (event.eventType === "pattern.discovered" || event.eventType === "pattern.updated") {
|
|
3488
|
+
return `synthesis-${agentId}-${ts}.md`;
|
|
3489
|
+
}
|
|
3490
|
+
return `execution-${agentId}-${ts}.md`;
|
|
3491
|
+
}
|
|
3492
|
+
return {
|
|
3493
|
+
async write(_graph) {
|
|
3494
|
+
},
|
|
3495
|
+
async writeEvent(event) {
|
|
3496
|
+
ensureDir();
|
|
3497
|
+
const markdown = event.eventType === "pattern.discovered" || event.eventType === "pattern.updated" ? patternEventToMarkdown(event) : executionEventToMarkdown(event);
|
|
3498
|
+
const fileName = eventFileName(event);
|
|
3499
|
+
(0, import_node_fs7.writeFileSync)((0, import_node_path7.join)(inboxDir, fileName), markdown, "utf-8");
|
|
3500
|
+
}
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3302
3504
|
// src/trace-store.ts
|
|
3303
3505
|
var import_promises = require("fs/promises");
|
|
3304
3506
|
var import_node_path8 = require("path");
|
|
@@ -3417,7 +3619,7 @@ function formatDuration2(ms) {
|
|
|
3417
3619
|
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
3418
3620
|
return `${(ms / 36e5).toFixed(1)}h`;
|
|
3419
3621
|
}
|
|
3420
|
-
function
|
|
3622
|
+
function nodeDuration2(node, graphEndTime) {
|
|
3421
3623
|
const end = node.endTime ?? graphEndTime;
|
|
3422
3624
|
return formatDuration2(end - node.startTime);
|
|
3423
3625
|
}
|
|
@@ -3451,7 +3653,7 @@ function toAsciiTree(graph) {
|
|
|
3451
3653
|
const node = graph.nodes.get(nodeId);
|
|
3452
3654
|
if (!node) return;
|
|
3453
3655
|
const icon = STATUS_ICONS[node.status];
|
|
3454
|
-
const duration =
|
|
3656
|
+
const duration = nodeDuration2(node, endTime);
|
|
3455
3657
|
const genAi = getGenAiInfo(node);
|
|
3456
3658
|
const violation = hasViolation(node, graph) ? " \u26A0" : "";
|
|
3457
3659
|
const errorInfo = node.status === "failed" && node.metadata.error ? ` \u2014 ${node.metadata.error}` : "";
|
|
@@ -3527,7 +3729,7 @@ function toTimeline(graph) {
|
|
|
3527
3729
|
}
|
|
3528
3730
|
}
|
|
3529
3731
|
const icon = STATUS_ICONS[node.status];
|
|
3530
|
-
const duration =
|
|
3732
|
+
const duration = nodeDuration2(node, graphEnd);
|
|
3531
3733
|
const violation = hasViolation(node, graph) ? " \u26A0" : "";
|
|
3532
3734
|
lines.push(`${bar} ${icon} ${node.name} (${duration})${violation}`);
|
|
3533
3735
|
}
|
|
@@ -3665,7 +3867,7 @@ function parseDuration(input) {
|
|
|
3665
3867
|
const n = parseInt(input, 10);
|
|
3666
3868
|
return Number.isNaN(n) ? 0 : n * 1e3;
|
|
3667
3869
|
}
|
|
3668
|
-
const value = parseFloat(match[1]);
|
|
3870
|
+
const value = parseFloat(match[1] ?? "0");
|
|
3669
3871
|
switch (match[2]?.toLowerCase()) {
|
|
3670
3872
|
case "s":
|
|
3671
3873
|
return value * 1e3;
|
|
@@ -3709,12 +3911,12 @@ function estimateInterval(history) {
|
|
|
3709
3911
|
const sorted = [...history].sort((a, b) => a - b);
|
|
3710
3912
|
const deltas = [];
|
|
3711
3913
|
for (let i = 1; i < sorted.length; i++) {
|
|
3712
|
-
const d = sorted[i] - sorted[i - 1];
|
|
3914
|
+
const d = (sorted[i] ?? 0) - (sorted[i - 1] ?? 0);
|
|
3713
3915
|
if (d > 0) deltas.push(d);
|
|
3714
3916
|
}
|
|
3715
3917
|
if (deltas.length === 0) return 0;
|
|
3716
3918
|
deltas.sort((a, b) => a - b);
|
|
3717
|
-
return deltas[Math.floor(deltas.length / 2)];
|
|
3919
|
+
return deltas[Math.floor(deltas.length / 2)] ?? 0;
|
|
3718
3920
|
}
|
|
3719
3921
|
function detectTransitions(previous, currentRecords, config, now) {
|
|
3720
3922
|
const alerts = [];
|
|
@@ -3871,7 +4073,7 @@ function parseWatchArgs(argv) {
|
|
|
3871
4073
|
if (args[0] === "watch") args.shift();
|
|
3872
4074
|
let i = 0;
|
|
3873
4075
|
while (i < args.length) {
|
|
3874
|
-
const arg = args[i];
|
|
4076
|
+
const arg = args[i] ?? "";
|
|
3875
4077
|
if (arg === "--help" || arg === "-h") {
|
|
3876
4078
|
printWatchUsage();
|
|
3877
4079
|
process.exit(0);
|
|
@@ -3940,7 +4142,7 @@ function parseWatchArgs(argv) {
|
|
|
3940
4142
|
}
|
|
3941
4143
|
notifyChannels.unshift({ type: "stdout" });
|
|
3942
4144
|
if (!stateFilePath) {
|
|
3943
|
-
stateFilePath = (0, import_node_path9.join)(dirs[0], ".agentflow-watch-state.json");
|
|
4145
|
+
stateFilePath = (0, import_node_path9.join)(dirs[0] ?? ".", ".agentflow-watch-state.json");
|
|
3944
4146
|
}
|
|
3945
4147
|
return {
|
|
3946
4148
|
dirs,
|
|
@@ -4103,11 +4305,13 @@ agentflow watch started`);
|
|
|
4103
4305
|
createPolicySource,
|
|
4104
4306
|
createSomaEventWriter,
|
|
4105
4307
|
createTraceStore,
|
|
4308
|
+
discoverAllProcessConfigs,
|
|
4106
4309
|
discoverProcess,
|
|
4107
4310
|
discoverProcessConfig,
|
|
4108
4311
|
findVariants,
|
|
4109
4312
|
findWaitingOn,
|
|
4110
4313
|
formatAuditReport,
|
|
4314
|
+
formatReceipt,
|
|
4111
4315
|
getBottlenecks,
|
|
4112
4316
|
getChildren,
|
|
4113
4317
|
getCriticalPath,
|
|
@@ -4129,6 +4333,7 @@ agentflow watch started`);
|
|
|
4129
4333
|
startWatch,
|
|
4130
4334
|
stitchTrace,
|
|
4131
4335
|
toAsciiTree,
|
|
4336
|
+
toReceipt,
|
|
4132
4337
|
toTimeline,
|
|
4133
4338
|
withGuards
|
|
4134
4339
|
});
|