linguclaw 0.4.2 → 0.5.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.
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Autonomous Task Planning & Decomposition Engine
3
+ *
4
+ * Core capabilities:
5
+ * - Reasoning Engine: Creates multi-step plans from high-level prompts
6
+ * - Sub-task Execution: Breaks goals into actionable pieces with dependency graphs
7
+ * - Self-Correction: Detects failures and pivots to alternative strategies
8
+ * - Plan Validation: Validates plans before execution
9
+ */
10
+ import { EventEmitter } from 'events';
11
+ import { BaseProvider } from './multi-provider';
12
+ export interface TaskGoal {
13
+ id: string;
14
+ description: string;
15
+ context?: string;
16
+ constraints?: string[];
17
+ priority: 'low' | 'medium' | 'high' | 'critical';
18
+ deadline?: Date;
19
+ parentGoalId?: string;
20
+ }
21
+ export interface SubTask {
22
+ id: string;
23
+ goalId: string;
24
+ description: string;
25
+ action: TaskAction;
26
+ dependencies: string[];
27
+ status: SubTaskStatus;
28
+ result?: any;
29
+ error?: string;
30
+ retryCount: number;
31
+ maxRetries: number;
32
+ estimatedDuration: number;
33
+ actualDuration?: number;
34
+ alternativeStrategies: string[];
35
+ metadata: Record<string, any>;
36
+ }
37
+ export type SubTaskStatus = 'pending' | 'ready' | 'running' | 'completed' | 'failed' | 'skipped' | 'retrying' | 'blocked';
38
+ export interface TaskAction {
39
+ type: 'shell' | 'filesystem' | 'browse' | 'search' | 'code_exec' | 'api_call' | 'llm_reason' | 'human_input';
40
+ command?: string;
41
+ params?: Record<string, any>;
42
+ }
43
+ export interface ExecutionPlan {
44
+ id: string;
45
+ goal: TaskGoal;
46
+ subtasks: SubTask[];
47
+ createdAt: Date;
48
+ updatedAt: Date;
49
+ status: 'planning' | 'ready' | 'executing' | 'completed' | 'failed' | 'replanning';
50
+ totalEstimatedDuration: number;
51
+ executionOrder: string[];
52
+ corrections: CorrectionRecord[];
53
+ }
54
+ export interface CorrectionRecord {
55
+ timestamp: Date;
56
+ failedSubtaskId: string;
57
+ errorType: string;
58
+ errorMessage: string;
59
+ strategy: 'retry' | 'alternative' | 'skip' | 'replan' | 'abort';
60
+ newSubtaskId?: string;
61
+ newAction?: string;
62
+ newDescription?: string;
63
+ reasoning: string;
64
+ }
65
+ export interface PlannerConfig {
66
+ maxSubtasks: number;
67
+ maxRetries: number;
68
+ maxReplans: number;
69
+ enableSelfCorrection: boolean;
70
+ enableParallelExecution: boolean;
71
+ planningTemperature: number;
72
+ executionTimeout: number;
73
+ }
74
+ export declare class TaskPlanner extends EventEmitter {
75
+ private provider;
76
+ private config;
77
+ private activePlans;
78
+ private replanCount;
79
+ constructor(provider: BaseProvider, config?: Partial<PlannerConfig>);
80
+ /**
81
+ * Create an execution plan from a high-level goal
82
+ */
83
+ createPlan(goal: TaskGoal, context?: string): Promise<ExecutionPlan>;
84
+ /**
85
+ * Get the next ready subtask(s) for execution
86
+ */
87
+ getReadySubtasks(plan: ExecutionPlan): SubTask[];
88
+ /**
89
+ * Mark a subtask as started
90
+ */
91
+ markRunning(plan: ExecutionPlan, subtaskId: string): void;
92
+ /**
93
+ * Mark a subtask as completed
94
+ */
95
+ markCompleted(plan: ExecutionPlan, subtaskId: string, result: any): void;
96
+ /**
97
+ * Handle subtask failure with self-correction
98
+ */
99
+ handleFailure(plan: ExecutionPlan, subtaskId: string, error: string): Promise<CorrectionRecord>;
100
+ /**
101
+ * Self-correction: Analyze failure and decide recovery strategy
102
+ */
103
+ private selfCorrect;
104
+ /**
105
+ * Replan: Create new subtasks for remaining work
106
+ */
107
+ private replan;
108
+ /**
109
+ * Classify error type for correction strategy
110
+ */
111
+ private classifyError;
112
+ /**
113
+ * Parse subtasks from LLM response
114
+ */
115
+ private parseSubtasks;
116
+ /**
117
+ * Parse action from subtask data
118
+ */
119
+ private parseAction;
120
+ /**
121
+ * Parse correction response from LLM
122
+ */
123
+ private parseCorrectionResponse;
124
+ /**
125
+ * Topological sort of subtask dependency graph
126
+ */
127
+ private topologicalSort;
128
+ /**
129
+ * Get plan status summary
130
+ */
131
+ getPlanSummary(plan: ExecutionPlan): {
132
+ total: number;
133
+ completed: number;
134
+ failed: number;
135
+ pending: number;
136
+ running: number;
137
+ corrections: number;
138
+ progress: number;
139
+ };
140
+ /**
141
+ * Get active plan by ID
142
+ */
143
+ getPlan(planId: string): ExecutionPlan | undefined;
144
+ }
145
+ //# sourceMappingURL=task-planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-planner.d.ts","sourceRoot":"","sources":["../src/task-planner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,520 @@
1
+ "use strict";
2
+ /**
3
+ * Autonomous Task Planning & Decomposition Engine
4
+ *
5
+ * Core capabilities:
6
+ * - Reasoning Engine: Creates multi-step plans from high-level prompts
7
+ * - Sub-task Execution: Breaks goals into actionable pieces with dependency graphs
8
+ * - Self-Correction: Detects failures and pivots to alternative strategies
9
+ * - Plan Validation: Validates plans before execution
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.TaskPlanner = void 0;
13
+ const events_1 = require("events");
14
+ const logger_1 = require("./logger");
15
+ const logger = (0, logger_1.getLogger)();
16
+ const DEFAULT_CONFIG = {
17
+ maxSubtasks: 20,
18
+ maxRetries: 3,
19
+ maxReplans: 2,
20
+ enableSelfCorrection: true,
21
+ enableParallelExecution: true,
22
+ planningTemperature: 0.3,
23
+ executionTimeout: 120,
24
+ };
25
+ // ==================== System Prompts ====================
26
+ const PLANNING_PROMPT = `You are an autonomous task planner for LinguClaw, an AI agent framework.
27
+
28
+ Your job is to decompose a high-level goal into a detailed execution plan.
29
+
30
+ For each subtask, specify:
31
+ 1. A clear description of what to do
32
+ 2. The action type: shell, filesystem, browse, search, code_exec, api_call, llm_reason
33
+ 3. Dependencies (which subtasks must complete first)
34
+ 4. Alternative strategies if this approach fails
35
+ 5. Estimated duration in seconds
36
+
37
+ IMPORTANT RULES:
38
+ - Break complex tasks into 3-15 manageable steps
39
+ - Each step should be independently verifiable
40
+ - Include verification steps after critical operations
41
+ - Consider failure modes and provide alternatives
42
+ - Order steps logically with proper dependencies
43
+ - Never use "cd" in shell commands
44
+
45
+ Return a JSON array of subtasks:
46
+ [
47
+ {
48
+ "id": "task-1",
49
+ "description": "Search for relevant information",
50
+ "action": { "type": "search", "params": { "query": "..." } },
51
+ "dependencies": [],
52
+ "alternativeStrategies": ["Use a different search engine", "Try browsing directly"],
53
+ "estimatedDuration": 10
54
+ }
55
+ ]`;
56
+ const SELF_CORRECTION_PROMPT = `You are a self-correction engine for LinguClaw.
57
+
58
+ A subtask has failed. Analyze the failure and decide the best recovery strategy.
59
+
60
+ Strategies:
61
+ 1. "retry" - Try the same approach again (for transient errors like timeouts)
62
+ 2. "alternative" - Use one of the predefined alternative strategies
63
+ 3. "skip" - Skip this subtask if it's not critical
64
+ 4. "replan" - Create a new plan from the current state
65
+ 5. "abort" - Stop execution if the failure is unrecoverable
66
+
67
+ Return JSON:
68
+ {
69
+ "strategy": "alternative",
70
+ "reasoning": "The website returned a 403 error, suggesting we're blocked. Switching to an alternative data source.",
71
+ "newAction": { "type": "browse", "params": { "url": "..." } },
72
+ "newDescription": "Fetch data from alternative source"
73
+ }`;
74
+ // ==================== Task Planner ====================
75
+ class TaskPlanner extends events_1.EventEmitter {
76
+ provider;
77
+ config;
78
+ activePlans = new Map();
79
+ replanCount = new Map();
80
+ constructor(provider, config) {
81
+ super();
82
+ this.provider = provider;
83
+ this.config = { ...DEFAULT_CONFIG, ...config };
84
+ }
85
+ /**
86
+ * Create an execution plan from a high-level goal
87
+ */
88
+ async createPlan(goal, context) {
89
+ logger.info(`[TaskPlanner] Creating plan for: ${goal.description}`);
90
+ this.emit('planning:start', { goalId: goal.id });
91
+ const messages = [
92
+ { role: 'system', content: PLANNING_PROMPT },
93
+ {
94
+ role: 'user',
95
+ content: `Goal: ${goal.description}
96
+ ${goal.context ? `\nContext: ${goal.context}` : ''}
97
+ ${context ? `\nAdditional context: ${context}` : ''}
98
+ ${goal.constraints?.length ? `\nConstraints: ${goal.constraints.join(', ')}` : ''}
99
+ Priority: ${goal.priority}`
100
+ },
101
+ ];
102
+ const response = await this.provider.complete(messages, this.config.planningTemperature, 2048);
103
+ if (response.error || !response.content) {
104
+ throw new Error(`Planning failed: ${response.error || 'Empty response'}`);
105
+ }
106
+ const subtasks = this.parseSubtasks(response.content, goal.id);
107
+ const executionOrder = this.topologicalSort(subtasks);
108
+ const plan = {
109
+ id: `plan-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`,
110
+ goal,
111
+ subtasks,
112
+ createdAt: new Date(),
113
+ updatedAt: new Date(),
114
+ status: 'ready',
115
+ totalEstimatedDuration: subtasks.reduce((sum, t) => sum + t.estimatedDuration, 0),
116
+ executionOrder,
117
+ corrections: [],
118
+ };
119
+ this.activePlans.set(plan.id, plan);
120
+ this.replanCount.set(plan.id, 0);
121
+ this.emit('planning:complete', { planId: plan.id, subtaskCount: subtasks.length });
122
+ logger.info(`[TaskPlanner] Plan created: ${plan.id} with ${subtasks.length} subtasks`);
123
+ return plan;
124
+ }
125
+ /**
126
+ * Get the next ready subtask(s) for execution
127
+ */
128
+ getReadySubtasks(plan) {
129
+ const completedIds = new Set(plan.subtasks
130
+ .filter(t => t.status === 'completed' || t.status === 'skipped')
131
+ .map(t => t.id));
132
+ return plan.subtasks.filter(task => {
133
+ if (task.status !== 'pending' && task.status !== 'ready')
134
+ return false;
135
+ const depsResolved = task.dependencies.every(dep => completedIds.has(dep));
136
+ if (depsResolved)
137
+ task.status = 'ready';
138
+ return depsResolved;
139
+ });
140
+ }
141
+ /**
142
+ * Mark a subtask as started
143
+ */
144
+ markRunning(plan, subtaskId) {
145
+ const task = plan.subtasks.find(t => t.id === subtaskId);
146
+ if (task) {
147
+ task.status = 'running';
148
+ task.metadata._startTime = Date.now();
149
+ this.emit('subtask:start', { planId: plan.id, subtaskId, description: task.description });
150
+ }
151
+ }
152
+ /**
153
+ * Mark a subtask as completed
154
+ */
155
+ markCompleted(plan, subtaskId, result) {
156
+ const task = plan.subtasks.find(t => t.id === subtaskId);
157
+ if (task) {
158
+ task.status = 'completed';
159
+ task.result = result;
160
+ if (task.metadata._startTime) {
161
+ task.actualDuration = (Date.now() - task.metadata._startTime) / 1000;
162
+ }
163
+ plan.updatedAt = new Date();
164
+ this.emit('subtask:complete', { planId: plan.id, subtaskId, result });
165
+ logger.info(`[TaskPlanner] Subtask completed: ${subtaskId}`);
166
+ }
167
+ // Check if all subtasks are done
168
+ if (plan.subtasks.every(t => t.status === 'completed' || t.status === 'skipped')) {
169
+ plan.status = 'completed';
170
+ this.emit('plan:complete', { planId: plan.id });
171
+ }
172
+ }
173
+ /**
174
+ * Handle subtask failure with self-correction
175
+ */
176
+ async handleFailure(plan, subtaskId, error) {
177
+ const task = plan.subtasks.find(t => t.id === subtaskId);
178
+ if (!task)
179
+ throw new Error(`Subtask ${subtaskId} not found`);
180
+ task.status = 'failed';
181
+ task.error = error;
182
+ logger.warn(`[TaskPlanner] Subtask failed: ${subtaskId} — ${error}`);
183
+ this.emit('subtask:fail', { planId: plan.id, subtaskId, error });
184
+ if (!this.config.enableSelfCorrection) {
185
+ const record = {
186
+ timestamp: new Date(),
187
+ failedSubtaskId: subtaskId,
188
+ errorType: this.classifyError(error),
189
+ errorMessage: error,
190
+ strategy: 'abort',
191
+ reasoning: 'Self-correction disabled',
192
+ };
193
+ plan.corrections.push(record);
194
+ plan.status = 'failed';
195
+ return record;
196
+ }
197
+ // Attempt self-correction
198
+ const correction = await this.selfCorrect(plan, task, error);
199
+ plan.corrections.push(correction);
200
+ switch (correction.strategy) {
201
+ case 'retry':
202
+ if (task.retryCount < task.maxRetries) {
203
+ task.retryCount++;
204
+ task.status = 'retrying';
205
+ task.error = undefined;
206
+ logger.info(`[TaskPlanner] Retrying subtask: ${subtaskId} (attempt ${task.retryCount})`);
207
+ }
208
+ else {
209
+ task.status = 'failed';
210
+ correction.strategy = 'skip'; // Downgrade to skip after max retries
211
+ }
212
+ break;
213
+ case 'alternative':
214
+ if (correction.newAction) {
215
+ // Create replacement subtask
216
+ const newTask = {
217
+ ...task,
218
+ id: `${task.id}-alt-${task.retryCount + 1}`,
219
+ description: correction.newDescription || task.description,
220
+ action: correction.newAction,
221
+ status: 'ready',
222
+ retryCount: 0,
223
+ error: undefined,
224
+ result: undefined,
225
+ metadata: { ...task.metadata, replacedSubtask: subtaskId },
226
+ };
227
+ // Insert after the failed task
228
+ const idx = plan.subtasks.findIndex(t => t.id === subtaskId);
229
+ plan.subtasks.splice(idx + 1, 0, newTask);
230
+ // Update dependencies pointing to old task
231
+ plan.subtasks.forEach(t => {
232
+ const depIdx = t.dependencies.indexOf(subtaskId);
233
+ if (depIdx >= 0)
234
+ t.dependencies[depIdx] = newTask.id;
235
+ });
236
+ task.status = 'skipped';
237
+ correction.newSubtaskId = newTask.id;
238
+ logger.info(`[TaskPlanner] Alternative strategy: ${newTask.id}`);
239
+ }
240
+ break;
241
+ case 'skip':
242
+ task.status = 'skipped';
243
+ logger.info(`[TaskPlanner] Skipping subtask: ${subtaskId}`);
244
+ break;
245
+ case 'replan':
246
+ const count = this.replanCount.get(plan.id) || 0;
247
+ if (count < this.config.maxReplans) {
248
+ this.replanCount.set(plan.id, count + 1);
249
+ plan.status = 'replanning';
250
+ await this.replan(plan);
251
+ logger.info(`[TaskPlanner] Replanned: ${plan.id}`);
252
+ }
253
+ else {
254
+ plan.status = 'failed';
255
+ logger.warn(`[TaskPlanner] Max replans reached for: ${plan.id}`);
256
+ }
257
+ break;
258
+ case 'abort':
259
+ plan.status = 'failed';
260
+ logger.warn(`[TaskPlanner] Plan aborted: ${plan.id}`);
261
+ break;
262
+ }
263
+ this.emit('correction', { planId: plan.id, correction });
264
+ return correction;
265
+ }
266
+ /**
267
+ * Self-correction: Analyze failure and decide recovery strategy
268
+ */
269
+ async selfCorrect(plan, failedTask, error) {
270
+ const errorType = this.classifyError(error);
271
+ // Fast path: transient errors → retry
272
+ if (errorType === 'transient') {
273
+ return {
274
+ timestamp: new Date(),
275
+ failedSubtaskId: failedTask.id,
276
+ errorType,
277
+ errorMessage: error,
278
+ strategy: 'retry',
279
+ reasoning: `Transient error detected (${errorType}). Will retry.`,
280
+ };
281
+ }
282
+ // Fast path: has alternatives → use them
283
+ if (failedTask.alternativeStrategies.length > 0 && errorType !== 'auth') {
284
+ try {
285
+ const messages = [
286
+ { role: 'system', content: SELF_CORRECTION_PROMPT },
287
+ {
288
+ role: 'user',
289
+ content: `Failed subtask: ${failedTask.description}
290
+ Action: ${JSON.stringify(failedTask.action)}
291
+ Error: ${error}
292
+ Error type: ${errorType}
293
+ Available alternatives: ${failedTask.alternativeStrategies.join('; ')}
294
+ Previous corrections: ${plan.corrections.length}
295
+
296
+ Decide the best recovery strategy.`,
297
+ },
298
+ ];
299
+ const response = await this.provider.complete(messages, 0.3, 512);
300
+ if (response.content) {
301
+ const parsed = this.parseCorrectionResponse(response.content);
302
+ return {
303
+ timestamp: new Date(),
304
+ failedSubtaskId: failedTask.id,
305
+ errorType,
306
+ errorMessage: error,
307
+ strategy: (parsed.strategy || 'skip'),
308
+ reasoning: parsed.reasoning || 'LLM-guided correction',
309
+ newAction: parsed.newAction,
310
+ newDescription: parsed.newDescription,
311
+ };
312
+ }
313
+ }
314
+ catch (e) {
315
+ logger.warn(`[TaskPlanner] Self-correction LLM call failed: ${e.message}`);
316
+ }
317
+ }
318
+ // Default fallback based on error type
319
+ return {
320
+ timestamp: new Date(),
321
+ failedSubtaskId: failedTask.id,
322
+ errorType,
323
+ errorMessage: error,
324
+ strategy: errorType === 'auth' ? 'abort' : 'skip',
325
+ reasoning: `Fallback: ${errorType} error with no alternatives available.`,
326
+ };
327
+ }
328
+ /**
329
+ * Replan: Create new subtasks for remaining work
330
+ */
331
+ async replan(plan) {
332
+ const completed = plan.subtasks
333
+ .filter(t => t.status === 'completed')
334
+ .map(t => `✓ ${t.description}: ${String(t.result).substring(0, 200)}`);
335
+ const failed = plan.subtasks
336
+ .filter(t => t.status === 'failed')
337
+ .map(t => `✗ ${t.description}: ${t.error}`);
338
+ const pending = plan.subtasks
339
+ .filter(t => t.status === 'pending' || t.status === 'ready')
340
+ .map(t => `○ ${t.description}`);
341
+ const messages = [
342
+ { role: 'system', content: PLANNING_PROMPT },
343
+ {
344
+ role: 'user',
345
+ content: `REPLANNING needed. Original goal: ${plan.goal.description}
346
+
347
+ Completed steps:
348
+ ${completed.join('\n')}
349
+
350
+ Failed steps:
351
+ ${failed.join('\n')}
352
+
353
+ Remaining steps (need revision):
354
+ ${pending.join('\n')}
355
+
356
+ Create a revised plan for the REMAINING work only. Do not repeat completed steps.`,
357
+ },
358
+ ];
359
+ const response = await this.provider.complete(messages, 0.3, 2048);
360
+ if (response.content) {
361
+ const newSubtasks = this.parseSubtasks(response.content, plan.goal.id);
362
+ // Remove pending/ready tasks and add new ones
363
+ plan.subtasks = plan.subtasks.filter(t => t.status === 'completed' || t.status === 'skipped' || t.status === 'failed');
364
+ plan.subtasks.push(...newSubtasks);
365
+ plan.executionOrder = this.topologicalSort(plan.subtasks);
366
+ plan.status = 'executing';
367
+ plan.updatedAt = new Date();
368
+ }
369
+ }
370
+ /**
371
+ * Classify error type for correction strategy
372
+ */
373
+ classifyError(error) {
374
+ const lower = error.toLowerCase();
375
+ if (lower.includes('timeout') || lower.includes('econnreset') || lower.includes('econnrefused'))
376
+ return 'transient';
377
+ if (lower.includes('404') || lower.includes('not found'))
378
+ return 'not_found';
379
+ if (lower.includes('403') || lower.includes('401') || lower.includes('unauthorized') || lower.includes('forbidden'))
380
+ return 'auth';
381
+ if (lower.includes('captcha') || lower.includes('rate limit') || lower.includes('429'))
382
+ return 'rate_limit';
383
+ if (lower.includes('syntax') || lower.includes('parse'))
384
+ return 'syntax';
385
+ if (lower.includes('permission') || lower.includes('eacces'))
386
+ return 'permission';
387
+ return 'unknown';
388
+ }
389
+ /**
390
+ * Parse subtasks from LLM response
391
+ */
392
+ parseSubtasks(content, goalId) {
393
+ try {
394
+ const jsonMatch = content.match(/\[[\s\S]*\]/);
395
+ if (!jsonMatch)
396
+ throw new Error('No JSON array found');
397
+ const parsed = JSON.parse(jsonMatch[0]);
398
+ if (!Array.isArray(parsed))
399
+ throw new Error('Not an array');
400
+ return parsed.slice(0, this.config.maxSubtasks).map((item, idx) => ({
401
+ id: item.id || `task-${idx + 1}`,
402
+ goalId,
403
+ description: item.description || '',
404
+ action: this.parseAction(item.action || item),
405
+ dependencies: Array.isArray(item.dependencies) ? item.dependencies : [],
406
+ status: 'pending',
407
+ retryCount: 0,
408
+ maxRetries: this.config.maxRetries,
409
+ estimatedDuration: item.estimatedDuration || 30,
410
+ alternativeStrategies: Array.isArray(item.alternativeStrategies) ? item.alternativeStrategies : [],
411
+ metadata: {},
412
+ }));
413
+ }
414
+ catch (e) {
415
+ logger.warn(`[TaskPlanner] Failed to parse subtasks: ${e.message}`);
416
+ // Fallback: single task
417
+ return [{
418
+ id: 'task-1',
419
+ goalId,
420
+ description: content.trim().substring(0, 500),
421
+ action: { type: 'llm_reason' },
422
+ dependencies: [],
423
+ status: 'pending',
424
+ retryCount: 0,
425
+ maxRetries: this.config.maxRetries,
426
+ estimatedDuration: 60,
427
+ alternativeStrategies: [],
428
+ metadata: {},
429
+ }];
430
+ }
431
+ }
432
+ /**
433
+ * Parse action from subtask data
434
+ */
435
+ parseAction(data) {
436
+ if (typeof data === 'object' && data.type) {
437
+ return {
438
+ type: data.type,
439
+ command: data.command,
440
+ params: data.params || {},
441
+ };
442
+ }
443
+ return { type: 'llm_reason' };
444
+ }
445
+ /**
446
+ * Parse correction response from LLM
447
+ */
448
+ parseCorrectionResponse(content) {
449
+ try {
450
+ const jsonMatch = content.match(/\{[\s\S]*\}/);
451
+ if (jsonMatch)
452
+ return JSON.parse(jsonMatch[0]);
453
+ }
454
+ catch (e) { /* fallback */ }
455
+ return { strategy: 'skip', reasoning: content.substring(0, 200) };
456
+ }
457
+ /**
458
+ * Topological sort of subtask dependency graph
459
+ */
460
+ topologicalSort(subtasks) {
461
+ const graph = new Map();
462
+ const inDegree = new Map();
463
+ for (const task of subtasks) {
464
+ if (!graph.has(task.id))
465
+ graph.set(task.id, []);
466
+ if (!inDegree.has(task.id))
467
+ inDegree.set(task.id, 0);
468
+ for (const dep of task.dependencies) {
469
+ if (!graph.has(dep))
470
+ graph.set(dep, []);
471
+ graph.get(dep).push(task.id);
472
+ inDegree.set(task.id, (inDegree.get(task.id) || 0) + 1);
473
+ }
474
+ }
475
+ const queue = [];
476
+ for (const [id, degree] of inDegree) {
477
+ if (degree === 0)
478
+ queue.push(id);
479
+ }
480
+ const sorted = [];
481
+ while (queue.length > 0) {
482
+ const current = queue.shift();
483
+ sorted.push(current);
484
+ for (const neighbor of graph.get(current) || []) {
485
+ const newDegree = (inDegree.get(neighbor) || 1) - 1;
486
+ inDegree.set(neighbor, newDegree);
487
+ if (newDegree === 0)
488
+ queue.push(neighbor);
489
+ }
490
+ }
491
+ return sorted;
492
+ }
493
+ /**
494
+ * Get plan status summary
495
+ */
496
+ getPlanSummary(plan) {
497
+ const total = plan.subtasks.length;
498
+ const completed = plan.subtasks.filter(t => t.status === 'completed').length;
499
+ const failed = plan.subtasks.filter(t => t.status === 'failed').length;
500
+ const pending = plan.subtasks.filter(t => t.status === 'pending' || t.status === 'ready').length;
501
+ const running = plan.subtasks.filter(t => t.status === 'running').length;
502
+ return {
503
+ total,
504
+ completed,
505
+ failed,
506
+ pending,
507
+ running,
508
+ corrections: plan.corrections.length,
509
+ progress: total > 0 ? Math.round((completed / total) * 100) : 0,
510
+ };
511
+ }
512
+ /**
513
+ * Get active plan by ID
514
+ */
515
+ getPlan(planId) {
516
+ return this.activePlans.get(planId);
517
+ }
518
+ }
519
+ exports.TaskPlanner = TaskPlanner;
520
+ //# sourceMappingURL=task-planner.js.map