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,831 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file planning-engine.js
|
|
3
|
+
* @description Agentic planning engine for task decomposition and execution planning
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { EventEmitter } = require('events');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Plan status types
|
|
13
|
+
* @enum {string}
|
|
14
|
+
*/
|
|
15
|
+
const PLAN_STATUS = {
|
|
16
|
+
DRAFT: 'draft',
|
|
17
|
+
READY: 'ready',
|
|
18
|
+
EXECUTING: 'executing',
|
|
19
|
+
PAUSED: 'paused',
|
|
20
|
+
COMPLETED: 'completed',
|
|
21
|
+
FAILED: 'failed',
|
|
22
|
+
CANCELLED: 'cancelled'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Task status types
|
|
27
|
+
* @enum {string}
|
|
28
|
+
*/
|
|
29
|
+
const TASK_STATUS = {
|
|
30
|
+
PENDING: 'pending',
|
|
31
|
+
READY: 'ready',
|
|
32
|
+
IN_PROGRESS: 'in-progress',
|
|
33
|
+
BLOCKED: 'blocked',
|
|
34
|
+
COMPLETED: 'completed',
|
|
35
|
+
FAILED: 'failed',
|
|
36
|
+
SKIPPED: 'skipped'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Task priority levels
|
|
41
|
+
* @enum {number}
|
|
42
|
+
*/
|
|
43
|
+
const PRIORITY = {
|
|
44
|
+
CRITICAL: 0,
|
|
45
|
+
HIGH: 1,
|
|
46
|
+
MEDIUM: 2,
|
|
47
|
+
LOW: 3,
|
|
48
|
+
OPTIONAL: 4
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {Object} Task
|
|
53
|
+
* @property {string} id - Task identifier
|
|
54
|
+
* @property {string} name - Task name
|
|
55
|
+
* @property {string} description - Task description
|
|
56
|
+
* @property {string} status - Task status
|
|
57
|
+
* @property {number} priority - Priority level
|
|
58
|
+
* @property {string[]} dependencies - Task IDs this depends on
|
|
59
|
+
* @property {string[]} outputs - Expected outputs
|
|
60
|
+
* @property {number} estimatedTime - Estimated time in ms
|
|
61
|
+
* @property {number} [actualTime] - Actual time taken
|
|
62
|
+
* @property {Object} [result] - Task result
|
|
63
|
+
* @property {Object} [metadata] - Additional metadata
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} Plan
|
|
68
|
+
* @property {string} id - Plan identifier
|
|
69
|
+
* @property {string} goal - High-level goal
|
|
70
|
+
* @property {string} status - Plan status
|
|
71
|
+
* @property {Task[]} tasks - Ordered list of tasks
|
|
72
|
+
* @property {Object} context - Planning context
|
|
73
|
+
* @property {number} createdAt - Creation timestamp
|
|
74
|
+
* @property {number} [startedAt] - Execution start timestamp
|
|
75
|
+
* @property {number} [completedAt] - Completion timestamp
|
|
76
|
+
* @property {Object} metrics - Execution metrics
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @typedef {Object} PlanningOptions
|
|
81
|
+
* @property {number} [maxTasks=50] - Maximum tasks in a plan
|
|
82
|
+
* @property {boolean} [parallelExecution=true] - Allow parallel task execution
|
|
83
|
+
* @property {number} [maxParallel=4] - Maximum parallel tasks
|
|
84
|
+
* @property {boolean} [adaptivePlanning=true] - Enable adaptive replanning
|
|
85
|
+
* @property {number} [replanThreshold=0.3] - Threshold for triggering replan
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Planning Engine class for task decomposition and execution planning
|
|
90
|
+
* @extends EventEmitter
|
|
91
|
+
*/
|
|
92
|
+
class PlanningEngine extends EventEmitter {
|
|
93
|
+
/**
|
|
94
|
+
* Create planning engine
|
|
95
|
+
* @param {PlanningOptions} [options={}] - Engine options
|
|
96
|
+
*/
|
|
97
|
+
constructor(options = {}) {
|
|
98
|
+
super();
|
|
99
|
+
|
|
100
|
+
this.maxTasks = options.maxTasks ?? 50;
|
|
101
|
+
this.parallelExecution = options.parallelExecution ?? true;
|
|
102
|
+
this.maxParallel = options.maxParallel ?? 4;
|
|
103
|
+
this.adaptivePlanning = options.adaptivePlanning ?? true;
|
|
104
|
+
this.replanThreshold = options.replanThreshold ?? 0.3;
|
|
105
|
+
|
|
106
|
+
// State
|
|
107
|
+
this.plans = new Map();
|
|
108
|
+
this.activePlan = null;
|
|
109
|
+
this.taskCounter = 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create a plan from a goal
|
|
114
|
+
* @param {string} goal - High-level goal description
|
|
115
|
+
* @param {Object} [context={}] - Additional context
|
|
116
|
+
* @returns {Promise<Plan>}
|
|
117
|
+
*/
|
|
118
|
+
async createPlan(goal, context = {}) {
|
|
119
|
+
const planId = this.generateId('plan');
|
|
120
|
+
|
|
121
|
+
const plan = {
|
|
122
|
+
id: planId,
|
|
123
|
+
goal,
|
|
124
|
+
status: PLAN_STATUS.DRAFT,
|
|
125
|
+
tasks: [],
|
|
126
|
+
context,
|
|
127
|
+
createdAt: Date.now(),
|
|
128
|
+
startedAt: null,
|
|
129
|
+
completedAt: null,
|
|
130
|
+
metrics: {
|
|
131
|
+
totalTasks: 0,
|
|
132
|
+
completedTasks: 0,
|
|
133
|
+
failedTasks: 0,
|
|
134
|
+
skippedTasks: 0,
|
|
135
|
+
totalEstimatedTime: 0,
|
|
136
|
+
actualTime: 0
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
this.emit('plan:creating', { planId, goal });
|
|
141
|
+
|
|
142
|
+
// Decompose goal into tasks
|
|
143
|
+
const tasks = await this.decomposeGoal(goal, context);
|
|
144
|
+
plan.tasks = tasks;
|
|
145
|
+
plan.metrics.totalTasks = tasks.length;
|
|
146
|
+
plan.metrics.totalEstimatedTime = tasks.reduce((sum, t) => sum + t.estimatedTime, 0);
|
|
147
|
+
|
|
148
|
+
// Optimize task order
|
|
149
|
+
this.optimizeTaskOrder(plan);
|
|
150
|
+
|
|
151
|
+
// Update status
|
|
152
|
+
plan.status = PLAN_STATUS.READY;
|
|
153
|
+
|
|
154
|
+
this.plans.set(planId, plan);
|
|
155
|
+
this.emit('plan:created', { plan });
|
|
156
|
+
|
|
157
|
+
return plan;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Decompose goal into tasks
|
|
162
|
+
* @private
|
|
163
|
+
*/
|
|
164
|
+
async decomposeGoal(goal, context) {
|
|
165
|
+
const tasks = [];
|
|
166
|
+
|
|
167
|
+
// Analyze goal for key components
|
|
168
|
+
const components = this.analyzeGoal(goal);
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < components.length; i++) {
|
|
171
|
+
const component = components[i];
|
|
172
|
+
|
|
173
|
+
const task = {
|
|
174
|
+
id: this.generateId('task'),
|
|
175
|
+
name: component.name,
|
|
176
|
+
description: component.description,
|
|
177
|
+
status: TASK_STATUS.PENDING,
|
|
178
|
+
priority: component.priority ?? PRIORITY.MEDIUM,
|
|
179
|
+
dependencies: [],
|
|
180
|
+
outputs: component.outputs || [],
|
|
181
|
+
estimatedTime: component.estimatedTime || 5000,
|
|
182
|
+
actualTime: null,
|
|
183
|
+
result: null,
|
|
184
|
+
metadata: {
|
|
185
|
+
component: component.type,
|
|
186
|
+
index: i
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Add dependencies from previous tasks if sequential
|
|
191
|
+
if (i > 0 && component.dependsOnPrevious !== false) {
|
|
192
|
+
task.dependencies.push(tasks[i - 1].id);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
tasks.push(task);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return tasks;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Analyze goal to extract components
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
analyzeGoal(goal) {
|
|
206
|
+
const components = [];
|
|
207
|
+
const goalLower = goal.toLowerCase();
|
|
208
|
+
|
|
209
|
+
// Pattern matching for common goal structures
|
|
210
|
+
const patterns = [
|
|
211
|
+
{ keyword: 'create', type: 'creation', priority: PRIORITY.HIGH },
|
|
212
|
+
{ keyword: 'build', type: 'construction', priority: PRIORITY.HIGH },
|
|
213
|
+
{ keyword: 'implement', type: 'implementation', priority: PRIORITY.HIGH },
|
|
214
|
+
{ keyword: 'fix', type: 'bugfix', priority: PRIORITY.CRITICAL },
|
|
215
|
+
{ keyword: 'test', type: 'testing', priority: PRIORITY.MEDIUM },
|
|
216
|
+
{ keyword: 'deploy', type: 'deployment', priority: PRIORITY.HIGH },
|
|
217
|
+
{ keyword: 'refactor', type: 'refactoring', priority: PRIORITY.MEDIUM },
|
|
218
|
+
{ keyword: 'document', type: 'documentation', priority: PRIORITY.LOW },
|
|
219
|
+
{ keyword: 'analyze', type: 'analysis', priority: PRIORITY.MEDIUM },
|
|
220
|
+
{ keyword: 'review', type: 'review', priority: PRIORITY.MEDIUM }
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// Match patterns
|
|
224
|
+
for (const pattern of patterns) {
|
|
225
|
+
if (goalLower.includes(pattern.keyword)) {
|
|
226
|
+
components.push({
|
|
227
|
+
name: `${pattern.keyword.charAt(0).toUpperCase() + pattern.keyword.slice(1)} phase`,
|
|
228
|
+
description: `${pattern.type} task for: ${goal.substring(0, 50)}`,
|
|
229
|
+
type: pattern.type,
|
|
230
|
+
priority: pattern.priority,
|
|
231
|
+
estimatedTime: this.estimateTime(pattern.type)
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// If no patterns matched, create generic decomposition
|
|
237
|
+
if (components.length === 0) {
|
|
238
|
+
components.push(
|
|
239
|
+
{
|
|
240
|
+
name: 'Analysis',
|
|
241
|
+
description: 'Analyze requirements and context',
|
|
242
|
+
type: 'analysis',
|
|
243
|
+
priority: PRIORITY.HIGH,
|
|
244
|
+
estimatedTime: 3000
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: 'Planning',
|
|
248
|
+
description: 'Create detailed implementation plan',
|
|
249
|
+
type: 'planning',
|
|
250
|
+
priority: PRIORITY.HIGH,
|
|
251
|
+
estimatedTime: 2000
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'Execution',
|
|
255
|
+
description: 'Execute the main task',
|
|
256
|
+
type: 'execution',
|
|
257
|
+
priority: PRIORITY.HIGH,
|
|
258
|
+
estimatedTime: 10000
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'Validation',
|
|
262
|
+
description: 'Validate results and quality',
|
|
263
|
+
type: 'validation',
|
|
264
|
+
priority: PRIORITY.MEDIUM,
|
|
265
|
+
estimatedTime: 3000
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return components;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Estimate time for task type
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
estimateTime(type) {
|
|
278
|
+
const estimates = {
|
|
279
|
+
creation: 15000,
|
|
280
|
+
construction: 20000,
|
|
281
|
+
implementation: 25000,
|
|
282
|
+
bugfix: 10000,
|
|
283
|
+
testing: 8000,
|
|
284
|
+
deployment: 12000,
|
|
285
|
+
refactoring: 15000,
|
|
286
|
+
documentation: 5000,
|
|
287
|
+
analysis: 5000,
|
|
288
|
+
review: 6000
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
return estimates[type] || 10000;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Optimize task order based on dependencies and priorities
|
|
296
|
+
* @private
|
|
297
|
+
*/
|
|
298
|
+
optimizeTaskOrder(plan) {
|
|
299
|
+
// Topological sort with priority consideration
|
|
300
|
+
const sorted = [];
|
|
301
|
+
const visited = new Set();
|
|
302
|
+
const visiting = new Set();
|
|
303
|
+
|
|
304
|
+
const visit = (taskId) => {
|
|
305
|
+
if (visited.has(taskId)) return;
|
|
306
|
+
if (visiting.has(taskId)) {
|
|
307
|
+
throw new Error(`Circular dependency detected for task ${taskId}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
visiting.add(taskId);
|
|
311
|
+
|
|
312
|
+
const task = plan.tasks.find(t => t.id === taskId);
|
|
313
|
+
if (task) {
|
|
314
|
+
for (const depId of task.dependencies) {
|
|
315
|
+
visit(depId);
|
|
316
|
+
}
|
|
317
|
+
visited.add(taskId);
|
|
318
|
+
sorted.push(task);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
visiting.delete(taskId);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// Sort by priority first, then visit
|
|
325
|
+
const byPriority = [...plan.tasks].sort((a, b) => a.priority - b.priority);
|
|
326
|
+
for (const task of byPriority) {
|
|
327
|
+
visit(task.id);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
plan.tasks = sorted;
|
|
331
|
+
|
|
332
|
+
// Update ready status for tasks with no dependencies
|
|
333
|
+
for (const task of plan.tasks) {
|
|
334
|
+
if (task.dependencies.length === 0) {
|
|
335
|
+
task.status = TASK_STATUS.READY;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Execute a plan
|
|
342
|
+
* @param {string} planId - Plan identifier
|
|
343
|
+
* @param {Function} executor - Task executor function (task) => Promise<result>
|
|
344
|
+
* @returns {Promise<Plan>}
|
|
345
|
+
*/
|
|
346
|
+
async executePlan(planId, executor) {
|
|
347
|
+
const plan = this.plans.get(planId);
|
|
348
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
349
|
+
if (plan.status === PLAN_STATUS.EXECUTING) {
|
|
350
|
+
throw new Error('Plan is already executing');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
plan.status = PLAN_STATUS.EXECUTING;
|
|
354
|
+
plan.startedAt = Date.now();
|
|
355
|
+
this.activePlan = plan;
|
|
356
|
+
|
|
357
|
+
this.emit('plan:executing', { planId });
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
if (this.parallelExecution) {
|
|
361
|
+
await this.executeParallel(plan, executor);
|
|
362
|
+
} else {
|
|
363
|
+
await this.executeSequential(plan, executor);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Determine final status
|
|
367
|
+
const hasFailures = plan.tasks.some(t => t.status === TASK_STATUS.FAILED);
|
|
368
|
+
plan.status = hasFailures ? PLAN_STATUS.FAILED : PLAN_STATUS.COMPLETED;
|
|
369
|
+
|
|
370
|
+
} catch (error) {
|
|
371
|
+
plan.status = PLAN_STATUS.FAILED;
|
|
372
|
+
this.emit('plan:error', { planId, error: error.message });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
plan.completedAt = Date.now();
|
|
376
|
+
plan.metrics.actualTime = plan.completedAt - plan.startedAt;
|
|
377
|
+
this.activePlan = null;
|
|
378
|
+
|
|
379
|
+
this.emit('plan:completed', { plan });
|
|
380
|
+
|
|
381
|
+
return plan;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Execute tasks sequentially
|
|
386
|
+
* @private
|
|
387
|
+
*/
|
|
388
|
+
async executeSequential(plan, executor) {
|
|
389
|
+
for (const task of plan.tasks) {
|
|
390
|
+
if (task.status === TASK_STATUS.SKIPPED) continue;
|
|
391
|
+
|
|
392
|
+
// Check dependencies
|
|
393
|
+
const blocked = task.dependencies.some(depId => {
|
|
394
|
+
const dep = plan.tasks.find(t => t.id === depId);
|
|
395
|
+
return dep && dep.status === TASK_STATUS.FAILED;
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
if (blocked) {
|
|
399
|
+
task.status = TASK_STATUS.SKIPPED;
|
|
400
|
+
plan.metrics.skippedTasks++;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
await this.executeTask(task, executor, plan);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Execute tasks in parallel where possible
|
|
410
|
+
* @private
|
|
411
|
+
*/
|
|
412
|
+
async executeParallel(plan, executor) {
|
|
413
|
+
const completed = new Set();
|
|
414
|
+
|
|
415
|
+
while (true) {
|
|
416
|
+
// Find tasks that are ready to execute
|
|
417
|
+
const ready = plan.tasks.filter(task => {
|
|
418
|
+
if (task.status !== TASK_STATUS.READY && task.status !== TASK_STATUS.PENDING) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
// Check all dependencies are completed
|
|
422
|
+
return task.dependencies.every(depId => completed.has(depId));
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
if (ready.length === 0) {
|
|
426
|
+
// No more tasks ready - check if we're done
|
|
427
|
+
const remaining = plan.tasks.filter(t =>
|
|
428
|
+
t.status === TASK_STATUS.PENDING ||
|
|
429
|
+
t.status === TASK_STATUS.READY ||
|
|
430
|
+
t.status === TASK_STATUS.IN_PROGRESS
|
|
431
|
+
);
|
|
432
|
+
if (remaining.length === 0) break;
|
|
433
|
+
|
|
434
|
+
// Check for blocked tasks (dependencies failed)
|
|
435
|
+
for (const task of remaining) {
|
|
436
|
+
const hasFailed = task.dependencies.some(depId => {
|
|
437
|
+
const dep = plan.tasks.find(t => t.id === depId);
|
|
438
|
+
return dep && dep.status === TASK_STATUS.FAILED;
|
|
439
|
+
});
|
|
440
|
+
if (hasFailed) {
|
|
441
|
+
task.status = TASK_STATUS.SKIPPED;
|
|
442
|
+
plan.metrics.skippedTasks++;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Execute batch of ready tasks
|
|
449
|
+
const batch = ready.slice(0, this.maxParallel);
|
|
450
|
+
await Promise.all(batch.map(async (task) => {
|
|
451
|
+
await this.executeTask(task, executor, plan);
|
|
452
|
+
completed.add(task.id);
|
|
453
|
+
}));
|
|
454
|
+
|
|
455
|
+
// Mark newly ready tasks
|
|
456
|
+
for (const task of plan.tasks) {
|
|
457
|
+
if (task.status === TASK_STATUS.PENDING) {
|
|
458
|
+
const allDepsComplete = task.dependencies.every(depId => completed.has(depId));
|
|
459
|
+
if (allDepsComplete) {
|
|
460
|
+
task.status = TASK_STATUS.READY;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Execute a single task
|
|
469
|
+
* @private
|
|
470
|
+
*/
|
|
471
|
+
async executeTask(task, executor, plan) {
|
|
472
|
+
task.status = TASK_STATUS.IN_PROGRESS;
|
|
473
|
+
const startTime = Date.now();
|
|
474
|
+
|
|
475
|
+
this.emit('task:start', { taskId: task.id, planId: plan.id, task });
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const result = await executor(task);
|
|
479
|
+
task.result = result;
|
|
480
|
+
task.status = TASK_STATUS.COMPLETED;
|
|
481
|
+
plan.metrics.completedTasks++;
|
|
482
|
+
|
|
483
|
+
this.emit('task:complete', { taskId: task.id, planId: plan.id, result });
|
|
484
|
+
|
|
485
|
+
// Check for replanning
|
|
486
|
+
if (this.adaptivePlanning) {
|
|
487
|
+
await this.checkReplan(plan, task);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
} catch (error) {
|
|
491
|
+
task.result = { error: error.message };
|
|
492
|
+
task.status = TASK_STATUS.FAILED;
|
|
493
|
+
plan.metrics.failedTasks++;
|
|
494
|
+
|
|
495
|
+
this.emit('task:error', { taskId: task.id, planId: plan.id, error: error.message });
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
task.actualTime = Date.now() - startTime;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Check if replanning is needed
|
|
503
|
+
* @private
|
|
504
|
+
*/
|
|
505
|
+
async checkReplan(plan, completedTask) {
|
|
506
|
+
// Calculate deviation from estimates
|
|
507
|
+
if (!completedTask.actualTime || !completedTask.estimatedTime) return;
|
|
508
|
+
|
|
509
|
+
const deviation = Math.abs(completedTask.actualTime - completedTask.estimatedTime) / completedTask.estimatedTime;
|
|
510
|
+
|
|
511
|
+
if (deviation > this.replanThreshold) {
|
|
512
|
+
this.emit('plan:replan-trigger', {
|
|
513
|
+
planId: plan.id,
|
|
514
|
+
taskId: completedTask.id,
|
|
515
|
+
deviation
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Adjust estimates for remaining tasks
|
|
519
|
+
const remaining = plan.tasks.filter(t =>
|
|
520
|
+
t.status === TASK_STATUS.PENDING || t.status === TASK_STATUS.READY
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
for (const task of remaining) {
|
|
524
|
+
if (task.metadata.component === completedTask.metadata.component) {
|
|
525
|
+
// Adjust based on actual vs estimated ratio
|
|
526
|
+
const ratio = completedTask.actualTime / completedTask.estimatedTime;
|
|
527
|
+
task.estimatedTime = Math.round(task.estimatedTime * ratio);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Pause plan execution
|
|
535
|
+
* @param {string} planId - Plan identifier
|
|
536
|
+
*/
|
|
537
|
+
pausePlan(planId) {
|
|
538
|
+
const plan = this.plans.get(planId);
|
|
539
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
540
|
+
|
|
541
|
+
if (plan.status === PLAN_STATUS.EXECUTING) {
|
|
542
|
+
plan.status = PLAN_STATUS.PAUSED;
|
|
543
|
+
this.emit('plan:paused', { planId });
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Resume plan execution
|
|
549
|
+
* @param {string} planId - Plan identifier
|
|
550
|
+
* @param {Function} executor - Task executor function
|
|
551
|
+
* @returns {Promise<Plan>}
|
|
552
|
+
*/
|
|
553
|
+
async resumePlan(planId, executor) {
|
|
554
|
+
const plan = this.plans.get(planId);
|
|
555
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
556
|
+
|
|
557
|
+
if (plan.status !== PLAN_STATUS.PAUSED) {
|
|
558
|
+
throw new Error('Plan is not paused');
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
this.emit('plan:resuming', { planId });
|
|
562
|
+
return this.executePlan(planId, executor);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Cancel plan execution
|
|
567
|
+
* @param {string} planId - Plan identifier
|
|
568
|
+
*/
|
|
569
|
+
cancelPlan(planId) {
|
|
570
|
+
const plan = this.plans.get(planId);
|
|
571
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
572
|
+
|
|
573
|
+
plan.status = PLAN_STATUS.CANCELLED;
|
|
574
|
+
plan.completedAt = Date.now();
|
|
575
|
+
|
|
576
|
+
// Mark pending tasks as skipped
|
|
577
|
+
for (const task of plan.tasks) {
|
|
578
|
+
if (task.status === TASK_STATUS.PENDING || task.status === TASK_STATUS.READY) {
|
|
579
|
+
task.status = TASK_STATUS.SKIPPED;
|
|
580
|
+
plan.metrics.skippedTasks++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
this.emit('plan:cancelled', { planId });
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Add task to existing plan
|
|
589
|
+
* @param {string} planId - Plan identifier
|
|
590
|
+
* @param {Object} taskDef - Task definition
|
|
591
|
+
* @returns {Task}
|
|
592
|
+
*/
|
|
593
|
+
addTask(planId, taskDef) {
|
|
594
|
+
const plan = this.plans.get(planId);
|
|
595
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
596
|
+
|
|
597
|
+
const task = {
|
|
598
|
+
id: this.generateId('task'),
|
|
599
|
+
name: taskDef.name,
|
|
600
|
+
description: taskDef.description || '',
|
|
601
|
+
status: TASK_STATUS.PENDING,
|
|
602
|
+
priority: taskDef.priority ?? PRIORITY.MEDIUM,
|
|
603
|
+
dependencies: taskDef.dependencies || [],
|
|
604
|
+
outputs: taskDef.outputs || [],
|
|
605
|
+
estimatedTime: taskDef.estimatedTime || 5000,
|
|
606
|
+
actualTime: null,
|
|
607
|
+
result: null,
|
|
608
|
+
metadata: taskDef.metadata || {}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// Insert at appropriate position based on dependencies
|
|
612
|
+
if (taskDef.after) {
|
|
613
|
+
const afterIndex = plan.tasks.findIndex(t => t.id === taskDef.after);
|
|
614
|
+
if (afterIndex >= 0) {
|
|
615
|
+
task.dependencies.push(taskDef.after);
|
|
616
|
+
plan.tasks.splice(afterIndex + 1, 0, task);
|
|
617
|
+
} else {
|
|
618
|
+
plan.tasks.push(task);
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
plan.tasks.push(task);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
plan.metrics.totalTasks++;
|
|
625
|
+
plan.metrics.totalEstimatedTime += task.estimatedTime;
|
|
626
|
+
|
|
627
|
+
this.emit('task:added', { planId, task });
|
|
628
|
+
|
|
629
|
+
return task;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Remove task from plan
|
|
634
|
+
* @param {string} planId - Plan identifier
|
|
635
|
+
* @param {string} taskId - Task identifier
|
|
636
|
+
*/
|
|
637
|
+
removeTask(planId, taskId) {
|
|
638
|
+
const plan = this.plans.get(planId);
|
|
639
|
+
if (!plan) throw new Error(`Plan ${planId} not found`);
|
|
640
|
+
|
|
641
|
+
const index = plan.tasks.findIndex(t => t.id === taskId);
|
|
642
|
+
if (index < 0) throw new Error(`Task ${taskId} not found`);
|
|
643
|
+
|
|
644
|
+
const task = plan.tasks[index];
|
|
645
|
+
|
|
646
|
+
// Remove from other tasks' dependencies
|
|
647
|
+
for (const t of plan.tasks) {
|
|
648
|
+
t.dependencies = t.dependencies.filter(d => d !== taskId);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
plan.tasks.splice(index, 1);
|
|
652
|
+
plan.metrics.totalTasks--;
|
|
653
|
+
plan.metrics.totalEstimatedTime -= task.estimatedTime;
|
|
654
|
+
|
|
655
|
+
this.emit('task:removed', { planId, taskId });
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Get plan by ID
|
|
660
|
+
* @param {string} planId - Plan identifier
|
|
661
|
+
* @returns {Plan|null}
|
|
662
|
+
*/
|
|
663
|
+
getPlan(planId) {
|
|
664
|
+
return this.plans.get(planId) || null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Get all plans
|
|
669
|
+
* @returns {Plan[]}
|
|
670
|
+
*/
|
|
671
|
+
getAllPlans() {
|
|
672
|
+
return Array.from(this.plans.values());
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Get plan progress
|
|
677
|
+
* @param {string} planId - Plan identifier
|
|
678
|
+
* @returns {Object}
|
|
679
|
+
*/
|
|
680
|
+
getProgress(planId) {
|
|
681
|
+
const plan = this.plans.get(planId);
|
|
682
|
+
if (!plan) return null;
|
|
683
|
+
|
|
684
|
+
const total = plan.metrics.totalTasks;
|
|
685
|
+
const completed = plan.metrics.completedTasks;
|
|
686
|
+
const failed = plan.metrics.failedTasks;
|
|
687
|
+
const skipped = plan.metrics.skippedTasks;
|
|
688
|
+
const inProgress = plan.tasks.filter(t => t.status === TASK_STATUS.IN_PROGRESS).length;
|
|
689
|
+
const pending = total - completed - failed - skipped - inProgress;
|
|
690
|
+
|
|
691
|
+
return {
|
|
692
|
+
total,
|
|
693
|
+
completed,
|
|
694
|
+
failed,
|
|
695
|
+
skipped,
|
|
696
|
+
inProgress,
|
|
697
|
+
pending,
|
|
698
|
+
percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
|
|
699
|
+
estimatedRemaining: plan.tasks
|
|
700
|
+
.filter(t => t.status === TASK_STATUS.PENDING || t.status === TASK_STATUS.READY)
|
|
701
|
+
.reduce((sum, t) => sum + t.estimatedTime, 0)
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Get dependency graph
|
|
707
|
+
* @param {string} planId - Plan identifier
|
|
708
|
+
* @returns {Object}
|
|
709
|
+
*/
|
|
710
|
+
getDependencyGraph(planId) {
|
|
711
|
+
const plan = this.plans.get(planId);
|
|
712
|
+
if (!plan) return null;
|
|
713
|
+
|
|
714
|
+
const nodes = plan.tasks.map(t => ({
|
|
715
|
+
id: t.id,
|
|
716
|
+
name: t.name,
|
|
717
|
+
status: t.status,
|
|
718
|
+
priority: t.priority
|
|
719
|
+
}));
|
|
720
|
+
|
|
721
|
+
const edges = [];
|
|
722
|
+
for (const task of plan.tasks) {
|
|
723
|
+
for (const dep of task.dependencies) {
|
|
724
|
+
edges.push({ from: dep, to: task.id });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return { nodes, edges };
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Generate unique ID
|
|
733
|
+
* @private
|
|
734
|
+
*/
|
|
735
|
+
generateId(prefix) {
|
|
736
|
+
return `${prefix}-${++this.taskCounter}-${Date.now().toString(36)}`;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Clear all plans
|
|
741
|
+
*/
|
|
742
|
+
clearPlans() {
|
|
743
|
+
this.plans.clear();
|
|
744
|
+
this.activePlan = null;
|
|
745
|
+
this.taskCounter = 0;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Export plan to readable format
|
|
750
|
+
* @param {string} planId - Plan identifier
|
|
751
|
+
* @returns {string}
|
|
752
|
+
*/
|
|
753
|
+
exportPlan(planId) {
|
|
754
|
+
const plan = this.plans.get(planId);
|
|
755
|
+
if (!plan) return '';
|
|
756
|
+
|
|
757
|
+
let output = `# Plan: ${plan.goal}\n\n`;
|
|
758
|
+
output += `**ID:** ${plan.id}\n`;
|
|
759
|
+
output += `**Status:** ${plan.status}\n`;
|
|
760
|
+
output += `**Created:** ${new Date(plan.createdAt).toISOString()}\n\n`;
|
|
761
|
+
|
|
762
|
+
output += `## Tasks (${plan.metrics.totalTasks})\n\n`;
|
|
763
|
+
|
|
764
|
+
for (let i = 0; i < plan.tasks.length; i++) {
|
|
765
|
+
const task = plan.tasks[i];
|
|
766
|
+
const statusIcon = this.getStatusIcon(task.status);
|
|
767
|
+
output += `${i + 1}. ${statusIcon} **${task.name}** [${task.status}]\n`;
|
|
768
|
+
output += ` - ${task.description}\n`;
|
|
769
|
+
if (task.dependencies.length > 0) {
|
|
770
|
+
output += ` - Depends on: ${task.dependencies.join(', ')}\n`;
|
|
771
|
+
}
|
|
772
|
+
output += `\n`;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
output += `## Metrics\n\n`;
|
|
776
|
+
output += `- Completed: ${plan.metrics.completedTasks}\n`;
|
|
777
|
+
output += `- Failed: ${plan.metrics.failedTasks}\n`;
|
|
778
|
+
output += `- Skipped: ${plan.metrics.skippedTasks}\n`;
|
|
779
|
+
if (plan.metrics.actualTime) {
|
|
780
|
+
output += `- Total Time: ${plan.metrics.actualTime}ms\n`;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return output;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Get status icon
|
|
788
|
+
* @private
|
|
789
|
+
*/
|
|
790
|
+
getStatusIcon(status) {
|
|
791
|
+
const icons = {
|
|
792
|
+
[TASK_STATUS.PENDING]: '⏳',
|
|
793
|
+
[TASK_STATUS.READY]: '📋',
|
|
794
|
+
[TASK_STATUS.IN_PROGRESS]: '🔄',
|
|
795
|
+
[TASK_STATUS.BLOCKED]: '🚫',
|
|
796
|
+
[TASK_STATUS.COMPLETED]: '✅',
|
|
797
|
+
[TASK_STATUS.FAILED]: '❌',
|
|
798
|
+
[TASK_STATUS.SKIPPED]: '⏭️'
|
|
799
|
+
};
|
|
800
|
+
return icons[status] || '•';
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Create planning engine
|
|
806
|
+
* @param {PlanningOptions} [options={}] - Engine options
|
|
807
|
+
* @returns {PlanningEngine}
|
|
808
|
+
*/
|
|
809
|
+
function createPlanningEngine(options = {}) {
|
|
810
|
+
return new PlanningEngine(options);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Create plan from goal
|
|
815
|
+
* @param {string} goal - Goal description
|
|
816
|
+
* @param {Object} [options={}] - Planning options
|
|
817
|
+
* @returns {Promise<Plan>}
|
|
818
|
+
*/
|
|
819
|
+
async function createPlan(goal, options = {}) {
|
|
820
|
+
const engine = createPlanningEngine(options);
|
|
821
|
+
return engine.createPlan(goal, options.context || {});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
module.exports = {
|
|
825
|
+
PlanningEngine,
|
|
826
|
+
createPlanningEngine,
|
|
827
|
+
createPlan,
|
|
828
|
+
PLAN_STATUS,
|
|
829
|
+
TASK_STATUS,
|
|
830
|
+
PRIORITY
|
|
831
|
+
};
|