@phuetz/code-buddy 0.1.1 → 0.1.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/dist/agent/codebuddy-agent.js +1 -0
- package/dist/agent/codebuddy-agent.js.map +1 -1
- package/dist/agent/execution/agent-executor.js +5 -7
- package/dist/agent/execution/agent-executor.js.map +1 -1
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +3 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/isolation/agent-workspace.d.ts +1 -0
- package/dist/agent/isolation/agent-workspace.js +10 -0
- package/dist/agent/isolation/agent-workspace.js.map +1 -1
- package/dist/agent/specialized/index.d.ts +9 -8
- package/dist/agent/specialized/index.js +16 -8
- package/dist/agent/specialized/index.js.map +1 -1
- package/dist/browser/controller.js +8 -4
- package/dist/browser/controller.js.map +1 -1
- package/dist/browser-automation/browser-manager.js +8 -1
- package/dist/browser-automation/browser-manager.js.map +1 -1
- package/dist/codebuddy/client.js +70 -11
- package/dist/codebuddy/client.js.map +1 -1
- package/dist/codebuddy/tools.d.ts +1 -7
- package/dist/codebuddy/tools.js +2 -30
- package/dist/codebuddy/tools.js.map +1 -1
- package/dist/commands/cli/daemon-commands.d.ts +14 -0
- package/dist/commands/cli/daemon-commands.js +166 -0
- package/dist/commands/cli/daemon-commands.js.map +1 -0
- package/dist/commands/cli/speak-command.d.ts +10 -0
- package/dist/commands/cli/speak-command.js +97 -0
- package/dist/commands/cli/speak-command.js.map +1 -0
- package/dist/commands/cli/utility-commands.d.ts +10 -0
- package/dist/commands/cli/utility-commands.js +88 -0
- package/dist/commands/cli/utility-commands.js.map +1 -0
- package/dist/commands/handlers/vibe-handlers.js +0 -1
- package/dist/commands/handlers/vibe-handlers.js.map +1 -1
- package/dist/commands/index.d.ts +8 -7
- package/dist/commands/index.js +10 -8
- package/dist/commands/index.js.map +1 -1
- package/dist/config/hot-reload/watcher.js +4 -4
- package/dist/config/hot-reload/watcher.js.map +1 -1
- package/dist/context/index.d.ts +12 -12
- package/dist/context/index.js +25 -12
- package/dist/context/index.js.map +1 -1
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.js +8 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.js +4 -4
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js +20 -333
- package/dist/index.js.map +1 -1
- package/dist/integrations/json-rpc/server.d.ts +9 -0
- package/dist/integrations/json-rpc/server.js +38 -8
- package/dist/integrations/json-rpc/server.js.map +1 -1
- package/dist/integrations/notification-integrations.d.ts +1 -0
- package/dist/integrations/notification-integrations.js +6 -1
- package/dist/integrations/notification-integrations.js.map +1 -1
- package/dist/memory/index.d.ts +2 -2
- package/dist/memory/index.js +2 -2
- package/dist/memory/index.js.map +1 -1
- package/dist/plugins/conflict-detection.js +2 -1
- package/dist/plugins/conflict-detection.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -3
- package/dist/plugins/index.js +3 -3
- package/dist/plugins/index.js.map +1 -1
- package/dist/providers/local-llm-provider.js +7 -4
- package/dist/providers/local-llm-provider.js.map +1 -1
- package/dist/scripting/codebuddy-bindings.js +4 -3
- package/dist/scripting/codebuddy-bindings.js.map +1 -1
- package/dist/security/index.d.ts +7 -5
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +2 -2
- package/dist/server/auth/index.js +2 -2
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/middleware/index.d.ts +5 -5
- package/dist/server/middleware/index.js +5 -5
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/middleware/rate-limit.js +15 -3
- package/dist/server/middleware/rate-limit.js.map +1 -1
- package/dist/server/websocket/handler.js +39 -1
- package/dist/server/websocket/handler.js.map +1 -1
- package/dist/tools/hooks/default-hooks.d.ts +1 -1
- package/dist/tools/hooks/default-hooks.js +2 -1
- package/dist/tools/hooks/default-hooks.js.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.js +11 -11
- package/dist/tools/index.js.map +1 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/errors.js +2 -8
- package/dist/types/errors.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -2
- package/dist/types/index.js.map +1 -1
- package/dist/ui/index.d.ts +17 -21
- package/dist/ui/index.js +25 -22
- package/dist/ui/index.js.map +1 -1
- package/dist/utils/config-validation/schema.d.ts +15 -15
- package/dist/utils/logger.js +3 -9
- package/dist/utils/logger.js.map +1 -1
- package/dist/workflows/index.d.ts +4 -279
- package/dist/workflows/index.js +8 -822
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/state-manager.d.ts +77 -0
- package/dist/workflows/state-manager.js +198 -0
- package/dist/workflows/state-manager.js.map +1 -0
- package/dist/workflows/step-manager.d.ts +39 -0
- package/dist/workflows/step-manager.js +196 -0
- package/dist/workflows/step-manager.js.map +1 -0
- package/dist/workflows/types.d.ts +87 -0
- package/dist/workflows/types.js +5 -0
- package/dist/workflows/types.js.map +1 -0
- package/dist/workflows/workflow-engine.d.ts +34 -0
- package/dist/workflows/workflow-engine.js +354 -0
- package/dist/workflows/workflow-engine.js.map +1 -0
- package/package.json +3 -1
package/dist/workflows/index.js
CHANGED
|
@@ -8,827 +8,13 @@
|
|
|
8
8
|
* - Conditional branching and error handling
|
|
9
9
|
* - Workflow persistence and resume capability
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
export class StepManager extends EventEmitter {
|
|
19
|
-
actionHandlers = new Map();
|
|
20
|
-
constructor() {
|
|
21
|
-
super();
|
|
22
|
-
this.registerBuiltInActions();
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Register built-in action handlers
|
|
26
|
-
*/
|
|
27
|
-
registerBuiltInActions() {
|
|
28
|
-
// Log action
|
|
29
|
-
this.registerAction('log', async (context) => {
|
|
30
|
-
const message = context.variables.message || 'No message';
|
|
31
|
-
console.log(`[Workflow ${context.instanceId}] ${message}`);
|
|
32
|
-
return { success: true, output: message };
|
|
33
|
-
});
|
|
34
|
-
// Delay action
|
|
35
|
-
this.registerAction('delay', async (context) => {
|
|
36
|
-
const ms = context.variables.delay || 1000;
|
|
37
|
-
await new Promise(resolve => setTimeout(resolve, ms));
|
|
38
|
-
return { success: true, output: `Delayed ${ms}ms` };
|
|
39
|
-
});
|
|
40
|
-
// Set variable action
|
|
41
|
-
this.registerAction('setVariable', async (context) => {
|
|
42
|
-
const name = context.variables.varName;
|
|
43
|
-
const value = context.variables.varValue;
|
|
44
|
-
if (name) {
|
|
45
|
-
context.variables[name] = value;
|
|
46
|
-
}
|
|
47
|
-
return { success: true, output: { [name]: value } };
|
|
48
|
-
});
|
|
49
|
-
// Conditional action (always succeeds, used for branching)
|
|
50
|
-
this.registerAction('conditional', async (context) => {
|
|
51
|
-
return { success: true, output: context.variables };
|
|
52
|
-
});
|
|
53
|
-
// Noop action
|
|
54
|
-
this.registerAction('noop', async () => {
|
|
55
|
-
return { success: true, output: 'No operation performed' };
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Register a custom action handler
|
|
60
|
-
*/
|
|
61
|
-
registerAction(name, handler) {
|
|
62
|
-
this.actionHandlers.set(name, handler);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Check if an action is registered
|
|
66
|
-
*/
|
|
67
|
-
hasAction(name) {
|
|
68
|
-
return this.actionHandlers.has(name);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Get all registered action names
|
|
72
|
-
*/
|
|
73
|
-
getRegisteredActions() {
|
|
74
|
-
return Array.from(this.actionHandlers.keys());
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Execute a step
|
|
78
|
-
*/
|
|
79
|
-
async executeStep(step, context, options = {}) {
|
|
80
|
-
const startTime = Date.now();
|
|
81
|
-
this.emit('step:start', { stepId: step.id, stepName: step.name });
|
|
82
|
-
try {
|
|
83
|
-
// Check condition
|
|
84
|
-
if (step.condition) {
|
|
85
|
-
const shouldRun = this.evaluateCondition(step.condition, context);
|
|
86
|
-
if (!shouldRun) {
|
|
87
|
-
this.emit('step:skipped', { stepId: step.id, reason: 'Condition not met' });
|
|
88
|
-
return { success: true, output: 'Step skipped', metadata: { skipped: true } };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// Execute action
|
|
92
|
-
let result;
|
|
93
|
-
if (typeof step.action === 'function') {
|
|
94
|
-
result = await this.executeWithTimeout(step.action(context), options.timeout || step.timeout || 30000);
|
|
95
|
-
}
|
|
96
|
-
else if (typeof step.action === 'string') {
|
|
97
|
-
const handler = this.actionHandlers.get(step.action);
|
|
98
|
-
if (!handler) {
|
|
99
|
-
throw new Error(`Unknown action: ${step.action}`);
|
|
100
|
-
}
|
|
101
|
-
result = await this.executeWithTimeout(handler(context), options.timeout || step.timeout || 30000);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
throw new Error('Invalid action type');
|
|
105
|
-
}
|
|
106
|
-
result.duration = Date.now() - startTime;
|
|
107
|
-
this.emit('step:complete', {
|
|
108
|
-
stepId: step.id,
|
|
109
|
-
stepName: step.name,
|
|
110
|
-
success: result.success,
|
|
111
|
-
duration: result.duration,
|
|
112
|
-
});
|
|
113
|
-
return result;
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
const duration = Date.now() - startTime;
|
|
117
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
118
|
-
this.emit('step:error', {
|
|
119
|
-
stepId: step.id,
|
|
120
|
-
stepName: step.name,
|
|
121
|
-
error: errorMessage,
|
|
122
|
-
duration,
|
|
123
|
-
});
|
|
124
|
-
return {
|
|
125
|
-
success: false,
|
|
126
|
-
error: errorMessage,
|
|
127
|
-
duration,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Execute with timeout
|
|
133
|
-
*/
|
|
134
|
-
async executeWithTimeout(promise, timeout) {
|
|
135
|
-
return new Promise((resolve, reject) => {
|
|
136
|
-
const timer = setTimeout(() => {
|
|
137
|
-
reject(new Error(`Step execution timed out after ${timeout}ms`));
|
|
138
|
-
}, timeout);
|
|
139
|
-
promise
|
|
140
|
-
.then((result) => {
|
|
141
|
-
clearTimeout(timer);
|
|
142
|
-
resolve(result);
|
|
143
|
-
})
|
|
144
|
-
.catch((error) => {
|
|
145
|
-
clearTimeout(timer);
|
|
146
|
-
reject(error);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Evaluate a condition
|
|
152
|
-
*/
|
|
153
|
-
evaluateCondition(condition, context) {
|
|
154
|
-
if (typeof condition === 'function') {
|
|
155
|
-
return condition(context);
|
|
156
|
-
}
|
|
157
|
-
// Simple condition evaluation for string conditions
|
|
158
|
-
// Supports: "variable === value", "variable > value", etc.
|
|
159
|
-
try {
|
|
160
|
-
// Check for variable existence conditions
|
|
161
|
-
if (condition.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
|
|
162
|
-
return !!context.variables[condition];
|
|
163
|
-
}
|
|
164
|
-
// Check for comparison conditions
|
|
165
|
-
const comparisonMatch = condition.match(/^(\w+)\s*(===|!==|==|!=|>=|<=|>|<)\s*(.+)$/);
|
|
166
|
-
if (comparisonMatch) {
|
|
167
|
-
const [, variable, operator, valueStr] = comparisonMatch;
|
|
168
|
-
const actualValue = context.variables[variable];
|
|
169
|
-
let expectedValue = valueStr;
|
|
170
|
-
// Parse value
|
|
171
|
-
if (valueStr === 'true')
|
|
172
|
-
expectedValue = true;
|
|
173
|
-
else if (valueStr === 'false')
|
|
174
|
-
expectedValue = false;
|
|
175
|
-
else if (valueStr === 'null')
|
|
176
|
-
expectedValue = null;
|
|
177
|
-
else if (valueStr === 'undefined')
|
|
178
|
-
expectedValue = undefined;
|
|
179
|
-
else if (!isNaN(Number(valueStr)))
|
|
180
|
-
expectedValue = Number(valueStr);
|
|
181
|
-
else if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
|
182
|
-
expectedValue = valueStr.slice(1, -1);
|
|
183
|
-
}
|
|
184
|
-
switch (operator) {
|
|
185
|
-
case '===':
|
|
186
|
-
case '==':
|
|
187
|
-
return actualValue === expectedValue;
|
|
188
|
-
case '!==':
|
|
189
|
-
case '!=':
|
|
190
|
-
return actualValue !== expectedValue;
|
|
191
|
-
case '>':
|
|
192
|
-
return actualValue > expectedValue;
|
|
193
|
-
case '<':
|
|
194
|
-
return actualValue < expectedValue;
|
|
195
|
-
case '>=':
|
|
196
|
-
return actualValue >= expectedValue;
|
|
197
|
-
case '<=':
|
|
198
|
-
return actualValue <= expectedValue;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
// Default to true for unrecognized conditions
|
|
202
|
-
return true;
|
|
203
|
-
}
|
|
204
|
-
catch {
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
// ============================================================================
|
|
210
|
-
// Workflow State Manager - Persists and manages workflow state
|
|
211
|
-
// ============================================================================
|
|
212
|
-
export class WorkflowStateManager {
|
|
213
|
-
statesDir;
|
|
214
|
-
states = new Map();
|
|
215
|
-
constructor(statesDir) {
|
|
216
|
-
this.statesDir = statesDir || path.join(os.homedir(), '.codebuddy', 'workflows');
|
|
217
|
-
this.ensureDir();
|
|
218
|
-
this.loadStates();
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Ensure the states directory exists
|
|
222
|
-
*/
|
|
223
|
-
ensureDir() {
|
|
224
|
-
if (!fs.existsSync(this.statesDir)) {
|
|
225
|
-
fs.mkdirSync(this.statesDir, { recursive: true });
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Load existing states from disk
|
|
230
|
-
*/
|
|
231
|
-
loadStates() {
|
|
232
|
-
try {
|
|
233
|
-
const files = fs.readdirSync(this.statesDir);
|
|
234
|
-
for (const file of files) {
|
|
235
|
-
if (!file.endsWith('.json'))
|
|
236
|
-
continue;
|
|
237
|
-
try {
|
|
238
|
-
const statePath = path.join(this.statesDir, file);
|
|
239
|
-
const data = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
240
|
-
const state = this.deserializeState(data);
|
|
241
|
-
this.states.set(state.instanceId, state);
|
|
242
|
-
}
|
|
243
|
-
catch {
|
|
244
|
-
// Skip invalid state files
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
// Directory doesn't exist or can't be read
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Serialize state for storage
|
|
254
|
-
*/
|
|
255
|
-
serializeState(state) {
|
|
256
|
-
return {
|
|
257
|
-
...state,
|
|
258
|
-
context: {
|
|
259
|
-
...state.context,
|
|
260
|
-
stepResults: Array.from(state.context.stepResults.entries()),
|
|
261
|
-
},
|
|
262
|
-
stepExecutions: Array.from(state.stepExecutions.entries()),
|
|
263
|
-
createdAt: state.createdAt.toISOString(),
|
|
264
|
-
startedAt: state.startedAt?.toISOString(),
|
|
265
|
-
completedAt: state.completedAt?.toISOString(),
|
|
266
|
-
pausedAt: state.pausedAt?.toISOString(),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Deserialize state from storage
|
|
271
|
-
*/
|
|
272
|
-
deserializeState(data) {
|
|
273
|
-
const contextData = data.context;
|
|
274
|
-
return {
|
|
275
|
-
...data,
|
|
276
|
-
context: {
|
|
277
|
-
...contextData,
|
|
278
|
-
stepResults: new Map(contextData.stepResults),
|
|
279
|
-
},
|
|
280
|
-
stepExecutions: new Map(data.stepExecutions),
|
|
281
|
-
createdAt: new Date(data.createdAt),
|
|
282
|
-
startedAt: data.startedAt ? new Date(data.startedAt) : undefined,
|
|
283
|
-
completedAt: data.completedAt ? new Date(data.completedAt) : undefined,
|
|
284
|
-
pausedAt: data.pausedAt ? new Date(data.pausedAt) : undefined,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Generate unique instance ID
|
|
289
|
-
*/
|
|
290
|
-
generateInstanceId() {
|
|
291
|
-
const timestamp = Date.now().toString(36);
|
|
292
|
-
const random = Math.random().toString(36).substring(2, 8);
|
|
293
|
-
return `wf_${timestamp}_${random}`;
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Create a new workflow state
|
|
297
|
-
*/
|
|
298
|
-
createState(workflowId, initialContext = {}) {
|
|
299
|
-
const instanceId = this.generateInstanceId();
|
|
300
|
-
const state = {
|
|
301
|
-
instanceId,
|
|
302
|
-
workflowId,
|
|
303
|
-
status: 'pending',
|
|
304
|
-
context: {
|
|
305
|
-
workflowId,
|
|
306
|
-
instanceId,
|
|
307
|
-
variables: { ...initialContext },
|
|
308
|
-
stepResults: new Map(),
|
|
309
|
-
metadata: {},
|
|
310
|
-
},
|
|
311
|
-
stepExecutions: new Map(),
|
|
312
|
-
currentStepIndex: 0,
|
|
313
|
-
createdAt: new Date(),
|
|
314
|
-
};
|
|
315
|
-
this.states.set(instanceId, state);
|
|
316
|
-
this.saveState(state);
|
|
317
|
-
return state;
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Get a workflow state by instance ID
|
|
321
|
-
*/
|
|
322
|
-
getState(instanceId) {
|
|
323
|
-
return this.states.get(instanceId);
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Update a workflow state
|
|
327
|
-
*/
|
|
328
|
-
updateState(instanceId, updates) {
|
|
329
|
-
const state = this.states.get(instanceId);
|
|
330
|
-
if (!state)
|
|
331
|
-
return undefined;
|
|
332
|
-
Object.assign(state, updates);
|
|
333
|
-
this.saveState(state);
|
|
334
|
-
return state;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Save state to disk
|
|
338
|
-
*/
|
|
339
|
-
saveState(state) {
|
|
340
|
-
const statePath = path.join(this.statesDir, `${state.instanceId}.json`);
|
|
341
|
-
fs.writeFileSync(statePath, JSON.stringify(this.serializeState(state), null, 2));
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Delete a workflow state
|
|
345
|
-
*/
|
|
346
|
-
deleteState(instanceId) {
|
|
347
|
-
const existed = this.states.delete(instanceId);
|
|
348
|
-
if (existed) {
|
|
349
|
-
const statePath = path.join(this.statesDir, `${instanceId}.json`);
|
|
350
|
-
if (fs.existsSync(statePath)) {
|
|
351
|
-
fs.unlinkSync(statePath);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
return existed;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Get all states
|
|
358
|
-
*/
|
|
359
|
-
getAllStates() {
|
|
360
|
-
return Array.from(this.states.values());
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Get states by workflow ID
|
|
364
|
-
*/
|
|
365
|
-
getStatesByWorkflow(workflowId) {
|
|
366
|
-
return Array.from(this.states.values()).filter(s => s.workflowId === workflowId);
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Get states by status
|
|
370
|
-
*/
|
|
371
|
-
getStatesByStatus(status) {
|
|
372
|
-
return Array.from(this.states.values()).filter(s => s.status === status);
|
|
373
|
-
}
|
|
374
|
-
/**
|
|
375
|
-
* Clear completed states
|
|
376
|
-
*/
|
|
377
|
-
clearCompleted() {
|
|
378
|
-
const completed = this.getStatesByStatus('completed');
|
|
379
|
-
const failed = this.getStatesByStatus('failed');
|
|
380
|
-
const cancelled = this.getStatesByStatus('cancelled');
|
|
381
|
-
const toDelete = [...completed, ...failed, ...cancelled];
|
|
382
|
-
for (const state of toDelete) {
|
|
383
|
-
this.deleteState(state.instanceId);
|
|
384
|
-
}
|
|
385
|
-
return toDelete.length;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Get statistics
|
|
389
|
-
*/
|
|
390
|
-
getStats() {
|
|
391
|
-
const states = this.getAllStates();
|
|
392
|
-
return {
|
|
393
|
-
total: states.length,
|
|
394
|
-
pending: states.filter(s => s.status === 'pending').length,
|
|
395
|
-
running: states.filter(s => s.status === 'running').length,
|
|
396
|
-
paused: states.filter(s => s.status === 'paused').length,
|
|
397
|
-
completed: states.filter(s => s.status === 'completed').length,
|
|
398
|
-
failed: states.filter(s => s.status === 'failed').length,
|
|
399
|
-
cancelled: states.filter(s => s.status === 'cancelled').length,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
// ============================================================================
|
|
404
|
-
// Workflow Engine - Main orchestrator for workflow execution
|
|
405
|
-
// ============================================================================
|
|
406
|
-
export class WorkflowEngine extends EventEmitter {
|
|
407
|
-
workflows = new Map();
|
|
408
|
-
stateManager;
|
|
409
|
-
stepManager;
|
|
410
|
-
runningWorkflows = new Set();
|
|
411
|
-
constructor(statesDir) {
|
|
412
|
-
super();
|
|
413
|
-
this.stateManager = new WorkflowStateManager(statesDir);
|
|
414
|
-
this.stepManager = new StepManager();
|
|
415
|
-
this.registerBuiltInWorkflows();
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Register built-in workflows
|
|
419
|
-
*/
|
|
420
|
-
registerBuiltInWorkflows() {
|
|
421
|
-
// Sample validation workflow
|
|
422
|
-
this.registerWorkflow({
|
|
423
|
-
id: 'validation',
|
|
424
|
-
name: 'Validation Workflow',
|
|
425
|
-
description: 'Basic validation workflow template',
|
|
426
|
-
version: '1.0.0',
|
|
427
|
-
steps: [
|
|
428
|
-
{ id: 'validate-input', name: 'Validate Input', action: 'noop' },
|
|
429
|
-
{ id: 'process', name: 'Process', action: 'noop' },
|
|
430
|
-
{ id: 'complete', name: 'Complete', action: 'log' },
|
|
431
|
-
],
|
|
432
|
-
});
|
|
433
|
-
// Sample data pipeline workflow
|
|
434
|
-
this.registerWorkflow({
|
|
435
|
-
id: 'data-pipeline',
|
|
436
|
-
name: 'Data Pipeline',
|
|
437
|
-
description: 'Sample data processing pipeline',
|
|
438
|
-
version: '1.0.0',
|
|
439
|
-
steps: [
|
|
440
|
-
{ id: 'extract', name: 'Extract Data', action: 'noop' },
|
|
441
|
-
{ id: 'transform', name: 'Transform Data', action: 'noop' },
|
|
442
|
-
{ id: 'load', name: 'Load Data', action: 'noop' },
|
|
443
|
-
],
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Register a workflow definition
|
|
448
|
-
*/
|
|
449
|
-
registerWorkflow(workflow) {
|
|
450
|
-
this.workflows.set(workflow.id, workflow);
|
|
451
|
-
this.emit('workflow:registered', { workflowId: workflow.id, name: workflow.name });
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Unregister a workflow
|
|
455
|
-
*/
|
|
456
|
-
unregisterWorkflow(workflowId) {
|
|
457
|
-
const existed = this.workflows.delete(workflowId);
|
|
458
|
-
if (existed) {
|
|
459
|
-
this.emit('workflow:unregistered', { workflowId });
|
|
460
|
-
}
|
|
461
|
-
return existed;
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Get a workflow definition
|
|
465
|
-
*/
|
|
466
|
-
getWorkflow(workflowId) {
|
|
467
|
-
return this.workflows.get(workflowId);
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Get all registered workflows
|
|
471
|
-
*/
|
|
472
|
-
getWorkflows() {
|
|
473
|
-
return Array.from(this.workflows.values());
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Register a custom action
|
|
477
|
-
*/
|
|
478
|
-
registerAction(name, handler) {
|
|
479
|
-
this.stepManager.registerAction(name, handler);
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Start a new workflow instance
|
|
483
|
-
*/
|
|
484
|
-
async startWorkflow(workflowId, options = {}) {
|
|
485
|
-
const workflow = this.workflows.get(workflowId);
|
|
486
|
-
if (!workflow) {
|
|
487
|
-
return {
|
|
488
|
-
success: false,
|
|
489
|
-
instanceId: '',
|
|
490
|
-
workflowId,
|
|
491
|
-
status: 'failed',
|
|
492
|
-
stepResults: new Map(),
|
|
493
|
-
finalContext: {},
|
|
494
|
-
duration: 0,
|
|
495
|
-
error: `Workflow not found: ${workflowId}`,
|
|
496
|
-
completedSteps: 0,
|
|
497
|
-
totalSteps: 0,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
const state = this.stateManager.createState(workflowId, {
|
|
501
|
-
...workflow.initialContext,
|
|
502
|
-
...options.initialContext,
|
|
503
|
-
});
|
|
504
|
-
return this.executeWorkflow(workflow, state, options);
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Resume a paused workflow
|
|
508
|
-
*/
|
|
509
|
-
async resumeWorkflow(instanceId) {
|
|
510
|
-
const state = this.stateManager.getState(instanceId);
|
|
511
|
-
if (!state) {
|
|
512
|
-
return {
|
|
513
|
-
success: false,
|
|
514
|
-
instanceId,
|
|
515
|
-
workflowId: '',
|
|
516
|
-
status: 'failed',
|
|
517
|
-
stepResults: new Map(),
|
|
518
|
-
finalContext: {},
|
|
519
|
-
duration: 0,
|
|
520
|
-
error: `Workflow instance not found: ${instanceId}`,
|
|
521
|
-
completedSteps: 0,
|
|
522
|
-
totalSteps: 0,
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
if (state.status !== 'paused') {
|
|
526
|
-
return {
|
|
527
|
-
success: false,
|
|
528
|
-
instanceId,
|
|
529
|
-
workflowId: state.workflowId,
|
|
530
|
-
status: state.status,
|
|
531
|
-
stepResults: state.context.stepResults,
|
|
532
|
-
finalContext: state.context.variables,
|
|
533
|
-
duration: state.totalDuration || 0,
|
|
534
|
-
error: `Workflow is not paused (status: ${state.status})`,
|
|
535
|
-
completedSteps: state.currentStepIndex,
|
|
536
|
-
totalSteps: this.workflows.get(state.workflowId)?.steps.length || 0,
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
const workflow = this.workflows.get(state.workflowId);
|
|
540
|
-
if (!workflow) {
|
|
541
|
-
return {
|
|
542
|
-
success: false,
|
|
543
|
-
instanceId,
|
|
544
|
-
workflowId: state.workflowId,
|
|
545
|
-
status: 'failed',
|
|
546
|
-
stepResults: state.context.stepResults,
|
|
547
|
-
finalContext: state.context.variables,
|
|
548
|
-
duration: 0,
|
|
549
|
-
error: `Workflow definition not found: ${state.workflowId}`,
|
|
550
|
-
completedSteps: state.currentStepIndex,
|
|
551
|
-
totalSteps: 0,
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
return this.executeWorkflow(workflow, state, {});
|
|
555
|
-
}
|
|
556
|
-
/**
|
|
557
|
-
* Pause a running workflow
|
|
558
|
-
*/
|
|
559
|
-
pauseWorkflow(instanceId) {
|
|
560
|
-
const state = this.stateManager.getState(instanceId);
|
|
561
|
-
if (!state || state.status !== 'running') {
|
|
562
|
-
return false;
|
|
563
|
-
}
|
|
564
|
-
this.stateManager.updateState(instanceId, {
|
|
565
|
-
status: 'paused',
|
|
566
|
-
pausedAt: new Date(),
|
|
567
|
-
});
|
|
568
|
-
this.emit('workflow:paused', { instanceId });
|
|
569
|
-
return true;
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Cancel a workflow
|
|
573
|
-
*/
|
|
574
|
-
cancelWorkflow(instanceId) {
|
|
575
|
-
const state = this.stateManager.getState(instanceId);
|
|
576
|
-
if (!state) {
|
|
577
|
-
return false;
|
|
578
|
-
}
|
|
579
|
-
if (state.status === 'completed' || state.status === 'cancelled') {
|
|
580
|
-
return false;
|
|
581
|
-
}
|
|
582
|
-
this.runningWorkflows.delete(instanceId);
|
|
583
|
-
this.stateManager.updateState(instanceId, {
|
|
584
|
-
status: 'cancelled',
|
|
585
|
-
completedAt: new Date(),
|
|
586
|
-
});
|
|
587
|
-
this.emit('workflow:cancelled', { instanceId });
|
|
588
|
-
return true;
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Execute a workflow
|
|
592
|
-
*/
|
|
593
|
-
async executeWorkflow(workflow, state, options) {
|
|
594
|
-
const startTime = Date.now();
|
|
595
|
-
const instanceId = state.instanceId;
|
|
596
|
-
// Mark as running
|
|
597
|
-
this.runningWorkflows.add(instanceId);
|
|
598
|
-
this.stateManager.updateState(instanceId, {
|
|
599
|
-
status: 'running',
|
|
600
|
-
startedAt: state.startedAt || new Date(),
|
|
601
|
-
});
|
|
602
|
-
this.emit('workflow:start', { instanceId, workflowId: workflow.id });
|
|
603
|
-
// Find starting step
|
|
604
|
-
let stepIndex = state.currentStepIndex;
|
|
605
|
-
if (options.startFromStep) {
|
|
606
|
-
const idx = workflow.steps.findIndex(s => s.id === options.startFromStep);
|
|
607
|
-
if (idx !== -1) {
|
|
608
|
-
stepIndex = idx;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
try {
|
|
612
|
-
for (; stepIndex < workflow.steps.length; stepIndex++) {
|
|
613
|
-
// Check if paused or cancelled
|
|
614
|
-
const currentState = this.stateManager.getState(instanceId);
|
|
615
|
-
if (!currentState || currentState.status === 'paused' || currentState.status === 'cancelled') {
|
|
616
|
-
break;
|
|
617
|
-
}
|
|
618
|
-
const step = workflow.steps[stepIndex];
|
|
619
|
-
state.context.currentStep = step.id;
|
|
620
|
-
// Emit step start
|
|
621
|
-
if (options.onStepStart) {
|
|
622
|
-
options.onStepStart(step.id);
|
|
623
|
-
}
|
|
624
|
-
// Create step execution record
|
|
625
|
-
const execution = {
|
|
626
|
-
stepId: step.id,
|
|
627
|
-
stepName: step.name,
|
|
628
|
-
status: 'running',
|
|
629
|
-
startedAt: new Date(),
|
|
630
|
-
retries: 0,
|
|
631
|
-
};
|
|
632
|
-
state.stepExecutions.set(step.id, execution);
|
|
633
|
-
// Execute step with retries
|
|
634
|
-
let result = null;
|
|
635
|
-
let retries = 0;
|
|
636
|
-
const maxRetries = step.maxRetries || 0;
|
|
637
|
-
while (retries <= maxRetries) {
|
|
638
|
-
result = await this.stepManager.executeStep(step, state.context, {
|
|
639
|
-
timeout: options.timeout || step.timeout,
|
|
640
|
-
});
|
|
641
|
-
if (result.success || !step.retryOnFailure) {
|
|
642
|
-
break;
|
|
643
|
-
}
|
|
644
|
-
retries++;
|
|
645
|
-
execution.retries = retries;
|
|
646
|
-
}
|
|
647
|
-
if (!result) {
|
|
648
|
-
result = { success: false, error: 'No result from step execution' };
|
|
649
|
-
}
|
|
650
|
-
// Update execution record
|
|
651
|
-
execution.status = result.success ? 'completed' : 'failed';
|
|
652
|
-
execution.completedAt = new Date();
|
|
653
|
-
execution.result = result;
|
|
654
|
-
// Store result in context
|
|
655
|
-
state.context.stepResults.set(step.id, result);
|
|
656
|
-
// Update state
|
|
657
|
-
this.stateManager.updateState(instanceId, {
|
|
658
|
-
currentStepIndex: stepIndex + 1,
|
|
659
|
-
context: state.context,
|
|
660
|
-
stepExecutions: state.stepExecutions,
|
|
661
|
-
});
|
|
662
|
-
// Emit step complete
|
|
663
|
-
if (options.onStepComplete) {
|
|
664
|
-
options.onStepComplete(execution);
|
|
665
|
-
}
|
|
666
|
-
// Handle failure
|
|
667
|
-
if (!result.success) {
|
|
668
|
-
if (step.onFailure) {
|
|
669
|
-
// Jump to failure step
|
|
670
|
-
const failureIdx = workflow.steps.findIndex(s => s.id === step.onFailure);
|
|
671
|
-
if (failureIdx !== -1) {
|
|
672
|
-
stepIndex = failureIdx - 1; // -1 because loop will increment
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
// Default: fail the workflow
|
|
677
|
-
throw new Error(result.error || `Step ${step.id} failed`);
|
|
678
|
-
}
|
|
679
|
-
// Handle success branching
|
|
680
|
-
if (step.onSuccess && result.success) {
|
|
681
|
-
const successIdx = workflow.steps.findIndex(s => s.id === step.onSuccess);
|
|
682
|
-
if (successIdx !== -1) {
|
|
683
|
-
stepIndex = successIdx - 1; // -1 because loop will increment
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
// Check final state
|
|
688
|
-
const finalState = this.stateManager.getState(instanceId);
|
|
689
|
-
if (finalState?.status === 'paused') {
|
|
690
|
-
return this.buildResult(finalState, workflow, startTime);
|
|
691
|
-
}
|
|
692
|
-
if (finalState?.status === 'cancelled') {
|
|
693
|
-
return this.buildResult(finalState, workflow, startTime);
|
|
694
|
-
}
|
|
695
|
-
// Mark as completed
|
|
696
|
-
this.runningWorkflows.delete(instanceId);
|
|
697
|
-
this.stateManager.updateState(instanceId, {
|
|
698
|
-
status: 'completed',
|
|
699
|
-
completedAt: new Date(),
|
|
700
|
-
totalDuration: Date.now() - startTime,
|
|
701
|
-
});
|
|
702
|
-
this.emit('workflow:complete', { instanceId, success: true });
|
|
703
|
-
return this.buildResult(this.stateManager.getState(instanceId), workflow, startTime);
|
|
704
|
-
}
|
|
705
|
-
catch (error) {
|
|
706
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
707
|
-
this.runningWorkflows.delete(instanceId);
|
|
708
|
-
this.stateManager.updateState(instanceId, {
|
|
709
|
-
status: 'failed',
|
|
710
|
-
completedAt: new Date(),
|
|
711
|
-
error: errorMessage,
|
|
712
|
-
totalDuration: Date.now() - startTime,
|
|
713
|
-
});
|
|
714
|
-
this.emit('workflow:error', { instanceId, error: errorMessage });
|
|
715
|
-
return this.buildResult(this.stateManager.getState(instanceId), workflow, startTime, errorMessage);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Build workflow result
|
|
720
|
-
*/
|
|
721
|
-
buildResult(state, workflow, startTime, error) {
|
|
722
|
-
return {
|
|
723
|
-
success: state.status === 'completed',
|
|
724
|
-
instanceId: state.instanceId,
|
|
725
|
-
workflowId: state.workflowId,
|
|
726
|
-
status: state.status,
|
|
727
|
-
stepResults: state.context.stepResults,
|
|
728
|
-
finalContext: state.context.variables,
|
|
729
|
-
duration: Date.now() - startTime,
|
|
730
|
-
error: error || state.error,
|
|
731
|
-
completedSteps: state.currentStepIndex,
|
|
732
|
-
totalSteps: workflow.steps.length,
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Get workflow state
|
|
737
|
-
*/
|
|
738
|
-
getWorkflowState(instanceId) {
|
|
739
|
-
return this.stateManager.getState(instanceId);
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Get all workflow instances
|
|
743
|
-
*/
|
|
744
|
-
getWorkflowInstances() {
|
|
745
|
-
return this.stateManager.getAllStates();
|
|
746
|
-
}
|
|
747
|
-
/**
|
|
748
|
-
* Get running workflows
|
|
749
|
-
*/
|
|
750
|
-
getRunningWorkflows() {
|
|
751
|
-
return Array.from(this.runningWorkflows);
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Get statistics
|
|
755
|
-
*/
|
|
756
|
-
getStats() {
|
|
757
|
-
return this.stateManager.getStats();
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Format workflow result for display
|
|
761
|
-
*/
|
|
762
|
-
formatResult(result) {
|
|
763
|
-
const statusEmoji = result.success ? '✅' : '❌';
|
|
764
|
-
let output = `\n${statusEmoji} Workflow Result: ${result.workflowId}\n`;
|
|
765
|
-
output += '═'.repeat(50) + '\n\n';
|
|
766
|
-
output += `Instance: ${result.instanceId}\n`;
|
|
767
|
-
output += `Status: ${result.status.toUpperCase()}\n`;
|
|
768
|
-
output += `Duration: ${(result.duration / 1000).toFixed(2)}s\n`;
|
|
769
|
-
output += `Steps: ${result.completedSteps}/${result.totalSteps}\n`;
|
|
770
|
-
if (result.error) {
|
|
771
|
-
output += `Error: ${result.error}\n`;
|
|
772
|
-
}
|
|
773
|
-
if (result.stepResults.size > 0) {
|
|
774
|
-
output += '\nStep Results:\n';
|
|
775
|
-
for (const [stepId, stepResult] of result.stepResults) {
|
|
776
|
-
const stepEmoji = stepResult.success ? ' ✓' : ' ✗';
|
|
777
|
-
output += `${stepEmoji} ${stepId}: ${stepResult.success ? 'completed' : 'failed'}`;
|
|
778
|
-
if (stepResult.duration) {
|
|
779
|
-
output += ` (${stepResult.duration}ms)`;
|
|
780
|
-
}
|
|
781
|
-
output += '\n';
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
output += '\n' + '═'.repeat(50) + '\n';
|
|
785
|
-
return output;
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* Format available workflows for display
|
|
789
|
-
*/
|
|
790
|
-
formatWorkflows() {
|
|
791
|
-
const workflows = this.getWorkflows();
|
|
792
|
-
if (workflows.length === 0) {
|
|
793
|
-
return 'No workflows registered.\n';
|
|
794
|
-
}
|
|
795
|
-
let output = 'Available Workflows:\n\n';
|
|
796
|
-
for (const workflow of workflows) {
|
|
797
|
-
output += ` 📋 ${workflow.name} (${workflow.id})\n`;
|
|
798
|
-
output += ` ${workflow.description}\n`;
|
|
799
|
-
output += ` Version: ${workflow.version} | Steps: ${workflow.steps.length}\n\n`;
|
|
800
|
-
}
|
|
801
|
-
return output;
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Clean up resources
|
|
805
|
-
*/
|
|
806
|
-
dispose() {
|
|
807
|
-
// Cancel all running workflows
|
|
808
|
-
for (const instanceId of this.runningWorkflows) {
|
|
809
|
-
this.cancelWorkflow(instanceId);
|
|
810
|
-
}
|
|
811
|
-
this.runningWorkflows.clear();
|
|
812
|
-
this.workflows.clear();
|
|
813
|
-
this.removeAllListeners();
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
// ============================================================================
|
|
817
|
-
// Singleton and Factory Functions
|
|
818
|
-
// ============================================================================
|
|
819
|
-
let workflowEngineInstance = null;
|
|
820
|
-
export function getWorkflowEngine(statesDir) {
|
|
821
|
-
if (!workflowEngineInstance) {
|
|
822
|
-
workflowEngineInstance = new WorkflowEngine(statesDir);
|
|
823
|
-
}
|
|
824
|
-
return workflowEngineInstance;
|
|
825
|
-
}
|
|
826
|
-
export function resetWorkflowEngine() {
|
|
827
|
-
if (workflowEngineInstance) {
|
|
828
|
-
workflowEngineInstance.dispose();
|
|
829
|
-
}
|
|
830
|
-
workflowEngineInstance = null;
|
|
831
|
-
}
|
|
11
|
+
// Step Manager
|
|
12
|
+
export { StepManager } from './step-manager.js';
|
|
13
|
+
// State Manager
|
|
14
|
+
export { WorkflowStateManager } from './state-manager.js';
|
|
15
|
+
// Workflow Engine
|
|
16
|
+
export { WorkflowEngine, getWorkflowEngine, resetWorkflowEngine, } from './workflow-engine.js';
|
|
832
17
|
export { PipelineCompositor, getPipelineCompositor, resetPipelineCompositor, } from './pipeline.js';
|
|
833
|
-
|
|
18
|
+
// Pipeline CLI Utilities — import directly from 'src/commands/pipeline.js'
|
|
19
|
+
// (Re-export removed to break circular dependency: workflows/index → commands/pipeline → workflows/index)
|
|
834
20
|
//# sourceMappingURL=index.js.map
|