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,485 @@
1
+ /**
2
+ * Workflow Execution Engine
3
+ * Contains executeWorkflow implementation (615 lines extracted)
4
+ */
5
+ import * as path from 'path';
6
+ import { promises as fsPromises } from 'fs';
7
+ import { generateWorkflowId } from '../../utils/timestamp-formatter.js';
8
+ import { loadConfig } from '../../config.js';
9
+ import { modelRouter } from '../../optimization/model-router.js';
10
+ import { tokenOptimizer } from '../../optimization/token-optimizer.js';
11
+ export { executeWorkflowImpl };
12
+ async function executeWorkflowImpl(parent, workflowName, input, options) {
13
+ const workflow = parent.workflows.get(workflowName);
14
+ if (!workflow) {
15
+ throw new Error(`Workflow '${workflowName}' not found`);
16
+ }
17
+ // Generate unique workflow ID: YYYY-MM-DD-DayName-HH-MM-shortid
18
+ // Example: 2025-11-23-Sunday-22-44-a1b2c3d4
19
+ const workflowId = generateWorkflowId();
20
+ // Setup output directory for file-based workflows
21
+ // Uses WORKFLOW_OUTPUT_DIR env var (default: ./workflow-output)
22
+ const config = loadConfig();
23
+ const baseOutputDir = config.workflow.outputDir;
24
+ const outputDir = path.join(process.cwd(), baseOutputDir, workflowName, workflowId);
25
+ // Initialize execution tracking
26
+ const execution = {
27
+ workflowName,
28
+ workflowId,
29
+ outputDir,
30
+ startTime: new Date(),
31
+ status: "running",
32
+ cost: 0,
33
+ outputs: [],
34
+ };
35
+ parent.executionHistory.push(execution);
36
+ console.error(`\n📝 Workflow manifest: ${path.join(outputDir, 'manifest.json')}`);
37
+ console.error(` To monitor progress: tail -f ${path.join(outputDir, 'manifest.json')}\n`);
38
+ // Merge variables
39
+ const variables = {
40
+ ...workflow.variables,
41
+ ...options?.variables,
42
+ input,
43
+ query: input, // Also provide 'query' alias for backwards compatibility with workflows that use ${query}
44
+ };
45
+ console.error(`🔍 Workflow variables initialized:`, JSON.stringify(variables, null, 2));
46
+ // Calculate step numbers for sequential/parallel execution display
47
+ const stepNumbers = parent.calculateStepNumbers(workflow);
48
+ const stepNumberEntries = Array.from(stepNumbers.entries());
49
+ console.error(`📊 Step numbering calculated:`, stepNumberEntries.map(([name, num]) => `${num}: ${name}`).join(', '));
50
+ // Execute steps
51
+ const stepOutputs = {};
52
+ let previousOutput = input;
53
+ // Initialize output directory if ANY step has saveToFile
54
+ const needsFileOutput = workflow.steps.some((s) => s.saveToFile);
55
+ if (needsFileOutput) {
56
+ try {
57
+ await fsPromises.mkdir(outputDir, { recursive: true });
58
+ // Create initial manifest
59
+ const manifest = {
60
+ workflowId,
61
+ workflowName,
62
+ startTime: new Date().toISOString(),
63
+ endTime: null,
64
+ status: 'running',
65
+ query: input,
66
+ steps: []
67
+ };
68
+ await fsPromises.writeFile(path.join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
69
+ console.error(`📁 Workflow output directory: ${outputDir}`);
70
+ }
71
+ catch (error) {
72
+ console.error('⚠️ Failed to initialize workflow directory:', error);
73
+ // Continue without file output
74
+ }
75
+ }
76
+ try {
77
+ for (let i = 0; i < workflow.steps.length; i++) {
78
+ const step = workflow.steps[i];
79
+ // Check condition
80
+ if (step.condition?.if) {
81
+ const shouldRun = parent.evaluateCondition(step.condition.if, variables, stepOutputs);
82
+ if (!shouldRun && step.condition.skip) {
83
+ console.error(`⏭️ Skipping step: ${step.name}`);
84
+ continue;
85
+ }
86
+ }
87
+ // 🔥 LIVE PROGRESS - Show what's starting
88
+ const stepNumber = i + 1;
89
+ const totalSteps = workflow.steps.length;
90
+ console.error(`\n${"⏳".repeat(40)}`);
91
+ console.error(`🚀 STARTING STEP ${stepNumber}/${totalSteps}: ${step.name} (${step.tool})`);
92
+ console.error(`${"⏳".repeat(40)}\n`);
93
+ // Load saved step files if requested (hybrid: summary + lazy loader)
94
+ if (step.loadFiles && step.loadFiles.length > 0 && outputDir) {
95
+ for (const stepId of step.loadFiles) {
96
+ const existingRef = stepOutputs[stepId];
97
+ if (existingRef) {
98
+ // Already have FileReference - reuse it directly
99
+ variables[stepId] = existingRef;
100
+ console.error(`📂 Loaded existing FileReference for '${stepId}' (${existingRef.summary.length} char summary)`);
101
+ // Also set output variable if defined
102
+ const previousStep = workflow.steps.find((s) => s.name === stepId);
103
+ if (previousStep?.output?.variable) {
104
+ variables[previousStep.output.variable] = existingRef;
105
+ }
106
+ }
107
+ else {
108
+ // Fallback: Try to load from file (for external files)
109
+ const filename = `${stepId}.md`;
110
+ const filepath = path.join(outputDir, filename);
111
+ try {
112
+ const fileContent = await fsPromises.readFile(filepath, 'utf-8');
113
+ const outputMatch = fileContent.match(/## Output\s*\n\n([\s\S]*)/);
114
+ const content = outputMatch ? outputMatch[1].trim() : fileContent;
115
+ // Create FileReference from loaded file
116
+ const fileRef = await parent.createFileReference(stepId, content, workflowId, workflowName, true, outputDir, stepNumbers.get(stepId), // Pass step number for filename
117
+ undefined // Model unknown for loaded files
118
+ );
119
+ variables[stepId] = fileRef;
120
+ stepOutputs[stepId] = fileRef;
121
+ console.error(`📂 Loaded external file for '${stepId}' (${fileRef.sizeBytes} bytes)`);
122
+ // Also set output variable
123
+ const previousStep = workflow.steps.find((s) => s.name === stepId);
124
+ if (previousStep?.output?.variable) {
125
+ variables[previousStep.output.variable] = fileRef;
126
+ }
127
+ }
128
+ catch (error) {
129
+ console.error(`⚠️ Could not load file for step '${stepId}':`, error);
130
+ // Continue without this file
131
+ }
132
+ }
133
+ }
134
+ }
135
+ // Prepare input
136
+ console.error(`\n🔍 [${step.name}] Preparing input...`);
137
+ console.error(`🔍 Available variables: [${Object.keys(variables).join(', ')}]`);
138
+ console.error(`🔍 Available stepOutputs: [${Object.keys(stepOutputs).join(', ')}]`);
139
+ // Extract variables that this step wants to use
140
+ const inputStr = JSON.stringify(step.input);
141
+ const usedVars = [...inputStr.matchAll(/\$\{([^}]+)\}/g)].map(m => m[1]);
142
+ if (usedVars.length > 0) {
143
+ console.error(`🔍 Variables needed by this step: [${usedVars.join(', ')}]`);
144
+ // Check if all needed variables exist
145
+ for (const varName of usedVars) {
146
+ const exists = variables.hasOwnProperty(varName) || stepOutputs.hasOwnProperty(varName);
147
+ if (!exists) {
148
+ console.error(`❌ MISSING: Variable '${varName}' is not available!`);
149
+ }
150
+ else {
151
+ const value = variables[varName] || stepOutputs[varName];
152
+ console.error(`✓ FOUND: '${varName}' (${typeof value}, ${String(value).length} chars)`);
153
+ }
154
+ }
155
+ }
156
+ let stepInput = input;
157
+ if (step.input) {
158
+ if (typeof step.input === "string") {
159
+ stepInput = await parent.interpolateVariables(step.input, variables, stepOutputs);
160
+ }
161
+ else {
162
+ // Handle any object structure - interpolate all string values
163
+ stepInput = {};
164
+ const debugLines = [
165
+ `🔍 Interpolating step input for ${step.name}:`,
166
+ ` Available variables: ${Object.keys(variables).join(', ')}`,
167
+ ` Available step outputs: ${Object.keys(stepOutputs).join(', ')}`,
168
+ ];
169
+ for (const [key, value] of Object.entries(step.input)) {
170
+ debugLines.push(` Processing ${step.name}.${key}: type=${typeof value}, value="${String(value).substring(0, 100)}"`);
171
+ if (typeof value === 'string') {
172
+ const interpolated = await parent.interpolateVariables(value, variables, stepOutputs);
173
+ if (value !== interpolated) {
174
+ debugLines.push(`🔄 Variable substitution in ${step.name}.${key}:`);
175
+ debugLines.push(` Before: ${value.substring(0, 100)}...`);
176
+ debugLines.push(` After: ${interpolated.substring(0, 100)}...`);
177
+ }
178
+ else {
179
+ debugLines.push(`⚠️ No substitution for ${step.name}.${key} - value: "${value.substring(0, 50)}..."`);
180
+ }
181
+ stepInput[key] = interpolated;
182
+ }
183
+ else {
184
+ stepInput[key] = value;
185
+ }
186
+ }
187
+ // Log to file and console
188
+ const debugMsg = debugLines.join('\n');
189
+ console.error(debugMsg);
190
+ await fsPromises.appendFile('/tmp/workflow-debug.log', debugMsg + '\n\n').catch(() => { });
191
+ // Special handling for previousStep reference
192
+ if (stepInput.previousStep && typeof stepInput.previousStep === 'string') {
193
+ const prevStepRef = stepOutputs[stepInput.previousStep];
194
+ stepInput.previousStep = prevStepRef ? prevStepRef.summary : previousOutput;
195
+ }
196
+ }
197
+ }
198
+ // RUNTIME PARAMETER RESOLUTION
199
+ // Resolve ${step_output} references and convert string numbers to actual numbers
200
+ const resolvedParams = parent.resolveStepParameters(step, variables, stepOutputs);
201
+ // Select optimal model if using smart routing
202
+ let model = resolvedParams.model;
203
+ if (workflow.settings?.optimization?.smartRouting && !model) {
204
+ const context = modelRouter.buildContext(typeof stepInput === "string" ? stepInput : stepInput?.prompt || "");
205
+ const selection = modelRouter.selectModel(context);
206
+ model = selection.primary;
207
+ console.error(`🧠 Smart routing selected: ${model} for ${step.name}`);
208
+ }
209
+ // Apply optimizations (only for string inputs)
210
+ if (workflow.settings?.optimization?.enabled && typeof stepInput === "string") {
211
+ const optimized = await tokenOptimizer.optimize({
212
+ prompt: stepInput,
213
+ model: model || "gpt-5-mini",
214
+ maxTokens: resolvedParams.maxTokens,
215
+ });
216
+ if (optimized.fromCache) {
217
+ // NOTE: When fromCache=true, optimized.prompt contains the cached RESPONSE (not the input)
218
+ // This is intentional - see token-optimizer.ts:175 where cached.response overwrites request.prompt
219
+ console.error(`✅ Using cached result for ${step.name}`);
220
+ console.error(`📦 Cached output length: ${optimized.prompt.length} chars`);
221
+ const cachedResult = optimized.prompt;
222
+ // Convert cached result to FileReference
223
+ const cachedFileRef = await parent.createFileReference(step.name, cachedResult, workflowId, workflowName, step.saveToFile || false, outputDir, stepNumbers.get(step.name), // Pass step number for filename
224
+ model || resolvedParams.model // Pass model name
225
+ );
226
+ stepOutputs[step.name] = cachedFileRef;
227
+ // Store in variables if output.variable is specified
228
+ if (step.output?.variable) {
229
+ variables[step.output.variable] = cachedFileRef;
230
+ console.error(`✅ Stored cached FileReference in variables['${step.output.variable}']`);
231
+ console.error(`✅ Available variables now: [${Object.keys(variables).join(', ')}]`);
232
+ }
233
+ previousOutput = cachedFileRef.summary;
234
+ execution.outputs.push({
235
+ step: step.name,
236
+ input: '[cached]',
237
+ output: cachedFileRef.summary,
238
+ filePath: cachedFileRef.filePath || undefined
239
+ });
240
+ continue; // Skip tool execution
241
+ }
242
+ // When not cached, optimized.prompt contains the compressed INPUT
243
+ stepInput = optimized.prompt;
244
+ }
245
+ // Execute step (with retry logic)
246
+ let attempts = 0;
247
+ const maxAttempts = step.retry?.attempts || 1;
248
+ let result = "";
249
+ let actualModelUsed = model || resolvedParams.model || "unknown";
250
+ while (attempts < maxAttempts) {
251
+ try {
252
+ console.error(`🔧 Executing step: ${step.name} (${step.tool})`);
253
+ if (options?.dryRun) {
254
+ result = `[DRY RUN] Would execute ${step.tool} with model ${model}`;
255
+ }
256
+ else {
257
+ // Emit before_invoke event for PromptEnhancementHandler (Phase 3: Enhanced context)
258
+ await parent.eventBus.publish('workflow.tool.before_invoke', {
259
+ stepName: step.name,
260
+ tool: step.tool,
261
+ input: stepInput,
262
+ context: {
263
+ step,
264
+ variables,
265
+ accumulatedResults: execution.outputs,
266
+ // NEW: Phase 3 context for smart prompt enhancement
267
+ stepIndex: i,
268
+ totalSteps: workflow.steps.length,
269
+ workflowName: workflowName
270
+ }
271
+ });
272
+ // Call the actual tool with RESOLVED parameters
273
+ console.error(`🔧 About to call ${step.tool} with stepInput:`, JSON.stringify(stepInput, null, 2).substring(0, 500));
274
+ const toolResult = await parent.callTool(step.tool, stepInput, {
275
+ model: model || resolvedParams.model,
276
+ maxTokens: resolvedParams.maxTokens,
277
+ temperature: resolvedParams.temperature,
278
+ skipValidation: true, // Skip validation for internal workflow calls
279
+ });
280
+ result = toolResult.result;
281
+ actualModelUsed = toolResult.modelUsed;
282
+ console.error(`✅ Tool executed with model: ${actualModelUsed}`);
283
+ }
284
+ break; // Success
285
+ }
286
+ catch (error) {
287
+ attempts++;
288
+ if (attempts >= maxAttempts) {
289
+ if (step.condition?.failOnError) {
290
+ throw error;
291
+ }
292
+ console.error(`❌ Step ${step.name} failed after ${maxAttempts} attempts`);
293
+ result = "";
294
+ }
295
+ else {
296
+ const backoff = (step.retry?.backoff || 1000) * attempts;
297
+ console.warn(`⚠️ Retrying ${step.name} in ${backoff}ms...`);
298
+ await new Promise((resolve) => setTimeout(resolve, backoff));
299
+ }
300
+ }
301
+ }
302
+ // Store output with validation
303
+ console.error(`📦 Step "${step.name}" completed - Output type: ${typeof result}`);
304
+ // VALIDATION: Ensure result is not undefined/null
305
+ if (result === undefined || result === null) {
306
+ const errorMsg = `Step '${step.name}' returned ${result}. This indicates tool '${step.tool}' execution failed silently.`;
307
+ console.error(`❌ ${errorMsg}`);
308
+ throw new Error(errorMsg);
309
+ }
310
+ // VALIDATION: Warn if result is empty
311
+ if (result.trim().length === 0) {
312
+ console.error(`⚠️ WARNING: Step '${step.name}' returned empty string. Tool may have failed.`);
313
+ }
314
+ console.error(`📦 Output length: ${result.length} chars, Preview: ${result.substring(0, 150)}...`);
315
+ // Convert result to string if it's an object
316
+ if (typeof result !== 'string') {
317
+ result = JSON.stringify(result, null, 2);
318
+ console.error(`📦 Converted object result to JSON string (${result.length} chars)`);
319
+ }
320
+ // Create FileReference for result (replaces full content storage)
321
+ const fileRef = await parent.createFileReference(step.name, result, workflowId, workflowName, step.saveToFile || false, outputDir, stepNumbers.get(step.name), // Pass step number for filename
322
+ actualModelUsed // Pass ACTUAL model used (not requested model)
323
+ );
324
+ // Store FileReference (replaces full content)
325
+ stepOutputs[step.name] = fileRef;
326
+ if (step.output?.variable) {
327
+ variables[step.output.variable] = fileRef;
328
+ console.error(`✅ Stored FileReference in variables['${step.output.variable}']`);
329
+ console.error(`✅ Available variables now: [${Object.keys(variables).join(', ')}]`);
330
+ }
331
+ // Update previous output with summary for chaining
332
+ previousOutput = fileRef.summary;
333
+ // Store summary in execution history (not full content)
334
+ execution.outputs.push({
335
+ step: step.name,
336
+ input: parent.extractInputSummary(stepInput),
337
+ output: fileRef.summary,
338
+ filePath: fileRef.filePath || undefined
339
+ });
340
+ console.error(`📦 Created FileReference: ${fileRef.sizeBytes} bytes → ${fileRef.summary.length} char summary`);
341
+ // 🔥 LIVE OUTPUT - Show summary for each step
342
+ console.error(`\n${"=".repeat(80)}`);
343
+ console.error(`✅ STEP ${stepNumber}/${totalSteps} COMPLETE: ${step.name}`);
344
+ console.error(`${"=".repeat(80)}`);
345
+ console.error(`\n${fileRef.summary}\n`);
346
+ if (fileRef.filePath) {
347
+ console.error(`📄 Full output saved to: ${fileRef.filePath}`);
348
+ }
349
+ console.error(`${"=".repeat(80)}\n`);
350
+ // Progress tracking is handled by manifest.json updates
351
+ const logOutput = fileRef.filePath
352
+ ? `${fileRef.summary}\n\n📄 Full output: ${fileRef.filePath}`
353
+ : fileRef.summary;
354
+ // Update manifest with step completion (manifest.json tracks all progress)
355
+ console.error(`✅ Step ${stepNumber}/${totalSteps} completed: ${step.name}`);
356
+ // (Session logging removed - manifest.json provides full tracking)
357
+ const _metadata = {
358
+ stepNumber,
359
+ stepName: step.name,
360
+ totalSteps,
361
+ workflowName,
362
+ filePath: fileRef.filePath,
363
+ sizeBytes: fileRef.sizeBytes
364
+ };
365
+ // Check if we should run next step in parallel
366
+ if (step.parallel && i < workflow.steps.length - 1) {
367
+ // In a real implementation, you'd use Promise.all or similar
368
+ console.error(`🔀 Next step will run in parallel`);
369
+ }
370
+ // Progressive checkpointing during workflow execution
371
+ const autoSynthesis = workflow.settings?.autoSynthesis;
372
+ if (autoSynthesis?.enabled && outputDir) {
373
+ const checkpointInterval = autoSynthesis.checkpointInterval ?? 10000;
374
+ const accumulatedResults = Object.values(stepOutputs);
375
+ const totalTokens = parent.estimateTotalTokens(accumulatedResults);
376
+ const logLevel = autoSynthesis.logLevel ?? 'info';
377
+ if (totalTokens > 0 && totalTokens % checkpointInterval < 5000) {
378
+ await parent.createCheckpoint(workflowName, outputDir, variables, i);
379
+ if (logLevel !== 'silent' && logLevel !== 'error') {
380
+ console.error(`💾 Checkpoint created at step ${i + 1}/${workflow.steps.length}`);
381
+ console.error(` Total tokens: ${totalTokens}`);
382
+ console.error(` Variables saved: ${Object.keys(variables).length}`);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ // Auto-synthesis: Check if we should generate a summary AFTER all steps complete
388
+ const autoSynthesis = workflow.settings?.autoSynthesis;
389
+ if (autoSynthesis?.enabled) {
390
+ // Collect all results
391
+ const accumulatedResults = Object.values(stepOutputs);
392
+ // Check if we should trigger synthesis
393
+ if (parent.shouldAutoSynthesize(workflow, accumulatedResults, workflow.steps.length - 1)) {
394
+ const totalTokens = parent.estimateTotalTokens(accumulatedResults);
395
+ const logLevel = autoSynthesis.logLevel ?? 'info';
396
+ if (logLevel !== 'silent') {
397
+ console.error(`\n🤖 Auto-synthesis triggered (${totalTokens} tokens accumulated)`);
398
+ console.error(` Threshold: ${autoSynthesis.tokenThreshold ?? 20000} tokens`);
399
+ console.error(` Tool: ${autoSynthesis.synthesisTool ?? 'gemini_analyze_text'}`);
400
+ console.error(`📊 Generating executive summary...`);
401
+ console.error(` Steps to synthesize: ${workflow.steps.length}`);
402
+ console.error(` Variables available: ${Object.keys(variables).length}`);
403
+ }
404
+ const synthesisStep = parent.createSynthesisStep(workflow, variables, outputDir);
405
+ try {
406
+ // Execute synthesis step with retry logic
407
+ const maxRetries = autoSynthesis.maxRetries ?? 3;
408
+ let synthesisResult = '';
409
+ let attempt = 0;
410
+ while (attempt < maxRetries) {
411
+ try {
412
+ const toolResult = await parent.callTool(synthesisStep.tool, synthesisStep.input ?? {}, {
413
+ maxTokens: typeof synthesisStep.maxTokens === 'number'
414
+ ? synthesisStep.maxTokens
415
+ : undefined,
416
+ skipValidation: true,
417
+ });
418
+ synthesisResult = toolResult.result;
419
+ console.error(`✅ Auto-synthesis executed with model: ${toolResult.modelUsed}`);
420
+ break; // Success
421
+ }
422
+ catch (error) {
423
+ attempt++;
424
+ if (attempt >= maxRetries) {
425
+ console.error(`❌ Auto-synthesis failed after ${maxRetries} attempts`);
426
+ throw error;
427
+ }
428
+ const backoff = 1000 * Math.pow(2, attempt - 1); // Exponential backoff
429
+ console.warn(`⚠️ Retrying synthesis in ${backoff}ms...`);
430
+ await new Promise(resolve => setTimeout(resolve, backoff));
431
+ }
432
+ }
433
+ // Convert synthesis result to FileReference
434
+ const synthesisFileRef = await parent.createFileReference(synthesisStep.name, synthesisResult, workflowId, workflowName, synthesisStep.saveToFile || false, outputDir, stepNumbers.get(synthesisStep.name), // Pass step number for filename
435
+ undefined // Model unknown for synthesis
436
+ );
437
+ // Store synthesis result
438
+ if (synthesisStep.output?.variable) {
439
+ variables[synthesisStep.output.variable] = synthesisFileRef;
440
+ stepOutputs[synthesisStep.name] = synthesisFileRef;
441
+ if (logLevel !== 'silent') {
442
+ console.error(`✅ Auto-synthesis complete! Stored FileReference in variables['${synthesisStep.output.variable}']`);
443
+ }
444
+ }
445
+ execution.outputs.push({
446
+ step: synthesisStep.name,
447
+ input: '[auto-synthesis]',
448
+ output: synthesisFileRef.summary,
449
+ filePath: synthesisFileRef.filePath || undefined
450
+ });
451
+ // Show synthesis result
452
+ if (logLevel !== 'silent') {
453
+ console.error(`\n${"=".repeat(80)}`);
454
+ console.error(`✅ AUTO-SYNTHESIS COMPLETE`);
455
+ console.error(`${"=".repeat(80)}`);
456
+ console.error(`\n${synthesisResult}\n`);
457
+ console.error(`${"=".repeat(80)}\n`);
458
+ }
459
+ }
460
+ catch (error) {
461
+ console.error(`⚠️ Auto-synthesis failed, continuing with full outputs:`, error);
462
+ // Don't throw - synthesis failure should not stop workflow
463
+ }
464
+ }
465
+ }
466
+ // Mark as completed
467
+ execution.status = "completed";
468
+ execution.endTime = new Date();
469
+ // Delete checkpoint on successful completion
470
+ if (workflow.settings?.autoSynthesis?.enabled && outputDir) {
471
+ await parent.deleteCheckpoint(outputDir);
472
+ }
473
+ // Workflow complete - manifest.json contains full execution details
474
+ console.error(`\n✅ Workflow complete! Manifest: ${path.join(outputDir, 'manifest.json')}\n`);
475
+ // Format output - prioritize runtime options over workflow YAML settings
476
+ return parent.formatOutput(execution, workflow.output?.format || "summary", options?.truncateSteps ?? workflow.output?.truncateSteps ?? true, options?.maxStepTokens ?? workflow.output?.maxStepTokens ?? 2500);
477
+ }
478
+ catch (error) {
479
+ execution.status = "failed";
480
+ execution.endTime = new Date();
481
+ // Manifest will reflect failed status
482
+ console.error(`\n❌ Workflow failed. Check manifest: ${path.join(outputDir, 'manifest.json')}\n`);
483
+ throw error;
484
+ }
485
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Workflow Executor
3
+ * Handles the main workflow execution logic
4
+ * Extracted from CustomWorkflowEngine to reduce file size
5
+ */
6
+ import * as path from 'path';
7
+ import { promises as fsPromises } from 'fs';
8
+ import { generateWorkflowId } from '../../utils/timestamp-formatter.js';
9
+ import { loadConfig } from '../../config.js';
10
+ import { WorkflowHelpers } from './WorkflowHelpers.js';
11
+ export class WorkflowExecutor {
12
+ constructor(sessionLogger, eventBus, variableInterpolator, stepParameterResolver, fileManager, outputFormatter, autoSynthesizer, callToolFn) {
13
+ this.sessionLogger = sessionLogger;
14
+ this.eventBus = eventBus;
15
+ this.variableInterpolator = variableInterpolator;
16
+ this.stepParameterResolver = stepParameterResolver;
17
+ this.fileManager = fileManager;
18
+ this.outputFormatter = outputFormatter;
19
+ this.autoSynthesizer = autoSynthesizer;
20
+ this.callToolFn = callToolFn;
21
+ }
22
+ async execute(workflow, input, options) {
23
+ // Generate unique workflow ID
24
+ const workflowId = generateWorkflowId();
25
+ // Setup output directory
26
+ const config = loadConfig();
27
+ const baseOutputDir = config.workflow.outputDir;
28
+ const outputDir = path.join(process.cwd(), baseOutputDir, workflow.name, workflowId);
29
+ // Initialize execution tracking
30
+ const execution = {
31
+ workflowName: workflow.name,
32
+ workflowId,
33
+ outputDir,
34
+ startTime: new Date(),
35
+ status: 'running',
36
+ cost: 0,
37
+ outputs: []
38
+ };
39
+ // Start session logging
40
+ const sessionId = await this.sessionLogger.startSession(`workflow-${workflow.name}`, input, workflow.name);
41
+ console.error(`\n📝 Session log: .workflow-sessions/${sessionId}.md`);
42
+ console.error(` To monitor progress: tail -f .workflow-sessions/${sessionId}.md\n`);
43
+ // Merge variables
44
+ const variables = {
45
+ ...workflow.variables,
46
+ ...options?.variables,
47
+ input,
48
+ query: input
49
+ };
50
+ console.error(`🔍 Workflow variables initialized:`, JSON.stringify(variables, null, 2));
51
+ // Calculate step numbers
52
+ const stepNumbers = WorkflowHelpers.calculateStepNumbers(workflow);
53
+ console.error(`📊 Step numbering calculated:`, Array.from(stepNumbers.entries())
54
+ .map(([name, num]) => `${num}: ${name}`)
55
+ .join(', '));
56
+ // Execute steps
57
+ const stepOutputs = {};
58
+ let previousOutput = input;
59
+ // Initialize output directory if needed
60
+ const needsFileOutput = workflow.steps.some((s) => s.saveToFile);
61
+ if (needsFileOutput) {
62
+ try {
63
+ await fsPromises.mkdir(outputDir, { recursive: true });
64
+ const manifest = {
65
+ workflowId,
66
+ workflowName: workflow.name,
67
+ startTime: new Date().toISOString(),
68
+ endTime: null,
69
+ status: 'running',
70
+ query: input,
71
+ steps: []
72
+ };
73
+ await fsPromises.writeFile(path.join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
74
+ console.error(`📁 Workflow output directory: ${outputDir}`);
75
+ }
76
+ catch (error) {
77
+ console.error('⚠️ Failed to initialize workflow directory:', error);
78
+ }
79
+ }
80
+ try {
81
+ // Execute all steps
82
+ for (let i = 0; i < workflow.steps.length; i++) {
83
+ const step = workflow.steps[i];
84
+ // Delegate to step execution (will be extracted further)
85
+ const result = await this.executeStep(step, i, workflow, variables, stepOutputs, previousOutput, execution, options);
86
+ if (result) {
87
+ previousOutput = result.output;
88
+ if (result.fileRef) {
89
+ stepOutputs[step.name] = result.fileRef;
90
+ }
91
+ }
92
+ }
93
+ execution.status = 'completed';
94
+ execution.endTime = new Date();
95
+ // End session
96
+ const sessionFile = await this.sessionLogger.endSession(true);
97
+ console.error(`\n✅ Workflow complete! Full session saved to: ${sessionFile}\n`);
98
+ // Format output
99
+ return this.outputFormatter.format(execution, workflow.output?.format || 'summary', options?.truncateSteps ?? workflow.output?.truncateSteps ?? true, options?.maxStepTokens ?? workflow.output?.maxStepTokens ?? 2500);
100
+ }
101
+ catch (error) {
102
+ execution.status = 'failed';
103
+ execution.endTime = new Date();
104
+ await this.sessionLogger.endSession(true);
105
+ throw error;
106
+ }
107
+ }
108
+ async executeStep(step, index, workflow, variables, stepOutputs, previousOutput, execution, options) {
109
+ // This will be extracted further in next iteration
110
+ // For now, just return null to indicate "not yet implemented"
111
+ return null;
112
+ }
113
+ }