musubi-sdd 3.5.1 → 3.6.1
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 +25 -3
- package/bin/musubi-orchestrate.js +309 -0
- package/package.json +1 -1
- package/src/llm-providers/anthropic-provider.js +175 -0
- package/src/llm-providers/base-provider.js +221 -0
- package/src/llm-providers/copilot-provider.js +262 -0
- package/src/llm-providers/index.js +214 -0
- package/src/llm-providers/openai-provider.js +205 -0
- package/src/orchestration/index.js +25 -0
- package/src/orchestration/patterns/swarm.js +111 -4
- package/src/orchestration/replanning/adaptive-goal-modifier.js +1150 -0
- package/src/orchestration/replanning/alternative-generator.js +508 -0
- package/src/orchestration/replanning/config.js +378 -0
- package/src/orchestration/replanning/goal-progress-tracker.js +727 -0
- package/src/orchestration/replanning/index.js +82 -0
- package/src/orchestration/replanning/plan-evaluator.js +455 -0
- package/src/orchestration/replanning/plan-monitor.js +379 -0
- package/src/orchestration/replanning/proactive-path-optimizer.js +972 -0
- package/src/orchestration/replanning/replan-history.js +402 -0
- package/src/orchestration/replanning/replanning-engine.js +706 -0
- package/src/templates/agents/claude-code/CLAUDE.md +45 -0
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +20 -0
- package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +89 -0
- package/src/templates/agents/codex/AGENTS.md +13 -0
- package/src/templates/agents/cursor/AGENTS.md +13 -0
- package/src/templates/agents/gemini-cli/GEMINI.md +13 -0
- package/src/templates/agents/github-copilot/AGENTS.md +13 -0
- package/src/templates/agents/qwen-code/QWEN.md +13 -0
- package/src/templates/agents/windsurf/AGENTS.md +13 -0
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core Replanning Engine for MUSUBI
|
|
3
|
+
* Provides dynamic task replanning capabilities
|
|
4
|
+
* @module orchestration/replanning/replanning-engine
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const EventEmitter = require('events');
|
|
11
|
+
const { ReplanTrigger, ReplanDecision, mergeConfig, defaultReplanningConfig } = require('./config');
|
|
12
|
+
const { PlanMonitor } = require('./plan-monitor');
|
|
13
|
+
const { PlanEvaluator } = require('./plan-evaluator');
|
|
14
|
+
const { AlternativeGenerator } = require('./alternative-generator');
|
|
15
|
+
const { ReplanHistory } = require('./replan-history');
|
|
16
|
+
const { createLLMProvider } = require('../../llm-providers');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Replanning Engine - Core engine for dynamic task replanning
|
|
20
|
+
*/
|
|
21
|
+
class ReplanningEngine extends EventEmitter {
|
|
22
|
+
/**
|
|
23
|
+
* Create a replanning engine
|
|
24
|
+
* @param {Object} [orchestrationEngine] - Reference to orchestration engine
|
|
25
|
+
* @param {Object} [options={}] - Engine options
|
|
26
|
+
*/
|
|
27
|
+
constructor(orchestrationEngine = null, options = {}) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
this.engine = orchestrationEngine;
|
|
31
|
+
this.config = mergeConfig(options.config || options);
|
|
32
|
+
|
|
33
|
+
// Initialize components
|
|
34
|
+
this.llmProvider = options.llmProvider || this.createLLMProvider();
|
|
35
|
+
this.monitor = new PlanMonitor({ config: this.config });
|
|
36
|
+
this.evaluator = new PlanEvaluator({ config: this.config.evaluation });
|
|
37
|
+
this.generator = new AlternativeGenerator(this.llmProvider, {
|
|
38
|
+
config: this.config.alternatives,
|
|
39
|
+
scorerConfig: this.config.evaluation
|
|
40
|
+
});
|
|
41
|
+
this.history = new ReplanHistory({ config: this.config.history });
|
|
42
|
+
|
|
43
|
+
// State
|
|
44
|
+
this.currentPlan = null;
|
|
45
|
+
this.planVersion = 0;
|
|
46
|
+
this.isExecuting = false;
|
|
47
|
+
this.executionContext = null;
|
|
48
|
+
|
|
49
|
+
// Wire up monitor events
|
|
50
|
+
this.setupMonitorEvents();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create LLM provider based on configuration
|
|
55
|
+
* @returns {LLMProvider} LLM provider instance
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
createLLMProvider() {
|
|
59
|
+
try {
|
|
60
|
+
const providerConfig = this.config.llmProvider || {};
|
|
61
|
+
return createLLMProvider(providerConfig.provider || 'auto', providerConfig);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn('Failed to create LLM provider:', error.message);
|
|
64
|
+
// Return a mock provider that warns on use
|
|
65
|
+
return {
|
|
66
|
+
complete: async () => {
|
|
67
|
+
throw new Error('No LLM provider available for replanning');
|
|
68
|
+
},
|
|
69
|
+
completeJSON: async () => {
|
|
70
|
+
throw new Error('No LLM provider available for replanning');
|
|
71
|
+
},
|
|
72
|
+
isAvailable: async () => false
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Setup monitor event handlers
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
setupMonitorEvents() {
|
|
82
|
+
this.monitor.on('trigger', async (trigger) => {
|
|
83
|
+
if (this.config.integration?.emitEvents) {
|
|
84
|
+
this.emit(`${this.config.integration.eventPrefix}:trigger`, trigger);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle the trigger
|
|
88
|
+
try {
|
|
89
|
+
await this.handleTrigger(trigger);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
this.emit('error', error);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Execute a plan with replanning support
|
|
98
|
+
* @param {Object} plan - Execution plan
|
|
99
|
+
* @param {Object} [options={}] - Execution options
|
|
100
|
+
* @returns {Promise<ExecutionResult>} Execution result
|
|
101
|
+
*/
|
|
102
|
+
async executeWithReplanning(plan, options = {}) {
|
|
103
|
+
if (!this.config.enabled) {
|
|
104
|
+
// Replanning disabled, delegate to engine
|
|
105
|
+
return this.delegateToEngine(plan, options);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.currentPlan = this.normalizePlan(plan);
|
|
109
|
+
this.planVersion = 0;
|
|
110
|
+
this.isExecuting = true;
|
|
111
|
+
|
|
112
|
+
// Initialize execution context
|
|
113
|
+
this.executionContext = {
|
|
114
|
+
planId: this.currentPlan.id,
|
|
115
|
+
startTime: Date.now(),
|
|
116
|
+
completed: [],
|
|
117
|
+
pending: [...(this.currentPlan.tasks || [])],
|
|
118
|
+
failed: [],
|
|
119
|
+
retries: 0
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Record initial snapshot
|
|
123
|
+
this.history.recordSnapshot(
|
|
124
|
+
this.currentPlan.id,
|
|
125
|
+
this.currentPlan,
|
|
126
|
+
'Initial plan'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Start monitoring
|
|
130
|
+
this.monitor.watch(this.currentPlan.id, {
|
|
131
|
+
plan: this.currentPlan,
|
|
132
|
+
tasks: this.currentPlan.tasks,
|
|
133
|
+
...this.executionContext
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
// Execute with replanning loop
|
|
138
|
+
const result = await this.executeLoop(options);
|
|
139
|
+
|
|
140
|
+
this.isExecuting = false;
|
|
141
|
+
this.monitor.unwatch(this.currentPlan.id);
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.isExecuting = false;
|
|
146
|
+
this.monitor.unwatch(this.currentPlan.id);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Main execution loop with replanning
|
|
153
|
+
* @param {Object} options - Execution options
|
|
154
|
+
* @returns {Promise<ExecutionResult>}
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
async executeLoop(options) {
|
|
158
|
+
while (this.executionContext.pending.length > 0 && this.isExecuting) {
|
|
159
|
+
const task = this.executionContext.pending.shift();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Execute task
|
|
163
|
+
const result = await this.executeTask(task, options);
|
|
164
|
+
|
|
165
|
+
// Record success
|
|
166
|
+
this.executionContext.completed.push({
|
|
167
|
+
...task,
|
|
168
|
+
result,
|
|
169
|
+
duration: result.duration,
|
|
170
|
+
completedAt: Date.now()
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Report to monitor
|
|
174
|
+
this.monitor.reportResult(this.currentPlan.id, {
|
|
175
|
+
taskId: task.id,
|
|
176
|
+
status: 'success',
|
|
177
|
+
output: result
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Record in evaluator
|
|
181
|
+
this.evaluator.recordExecution(task.skill || task.name, {
|
|
182
|
+
success: true,
|
|
183
|
+
duration: result.duration
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
} catch (error) {
|
|
187
|
+
// Record failure
|
|
188
|
+
const failedTask = {
|
|
189
|
+
...task,
|
|
190
|
+
error,
|
|
191
|
+
failedAt: Date.now(),
|
|
192
|
+
attempts: (task.attempts || 0) + 1
|
|
193
|
+
};
|
|
194
|
+
this.executionContext.failed.push(failedTask);
|
|
195
|
+
|
|
196
|
+
// Report to monitor (may trigger replanning)
|
|
197
|
+
const trigger = this.monitor.reportResult(this.currentPlan.id, {
|
|
198
|
+
taskId: task.id,
|
|
199
|
+
status: 'failed',
|
|
200
|
+
error
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Record in evaluator
|
|
204
|
+
this.evaluator.recordExecution(task.skill || task.name, {
|
|
205
|
+
success: false,
|
|
206
|
+
duration: Date.now() - (task.startTime || Date.now())
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// If trigger was detected, wait for replanning to complete
|
|
210
|
+
if (trigger) {
|
|
211
|
+
// Replanning is handled by trigger event
|
|
212
|
+
// Continue to next iteration
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Generate final result
|
|
218
|
+
return this.generateResult();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Execute a single task
|
|
223
|
+
* @param {Object} task - Task to execute
|
|
224
|
+
* @param {Object} options - Execution options
|
|
225
|
+
* @returns {Promise<Object>} Task result
|
|
226
|
+
* @private
|
|
227
|
+
*/
|
|
228
|
+
async executeTask(task, options) {
|
|
229
|
+
task.startTime = Date.now();
|
|
230
|
+
|
|
231
|
+
if (this.engine) {
|
|
232
|
+
// Delegate to orchestration engine
|
|
233
|
+
return this.engine.executeSkill(task.skill || task.name, task.parameters, options);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Mock execution for testing
|
|
237
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
238
|
+
return {
|
|
239
|
+
success: true,
|
|
240
|
+
duration: Date.now() - task.startTime,
|
|
241
|
+
output: `Executed ${task.skill || task.name}`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Handle a replanning trigger
|
|
247
|
+
* @param {Object} trigger - Trigger event
|
|
248
|
+
* @returns {Promise<void>}
|
|
249
|
+
* @private
|
|
250
|
+
*/
|
|
251
|
+
async handleTrigger(trigger) {
|
|
252
|
+
const failedTask = this.executionContext.failed[this.executionContext.failed.length - 1];
|
|
253
|
+
|
|
254
|
+
// Generate alternatives
|
|
255
|
+
const alternatives = await this.generator.generateAlternatives(
|
|
256
|
+
failedTask,
|
|
257
|
+
this.executionContext
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Select best alternative or request human input
|
|
261
|
+
const decision = await this.selectAlternative(alternatives, trigger);
|
|
262
|
+
|
|
263
|
+
// Record the replan event
|
|
264
|
+
const event = this.history.record({
|
|
265
|
+
trigger: trigger.type,
|
|
266
|
+
decision: decision.type,
|
|
267
|
+
planId: this.currentPlan.id,
|
|
268
|
+
failedTask: {
|
|
269
|
+
id: failedTask.id,
|
|
270
|
+
name: failedTask.name,
|
|
271
|
+
skill: failedTask.skill,
|
|
272
|
+
error: failedTask.error?.message
|
|
273
|
+
},
|
|
274
|
+
alternatives: alternatives.map(a => ({
|
|
275
|
+
id: a.id,
|
|
276
|
+
description: a.description,
|
|
277
|
+
confidence: a.confidence
|
|
278
|
+
})),
|
|
279
|
+
selectedAlternative: decision.alternative ? {
|
|
280
|
+
id: decision.alternative.id,
|
|
281
|
+
description: decision.alternative.description,
|
|
282
|
+
confidence: decision.alternative.confidence,
|
|
283
|
+
reasoning: decision.alternative.reasoning
|
|
284
|
+
} : null,
|
|
285
|
+
context: {
|
|
286
|
+
completedCount: this.executionContext.completed.length,
|
|
287
|
+
pendingCount: this.executionContext.pending.length,
|
|
288
|
+
failedCount: this.executionContext.failed.length
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Apply the decision
|
|
293
|
+
await this.applyDecision(decision, trigger);
|
|
294
|
+
|
|
295
|
+
// Emit replan event
|
|
296
|
+
this.emit('replan', { event, decision, alternatives });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Select the best alternative or request human input
|
|
301
|
+
* @param {Alternative[]} alternatives - Available alternatives
|
|
302
|
+
* @param {Object} trigger - Trigger event
|
|
303
|
+
* @returns {Promise<Decision>} Selected decision
|
|
304
|
+
* @private
|
|
305
|
+
*/
|
|
306
|
+
async selectAlternative(alternatives, trigger) {
|
|
307
|
+
// Check if human approval is always required for this trigger
|
|
308
|
+
const alwaysApprove = this.config.humanInLoop?.alwaysApprove || [];
|
|
309
|
+
const requiresApproval = alwaysApprove.includes(trigger.type);
|
|
310
|
+
|
|
311
|
+
if (alternatives.length === 0) {
|
|
312
|
+
return {
|
|
313
|
+
type: ReplanDecision.ABORT,
|
|
314
|
+
alternative: null,
|
|
315
|
+
reason: 'No viable alternatives found'
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const bestAlternative = alternatives[0];
|
|
320
|
+
|
|
321
|
+
// Check if confidence is below threshold
|
|
322
|
+
const threshold = this.config.alternatives?.humanApprovalThreshold || 0.7;
|
|
323
|
+
if (requiresApproval || bestAlternative.confidence < threshold) {
|
|
324
|
+
if (this.config.humanInLoop?.enabled) {
|
|
325
|
+
return this.requestHumanApproval(alternatives, trigger);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Auto-select best alternative
|
|
330
|
+
return {
|
|
331
|
+
type: this.getDecisionType(bestAlternative),
|
|
332
|
+
alternative: bestAlternative,
|
|
333
|
+
reason: `Auto-selected with confidence ${bestAlternative.confidence}`
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get decision type based on alternative
|
|
339
|
+
* @param {Alternative} alternative - Selected alternative
|
|
340
|
+
* @returns {string} Decision type
|
|
341
|
+
* @private
|
|
342
|
+
*/
|
|
343
|
+
getDecisionType(alternative) {
|
|
344
|
+
if (alternative.id === 'retry') {
|
|
345
|
+
return ReplanDecision.RETRY;
|
|
346
|
+
}
|
|
347
|
+
if (alternative.source === 'skip') {
|
|
348
|
+
return ReplanDecision.SKIP;
|
|
349
|
+
}
|
|
350
|
+
return ReplanDecision.REPLACE;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Request human approval for alternatives
|
|
355
|
+
* @param {Alternative[]} alternatives - Available alternatives
|
|
356
|
+
* @param {Object} trigger - Trigger event
|
|
357
|
+
* @returns {Promise<Decision>} Human decision
|
|
358
|
+
* @private
|
|
359
|
+
*/
|
|
360
|
+
async requestHumanApproval(alternatives, trigger) {
|
|
361
|
+
return new Promise((resolve) => {
|
|
362
|
+
const timeout = this.config.humanInLoop?.timeout || 300000;
|
|
363
|
+
|
|
364
|
+
// Emit event for human review
|
|
365
|
+
this.emit('replan:review-required', {
|
|
366
|
+
trigger,
|
|
367
|
+
alternatives,
|
|
368
|
+
timeout,
|
|
369
|
+
respond: (decision) => {
|
|
370
|
+
resolve(decision);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Timeout handling
|
|
375
|
+
setTimeout(() => {
|
|
376
|
+
const defaultAction = this.config.humanInLoop?.defaultOnTimeout || 'abort';
|
|
377
|
+
resolve({
|
|
378
|
+
type: defaultAction === 'abort' ? ReplanDecision.ABORT : ReplanDecision.SKIP,
|
|
379
|
+
alternative: null,
|
|
380
|
+
reason: 'Human approval timeout'
|
|
381
|
+
});
|
|
382
|
+
}, timeout);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Apply the selected decision
|
|
388
|
+
* @param {Decision} decision - Decision to apply
|
|
389
|
+
* @param {Object} trigger - Original trigger
|
|
390
|
+
* @private
|
|
391
|
+
*/
|
|
392
|
+
async applyDecision(decision, trigger) {
|
|
393
|
+
const planId = this.currentPlan.id;
|
|
394
|
+
|
|
395
|
+
switch (decision.type) {
|
|
396
|
+
case ReplanDecision.RETRY:
|
|
397
|
+
// Add task back to pending
|
|
398
|
+
const retryTask = {
|
|
399
|
+
...decision.alternative.task,
|
|
400
|
+
attempts: (decision.alternative.task.attempts || 0) + 1
|
|
401
|
+
};
|
|
402
|
+
this.executionContext.pending.unshift(retryTask);
|
|
403
|
+
this.executionContext.retries++;
|
|
404
|
+
break;
|
|
405
|
+
|
|
406
|
+
case ReplanDecision.REPLACE:
|
|
407
|
+
// Add alternative task to pending
|
|
408
|
+
this.executionContext.pending.unshift(decision.alternative.task);
|
|
409
|
+
this.planVersion++;
|
|
410
|
+
this.history.recordSnapshot(planId, this.currentPlan, 'Task replaced');
|
|
411
|
+
break;
|
|
412
|
+
|
|
413
|
+
case ReplanDecision.SKIP:
|
|
414
|
+
// Do nothing, task is already removed from pending
|
|
415
|
+
break;
|
|
416
|
+
|
|
417
|
+
case ReplanDecision.INSERT:
|
|
418
|
+
// Insert new tasks at appropriate position
|
|
419
|
+
if (decision.tasks) {
|
|
420
|
+
this.executionContext.pending.unshift(...decision.tasks);
|
|
421
|
+
this.planVersion++;
|
|
422
|
+
}
|
|
423
|
+
break;
|
|
424
|
+
|
|
425
|
+
case ReplanDecision.ABORT:
|
|
426
|
+
// Stop execution
|
|
427
|
+
this.isExecuting = false;
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
case ReplanDecision.HUMAN_REVIEW:
|
|
431
|
+
// Already handled
|
|
432
|
+
break;
|
|
433
|
+
|
|
434
|
+
default:
|
|
435
|
+
console.warn(`Unknown decision type: ${decision.type}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Update history with outcome
|
|
439
|
+
const lastEvent = this.history.getEvents({ limit: 1, sort: 'desc' })[0];
|
|
440
|
+
if (lastEvent) {
|
|
441
|
+
lastEvent.outcome = {
|
|
442
|
+
success: decision.type !== ReplanDecision.ABORT,
|
|
443
|
+
applied: decision.type
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Generate final execution result
|
|
450
|
+
* @returns {ExecutionResult} Final result
|
|
451
|
+
* @private
|
|
452
|
+
*/
|
|
453
|
+
generateResult() {
|
|
454
|
+
const evaluation = this.evaluator.evaluate(
|
|
455
|
+
this.currentPlan,
|
|
456
|
+
this.executionContext
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
planId: this.currentPlan.id,
|
|
461
|
+
planVersion: this.planVersion,
|
|
462
|
+
status: this.executionContext.failed.length === 0 ? 'success' : 'partial',
|
|
463
|
+
completed: this.executionContext.completed,
|
|
464
|
+
failed: this.executionContext.failed,
|
|
465
|
+
pending: this.executionContext.pending,
|
|
466
|
+
evaluation,
|
|
467
|
+
replanCount: this.planVersion,
|
|
468
|
+
history: this.history.getMetrics(),
|
|
469
|
+
duration: Date.now() - this.executionContext.startTime
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Normalize plan structure
|
|
475
|
+
* @param {Object} plan - Input plan
|
|
476
|
+
* @returns {Object} Normalized plan
|
|
477
|
+
* @private
|
|
478
|
+
*/
|
|
479
|
+
normalizePlan(plan) {
|
|
480
|
+
const normalizedTasks = (plan.tasks || []).map((task, index) => {
|
|
481
|
+
const normalized = {
|
|
482
|
+
...task,
|
|
483
|
+
id: task.id || `task-${index}`,
|
|
484
|
+
name: task.name || task.skill,
|
|
485
|
+
skill: task.skill || task.name,
|
|
486
|
+
parameters: task.parameters || {},
|
|
487
|
+
dependencies: task.dependencies || []
|
|
488
|
+
};
|
|
489
|
+
return normalized;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
...plan,
|
|
494
|
+
id: plan.id || `plan-${Date.now()}`,
|
|
495
|
+
version: plan.version || 1,
|
|
496
|
+
tasks: normalizedTasks
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Delegate execution to orchestration engine
|
|
502
|
+
* @param {Object} plan - Plan to execute
|
|
503
|
+
* @param {Object} options - Options
|
|
504
|
+
* @returns {Promise<Object>} Result
|
|
505
|
+
* @private
|
|
506
|
+
*/
|
|
507
|
+
async delegateToEngine(plan, options) {
|
|
508
|
+
if (!this.engine) {
|
|
509
|
+
throw new Error('No orchestration engine available');
|
|
510
|
+
}
|
|
511
|
+
return this.engine.execute(plan.pattern || 'sequential', {
|
|
512
|
+
...options,
|
|
513
|
+
tasks: plan.tasks
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Manually trigger replanning
|
|
519
|
+
* @param {string} [reason] - Reason for manual replan
|
|
520
|
+
* @returns {Promise<void>}
|
|
521
|
+
*/
|
|
522
|
+
async replan(reason = 'Manual replan request') {
|
|
523
|
+
if (!this.isExecuting) {
|
|
524
|
+
throw new Error('Cannot replan when not executing');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
this.monitor.requestReplan(this.currentPlan.id, reason);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Add a task to the current plan
|
|
532
|
+
* @param {Object} task - Task to add
|
|
533
|
+
* @param {string} [position='end'] - Position: 'start', 'end', or task ID
|
|
534
|
+
*/
|
|
535
|
+
addTask(task, position = 'end') {
|
|
536
|
+
if (!this.currentPlan) {
|
|
537
|
+
throw new Error('No active plan');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const normalizedTask = {
|
|
541
|
+
id: task.id || `task-${Date.now()}`,
|
|
542
|
+
...task
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
if (position === 'start') {
|
|
546
|
+
this.executionContext.pending.unshift(normalizedTask);
|
|
547
|
+
} else if (position === 'end') {
|
|
548
|
+
this.executionContext.pending.push(normalizedTask);
|
|
549
|
+
} else {
|
|
550
|
+
// Insert after specific task
|
|
551
|
+
const index = this.executionContext.pending.findIndex(t => t.id === position);
|
|
552
|
+
if (index !== -1) {
|
|
553
|
+
this.executionContext.pending.splice(index + 1, 0, normalizedTask);
|
|
554
|
+
} else {
|
|
555
|
+
this.executionContext.pending.push(normalizedTask);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
this.planVersion++;
|
|
560
|
+
this.emit('plan:modified', { action: 'add', task: normalizedTask });
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Remove a task from the current plan
|
|
565
|
+
* @param {string} taskId - Task ID to remove
|
|
566
|
+
* @returns {boolean} Whether task was found and removed
|
|
567
|
+
*/
|
|
568
|
+
removeTask(taskId) {
|
|
569
|
+
if (!this.currentPlan) {
|
|
570
|
+
throw new Error('No active plan');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const index = this.executionContext.pending.findIndex(t => t.id === taskId);
|
|
574
|
+
if (index !== -1) {
|
|
575
|
+
const removed = this.executionContext.pending.splice(index, 1)[0];
|
|
576
|
+
this.planVersion++;
|
|
577
|
+
this.emit('plan:modified', { action: 'remove', task: removed });
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Reorder tasks in the current plan
|
|
586
|
+
* @param {string[]} taskIds - Ordered task IDs
|
|
587
|
+
*/
|
|
588
|
+
reorderTasks(taskIds) {
|
|
589
|
+
if (!this.currentPlan) {
|
|
590
|
+
throw new Error('No active plan');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const taskMap = new Map(
|
|
594
|
+
this.executionContext.pending.map(t => [t.id, t])
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const reordered = [];
|
|
598
|
+
for (const id of taskIds) {
|
|
599
|
+
const task = taskMap.get(id);
|
|
600
|
+
if (task) {
|
|
601
|
+
reordered.push(task);
|
|
602
|
+
taskMap.delete(id);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Add remaining tasks at end
|
|
607
|
+
reordered.push(...taskMap.values());
|
|
608
|
+
|
|
609
|
+
this.executionContext.pending = reordered;
|
|
610
|
+
this.planVersion++;
|
|
611
|
+
this.emit('plan:modified', { action: 'reorder', order: taskIds });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Modify a task in the current plan
|
|
616
|
+
* @param {string} taskId - Task ID to modify
|
|
617
|
+
* @param {Object} updates - Updates to apply
|
|
618
|
+
* @returns {boolean} Whether task was found and modified
|
|
619
|
+
*/
|
|
620
|
+
modifyTask(taskId, updates) {
|
|
621
|
+
if (!this.currentPlan) {
|
|
622
|
+
throw new Error('No active plan');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const task = this.executionContext.pending.find(t => t.id === taskId);
|
|
626
|
+
if (task) {
|
|
627
|
+
Object.assign(task, updates);
|
|
628
|
+
this.planVersion++;
|
|
629
|
+
this.emit('plan:modified', { action: 'modify', taskId, updates });
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Get plan history
|
|
638
|
+
* @param {Object} [filter] - Filter options
|
|
639
|
+
* @returns {Object} Plan history
|
|
640
|
+
*/
|
|
641
|
+
getPlanHistory(filter) {
|
|
642
|
+
return {
|
|
643
|
+
events: this.history.getEvents(filter),
|
|
644
|
+
metrics: this.history.getMetrics(),
|
|
645
|
+
snapshots: this.currentPlan ? this.history.getSnapshots(this.currentPlan.id) : []
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Get current plan state
|
|
651
|
+
* @returns {Object|null} Current plan
|
|
652
|
+
*/
|
|
653
|
+
getCurrentPlan() {
|
|
654
|
+
if (!this.currentPlan) return null;
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
...this.currentPlan,
|
|
658
|
+
version: this.planVersion,
|
|
659
|
+
context: this.executionContext
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Check if LLM provider is available
|
|
665
|
+
* @returns {Promise<boolean>}
|
|
666
|
+
*/
|
|
667
|
+
async isLLMAvailable() {
|
|
668
|
+
return this.llmProvider.isAvailable();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Export history report
|
|
673
|
+
* @param {string} [format='markdown'] - Export format
|
|
674
|
+
* @returns {string} Report content
|
|
675
|
+
*/
|
|
676
|
+
exportHistory(format = 'markdown') {
|
|
677
|
+
if (format === 'json') {
|
|
678
|
+
return this.history.exportJSON();
|
|
679
|
+
}
|
|
680
|
+
return this.history.exportMarkdown();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* @typedef {Object} ExecutionResult
|
|
686
|
+
* @property {string} planId - Plan identifier
|
|
687
|
+
* @property {number} planVersion - Final plan version
|
|
688
|
+
* @property {string} status - Execution status
|
|
689
|
+
* @property {Object[]} completed - Completed tasks
|
|
690
|
+
* @property {Object[]} failed - Failed tasks
|
|
691
|
+
* @property {Object[]} pending - Remaining pending tasks
|
|
692
|
+
* @property {Object} evaluation - Plan evaluation
|
|
693
|
+
* @property {number} replanCount - Number of replans
|
|
694
|
+
* @property {Object} history - History metrics
|
|
695
|
+
* @property {number} duration - Total duration in ms
|
|
696
|
+
*/
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* @typedef {Object} Decision
|
|
700
|
+
* @property {string} type - Decision type from ReplanDecision
|
|
701
|
+
* @property {Object|null} alternative - Selected alternative
|
|
702
|
+
* @property {string} reason - Reason for decision
|
|
703
|
+
* @property {Object[]} [tasks] - Tasks for INSERT decision
|
|
704
|
+
*/
|
|
705
|
+
|
|
706
|
+
module.exports = { ReplanningEngine };
|