musubi-sdd 3.10.0 → 5.1.0
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/README.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill Executor - Execute skills with priority, retry, and guardrails
|
|
3
|
+
* Sprint 3.2: Skill System Architecture
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - P-label priority execution (P0-P3)
|
|
7
|
+
* - Parallel and sequential execution
|
|
8
|
+
* - Input/output validation
|
|
9
|
+
* - Retry with exponential backoff
|
|
10
|
+
* - Execution hooks
|
|
11
|
+
* - Guardrail integration
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const EventEmitter = require('events');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Execution status
|
|
18
|
+
*/
|
|
19
|
+
const ExecutionStatus = {
|
|
20
|
+
PENDING: 'pending',
|
|
21
|
+
RUNNING: 'running',
|
|
22
|
+
COMPLETED: 'completed',
|
|
23
|
+
FAILED: 'failed',
|
|
24
|
+
CANCELLED: 'cancelled',
|
|
25
|
+
TIMEOUT: 'timeout',
|
|
26
|
+
SKIPPED: 'skipped'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Execution result
|
|
31
|
+
*/
|
|
32
|
+
class ExecutionResult {
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
this.skillId = options.skillId || '';
|
|
35
|
+
this.status = options.status || ExecutionStatus.PENDING;
|
|
36
|
+
this.output = options.output || null;
|
|
37
|
+
this.error = options.error || null;
|
|
38
|
+
this.startTime = options.startTime || null;
|
|
39
|
+
this.endTime = options.endTime || null;
|
|
40
|
+
this.duration = options.duration || 0;
|
|
41
|
+
this.attempts = options.attempts || 0;
|
|
42
|
+
this.metadata = options.metadata || {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get success() {
|
|
46
|
+
return this.status === ExecutionStatus.COMPLETED;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
toJSON() {
|
|
50
|
+
return {
|
|
51
|
+
skillId: this.skillId,
|
|
52
|
+
status: this.status,
|
|
53
|
+
success: this.success,
|
|
54
|
+
output: this.output,
|
|
55
|
+
error: this.error,
|
|
56
|
+
duration: this.duration,
|
|
57
|
+
attempts: this.attempts,
|
|
58
|
+
metadata: this.metadata
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Execution context
|
|
65
|
+
*/
|
|
66
|
+
class ExecutionContext {
|
|
67
|
+
constructor(options = {}) {
|
|
68
|
+
this.executionId = options.executionId || this._generateId();
|
|
69
|
+
this.skillId = options.skillId || '';
|
|
70
|
+
this.input = options.input || {};
|
|
71
|
+
this.variables = new Map(Object.entries(options.variables || {}));
|
|
72
|
+
this.parentContext = options.parentContext || null;
|
|
73
|
+
this.startTime = null;
|
|
74
|
+
this.metadata = options.metadata || {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_generateId() {
|
|
78
|
+
return `exec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getVariable(name) {
|
|
82
|
+
if (this.variables.has(name)) {
|
|
83
|
+
return this.variables.get(name);
|
|
84
|
+
}
|
|
85
|
+
if (this.parentContext) {
|
|
86
|
+
return this.parentContext.getVariable(name);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setVariable(name, value) {
|
|
92
|
+
this.variables.set(name, value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
createChild(options = {}) {
|
|
96
|
+
return new ExecutionContext({
|
|
97
|
+
...options,
|
|
98
|
+
parentContext: this,
|
|
99
|
+
variables: options.variables || {}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Input/Output validator
|
|
106
|
+
*/
|
|
107
|
+
class IOValidator {
|
|
108
|
+
constructor() {
|
|
109
|
+
this.typeValidators = {
|
|
110
|
+
string: (v) => typeof v === 'string',
|
|
111
|
+
number: (v) => typeof v === 'number' && !isNaN(v),
|
|
112
|
+
boolean: (v) => typeof v === 'boolean',
|
|
113
|
+
array: (v) => Array.isArray(v),
|
|
114
|
+
object: (v) => typeof v === 'object' && v !== null && !Array.isArray(v),
|
|
115
|
+
any: () => true
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
validateInput(input, schema) {
|
|
120
|
+
const errors = [];
|
|
121
|
+
|
|
122
|
+
for (const field of schema) {
|
|
123
|
+
const value = input[field.name];
|
|
124
|
+
|
|
125
|
+
// Required check
|
|
126
|
+
if (field.required && (value === undefined || value === null)) {
|
|
127
|
+
errors.push(`Missing required input: ${field.name}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Type check
|
|
132
|
+
if (value !== undefined && value !== null && field.type) {
|
|
133
|
+
const validator = this.typeValidators[field.type];
|
|
134
|
+
if (validator && !validator(value)) {
|
|
135
|
+
errors.push(`Invalid type for ${field.name}: expected ${field.type}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Custom validation
|
|
140
|
+
if (field.validate && value !== undefined) {
|
|
141
|
+
try {
|
|
142
|
+
const result = field.validate(value);
|
|
143
|
+
if (result !== true && typeof result === 'string') {
|
|
144
|
+
errors.push(result);
|
|
145
|
+
}
|
|
146
|
+
} catch (e) {
|
|
147
|
+
errors.push(`Validation error for ${field.name}: ${e.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
valid: errors.length === 0,
|
|
154
|
+
errors
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
validateOutput(output, schema) {
|
|
159
|
+
const errors = [];
|
|
160
|
+
|
|
161
|
+
for (const field of schema) {
|
|
162
|
+
const value = output?.[field.name];
|
|
163
|
+
|
|
164
|
+
if (field.required && (value === undefined || value === null)) {
|
|
165
|
+
errors.push(`Missing required output: ${field.name}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (value !== undefined && value !== null && field.type) {
|
|
169
|
+
const validator = this.typeValidators[field.type];
|
|
170
|
+
if (validator && !validator(value)) {
|
|
171
|
+
errors.push(`Invalid output type for ${field.name}: expected ${field.type}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
valid: errors.length === 0,
|
|
178
|
+
errors
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Main Skill Executor class
|
|
185
|
+
*/
|
|
186
|
+
class SkillExecutor extends EventEmitter {
|
|
187
|
+
constructor(registry, options = {}) {
|
|
188
|
+
super();
|
|
189
|
+
this.registry = registry;
|
|
190
|
+
this.validator = new IOValidator();
|
|
191
|
+
this.guardrails = options.guardrails || [];
|
|
192
|
+
this.activeExecutions = new Map();
|
|
193
|
+
this.executionHistory = [];
|
|
194
|
+
this.maxHistorySize = options.maxHistorySize || 1000;
|
|
195
|
+
|
|
196
|
+
// Hooks
|
|
197
|
+
this.hooks = {
|
|
198
|
+
beforeExecute: options.beforeExecute || [],
|
|
199
|
+
afterExecute: options.afterExecute || [],
|
|
200
|
+
onError: options.onError || []
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Options
|
|
204
|
+
this.options = {
|
|
205
|
+
defaultTimeout: options.defaultTimeout || 30000,
|
|
206
|
+
maxConcurrent: options.maxConcurrent || 10,
|
|
207
|
+
enableMetrics: options.enableMetrics !== false
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
this._concurrentCount = 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Execute a single skill
|
|
215
|
+
*/
|
|
216
|
+
async execute(skillId, input = {}, options = {}) {
|
|
217
|
+
const skillEntry = this.registry.getSkillEntry(skillId);
|
|
218
|
+
if (!skillEntry) {
|
|
219
|
+
throw new Error(`Skill '${skillId}' not found`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const { metadata, handler } = skillEntry;
|
|
223
|
+
|
|
224
|
+
if (!handler) {
|
|
225
|
+
throw new Error(`Skill '${skillId}' has no handler`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Create execution context
|
|
229
|
+
const context = new ExecutionContext({
|
|
230
|
+
skillId,
|
|
231
|
+
input,
|
|
232
|
+
variables: options.variables,
|
|
233
|
+
metadata: options.metadata
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Create result
|
|
237
|
+
const result = new ExecutionResult({
|
|
238
|
+
skillId,
|
|
239
|
+
status: ExecutionStatus.PENDING
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Track execution
|
|
243
|
+
this.activeExecutions.set(context.executionId, { context, result, cancelled: false });
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Check concurrency limit
|
|
247
|
+
if (this._concurrentCount >= this.options.maxConcurrent) {
|
|
248
|
+
await this._waitForSlot();
|
|
249
|
+
}
|
|
250
|
+
this._concurrentCount++;
|
|
251
|
+
|
|
252
|
+
// Validate input
|
|
253
|
+
const inputValidation = this.validator.validateInput(input, metadata.inputs);
|
|
254
|
+
if (!inputValidation.valid) {
|
|
255
|
+
throw new Error(`Input validation failed: ${inputValidation.errors.join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Run guardrails (pre-execution)
|
|
259
|
+
await this._runGuardrails('pre', { skillId, input, context });
|
|
260
|
+
|
|
261
|
+
// Run beforeExecute hooks
|
|
262
|
+
for (const hook of this.hooks.beforeExecute) {
|
|
263
|
+
await hook(context, metadata);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Start execution
|
|
267
|
+
result.status = ExecutionStatus.RUNNING;
|
|
268
|
+
result.startTime = Date.now();
|
|
269
|
+
context.startTime = result.startTime;
|
|
270
|
+
|
|
271
|
+
this.emit('execution-started', { executionId: context.executionId, skillId });
|
|
272
|
+
|
|
273
|
+
// Execute with retry
|
|
274
|
+
const output = await this._executeWithRetry(
|
|
275
|
+
handler,
|
|
276
|
+
context,
|
|
277
|
+
metadata.retryPolicy,
|
|
278
|
+
options.timeout || metadata.timeout || this.options.defaultTimeout,
|
|
279
|
+
result
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
// Validate output
|
|
283
|
+
const outputValidation = this.validator.validateOutput(output, metadata.outputs);
|
|
284
|
+
if (!outputValidation.valid) {
|
|
285
|
+
throw new Error(`Output validation failed: ${outputValidation.errors.join(', ')}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Run guardrails (post-execution)
|
|
289
|
+
await this._runGuardrails('post', { skillId, input, output, context });
|
|
290
|
+
|
|
291
|
+
// Complete
|
|
292
|
+
result.status = ExecutionStatus.COMPLETED;
|
|
293
|
+
result.output = output;
|
|
294
|
+
result.endTime = Date.now();
|
|
295
|
+
result.duration = result.endTime - result.startTime;
|
|
296
|
+
|
|
297
|
+
// Run afterExecute hooks
|
|
298
|
+
for (const hook of this.hooks.afterExecute) {
|
|
299
|
+
await hook(context, result);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Record stats
|
|
303
|
+
if (this.options.enableMetrics) {
|
|
304
|
+
this.registry.recordExecution(skillId, true, result.duration);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.emit('execution-completed', {
|
|
308
|
+
executionId: context.executionId,
|
|
309
|
+
skillId,
|
|
310
|
+
duration: result.duration
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
} catch (error) {
|
|
314
|
+
result.status = this.activeExecutions.get(context.executionId)?.cancelled
|
|
315
|
+
? ExecutionStatus.CANCELLED
|
|
316
|
+
: ExecutionStatus.FAILED;
|
|
317
|
+
result.error = error.message;
|
|
318
|
+
result.endTime = Date.now();
|
|
319
|
+
result.duration = result.startTime ? result.endTime - result.startTime : 0;
|
|
320
|
+
|
|
321
|
+
// Run onError hooks
|
|
322
|
+
for (const hook of this.hooks.onError) {
|
|
323
|
+
await hook(error, context, result);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Record stats
|
|
327
|
+
if (this.options.enableMetrics) {
|
|
328
|
+
this.registry.recordExecution(skillId, false, result.duration);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
this.emit('execution-failed', {
|
|
332
|
+
executionId: context.executionId,
|
|
333
|
+
skillId,
|
|
334
|
+
error: error.message
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
} finally {
|
|
338
|
+
this._concurrentCount--;
|
|
339
|
+
this.activeExecutions.delete(context.executionId);
|
|
340
|
+
this._addToHistory(result);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Execute multiple skills in parallel
|
|
348
|
+
*/
|
|
349
|
+
async executeParallel(tasks, options = {}) {
|
|
350
|
+
// Sort by priority
|
|
351
|
+
const sortedTasks = [...tasks].sort((a, b) => {
|
|
352
|
+
const priorityOrder = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
353
|
+
return (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Group by priority
|
|
357
|
+
const priorityGroups = new Map();
|
|
358
|
+
for (const task of sortedTasks) {
|
|
359
|
+
const priority = task.priority || 'P2';
|
|
360
|
+
if (!priorityGroups.has(priority)) {
|
|
361
|
+
priorityGroups.set(priority, []);
|
|
362
|
+
}
|
|
363
|
+
priorityGroups.get(priority).push(task);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const results = new Map();
|
|
367
|
+
|
|
368
|
+
// Execute P0 first (blocking)
|
|
369
|
+
if (priorityGroups.has('P0')) {
|
|
370
|
+
for (const task of priorityGroups.get('P0')) {
|
|
371
|
+
const result = await this.execute(task.skillId, task.input, task.options);
|
|
372
|
+
results.set(task.skillId, result);
|
|
373
|
+
|
|
374
|
+
// P0 failure stops everything
|
|
375
|
+
if (!result.success && options.failFast !== false) {
|
|
376
|
+
return { results: Object.fromEntries(results), partial: true };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Execute P1-P3 in parallel within each priority level
|
|
382
|
+
for (const priority of ['P1', 'P2', 'P3']) {
|
|
383
|
+
if (priorityGroups.has(priority)) {
|
|
384
|
+
const groupTasks = priorityGroups.get(priority);
|
|
385
|
+
const groupResults = await Promise.allSettled(
|
|
386
|
+
groupTasks.map(task =>
|
|
387
|
+
this.execute(task.skillId, task.input, task.options)
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
groupTasks.forEach((task, index) => {
|
|
392
|
+
const settled = groupResults[index];
|
|
393
|
+
if (settled.status === 'fulfilled') {
|
|
394
|
+
results.set(task.skillId, settled.value);
|
|
395
|
+
} else {
|
|
396
|
+
results.set(task.skillId, new ExecutionResult({
|
|
397
|
+
skillId: task.skillId,
|
|
398
|
+
status: ExecutionStatus.FAILED,
|
|
399
|
+
error: settled.reason?.message || 'Unknown error'
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
results: Object.fromEntries(results),
|
|
408
|
+
partial: false,
|
|
409
|
+
summary: this._summarizeResults(results)
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Execute skills sequentially
|
|
415
|
+
*/
|
|
416
|
+
async executeSequential(tasks, options = {}) {
|
|
417
|
+
const results = [];
|
|
418
|
+
|
|
419
|
+
for (const task of tasks) {
|
|
420
|
+
const result = await this.execute(task.skillId, task.input, task.options);
|
|
421
|
+
results.push(result);
|
|
422
|
+
|
|
423
|
+
if (!result.success && options.stopOnError !== false) {
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
results,
|
|
430
|
+
completed: results.length === tasks.length,
|
|
431
|
+
summary: this._summarizeResults(new Map(results.map(r => [r.skillId, r])))
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Execute with dependency resolution
|
|
437
|
+
*/
|
|
438
|
+
async executeWithDependencies(skillId, input = {}, options = {}) {
|
|
439
|
+
const order = this.registry.resolveDependencies(skillId);
|
|
440
|
+
const results = new Map();
|
|
441
|
+
|
|
442
|
+
for (const depSkillId of order) {
|
|
443
|
+
// Use output from dependencies as input
|
|
444
|
+
const depInput = depSkillId === skillId
|
|
445
|
+
? input
|
|
446
|
+
: this._mergeInputFromResults(results, depSkillId);
|
|
447
|
+
|
|
448
|
+
const result = await this.execute(depSkillId, depInput, options);
|
|
449
|
+
results.set(depSkillId, result);
|
|
450
|
+
|
|
451
|
+
if (!result.success) {
|
|
452
|
+
return {
|
|
453
|
+
results: Object.fromEntries(results),
|
|
454
|
+
failed: true,
|
|
455
|
+
failedAt: depSkillId
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
results: Object.fromEntries(results),
|
|
462
|
+
failed: false,
|
|
463
|
+
finalResult: results.get(skillId)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Cancel an execution
|
|
469
|
+
*/
|
|
470
|
+
cancel(executionId) {
|
|
471
|
+
const execution = this.activeExecutions.get(executionId);
|
|
472
|
+
if (execution) {
|
|
473
|
+
execution.cancelled = true;
|
|
474
|
+
this.emit('execution-cancelled', { executionId });
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Cancel all active executions
|
|
482
|
+
*/
|
|
483
|
+
cancelAll() {
|
|
484
|
+
for (const [executionId] of this.activeExecutions) {
|
|
485
|
+
this.cancel(executionId);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Get active execution count
|
|
491
|
+
*/
|
|
492
|
+
getActiveCount() {
|
|
493
|
+
return this.activeExecutions.size;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Get execution history
|
|
498
|
+
*/
|
|
499
|
+
getHistory(limit = 100) {
|
|
500
|
+
return this.executionHistory.slice(-limit);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Add guardrail
|
|
505
|
+
*/
|
|
506
|
+
addGuardrail(guardrail) {
|
|
507
|
+
this.guardrails.push(guardrail);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Add hook
|
|
512
|
+
*/
|
|
513
|
+
addHook(event, handler) {
|
|
514
|
+
if (this.hooks[event]) {
|
|
515
|
+
this.hooks[event].push(handler);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Private methods
|
|
520
|
+
|
|
521
|
+
async _executeWithRetry(handler, context, retryPolicy, timeout, result) {
|
|
522
|
+
const maxRetries = retryPolicy?.maxRetries || 0;
|
|
523
|
+
const backoffMs = retryPolicy?.backoffMs || 1000;
|
|
524
|
+
const backoffMultiplier = retryPolicy?.backoffMultiplier || 2;
|
|
525
|
+
|
|
526
|
+
let lastError;
|
|
527
|
+
let currentBackoff = backoffMs;
|
|
528
|
+
|
|
529
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
530
|
+
result.attempts = attempt + 1;
|
|
531
|
+
|
|
532
|
+
// Check if cancelled
|
|
533
|
+
const execution = this.activeExecutions.get(context.executionId);
|
|
534
|
+
if (execution?.cancelled) {
|
|
535
|
+
throw new Error('Execution cancelled');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
// Execute with timeout
|
|
540
|
+
const output = await this._executeWithTimeout(handler, context, timeout);
|
|
541
|
+
return output;
|
|
542
|
+
} catch (error) {
|
|
543
|
+
lastError = error;
|
|
544
|
+
|
|
545
|
+
// Check if retryable
|
|
546
|
+
if (!this._isRetryable(error)) {
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (attempt < maxRetries) {
|
|
551
|
+
this.emit('execution-retry', {
|
|
552
|
+
executionId: context.executionId,
|
|
553
|
+
skillId: context.skillId,
|
|
554
|
+
attempt: attempt + 1,
|
|
555
|
+
maxRetries,
|
|
556
|
+
nextRetryMs: currentBackoff
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
await this._sleep(currentBackoff);
|
|
560
|
+
currentBackoff *= backoffMultiplier;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
throw lastError;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async _executeWithTimeout(handler, context, timeout) {
|
|
569
|
+
return new Promise((resolve, reject) => {
|
|
570
|
+
const timeoutId = setTimeout(() => {
|
|
571
|
+
reject(new Error(`Execution timeout after ${timeout}ms`));
|
|
572
|
+
}, timeout);
|
|
573
|
+
|
|
574
|
+
Promise.resolve(handler(context.input, context))
|
|
575
|
+
.then(result => {
|
|
576
|
+
clearTimeout(timeoutId);
|
|
577
|
+
resolve(result);
|
|
578
|
+
})
|
|
579
|
+
.catch(error => {
|
|
580
|
+
clearTimeout(timeoutId);
|
|
581
|
+
reject(error);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async _runGuardrails(phase, data) {
|
|
587
|
+
for (const guardrail of this.guardrails) {
|
|
588
|
+
if (guardrail.phase === phase || guardrail.phase === 'both') {
|
|
589
|
+
const result = await guardrail.check(data);
|
|
590
|
+
if (!result.passed) {
|
|
591
|
+
throw new Error(`Guardrail '${guardrail.name}' failed: ${result.reason}`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
_isRetryable(error) {
|
|
598
|
+
const nonRetryable = [
|
|
599
|
+
'Execution cancelled',
|
|
600
|
+
'Input validation failed',
|
|
601
|
+
'Output validation failed',
|
|
602
|
+
'Guardrail'
|
|
603
|
+
];
|
|
604
|
+
return !nonRetryable.some(msg => error.message.includes(msg));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
async _waitForSlot() {
|
|
608
|
+
while (this._concurrentCount >= this.options.maxConcurrent) {
|
|
609
|
+
await this._sleep(100);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
_sleep(ms) {
|
|
614
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
_addToHistory(result) {
|
|
618
|
+
this.executionHistory.push(result);
|
|
619
|
+
if (this.executionHistory.length > this.maxHistorySize) {
|
|
620
|
+
this.executionHistory.shift();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
_summarizeResults(resultsMap) {
|
|
625
|
+
let success = 0;
|
|
626
|
+
let failed = 0;
|
|
627
|
+
let totalDuration = 0;
|
|
628
|
+
|
|
629
|
+
for (const result of resultsMap.values()) {
|
|
630
|
+
if (result.success) {
|
|
631
|
+
success++;
|
|
632
|
+
} else {
|
|
633
|
+
failed++;
|
|
634
|
+
}
|
|
635
|
+
totalDuration += result.duration || 0;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
total: resultsMap.size,
|
|
640
|
+
success,
|
|
641
|
+
failed,
|
|
642
|
+
totalDuration,
|
|
643
|
+
averageDuration: resultsMap.size > 0 ? totalDuration / resultsMap.size : 0
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
_mergeInputFromResults(results, skillId) {
|
|
648
|
+
// Simple merge - could be extended with explicit mappings
|
|
649
|
+
const merged = {};
|
|
650
|
+
for (const result of results.values()) {
|
|
651
|
+
if (result.output && typeof result.output === 'object') {
|
|
652
|
+
Object.assign(merged, result.output);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return merged;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
module.exports = {
|
|
660
|
+
SkillExecutor,
|
|
661
|
+
ExecutionResult,
|
|
662
|
+
ExecutionContext,
|
|
663
|
+
ExecutionStatus,
|
|
664
|
+
IOValidator
|
|
665
|
+
};
|