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,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Recovery Handler
|
|
3
|
+
* Implements circuit breaker pattern for failing steps
|
|
4
|
+
* Tracks failure rates and applies recovery strategies
|
|
5
|
+
*/
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import { WorkflowEvents } from '../events/WorkflowEventBus.js';
|
|
8
|
+
var CircuitState;
|
|
9
|
+
(function (CircuitState) {
|
|
10
|
+
CircuitState["CLOSED"] = "CLOSED";
|
|
11
|
+
CircuitState["OPEN"] = "OPEN";
|
|
12
|
+
CircuitState["HALF_OPEN"] = "HALF_OPEN"; // Testing if service recovered
|
|
13
|
+
})(CircuitState || (CircuitState = {}));
|
|
14
|
+
export class ErrorRecoveryHandler extends EventEmitter {
|
|
15
|
+
constructor(eventBus) {
|
|
16
|
+
super();
|
|
17
|
+
this.failureRecords = new Map();
|
|
18
|
+
this.circuitStates = new Map();
|
|
19
|
+
// Circuit breaker thresholds
|
|
20
|
+
this.failureThreshold = 3; // Open circuit after 3 failures
|
|
21
|
+
this.recoveryTimeout = 30000; // 30 seconds
|
|
22
|
+
this.halfOpenAttempts = 1; // Allow 1 attempt in half-open state
|
|
23
|
+
this.eventBus = eventBus;
|
|
24
|
+
// Subscribe to failure events
|
|
25
|
+
this.eventBus.subscribe(WorkflowEvents.STEP_FAILED, this.handleStepFailure.bind(this));
|
|
26
|
+
this.eventBus.subscribe(WorkflowEvents.STEP_COMPLETED, this.handleStepSuccess.bind(this));
|
|
27
|
+
this.eventBus.subscribe(WorkflowEvents.TOOL_FAILURE, this.handleToolFailure.bind(this));
|
|
28
|
+
}
|
|
29
|
+
async handleStepFailure(event) {
|
|
30
|
+
const { stepName, error } = event;
|
|
31
|
+
// Update failure record
|
|
32
|
+
const record = this.failureRecords.get(stepName) || {
|
|
33
|
+
stepName,
|
|
34
|
+
failures: 0,
|
|
35
|
+
lastFailureTime: 0,
|
|
36
|
+
consecutiveFailures: 0
|
|
37
|
+
};
|
|
38
|
+
record.failures++;
|
|
39
|
+
record.consecutiveFailures++;
|
|
40
|
+
record.lastFailureTime = Date.now();
|
|
41
|
+
this.failureRecords.set(stepName, record);
|
|
42
|
+
console.error(`[ErrorRecovery] Step ${stepName} failed (${record.consecutiveFailures} consecutive failures)`);
|
|
43
|
+
// Check if circuit should open
|
|
44
|
+
if (record.consecutiveFailures >= this.failureThreshold) {
|
|
45
|
+
await this.openCircuit(stepName);
|
|
46
|
+
}
|
|
47
|
+
// Emit recovery suggestions
|
|
48
|
+
const suggestions = this.getRecoverySuggestions(stepName, error);
|
|
49
|
+
await this.eventBus.publish('workflow.error.recovery_suggestions', {
|
|
50
|
+
stepName,
|
|
51
|
+
suggestions
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async handleStepSuccess(event) {
|
|
55
|
+
const { stepName } = event;
|
|
56
|
+
// Reset consecutive failures
|
|
57
|
+
const record = this.failureRecords.get(stepName);
|
|
58
|
+
if (record) {
|
|
59
|
+
record.consecutiveFailures = 0;
|
|
60
|
+
this.failureRecords.set(stepName, record);
|
|
61
|
+
}
|
|
62
|
+
// Close circuit if it was open
|
|
63
|
+
const state = this.circuitStates.get(stepName);
|
|
64
|
+
if (state === CircuitState.HALF_OPEN) {
|
|
65
|
+
await this.closeCircuit(stepName);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async handleToolFailure(event) {
|
|
69
|
+
const { stepName, tool, error } = event;
|
|
70
|
+
console.error(`[ErrorRecovery] Tool ${tool} failed for step ${stepName}`);
|
|
71
|
+
// Check if we should attempt recovery
|
|
72
|
+
const shouldRecover = await this.shouldAttemptRecovery(stepName);
|
|
73
|
+
if (shouldRecover) {
|
|
74
|
+
await this.eventBus.publish(WorkflowEvents.ERROR_RECOVERED, {
|
|
75
|
+
stepName,
|
|
76
|
+
tool,
|
|
77
|
+
recoveryAction: 'retry'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async openCircuit(stepName) {
|
|
82
|
+
this.circuitStates.set(stepName, CircuitState.OPEN);
|
|
83
|
+
console.error(`[ErrorRecovery] Circuit OPEN for step ${stepName} - rejecting requests`);
|
|
84
|
+
this.emit('circuit:opened', { stepName });
|
|
85
|
+
// Schedule half-open transition
|
|
86
|
+
setTimeout(() => {
|
|
87
|
+
this.transitionToHalfOpen(stepName);
|
|
88
|
+
}, this.recoveryTimeout);
|
|
89
|
+
}
|
|
90
|
+
async closeCircuit(stepName) {
|
|
91
|
+
this.circuitStates.set(stepName, CircuitState.CLOSED);
|
|
92
|
+
console.error(`[ErrorRecovery] Circuit CLOSED for step ${stepName} - normal operation`);
|
|
93
|
+
this.emit('circuit:closed', { stepName });
|
|
94
|
+
}
|
|
95
|
+
transitionToHalfOpen(stepName) {
|
|
96
|
+
const state = this.circuitStates.get(stepName);
|
|
97
|
+
if (state === CircuitState.OPEN) {
|
|
98
|
+
this.circuitStates.set(stepName, CircuitState.HALF_OPEN);
|
|
99
|
+
console.error(`[ErrorRecovery] Circuit HALF_OPEN for step ${stepName} - testing recovery`);
|
|
100
|
+
this.emit('circuit:half_open', { stepName });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async shouldAttemptRecovery(stepName) {
|
|
104
|
+
const state = this.circuitStates.get(stepName) || CircuitState.CLOSED;
|
|
105
|
+
// Don't attempt recovery if circuit is open
|
|
106
|
+
if (state === CircuitState.OPEN) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
// In half-open state, allow limited attempts
|
|
110
|
+
if (state === CircuitState.HALF_OPEN) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
// In closed state, always attempt recovery
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
getRecoverySuggestions(stepName, error) {
|
|
117
|
+
const suggestions = [];
|
|
118
|
+
const errorMessage = error.message.toLowerCase();
|
|
119
|
+
// Rate limiting
|
|
120
|
+
if (errorMessage.includes('rate limit') || errorMessage.includes('429')) {
|
|
121
|
+
suggestions.push('Wait for rate limit to reset');
|
|
122
|
+
suggestions.push('Consider using a different model or provider');
|
|
123
|
+
}
|
|
124
|
+
// Timeout errors
|
|
125
|
+
if (errorMessage.includes('timeout') || errorMessage.includes('timed out')) {
|
|
126
|
+
suggestions.push('Increase maxTokens or timeout settings');
|
|
127
|
+
suggestions.push('Simplify the prompt or split into smaller steps');
|
|
128
|
+
}
|
|
129
|
+
// Model errors
|
|
130
|
+
if (errorMessage.includes('model') || errorMessage.includes('404')) {
|
|
131
|
+
suggestions.push('Check if model name is correct');
|
|
132
|
+
suggestions.push('Try a different model or fallback option');
|
|
133
|
+
}
|
|
134
|
+
// Generic suggestions
|
|
135
|
+
if (suggestions.length === 0) {
|
|
136
|
+
suggestions.push('Review step configuration');
|
|
137
|
+
suggestions.push('Check input data validity');
|
|
138
|
+
suggestions.push('Enable retry with backoff');
|
|
139
|
+
}
|
|
140
|
+
return suggestions;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get circuit state for a step
|
|
144
|
+
*/
|
|
145
|
+
getCircuitState(stepName) {
|
|
146
|
+
return this.circuitStates.get(stepName) || CircuitState.CLOSED;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get failure statistics for a step
|
|
150
|
+
*/
|
|
151
|
+
getFailureStats(stepName) {
|
|
152
|
+
return this.failureRecords.get(stepName) || null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Reset circuit breaker for a step
|
|
156
|
+
*/
|
|
157
|
+
resetCircuit(stepName) {
|
|
158
|
+
this.circuitStates.delete(stepName);
|
|
159
|
+
this.failureRecords.delete(stepName);
|
|
160
|
+
console.error(`[ErrorRecovery] Circuit reset for step ${stepName}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Enhancement Handler
|
|
3
|
+
* Wraps PromptEngineer to enhance step prompts before tool invocation
|
|
4
|
+
* Subscribes to workflow.tool.before_invoke events
|
|
5
|
+
*/
|
|
6
|
+
import { WorkflowEvents } from '../events/WorkflowEventBus.js';
|
|
7
|
+
import { PromptEngineer } from '../../../prompt-engineer.js';
|
|
8
|
+
export class PromptEnhancementHandler {
|
|
9
|
+
constructor(eventBus) {
|
|
10
|
+
this.enhancedSteps = new Set(); // Track enhanced steps to prevent double-enhancement
|
|
11
|
+
this.eventBus = eventBus;
|
|
12
|
+
this.promptEngineer = new PromptEngineer();
|
|
13
|
+
// Subscribe to before_invoke events
|
|
14
|
+
this.eventBus.subscribe(WorkflowEvents.TOOL_BEFORE_INVOKE, this.handleBeforeInvoke.bind(this));
|
|
15
|
+
}
|
|
16
|
+
async handleBeforeInvoke(event) {
|
|
17
|
+
const { tool, input, context } = event;
|
|
18
|
+
const { step } = context;
|
|
19
|
+
// Check if already enhanced (prevent double-enhancement)
|
|
20
|
+
if (this.enhancedSteps.has(step.name)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Check if step has promptTechnique specified
|
|
24
|
+
const technique = step.promptTechnique;
|
|
25
|
+
if (!technique) {
|
|
26
|
+
// No enhancement needed
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Mark as enhanced
|
|
30
|
+
this.enhancedSteps.add(step.name);
|
|
31
|
+
try {
|
|
32
|
+
// Extract the query/prompt from input
|
|
33
|
+
const query = this.extractQuery(input);
|
|
34
|
+
if (!query) {
|
|
35
|
+
console.error(`[PromptEnhancement] No query found in input for step ${step.name}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Get accumulated results for context (legacy)
|
|
39
|
+
const previousResults = event.context.accumulatedResults || [];
|
|
40
|
+
// Build EnhancementContext (NEW - Phase 3)
|
|
41
|
+
const enhancementContext = {
|
|
42
|
+
stepNumber: context.stepIndex !== undefined ? context.stepIndex + 1 : undefined,
|
|
43
|
+
totalSteps: context.totalSteps,
|
|
44
|
+
workflowName: context.workflowName,
|
|
45
|
+
previousSteps: previousResults.map((result, idx) => ({
|
|
46
|
+
name: result.tool || `step-${idx}`, // ToolResult has 'tool' not 'step'
|
|
47
|
+
output: result.output,
|
|
48
|
+
technique: undefined // Not tracked yet
|
|
49
|
+
})),
|
|
50
|
+
workflowVariables: context.variables,
|
|
51
|
+
targetModel: tool
|
|
52
|
+
};
|
|
53
|
+
// Apply prompt engineering technique with enhanced context
|
|
54
|
+
const enhancedPrompt = this.promptEngineer.applyTechnique(tool, technique, query, previousResults, // Legacy - still pass for backwards compat
|
|
55
|
+
enhancementContext // NEW - rich context
|
|
56
|
+
);
|
|
57
|
+
// Update the input with enhanced prompt
|
|
58
|
+
this.updateInputWithEnhancedPrompt(input, enhancedPrompt);
|
|
59
|
+
// Log enhancement
|
|
60
|
+
const description = this.promptEngineer.getTechniqueDescription(technique);
|
|
61
|
+
console.error(`[PromptEnhancement] Applied "${description}" to ${step.name} (${tool})`);
|
|
62
|
+
// Publish enhancement event
|
|
63
|
+
await this.eventBus.publish('workflow.prompt.enhanced', {
|
|
64
|
+
stepName: step.name,
|
|
65
|
+
technique,
|
|
66
|
+
originalQuery: query.substring(0, 100),
|
|
67
|
+
enhancedLength: enhancedPrompt.length
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(`[PromptEnhancement] Error enhancing prompt for ${step.name}:`, error);
|
|
72
|
+
// Non-fatal - continue with original prompt
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Extract query/prompt from various input formats
|
|
77
|
+
*/
|
|
78
|
+
extractQuery(input) {
|
|
79
|
+
if (typeof input === 'string') {
|
|
80
|
+
return input;
|
|
81
|
+
}
|
|
82
|
+
// Try common field names
|
|
83
|
+
const record = input;
|
|
84
|
+
const query = record.query ||
|
|
85
|
+
record.prompt ||
|
|
86
|
+
record.thought ||
|
|
87
|
+
record.problem ||
|
|
88
|
+
record.topic ||
|
|
89
|
+
record.content;
|
|
90
|
+
return typeof query === 'string' ? query : null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Update input object with enhanced prompt
|
|
94
|
+
*/
|
|
95
|
+
updateInputWithEnhancedPrompt(input, enhancedPrompt) {
|
|
96
|
+
if (typeof input === 'string') {
|
|
97
|
+
// Can't modify string input - this shouldn't happen as we'd extract query first
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const record = input;
|
|
101
|
+
// Update the field that contained the query
|
|
102
|
+
if (record.query !== undefined)
|
|
103
|
+
record.query = enhancedPrompt;
|
|
104
|
+
else if (record.prompt !== undefined)
|
|
105
|
+
record.prompt = enhancedPrompt;
|
|
106
|
+
else if (record.thought !== undefined)
|
|
107
|
+
record.thought = enhancedPrompt;
|
|
108
|
+
else if (record.problem !== undefined)
|
|
109
|
+
record.problem = enhancedPrompt;
|
|
110
|
+
else if (record.topic !== undefined)
|
|
111
|
+
record.topic = enhancedPrompt;
|
|
112
|
+
else if (record.content !== undefined)
|
|
113
|
+
record.content = enhancedPrompt;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Persistence Handler
|
|
3
|
+
* Handles workflow session checkpointing and restoration
|
|
4
|
+
* Subscribes to state change events for automatic persistence
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { WorkflowEvents } from '../events/WorkflowEventBus.js';
|
|
9
|
+
export class SessionPersistenceHandler {
|
|
10
|
+
constructor(eventBus, options = {}) {
|
|
11
|
+
this.eventBus = eventBus;
|
|
12
|
+
this.checkpointDir = options.checkpointDir || './.workflow-checkpoints';
|
|
13
|
+
this.autoSaveEnabled = options.autoSave !== false; // Default: true
|
|
14
|
+
// Ensure checkpoint directory exists
|
|
15
|
+
this.ensureCheckpointDir();
|
|
16
|
+
// Subscribe to events for auto-save
|
|
17
|
+
if (this.autoSaveEnabled) {
|
|
18
|
+
this.setupAutoSave();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
ensureCheckpointDir() {
|
|
22
|
+
if (!fs.existsSync(this.checkpointDir)) {
|
|
23
|
+
fs.mkdirSync(this.checkpointDir, { recursive: true });
|
|
24
|
+
console.error(`[SessionPersistence] Created checkpoint directory: ${this.checkpointDir}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
setupAutoSave() {
|
|
28
|
+
// Save checkpoint after each step completes
|
|
29
|
+
this.eventBus.subscribe(WorkflowEvents.STEP_COMPLETED, this.handleStepCompleted.bind(this));
|
|
30
|
+
// Save checkpoint when workflow completes
|
|
31
|
+
this.eventBus.subscribe(WorkflowEvents.WORKFLOW_COMPLETED, this.handleWorkflowCompleted.bind(this));
|
|
32
|
+
// Save checkpoint before failure
|
|
33
|
+
this.eventBus.subscribe(WorkflowEvents.WORKFLOW_FAILED, this.handleWorkflowFailed.bind(this));
|
|
34
|
+
}
|
|
35
|
+
async handleStepCompleted(event) {
|
|
36
|
+
// Auto-save checkpoint after each step
|
|
37
|
+
console.error(`[SessionPersistence] Auto-saving checkpoint after step: ${event.stepName}`);
|
|
38
|
+
// Publish checkpoint event
|
|
39
|
+
await this.eventBus.publish(WorkflowEvents.SESSION_CHECKPOINT, {
|
|
40
|
+
stepName: event.stepName,
|
|
41
|
+
reason: 'step_completed'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async handleWorkflowCompleted(event) {
|
|
45
|
+
console.error(`[SessionPersistence] Workflow ${event.workflowName} completed - saving final checkpoint`);
|
|
46
|
+
await this.eventBus.publish(WorkflowEvents.SESSION_CHECKPOINT, {
|
|
47
|
+
workflowId: event.workflowId,
|
|
48
|
+
reason: 'workflow_completed'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
async handleWorkflowFailed(event) {
|
|
52
|
+
console.error(`[SessionPersistence] Workflow ${event.workflowName} failed - saving error checkpoint`);
|
|
53
|
+
await this.eventBus.publish(WorkflowEvents.SESSION_CHECKPOINT, {
|
|
54
|
+
workflowId: event.workflowId,
|
|
55
|
+
reason: 'workflow_failed',
|
|
56
|
+
error: event.error.message
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Save workflow checkpoint to disk
|
|
61
|
+
*/
|
|
62
|
+
async saveCheckpoint(checkpoint) {
|
|
63
|
+
const filename = this.getCheckpointFilename(checkpoint.workflowId);
|
|
64
|
+
const filepath = path.join(this.checkpointDir, filename);
|
|
65
|
+
try {
|
|
66
|
+
const data = JSON.stringify(checkpoint, null, 2);
|
|
67
|
+
await fs.promises.writeFile(filepath, data, 'utf-8');
|
|
68
|
+
console.error(`[SessionPersistence] Checkpoint saved: ${filepath}`);
|
|
69
|
+
return filepath;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error(`[SessionPersistence] Error saving checkpoint:`, error);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Load workflow checkpoint from disk
|
|
78
|
+
*/
|
|
79
|
+
async loadCheckpoint(workflowId) {
|
|
80
|
+
const filename = this.getCheckpointFilename(workflowId);
|
|
81
|
+
const filepath = path.join(this.checkpointDir, filename);
|
|
82
|
+
try {
|
|
83
|
+
if (!fs.existsSync(filepath)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const data = await fs.promises.readFile(filepath, 'utf-8');
|
|
87
|
+
const checkpoint = JSON.parse(data);
|
|
88
|
+
console.error(`[SessionPersistence] Checkpoint loaded: ${filepath}`);
|
|
89
|
+
// Publish restore event
|
|
90
|
+
await this.eventBus.publish(WorkflowEvents.SESSION_RESTORED, {
|
|
91
|
+
workflowId: checkpoint.workflowId,
|
|
92
|
+
workflowName: checkpoint.workflowName,
|
|
93
|
+
state: checkpoint.state
|
|
94
|
+
});
|
|
95
|
+
return checkpoint;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error(`[SessionPersistence] Error loading checkpoint:`, error);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* List all checkpoints for a workflow
|
|
104
|
+
*/
|
|
105
|
+
listCheckpoints(workflowName) {
|
|
106
|
+
try {
|
|
107
|
+
const files = fs.readdirSync(this.checkpointDir);
|
|
108
|
+
if (workflowName) {
|
|
109
|
+
return files.filter((f) => f.includes(workflowName) && f.endsWith('.json'));
|
|
110
|
+
}
|
|
111
|
+
return files.filter((f) => f.endsWith('.json'));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error(`[SessionPersistence] Error listing checkpoints:`, error);
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Delete checkpoint file
|
|
120
|
+
*/
|
|
121
|
+
async deleteCheckpoint(workflowId) {
|
|
122
|
+
const filename = this.getCheckpointFilename(workflowId);
|
|
123
|
+
const filepath = path.join(this.checkpointDir, filename);
|
|
124
|
+
try {
|
|
125
|
+
if (fs.existsSync(filepath)) {
|
|
126
|
+
await fs.promises.unlink(filepath);
|
|
127
|
+
console.error(`[SessionPersistence] Checkpoint deleted: ${filepath}`);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error(`[SessionPersistence] Error deleting checkpoint:`, error);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get checkpoint filename for workflow ID
|
|
139
|
+
*/
|
|
140
|
+
getCheckpointFilename(workflowId) {
|
|
141
|
+
return `checkpoint-${workflowId}.json`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Clean up old checkpoints (older than specified days)
|
|
145
|
+
*/
|
|
146
|
+
async cleanupOldCheckpoints(daysOld = 7) {
|
|
147
|
+
const cutoffTime = Date.now() - daysOld * 24 * 60 * 60 * 1000;
|
|
148
|
+
let deletedCount = 0;
|
|
149
|
+
try {
|
|
150
|
+
const files = this.listCheckpoints();
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
const filepath = path.join(this.checkpointDir, file);
|
|
153
|
+
const stats = fs.statSync(filepath);
|
|
154
|
+
if (stats.mtimeMs < cutoffTime) {
|
|
155
|
+
await fs.promises.unlink(filepath);
|
|
156
|
+
deletedCount++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
console.error(`[SessionPersistence] Cleaned up ${deletedCount} old checkpoints (>${daysOld} days)`);
|
|
160
|
+
return deletedCount;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error(`[SessionPersistence] Error cleaning up checkpoints:`, error);
|
|
164
|
+
return deletedCount;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step Execution Handler
|
|
3
|
+
* Subscribes to step.ready events and executes workflow steps
|
|
4
|
+
*
|
|
5
|
+
* Phase 1: Event infrastructure and state management
|
|
6
|
+
* Phase 2: Will integrate with actual tool execution
|
|
7
|
+
*/
|
|
8
|
+
import { WorkflowEvents } from '../events/WorkflowEventBus.js';
|
|
9
|
+
import { WorkflowState } from '../state/interfaces/IStateMachine.js';
|
|
10
|
+
export class StepExecutionHandler {
|
|
11
|
+
constructor(eventBus, stateMachine) {
|
|
12
|
+
this.activeExecutions = new Map();
|
|
13
|
+
this.eventBus = eventBus;
|
|
14
|
+
this.stateMachine = stateMachine;
|
|
15
|
+
// Subscribe to step ready events
|
|
16
|
+
this.eventBus.subscribe(WorkflowEvents.STEP_READY, this.handleStepReady.bind(this));
|
|
17
|
+
}
|
|
18
|
+
async handleStepReady(context) {
|
|
19
|
+
const { step, workflowId, stepIndex, totalSteps } = context;
|
|
20
|
+
try {
|
|
21
|
+
// Transition to executing state
|
|
22
|
+
this.stateMachine.transition(WorkflowState.STEP_EXECUTING, {
|
|
23
|
+
workflowId,
|
|
24
|
+
workflowName: context.workflowName,
|
|
25
|
+
currentStep: step.name,
|
|
26
|
+
stepIndex,
|
|
27
|
+
totalSteps
|
|
28
|
+
});
|
|
29
|
+
// Check if step should be skipped
|
|
30
|
+
if (await this.shouldSkipStep(step, context)) {
|
|
31
|
+
await this.eventBus.publish(WorkflowEvents.STEP_SKIPPED, {
|
|
32
|
+
stepName: step.name,
|
|
33
|
+
reason: 'condition not met'
|
|
34
|
+
});
|
|
35
|
+
// Transition back to running
|
|
36
|
+
this.stateMachine.transition(WorkflowState.RUNNING, {
|
|
37
|
+
workflowId,
|
|
38
|
+
workflowName: context.workflowName
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Execute step with retry logic
|
|
43
|
+
const result = await this.executeStepWithRetry(context);
|
|
44
|
+
// Publish completion event
|
|
45
|
+
await this.eventBus.publish(WorkflowEvents.STEP_COMPLETED, result);
|
|
46
|
+
// Transition back to running
|
|
47
|
+
this.stateMachine.transition(WorkflowState.RUNNING, {
|
|
48
|
+
workflowId,
|
|
49
|
+
workflowName: context.workflowName
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Handle step failure
|
|
54
|
+
await this.handleStepFailure(context, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async executeStepWithRetry(context) {
|
|
58
|
+
const { step, workflowId } = context;
|
|
59
|
+
const maxAttempts = step.retry?.attempts || 1;
|
|
60
|
+
const backoff = step.retry?.backoff || 1000;
|
|
61
|
+
let lastError;
|
|
62
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
63
|
+
try {
|
|
64
|
+
if (attempt > 1) {
|
|
65
|
+
// Transition to retrying state
|
|
66
|
+
this.stateMachine.transition(WorkflowState.RETRYING, {
|
|
67
|
+
workflowId,
|
|
68
|
+
workflowName: context.workflowName,
|
|
69
|
+
currentStep: step.name,
|
|
70
|
+
metadata: {
|
|
71
|
+
retryAttempt: attempt,
|
|
72
|
+
maxRetries: maxAttempts
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// Wait with exponential backoff
|
|
76
|
+
await this.delay(backoff * Math.pow(2, attempt - 2));
|
|
77
|
+
}
|
|
78
|
+
// Execute the step
|
|
79
|
+
const result = await this.executeStep(context);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
lastError = error;
|
|
84
|
+
console.error(`[StepExecutionHandler] Attempt ${attempt}/${maxAttempts} failed:`, error);
|
|
85
|
+
// If this was the last attempt, throw
|
|
86
|
+
if (attempt === maxAttempts) {
|
|
87
|
+
throw lastError;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Should never reach here, but TypeScript requires it
|
|
92
|
+
throw lastError || new Error('Step execution failed');
|
|
93
|
+
}
|
|
94
|
+
async executeStep(context) {
|
|
95
|
+
const { step } = context;
|
|
96
|
+
const startTime = Date.now();
|
|
97
|
+
// Create abort controller for this execution
|
|
98
|
+
const abortController = new AbortController();
|
|
99
|
+
this.activeExecutions.set(step.name, abortController);
|
|
100
|
+
try {
|
|
101
|
+
// Publish before invoke event (for PromptEnhancementHandler)
|
|
102
|
+
await this.eventBus.publish(WorkflowEvents.TOOL_BEFORE_INVOKE, {
|
|
103
|
+
stepName: step.name,
|
|
104
|
+
tool: step.tool,
|
|
105
|
+
input: step.input,
|
|
106
|
+
context
|
|
107
|
+
});
|
|
108
|
+
// TODO Phase 2: Integrate with actual tool execution via tool-mapper.ts
|
|
109
|
+
// For now, just simulate execution
|
|
110
|
+
const mockResult = {
|
|
111
|
+
output: `[Phase 1] Simulated execution of ${step.tool}`,
|
|
112
|
+
tokensUsed: 0,
|
|
113
|
+
cost: 0
|
|
114
|
+
};
|
|
115
|
+
// Publish tool success event
|
|
116
|
+
await this.eventBus.publish(WorkflowEvents.TOOL_SUCCESS, {
|
|
117
|
+
stepName: step.name,
|
|
118
|
+
tool: step.tool,
|
|
119
|
+
result: mockResult
|
|
120
|
+
});
|
|
121
|
+
const duration = Date.now() - startTime;
|
|
122
|
+
return {
|
|
123
|
+
stepName: step.name,
|
|
124
|
+
success: true,
|
|
125
|
+
output: mockResult.output,
|
|
126
|
+
duration,
|
|
127
|
+
tokensUsed: mockResult.tokensUsed,
|
|
128
|
+
cost: mockResult.cost
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const duration = Date.now() - startTime;
|
|
133
|
+
// Publish tool failure event
|
|
134
|
+
await this.eventBus.publish(WorkflowEvents.TOOL_FAILURE, {
|
|
135
|
+
stepName: step.name,
|
|
136
|
+
tool: step.tool,
|
|
137
|
+
error
|
|
138
|
+
});
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
this.activeExecutions.delete(step.name);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async shouldSkipStep(step, context) {
|
|
146
|
+
// Check condition.skip flag
|
|
147
|
+
if (step.condition?.skip) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
// Check condition.if expression
|
|
151
|
+
if (step.condition?.if) {
|
|
152
|
+
// Simple condition evaluation (can be enhanced)
|
|
153
|
+
const conditionMet = this.evaluateCondition(step.condition.if, context.variables);
|
|
154
|
+
return !conditionMet;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
evaluateCondition(condition, variables) {
|
|
159
|
+
// Simple equality check: "variable == value"
|
|
160
|
+
const match = condition.match(/^(\w+)\s*==\s*(.+)$/);
|
|
161
|
+
if (match) {
|
|
162
|
+
const [, variable, expectedValue] = match;
|
|
163
|
+
const actualValue = variables[variable];
|
|
164
|
+
return String(actualValue).trim() === expectedValue.trim().replace(/['"]/, '');
|
|
165
|
+
}
|
|
166
|
+
// Default: check if variable exists and is truthy
|
|
167
|
+
return !!variables[condition];
|
|
168
|
+
}
|
|
169
|
+
async handleStepFailure(context, error) {
|
|
170
|
+
const { step, workflowId } = context;
|
|
171
|
+
// Check if step allows failure
|
|
172
|
+
const failOnError = step.condition?.failOnError !== false;
|
|
173
|
+
if (failOnError) {
|
|
174
|
+
// Publish step failed event
|
|
175
|
+
await this.eventBus.publish(WorkflowEvents.STEP_FAILED, {
|
|
176
|
+
stepName: step.name,
|
|
177
|
+
error,
|
|
178
|
+
fatal: true
|
|
179
|
+
});
|
|
180
|
+
// Transition to failed state
|
|
181
|
+
this.stateMachine.transition(WorkflowState.FAILED, {
|
|
182
|
+
workflowId,
|
|
183
|
+
workflowName: context.workflowName,
|
|
184
|
+
currentStep: step.name,
|
|
185
|
+
error
|
|
186
|
+
});
|
|
187
|
+
// Publish workflow failed event
|
|
188
|
+
await this.eventBus.publish(WorkflowEvents.WORKFLOW_FAILED, {
|
|
189
|
+
workflowId,
|
|
190
|
+
workflowName: context.workflowName,
|
|
191
|
+
error,
|
|
192
|
+
failedAt: step.name
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Non-fatal failure - continue workflow
|
|
197
|
+
await this.eventBus.publish(WorkflowEvents.STEP_FAILED, {
|
|
198
|
+
stepName: step.name,
|
|
199
|
+
error,
|
|
200
|
+
fatal: false
|
|
201
|
+
});
|
|
202
|
+
// Transition back to running
|
|
203
|
+
this.stateMachine.transition(WorkflowState.RUNNING, {
|
|
204
|
+
workflowId,
|
|
205
|
+
workflowName: context.workflowName
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
delay(ms) {
|
|
210
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Abort a specific step execution
|
|
214
|
+
*/
|
|
215
|
+
abortStep(stepName) {
|
|
216
|
+
const controller = this.activeExecutions.get(stepName);
|
|
217
|
+
if (controller) {
|
|
218
|
+
controller.abort();
|
|
219
|
+
this.activeExecutions.delete(stepName);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Abort all active step executions
|
|
224
|
+
*/
|
|
225
|
+
abortAll() {
|
|
226
|
+
for (const controller of this.activeExecutions.values()) {
|
|
227
|
+
controller.abort();
|
|
228
|
+
}
|
|
229
|
+
this.activeExecutions.clear();
|
|
230
|
+
}
|
|
231
|
+
}
|