0xray 4.0.36 → 4.0.38

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.
Files changed (169) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.xray/features.json +1 -1
  3. package/dist/CHANGELOG.md +28 -0
  4. package/dist/analytics/emerging-pattern-detector.d.ts +12 -1
  5. package/dist/analytics/emerging-pattern-detector.js +37 -12
  6. package/dist/analytics/pattern-learning-engine.js +1 -9
  7. package/dist/analytics/predictive-analytics.js +51 -33
  8. package/dist/analytics/prompt-pattern-analyzer.d.ts +1 -0
  9. package/dist/analytics/prompt-pattern-analyzer.js +39 -20
  10. package/dist/analytics/routing-performance-analyzer.d.ts +2 -0
  11. package/dist/analytics/routing-performance-analyzer.js +89 -37
  12. package/dist/analytics/simple-pattern-analyzer.d.ts +4 -0
  13. package/dist/analytics/simple-pattern-analyzer.js +66 -28
  14. package/dist/architect/architect-tools.d.ts +3 -0
  15. package/dist/architect/architect-tools.js +22 -0
  16. package/dist/cli/commands/grok-install.js +1 -1
  17. package/dist/cli/commands/hermes-install.js +11 -5
  18. package/dist/cli/commands/openclaw-install.js +13 -7
  19. package/dist/cli/commands/opencode-install.js +7 -4
  20. package/dist/cli/commands/suit-monitor.d.ts +30 -0
  21. package/dist/cli/commands/suit-monitor.js +128 -0
  22. package/dist/cli/index.js +52 -15
  23. package/dist/cli/suit-pulse.d.ts +29 -0
  24. package/dist/cli/suit-pulse.js +270 -0
  25. package/dist/delegation/agent-delegator.d.ts +4 -2
  26. package/dist/delegation/agent-delegator.js +46 -27
  27. package/dist/delegation/agent-expertise.js +2 -1
  28. package/dist/delegation/analytics/outcome-tracker.d.ts +6 -0
  29. package/dist/delegation/analytics/outcome-tracker.js +17 -3
  30. package/dist/delegation/ast-code-parser.d.ts +5 -0
  31. package/dist/delegation/ast-code-parser.js +53 -48
  32. package/dist/delegation/codebase-context-analyzer.d.ts +3 -7
  33. package/dist/delegation/codebase-context-analyzer.js +188 -159
  34. package/dist/delegation/session-coordinator.js +3 -2
  35. package/dist/enforcement/loaders/codex-loader.js +18 -14
  36. package/dist/enforcement/validators/architecture-validators.js +115 -9
  37. package/dist/governance/codex-policy.service.d.ts +12 -2
  38. package/dist/governance/codex-policy.service.js +91 -16
  39. package/dist/integrations/grok/hooks/session-start.js +6 -6
  40. package/dist/integrations/hooks/goggles-pipeline.mjs +169 -28
  41. package/dist/integrations/hooks/goggles-planes.json +172 -8
  42. package/dist/integrations/hooks/plates.cjs +138 -0
  43. package/dist/integrations/hooks/station-hook-runtime.cjs +63 -5
  44. package/dist/integrations/hooks/write-home.cjs +59 -0
  45. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  46. package/dist/mcps/architect-tools.server.d.ts +6 -0
  47. package/dist/mcps/architect-tools.server.js +19 -18
  48. package/dist/mcps/orchestrator/server.d.ts +4 -3
  49. package/dist/mcps/orchestrator/server.js +29 -17
  50. package/dist/memory-routing/lookup-cache.d.ts +8 -0
  51. package/dist/memory-routing/lookup-cache.js +72 -0
  52. package/dist/memory-routing/plates.d.ts +1 -1
  53. package/dist/memory-routing/plates.js +26 -0
  54. package/dist/memory-routing/provider-loader.js +3 -0
  55. package/dist/metrics/agent-metrics.d.ts +14 -5
  56. package/dist/metrics/agent-metrics.js +362 -224
  57. package/dist/nucleus/synthesis-consult-receipt.js +38 -16
  58. package/dist/orchestrator/enhanced-multi-agent-orchestrator.d.ts +5 -0
  59. package/dist/orchestrator/enhanced-multi-agent-orchestrator.js +9 -0
  60. package/dist/orchestrator/multi-agent-orchestration-coordinator.js +1 -2
  61. package/dist/orchestrator/orchestrator.js +1 -2
  62. package/dist/processors/implementations/agents-md-validation-processor.d.ts +0 -1
  63. package/dist/processors/implementations/agents-md-validation-processor.js +0 -11
  64. package/dist/processors/implementations/console-log-guard-processor.js +39 -50
  65. package/dist/processors/implementations/performance-budget-processor.d.ts +0 -5
  66. package/dist/processors/implementations/performance-budget-processor.js +29 -38
  67. package/dist/processors/implementations/storytelling-trigger-processor.d.ts +5 -0
  68. package/dist/processors/implementations/storytelling-trigger-processor.js +69 -16
  69. package/dist/processors/implementations/test-auto-creation-processor.js +39 -36
  70. package/dist/reporting/framework-reporting-system.d.ts +3 -0
  71. package/dist/reporting/framework-reporting-system.js +61 -8
  72. package/dist/reporting/log-parser.js +188 -105
  73. package/dist/reporting/metrics.js +42 -18
  74. package/dist/security/prompt-security-validator.js +8 -7
  75. package/dist/security/security-auditor.d.ts +4 -0
  76. package/dist/security/security-auditor.js +132 -57
  77. package/dist/security/security-hardener.d.ts +2 -0
  78. package/dist/security/security-hardener.js +31 -6
  79. package/dist/security/security-headers.d.ts +2 -0
  80. package/dist/security/security-headers.js +23 -21
  81. package/dist/security/security-scanner.js +18 -4
  82. package/dist/session/session-cleanup-manager.d.ts +2 -0
  83. package/dist/session/session-cleanup-manager.js +29 -8
  84. package/dist/session/session-monitor.d.ts +14 -4
  85. package/dist/session/session-monitor.js +208 -106
  86. package/dist/session/session-state-manager.d.ts +11 -0
  87. package/dist/session/session-state-manager.js +90 -8
  88. package/docs-site/docs/plates/activity-log.md +38 -0
  89. package/docs-site/docs/plates/cursor-compact.md +35 -0
  90. package/docs-site/docs/plates/grok-compact.md +35 -0
  91. package/docs-site/docs/plates/index.md +25 -3
  92. package/docs-site/docs/plates/inference-files.md +35 -0
  93. package/docs-site/docs/plates/kept-line.md +35 -0
  94. package/docs-site/docs/plates/lens-gate.md +35 -0
  95. package/docs-site/docs/plates/lens-page.md +35 -0
  96. package/docs-site/docs/plates/notes-page.md +35 -0
  97. package/docs-site/docs/plates/package-face.md +35 -0
  98. package/docs-site/docs/plates/payload-heat.md +35 -0
  99. package/docs-site/docs/plates/pickup-stamp.md +35 -0
  100. package/docs-site/docs/plates/pre-tool.md +35 -0
  101. package/docs-site/docs/plates/record-map.md +41 -0
  102. package/docs-site/docs/plates/reflection-page.md +35 -0
  103. package/docs-site/docs/plates/review.md +57 -0
  104. package/docs-site/docs/plates/session-capture.md +38 -0
  105. package/docs-site/docs/plates/site-manual.md +35 -0
  106. package/docs-site/docs/plates/stamp-plate.md +35 -0
  107. package/docs-site/docs/plates/station-card.md +35 -0
  108. package/docs-site/docs/plates/station-heat.md +35 -0
  109. package/docs-site/docs/plates/suit-organs.md +38 -0
  110. package/docs-site/docs/plates/suit-settings.md +35 -0
  111. package/docs-site/docs/plates/suit-wear.md +37 -0
  112. package/docs-site/docs/plates/trail-state.md +35 -0
  113. package/docs-site/docs/plates/work-fresh.md +35 -0
  114. package/docs-site/docs/plates/write-home.md +38 -0
  115. package/package.json +3 -2
  116. package/scripts/node/install-bridges.cjs +302 -78
  117. package/scripts/node/postinstall.cjs +14 -3
  118. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  119. package/src/mcps/architect-tools.server.ts +25 -20
  120. package/src/mcps/orchestrator/server.ts +33 -19
  121. package/xray/features.json +1 -1
  122. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +0 -1
  123. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +0 -1
  124. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.d.ts.map +0 -1
  125. package/vendor/@0xray/repertoire/dist/governance/ontological-trap-enforcer.js.map +0 -1
  126. package/vendor/@0xray/repertoire/dist/index.d.ts.map +0 -1
  127. package/vendor/@0xray/repertoire/dist/index.js.map +0 -1
  128. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.d.ts.map +0 -1
  129. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-ingester.js.map +0 -1
  130. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.d.ts.map +0 -1
  131. package/vendor/@0xray/repertoire/dist/ingestion/groover-log-parser.js.map +0 -1
  132. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.d.ts.map +0 -1
  133. package/vendor/@0xray/repertoire/dist/ingestion/orchestrator-feedback-ingester.js.map +0 -1
  134. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +0 -1
  135. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +0 -1
  136. package/vendor/@0xray/repertoire/dist/mcp/server.d.ts.map +0 -1
  137. package/vendor/@0xray/repertoire/dist/mcp/server.js.map +0 -1
  138. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.d.ts.map +0 -1
  139. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/RepertoireOrchestratorBridge.js.map +0 -1
  140. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.d.ts.map +0 -1
  141. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/capability-enhancer.js.map +0 -1
  142. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +0 -1
  143. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +0 -1
  144. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.d.ts.map +0 -1
  145. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-agent-affinity.js.map +0 -1
  146. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +0 -1
  147. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +0 -1
  148. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +0 -1
  149. package/vendor/@0xray/repertoire/dist/paths.js.map +0 -1
  150. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +0 -1
  151. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +0 -1
  152. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +0 -1
  153. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +0 -1
  154. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.d.ts.map +0 -1
  155. package/vendor/@0xray/repertoire/dist/registry/InferenceStateManager.js.map +0 -1
  156. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.d.ts.map +0 -1
  157. package/vendor/@0xray/repertoire/dist/registry/confidence-decay.js.map +0 -1
  158. package/vendor/@0xray/repertoire/dist/registry/signal-prune.d.ts.map +0 -1
  159. package/vendor/@0xray/repertoire/dist/registry/signal-prune.js.map +0 -1
  160. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.d.ts.map +0 -1
  161. package/vendor/@0xray/repertoire/dist/synthesis/concrete-sections.js.map +0 -1
  162. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.d.ts.map +0 -1
  163. package/vendor/@0xray/repertoire/dist/synthesis/meta-inference-engine.js.map +0 -1
  164. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.d.ts.map +0 -1
  165. package/vendor/@0xray/repertoire/dist/synthesis/primitive-confidence-aggregator.js.map +0 -1
  166. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.d.ts.map +0 -1
  167. package/vendor/@0xray/repertoire/dist/synthesis/synthesis-prompt-builder.js.map +0 -1
  168. package/vendor/@0xray/repertoire/dist/types.d.ts.map +0 -1
  169. package/vendor/@0xray/repertoire/dist/types.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0xray",
3
- "version": "4.0.36",
3
+ "version": "4.0.38",
4
4
  "description": "0xRay suit for Grok, Hermes, OpenClaw, OpenCode: mill+inspect, Codex, 7 MCP servers. Costume 45/42 is opt-in foundry, not the default wear.",
5
5
  "author": {
6
6
  "name": "0xRay AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "./features.schema.json",
3
- "version": "4.0.36",
3
+ "version": "4.0.38",
4
4
  "description": "0xRay Framework - Unified Feature Configuration",
5
5
  "suit_temperament": {
6
6
  "profile": "auto",
package/dist/CHANGELOG.md CHANGED
@@ -42,6 +42,34 @@ The format is based on [Conventional Commits](https://www.conventionalcommits.or
42
42
  - Archive root `integrations/` costume SKILL dump → `docs/archive/integrations-costume/`. Live wear is `src/integrations`.
43
43
  - Archive root `commands/` stale copy → `docs/archive/commands-copy/`. Live wear is `src/opencode/commands`.
44
44
 
45
+ ## [4.0.38] - 2026-10-03
46
+
47
+ ### 🔄 Changes
48
+
49
+ ### 🔎 Other Changes
50
+ - Merge pull request #171 from 0xRayAI/fix/solar-verdict-decides (7b6ac081a)
51
+ - Merge remote-tracking branch 'origin/main' into fix/solar-verdict-decides (a342e0b0d)
52
+ - fix(types): declare work-freshness so the Codex 70 import typechecks (16b2db194)
53
+ - feat(codex): term 70 requires current source and worn npm (dacecc916)
54
+ - fix(governance): honor Dynamo's recommendation (968cd6a14)
55
+
56
+ ---
57
+
58
+ ## [4.0.37] - 2026-10-02
59
+
60
+ ### 🔄 Changes
61
+
62
+ ### 🐛 Bug Fixes
63
+ - fix: a naked checkout wakes with repertoire on (7b75bedbc)
64
+
65
+ ### ⚡ Performance
66
+ - perf: cut repeated work on the processor data path (baadb4d35)
67
+
68
+ ### 📚 Documentation
69
+ - docs: move fleet house SSOT out of the kit (7dcf08e94)
70
+
71
+ ---
72
+
45
73
  ## [4.0.36] - 2026-10-02
46
74
 
47
75
  ### 🔄 Changes
@@ -32,6 +32,10 @@ export declare class EmergingPatternDetector {
32
32
  * Extract meaningful keywords from task description
33
33
  */
34
34
  private extractKeywords;
35
+ /**
36
+ * Jaccard |A ∩ B| / |A ∪ B| without copying either set.
37
+ */
38
+ private jaccard;
35
39
  /**
36
40
  * Calculate Jaccard similarity between two keyword sets
37
41
  */
@@ -43,7 +47,14 @@ export declare class EmergingPatternDetector {
43
47
  /**
44
48
  * Detect emergent patterns from recent routing outcomes
45
49
  */
46
- detectEmergingPatterns(outcomes: RoutingOutcome[]): PatternDiscoveryResult;
50
+ detectEmergingPatterns(outcomes: ReadonlyArray<{
51
+ taskId: string;
52
+ taskDescription?: string;
53
+ routedAgent: string;
54
+ routedSkill: string;
55
+ success?: boolean;
56
+ confidence: number;
57
+ }>): PatternDiscoveryResult;
47
58
  /**
48
59
  * Check if a specific pattern is emerging
49
60
  */
@@ -26,21 +26,32 @@ export class EmergingPatternDetector {
26
26
  const words = text.toLowerCase()
27
27
  .replace(/[^\w\s]/g, ' ')
28
28
  .split(/\s+/)
29
- .filter(word => word.length > 2)
30
- .filter(word => !this.stopWords.has(word));
29
+ .filter(word => word.length > 2 && !this.stopWords.has(word));
31
30
  return [...new Set(words)];
32
31
  }
32
+ /**
33
+ * Jaccard |A ∩ B| / |A ∪ B| without copying either set.
34
+ */
35
+ jaccard(left, right) {
36
+ if (left.size === 0 || right.size === 0)
37
+ return 0;
38
+ let intersection = 0;
39
+ const small = left.size <= right.size ? left : right;
40
+ const large = small === left ? right : left;
41
+ for (const word of small) {
42
+ if (large.has(word))
43
+ intersection++;
44
+ }
45
+ const union = left.size + right.size - intersection;
46
+ return union === 0 ? 0 : intersection / union;
47
+ }
33
48
  /**
34
49
  * Calculate Jaccard similarity between two keyword sets
35
50
  */
36
51
  calculateSimilarity(keywords1, keywords2) {
37
52
  if (keywords1.length === 0 || keywords2.length === 0)
38
53
  return 0;
39
- const set1 = new Set(keywords1);
40
- const set2 = new Set(keywords2);
41
- const intersection = new Set([...set1].filter(x => set2.has(x)));
42
- const union = new Set([...set1, ...set2]);
43
- return intersection.size / union.size;
54
+ return this.jaccard(new Set(keywords1), new Set(keywords2));
44
55
  }
45
56
  /**
46
57
  * Cluster similar task descriptions
@@ -48,6 +59,10 @@ export class EmergingPatternDetector {
48
59
  clusterTasks(tasks) {
49
60
  const clusters = [];
50
61
  const assigned = new Set();
62
+ const keywordSets = new Map();
63
+ for (const task of tasks) {
64
+ keywordSets.set(task, new Set(task.keywords));
65
+ }
51
66
  // Sort by frequency (most frequent first)
52
67
  const sortedTasks = [...tasks].sort((a, b) => b.keywords.length - a.keywords.length);
53
68
  for (const task of sortedTasks) {
@@ -62,7 +77,7 @@ export class EmergingPatternDetector {
62
77
  for (const otherTask of sortedTasks) {
63
78
  if (assigned.has(otherTask.id) || otherTask.id === task.id)
64
79
  continue;
65
- const similarity = this.calculateSimilarity([...clusterKeywords], otherTask.keywords);
80
+ const similarity = this.jaccard(clusterKeywords, keywordSets.get(otherTask) ?? new Set(otherTask.keywords));
66
81
  if (similarity >= this.clusterSimilarityThreshold) {
67
82
  otherTask.keywords.forEach(kw => clusterKeywords.add(kw));
68
83
  clusterDescriptions.push(otherTask.description);
@@ -124,12 +139,22 @@ export class EmergingPatternDetector {
124
139
  continue;
125
140
  if (cluster.confidence < this.minConfidenceThreshold)
126
141
  continue;
127
- // Get success metrics for this cluster
128
- const clusterOutcomes = taskData.filter(t => cluster.patterns.includes(t.description));
129
- const successCount = clusterOutcomes.filter(t => t.success).length;
142
+ // Same membership as description includes, in taskData order.
143
+ const patternDescriptions = new Set(cluster.patterns);
144
+ const clusterOutcomes = [];
145
+ let successCount = 0;
146
+ let confidenceSum = 0;
147
+ for (const task of taskData) {
148
+ if (!patternDescriptions.has(task.description))
149
+ continue;
150
+ clusterOutcomes.push(task);
151
+ if (task.success)
152
+ successCount++;
153
+ confidenceSum += task.confidence;
154
+ }
130
155
  const successRate = clusterOutcomes.length > 0 ? successCount / clusterOutcomes.length : 0;
131
156
  const avgConfidence = clusterOutcomes.length > 0
132
- ? clusterOutcomes.reduce((sum, t) => sum + t.confidence, 0) / clusterOutcomes.length
157
+ ? confidenceSum / clusterOutcomes.length
133
158
  : 0;
134
159
  // Determine suggested agent/skill based on most common
135
160
  const agentCounts = new Map();
@@ -78,15 +78,7 @@ export class PatternLearningEngine {
78
78
  }
79
79
  // Detect emerging patterns for new additions
80
80
  if (this.config.enableAutoAddition) {
81
- const emergentResult = emergingPatternDetector.detectEmergingPatterns(outcomes.map(o => ({
82
- taskId: o.taskId,
83
- taskDescription: o.taskDescription,
84
- routedAgent: o.routedAgent,
85
- routedSkill: o.routedSkill,
86
- confidence: o.confidence,
87
- timestamp: new Date(),
88
- success: o.success
89
- })));
81
+ const emergentResult = emergingPatternDetector.detectEmergingPatterns(outcomes);
90
82
  result.newPatterns = this.generateNewPatterns(emergentResult.emergentPatterns, existingMappings);
91
83
  }
92
84
  // Generate recommendations
@@ -16,11 +16,16 @@ function keywordOverlapScore(description, historicalDescriptions) {
16
16
  return 0;
17
17
  let totalScore = 0;
18
18
  let matchCount = 0;
19
+ const histWords = new Set();
19
20
  for (const hist of historicalDescriptions) {
20
- const histWords = new Set(hist.toLowerCase().split(/\W+/).filter(w => w.length > 2));
21
+ histWords.clear();
22
+ for (const word of hist.toLowerCase().split(/\W+/)) {
23
+ if (word.length > 2)
24
+ histWords.add(word);
25
+ }
21
26
  let overlap = 0;
22
- for (const w of descWords) {
23
- if (histWords.has(w))
27
+ for (const word of descWords) {
28
+ if (histWords.has(word))
24
29
  overlap++;
25
30
  }
26
31
  if (overlap > 0) {
@@ -64,42 +69,50 @@ function estimateTaskDuration(agentOutcomes) {
64
69
  */
65
70
  function buildAgentMetrics(agentOutcomes) {
66
71
  const totalTasks = agentOutcomes.length;
67
- const successfulTasks = agentOutcomes.filter(o => o.success).length;
68
- const failedTasks = totalTasks - successfulTasks;
69
- // Calculate average execution time
70
- const durations = agentOutcomes
71
- .filter(o => o.executionTimeMs !== undefined)
72
- .map(o => o.executionTimeMs);
73
- const averageExecutionTime = durations.length > 0
74
- ? durations.reduce((a, b) => a + b, 0) / durations.length
75
- : 0;
76
- // Recent performance (last 20 tasks)
77
- const recentPerformance = agentOutcomes
78
- .slice(-20)
79
- .map(o => o.success ? 1 : 0);
80
- // Task type breakdown
81
- const taskTypeBreakdown = {};
82
- for (const outcome of agentOutcomes) {
72
+ let successfulTasks = 0;
73
+ let durationSum = 0;
74
+ let durationCount = 0;
75
+ const recentStart = Math.max(0, totalTasks - 20);
76
+ const recentPerformance = [];
77
+ const taskCounts = new Map();
78
+ for (let i = 0; i < agentOutcomes.length; i++) {
79
+ const outcome = agentOutcomes[i];
80
+ if (!outcome)
81
+ continue;
82
+ if (outcome.success)
83
+ successfulTasks++;
84
+ if (outcome.executionTimeMs !== undefined) {
85
+ durationSum += outcome.executionTimeMs;
86
+ durationCount++;
87
+ }
83
88
  const taskType = outcome.taskType || "unknown";
84
- if (!taskTypeBreakdown[taskType]) {
85
- taskTypeBreakdown[taskType] = { count: 0, successRate: 0 };
89
+ const bucket = taskCounts.get(taskType);
90
+ if (bucket) {
91
+ bucket.count++;
92
+ if (outcome.success)
93
+ bucket.successes++;
86
94
  }
87
- taskTypeBreakdown[taskType].count++;
88
- }
89
- // Calculate per-task-type success rates
90
- for (const taskType of Object.keys(taskTypeBreakdown)) {
91
- const taskOutcomes = agentOutcomes.filter(o => (o.taskType || "unknown") === taskType);
92
- const successes = taskOutcomes.filter(o => o.success).length;
93
- const entry = taskTypeBreakdown[taskType];
94
- if (entry) {
95
- entry.successRate = successes / taskOutcomes.length;
95
+ else {
96
+ taskCounts.set(taskType, {
97
+ count: 1,
98
+ successes: outcome.success ? 1 : 0,
99
+ });
96
100
  }
101
+ if (i >= recentStart)
102
+ recentPerformance.push(outcome.success ? 1 : 0);
103
+ }
104
+ const taskTypeBreakdown = {};
105
+ for (const [taskType, bucket] of taskCounts) {
106
+ taskTypeBreakdown[taskType] = {
107
+ count: bucket.count,
108
+ successRate: bucket.count > 0 ? bucket.successes / bucket.count : 0,
109
+ };
97
110
  }
98
111
  return {
99
112
  totalTasks,
100
113
  successfulTasks,
101
- failedTasks,
102
- averageExecutionTime,
114
+ failedTasks: totalTasks - successfulTasks,
115
+ averageExecutionTime: durationCount > 0 ? durationSum / durationCount : 0,
103
116
  successRate: totalTasks > 0 ? successfulTasks / totalTasks : 0,
104
117
  recentPerformance,
105
118
  taskTypeBreakdown,
@@ -200,6 +213,11 @@ export const predictiveAnalytics = {
200
213
  const outcomes = routingOutcomeTracker.getOutcomes();
201
214
  if (stats.length === 0 || outcomes.length === 0)
202
215
  return null;
216
+ const statByAgent = new Map();
217
+ for (const stat of stats) {
218
+ if (!statByAgent.has(stat.agent))
219
+ statByAgent.set(stat.agent, stat);
220
+ }
203
221
  // Group outcomes by agent
204
222
  const byAgent = new Map();
205
223
  for (const o of outcomes) {
@@ -215,7 +233,7 @@ export const predictiveAnalytics = {
215
233
  for (const [agent, agentOutcomes] of byAgent) {
216
234
  const descriptions = agentOutcomes.map(o => o.taskDescription);
217
235
  const overlap = keywordOverlapScore(taskDescription, descriptions);
218
- const agentStat = stats.find(s => s.agent === agent);
236
+ const agentStat = statByAgent.get(agent);
219
237
  const successRate = agentStat?.successRate ?? 0;
220
238
  const total = agentStat?.total ?? 0;
221
239
  // Weighted score: 70% keyword overlap + 30% historical success rate
@@ -66,6 +66,7 @@ declare class PromptPatternAnalyzer {
66
66
  /**
67
67
  * Detect gaps where templates don't match user requests
68
68
  */
69
+ private indexOutcomes;
69
70
  private detectTemplateGaps;
70
71
  /**
71
72
  * Identify emerging patterns from real usage
@@ -20,17 +20,22 @@ class PromptPatternAnalyzer {
20
20
  if (promptData.length === 0) {
21
21
  return this.emptyComparisonResult();
22
22
  }
23
- const templateMatches = promptData.filter((p) => p.templatePrompt && p.templatePrompt.length > 0);
24
- const nonTemplatePrompts = promptData.filter((p) => !p.templatePrompt || p.templatePrompt.length === 0);
25
- const gaps = this.detectTemplateGaps(promptData, outcomes);
26
- const emergingPatterns = this.identifyEmergingPatterns(promptData, outcomes);
27
- const topMissedKeywords = this.analyzeMissedKeywords(promptData);
23
+ let templateMatchCount = 0;
24
+ for (const prompt of promptData) {
25
+ if (prompt.templatePrompt && prompt.templatePrompt.length > 0)
26
+ templateMatchCount++;
27
+ }
28
+ const keywordsByPrompt = promptData.map((prompt) => this.extractKeywords(prompt.userRequest || ""));
29
+ const outcomeByTask = this.indexOutcomes(outcomes);
30
+ const gaps = this.detectTemplateGaps(promptData, outcomeByTask);
31
+ const emergingPatterns = this.identifyEmergingPatterns(promptData, keywordsByPrompt, outcomeByTask);
32
+ const topMissedKeywords = this.analyzeMissedKeywords(promptData, keywordsByPrompt);
28
33
  const agentCoverage = this.calculateAgentCoverage(promptData);
29
34
  return {
30
35
  totalPrompts: promptData.length,
31
- templateMatches: templateMatches.length,
32
- nonTemplatePrompts: nonTemplatePrompts.length,
33
- templateMatchRate: promptData.length > 0 ? templateMatches.length / promptData.length : 0,
36
+ templateMatches: templateMatchCount,
37
+ nonTemplatePrompts: promptData.length - templateMatchCount,
38
+ templateMatchRate: promptData.length > 0 ? templateMatchCount / promptData.length : 0,
34
39
  gaps,
35
40
  emergingPatterns,
36
41
  topMissedKeywords,
@@ -40,7 +45,15 @@ class PromptPatternAnalyzer {
40
45
  /**
41
46
  * Detect gaps where templates don't match user requests
42
47
  */
43
- detectTemplateGaps(promptData, outcomes) {
48
+ indexOutcomes(outcomes) {
49
+ const indexed = new Map();
50
+ for (const outcome of outcomes) {
51
+ if (!indexed.has(outcome.taskId))
52
+ indexed.set(outcome.taskId, outcome);
53
+ }
54
+ return indexed;
55
+ }
56
+ detectTemplateGaps(promptData, outcomeByTask) {
44
57
  const gaps = [];
45
58
  const gapMap = new Map();
46
59
  for (const prompt of promptData) {
@@ -54,7 +67,7 @@ class PromptPatternAnalyzer {
54
67
  }
55
68
  }
56
69
  else {
57
- const outcome = outcomes.find((o) => o.taskId === prompt.taskId);
70
+ const outcome = outcomeByTask.get(prompt.taskId);
58
71
  gaps.push({
59
72
  gapType: this.classifyGapType(prompt, outcome),
60
73
  userRequest: prompt.userRequest || '',
@@ -79,11 +92,10 @@ class PromptPatternAnalyzer {
79
92
  /**
80
93
  * Identify emerging patterns from real usage
81
94
  */
82
- identifyEmergingPatterns(promptData, outcomes) {
95
+ identifyEmergingPatterns(promptData, keywordsByPrompt, outcomeByTask) {
83
96
  const patternMap = new Map();
84
97
  const keywordFrequency = new Map();
85
- for (const prompt of promptData) {
86
- const keywords = this.extractKeywords(prompt.userRequest || '');
98
+ for (const keywords of keywordsByPrompt) {
87
99
  for (const keyword of keywords) {
88
100
  keywordFrequency.set(keyword, (keywordFrequency.get(keyword) || 0) + 1);
89
101
  }
@@ -92,13 +104,17 @@ class PromptPatternAnalyzer {
92
104
  .filter(([, count]) => count >= this.minFrequencyThreshold)
93
105
  .sort((a, b) => b[1] - a[1])
94
106
  .slice(0, 50);
95
- for (const prompt of promptData) {
96
- const keywords = this.extractKeywords(prompt.userRequest || '');
97
- const significantKeywordsInPrompt = keywords.filter((kw) => significantKeywords.some(([sigKw]) => kw === sigKw));
107
+ const significantSet = new Set(significantKeywords.map(([keyword]) => keyword));
108
+ for (let i = 0; i < promptData.length; i++) {
109
+ const prompt = promptData[i];
110
+ const keywords = keywordsByPrompt[i];
111
+ if (!prompt || !keywords)
112
+ continue;
113
+ const significantKeywordsInPrompt = keywords.filter((kw) => significantSet.has(kw));
98
114
  if (significantKeywordsInPrompt.length === 0)
99
115
  continue;
100
116
  const patternKey = significantKeywordsInPrompt.sort().join("|");
101
- const outcome = outcomes.find((o) => o.taskId === prompt.taskId);
117
+ const outcome = outcomeByTask.get(prompt.taskId);
102
118
  if (!outcome)
103
119
  continue;
104
120
  if (patternMap.has(patternKey)) {
@@ -139,12 +155,15 @@ class PromptPatternAnalyzer {
139
155
  /**
140
156
  * Analyze frequently missed keywords
141
157
  */
142
- analyzeMissedKeywords(promptData) {
158
+ analyzeMissedKeywords(promptData, keywordsByPrompt) {
143
159
  const missedKeywords = new Map();
144
160
  const keywordToAgent = new Map();
145
- for (const prompt of promptData) {
161
+ for (let i = 0; i < promptData.length; i++) {
162
+ const prompt = promptData[i];
163
+ const keywords = keywordsByPrompt[i];
164
+ if (!prompt || !keywords)
165
+ continue;
146
166
  if (!prompt.templatePrompt || prompt.templatePrompt.length === 0) {
147
- const keywords = this.extractKeywords(prompt.userRequest || '');
148
167
  for (const keyword of keywords) {
149
168
  if (keyword.length > 3) {
150
169
  missedKeywords.set(keyword, (missedKeywords.get(keyword) || 0) + 1);
@@ -62,6 +62,8 @@ declare class RoutingPerformanceAnalyzer {
62
62
  private readonly lowConfidenceThreshold;
63
63
  private readonly mediumConfidenceThreshold;
64
64
  private readonly minSamplesForMetrics;
65
+ /** First row for each taskId, matching Array.find. */
66
+ private firstByTaskId;
65
67
  /**
66
68
  * Generate comprehensive routing performance report
67
69
  */
@@ -11,6 +11,15 @@ class RoutingPerformanceAnalyzer {
11
11
  lowConfidenceThreshold = 0.6;
12
12
  mediumConfidenceThreshold = 0.8;
13
13
  minSamplesForMetrics = 5;
14
+ /** First row for each taskId, matching Array.find. */
15
+ firstByTaskId(rows) {
16
+ const indexed = new Map();
17
+ for (const row of rows) {
18
+ if (!indexed.has(row.taskId))
19
+ indexed.set(row.taskId, row);
20
+ }
21
+ return indexed;
22
+ }
14
23
  /**
15
24
  * Generate comprehensive routing performance report
16
25
  */
@@ -25,7 +34,7 @@ class RoutingPerformanceAnalyzer {
25
34
  }
26
35
  const agentMetrics = this.calculateAgentMetrics(outcomes, promptData);
27
36
  const keywordEffectiveness = this.analyzeKeywordEffectiveness(decisions, outcomes);
28
- const confidenceMetrics = this.analyzeConfidenceThresholds(outcomes);
37
+ const confidenceMetrics = this.analyzeConfidenceThresholds(outcomes, promptData);
29
38
  const timeRange = this.calculateTimeRange(outcomes);
30
39
  const overallStats = this.calculateOverallStats(outcomes);
31
40
  const recommendations = this.generateRecommendations(agentMetrics, keywordEffectiveness, confidenceMetrics);
@@ -45,9 +54,10 @@ class RoutingPerformanceAnalyzer {
45
54
  */
46
55
  calculateAgentMetrics(outcomes, promptData) {
47
56
  const agentMap = new Map();
57
+ const promptByTask = this.firstByTaskId(promptData);
48
58
  for (const outcome of outcomes) {
49
59
  const agent = outcome.routedAgent;
50
- const prompt = promptData.find((p) => p.taskId === outcome.taskId);
60
+ const prompt = promptByTask.get(outcome.taskId);
51
61
  const confidence = prompt?.confidence ?? 0;
52
62
  const executionTime = prompt?.usageMetadata?.retryCount ?? 0;
53
63
  if (!agentMap.has(agent)) {
@@ -58,7 +68,7 @@ class RoutingPerformanceAnalyzer {
58
68
  failures: 0,
59
69
  escalated: 0,
60
70
  confidenceSum: 0,
61
- executionTimes: [],
71
+ executionSum: 0,
62
72
  highConfidence: 0,
63
73
  mediumConfidence: 0,
64
74
  lowConfidence: 0,
@@ -91,15 +101,12 @@ class RoutingPerformanceAnalyzer {
91
101
  if (confidence < 0.75) {
92
102
  metrics.escalated++;
93
103
  }
94
- metrics.executionTimes.push(executionTime);
104
+ metrics.executionSum += executionTime;
95
105
  }
96
106
  return Array.from(agentMap.entries())
97
107
  .filter(([, metrics]) => metrics.total >= this.minSamplesForMetrics)
98
108
  .map(([agent, metrics]) => {
99
- const avgExecutionTime = metrics.executionTimes.length > 0
100
- ? metrics.executionTimes.reduce((sum, time) => sum + time, 0) /
101
- metrics.executionTimes.length
102
- : 0;
109
+ const avgExecutionTime = metrics.total > 0 ? metrics.executionSum / metrics.total : 0;
103
110
  const timestamps = metrics.timestamps.sort((a, b) => a.getTime() - b.getTime());
104
111
  return {
105
112
  agent,
@@ -129,11 +136,12 @@ class RoutingPerformanceAnalyzer {
129
136
  */
130
137
  analyzeKeywordEffectiveness(decisions, outcomes) {
131
138
  const keywordMap = new Map();
139
+ const outcomeByTask = this.firstByTaskId(outcomes);
132
140
  for (const decision of decisions) {
133
141
  const keyword = decision.keywordMatched;
134
142
  if (!keyword)
135
143
  continue;
136
- const outcome = outcomes.find((o) => o.taskId === decision.taskId);
144
+ const outcome = outcomeByTask.get(decision.taskId);
137
145
  if (!outcome)
138
146
  continue;
139
147
  if (!keywordMap.has(keyword)) {
@@ -175,30 +183,43 @@ class RoutingPerformanceAnalyzer {
175
183
  /**
176
184
  * Analyze confidence threshold performance
177
185
  */
178
- analyzeConfidenceThresholds(outcomes) {
179
- const promptData = routingOutcomeTracker.getPromptData();
186
+ analyzeConfidenceThresholds(outcomes, promptData) {
180
187
  const thresholds = [0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95];
181
- return thresholds.map((threshold) => {
182
- const belowThresholdOutcomes = outcomes.filter((outcome) => {
183
- const prompt = promptData.find((p) => p.taskId === outcome.taskId);
184
- const confidence = prompt?.confidence ?? 1;
185
- return confidence < threshold;
186
- });
187
- const totalRoutings = belowThresholdOutcomes.length;
188
- const successfulRoutings = belowThresholdOutcomes.filter((o) => o.success === true).length;
189
- const successRate = totalRoutings > 0 ? successfulRoutings / totalRoutings : 0;
190
- const escalatedCount = belowThresholdOutcomes.length;
188
+ const promptByTask = this.firstByTaskId(promptData);
189
+ const totalRoutings = thresholds.map(() => 0);
190
+ const successfulRoutings = thresholds.map(() => 0);
191
+ for (const outcome of outcomes) {
192
+ const prompt = promptByTask.get(outcome.taskId);
193
+ const confidence = prompt?.confidence ?? 1;
194
+ for (let i = 0; i < thresholds.length; i++) {
195
+ const threshold = thresholds[i];
196
+ if (threshold === undefined || !(confidence < threshold))
197
+ continue;
198
+ const total = totalRoutings[i];
199
+ const successful = successfulRoutings[i];
200
+ if (total === undefined || successful === undefined)
201
+ continue;
202
+ totalRoutings[i] = total + 1;
203
+ if (outcome.success === true)
204
+ successfulRoutings[i] = successful + 1;
205
+ }
206
+ }
207
+ return thresholds.map((threshold, i) => {
208
+ const total = totalRoutings[i] ?? 0;
209
+ const successful = successfulRoutings[i] ?? 0;
210
+ const successRate = total > 0 ? successful / total : 0;
211
+ const escalatedCount = total;
191
212
  let recommendation = "Threshold is optimal";
192
- if (successRate < 0.6 && escalatedCount > totalRoutings * 0.5) {
213
+ if (successRate < 0.6 && escalatedCount > total * 0.5) {
193
214
  recommendation = "Consider lowering threshold to reduce escalations";
194
215
  }
195
- else if (successRate > 0.9 && escalatedCount < totalRoutings * 0.2) {
216
+ else if (successRate > 0.9 && escalatedCount < total * 0.2) {
196
217
  recommendation = "Consider raising threshold to improve routing precision";
197
218
  }
198
219
  return {
199
220
  threshold,
200
- totalRoutings,
201
- successfulRoutings,
221
+ totalRoutings: total,
222
+ successfulRoutings: successful,
202
223
  successRate,
203
224
  escalatedCount,
204
225
  recommendation,
@@ -209,10 +230,14 @@ class RoutingPerformanceAnalyzer {
209
230
  * Calculate overall routing statistics
210
231
  */
211
232
  calculateOverallStats(outcomes) {
212
- const successful = outcomes.filter((o) => o.success === true).length;
233
+ let successful = 0;
234
+ let totalConfidence = 0;
235
+ for (const outcome of outcomes) {
236
+ if (outcome.success === true)
237
+ successful++;
238
+ totalConfidence += outcome.confidence || 0;
239
+ }
213
240
  const successRate = outcomes.length > 0 ? successful / outcomes.length : 0;
214
- // Confidence is in outcomes, not promptData
215
- const totalConfidence = outcomes.reduce((sum, o) => sum + (o.confidence || 0), 0);
216
241
  const avgConfidence = outcomes.length > 0 ? totalConfidence / outcomes.length : 0;
217
242
  return { successRate, avgConfidence };
218
243
  }
@@ -228,16 +253,43 @@ class RoutingPerformanceAnalyzer {
228
253
  const d = ts instanceof Date ? ts : new Date(ts);
229
254
  return d.getTime();
230
255
  };
231
- const sorted = [...outcomes].sort((a, b) => getTime(a.timestamp) - getTime(b.timestamp));
232
- const now = new Date();
233
- const firstOutcome = sorted[0];
234
- const lastOutcome = sorted[sorted.length - 1];
235
- const firstTs = firstOutcome?.timestamp;
236
- const lastTs = lastOutcome?.timestamp;
237
- return {
238
- start: firstTs instanceof Date ? firstTs : (firstTs ? new Date(firstTs) : now),
239
- end: lastTs instanceof Date ? lastTs : (lastTs ? new Date(lastTs) : now),
256
+ const asRange = (firstTs, lastTs) => {
257
+ const now = new Date();
258
+ return {
259
+ start: firstTs instanceof Date ? firstTs : (firstTs ? new Date(firstTs) : now),
260
+ end: lastTs instanceof Date ? lastTs : (lastTs ? new Date(lastTs) : now),
261
+ };
240
262
  };
263
+ const firstOutcome = outcomes[0];
264
+ if (firstOutcome) {
265
+ let startTs = firstOutcome.timestamp;
266
+ let endTs = startTs;
267
+ let startTime = getTime(startTs);
268
+ let endTime = startTime;
269
+ let finite = !Number.isNaN(startTime);
270
+ for (let i = 1; finite && i < outcomes.length; i++) {
271
+ const outcome = outcomes[i];
272
+ if (!outcome)
273
+ continue;
274
+ const time = getTime(outcome.timestamp);
275
+ if (Number.isNaN(time)) {
276
+ finite = false;
277
+ break;
278
+ }
279
+ if (time < startTime) {
280
+ startTime = time;
281
+ startTs = outcome.timestamp;
282
+ }
283
+ if (time >= endTime) {
284
+ endTime = time;
285
+ endTs = outcome.timestamp;
286
+ }
287
+ }
288
+ if (finite)
289
+ return asRange(startTs, endTs);
290
+ }
291
+ const sorted = [...outcomes].sort((a, b) => getTime(a.timestamp) - getTime(b.timestamp));
292
+ return asRange(sorted[0]?.timestamp, sorted[sorted.length - 1]?.timestamp);
241
293
  }
242
294
  /**
243
295
  * Generate recommendations based on analysis
@@ -66,6 +66,10 @@ export declare class SimplePatternAnalyzer {
66
66
  * Calculate statistics from parsed entries
67
67
  */
68
68
  private calculateStats;
69
+ /**
70
+ * Earliest and latest timestamp. Matches a stable sort on finite times.
71
+ */
72
+ private entryDateRange;
69
73
  /**
70
74
  * Generate human-readable insights
71
75
  */