tachibot-mcp 2.0.2

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 (214) hide show
  1. package/.env.example +260 -0
  2. package/CHANGELOG.md +54 -0
  3. package/CODE_OF_CONDUCT.md +56 -0
  4. package/CONTRIBUTING.md +54 -0
  5. package/Dockerfile +36 -0
  6. package/LICENSE +644 -0
  7. package/README.md +201 -0
  8. package/SECURITY.md +95 -0
  9. package/dist/personality/komaai-expressions.js +12 -0
  10. package/dist/profiles/balanced.json +33 -0
  11. package/dist/profiles/code_focus.json +33 -0
  12. package/dist/profiles/full.json +33 -0
  13. package/dist/profiles/minimal.json +33 -0
  14. package/dist/profiles/research_power.json +33 -0
  15. package/dist/scripts/build-profiles.js +46 -0
  16. package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
  17. package/dist/src/application/services/focus/FocusTool.service.js +109 -0
  18. package/dist/src/application/services/focus/ModeRegistry.js +46 -0
  19. package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
  20. package/dist/src/application/services/focus/modes/status.mode.js +50 -0
  21. package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
  22. package/dist/src/collaborative-orchestrator.js +391 -0
  23. package/dist/src/config/model-constants.js +188 -0
  24. package/dist/src/config/model-defaults.js +57 -0
  25. package/dist/src/config/model-preferences.js +382 -0
  26. package/dist/src/config/timeout-config.js +130 -0
  27. package/dist/src/config.js +173 -0
  28. package/dist/src/domain/interfaces/IFocusMode.js +5 -0
  29. package/dist/src/domain/interfaces/IProvider.js +6 -0
  30. package/dist/src/domain/interfaces/ITool.js +5 -0
  31. package/dist/src/focus-deep.js +245 -0
  32. package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
  33. package/dist/src/mcp-client.js +90 -0
  34. package/dist/src/memory/index.js +17 -0
  35. package/dist/src/memory/memory-config.js +135 -0
  36. package/dist/src/memory/memory-interface.js +174 -0
  37. package/dist/src/memory/memory-manager.js +383 -0
  38. package/dist/src/memory/providers/devlog-provider.js +385 -0
  39. package/dist/src/memory/providers/hybrid-provider.js +399 -0
  40. package/dist/src/memory/providers/local-provider.js +388 -0
  41. package/dist/src/memory/providers/mem0-provider.js +337 -0
  42. package/dist/src/modes/architect.js +477 -0
  43. package/dist/src/modes/auditor.js +362 -0
  44. package/dist/src/modes/challenger.js +841 -0
  45. package/dist/src/modes/code-reviewer.js +382 -0
  46. package/dist/src/modes/commit-guardian.js +424 -0
  47. package/dist/src/modes/documentation-writer.js +572 -0
  48. package/dist/src/modes/scout.js +587 -0
  49. package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
  50. package/dist/src/modes/shared/helpers/index.js +17 -0
  51. package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
  52. package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
  53. package/dist/src/modes/test-architect.js +767 -0
  54. package/dist/src/modes/verifier.js +378 -0
  55. package/dist/src/monitoring/performance-monitor.js +435 -0
  56. package/dist/src/optimization/batch-executor.js +121 -0
  57. package/dist/src/optimization/context-pruner.js +196 -0
  58. package/dist/src/optimization/cost-monitor.js +338 -0
  59. package/dist/src/optimization/index.js +65 -0
  60. package/dist/src/optimization/model-router.js +264 -0
  61. package/dist/src/optimization/result-cache.js +114 -0
  62. package/dist/src/optimization/token-optimizer.js +257 -0
  63. package/dist/src/optimization/token-tracker.js +118 -0
  64. package/dist/src/orchestrator-instructions.js +128 -0
  65. package/dist/src/orchestrator-lite.js +139 -0
  66. package/dist/src/orchestrator.js +191 -0
  67. package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
  68. package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
  69. package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
  70. package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
  71. package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
  72. package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
  73. package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
  74. package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
  75. package/dist/src/profiles/balanced.js +37 -0
  76. package/dist/src/profiles/code_focus.js +37 -0
  77. package/dist/src/profiles/debug_intensive.js +59 -0
  78. package/dist/src/profiles/full.js +37 -0
  79. package/dist/src/profiles/minimal.js +37 -0
  80. package/dist/src/profiles/research_code.js +59 -0
  81. package/dist/src/profiles/research_power.js +37 -0
  82. package/dist/src/profiles/types.js +5 -0
  83. package/dist/src/profiles/workflow_builder.js +53 -0
  84. package/dist/src/prompt-engineer-lite.js +78 -0
  85. package/dist/src/prompt-engineer.js +399 -0
  86. package/dist/src/reasoning-chain.js +508 -0
  87. package/dist/src/sequential-thinking.js +291 -0
  88. package/dist/src/server-diagnostic.js +74 -0
  89. package/dist/src/server-raw.js +158 -0
  90. package/dist/src/server-simple.js +58 -0
  91. package/dist/src/server.js +514 -0
  92. package/dist/src/session/session-logger.js +617 -0
  93. package/dist/src/session/session-manager.js +571 -0
  94. package/dist/src/session/session-tools.js +400 -0
  95. package/dist/src/tools/advanced-modes.js +200 -0
  96. package/dist/src/tools/claude-integration.js +356 -0
  97. package/dist/src/tools/consolidated/ai-router.js +174 -0
  98. package/dist/src/tools/consolidated/ai-tool.js +48 -0
  99. package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
  100. package/dist/src/tools/consolidated/environment-detector.js +80 -0
  101. package/dist/src/tools/consolidated/index.js +50 -0
  102. package/dist/src/tools/consolidated/search-tool.js +110 -0
  103. package/dist/src/tools/consolidated/workflow-tool.js +238 -0
  104. package/dist/src/tools/gemini-tools.js +329 -0
  105. package/dist/src/tools/grok-enhanced.js +376 -0
  106. package/dist/src/tools/grok-tools.js +299 -0
  107. package/dist/src/tools/lmstudio-tools.js +223 -0
  108. package/dist/src/tools/openai-tools.js +498 -0
  109. package/dist/src/tools/openrouter-tools.js +317 -0
  110. package/dist/src/tools/optimized-wrapper.js +204 -0
  111. package/dist/src/tools/perplexity-tools.js +294 -0
  112. package/dist/src/tools/pingpong-tool.js +343 -0
  113. package/dist/src/tools/qwen-wrapper.js +74 -0
  114. package/dist/src/tools/tool-router.js +444 -0
  115. package/dist/src/tools/unified-ai-provider.js +260 -0
  116. package/dist/src/tools/workflow-runner.js +425 -0
  117. package/dist/src/tools/workflow-validator-tool.js +107 -0
  118. package/dist/src/types.js +23 -0
  119. package/dist/src/utils/input-validator.js +130 -0
  120. package/dist/src/utils/model-router.js +91 -0
  121. package/dist/src/utils/progress-stream.js +255 -0
  122. package/dist/src/utils/provider-router.js +88 -0
  123. package/dist/src/utils/smart-api-client.js +146 -0
  124. package/dist/src/utils/table-builder.js +218 -0
  125. package/dist/src/utils/timestamp-formatter.js +134 -0
  126. package/dist/src/utils/tool-compressor.js +257 -0
  127. package/dist/src/utils/tool-config.js +201 -0
  128. package/dist/src/validators/dependency-graph-validator.js +147 -0
  129. package/dist/src/validators/interpolation-validator.js +222 -0
  130. package/dist/src/validators/output-usage-validator.js +151 -0
  131. package/dist/src/validators/syntax-validator.js +102 -0
  132. package/dist/src/validators/tool-registry-validator.js +123 -0
  133. package/dist/src/validators/tool-types.js +97 -0
  134. package/dist/src/validators/types.js +8 -0
  135. package/dist/src/validators/workflow-validator.js +134 -0
  136. package/dist/src/visualizer-lite.js +42 -0
  137. package/dist/src/visualizer.js +179 -0
  138. package/dist/src/workflows/circuit-breaker.js +199 -0
  139. package/dist/src/workflows/custom-workflows.js +451 -0
  140. package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
  141. package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
  142. package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
  143. package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
  144. package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
  145. package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
  146. package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
  147. package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
  148. package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
  149. package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
  150. package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
  151. package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
  152. package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
  153. package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
  154. package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
  155. package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
  156. package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
  157. package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
  158. package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
  159. package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
  160. package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
  161. package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
  162. package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
  163. package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
  164. package/dist/src/workflows/fallback-strategies.js +373 -0
  165. package/dist/src/workflows/message-queue.js +455 -0
  166. package/dist/src/workflows/model-router.js +189 -0
  167. package/dist/src/workflows/orchestrator-examples.js +174 -0
  168. package/dist/src/workflows/orchestrator-integration.js +200 -0
  169. package/dist/src/workflows/self-healing.js +524 -0
  170. package/dist/src/workflows/tool-mapper.js +407 -0
  171. package/dist/src/workflows/tool-orchestrator.js +796 -0
  172. package/dist/src/workflows/workflow-engine.js +573 -0
  173. package/dist/src/workflows/workflow-parser.js +283 -0
  174. package/dist/src/workflows/workflow-types.js +95 -0
  175. package/dist/src/workflows.js +568 -0
  176. package/dist/test-workflow-file-output.js +93 -0
  177. package/docs/API_KEYS.md +570 -0
  178. package/docs/CLAUDE_CODE_SETUP.md +181 -0
  179. package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
  180. package/docs/CONFIGURATION.md +745 -0
  181. package/docs/FOCUS_MODES.md +240 -0
  182. package/docs/INSTALLATION_BOTH.md +145 -0
  183. package/docs/TERMS.md +352 -0
  184. package/docs/TOOLS_REFERENCE.md +1622 -0
  185. package/docs/TOOL_PARAMETERS.md +496 -0
  186. package/docs/TOOL_PROFILES.md +236 -0
  187. package/docs/WORKFLOWS.md +987 -0
  188. package/docs/WORKFLOW_OUTPUT.md +198 -0
  189. package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
  190. package/docs/workflows/design-brainstorm.md +335 -0
  191. package/package.json +97 -0
  192. package/profiles/balanced.json +37 -0
  193. package/profiles/code_focus.json +37 -0
  194. package/profiles/debug_intensive.json +34 -0
  195. package/profiles/full.json +37 -0
  196. package/profiles/minimal.json +37 -0
  197. package/profiles/research_power.json +37 -0
  198. package/profiles/workflow_builder.json +37 -0
  199. package/smithery.yaml +66 -0
  200. package/start.sh +8 -0
  201. package/tools.config.json +81 -0
  202. package/tsconfig.json +18 -0
  203. package/workflows/accessibility-code-audit.yaml +92 -0
  204. package/workflows/code-architecture-review.yaml +202 -0
  205. package/workflows/code-review.yaml +142 -0
  206. package/workflows/core/iterative-problem-solver.yaml +283 -0
  207. package/workflows/creative-brainstorm-yaml.yaml +215 -0
  208. package/workflows/pingpong.yaml +141 -0
  209. package/workflows/system/README.md +412 -0
  210. package/workflows/system/challenger.yaml +175 -0
  211. package/workflows/system/scout.yaml +164 -0
  212. package/workflows/system/verifier.yaml +133 -0
  213. package/workflows/ultra-creative-brainstorm.yaml +318 -0
  214. package/workflows/ux-research-flow.yaml +92 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Workflow validator MCP tool
3
+ */
4
+ import { z } from 'zod';
5
+ import { workflowValidator } from '../validators/workflow-validator.js';
6
+ // Get all registered tools from the server
7
+ // We'll treat ALL known tools as "enabled" for validation purposes
8
+ // The tool validator will distinguish between "unknown" (error) and "disabled" (warning)
9
+ function getAllKnownTools() {
10
+ // Return all known tool names
11
+ return new Set([
12
+ // Core
13
+ 'think', 'focus', 'nextThought',
14
+ // Perplexity
15
+ 'perplexity_ask', 'perplexity_reason', 'perplexity_research',
16
+ // Grok
17
+ 'grok_reason', 'grok_code', 'grok_debug', 'grok_architect', 'grok_brainstorm', 'grok_search',
18
+ // OpenAI
19
+ 'openai_compare', 'openai_brainstorm', 'openai_gpt5_reason', 'openai_code_review', 'openai_explain',
20
+ // Gemini
21
+ 'gemini_brainstorm', 'gemini_analyze_code', 'gemini_analyze_text',
22
+ // Qwen
23
+ 'qwen_coder',
24
+ // Kimi
25
+ 'kimi_thinking',
26
+ // Advanced modes
27
+ 'verifier', 'scout', 'challenger', 'hunter',
28
+ // Workflow
29
+ 'workflow', 'list_workflows', 'create_workflow', 'visualize_workflow',
30
+ // Collaborative
31
+ 'pingpong', 'qwen_competitive'
32
+ ]);
33
+ }
34
+ export const validateWorkflowTool = {
35
+ name: 'validate_workflow',
36
+ description: `Validates workflow YAML/JSON files for correctness.
37
+
38
+ Checks:
39
+ - Valid YAML/JSON syntax
40
+ - Interpolation references (\${step.output}, \${variable}) point to valid steps/variables
41
+ - Tool names exist and are enabled in tools.config.json
42
+ - No circular dependencies in step execution order
43
+ - Variable names follow snake_case convention
44
+
45
+ Returns detailed error messages with suggestions for fixing issues.`,
46
+ parameters: z.object({
47
+ workflowContent: z.string()
48
+ .describe('The YAML or JSON content of the workflow to validate'),
49
+ isJson: z.boolean()
50
+ .optional()
51
+ .default(false)
52
+ .describe('Set to true if the content is JSON instead of YAML'),
53
+ format: z.enum(['text', 'json'])
54
+ .optional()
55
+ .default('text')
56
+ .describe('Output format: "text" for human-readable, "json" for structured data')
57
+ }),
58
+ execute: async (args) => {
59
+ try {
60
+ const result = await workflowValidator.validate(args.workflowContent, args.isJson || false, getAllKnownTools());
61
+ if (args.format === 'json') {
62
+ return workflowValidator.formatResultsJSON(result);
63
+ }
64
+ else {
65
+ return workflowValidator.formatResults(result);
66
+ }
67
+ }
68
+ catch (error) {
69
+ return `❌ Validation error: ${error.message}`;
70
+ }
71
+ }
72
+ };
73
+ export const validateWorkflowFileTool = {
74
+ name: 'validate_workflow_file',
75
+ description: `Validates a workflow file from the filesystem.
76
+
77
+ Checks:
78
+ - Valid YAML/JSON syntax
79
+ - Interpolation references (\${step.output}, \${variable}) point to valid steps/variables
80
+ - Tool names exist and are enabled in tools.config.json
81
+ - No circular dependencies in step execution order
82
+ - Variable names follow snake_case convention
83
+
84
+ Returns detailed error messages with suggestions for fixing issues.`,
85
+ parameters: z.object({
86
+ filePath: z.string()
87
+ .describe('Path to the workflow file (YAML or JSON)'),
88
+ format: z.enum(['text', 'json'])
89
+ .optional()
90
+ .default('text')
91
+ .describe('Output format: "text" for human-readable, "json" for structured data')
92
+ }),
93
+ execute: async (args) => {
94
+ try {
95
+ const result = await workflowValidator.validateFile(args.filePath, getAllKnownTools());
96
+ if (args.format === 'json') {
97
+ return workflowValidator.formatResultsJSON(result);
98
+ }
99
+ else {
100
+ return workflowValidator.formatResults(result);
101
+ }
102
+ }
103
+ catch (error) {
104
+ return `❌ Validation error: ${error.message}`;
105
+ }
106
+ }
107
+ };
@@ -0,0 +1,23 @@
1
+ export var WorkflowType;
2
+ (function (WorkflowType) {
3
+ WorkflowType["CREATIVE_DISCOVERY"] = "creative_discovery";
4
+ WorkflowType["DEEP_RESEARCH"] = "deep_research";
5
+ WorkflowType["PROBLEM_SOLVING"] = "problem_solving";
6
+ WorkflowType["SYNTHESIS"] = "synthesis";
7
+ WorkflowType["FACT_CHECK"] = "fact_check";
8
+ // New Developer-Focused Workflows
9
+ WorkflowType["RAPID_PROTOTYPE"] = "rapid_prototype";
10
+ WorkflowType["CODE_QUALITY"] = "code_quality";
11
+ WorkflowType["SECURE_DEPLOYMENT"] = "secure_deployment";
12
+ WorkflowType["FULL_STACK_DEVELOPMENT"] = "full_stack_development";
13
+ WorkflowType["TEST_DRIVEN_DEVELOPMENT"] = "test_driven_development";
14
+ WorkflowType["CODE_REVIEW_WORKFLOW"] = "code_review_workflow";
15
+ WorkflowType["DOCUMENTATION_GENERATION"] = "documentation_generation";
16
+ })(WorkflowType || (WorkflowType = {}));
17
+ export var ToolStatus;
18
+ (function (ToolStatus) {
19
+ ToolStatus["IDLE"] = "idle";
20
+ ToolStatus["PROCESSING"] = "processing";
21
+ ToolStatus["COMPLETE"] = "complete";
22
+ ToolStatus["ERROR"] = "error";
23
+ })(ToolStatus || (ToolStatus = {}));
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Input Validation and Sanitization Utilities
3
+ * Prevents prompt injection, XSS, and other security issues
4
+ */
5
+ // Security constants
6
+ export const MAX_INPUT_LENGTH = 50000; // 50k chars max
7
+ export const MAX_PROMPT_LENGTH = 20000;
8
+ export const MAX_SYSTEM_PROMPT_LENGTH = 5000;
9
+ // Patterns to detect potential injection attempts
10
+ // NOTE: Role injection (user:/assistant:/system:) pattern removed due to false positives
11
+ // with legitimate LLM-generated content. For LLM-to-LLM calls, use skipValidation flag.
12
+ const SUSPICIOUS_PATTERNS = [
13
+ /<\s*script/gi, // XSS attempts
14
+ /\b(exec|eval|require)\s*\(/gi, // Code execution attempts (must be function calls)
15
+ /;\s*(rm|del|format|drop\s+table)/gi, // Command/SQL injection
16
+ /\.\.\//g, // Path traversal
17
+ /\x00/g, // Null byte injection
18
+ ];
19
+ /**
20
+ * Sanitize user input - remove control characters and limit length
21
+ */
22
+ export function sanitizeInput(input, maxLength = MAX_INPUT_LENGTH) {
23
+ if (typeof input !== 'string') {
24
+ throw new Error('Input must be a string');
25
+ }
26
+ // Remove control characters (except newlines, tabs)
27
+ let sanitized = input.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
28
+ // Limit length
29
+ if (sanitized.length > maxLength) {
30
+ sanitized = sanitized.slice(0, maxLength);
31
+ }
32
+ return sanitized;
33
+ }
34
+ /**
35
+ * Validate and sanitize tool input
36
+ */
37
+ export function validateToolInput(input) {
38
+ try {
39
+ if (typeof input === 'string') {
40
+ // Check for empty input
41
+ if (input.trim().length === 0) {
42
+ return {
43
+ valid: false,
44
+ error: 'Input cannot be empty'
45
+ };
46
+ }
47
+ // Check length before sanitization
48
+ if (input.length > MAX_INPUT_LENGTH) {
49
+ return {
50
+ valid: false,
51
+ error: `Input too long (max ${MAX_INPUT_LENGTH} characters)`
52
+ };
53
+ }
54
+ // Check for suspicious patterns
55
+ for (const pattern of SUSPICIOUS_PATTERNS) {
56
+ if (pattern.test(input)) {
57
+ return {
58
+ valid: false,
59
+ error: 'Input contains potentially malicious content'
60
+ };
61
+ }
62
+ }
63
+ return {
64
+ valid: true,
65
+ sanitized: sanitizeInput(input)
66
+ };
67
+ }
68
+ if (typeof input === 'object' && input !== null) {
69
+ // Recursively sanitize object properties
70
+ const sanitized = Array.isArray(input) ? [] : {};
71
+ for (const [key, value] of Object.entries(input)) {
72
+ if (typeof value === 'string') {
73
+ const result = validateToolInput(value);
74
+ if (!result.valid)
75
+ return result;
76
+ sanitized[key] = result.sanitized;
77
+ }
78
+ else if (typeof value === 'object') {
79
+ const result = validateToolInput(value);
80
+ if (!result.valid)
81
+ return result;
82
+ sanitized[key] = result.sanitized;
83
+ }
84
+ else {
85
+ sanitized[key] = value;
86
+ }
87
+ }
88
+ return { valid: true, sanitized };
89
+ }
90
+ return { valid: true, sanitized: input };
91
+ }
92
+ catch (error) {
93
+ return {
94
+ valid: false,
95
+ error: error instanceof Error ? error.message : 'Validation error'
96
+ };
97
+ }
98
+ }
99
+ /**
100
+ * Redact secrets from strings before logging
101
+ */
102
+ export function sanitizeForLogging(text) {
103
+ let sanitized = text;
104
+ // Redact API keys
105
+ sanitized = sanitized.replace(/Bearer\s+[\w-]+/gi, 'Bearer [REDACTED]');
106
+ sanitized = sanitized.replace(/sk-[\w-]+/g, 'sk-[REDACTED]');
107
+ sanitized = sanitized.replace(/gsk_[\w-]+/g, 'gsk_[REDACTED]');
108
+ // Redact environment variable values
109
+ sanitized = sanitized.replace(/(API_KEY|TOKEN|SECRET)=[\w-]+/gi, '$1=[REDACTED]');
110
+ // Redact potential tokens in errors
111
+ sanitized = sanitized.replace(/["'][\w-]{32,}["']/g, '"[REDACTED]"');
112
+ return sanitized;
113
+ }
114
+ /**
115
+ * Validate model name to prevent injection
116
+ */
117
+ export function validateModelName(model) {
118
+ // Allow only alphanumeric, hyphens, underscores, dots
119
+ const validPattern = /^[a-zA-Z0-9._-]+$/;
120
+ return validPattern.test(model) && model.length < 100;
121
+ }
122
+ /**
123
+ * Sanitize error for safe logging
124
+ */
125
+ export function sanitizeError(error) {
126
+ if (error instanceof Error) {
127
+ return sanitizeForLogging(error.message);
128
+ }
129
+ return sanitizeForLogging(String(error));
130
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Shared Model Router Utility
3
+ *
4
+ * Centralizes AI provider routing logic to eliminate duplication across
5
+ * Challenger, Verifier, Scout, and other tools.
6
+ */
7
+ /**
8
+ * Routes model requests to appropriate AI provider based on model name
9
+ */
10
+ export class SharedModelRouter {
11
+ /**
12
+ * Call any AI model with automatic provider routing
13
+ */
14
+ async callModel(options) {
15
+ const { model, prompt, maxTokens = 2000, temperature = 0.7, messages } = options;
16
+ const requestMessages = messages || [{ role: 'user', content: prompt }];
17
+ try {
18
+ let content;
19
+ let provider;
20
+ // Route to appropriate provider based on model name
21
+ if (model.includes('gemini')) {
22
+ const { callGemini } = await import('../tools/gemini-tools.js');
23
+ content = await callGemini(prompt, model, undefined, temperature);
24
+ provider = 'Google Gemini';
25
+ }
26
+ else if (model.includes('grok')) {
27
+ const { callGrok } = await import('../tools/grok-tools.js');
28
+ content = await callGrok(requestMessages, model, temperature, maxTokens);
29
+ provider = 'xAI Grok';
30
+ }
31
+ else if (model.includes('qwen') || model.includes('qwq')) {
32
+ // Qwen models go through OpenRouter
33
+ const { callOpenRouter } = await import('../tools/openrouter-tools.js');
34
+ content = await callOpenRouter(requestMessages, model, temperature, maxTokens);
35
+ provider = 'OpenRouter';
36
+ }
37
+ else if (model.includes('gpt')) {
38
+ const { callOpenAI } = await import('../tools/openai-tools.js');
39
+ content = await callOpenAI(requestMessages, model, temperature, maxTokens);
40
+ provider = 'OpenAI';
41
+ }
42
+ else if (model.includes('perplexity') || model.includes('sonar')) {
43
+ const { callPerplexity } = await import('../tools/perplexity-tools.js');
44
+ content = await callPerplexity(requestMessages, model);
45
+ provider = 'Perplexity';
46
+ }
47
+ else {
48
+ throw new Error(`Unknown model: ${model}. Supported providers: Gemini, Grok, OpenRouter (qwen/qwq), OpenAI (gpt), Perplexity (sonar)`);
49
+ }
50
+ return {
51
+ content,
52
+ tokens: Math.floor(content.length / 4),
53
+ provider
54
+ };
55
+ }
56
+ catch (error) {
57
+ console.error(`Error calling model ${model}:`, error);
58
+ throw new Error(`Failed to call ${model}: ${error.message}`);
59
+ }
60
+ }
61
+ /**
62
+ * Get provider name for a model (useful for logging)
63
+ */
64
+ getProviderName(model) {
65
+ if (model.includes('gemini'))
66
+ return 'Google Gemini';
67
+ if (model.includes('grok'))
68
+ return 'xAI Grok';
69
+ if (model.includes('qwen') || model.includes('qwq'))
70
+ return 'OpenRouter';
71
+ if (model.includes('gpt'))
72
+ return 'OpenAI';
73
+ if (model.includes('perplexity') || model.includes('sonar'))
74
+ return 'Perplexity';
75
+ return 'Unknown';
76
+ }
77
+ /**
78
+ * Check if a model is supported
79
+ */
80
+ isModelSupported(model) {
81
+ return (model.includes('gemini') ||
82
+ model.includes('grok') ||
83
+ model.includes('gpt') ||
84
+ model.includes('qwen') ||
85
+ model.includes('qwq') ||
86
+ model.includes('perplexity') ||
87
+ model.includes('sonar'));
88
+ }
89
+ }
90
+ // Singleton instance
91
+ export const modelRouter = new SharedModelRouter();
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Progress Streaming Utilities
3
+ *
4
+ * Provides real-time progress updates for long-running MCP operations.
5
+ * Users see incremental output instead of waiting for final result.
6
+ */
7
+ export class ProgressStream {
8
+ constructor(totalSteps = 1) {
9
+ this.updates = [];
10
+ this.currentStep = 0;
11
+ this.startTime = new Date();
12
+ this.totalSteps = totalSteps;
13
+ }
14
+ /**
15
+ * Emit progress update to stderr (visible to user in real-time)
16
+ */
17
+ emit(update) {
18
+ this.updates.push(update);
19
+ // Format for CLI output
20
+ const elapsed = Math.floor((update.timestamp.getTime() - this.startTime.getTime()) / 1000);
21
+ const timeStr = `[${elapsed}s]`;
22
+ let output = '';
23
+ switch (update.type) {
24
+ case 'start':
25
+ output = `\n🚀 ${update.message}`;
26
+ break;
27
+ case 'progress':
28
+ const progressBar = this.renderProgressBar(update.percentage || 0);
29
+ output = `\r${timeStr} ${progressBar} ${update.message}`;
30
+ break;
31
+ case 'step':
32
+ output = `\n${timeStr} ⚙️ Step ${this.currentStep}/${this.totalSteps}: ${update.message}`;
33
+ break;
34
+ case 'complete':
35
+ output = `\n\n✅ ${update.message} (completed in ${elapsed}s)`;
36
+ break;
37
+ case 'error':
38
+ output = `\n\n❌ ${update.message}`;
39
+ break;
40
+ }
41
+ // Write to stderr so it doesn't interfere with MCP JSON-RPC on stdout
42
+ console.error(output);
43
+ }
44
+ /**
45
+ * Render ASCII progress bar
46
+ */
47
+ renderProgressBar(percentage) {
48
+ const width = 20;
49
+ const filled = Math.floor(width * (percentage / 100));
50
+ const empty = width - filled;
51
+ return `[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${percentage.toFixed(0)}%`;
52
+ }
53
+ /**
54
+ * Mark operation start
55
+ */
56
+ start(message) {
57
+ this.emit({
58
+ type: 'start',
59
+ message,
60
+ timestamp: new Date()
61
+ });
62
+ }
63
+ /**
64
+ * Update progress
65
+ */
66
+ progress(message, step, total) {
67
+ if (step !== undefined && total !== undefined) {
68
+ this.currentStep = step;
69
+ this.totalSteps = total;
70
+ const percentage = (step / total) * 100;
71
+ this.emit({
72
+ type: 'progress',
73
+ message,
74
+ percentage,
75
+ timestamp: new Date()
76
+ });
77
+ }
78
+ else {
79
+ this.emit({
80
+ type: 'progress',
81
+ message,
82
+ percentage: undefined,
83
+ timestamp: new Date()
84
+ });
85
+ }
86
+ }
87
+ /**
88
+ * Mark step completion
89
+ */
90
+ step(message, stepNumber) {
91
+ if (stepNumber !== undefined) {
92
+ this.currentStep = stepNumber;
93
+ }
94
+ else {
95
+ this.currentStep++;
96
+ }
97
+ this.emit({
98
+ type: 'step',
99
+ message,
100
+ metadata: { stepNumber: this.currentStep, totalSteps: this.totalSteps },
101
+ timestamp: new Date()
102
+ });
103
+ }
104
+ /**
105
+ * Mark operation complete
106
+ */
107
+ complete(message) {
108
+ this.emit({
109
+ type: 'complete',
110
+ message,
111
+ timestamp: new Date()
112
+ });
113
+ }
114
+ /**
115
+ * Mark operation error
116
+ */
117
+ error(message) {
118
+ this.emit({
119
+ type: 'error',
120
+ message,
121
+ timestamp: new Date()
122
+ });
123
+ }
124
+ /**
125
+ * Get all updates
126
+ */
127
+ getUpdates() {
128
+ return [...this.updates];
129
+ }
130
+ /**
131
+ * Get duration in seconds
132
+ */
133
+ getDuration() {
134
+ return Math.floor((new Date().getTime() - this.startTime.getTime()) / 1000);
135
+ }
136
+ }
137
+ /**
138
+ * Create progress stream for operation
139
+ */
140
+ export function createProgressStream(totalSteps = 1) {
141
+ return new ProgressStream(totalSteps);
142
+ }
143
+ /**
144
+ * Wrap async operation with progress tracking
145
+ */
146
+ export async function withProgress(operation, operationName, totalSteps) {
147
+ const stream = new ProgressStream(totalSteps || 1);
148
+ stream.start(operationName);
149
+ try {
150
+ const result = await operation(stream);
151
+ stream.complete(`${operationName} finished`);
152
+ return result;
153
+ }
154
+ catch (error) {
155
+ const errorMsg = error instanceof Error ? error.message : String(error);
156
+ stream.error(`${operationName} failed: ${errorMsg}`);
157
+ throw error;
158
+ }
159
+ }
160
+ /**
161
+ * Progress reporter for multi-model operations
162
+ */
163
+ export class MultiModelProgressReporter {
164
+ constructor(models, operationName) {
165
+ this.models = models;
166
+ this.stream = new ProgressStream(models.length);
167
+ this.results = new Map(models.map(m => [m, { status: 'pending' }]));
168
+ this.stream.start(`${operationName} with ${models.length} models`);
169
+ this.printModelTable();
170
+ }
171
+ /**
172
+ * Print table of models and their status
173
+ */
174
+ printModelTable() {
175
+ console.error('\n📊 Model Status:');
176
+ console.error('┌' + '─'.repeat(50) + '┐');
177
+ this.models.forEach(model => {
178
+ const status = this.results.get(model)?.status || 'pending';
179
+ const icon = this.getStatusIcon(status);
180
+ const padding = ' '.repeat(Math.max(0, 40 - model.length));
181
+ console.error(`│ ${icon} ${model}${padding} │`);
182
+ });
183
+ console.error('└' + '─'.repeat(50) + '┘\n');
184
+ }
185
+ /**
186
+ * Get icon for status
187
+ */
188
+ getStatusIcon(status) {
189
+ switch (status) {
190
+ case 'pending': return '⏳';
191
+ case 'running': return '🔄';
192
+ case 'complete': return '✅';
193
+ case 'error': return '❌';
194
+ default: return '�';
195
+ }
196
+ }
197
+ /**
198
+ * Mark model as running
199
+ */
200
+ modelStarted(model) {
201
+ const result = this.results.get(model);
202
+ if (result) {
203
+ result.status = 'running';
204
+ this.stream.step(`${model} processing...`);
205
+ }
206
+ }
207
+ /**
208
+ * Mark model as complete
209
+ */
210
+ modelCompleted(model, output) {
211
+ const result = this.results.get(model);
212
+ if (result) {
213
+ result.status = 'complete';
214
+ result.output = output;
215
+ const completed = Array.from(this.results.values()).filter(r => r.status === 'complete').length;
216
+ this.stream.progress(`${completed}/${this.models.length} models completed`, completed, this.models.length);
217
+ // Show preview of output
218
+ const preview = output.substring(0, 100).replace(/\n/g, ' ');
219
+ console.error(` 📝 Preview: ${preview}${output.length > 100 ? '...' : ''}`);
220
+ }
221
+ }
222
+ /**
223
+ * Mark model as error
224
+ */
225
+ modelFailed(model, error) {
226
+ const result = this.results.get(model);
227
+ if (result) {
228
+ result.status = 'error';
229
+ console.error(` ⚠️ ${model} failed: ${error}`);
230
+ }
231
+ }
232
+ /**
233
+ * Complete all operations
234
+ */
235
+ complete(message) {
236
+ const completed = Array.from(this.results.values()).filter(r => r.status === 'complete').length;
237
+ const failed = Array.from(this.results.values()).filter(r => r.status === 'error').length;
238
+ const summary = message || `${completed} models completed${failed > 0 ? `, ${failed} failed` : ''}`;
239
+ this.stream.complete(summary);
240
+ // Print final table
241
+ this.printModelTable();
242
+ }
243
+ /**
244
+ * Get progress stream
245
+ */
246
+ getStream() {
247
+ return this.stream;
248
+ }
249
+ }
250
+ /**
251
+ * Create progress reporter for multi-model operation
252
+ */
253
+ export function createMultiModelReporter(models, operationName) {
254
+ return new MultiModelProgressReporter(models, operationName);
255
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ProviderRouter - Smart routing and failover between multiple providers
3
+ *
4
+ * Features:
5
+ * - Automatic failover on errors
6
+ * - Priority-based provider selection
7
+ * - Integration with SmartAPIClient for retries
8
+ * - NO metrics collection - privacy-first design
9
+ */
10
+ import { smartAPIClient } from './smart-api-client.js';
11
+ export class ProviderRouter {
12
+ constructor() {
13
+ this.smartClient = smartAPIClient;
14
+ }
15
+ static getInstance() {
16
+ if (!ProviderRouter.instance) {
17
+ ProviderRouter.instance = new ProviderRouter();
18
+ }
19
+ return ProviderRouter.instance;
20
+ }
21
+ /**
22
+ * Route request through providers with automatic failover
23
+ */
24
+ async route(providers, request, apiConfig) {
25
+ const startTime = Date.now();
26
+ const failedProviders = [];
27
+ let attempts = 0;
28
+ // Filter and sort providers
29
+ const availableProviders = this.selectAvailableProviders(providers);
30
+ if (availableProviders.length === 0) {
31
+ throw new Error('[ProviderRouter] No providers available or enabled');
32
+ }
33
+ console.log(`[ProviderRouter] Available providers: ${availableProviders.map(p => p.name).join(', ')}`);
34
+ // Try each provider in order
35
+ for (const provider of availableProviders) {
36
+ attempts++;
37
+ try {
38
+ console.log(`[ProviderRouter] Attempting ${provider.name} (attempt ${attempts}/${availableProviders.length})`);
39
+ const result = await this.smartClient.callWithRetries(provider.callable, {
40
+ provider: provider.name,
41
+ ...apiConfig
42
+ });
43
+ const totalTime = Date.now() - startTime;
44
+ console.log(`[ProviderRouter] Success with ${provider.name} ` +
45
+ `(total time: ${totalTime}ms, attempts: ${attempts})`);
46
+ return {
47
+ result,
48
+ provider: provider.name,
49
+ attempts,
50
+ totalTime,
51
+ failedProviders
52
+ };
53
+ }
54
+ catch (error) {
55
+ console.error(`[ProviderRouter] Provider ${provider.name} failed: ${error.message}`);
56
+ failedProviders.push(provider.name);
57
+ // If this was the last provider, throw
58
+ if (attempts >= availableProviders.length) {
59
+ const totalTime = Date.now() - startTime;
60
+ throw new Error(`[ProviderRouter] All providers exhausted (${availableProviders.map(p => p.name).join(', ')}) ` +
61
+ `after ${attempts} attempts in ${totalTime}ms. Last error: ${error.message}`);
62
+ }
63
+ // Otherwise continue to next provider
64
+ console.log(`[ProviderRouter] Failing over to next provider...`);
65
+ }
66
+ }
67
+ // Should never reach here, but TypeScript needs it
68
+ throw new Error('[ProviderRouter] Unexpected routing failure');
69
+ }
70
+ /**
71
+ * Select and order available providers based on priority
72
+ */
73
+ selectAvailableProviders(providers) {
74
+ // Filter enabled providers
75
+ const enabled = providers.filter(p => p.enabled);
76
+ if (enabled.length === 0) {
77
+ return [];
78
+ }
79
+ // Sort by priority (lower first)
80
+ return enabled.sort((a, b) => {
81
+ const priorityA = a.priority ?? 999;
82
+ const priorityB = b.priority ?? 999;
83
+ return priorityA - priorityB;
84
+ });
85
+ }
86
+ }
87
+ // Export singleton instance
88
+ export const providerRouter = ProviderRouter.getInstance();