strray-ai 1.13.3 → 1.13.5

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.
@@ -146,6 +146,44 @@ function extractTaskDescription(input) {
146
146
  }
147
147
  return null;
148
148
  }
149
+ /**
150
+ * Estimate complexity score based on message content
151
+ * Higher complexity = orchestrator routing
152
+ * Lower complexity = code-reviewer routing
153
+ */
154
+ function estimateComplexity(message) {
155
+ const text = message.toLowerCase();
156
+ // High complexity indicators
157
+ const highComplexityKeywords = [
158
+ "architecture", "system", "design", "complex", "multiple",
159
+ "integrate", "database", "migration", "refactor",
160
+ "performance", "optimize", "security", "audit",
161
+ "orchestrate", "coordinate", "workflow"
162
+ ];
163
+ // Low complexity indicators
164
+ const lowComplexityKeywords = [
165
+ "review", "check", "simple", "quick", "fix",
166
+ "small", "typo", "format", "lint", "test"
167
+ ];
168
+ let score = 50; // default medium
169
+ // Check message length
170
+ if (message.length > 200)
171
+ score += 10;
172
+ if (message.length > 500)
173
+ score += 15;
174
+ // Check for high complexity keywords
175
+ for (const keyword of highComplexityKeywords) {
176
+ if (text.includes(keyword))
177
+ score += 8;
178
+ }
179
+ // Check for low complexity keywords
180
+ for (const keyword of lowComplexityKeywords) {
181
+ if (text.includes(keyword))
182
+ score -= 5;
183
+ }
184
+ // Clamp to 0-100
185
+ return Math.max(0, Math.min(100, score));
186
+ }
149
187
  async function loadTaskSkillRouter() {
150
188
  if (taskSkillRouterInstance) {
151
189
  return; // Already loaded
@@ -402,34 +440,7 @@ export default async function strrayCodexPlugin(input) {
402
440
  showEssentialLinks: true
403
441
  });
404
442
  }
405
- // ============================================================
406
- // PROMPT-LEVEL ROUTING: Route user prompts to best agent
407
- // ============================================================
408
- const userPrompt = String(_input.prompt || _input.message || _input.content || "");
409
- if (userPrompt && userPrompt.length > 0) {
410
- try {
411
- await loadTaskSkillRouter();
412
- if (taskSkillRouterInstance) {
413
- const routingResult = taskSkillRouterInstance.routeTask(userPrompt, {
414
- source: "prompt",
415
- });
416
- if (routingResult && routingResult.agent) {
417
- const logger = await getOrCreateLogger(directory);
418
- logger.log(`šŸŽÆ Prompt routed: "${userPrompt.slice(0, 50)}${userPrompt.length > 50 ? "..." : ""}" → ${routingResult.agent} (confidence: ${routingResult.confidence})`);
419
- // Add routing context to system prompt
420
- leanPrompt += `\n\nšŸŽÆ Recommended Agent: @${routingResult.agent}\n`;
421
- leanPrompt += `šŸ“Š Confidence: ${Math.round(routingResult.confidence * 100)}%\n`;
422
- if (routingResult.context?.complexity > 50) {
423
- leanPrompt += `āš ļø High complexity detected - consider using @orchestrator\n`;
424
- }
425
- }
426
- }
427
- }
428
- catch (e) {
429
- const logger = await getOrCreateLogger(directory);
430
- logger.error("Prompt routing error:", e);
431
- }
432
- }
443
+ // Routing is handled in chat.message hook - this hook only does system prompt injection
433
444
  if (output.system && Array.isArray(output.system)) {
434
445
  output.system = [leanPrompt];
435
446
  }
@@ -465,33 +476,7 @@ export default async function strrayCodexPlugin(input) {
465
476
  }
466
477
  }
467
478
  const { tool, args } = input;
468
- // ============================================================
469
- // TASK ROUTING: Analyze task and route to best agent
470
- // Enabled in v1.10.5 - provides analytics data
471
- // ============================================================
472
- const taskDescription = extractTaskDescription(input);
473
- if (taskDescription && featuresConfigLoader) {
474
- try {
475
- await loadTaskSkillRouter();
476
- if (taskSkillRouterInstance) {
477
- const routingResult = taskSkillRouterInstance.routeTask(taskDescription, {
478
- toolName: tool,
479
- });
480
- if (routingResult && routingResult.agent) {
481
- logger.log(`šŸŽÆ Task routed: "${taskDescription.slice(0, 50)}..." → ${routingResult.agent} (confidence: ${routingResult.confidence})`);
482
- // Store routing result for downstream processing
483
- output._strrayRouting = routingResult;
484
- // If complexity is high, log a warning
485
- if (routingResult.context?.complexity > 50) {
486
- logger.log(`āš ļø High complexity task detected (${routingResult.context.complexity}) - consider multi-agent orchestration`);
487
- }
488
- }
489
- }
490
- }
491
- catch (e) {
492
- logger.error("Task routing error:", e);
493
- }
494
- }
479
+ // Routing is handled in chat.message hook - this hook only does tool execution logging
495
480
  // ENFORCER QUALITY GATE CHECK - Block on violations
496
481
  await importQualityGate(directory);
497
482
  if (!runQualityGateWithLogging) {
@@ -714,47 +699,101 @@ export default async function strrayCodexPlugin(input) {
714
699
  }
715
700
  },
716
701
  /**
717
- * experimental.chat.user.before - Intercept user messages for routing
718
- * This hook fires before the user's message is sent to the LLM
702
+ * chat.message - Intercept user messages for routing
703
+ * Output contains message and parts with user content
719
704
  */
720
- "experimental.chat.user.before": async (input, output) => {
705
+ "chat.message": async (input, output) => {
721
706
  const logger = await getOrCreateLogger(directory);
722
- // Get user message
723
- const userContent = String(input.content || input.message || input.prompt || "");
724
- if (!userContent || userContent.length === 0) {
707
+ // DEBUG: Log ALL output
708
+ const debugLogPath = path.join(process.cwd(), "logs", "framework", "routing-debug.log");
709
+ fs.appendFileSync(debugLogPath, `\n[${new Date().toISOString()}] === chat.message HOOK FIRED ===\n`);
710
+ fs.appendFileSync(debugLogPath, `OUTPUT KEYS: ${JSON.stringify(Object.keys(output || {}))}\n`);
711
+ fs.appendFileSync(debugLogPath, `MESSAGE: ${JSON.stringify(output?.message)}\n`);
712
+ fs.appendFileSync(debugLogPath, `PARTS: ${JSON.stringify(output?.parts)}\n`);
713
+ // Extract user message from parts (TextPart has type="text" and text field)
714
+ let userMessage = "";
715
+ if (output?.parts && Array.isArray(output.parts)) {
716
+ for (const part of output.parts) {
717
+ if (part?.type === "text" && part?.text) {
718
+ userMessage = part.text;
719
+ break;
720
+ }
721
+ }
722
+ }
723
+ fs.appendFileSync(debugLogPath, `userMessage: "${userMessage.slice(0, 100)}"\n`);
724
+ if (!userMessage || userMessage.length === 0) {
725
+ fs.appendFileSync(debugLogPath, `SKIP: No user text found\n`);
725
726
  return;
726
727
  }
727
- logger.log(`šŸ‘¤ User message received: "${userContent.slice(0, 50)}${userContent.length > 50 ? "..." : ""}"`);
728
+ logger.log(`šŸ‘¤ User message: "${userMessage.slice(0, 50)}..."`);
728
729
  try {
729
730
  await loadTaskSkillRouter();
730
731
  if (taskSkillRouterInstance) {
731
- // Route based on user content
732
- const routingResult = taskSkillRouterInstance.routeTask(userContent, {
733
- source: "user_message",
732
+ // Get complexity score for tiebreaking
733
+ let complexityScore = 50; // default medium
734
+ try {
735
+ if (featuresConfigLoader) {
736
+ const config = featuresConfigLoader.loadConfig();
737
+ if (config.model_routing?.complexity?.enabled) {
738
+ // Estimate complexity based on message length and keywords
739
+ complexityScore = estimateComplexity(userMessage);
740
+ }
741
+ }
742
+ }
743
+ catch (e) {
744
+ // Silent fail for complexity estimation
745
+ }
746
+ fs.appendFileSync(debugLogPath, `Complexity estimated: ${complexityScore}\n`);
747
+ // Route with complexity context
748
+ const routingResult = taskSkillRouterInstance.routeTask(userMessage, {
749
+ source: "chat_message",
750
+ complexity: complexityScore,
734
751
  });
752
+ fs.appendFileSync(debugLogPath, `Routing result: ${JSON.stringify(routingResult)}\n`);
735
753
  if (routingResult && routingResult.agent) {
736
- logger.log(`šŸŽÆ User message routed to: @${routingResult.agent} (confidence: ${Math.round(routingResult.confidence * 100)}%)`);
737
- // Add routing hint to user's message
738
- const routingHint = `[Suggested Agent: @${routingResult.agent}]\n`;
739
- // Modify output to include routing hint
740
- if (output.content !== undefined) {
741
- output.content = routingHint + output.content;
754
+ // Apply weighted confidence scoring
755
+ let finalConfidence = routingResult.confidence;
756
+ let routingMethod = "keyword";
757
+ // If keyword confidence is low, use complexity-based routing
758
+ if (routingResult.confidence < 0.7 && complexityScore > 50) {
759
+ // High complexity tasks get orchestrator boost
760
+ if (complexityScore > 70) {
761
+ routingResult.agent = "orchestrator";
762
+ finalConfidence = Math.min(0.85, routingResult.confidence + 0.15);
763
+ routingMethod = "complexity";
764
+ }
742
765
  }
743
- else if (output.message !== undefined) {
744
- output.message = routingHint + output.message;
766
+ // If low complexity and low confidence, boost code-reviewer
767
+ if (routingResult.confidence < 0.6 && complexityScore < 30) {
768
+ routingResult.agent = "code-reviewer";
769
+ finalConfidence = Math.min(0.75, routingResult.confidence + 0.15);
770
+ routingMethod = "complexity";
771
+ }
772
+ logger.log(`šŸŽÆ Routed to: @${routingResult.agent} (${Math.round(finalConfidence * 100)}%) via ${routingMethod}`);
773
+ fs.appendFileSync(debugLogPath, `Final agent: ${routingResult.agent}, confidence: ${finalConfidence}, method: ${routingMethod}\n`);
774
+ // Store routing in session for later use
775
+ const sessionRoutingPath = path.join(process.cwd(), "logs", "framework", "session-routing.json");
776
+ try {
777
+ fs.appendFileSync(sessionRoutingPath, JSON.stringify({
778
+ timestamp: new Date().toISOString(),
779
+ message: userMessage.slice(0, 100),
780
+ agent: routingResult.agent,
781
+ confidence: finalConfidence,
782
+ method: routingMethod,
783
+ complexity: complexityScore,
784
+ }) + "\n");
785
+ }
786
+ catch (e) {
787
+ // Silent fail for session routing logging
745
788
  }
746
- // Log routing outcome
747
- logToolActivity(directory, "routing", "user_message", {
748
- agent: routingResult.agent,
749
- confidence: routingResult.confidence,
750
- skill: routingResult.skill,
751
- });
752
789
  }
753
790
  }
754
791
  }
755
792
  catch (e) {
756
- logger.error("User message routing error:", e);
793
+ logger.error("Chat message routing error:", e);
794
+ fs.appendFileSync(debugLogPath, `ERROR: ${e}\n`);
757
795
  }
796
+ fs.appendFileSync(debugLogPath, `=== END chat.message ===\n`);
758
797
  },
759
798
  config: async (_config) => {
760
799
  const logger = await getOrCreateLogger(directory);