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.
- package/.env.example +260 -0
- package/CHANGELOG.md +54 -0
- package/CODE_OF_CONDUCT.md +56 -0
- package/CONTRIBUTING.md +54 -0
- package/Dockerfile +36 -0
- package/LICENSE +644 -0
- package/README.md +201 -0
- package/SECURITY.md +95 -0
- package/dist/personality/komaai-expressions.js +12 -0
- package/dist/profiles/balanced.json +33 -0
- package/dist/profiles/code_focus.json +33 -0
- package/dist/profiles/full.json +33 -0
- package/dist/profiles/minimal.json +33 -0
- package/dist/profiles/research_power.json +33 -0
- package/dist/scripts/build-profiles.js +46 -0
- package/dist/src/application/services/focus/FocusModeRegistry.js +46 -0
- package/dist/src/application/services/focus/FocusTool.service.js +109 -0
- package/dist/src/application/services/focus/ModeRegistry.js +46 -0
- package/dist/src/application/services/focus/modes/focus-deep.mode.js +27 -0
- package/dist/src/application/services/focus/modes/status.mode.js +50 -0
- package/dist/src/application/services/focus/modes/tachibot-status.mode.js +50 -0
- package/dist/src/collaborative-orchestrator.js +391 -0
- package/dist/src/config/model-constants.js +188 -0
- package/dist/src/config/model-defaults.js +57 -0
- package/dist/src/config/model-preferences.js +382 -0
- package/dist/src/config/timeout-config.js +130 -0
- package/dist/src/config.js +173 -0
- package/dist/src/domain/interfaces/IFocusMode.js +5 -0
- package/dist/src/domain/interfaces/IProvider.js +6 -0
- package/dist/src/domain/interfaces/ITool.js +5 -0
- package/dist/src/focus-deep.js +245 -0
- package/dist/src/infrastructure/ascii/art/robots.ascii.js +16 -0
- package/dist/src/mcp-client.js +90 -0
- package/dist/src/memory/index.js +17 -0
- package/dist/src/memory/memory-config.js +135 -0
- package/dist/src/memory/memory-interface.js +174 -0
- package/dist/src/memory/memory-manager.js +383 -0
- package/dist/src/memory/providers/devlog-provider.js +385 -0
- package/dist/src/memory/providers/hybrid-provider.js +399 -0
- package/dist/src/memory/providers/local-provider.js +388 -0
- package/dist/src/memory/providers/mem0-provider.js +337 -0
- package/dist/src/modes/architect.js +477 -0
- package/dist/src/modes/auditor.js +362 -0
- package/dist/src/modes/challenger.js +841 -0
- package/dist/src/modes/code-reviewer.js +382 -0
- package/dist/src/modes/commit-guardian.js +424 -0
- package/dist/src/modes/documentation-writer.js +572 -0
- package/dist/src/modes/scout.js +587 -0
- package/dist/src/modes/shared/helpers/challenger-helpers.js +454 -0
- package/dist/src/modes/shared/helpers/index.js +17 -0
- package/dist/src/modes/shared/helpers/scout-helpers.js +270 -0
- package/dist/src/modes/shared/helpers/verifier-helpers.js +332 -0
- package/dist/src/modes/test-architect.js +767 -0
- package/dist/src/modes/verifier.js +378 -0
- package/dist/src/monitoring/performance-monitor.js +435 -0
- package/dist/src/optimization/batch-executor.js +121 -0
- package/dist/src/optimization/context-pruner.js +196 -0
- package/dist/src/optimization/cost-monitor.js +338 -0
- package/dist/src/optimization/index.js +65 -0
- package/dist/src/optimization/model-router.js +264 -0
- package/dist/src/optimization/result-cache.js +114 -0
- package/dist/src/optimization/token-optimizer.js +257 -0
- package/dist/src/optimization/token-tracker.js +118 -0
- package/dist/src/orchestrator-instructions.js +128 -0
- package/dist/src/orchestrator-lite.js +139 -0
- package/dist/src/orchestrator.js +191 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionEngine.js +1 -0
- package/dist/src/orchestrators/collaborative/interfaces/IToolExecutionStrategy.js +5 -0
- package/dist/src/orchestrators/collaborative/interfaces/IVisualizationRenderer.js +1 -0
- package/dist/src/orchestrators/collaborative/registries/ModelProviderRegistry.js +95 -0
- package/dist/src/orchestrators/collaborative/registries/ToolAdapterRegistry.js +64 -0
- package/dist/src/orchestrators/collaborative/services/tool-execution/ToolExecutionService.js +502 -0
- package/dist/src/orchestrators/collaborative/services/visualization/VisualizationService.js +206 -0
- package/dist/src/orchestrators/collaborative/types/session-types.js +5 -0
- package/dist/src/profiles/balanced.js +37 -0
- package/dist/src/profiles/code_focus.js +37 -0
- package/dist/src/profiles/debug_intensive.js +59 -0
- package/dist/src/profiles/full.js +37 -0
- package/dist/src/profiles/minimal.js +37 -0
- package/dist/src/profiles/research_code.js +59 -0
- package/dist/src/profiles/research_power.js +37 -0
- package/dist/src/profiles/types.js +5 -0
- package/dist/src/profiles/workflow_builder.js +53 -0
- package/dist/src/prompt-engineer-lite.js +78 -0
- package/dist/src/prompt-engineer.js +399 -0
- package/dist/src/reasoning-chain.js +508 -0
- package/dist/src/sequential-thinking.js +291 -0
- package/dist/src/server-diagnostic.js +74 -0
- package/dist/src/server-raw.js +158 -0
- package/dist/src/server-simple.js +58 -0
- package/dist/src/server.js +514 -0
- package/dist/src/session/session-logger.js +617 -0
- package/dist/src/session/session-manager.js +571 -0
- package/dist/src/session/session-tools.js +400 -0
- package/dist/src/tools/advanced-modes.js +200 -0
- package/dist/src/tools/claude-integration.js +356 -0
- package/dist/src/tools/consolidated/ai-router.js +174 -0
- package/dist/src/tools/consolidated/ai-tool.js +48 -0
- package/dist/src/tools/consolidated/brainstorm-tool.js +87 -0
- package/dist/src/tools/consolidated/environment-detector.js +80 -0
- package/dist/src/tools/consolidated/index.js +50 -0
- package/dist/src/tools/consolidated/search-tool.js +110 -0
- package/dist/src/tools/consolidated/workflow-tool.js +238 -0
- package/dist/src/tools/gemini-tools.js +329 -0
- package/dist/src/tools/grok-enhanced.js +376 -0
- package/dist/src/tools/grok-tools.js +299 -0
- package/dist/src/tools/lmstudio-tools.js +223 -0
- package/dist/src/tools/openai-tools.js +498 -0
- package/dist/src/tools/openrouter-tools.js +317 -0
- package/dist/src/tools/optimized-wrapper.js +204 -0
- package/dist/src/tools/perplexity-tools.js +294 -0
- package/dist/src/tools/pingpong-tool.js +343 -0
- package/dist/src/tools/qwen-wrapper.js +74 -0
- package/dist/src/tools/tool-router.js +444 -0
- package/dist/src/tools/unified-ai-provider.js +260 -0
- package/dist/src/tools/workflow-runner.js +425 -0
- package/dist/src/tools/workflow-validator-tool.js +107 -0
- package/dist/src/types.js +23 -0
- package/dist/src/utils/input-validator.js +130 -0
- package/dist/src/utils/model-router.js +91 -0
- package/dist/src/utils/progress-stream.js +255 -0
- package/dist/src/utils/provider-router.js +88 -0
- package/dist/src/utils/smart-api-client.js +146 -0
- package/dist/src/utils/table-builder.js +218 -0
- package/dist/src/utils/timestamp-formatter.js +134 -0
- package/dist/src/utils/tool-compressor.js +257 -0
- package/dist/src/utils/tool-config.js +201 -0
- package/dist/src/validators/dependency-graph-validator.js +147 -0
- package/dist/src/validators/interpolation-validator.js +222 -0
- package/dist/src/validators/output-usage-validator.js +151 -0
- package/dist/src/validators/syntax-validator.js +102 -0
- package/dist/src/validators/tool-registry-validator.js +123 -0
- package/dist/src/validators/tool-types.js +97 -0
- package/dist/src/validators/types.js +8 -0
- package/dist/src/validators/workflow-validator.js +134 -0
- package/dist/src/visualizer-lite.js +42 -0
- package/dist/src/visualizer.js +179 -0
- package/dist/src/workflows/circuit-breaker.js +199 -0
- package/dist/src/workflows/custom-workflows.js +451 -0
- package/dist/src/workflows/engine/AutoSynthesizer.js +97 -0
- package/dist/src/workflows/engine/StepParameterResolver.js +74 -0
- package/dist/src/workflows/engine/VariableInterpolator.js +123 -0
- package/dist/src/workflows/engine/WorkflowDiscovery.js +125 -0
- package/dist/src/workflows/engine/WorkflowExecutionEngine.js +485 -0
- package/dist/src/workflows/engine/WorkflowExecutor.js +113 -0
- package/dist/src/workflows/engine/WorkflowFileManager.js +244 -0
- package/dist/src/workflows/engine/WorkflowHelpers.js +114 -0
- package/dist/src/workflows/engine/WorkflowOutputFormatter.js +83 -0
- package/dist/src/workflows/engine/events/WorkflowEventBus.js +132 -0
- package/dist/src/workflows/engine/events/interfaces/IEventBus.js +5 -0
- package/dist/src/workflows/engine/handlers/ErrorRecoveryHandler.js +162 -0
- package/dist/src/workflows/engine/handlers/PromptEnhancementHandler.js +115 -0
- package/dist/src/workflows/engine/handlers/SessionPersistenceHandler.js +167 -0
- package/dist/src/workflows/engine/handlers/StepExecutionHandler.js +231 -0
- package/dist/src/workflows/engine/handlers/ToolInvocationHandler.js +46 -0
- package/dist/src/workflows/engine/interfaces/IAutoSynthesizer.js +5 -0
- package/dist/src/workflows/engine/interfaces/IStepParameterResolver.js +5 -0
- package/dist/src/workflows/engine/interfaces/IVariableInterpolator.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowDiscovery.js +4 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowFileManager.js +5 -0
- package/dist/src/workflows/engine/interfaces/IWorkflowOutputFormatter.js +5 -0
- package/dist/src/workflows/engine/state/WorkflowStateMachine.js +194 -0
- package/dist/src/workflows/engine/state/interfaces/IStateMachine.js +17 -0
- package/dist/src/workflows/fallback-strategies.js +373 -0
- package/dist/src/workflows/message-queue.js +455 -0
- package/dist/src/workflows/model-router.js +189 -0
- package/dist/src/workflows/orchestrator-examples.js +174 -0
- package/dist/src/workflows/orchestrator-integration.js +200 -0
- package/dist/src/workflows/self-healing.js +524 -0
- package/dist/src/workflows/tool-mapper.js +407 -0
- package/dist/src/workflows/tool-orchestrator.js +796 -0
- package/dist/src/workflows/workflow-engine.js +573 -0
- package/dist/src/workflows/workflow-parser.js +283 -0
- package/dist/src/workflows/workflow-types.js +95 -0
- package/dist/src/workflows.js +568 -0
- package/dist/test-workflow-file-output.js +93 -0
- package/docs/API_KEYS.md +570 -0
- package/docs/CLAUDE_CODE_SETUP.md +181 -0
- package/docs/CLAUDE_DESKTOP_MANUAL.md +127 -0
- package/docs/CONFIGURATION.md +745 -0
- package/docs/FOCUS_MODES.md +240 -0
- package/docs/INSTALLATION_BOTH.md +145 -0
- package/docs/TERMS.md +352 -0
- package/docs/TOOLS_REFERENCE.md +1622 -0
- package/docs/TOOL_PARAMETERS.md +496 -0
- package/docs/TOOL_PROFILES.md +236 -0
- package/docs/WORKFLOWS.md +987 -0
- package/docs/WORKFLOW_OUTPUT.md +198 -0
- package/docs/WORKFLOW_PROGRESS_TRACKING.md +305 -0
- package/docs/workflows/design-brainstorm.md +335 -0
- package/package.json +97 -0
- package/profiles/balanced.json +37 -0
- package/profiles/code_focus.json +37 -0
- package/profiles/debug_intensive.json +34 -0
- package/profiles/full.json +37 -0
- package/profiles/minimal.json +37 -0
- package/profiles/research_power.json +37 -0
- package/profiles/workflow_builder.json +37 -0
- package/smithery.yaml +66 -0
- package/start.sh +8 -0
- package/tools.config.json +81 -0
- package/tsconfig.json +18 -0
- package/workflows/accessibility-code-audit.yaml +92 -0
- package/workflows/code-architecture-review.yaml +202 -0
- package/workflows/code-review.yaml +142 -0
- package/workflows/core/iterative-problem-solver.yaml +283 -0
- package/workflows/creative-brainstorm-yaml.yaml +215 -0
- package/workflows/pingpong.yaml +141 -0
- package/workflows/system/README.md +412 -0
- package/workflows/system/challenger.yaml +175 -0
- package/workflows/system/scout.yaml +164 -0
- package/workflows/system/verifier.yaml +133 -0
- package/workflows/ultra-creative-brainstorm.yaml +318 -0
- package/workflows/ux-research-flow.yaml +92 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { ModelRouter } from './model-router.js';
|
|
5
|
+
import { WorkflowParser } from './workflow-parser.js';
|
|
6
|
+
import { TokenTracker } from '../optimization/token-tracker.js';
|
|
7
|
+
import { ResultCache } from '../optimization/result-cache.js';
|
|
8
|
+
import { BatchExecutor } from '../optimization/batch-executor.js';
|
|
9
|
+
import { performanceMonitor } from '../monitoring/performance-monitor.js';
|
|
10
|
+
import { costMonitor } from '../optimization/cost-monitor.js';
|
|
11
|
+
export class WorkflowEngine {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.modelRouter = new ModelRouter();
|
|
14
|
+
this.parser = new WorkflowParser();
|
|
15
|
+
this.tokenTracker = new TokenTracker();
|
|
16
|
+
this.cache = new ResultCache();
|
|
17
|
+
this.batchExecutor = new BatchExecutor();
|
|
18
|
+
}
|
|
19
|
+
async parseWorkflow(yaml) {
|
|
20
|
+
return this.parser.parse(yaml);
|
|
21
|
+
}
|
|
22
|
+
async executeWorkflow(workflow, query) {
|
|
23
|
+
// Generate unique workflow ID: YYYYMMDD-HHMMSS-xxxx
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const timestamp = now.toISOString().replace(/[-:]/g, '').split('.')[0].replace('T', '-');
|
|
26
|
+
const shortId = randomUUID().split('-')[0];
|
|
27
|
+
const workflowId = `${timestamp}-${shortId}`;
|
|
28
|
+
// Setup output directory
|
|
29
|
+
const outputDir = join(process.cwd(), 'workflow-output', workflow.name, workflowId);
|
|
30
|
+
const context = {
|
|
31
|
+
workflowId,
|
|
32
|
+
workflowName: workflow.name,
|
|
33
|
+
query,
|
|
34
|
+
results: new Map(),
|
|
35
|
+
tokens: new Map(),
|
|
36
|
+
metadata: new Map(),
|
|
37
|
+
startTime: Date.now(),
|
|
38
|
+
outputDir
|
|
39
|
+
};
|
|
40
|
+
// Create output directory and manifest
|
|
41
|
+
await this.initializeWorkflowDirectory(context, workflow);
|
|
42
|
+
const stepGroups = this.groupStepsByDependencies(workflow.steps);
|
|
43
|
+
for (const group of stepGroups) {
|
|
44
|
+
if (group.length === 1 && !group[0].parallel) {
|
|
45
|
+
await this.executeStep(group[0], context, workflow);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await this.executeParallelSteps(group, context, workflow);
|
|
49
|
+
}
|
|
50
|
+
if (workflow.defaults?.earlyTermination && this.shouldTerminateEarly(context)) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return this.synthesizeResults(context, workflow);
|
|
55
|
+
}
|
|
56
|
+
async executeStep(step, context, workflow) {
|
|
57
|
+
if (step.condition && !this.evaluateCondition(step.condition, context)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const cacheKey = this.getCacheKey(step, context);
|
|
61
|
+
const cached = await this.cache.get(cacheKey);
|
|
62
|
+
if (cached) {
|
|
63
|
+
context.results.set(step.id, cached.result);
|
|
64
|
+
context.tokens.set(step.id, cached.tokens);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const models = this.selectModels(step, context);
|
|
68
|
+
const maxTokens = step.maxTokens || workflow.defaults?.maxTokens || 2000;
|
|
69
|
+
const stepContext = await this.buildStepContext(step, context);
|
|
70
|
+
const requestMeta = models.map(model => ({
|
|
71
|
+
model,
|
|
72
|
+
requestId: `${step.id}-${model}-${randomUUID()}`
|
|
73
|
+
}));
|
|
74
|
+
requestMeta.forEach(({ requestId, model }) => {
|
|
75
|
+
performanceMonitor.startRequest(requestId, model);
|
|
76
|
+
});
|
|
77
|
+
let result;
|
|
78
|
+
try {
|
|
79
|
+
result = await this.executeModeWithModels(step.mode, models, stepContext, maxTokens, step);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
requestMeta.forEach(({ requestId, model }) => {
|
|
83
|
+
performanceMonitor.completeRequest(requestId, false, undefined, error instanceof Error ? error.message : String(error), { stepId: step.id, mode: step.mode, model });
|
|
84
|
+
});
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
context.results.set(step.id, result);
|
|
88
|
+
const metrics = this.tokenTracker.track(step.id, result, models[0]);
|
|
89
|
+
context.tokens.set(step.id, metrics.total);
|
|
90
|
+
// NEW: Save to file if requested
|
|
91
|
+
if (step.saveToFile && context.outputDir) {
|
|
92
|
+
try {
|
|
93
|
+
const filename = await this.saveStepOutput(context, step.id, result, metrics);
|
|
94
|
+
// Replace in-memory result with file reference + summary
|
|
95
|
+
context.results.set(step.id, {
|
|
96
|
+
savedTo: filename,
|
|
97
|
+
filePath: join(context.outputDir, filename),
|
|
98
|
+
summary: this.extractSummary(result),
|
|
99
|
+
tokens: metrics.total
|
|
100
|
+
});
|
|
101
|
+
await this.updateManifest(context, step.id, 'completed', metrics, filename);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error(`Failed to save step output for ${step.id}:`, error);
|
|
105
|
+
await this.updateManifest(context, step.id, 'failed', metrics, undefined, error instanceof Error ? error.message : String(error));
|
|
106
|
+
// Don't throw - keep the in-memory result
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (models[0]) {
|
|
110
|
+
const costResult = await costMonitor.trackUsage(models[0], metrics.input, metrics.output, step.id);
|
|
111
|
+
if (costResult.alert) {
|
|
112
|
+
console.warn(`⚠️ Cost Alert (${models[0]}): ${costResult.alert.message}`);
|
|
113
|
+
}
|
|
114
|
+
if (!costResult.allowed) {
|
|
115
|
+
requestMeta.forEach(({ requestId, model }) => {
|
|
116
|
+
performanceMonitor.completeRequest(requestId, false, { input: metrics.input, output: metrics.output }, costResult.alert?.message || 'Budget exceeded', { stepId: step.id, mode: step.mode, model });
|
|
117
|
+
});
|
|
118
|
+
throw new Error(`Cost limit exceeded for ${models[0]} on step ${step.id}: ${costResult.alert?.message || 'budget exceeded'}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const primaryRequest = requestMeta[0];
|
|
122
|
+
if (primaryRequest) {
|
|
123
|
+
performanceMonitor.completeRequest(primaryRequest.requestId, true, { input: metrics.input, output: metrics.output }, undefined, { stepId: step.id, mode: step.mode, model: primaryRequest.model });
|
|
124
|
+
// Mark secondary models as completed for visibility (token usage tracked via primary)
|
|
125
|
+
requestMeta.slice(1).forEach(({ requestId, model }) => {
|
|
126
|
+
performanceMonitor.completeRequest(requestId, true, undefined, undefined, { stepId: step.id, mode: step.mode, model });
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
await this.cache.set(cacheKey, {
|
|
130
|
+
result,
|
|
131
|
+
tokens: metrics.total,
|
|
132
|
+
timestamp: Date.now()
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async executeParallelSteps(steps, context, workflow) {
|
|
136
|
+
console.error(`[Workflow] Starting ${steps.length} parallel steps: ${steps.map(s => s.id).join(', ')}`);
|
|
137
|
+
// Track execution progress
|
|
138
|
+
const tracker = {
|
|
139
|
+
started: new Set(),
|
|
140
|
+
completed: new Set(),
|
|
141
|
+
failed: new Set()
|
|
142
|
+
};
|
|
143
|
+
// Execute all steps with individual error handling
|
|
144
|
+
const executions = steps.map(async (step, index) => {
|
|
145
|
+
tracker.started.add(step.id);
|
|
146
|
+
console.error(`[Workflow] Step ${index + 1}/${steps.length} started: ${step.id}`);
|
|
147
|
+
try {
|
|
148
|
+
const result = await this.executeStep(step, context, workflow);
|
|
149
|
+
tracker.completed.add(step.id);
|
|
150
|
+
console.error(`[Workflow] Step ${index + 1}/${steps.length} ✓ completed: ${step.id}`);
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
tracker.failed.add(step.id);
|
|
155
|
+
console.error(`[Workflow] Step ${index + 1}/${steps.length} ✗ failed: ${step.id}`, error);
|
|
156
|
+
// Store error in context for later inspection
|
|
157
|
+
context.metadata.set(`error:${step.id}`, error instanceof Error ? error.message : String(error));
|
|
158
|
+
// Update manifest for failed step
|
|
159
|
+
await this.updateManifest(context, step.id, 'failed', undefined, undefined, error instanceof Error ? error.message : String(error));
|
|
160
|
+
throw error; // Re-throw so Promise.allSettled captures it
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
// Use Promise.allSettled to ensure ALL steps execute regardless of failures
|
|
164
|
+
const results = await Promise.allSettled(executions);
|
|
165
|
+
// Log execution summary
|
|
166
|
+
const succeeded = results.filter(r => r.status === 'fulfilled').length;
|
|
167
|
+
const failed = results.filter(r => r.status === 'rejected').length;
|
|
168
|
+
console.error(`[Workflow] Parallel execution complete: ${succeeded} succeeded, ${failed} failed`);
|
|
169
|
+
// Debug summary
|
|
170
|
+
console.error('[Workflow] Execution summary:', {
|
|
171
|
+
started: tracker.started.size,
|
|
172
|
+
completed: tracker.completed.size,
|
|
173
|
+
failed: tracker.failed.size,
|
|
174
|
+
notStarted: steps.filter(s => !tracker.started.has(s.id)).map(s => s.id),
|
|
175
|
+
notCompleted: steps.filter(s => !tracker.completed.has(s.id)).map(s => s.id)
|
|
176
|
+
});
|
|
177
|
+
// Only throw if ALL steps failed (partial success is acceptable for file-based workflows)
|
|
178
|
+
if (failed === steps.length) {
|
|
179
|
+
throw new Error(`All ${steps.length} parallel steps failed`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
selectModels(step, context) {
|
|
183
|
+
if (step.model) {
|
|
184
|
+
return Array.isArray(step.model) ? step.model : [step.model];
|
|
185
|
+
}
|
|
186
|
+
const task = {
|
|
187
|
+
type: step.mode,
|
|
188
|
+
complexity: this.estimateComplexity(context),
|
|
189
|
+
needsCurrentInfo: step.mode === 'scout' || step.mode === 'research'
|
|
190
|
+
};
|
|
191
|
+
const constraints = {
|
|
192
|
+
budget: context.metadata.get('maxCostPerQuery'),
|
|
193
|
+
speed: context.metadata.get('preferSpeed')
|
|
194
|
+
};
|
|
195
|
+
return [this.modelRouter.selectModel(task, constraints)];
|
|
196
|
+
}
|
|
197
|
+
async buildStepContext(step, context) {
|
|
198
|
+
let stepContext = { query: context.query };
|
|
199
|
+
// NEW: Load files if requested
|
|
200
|
+
if (step.loadFiles && step.loadFiles.length > 0) {
|
|
201
|
+
const loadedFiles = await this.loadStepFiles(context, step.loadFiles);
|
|
202
|
+
stepContext.loadedFiles = loadedFiles;
|
|
203
|
+
}
|
|
204
|
+
if (step.contextFrom) {
|
|
205
|
+
const parts = step.contextFrom.split('.');
|
|
206
|
+
const stepId = parts[0];
|
|
207
|
+
const property = parts[1];
|
|
208
|
+
const result = context.results.get(stepId);
|
|
209
|
+
if (result) {
|
|
210
|
+
stepContext = {
|
|
211
|
+
...stepContext,
|
|
212
|
+
...(property ? { [property]: result[property] } : { context: result })
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return stepContext;
|
|
217
|
+
}
|
|
218
|
+
async executeModeWithModels(mode, models, context, maxTokens, step) {
|
|
219
|
+
if (models.length === 1) {
|
|
220
|
+
return this.executeMode(mode, models[0], context, maxTokens, step);
|
|
221
|
+
}
|
|
222
|
+
const executions = models.map(model => this.executeMode(mode, model, context, maxTokens, step));
|
|
223
|
+
const results = await Promise.all(executions);
|
|
224
|
+
return this.combineModelResults(results, step);
|
|
225
|
+
}
|
|
226
|
+
async executeMode(mode, model, context, maxTokens, step) {
|
|
227
|
+
const modeHandlers = {
|
|
228
|
+
'verifier': async (m, c, t, s) => {
|
|
229
|
+
const { Verifier } = await import('../modes/verifier.js');
|
|
230
|
+
const verifier = new Verifier();
|
|
231
|
+
return verifier.verify(c.query, {
|
|
232
|
+
model: m,
|
|
233
|
+
maxTokens: t,
|
|
234
|
+
variant: s.variant
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
'challenger': async (m, c, t, s) => {
|
|
238
|
+
const { Challenger } = await import('../modes/challenger.js');
|
|
239
|
+
const challenger = new Challenger();
|
|
240
|
+
return challenger.challenge(c, {
|
|
241
|
+
model: m,
|
|
242
|
+
maxTokens: t
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
'scout': async (m, c, t, s) => {
|
|
246
|
+
const { Scout } = await import('../modes/scout.js');
|
|
247
|
+
const scout = new Scout();
|
|
248
|
+
return scout.scout(c.query, {
|
|
249
|
+
variant: s.variant || 'research_scout',
|
|
250
|
+
maxTokens: t
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
'auditor': async (m, c, t, s) => {
|
|
254
|
+
const { Auditor } = await import('../modes/auditor.js');
|
|
255
|
+
const auditor = new Auditor();
|
|
256
|
+
return auditor.audit(c, {
|
|
257
|
+
model: m,
|
|
258
|
+
maxTokens: t
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
'commit_guardian': async (m, c, t, s) => {
|
|
262
|
+
const { CommitGuardian } = await import('../modes/commit-guardian.js');
|
|
263
|
+
const guardian = new CommitGuardian();
|
|
264
|
+
return guardian.validate(c, {
|
|
265
|
+
model: m,
|
|
266
|
+
maxTokens: t
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
'architect': async (m, c, t, s) => {
|
|
270
|
+
const { Architect } = await import('../modes/architect.js');
|
|
271
|
+
const architect = new Architect();
|
|
272
|
+
return architect.analyze(c.query || c, {
|
|
273
|
+
maxTokens: t,
|
|
274
|
+
depth: s.variant
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
'synthesis': async (m, c, t, s) => {
|
|
278
|
+
return this.synthesize(c, t);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const handler = modeHandlers[mode];
|
|
282
|
+
if (!handler) {
|
|
283
|
+
throw new Error(`Unknown mode: ${mode}`);
|
|
284
|
+
}
|
|
285
|
+
return handler(model, context, maxTokens, step);
|
|
286
|
+
}
|
|
287
|
+
combineModelResults(results, step) {
|
|
288
|
+
if (step.mode === 'verifier') {
|
|
289
|
+
return this.calculateConsensus(results);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
results,
|
|
293
|
+
combined: true,
|
|
294
|
+
count: results.length
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
calculateConsensus(responses) {
|
|
298
|
+
const grouped = new Map();
|
|
299
|
+
for (const response of responses) {
|
|
300
|
+
const key = JSON.stringify(response.conclusion || response);
|
|
301
|
+
if (!grouped.has(key)) {
|
|
302
|
+
grouped.set(key, []);
|
|
303
|
+
}
|
|
304
|
+
grouped.get(key).push(response);
|
|
305
|
+
}
|
|
306
|
+
const sorted = Array.from(grouped.entries())
|
|
307
|
+
.sort((a, b) => b[1].length - a[1].length);
|
|
308
|
+
const majority = sorted[0];
|
|
309
|
+
const consensus = majority[1].length / responses.length;
|
|
310
|
+
const outliers = sorted.slice(1).flatMap(([_, responses]) => responses);
|
|
311
|
+
return {
|
|
312
|
+
consensus,
|
|
313
|
+
majority: majority[0],
|
|
314
|
+
outliers,
|
|
315
|
+
shouldTerminate: consensus >= 0.8
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
shouldTerminateEarly(context) {
|
|
319
|
+
const lastResult = Array.from(context.results.values()).pop();
|
|
320
|
+
if (lastResult && lastResult.consensus && lastResult.consensus >= 0.9) {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
groupStepsByDependencies(steps) {
|
|
326
|
+
const groups = [];
|
|
327
|
+
const processed = new Set();
|
|
328
|
+
while (processed.size < steps.length) {
|
|
329
|
+
const group = [];
|
|
330
|
+
for (const step of steps) {
|
|
331
|
+
if (processed.has(step.id))
|
|
332
|
+
continue;
|
|
333
|
+
const ready = !step.dependsOn ||
|
|
334
|
+
step.dependsOn.every(dep => processed.has(dep));
|
|
335
|
+
if (ready) {
|
|
336
|
+
group.push(step);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (group.length === 0) {
|
|
340
|
+
throw new Error('Circular dependency detected in workflow');
|
|
341
|
+
}
|
|
342
|
+
groups.push(group);
|
|
343
|
+
group.forEach(step => processed.add(step.id));
|
|
344
|
+
}
|
|
345
|
+
return groups;
|
|
346
|
+
}
|
|
347
|
+
getCacheKey(step, context) {
|
|
348
|
+
return `${step.mode}:${step.id}:${context.query}:${JSON.stringify(step)}`;
|
|
349
|
+
}
|
|
350
|
+
estimateComplexity(context) {
|
|
351
|
+
const queryLength = context.query.length;
|
|
352
|
+
const resultsCount = context.results.size;
|
|
353
|
+
if (queryLength > 500 || resultsCount > 5)
|
|
354
|
+
return 0.8;
|
|
355
|
+
if (queryLength > 200 || resultsCount > 2)
|
|
356
|
+
return 0.5;
|
|
357
|
+
return 0.2;
|
|
358
|
+
}
|
|
359
|
+
evaluateCondition(condition, context) {
|
|
360
|
+
try {
|
|
361
|
+
const func = new Function('context', `return ${condition}`);
|
|
362
|
+
return func(context);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async synthesize(context, maxTokens) {
|
|
369
|
+
const inputs = Object.values(context).filter(v => v != null);
|
|
370
|
+
return {
|
|
371
|
+
synthesis: `Combined insights from ${inputs.length} sources`,
|
|
372
|
+
inputs,
|
|
373
|
+
timestamp: Date.now()
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// File-based workflow output methods
|
|
378
|
+
// ============================================================================
|
|
379
|
+
async initializeWorkflowDirectory(context, workflow) {
|
|
380
|
+
if (!context.outputDir)
|
|
381
|
+
return;
|
|
382
|
+
try {
|
|
383
|
+
await fs.mkdir(context.outputDir, { recursive: true });
|
|
384
|
+
const manifest = {
|
|
385
|
+
workflowId: context.workflowId,
|
|
386
|
+
workflowName: context.workflowName,
|
|
387
|
+
startTime: new Date(context.startTime).toISOString(),
|
|
388
|
+
endTime: null,
|
|
389
|
+
status: 'running',
|
|
390
|
+
query: context.query,
|
|
391
|
+
steps: []
|
|
392
|
+
};
|
|
393
|
+
await fs.writeFile(join(context.outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
console.error('Failed to initialize workflow directory:', error);
|
|
397
|
+
// Clear outputDir to prevent further file operations
|
|
398
|
+
context.outputDir = undefined;
|
|
399
|
+
throw new Error(`Failed to initialize workflow directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async updateManifest(context, stepId, status, metrics, outputFile, error) {
|
|
403
|
+
if (!context.outputDir)
|
|
404
|
+
return;
|
|
405
|
+
try {
|
|
406
|
+
const manifestPath = join(context.outputDir, 'manifest.json');
|
|
407
|
+
const manifestData = await fs.readFile(manifestPath, 'utf-8');
|
|
408
|
+
const manifest = JSON.parse(manifestData);
|
|
409
|
+
const existingStepIndex = manifest.steps.findIndex((s) => s.id === stepId);
|
|
410
|
+
const stepData = {
|
|
411
|
+
id: stepId,
|
|
412
|
+
status,
|
|
413
|
+
outputFile,
|
|
414
|
+
tokens: metrics?.total,
|
|
415
|
+
cost: metrics?.cost,
|
|
416
|
+
error,
|
|
417
|
+
timestamp: new Date().toISOString()
|
|
418
|
+
};
|
|
419
|
+
if (existingStepIndex >= 0) {
|
|
420
|
+
manifest.steps[existingStepIndex] = stepData;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
manifest.steps.push(stepData);
|
|
424
|
+
}
|
|
425
|
+
// Update workflow status
|
|
426
|
+
const allCompleted = manifest.steps.every((s) => s.status === 'completed');
|
|
427
|
+
const anyFailed = manifest.steps.some((s) => s.status === 'failed');
|
|
428
|
+
if (anyFailed) {
|
|
429
|
+
manifest.status = 'failed';
|
|
430
|
+
manifest.endTime = new Date().toISOString();
|
|
431
|
+
}
|
|
432
|
+
else if (allCompleted && manifest.steps.length > 0) {
|
|
433
|
+
manifest.status = 'completed';
|
|
434
|
+
manifest.endTime = new Date().toISOString();
|
|
435
|
+
}
|
|
436
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
console.error(`Failed to update manifest for step ${stepId}:`, error);
|
|
440
|
+
// Don't throw - manifest updates are not critical
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async saveStepOutput(context, stepId, result, metrics) {
|
|
444
|
+
if (!context.outputDir) {
|
|
445
|
+
throw new Error('Output directory not initialized');
|
|
446
|
+
}
|
|
447
|
+
const filename = `${stepId}.md`;
|
|
448
|
+
const filepath = join(context.outputDir, filename);
|
|
449
|
+
const markdown = this.formatAsMarkdown(stepId, result, metrics, context);
|
|
450
|
+
await fs.writeFile(filepath, markdown);
|
|
451
|
+
return filename;
|
|
452
|
+
}
|
|
453
|
+
formatAsMarkdown(stepId, result, metrics, context) {
|
|
454
|
+
const lines = [];
|
|
455
|
+
lines.push(`# ${stepId}`);
|
|
456
|
+
lines.push('');
|
|
457
|
+
lines.push(`**Workflow:** ${context.workflowName}`);
|
|
458
|
+
lines.push(`**Workflow ID:** ${context.workflowId}`);
|
|
459
|
+
lines.push(`**Timestamp:** ${new Date().toISOString()}`);
|
|
460
|
+
lines.push('');
|
|
461
|
+
lines.push(`## Metrics`);
|
|
462
|
+
lines.push('');
|
|
463
|
+
lines.push(`- **Model:** ${metrics.model}`);
|
|
464
|
+
lines.push(`- **Input Tokens:** ${metrics.input.toLocaleString()}`);
|
|
465
|
+
lines.push(`- **Output Tokens:** ${metrics.output.toLocaleString()}`);
|
|
466
|
+
lines.push(`- **Total Tokens:** ${metrics.total.toLocaleString()}`);
|
|
467
|
+
lines.push(`- **Estimated Cost:** $${metrics.cost.toFixed(4)}`);
|
|
468
|
+
lines.push('');
|
|
469
|
+
lines.push(`## Result`);
|
|
470
|
+
lines.push('');
|
|
471
|
+
if (typeof result === 'string') {
|
|
472
|
+
lines.push(result);
|
|
473
|
+
}
|
|
474
|
+
else if (result && typeof result === 'object') {
|
|
475
|
+
// Handle structured results
|
|
476
|
+
if (result.summary) {
|
|
477
|
+
lines.push(`### Summary`);
|
|
478
|
+
lines.push('');
|
|
479
|
+
lines.push(result.summary);
|
|
480
|
+
lines.push('');
|
|
481
|
+
}
|
|
482
|
+
if (result.findings || result.issues) {
|
|
483
|
+
lines.push(`### Findings`);
|
|
484
|
+
lines.push('');
|
|
485
|
+
const findings = result.findings || result.issues;
|
|
486
|
+
if (Array.isArray(findings)) {
|
|
487
|
+
findings.forEach((finding, i) => {
|
|
488
|
+
lines.push(`${i + 1}. ${JSON.stringify(finding, null, 2)}`);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
lines.push(JSON.stringify(findings, null, 2));
|
|
493
|
+
}
|
|
494
|
+
lines.push('');
|
|
495
|
+
}
|
|
496
|
+
if (result.recommendations) {
|
|
497
|
+
lines.push(`### Recommendations`);
|
|
498
|
+
lines.push('');
|
|
499
|
+
if (Array.isArray(result.recommendations)) {
|
|
500
|
+
result.recommendations.forEach((rec, i) => {
|
|
501
|
+
lines.push(`${i + 1}. ${rec}`);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
lines.push(result.recommendations);
|
|
506
|
+
}
|
|
507
|
+
lines.push('');
|
|
508
|
+
}
|
|
509
|
+
// Add full raw result
|
|
510
|
+
lines.push(`### Raw Result`);
|
|
511
|
+
lines.push('');
|
|
512
|
+
lines.push('```json');
|
|
513
|
+
lines.push(JSON.stringify(result, null, 2));
|
|
514
|
+
lines.push('```');
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
lines.push(String(result));
|
|
518
|
+
}
|
|
519
|
+
return lines.join('\n');
|
|
520
|
+
}
|
|
521
|
+
async loadStepFiles(context, stepIds) {
|
|
522
|
+
if (!context.outputDir) {
|
|
523
|
+
return {};
|
|
524
|
+
}
|
|
525
|
+
const loaded = {};
|
|
526
|
+
for (const stepId of stepIds) {
|
|
527
|
+
const filepath = join(context.outputDir, `${stepId}.md`);
|
|
528
|
+
try {
|
|
529
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
530
|
+
loaded[stepId] = content;
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
console.warn(`Could not load file for step ${stepId}: ${error}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return loaded;
|
|
537
|
+
}
|
|
538
|
+
extractSummary(result) {
|
|
539
|
+
if (typeof result === 'string') {
|
|
540
|
+
return result.slice(0, 200) + (result.length > 200 ? '...' : '');
|
|
541
|
+
}
|
|
542
|
+
if (result?.summary) {
|
|
543
|
+
return typeof result.summary === 'string'
|
|
544
|
+
? result.summary
|
|
545
|
+
: JSON.stringify(result.summary).slice(0, 200) + '...';
|
|
546
|
+
}
|
|
547
|
+
const resultStr = JSON.stringify(result);
|
|
548
|
+
return resultStr.slice(0, 200) + (resultStr.length > 200 ? '...' : '');
|
|
549
|
+
}
|
|
550
|
+
// ============================================================================
|
|
551
|
+
async synthesizeResults(context, workflow) {
|
|
552
|
+
const duration = Date.now() - context.startTime;
|
|
553
|
+
const totalTokens = Array.from(context.tokens.values()).reduce((a, b) => a + b, 0);
|
|
554
|
+
const response = {
|
|
555
|
+
workflow: workflow.name,
|
|
556
|
+
workflowId: context.workflowId,
|
|
557
|
+
query: context.query,
|
|
558
|
+
results: Object.fromEntries(context.results),
|
|
559
|
+
metrics: {
|
|
560
|
+
duration,
|
|
561
|
+
totalTokens,
|
|
562
|
+
estimatedCost: workflow.estimatedCost,
|
|
563
|
+
steps: context.results.size
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
// Include output directory info if files were saved
|
|
567
|
+
if (context.outputDir) {
|
|
568
|
+
response.outputDirectory = context.outputDir;
|
|
569
|
+
response.message = `Results saved to: ${context.outputDir}`;
|
|
570
|
+
}
|
|
571
|
+
return response;
|
|
572
|
+
}
|
|
573
|
+
}
|