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/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].agentId
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/prompt-builder.ts
611
- var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
612
- function fmtDuration(ms) {
613
- if (ms < 1e3) return `${ms}ms`;
614
- return `${(ms / 1e3).toFixed(1)}s`;
613
+ // src/graph-query.ts
614
+ function getNode(graph, nodeId) {
615
+ return graph.nodes.get(nodeId);
615
616
  }
616
- function fmtTime(ts) {
617
- return new Date(ts).toISOString();
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 durationStats(durations) {
620
- if (durations.length === 0) return { avg: 0, p50: 0, p95: 0, min: 0, max: 0 };
621
- const sorted = [...durations].sort((a, b) => a - b);
622
- const sum = sorted.reduce((a, b) => a + b, 0);
623
- return {
624
- avg: Math.round(sum / sorted.length),
625
- p50: sorted[Math.floor(sorted.length * 0.5)] ?? 0,
626
- p95: sorted[Math.floor(sorted.length * 0.95)] ?? 0,
627
- min: sorted[0] ?? 0,
628
- max: sorted[sorted.length - 1] ?? 0
629
- };
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 buildFailureAnalysisPrompt(events, profile) {
632
- const stats = durationStats(profile.recentDurations);
633
- const failureDetails = events.map((e, i) => {
634
- const lines = [
635
- `Failure ${i + 1}:`,
636
- ` Time: ${fmtTime(e.timestamp)}`,
637
- ` Duration: ${fmtDuration(e.duration)}`,
638
- ` Path: ${e.pathSignature}`
639
- ];
640
- if (e.failurePoint) {
641
- lines.push(` Failed at: ${e.failurePoint.nodeName} (${e.failurePoint.nodeType})`);
642
- if (e.failurePoint.error) lines.push(` Error: ${e.failurePoint.error}`);
643
- }
644
- if (e.violations.length > 0) {
645
- lines.push(` Violations: ${e.violations.map((v) => v.message).join("; ")}`);
646
- }
647
- return lines.join("\n");
648
- }).join("\n\n");
649
- return `${ROLE}
650
-
651
- ## Agent Profile
652
- - Agent: ${profile.agentId}
653
- - Total runs: ${profile.totalRuns}
654
- - Failure rate: ${(profile.failureRate * 100).toFixed(1)}% (${profile.failureCount} failures / ${profile.totalRuns} total)
655
- - Avg duration: ${fmtDuration(stats.avg)} (p50: ${fmtDuration(stats.p50)}, p95: ${fmtDuration(stats.p95)})
656
- - Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
657
- - Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
658
-
659
- ## Recent Failures (${events.length})
660
-
661
- ${failureDetails}
662
-
663
- ## Question
664
- Analyze these failures. What patterns do you see? What is the most likely root cause? Are these related or independent failures?`;
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 buildAnomalyExplanationPrompt(event, profile) {
667
- const stats = durationStats(profile.recentDurations);
668
- const eventDetails = [
669
- `Time: ${fmtTime(event.timestamp)}`,
670
- `Status: ${event.status}`,
671
- `Duration: ${fmtDuration(event.duration)}`,
672
- `Path: ${event.pathSignature}`,
673
- `Node count: ${event.nodeCount}`
674
- ];
675
- if (event.processContext) {
676
- eventDetails.push(`Conformance score: ${event.processContext.conformanceScore}`);
677
- eventDetails.push(`Is anomaly: ${event.processContext.isAnomaly}`);
678
- eventDetails.push(`Variant: ${event.processContext.variant}`);
679
- }
680
- if (event.failurePoint) {
681
- eventDetails.push(`Failed at: ${event.failurePoint.nodeName} (${event.failurePoint.nodeType})`);
682
- if (event.failurePoint.error) eventDetails.push(`Error: ${event.failurePoint.error}`);
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
- if (event.violations.length > 0) {
685
- eventDetails.push(`Violations: ${event.violations.map((v) => v.message).join("; ")}`);
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 `${ROLE}
688
-
689
- ## Agent Baseline (from profile)
690
- - Agent: ${profile.agentId}
691
- - Total runs: ${profile.totalRuns}
692
- - Typical failure rate: ${(profile.failureRate * 100).toFixed(1)}%
693
- - Typical duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}
694
- - Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
695
- - Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
696
-
697
- ## Anomalous Execution
698
- ${eventDetails.join("\n")}
699
-
700
- ## Question
701
- This execution has been flagged as anomalous. Explain what is unusual about it compared to the agent's typical behavior. What might have caused this deviation?`;
666
+ return dfs(root).path;
702
667
  }
703
- function buildAgentSummaryPrompt(profile, recentEvents, patterns) {
704
- const stats = durationStats(profile.recentDurations);
705
- const recentOutcomes = recentEvents.slice(0, 10).map((e) => ` ${fmtTime(e.timestamp)} \u2014 ${e.eventType} (${fmtDuration(e.duration)})`).join("\n");
706
- const patternSummary = patterns.length > 0 ? patterns.slice(0, 3).map((p) => {
707
- const lines = [
708
- ` Variants: ${p.pattern.variantCount} across ${p.pattern.totalGraphs} executions`,
709
- ` Top variant: ${p.pattern.topVariants[0]?.pathSignature ?? "N/A"} (${p.pattern.topVariants[0]?.percentage.toFixed(0) ?? 0}%)`
710
- ];
711
- if (p.pattern.topBottlenecks.length > 0) {
712
- const topB = p.pattern.topBottlenecks[0];
713
- if (topB)
714
- lines.push(` Top bottleneck: ${topB.nodeName} (p95: ${fmtDuration(topB.p95)})`);
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
- return lines.join("\n");
717
- }).join("\n\n") : " No patterns discovered yet.";
718
- const dataNote = recentEvents.length === 0 && patterns.length === 0 ? "\nNote: Limited data available. Summary is based only on the profile statistics.\n" : "";
719
- return `${ROLE}
720
-
721
- ## Agent Profile
722
- - Agent: ${profile.agentId}
723
- - Total runs: ${profile.totalRuns}
724
- - Success rate: ${((1 - profile.failureRate) * 100).toFixed(1)}% (${profile.successCount} successes, ${profile.failureCount} failures)
725
- - Duration: avg ${fmtDuration(stats.avg)}, p50 ${fmtDuration(stats.p50)}, p95 ${fmtDuration(stats.p95)}, range ${fmtDuration(stats.min)}\u2013${fmtDuration(stats.max)}
726
- - Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
727
- - Last conformance score: ${profile.lastConformanceScore ?? "N/A"}
728
- - Last pattern analysis: ${profile.lastPatternTimestamp ? fmtTime(profile.lastPatternTimestamp) : "never"}
729
- ${dataNote}
730
- ## Recent Executions (last ${recentEvents.slice(0, 10).length})
731
- ${recentOutcomes || " No recent events."}
732
-
733
- ## Pattern Analysis
734
- ${patternSummary}
735
-
736
- ## Question
737
- Provide a health summary for this agent. What are the key observations? Is the agent healthy, degrading, or in trouble? What should the operator pay attention to?`;
675
+ }
676
+ return results;
738
677
  }
739
- function buildFixSuggestionPrompt(events, profile, patterns) {
740
- const failureGroups = /* @__PURE__ */ new Map();
741
- for (const e of events) {
742
- const key = e.failurePoint?.error ?? e.pathSignature;
743
- const group = failureGroups.get(key) ?? [];
744
- group.push(e);
745
- failureGroups.set(key, group);
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
- const failureGroupSummary = [...failureGroups.entries()].map(([key, group]) => {
748
- const latest = group[0];
749
- return ` "${key}" \u2014 ${group.length} occurrence(s), latest at ${latest ? fmtTime(latest.timestamp) : "unknown"}`;
750
- }).join("\n");
751
- const bottleneckDetails = patterns.flatMap((p) => p.pattern.topBottlenecks).map((b) => ` ${b.nodeName} (${b.nodeType}) \u2014 p95: ${fmtDuration(b.p95)}`);
752
- const uniqueBottlenecks = [...new Set(bottleneckDetails)].join("\n");
753
- const conformanceIssues = events.filter((e) => e.processContext && e.processContext.conformanceScore < 0.8).map(
754
- (e) => ` ${fmtTime(e.timestamp)}: conformance ${e.processContext?.conformanceScore}, variant "${e.processContext?.variant}"`
755
- ).join("\n");
756
- return `${ROLE}
757
-
758
- ## Agent Profile
759
- - Agent: ${profile.agentId}
760
- - Failure rate: ${(profile.failureRate * 100).toFixed(1)}%
761
- - Known bottlenecks: ${profile.knownBottlenecks.length > 0 ? profile.knownBottlenecks.join(", ") : "none"}
762
-
763
- ## Failure Patterns (${events.length} failures)
764
- ${failureGroupSummary || " No failures recorded."}
765
-
766
- ## Bottlenecks
767
- ${uniqueBottlenecks || " No bottlenecks detected."}
768
-
769
- ## Conformance Issues
770
- ${conformanceIssues || " No conformance issues."}
771
-
772
- ## Question
773
- Based on the failure patterns, bottlenecks, and conformance issues above, provide specific, actionable recommendations to improve this agent's reliability and performance. Prioritize by impact.`;
691
+ return result;
774
692
  }
775
-
776
- // src/insight-engine.ts
777
- var DEFAULT_CACHE_TTL_MS = 36e5;
778
- var SCHEMA_VERSION2 = 1;
779
- function simpleHash(input) {
780
- let hash = 0;
781
- for (let i = 0; i < input.length; i++) {
782
- const char = input.charCodeAt(i);
783
- hash = (hash << 5) - hash + char | 0;
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 (hash >>> 0).toString(36);
786
- }
787
- function createInsightEngine(store, analysisFn, config) {
788
- const cacheTtlMs = config?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
789
- function checkCache(agentId, insightType, dataHash) {
790
- const recent = store.getRecentInsights(agentId, { type: insightType, limit: 1 });
791
- if (recent.length === 0) return null;
792
- const cached = recent[0];
793
- if (!cached || cached.dataHash !== dataHash) return null;
794
- const age = Date.now() - cached.timestamp;
795
- if (age >= cacheTtlMs) return null;
796
- return cached;
797
- }
798
- function storeAndReturn(agentId, insightType, prompt, response, dataHash) {
799
- const event = {
800
- eventType: "insight.generated",
801
- agentId,
802
- timestamp: Date.now(),
803
- schemaVersion: SCHEMA_VERSION2,
804
- insightType,
805
- prompt,
806
- response,
807
- dataHash
808
- };
809
- store.appendInsight(event);
810
- return {
811
- agentId,
812
- insightType,
813
- content: response,
814
- cached: false,
815
- timestamp: event.timestamp
816
- };
817
- }
818
- function shortCircuit(agentId, insightType, content, cached, timestamp) {
819
- return { agentId, insightType, content, cached, timestamp: timestamp ?? Date.now() };
820
- }
821
- return {
822
- async explainFailures(agentId) {
823
- const profile = store.getAgentProfile(agentId);
824
- if (!profile) {
825
- return shortCircuit(
826
- agentId,
827
- "failure-analysis",
828
- "No data available for this agent.",
829
- false
830
- );
831
- }
832
- const events = store.getRecentEvents(agentId, { limit: 50 });
833
- const failures = events.filter((e) => e.eventType === "execution.failed");
834
- if (failures.length === 0) {
835
- return shortCircuit(
836
- agentId,
837
- "failure-analysis",
838
- "No recent failures found for this agent.",
839
- false
840
- );
841
- }
842
- const dataHash = simpleHash(JSON.stringify({ failures, profile }));
843
- const cached = checkCache(agentId, "failure-analysis", dataHash);
844
- if (cached) {
845
- return shortCircuit(agentId, "failure-analysis", cached.response, true, cached.timestamp);
846
- }
847
- const prompt = buildFailureAnalysisPrompt(failures, profile);
848
- try {
849
- const response = await analysisFn(prompt);
850
- return storeAndReturn(agentId, "failure-analysis", prompt, response, dataHash);
851
- } catch (err) {
852
- const message = err instanceof Error ? err.message : String(err);
853
- return shortCircuit(agentId, "failure-analysis", `Analysis failed: ${message}`, false);
854
- }
855
- },
856
- async explainAnomaly(agentId, event) {
857
- const profile = store.getAgentProfile(agentId);
858
- if (!profile) {
859
- return shortCircuit(
860
- agentId,
861
- "anomaly-explanation",
862
- "No data available for this agent.",
863
- false
864
- );
865
- }
866
- const dataHash = simpleHash(JSON.stringify({ event, profile }));
867
- const cached = checkCache(agentId, "anomaly-explanation", dataHash);
868
- if (cached) {
869
- return shortCircuit(
870
- agentId,
871
- "anomaly-explanation",
872
- cached.response,
873
- true,
874
- cached.timestamp
875
- );
876
- }
877
- const prompt = buildAnomalyExplanationPrompt(event, profile);
878
- try {
879
- const response = await analysisFn(prompt);
880
- return storeAndReturn(agentId, "anomaly-explanation", prompt, response, dataHash);
881
- } catch (err) {
882
- const message = err instanceof Error ? err.message : String(err);
883
- return shortCircuit(agentId, "anomaly-explanation", `Analysis failed: ${message}`, false);
884
- }
885
- },
886
- async summarizeAgent(agentId) {
887
- const profile = store.getAgentProfile(agentId);
888
- if (!profile) {
889
- return shortCircuit(agentId, "agent-summary", "No data available for this agent.", false);
890
- }
891
- const recentEvents = store.getRecentEvents(agentId, { limit: 20 });
892
- const patterns = store.getPatternHistory(agentId, { limit: 5 });
893
- const dataHash = simpleHash(JSON.stringify({ profile, recentEvents, patterns }));
894
- const cached = checkCache(agentId, "agent-summary", dataHash);
895
- if (cached) {
896
- return shortCircuit(agentId, "agent-summary", cached.response, true, cached.timestamp);
897
- }
898
- const prompt = buildAgentSummaryPrompt(profile, recentEvents, patterns);
899
- try {
900
- const response = await analysisFn(prompt);
901
- return storeAndReturn(agentId, "agent-summary", prompt, response, dataHash);
902
- } catch (err) {
903
- const message = err instanceof Error ? err.message : String(err);
904
- return shortCircuit(agentId, "agent-summary", `Analysis failed: ${message}`, false);
905
- }
906
- },
907
- async suggestFixes(agentId) {
908
- const profile = store.getAgentProfile(agentId);
909
- if (!profile) {
910
- return shortCircuit(agentId, "fix-suggestion", "No data available for this agent.", false);
911
- }
912
- const events = store.getRecentEvents(agentId, { limit: 50 });
913
- const failures = events.filter((e) => e.eventType === "execution.failed");
914
- const patterns = store.getPatternHistory(agentId, { limit: 5 });
915
- if (failures.length === 0 && profile.knownBottlenecks.length === 0) {
916
- return shortCircuit(
917
- agentId,
918
- "fix-suggestion",
919
- "Agent is healthy \u2014 no failures or bottlenecks detected.",
920
- false
921
- );
922
- }
923
- const dataHash = simpleHash(JSON.stringify({ failures, profile, patterns }));
924
- const cached = checkCache(agentId, "fix-suggestion", dataHash);
925
- if (cached) {
926
- return shortCircuit(agentId, "fix-suggestion", cached.response, true, cached.timestamp);
927
- }
928
- const prompt = buildFixSuggestionPrompt(failures, profile, patterns);
929
- try {
930
- const response = await analysisFn(prompt);
931
- return storeAndReturn(agentId, "fix-suggestion", prompt, response, dataHash);
932
- } catch (err) {
933
- const message = err instanceof Error ? err.message : String(err);
934
- return shortCircuit(agentId, "fix-suggestion", `Analysis failed: ${message}`, false);
935
- }
936
- }
937
- };
938
- }
939
-
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: `Node ${node.id} (${node.type}: ${node.name}) has been running for ${elapsed}ms, exceeding timeout of ${timeoutThreshold}ms`,
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: `Graph depth ${depth} exceeds maximum depth of ${maxDepth}`,
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: `Total agent/subagent count ${agentCount} exceeds maximum of ${maxAgentSpawns}`,
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(...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now));
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: `Detected ${newCount} consecutive ${newType} nodes along path to ${node.name}`,
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: `Agent ${graph.agentId} has a recent failure rate of ${(failureRate * 100).toFixed(0)}% (threshold: ${(maxFailureRate * 100).toFixed(0)}%)`,
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: `Agent ${graph.agentId} conformance score ${(conformanceScore * 100).toFixed(0)}% is below threshold ${(minConformance * 100).toFixed(0)}%`,
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: `Node ${node.name} (${node.type}) is a known bottleneck`,
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
- addEdge: (from, to, type) => builder.addEdge(from, to, type),
1325
- pushEvent: (event) => builder.pushEvent(event),
1326
- updateState: (nodeId, state) => builder.updateState(nodeId, state),
1327
- withParent: (parentId, fn) => builder.withParent(parentId, fn),
1328
- getSnapshot: () => builder.getSnapshot(),
1329
- build: () => {
1330
- const snapshot = builder.getSnapshot();
1331
- const violations = checkGuards(snapshot, config);
1332
- handleViolations(violations);
1333
- return builder.build();
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
- return (0, import_node_path2.join)(profilesDir, `${agentId}.json`);
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
- ensureDir();
1832
- const markdown = event.eventType === "pattern.discovered" || event.eventType === "pattern.updated" ? patternEventToMarkdown(event) : executionEventToMarkdown(event);
1833
- const fileName = eventFileName(event);
1834
- (0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(inboxDir, fileName), markdown, "utf-8");
1691
+ this.append(event);
1835
1692
  }
1836
1693
  };
1837
1694
  }
1838
1695
 
1839
1696
  // src/live.ts
1840
- var import_node_fs6 = require("fs");
1841
- var import_node_path6 = require("path");
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 import_node_fs5 = require("fs");
1898
- var import_node_path5 = require("path");
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, import_node_fs5.readFileSync)(`/proc/${pid}/cmdline`, "utf8");
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, import_node_fs5.readFileSync)(path, "utf8").trim(), 10);
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, import_node_fs5.existsSync)(config.pidFile),
1933
- reason: (0, import_node_fs5.existsSync)(config.pidFile) ? "PID file exists but content is invalid" : "No PID file found"
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.execSync)(
1954
- `systemctl --user show ${unit} --property=ActiveState,SubState,MainPID,NRestarts,Result --no-pager 2>/dev/null`,
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, import_node_fs5.existsSync)(config.workersFile)) return null;
1847
+ if (!config.workersFile || !(0, import_node_fs3.existsSync)(config.workersFile)) return null;
1983
1848
  try {
1984
- const data = JSON.parse((0, import_node_fs5.readFileSync)(config.workersFile, "utf8"));
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, import_node_fs5.readFileSync)(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").trim();
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
- let pidFile;
2046
- let workersFile;
2047
- let processName = "";
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, import_node_fs5.existsSync)(dir)) continue;
1916
+ if (!(0, import_node_fs3.existsSync)(dir)) continue;
2050
1917
  let entries;
2051
1918
  try {
2052
- entries = (0, import_node_fs5.readdirSync)(dir);
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, import_node_path5.join)(dir, f);
1924
+ const fp = (0, import_node_path3.join)(dir, f);
2058
1925
  try {
2059
- if (!(0, import_node_fs5.statSync)(fp).isFile()) continue;
1926
+ if (!(0, import_node_fs3.statSync)(fp).isFile()) continue;
2060
1927
  } catch {
2061
1928
  continue;
2062
1929
  }
2063
- if (f.endsWith(".pid") && !pidFile) {
2064
- pidFile = fp;
2065
- if (!processName) {
2066
- processName = (0, import_node_path5.basename)(f, ".pid");
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 ((f === "workers.json" || f.endsWith("-workers.json")) && !workersFile) {
2070
- workersFile = fp;
2071
- if (!processName && f !== "workers.json") {
2072
- processName = (0, import_node_path5.basename)(f, "-workers.json");
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 unitName = `${processName}.service`;
2082
- const result = (0, import_node_child_process.execSync)(
2083
- `systemctl --user show ${unitName} --property=LoadState --no-pager 2>/dev/null`,
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
- if (result.includes("LoadState=loaded")) {
2087
- systemdUnit = unitName;
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
- return { processName, pidFile, workersFile, systemdUnit };
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, import_node_fs5.readFileSync)(
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, import_node_fs5.readFileSync)(`/proc/${p.pid}/status`, "utf8");
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, import_node_path6.resolve)(arg));
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, import_node_path6.resolve)("."));
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, import_node_fs6.statSync)(d);
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, import_node_fs6.readdirSync)(d)) {
2219
+ for (const f of (0, import_node_fs4.readdirSync)(d)) {
2325
2220
  if (f.startsWith(".")) continue;
2326
- const fp = (0, import_node_path6.join)(d, f);
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, import_node_fs6.statSync)(fp);
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, import_node_fs6.readFileSync)(fp, "utf8"));
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, import_node_path6.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
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, import_node_fs6.readFileSync)(file.path, "utf8").trim();
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, import_node_fs6.existsSync)(d));
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, import_node_fs6.existsSync)(d));
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, import_node_fs6.watch)(dir, { recursive: config.recursive }, () => {
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 import_node_fs7 = require("fs");
3143
- var import_node_path7 = require("path");
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, import_node_fs7.existsSync)(dir)) return result;
3151
- for (const entry of (0, import_node_fs7.readdirSync)(dir)) {
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, import_node_path7.join)(dir, entry);
3195
+ const full = (0, import_node_path6.join)(dir, entry);
3154
3196
  try {
3155
- const stat = (0, import_node_fs7.statSync)(full);
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, import_node_path7.basename)(filePath, ".json");
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, import_node_path7.resolve)(tracesDir);
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, import_node_fs7.existsSync)(resolvedTracesDir)) {
3271
- (0, import_node_fs7.mkdirSync)(resolvedTracesDir, { recursive: true });
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, import_node_path7.join)(resolvedTracesDir, filename);
3278
- const resolvedOut = (0, import_node_path7.resolve)(outPath);
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, import_node_fs7.writeFileSync)(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
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 nodeDuration(node, graphEndTime) {
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 = nodeDuration(node, endTime);
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 = nodeDuration(node, graphEnd);
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
  });