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,257 @@
1
+ /**
2
+ * Tool Compressor - Aggressive token reduction for MCP tools
3
+ * Target: Reduce from ~400 tokens per tool to ~40 tokens
4
+ */
5
+ import { z } from 'zod';
6
+ /**
7
+ * Compress a tool description to minimal tokens
8
+ */
9
+ export function compressToolDescription(tool) {
10
+ return {
11
+ n: compressName(tool.name),
12
+ d: compressDescription(tool.description),
13
+ p: compressParameters(tool.parameters)
14
+ };
15
+ }
16
+ /**
17
+ * Compress tool name
18
+ */
19
+ function compressName(name) {
20
+ // Remove underscores, use abbreviations
21
+ const abbreviations = {
22
+ 'perplexity': 'pplx',
23
+ 'gemini': 'gem',
24
+ 'openai': 'oai',
25
+ 'research': 'rsch',
26
+ 'analyze': 'anlz',
27
+ 'brainstorm': 'bstm',
28
+ 'workflow': 'wf',
29
+ 'visualize': 'viz',
30
+ 'create': 'mk',
31
+ 'list': 'ls'
32
+ };
33
+ let compressed = name.toLowerCase();
34
+ Object.entries(abbreviations).forEach(([full, abbr]) => {
35
+ compressed = compressed.replace(full, abbr);
36
+ });
37
+ return compressed.replace(/_/g, '');
38
+ }
39
+ /**
40
+ * Compress description to max 10 words
41
+ */
42
+ function compressDescription(description) {
43
+ // Remove common filler words
44
+ const fillers = ['the', 'a', 'an', 'to', 'for', 'with', 'and', 'or', 'but', 'in', 'on', 'at', 'from', 'by'];
45
+ const words = description
46
+ .toLowerCase()
47
+ .split(/\s+/)
48
+ .filter(word => !fillers.includes(word))
49
+ .slice(0, 7); // Max 7 meaningful words
50
+ return words.join(' ');
51
+ }
52
+ /**
53
+ * Compress parameters to minimal schema notation
54
+ */
55
+ function compressParameters(params) {
56
+ if (!params)
57
+ return {};
58
+ const compressed = {};
59
+ // Handle Zod schema
60
+ if (params._def) {
61
+ const shape = params._def.shape?.() || {};
62
+ Object.entries(shape).forEach(([key, value]) => {
63
+ compressed[compressParamName(key)] = compressParamType(value);
64
+ });
65
+ }
66
+ // Handle plain object
67
+ else if (typeof params === 'object') {
68
+ Object.entries(params).forEach(([key, value]) => {
69
+ compressed[compressParamName(key)] =
70
+ typeof value === 'object' ? compressParamType(value) : 's';
71
+ });
72
+ }
73
+ return compressed;
74
+ }
75
+ /**
76
+ * Compress parameter names
77
+ */
78
+ function compressParamName(name) {
79
+ const mapping = {
80
+ 'query': 'q',
81
+ 'prompt': 'p',
82
+ 'model': 'm',
83
+ 'temperature': 't',
84
+ 'maxTokens': 'mx',
85
+ 'max_tokens': 'mx',
86
+ 'context': 'c',
87
+ 'content': 'c',
88
+ 'message': 'msg',
89
+ 'depth': 'd',
90
+ 'name': 'n',
91
+ 'type': 'typ',
92
+ 'projectPath': 'path',
93
+ 'input': 'i',
94
+ 'output': 'o'
95
+ };
96
+ return mapping[name] || name.slice(0, 3);
97
+ }
98
+ /**
99
+ * Compress parameter type to shorthand
100
+ */
101
+ function compressParamType(zodType) {
102
+ if (!zodType)
103
+ return 's'; // Default to string
104
+ const typeName = zodType._def?.typeName || zodType.type || 'string';
105
+ const typeMap = {
106
+ 'ZodString': 's',
107
+ 'ZodNumber': 'n',
108
+ 'ZodBoolean': 'b',
109
+ 'ZodArray': 'a',
110
+ 'ZodObject': 'o',
111
+ 'ZodEnum': 'e',
112
+ 'ZodOptional': '?',
113
+ 'string': 's',
114
+ 'number': 'n',
115
+ 'boolean': 'b',
116
+ 'array': 'a',
117
+ 'object': 'o'
118
+ };
119
+ let compressed = typeMap[typeName] || 's';
120
+ // Handle optional
121
+ if (zodType._def?.typeName === 'ZodOptional') {
122
+ const innerType = compressParamType(zodType._def.innerType);
123
+ compressed = innerType + '?';
124
+ }
125
+ // Handle enum values
126
+ if (zodType._def?.values) {
127
+ const values = zodType._def.values.slice(0, 3).join('|');
128
+ compressed = `e[${values}]`;
129
+ }
130
+ return compressed;
131
+ }
132
+ /**
133
+ * Consolidate multiple tools into one
134
+ */
135
+ export function consolidateTools(tools, groupName) {
136
+ const consolidationMap = {
137
+ 'ai_models': {
138
+ n: 'ai',
139
+ d: 'query ai models',
140
+ p: {
141
+ m: 's', // model
142
+ p: 's', // prompt
143
+ t: 'n?', // temperature (optional)
144
+ mx: 'n?' // maxTokens (optional)
145
+ }
146
+ },
147
+ 'research': {
148
+ n: 'rsch',
149
+ d: 'web research citations',
150
+ p: {
151
+ q: 's', // query
152
+ d: 'e[quick|standard|deep]?', // depth
153
+ s: 'a?' // sources (optional array)
154
+ }
155
+ },
156
+ 'code': {
157
+ n: 'code',
158
+ d: 'analyze review code',
159
+ p: {
160
+ c: 's', // code
161
+ m: 'e[review|analyze|fix]?', // mode
162
+ f: 's?' // focus area (optional)
163
+ }
164
+ },
165
+ 'workflow': {
166
+ n: 'wf',
167
+ d: 'execute workflows',
168
+ p: {
169
+ n: 's', // name
170
+ i: 's', // input
171
+ o: 'o?' // options (optional object)
172
+ }
173
+ }
174
+ };
175
+ return consolidationMap[groupName] || {
176
+ n: groupName.slice(0, 5),
177
+ d: `${groupName} tools`,
178
+ p: { i: 's' }
179
+ };
180
+ }
181
+ /**
182
+ * Expand compressed tool back to full MCP format
183
+ */
184
+ export function expandCompressedTool(compressed) {
185
+ // Expand parameter names
186
+ const expandParamName = (short) => {
187
+ const mapping = {
188
+ 'q': 'query',
189
+ 'p': 'prompt',
190
+ 'm': 'model',
191
+ 't': 'temperature',
192
+ 'mx': 'maxTokens',
193
+ 'c': 'content',
194
+ 'd': 'depth',
195
+ 'n': 'name',
196
+ 'i': 'input',
197
+ 'o': 'output'
198
+ };
199
+ return mapping[short] || short;
200
+ };
201
+ // Expand parameter types
202
+ const expandParamType = (short) => {
203
+ if (short.endsWith('?')) {
204
+ return z.optional(expandParamType(short.slice(0, -1)));
205
+ }
206
+ const typeMap = {
207
+ 's': z.string(),
208
+ 'n': z.number(),
209
+ 'b': z.boolean(),
210
+ 'a': z.array(z.string()),
211
+ 'o': z.object({})
212
+ };
213
+ // Handle enums
214
+ if (short.startsWith('e[')) {
215
+ const values = short.slice(2, -1).split('|');
216
+ return z.enum(values);
217
+ }
218
+ return typeMap[short] || z.string();
219
+ };
220
+ // Build expanded parameters
221
+ const parameters = {};
222
+ Object.entries(compressed.p).forEach(([key, type]) => {
223
+ parameters[expandParamName(key)] = expandParamType(type);
224
+ });
225
+ return {
226
+ name: compressed.n,
227
+ description: compressed.d,
228
+ parameters: z.object(parameters)
229
+ };
230
+ }
231
+ /**
232
+ * Calculate estimated token count for a tool
233
+ */
234
+ export function estimateTokens(tool) {
235
+ const str = JSON.stringify(tool);
236
+ // Rough estimate: 1 token ≈ 4 characters
237
+ return Math.ceil(str.length / 4);
238
+ }
239
+ /**
240
+ * Batch compress all tools with token budget
241
+ */
242
+ export function compressAllTools(tools, maxTokens = 10000) {
243
+ const compressed = [];
244
+ let totalTokens = 0;
245
+ for (const tool of tools) {
246
+ const comp = compressToolDescription(tool);
247
+ const tokens = estimateTokens(comp);
248
+ if (totalTokens + tokens <= maxTokens) {
249
+ compressed.push(comp);
250
+ totalTokens += tokens;
251
+ }
252
+ else {
253
+ console.warn(`Skipping tool ${tool.name} - would exceed token budget`);
254
+ }
255
+ }
256
+ return { compressed, totalTokens };
257
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Tool configuration manager with profile support
3
+ * Allows disabling specific tools via config file or environment variables
4
+ *
5
+ * Precedence (highest to lowest):
6
+ * 1. customProfile.enabled = true in tools.config.json
7
+ * 2. TACHIBOT_PROFILE environment variable
8
+ * 3. activeProfile in tools.config.json
9
+ * 4. Fallback: all tools enabled
10
+ */
11
+ import { readFileSync, existsSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ // Get __dirname equivalent in ES modules
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ let toolConfig = { tools: {} };
18
+ let activeTools = {};
19
+ let profileSource = 'default';
20
+ /**
21
+ * Load a profile from the profiles/ directory
22
+ */
23
+ function loadProfileFromFile(profileName) {
24
+ try {
25
+ const profilePath = join(__dirname, '../../../profiles', `${profileName}.json`);
26
+ if (existsSync(profilePath)) {
27
+ const content = readFileSync(profilePath, 'utf-8');
28
+ return JSON.parse(content);
29
+ }
30
+ }
31
+ catch (error) {
32
+ console.warn(`⚠️ Could not load profile '${profileName}' from profiles/ directory`);
33
+ }
34
+ return null;
35
+ }
36
+ // Load config once at startup
37
+ try {
38
+ // Use __dirname to find config relative to source code location, not cwd
39
+ // When built: dist/src/utils/tool-config.js -> go up 3 levels to project root
40
+ const configPath = join(__dirname, '../../../tools.config.json');
41
+ if (existsSync(configPath)) {
42
+ const configContent = readFileSync(configPath, 'utf-8');
43
+ toolConfig = JSON.parse(configContent);
44
+ // Priority 1: Custom profile enabled in tools.config.json
45
+ if (toolConfig.customProfile?.enabled) {
46
+ activeTools = toolConfig.customProfile.tools;
47
+ profileSource = 'custom';
48
+ console.error(`📋 Using custom profile from tools.config.json`);
49
+ }
50
+ // Priority 2: TACHIBOT_PROFILE environment variable
51
+ else if (process.env.TACHIBOT_PROFILE) {
52
+ const envProfile = process.env.TACHIBOT_PROFILE;
53
+ const profile = loadProfileFromFile(envProfile);
54
+ if (profile) {
55
+ activeTools = profile.tools;
56
+ profileSource = envProfile;
57
+ console.error(`📋 Using profile '${envProfile}' from TACHIBOT_PROFILE env var`);
58
+ console.error(` ${profile.description}`);
59
+ }
60
+ else {
61
+ console.warn(`⚠️ Profile '${envProfile}' not found, falling back to activeProfile`);
62
+ // Fall through to next priority
63
+ }
64
+ }
65
+ // Priority 3: activeProfile in tools.config.json
66
+ if (!activeTools || Object.keys(activeTools).length === 0) {
67
+ if (toolConfig.activeProfile) {
68
+ // Try to load from profiles/ directory first (new structure)
69
+ const profile = loadProfileFromFile(toolConfig.activeProfile);
70
+ if (profile) {
71
+ activeTools = profile.tools;
72
+ profileSource = toolConfig.activeProfile;
73
+ console.error(`📋 Using profile '${toolConfig.activeProfile}': ${profile.description}`);
74
+ }
75
+ // Fall back to embedded profile in tools.config.json (legacy)
76
+ else if (toolConfig.profiles?.[toolConfig.activeProfile]) {
77
+ const profile = toolConfig.profiles[toolConfig.activeProfile];
78
+ activeTools = profile.tools;
79
+ profileSource = toolConfig.activeProfile;
80
+ console.error(`📋 Using profile '${toolConfig.activeProfile}' from tools.config.json (legacy)`);
81
+ console.error(` ${profile.description}`);
82
+ }
83
+ }
84
+ else if (toolConfig.tools) {
85
+ // Fallback to legacy flat tools config
86
+ activeTools = toolConfig.tools;
87
+ profileSource = 'legacy';
88
+ console.error(`📋 Using legacy flat configuration from tools.config.json`);
89
+ }
90
+ }
91
+ }
92
+ // Final fallback: all tools enabled
93
+ if (!activeTools || Object.keys(activeTools).length === 0) {
94
+ console.warn(`⚠️ No valid configuration found, all tools enabled by default`);
95
+ profileSource = 'default';
96
+ }
97
+ }
98
+ catch (error) {
99
+ console.warn(`⚠️ Could not load tools.config.json, all tools enabled by default`);
100
+ profileSource = 'default';
101
+ }
102
+ /**
103
+ * Check if a tool should be registered
104
+ * Priority: Environment variables > Profile config > Default (enabled)
105
+ */
106
+ export function isToolEnabled(toolName) {
107
+ // Environment variable override (highest priority)
108
+ const envEnable = process.env[`ENABLE_TOOL_${toolName.toUpperCase()}`];
109
+ if (envEnable === 'true')
110
+ return true;
111
+ const envDisable = process.env[`DISABLE_TOOL_${toolName.toUpperCase()}`];
112
+ if (envDisable === 'true')
113
+ return false;
114
+ // Check if globally disabled
115
+ if (process.env.DISABLE_ALL_TOOLS === 'true')
116
+ return false;
117
+ // Check active profile
118
+ const toolStatus = activeTools[toolName];
119
+ // If no profile loaded (default mode), enable all tools
120
+ if (profileSource === 'default') {
121
+ return true;
122
+ }
123
+ // Simple rule: If tool not in profile config, it's DISABLED
124
+ // Profiles are explicit allowlists - if it's not listed, it's off
125
+ if (toolStatus === undefined) {
126
+ return false;
127
+ }
128
+ // Use the config value
129
+ if (!toolStatus) {
130
+ console.error(` ⏭️ Skipping ${toolName} (disabled in profile)`);
131
+ }
132
+ return toolStatus;
133
+ }
134
+ /**
135
+ * Get list of disabled tools for logging
136
+ */
137
+ export function getDisabledTools() {
138
+ if (!activeTools)
139
+ return [];
140
+ return Object.entries(activeTools)
141
+ .filter(([_, enabled]) => !enabled)
142
+ .map(([name]) => name);
143
+ }
144
+ /**
145
+ * Get list of enabled tools for logging
146
+ */
147
+ export function getEnabledTools() {
148
+ if (!activeTools)
149
+ return [];
150
+ return Object.entries(activeTools)
151
+ .filter(([_, enabled]) => enabled)
152
+ .map(([name]) => name);
153
+ }
154
+ /**
155
+ * Get active profile info
156
+ */
157
+ export function getActiveProfile() {
158
+ if (profileSource === 'default') {
159
+ return null;
160
+ }
161
+ if (profileSource === 'custom') {
162
+ return {
163
+ name: 'custom',
164
+ description: toolConfig.customProfile?.description
165
+ };
166
+ }
167
+ if (profileSource === 'legacy') {
168
+ return {
169
+ name: 'legacy',
170
+ description: 'Legacy flat configuration'
171
+ };
172
+ }
173
+ // For named profiles, try to get description
174
+ const profile = loadProfileFromFile(profileSource);
175
+ return {
176
+ name: profileSource,
177
+ description: profile?.description
178
+ };
179
+ }
180
+ /**
181
+ * Log configuration summary
182
+ */
183
+ export function logToolConfiguration() {
184
+ const profile = getActiveProfile();
185
+ if (profile) {
186
+ console.error(`🎯 Active profile: ${profile.name}`);
187
+ if (profile.description) {
188
+ console.error(` ${profile.description}`);
189
+ }
190
+ }
191
+ const disabled = getDisabledTools();
192
+ if (disabled.length > 0) {
193
+ console.error(`🚫 Disabled tools: ${disabled.join(', ')}`);
194
+ }
195
+ // Check for environment overrides
196
+ const envVars = Object.keys(process.env);
197
+ const toolOverrides = envVars.filter(key => key.startsWith('ENABLE_TOOL_') || key.startsWith('DISABLE_TOOL_'));
198
+ if (toolOverrides.length > 0) {
199
+ console.error(`🔧 Environment overrides active: ${toolOverrides.join(', ')}`);
200
+ }
201
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Dependency graph validator - detects circular dependencies and validates execution order
3
+ */
4
+ export class DependencyGraphValidator {
5
+ constructor() {
6
+ this.interpolationRegex = /\$\{([^.}]+)(?:\.[^}]+)?\}/g;
7
+ }
8
+ validate(context) {
9
+ const errors = [];
10
+ // Build dependency graph
11
+ const graph = this.buildDependencyGraph(context.workflow.steps);
12
+ // Check for circular dependencies
13
+ const cycle = this.detectCycle(graph);
14
+ if (cycle) {
15
+ errors.push({
16
+ type: 'dependency',
17
+ severity: 'error',
18
+ message: `Circular dependency detected: ${cycle.join(' → ')} → ${cycle[0]}`,
19
+ path: '$.steps',
20
+ suggestion: 'Reorganize steps to remove circular dependencies'
21
+ });
22
+ }
23
+ // Validate execution order
24
+ const orderErrors = this.validateExecutionOrder(context.workflow.steps, graph);
25
+ errors.push(...orderErrors);
26
+ return errors;
27
+ }
28
+ /**
29
+ * Build dependency graph from workflow steps
30
+ * Returns Map<stepName, Set<dependencies>>
31
+ */
32
+ buildDependencyGraph(steps) {
33
+ const graph = new Map();
34
+ steps.forEach(step => {
35
+ graph.set(step.name, new Set());
36
+ // Add explicit dependencies
37
+ if (step.dependsOn && Array.isArray(step.dependsOn)) {
38
+ step.dependsOn.forEach((dep) => graph.get(step.name).add(dep));
39
+ }
40
+ // Add dependencies from loadFiles
41
+ if (step.loadFiles && Array.isArray(step.loadFiles)) {
42
+ step.loadFiles.forEach((fileRef) => graph.get(step.name).add(fileRef));
43
+ }
44
+ // Add implicit dependencies from interpolations in input
45
+ if (step.input) {
46
+ const inputStr = JSON.stringify(step.input);
47
+ const matches = inputStr.matchAll(this.interpolationRegex);
48
+ for (const match of matches) {
49
+ const reference = match[1]; // Get the step name part
50
+ // Only add if it's referencing another step (not a variable)
51
+ if (steps.some(s => s.name === reference)) {
52
+ graph.get(step.name).add(reference);
53
+ }
54
+ }
55
+ }
56
+ // Add implicit dependencies from 'when' condition
57
+ if (step.when) {
58
+ const matches = step.when.matchAll(this.interpolationRegex);
59
+ for (const match of matches) {
60
+ const reference = match[1];
61
+ if (steps.some(s => s.name === reference)) {
62
+ graph.get(step.name).add(reference);
63
+ }
64
+ }
65
+ }
66
+ });
67
+ return graph;
68
+ }
69
+ /**
70
+ * Detect circular dependencies using DFS
71
+ * Returns the cycle path if found, null otherwise
72
+ */
73
+ detectCycle(graph) {
74
+ const visiting = new Set();
75
+ const visited = new Set();
76
+ const path = [];
77
+ for (const node of graph.keys()) {
78
+ if (!visited.has(node)) {
79
+ if (this.hasCycleDFS(node, graph, visiting, visited, path)) {
80
+ // Extract just the cycle from the path
81
+ const cycleStart = path.indexOf(path[path.length - 1]);
82
+ return path.slice(cycleStart);
83
+ }
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ /**
89
+ * DFS helper to detect cycles
90
+ */
91
+ hasCycleDFS(node, graph, visiting, visited, path) {
92
+ if (visiting.has(node)) {
93
+ // Found a cycle - add the node that creates the cycle
94
+ path.push(node);
95
+ return true;
96
+ }
97
+ if (visited.has(node)) {
98
+ return false;
99
+ }
100
+ visiting.add(node);
101
+ path.push(node);
102
+ const neighbors = graph.get(node) || new Set();
103
+ for (const neighbor of neighbors) {
104
+ if (this.hasCycleDFS(neighbor, graph, visiting, visited, path)) {
105
+ return true;
106
+ }
107
+ }
108
+ visiting.delete(node);
109
+ visited.add(node);
110
+ path.pop();
111
+ return false;
112
+ }
113
+ /**
114
+ * Validate that dependencies appear before dependent steps
115
+ */
116
+ validateExecutionOrder(steps, graph) {
117
+ const errors = [];
118
+ const stepPositions = new Map(steps.map((s, i) => [s.name, i]));
119
+ for (const [stepName, dependencies] of graph.entries()) {
120
+ const currentPos = stepPositions.get(stepName);
121
+ if (currentPos === undefined)
122
+ continue;
123
+ for (const dep of dependencies) {
124
+ const depPos = stepPositions.get(dep);
125
+ if (depPos === undefined) {
126
+ errors.push({
127
+ type: 'dependency',
128
+ severity: 'error',
129
+ message: `Step '${stepName}' depends on undefined step '${dep}'`,
130
+ path: `$.steps[${currentPos}]`,
131
+ suggestion: `Define step '${dep}' or remove the dependency`
132
+ });
133
+ }
134
+ else if (depPos >= currentPos) {
135
+ errors.push({
136
+ type: 'dependency',
137
+ severity: 'error',
138
+ message: `Step '${stepName}' depends on '${dep}' which appears later in the workflow`,
139
+ path: `$.steps[${currentPos}]`,
140
+ suggestion: `Move step '${dep}' (currently at position ${depPos + 1}) before step '${stepName}' (position ${currentPos + 1})`
141
+ });
142
+ }
143
+ }
144
+ }
145
+ return errors;
146
+ }
147
+ }