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,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow File Manager
|
|
3
|
+
* Handles file references, output saving, and manifest management
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fsPromises } from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export class WorkflowFileManager {
|
|
8
|
+
/**
|
|
9
|
+
* Extracts a summary from content (first 200 chars)
|
|
10
|
+
*/
|
|
11
|
+
extractSummary(result) {
|
|
12
|
+
// Handle null/undefined explicitly
|
|
13
|
+
if (result === null)
|
|
14
|
+
return '[null]';
|
|
15
|
+
if (result === undefined)
|
|
16
|
+
return '[undefined]';
|
|
17
|
+
// Handle primitives (including falsy: 0, false, "")
|
|
18
|
+
if (typeof result === 'string') {
|
|
19
|
+
return this.truncateString(result, 200);
|
|
20
|
+
}
|
|
21
|
+
if (typeof result === 'number' || typeof result === 'boolean') {
|
|
22
|
+
return String(result);
|
|
23
|
+
}
|
|
24
|
+
// Handle objects with summary property
|
|
25
|
+
if (result && typeof result === 'object' && 'summary' in result) {
|
|
26
|
+
const summary = result.summary;
|
|
27
|
+
if (typeof summary === 'string') {
|
|
28
|
+
return this.truncateString(summary, 200);
|
|
29
|
+
}
|
|
30
|
+
// If summary is not a string, JSON.stringify it
|
|
31
|
+
try {
|
|
32
|
+
const summaryStr = JSON.stringify(summary, this.getCircularReplacer());
|
|
33
|
+
return this.truncateString(summaryStr, 200);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return '[Summary unavailable]';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Default: JSON.stringify with circular ref handling
|
|
40
|
+
try {
|
|
41
|
+
const resultStr = JSON.stringify(result, this.getCircularReplacer());
|
|
42
|
+
return this.truncateString(resultStr, 200);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return '[Summary unavailable]';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates a file reference for step output
|
|
50
|
+
*/
|
|
51
|
+
async createFileReference(options) {
|
|
52
|
+
const { stepName, content, workflowId, workflowName, saveToFile: initialSaveToFile, outputDir, stepNumber, modelName } = options;
|
|
53
|
+
const id = `${workflowId}-${stepName}-${Date.now()}`;
|
|
54
|
+
const summary = this.extractSummary(content);
|
|
55
|
+
const sizeBytes = Buffer.byteLength(content, 'utf-8');
|
|
56
|
+
let filePath = null;
|
|
57
|
+
let _inMemoryContent = undefined;
|
|
58
|
+
let saveToFile = initialSaveToFile;
|
|
59
|
+
// Auto-promote large in-memory results to file (>1MB)
|
|
60
|
+
if (!saveToFile && sizeBytes > 1000000) {
|
|
61
|
+
console.error(`⚠️ Step ${stepName} output is ${sizeBytes} bytes - forcing file save`);
|
|
62
|
+
saveToFile = true;
|
|
63
|
+
}
|
|
64
|
+
if (saveToFile && outputDir) {
|
|
65
|
+
// Save to file with step number and datetime
|
|
66
|
+
const safeStepName = stepName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
67
|
+
// Generate datetime string: YYYY-MM-DD-HH-MM-SS-DayName
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const year = now.getFullYear();
|
|
70
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
71
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
72
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
73
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
74
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
75
|
+
const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
76
|
+
const dayName = dayNames[now.getDay()];
|
|
77
|
+
const datetime = `${year}-${month}-${day}-${hours}-${minutes}-${seconds}-${dayName}`;
|
|
78
|
+
// Sanitize model name for filename (remove special chars, keep alphanumeric and hyphens)
|
|
79
|
+
const safeModelName = modelName ? modelName.replace(/[^a-zA-Z0-9-]/g, '-') : undefined;
|
|
80
|
+
// Build filename: {stepNumber}-{stepName}-{modelName}-{datetime}.md
|
|
81
|
+
const filenamePrefix = stepNumber ? `${stepNumber}-` : '';
|
|
82
|
+
const modelSuffix = safeModelName ? `-${safeModelName}` : '';
|
|
83
|
+
const filename = `${filenamePrefix}${safeStepName}${modelSuffix}-${datetime}.md`;
|
|
84
|
+
filePath = path.join(outputDir, filename);
|
|
85
|
+
try {
|
|
86
|
+
// Format as markdown with metadata
|
|
87
|
+
const metadataLines = [
|
|
88
|
+
`# ${stepName}`,
|
|
89
|
+
'',
|
|
90
|
+
`**Workflow:** ${workflowName}`,
|
|
91
|
+
`**Workflow ID:** ${workflowId}`,
|
|
92
|
+
`**Timestamp:** ${new Date().toISOString()}`,
|
|
93
|
+
`**Size:** ${sizeBytes} bytes`,
|
|
94
|
+
...(modelName ? [`**Model:** ${modelName}`] : []),
|
|
95
|
+
'',
|
|
96
|
+
'## Output',
|
|
97
|
+
'',
|
|
98
|
+
content,
|
|
99
|
+
''
|
|
100
|
+
];
|
|
101
|
+
await fsPromises.writeFile(filePath, metadataLines.join('\n'));
|
|
102
|
+
console.error(`💾 Saved step output to: ${filePath}`);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(`⚠️ File save failed for ${stepName}, falling back to in-memory:`, error);
|
|
106
|
+
filePath = null;
|
|
107
|
+
_inMemoryContent = content;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Keep in memory
|
|
112
|
+
_inMemoryContent = content;
|
|
113
|
+
}
|
|
114
|
+
// Create lazy loader with caching
|
|
115
|
+
let _cachedContent = null;
|
|
116
|
+
const getContent = async () => {
|
|
117
|
+
// Return cached content if available
|
|
118
|
+
if (_cachedContent)
|
|
119
|
+
return _cachedContent;
|
|
120
|
+
if (filePath) {
|
|
121
|
+
try {
|
|
122
|
+
const markdown = await fsPromises.readFile(filePath, 'utf-8');
|
|
123
|
+
// Extract content from markdown (skip metadata)
|
|
124
|
+
const outputMatch = markdown.match(/## Output\s*\n\n([\s\S]*)/);
|
|
125
|
+
_cachedContent = outputMatch ? outputMatch[1].trim() : markdown;
|
|
126
|
+
return _cachedContent;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw new Error(`Failed to load content from ${filePath}: ${error}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (_inMemoryContent !== undefined) {
|
|
133
|
+
_cachedContent = _inMemoryContent;
|
|
134
|
+
return _cachedContent;
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`No content available for step ${stepName}`);
|
|
137
|
+
};
|
|
138
|
+
return {
|
|
139
|
+
id,
|
|
140
|
+
stepName,
|
|
141
|
+
summary,
|
|
142
|
+
filePath,
|
|
143
|
+
sizeBytes,
|
|
144
|
+
getContent,
|
|
145
|
+
_inMemoryContent
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Initializes output directory for workflow execution
|
|
150
|
+
*/
|
|
151
|
+
async initializeOutputDirectory(workflowName, workflowId, query) {
|
|
152
|
+
const outputDir = path.join(process.cwd(), 'workflow-output', workflowName, workflowId);
|
|
153
|
+
try {
|
|
154
|
+
await fsPromises.mkdir(outputDir, { recursive: true });
|
|
155
|
+
// Create initial manifest
|
|
156
|
+
const manifest = {
|
|
157
|
+
workflowId,
|
|
158
|
+
workflowName,
|
|
159
|
+
startTime: new Date().toISOString(),
|
|
160
|
+
endTime: null,
|
|
161
|
+
status: 'running',
|
|
162
|
+
query,
|
|
163
|
+
steps: []
|
|
164
|
+
};
|
|
165
|
+
await fsPromises.writeFile(path.join(outputDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
166
|
+
console.error(`📁 Workflow output directory: ${outputDir}`);
|
|
167
|
+
return outputDir;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('⚠️ Failed to initialize workflow directory:', error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Updates workflow manifest with step status
|
|
176
|
+
*/
|
|
177
|
+
async updateManifest(outputDir, stepId, status, outputFile, error) {
|
|
178
|
+
const manifestPath = path.join(outputDir, 'manifest.json');
|
|
179
|
+
try {
|
|
180
|
+
const data = await fsPromises.readFile(manifestPath, 'utf-8');
|
|
181
|
+
const manifest = JSON.parse(data);
|
|
182
|
+
// Update or add step
|
|
183
|
+
const existingIdx = manifest.steps.findIndex(s => s.id === stepId);
|
|
184
|
+
const stepData = {
|
|
185
|
+
id: stepId,
|
|
186
|
+
status,
|
|
187
|
+
outputFile,
|
|
188
|
+
error,
|
|
189
|
+
timestamp: new Date().toISOString()
|
|
190
|
+
};
|
|
191
|
+
if (existingIdx >= 0) {
|
|
192
|
+
manifest.steps[existingIdx] = stepData;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
manifest.steps.push(stepData);
|
|
196
|
+
}
|
|
197
|
+
// Update workflow status
|
|
198
|
+
const allCompleted = manifest.steps.every(s => s.status === 'completed');
|
|
199
|
+
const anyFailed = manifest.steps.some(s => s.status === 'failed');
|
|
200
|
+
if (anyFailed) {
|
|
201
|
+
manifest.status = 'failed';
|
|
202
|
+
manifest.endTime = new Date().toISOString();
|
|
203
|
+
}
|
|
204
|
+
else if (allCompleted) {
|
|
205
|
+
manifest.status = 'completed';
|
|
206
|
+
manifest.endTime = new Date().toISOString();
|
|
207
|
+
}
|
|
208
|
+
await fsPromises.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
console.error('Failed to update manifest:', error);
|
|
212
|
+
// Don't throw - manifest updates are not critical
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Unicode-safe truncation helper
|
|
217
|
+
*/
|
|
218
|
+
truncateString(str, maxLength) {
|
|
219
|
+
if (str.length <= maxLength) {
|
|
220
|
+
return str;
|
|
221
|
+
}
|
|
222
|
+
// Use Array.from() for Unicode safety (handles emoji, multi-byte chars)
|
|
223
|
+
const chars = Array.from(str);
|
|
224
|
+
if (chars.length <= maxLength) {
|
|
225
|
+
return str;
|
|
226
|
+
}
|
|
227
|
+
return chars.slice(0, maxLength).join('') + '...';
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Circular reference replacer for JSON.stringify
|
|
231
|
+
*/
|
|
232
|
+
getCircularReplacer() {
|
|
233
|
+
const seen = new WeakSet();
|
|
234
|
+
return (key, value) => {
|
|
235
|
+
if (typeof value === 'object' && value !== null) {
|
|
236
|
+
if (seen.has(value)) {
|
|
237
|
+
return '[Circular]';
|
|
238
|
+
}
|
|
239
|
+
seen.add(value);
|
|
240
|
+
}
|
|
241
|
+
return value;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Helper Utilities
|
|
3
|
+
* Small helper functions for workflow processing
|
|
4
|
+
*/
|
|
5
|
+
export class WorkflowHelpers {
|
|
6
|
+
/**
|
|
7
|
+
* Group workflow steps by dependencies for parallel execution
|
|
8
|
+
* Steps with no dependencies run first, then steps depending on them, etc.
|
|
9
|
+
*/
|
|
10
|
+
static groupStepsByDependencies(steps) {
|
|
11
|
+
const groups = [];
|
|
12
|
+
const processed = new Set();
|
|
13
|
+
while (processed.size < steps.length) {
|
|
14
|
+
const group = [];
|
|
15
|
+
for (const step of steps) {
|
|
16
|
+
if (processed.has(step.name))
|
|
17
|
+
continue;
|
|
18
|
+
// Check if all dependencies are satisfied
|
|
19
|
+
const ready = !step.dependsOn ||
|
|
20
|
+
step.dependsOn.every(dep => processed.has(dep));
|
|
21
|
+
if (ready) {
|
|
22
|
+
group.push(step);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (group.length === 0) {
|
|
26
|
+
throw new Error('Circular dependency detected in workflow steps');
|
|
27
|
+
}
|
|
28
|
+
groups.push(group);
|
|
29
|
+
group.forEach(step => processed.add(step.name));
|
|
30
|
+
}
|
|
31
|
+
return groups;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Calculate step numbers for display (handles parallel execution)
|
|
35
|
+
* Example: step1=1, step2=2, step3=3a, step4=3b (parallel), step5=4
|
|
36
|
+
*
|
|
37
|
+
* Rules:
|
|
38
|
+
* - Steps are SEQUENTIAL by default (1, 2, 3...)
|
|
39
|
+
* - Steps marked with `parallel: true` share the same number (3a, 3b)
|
|
40
|
+
* - The `parallel` flag means "run in parallel with NEXT step"
|
|
41
|
+
*/
|
|
42
|
+
static calculateStepNumbers(workflow) {
|
|
43
|
+
const stepNumbers = new Map();
|
|
44
|
+
let sequentialNumber = 1;
|
|
45
|
+
let i = 0;
|
|
46
|
+
while (i < workflow.steps.length) {
|
|
47
|
+
const step = workflow.steps[i];
|
|
48
|
+
// Check if this step and subsequent steps form a parallel group
|
|
49
|
+
const parallelGroup = [step];
|
|
50
|
+
let j = i + 1;
|
|
51
|
+
// If current step has parallel flag, collect all subsequent parallel steps
|
|
52
|
+
if (step.parallel === true) {
|
|
53
|
+
while (j < workflow.steps.length && workflow.steps[j - 1].parallel === true) {
|
|
54
|
+
parallelGroup.push(workflow.steps[j]);
|
|
55
|
+
j++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Assign numbers
|
|
59
|
+
console.error(`[WorkflowHelpers] Step ${step.name}: parallelGroup.length = ${parallelGroup.length}`);
|
|
60
|
+
if (parallelGroup.length === 1) {
|
|
61
|
+
// Single sequential step
|
|
62
|
+
stepNumbers.set(step.name, String(sequentialNumber));
|
|
63
|
+
console.error(`[WorkflowHelpers] Assigned sequential: ${step.name} -> ${sequentialNumber}`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Parallel group - use letters (3a, 3b, 3c)
|
|
67
|
+
console.error(`[WorkflowHelpers] Parallel group with ${parallelGroup.length} steps`);
|
|
68
|
+
parallelGroup.forEach((parallelStep, index) => {
|
|
69
|
+
const letter = String.fromCharCode(97 + index); // 97 = 'a'
|
|
70
|
+
stepNumbers.set(parallelStep.name, `${sequentialNumber}${letter}`);
|
|
71
|
+
console.error(`[WorkflowHelpers] Assigned parallel: ${parallelStep.name} -> ${sequentialNumber}${letter}`);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
sequentialNumber++;
|
|
75
|
+
i = j;
|
|
76
|
+
}
|
|
77
|
+
return stepNumbers;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if two dependency sets are identical
|
|
81
|
+
*/
|
|
82
|
+
static haveSameDependencies(deps1, deps2) {
|
|
83
|
+
if (deps1.size !== deps2.size)
|
|
84
|
+
return false;
|
|
85
|
+
for (const dep of deps1) {
|
|
86
|
+
if (!deps2.has(dep))
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Estimate token count for any data type
|
|
93
|
+
* Uses character-based approximation: tokens ≈ characters / 4
|
|
94
|
+
*/
|
|
95
|
+
static estimateTokens(data) {
|
|
96
|
+
const text = typeof data === 'string' ? data : JSON.stringify(data);
|
|
97
|
+
return Math.ceil(text.length / 4);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Evaluate a condition string (basic implementation)
|
|
101
|
+
* Returns true/false based on condition evaluation
|
|
102
|
+
*/
|
|
103
|
+
static evaluateCondition(condition, context) {
|
|
104
|
+
// Simple equality check: "variable == value"
|
|
105
|
+
const match = condition.match(/^(\w+)\s*==\s*(.+)$/);
|
|
106
|
+
if (match) {
|
|
107
|
+
const [, variable, expectedValue] = match;
|
|
108
|
+
const actualValue = context[variable];
|
|
109
|
+
return String(actualValue).trim() === expectedValue.trim().replace(/['"]/g, '');
|
|
110
|
+
}
|
|
111
|
+
// Default: if variable exists and is truthy
|
|
112
|
+
return !!context[condition];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Output Formatter
|
|
3
|
+
* Formats workflow execution results in multiple formats (summary, detailed, JSON)
|
|
4
|
+
*/
|
|
5
|
+
export class WorkflowOutputFormatter {
|
|
6
|
+
/**
|
|
7
|
+
* Formats workflow execution results
|
|
8
|
+
*/
|
|
9
|
+
format(execution, format, truncateSteps, maxStepTokens) {
|
|
10
|
+
// Check if auto-synthesis ran - if so, return ONLY the synthesis
|
|
11
|
+
const synthesisStep = execution.outputs.find(step => step.step === 'auto-synthesis');
|
|
12
|
+
if (synthesisStep) {
|
|
13
|
+
// Auto-synthesis ran - return only the synthesis output to prevent MCP 25k limit
|
|
14
|
+
return synthesisStep.output;
|
|
15
|
+
}
|
|
16
|
+
switch (format) {
|
|
17
|
+
case "json":
|
|
18
|
+
return {
|
|
19
|
+
workflowName: execution.workflowName,
|
|
20
|
+
workflowId: execution.workflowId,
|
|
21
|
+
outputDir: execution.outputDir,
|
|
22
|
+
status: execution.status,
|
|
23
|
+
steps: execution.outputs.map(out => ({
|
|
24
|
+
step: out.step,
|
|
25
|
+
summary: out.output,
|
|
26
|
+
filePath: out.filePath
|
|
27
|
+
}))
|
|
28
|
+
};
|
|
29
|
+
case "detailed":
|
|
30
|
+
return this.formatDetailed(execution);
|
|
31
|
+
case "summary":
|
|
32
|
+
default:
|
|
33
|
+
return this.formatSummary(execution);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Formats detailed output with all step information
|
|
38
|
+
*/
|
|
39
|
+
formatDetailed(execution) {
|
|
40
|
+
const duration = execution.endTime && execution.startTime
|
|
41
|
+
? (execution.endTime.getTime() - execution.startTime.getTime()) / 1000
|
|
42
|
+
: 0;
|
|
43
|
+
let output = `# Workflow: ${execution.workflowName}\n\n`;
|
|
44
|
+
output += `**Duration:** ${duration.toFixed(1)}s\n`;
|
|
45
|
+
output += `**Steps Completed:** ${execution.outputs.length}\n`;
|
|
46
|
+
if (execution.outputDir) {
|
|
47
|
+
output += `**Output Directory:** ${execution.outputDir}\n`;
|
|
48
|
+
}
|
|
49
|
+
output += `\n---\n\n`;
|
|
50
|
+
// Show summaries for each step
|
|
51
|
+
for (let i = 0; i < execution.outputs.length; i++) {
|
|
52
|
+
const step = execution.outputs[i];
|
|
53
|
+
output += `## Step ${i + 1}: ${step.step}\n\n`;
|
|
54
|
+
if (step.input && step.input !== '[cached]') {
|
|
55
|
+
output += `**Input:**\n${step.input}...\n\n`;
|
|
56
|
+
}
|
|
57
|
+
output += `${step.output}\n\n`;
|
|
58
|
+
if (step.filePath) {
|
|
59
|
+
output += `📄 *Full output saved to: ${step.filePath}*\n\n`;
|
|
60
|
+
}
|
|
61
|
+
output += `---\n\n`;
|
|
62
|
+
}
|
|
63
|
+
output += `\n**Workflow Complete** ✓\n\n`;
|
|
64
|
+
output += `**Next Steps:**\nUse Read tool to analyze full outputs from saved files.\n`;
|
|
65
|
+
return output;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Formats summary output with last step and file paths
|
|
69
|
+
*/
|
|
70
|
+
formatSummary(execution) {
|
|
71
|
+
const lastOutput = execution.outputs[execution.outputs.length - 1];
|
|
72
|
+
const savedFiles = execution.outputs
|
|
73
|
+
.filter(out => out.filePath)
|
|
74
|
+
.map(out => ` - ${out.step}: ${out.filePath}`)
|
|
75
|
+
.join('\n');
|
|
76
|
+
let result = lastOutput?.output || "Workflow completed";
|
|
77
|
+
if (savedFiles) {
|
|
78
|
+
result += `\n\n**Files saved:**\n${savedFiles}\n\n`;
|
|
79
|
+
result += `Use Read tool to access full content for detailed analysis.`;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Event Bus
|
|
3
|
+
* Central pub/sub event bus for workflow coordination
|
|
4
|
+
* Extends EventEmitter pattern from MessageQueue
|
|
5
|
+
*/
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
/**
|
|
8
|
+
* Standard workflow event topics
|
|
9
|
+
*/
|
|
10
|
+
export const WorkflowEvents = {
|
|
11
|
+
// Workflow lifecycle
|
|
12
|
+
WORKFLOW_STARTED: 'workflow.started',
|
|
13
|
+
WORKFLOW_VALIDATING: 'workflow.validating',
|
|
14
|
+
WORKFLOW_COMPLETED: 'workflow.completed',
|
|
15
|
+
WORKFLOW_FAILED: 'workflow.failed',
|
|
16
|
+
WORKFLOW_PAUSED: 'workflow.paused',
|
|
17
|
+
WORKFLOW_RESUMED: 'workflow.resumed',
|
|
18
|
+
// Step lifecycle
|
|
19
|
+
STEP_READY: 'workflow.step.ready',
|
|
20
|
+
STEP_STARTED: 'workflow.step.started',
|
|
21
|
+
STEP_COMPLETED: 'workflow.step.completed',
|
|
22
|
+
STEP_FAILED: 'workflow.step.failed',
|
|
23
|
+
STEP_SKIPPED: 'workflow.step.skipped',
|
|
24
|
+
STEP_RETRYING: 'workflow.step.retrying',
|
|
25
|
+
// Tool invocation
|
|
26
|
+
TOOL_BEFORE_INVOKE: 'workflow.tool.before_invoke',
|
|
27
|
+
TOOL_INVOKED: 'workflow.tool.invoked',
|
|
28
|
+
TOOL_SUCCESS: 'workflow.tool.success',
|
|
29
|
+
TOOL_FAILURE: 'workflow.tool.failure',
|
|
30
|
+
// Variable & file operations
|
|
31
|
+
VARIABLE_RESOLVED: 'workflow.variable.resolved',
|
|
32
|
+
FILE_CREATED: 'workflow.file.created',
|
|
33
|
+
FILE_REFERENCE_CREATED: 'workflow.file_reference.created',
|
|
34
|
+
// Synthesis
|
|
35
|
+
SYNTHESIS_STARTED: 'workflow.synthesis.started',
|
|
36
|
+
SYNTHESIS_COMPLETED: 'workflow.synthesis.completed',
|
|
37
|
+
// Session management
|
|
38
|
+
SESSION_CHECKPOINT: 'workflow.session.checkpoint',
|
|
39
|
+
SESSION_RESTORED: 'workflow.session.restored',
|
|
40
|
+
// Error handling
|
|
41
|
+
ERROR_OCCURRED: 'workflow.error.occurred',
|
|
42
|
+
ERROR_RECOVERED: 'workflow.error.recovered'
|
|
43
|
+
};
|
|
44
|
+
export class WorkflowEventBus extends EventEmitter {
|
|
45
|
+
constructor() {
|
|
46
|
+
super();
|
|
47
|
+
// Increase max listeners for complex workflows
|
|
48
|
+
this.setMaxListeners(100);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Singleton instance (optional - can also use new WorkflowEventBus())
|
|
52
|
+
*/
|
|
53
|
+
static getInstance() {
|
|
54
|
+
if (!WorkflowEventBus.instance) {
|
|
55
|
+
WorkflowEventBus.instance = new WorkflowEventBus();
|
|
56
|
+
}
|
|
57
|
+
return WorkflowEventBus.instance;
|
|
58
|
+
}
|
|
59
|
+
subscribe(topic, handler) {
|
|
60
|
+
this.on(topic, handler);
|
|
61
|
+
// Return unsubscribe function
|
|
62
|
+
return () => {
|
|
63
|
+
this.off(topic, handler);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async publish(topic, data) {
|
|
67
|
+
// Log event for debugging (can be disabled in production)
|
|
68
|
+
if (process.env.DEBUG_WORKFLOW_EVENTS === 'true') {
|
|
69
|
+
console.error(`[EventBus] ${topic}:`, JSON.stringify(data, null, 2));
|
|
70
|
+
}
|
|
71
|
+
// Emit to all synchronous listeners
|
|
72
|
+
this.emit(topic, data);
|
|
73
|
+
// Get all listeners for this topic
|
|
74
|
+
const listeners = this.listeners(topic);
|
|
75
|
+
// Execute async handlers
|
|
76
|
+
const asyncHandlers = listeners.filter((listener) => listener.constructor.name === 'AsyncFunction');
|
|
77
|
+
if (asyncHandlers.length > 0) {
|
|
78
|
+
await Promise.all(asyncHandlers.map((handler) => Promise.resolve(handler(data))));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
clear(topic) {
|
|
82
|
+
this.removeAllListeners(topic);
|
|
83
|
+
}
|
|
84
|
+
clearAll() {
|
|
85
|
+
this.removeAllListeners();
|
|
86
|
+
}
|
|
87
|
+
listenerCount(topic) {
|
|
88
|
+
return super.listenerCount(topic);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe to multiple topics with same handler
|
|
92
|
+
*/
|
|
93
|
+
subscribeMultiple(topics, handler) {
|
|
94
|
+
const unsubscribers = topics.map((topic) => this.subscribe(topic, handler));
|
|
95
|
+
return () => {
|
|
96
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Wait for a specific event to occur (Promise-based)
|
|
101
|
+
*/
|
|
102
|
+
async waitFor(topic, timeout = 30000) {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
this.off(topic, handler);
|
|
106
|
+
reject(new Error(`Timeout waiting for event: ${topic}`));
|
|
107
|
+
}, timeout);
|
|
108
|
+
const handler = (data) => {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
resolve(data);
|
|
111
|
+
};
|
|
112
|
+
this.once(topic, handler);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Publish with error handling
|
|
117
|
+
*/
|
|
118
|
+
async publishSafe(topic, data) {
|
|
119
|
+
try {
|
|
120
|
+
await this.publish(topic, data);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error(`[EventBus] Error publishing to ${topic}:`, error);
|
|
124
|
+
// Emit error event for monitoring
|
|
125
|
+
this.emit(WorkflowEvents.ERROR_OCCURRED, {
|
|
126
|
+
topic,
|
|
127
|
+
error,
|
|
128
|
+
data
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|