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.
- package/dist/api-integrations.d.ts +137 -0
- package/dist/api-integrations.d.ts.map +1 -0
- package/dist/api-integrations.js +348 -0
- package/dist/api-integrations.js.map +1 -0
- package/dist/chain-of-thought.d.ts +136 -0
- package/dist/chain-of-thought.d.ts.map +1 -0
- package/dist/chain-of-thought.js +337 -0
- package/dist/chain-of-thought.js.map +1 -0
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/code-sandbox.d.ts +81 -0
- package/dist/code-sandbox.d.ts.map +1 -0
- package/dist/code-sandbox.js +363 -0
- package/dist/code-sandbox.js.map +1 -0
- package/dist/lib.d.ts +22 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +63 -0
- package/dist/lib.js.map +1 -0
- package/dist/orchestrator.d.ts +28 -3
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +200 -20
- package/dist/orchestrator.js.map +1 -1
- package/dist/session-memory.d.ts +116 -0
- package/dist/session-memory.d.ts.map +1 -0
- package/dist/session-memory.js +351 -0
- package/dist/session-memory.js.map +1 -0
- package/dist/static/dashboard.html +1 -1
- package/dist/static/hub.html +1 -1
- package/dist/static/index.html +130 -5
- package/dist/task-planner.d.ts +145 -0
- package/dist/task-planner.d.ts.map +1 -0
- package/dist/task-planner.js +520 -0
- package/dist/task-planner.js.map +1 -0
- package/dist/web.d.ts +6 -2
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +186 -4
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|