@defai.digital/ax-cli 2.8.0 → 3.0.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 +85 -9
- package/dist/agent/context-manager.js +9 -0
- package/dist/agent/context-manager.js.map +1 -1
- package/dist/agent/dependency-resolver.js +13 -5
- package/dist/agent/dependency-resolver.js.map +1 -1
- package/dist/agent/llm-agent.d.ts +50 -0
- package/dist/agent/llm-agent.js +539 -3
- package/dist/agent/llm-agent.js.map +1 -1
- package/dist/agent/subagent-orchestrator.d.ts +4 -0
- package/dist/agent/subagent-orchestrator.js +54 -11
- package/dist/agent/subagent-orchestrator.js.map +1 -1
- package/dist/agent/subagent.js +35 -11
- package/dist/agent/subagent.js.map +1 -1
- package/dist/checkpoint/manager.js +4 -0
- package/dist/checkpoint/manager.js.map +1 -1
- package/dist/commands/cache.js +5 -3
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/memory.js +21 -16
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/plan.d.ts +43 -0
- package/dist/commands/plan.js +385 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/constants.d.ts +32 -0
- package/dist/constants.js +33 -0
- package/dist/constants.js.map +1 -1
- package/dist/hooks/use-enhanced-input.d.ts +5 -1
- package/dist/hooks/use-enhanced-input.js +23 -10
- package/dist/hooks/use-enhanced-input.js.map +1 -1
- package/dist/hooks/use-input-handler.d.ts +11 -1
- package/dist/hooks/use-input-handler.js +270 -2
- package/dist/hooks/use-input-handler.js.map +1 -1
- package/dist/llm/tools.d.ts +5 -0
- package/dist/llm/tools.js +57 -6
- package/dist/llm/tools.js.map +1 -1
- package/dist/mcp/client.js +19 -12
- package/dist/mcp/client.js.map +1 -1
- package/dist/planner/dependency-resolver.d.ts +72 -0
- package/dist/planner/dependency-resolver.js +272 -0
- package/dist/planner/dependency-resolver.js.map +1 -0
- package/dist/planner/index.d.ts +12 -0
- package/dist/planner/index.js +26 -0
- package/dist/planner/index.js.map +1 -0
- package/dist/planner/plan-generator.d.ts +74 -0
- package/dist/planner/plan-generator.js +244 -0
- package/dist/planner/plan-generator.js.map +1 -0
- package/dist/planner/plan-storage.d.ts +98 -0
- package/dist/planner/plan-storage.js +330 -0
- package/dist/planner/plan-storage.js.map +1 -0
- package/dist/planner/prompts/planning-prompt.d.ts +41 -0
- package/dist/planner/prompts/planning-prompt.js +289 -0
- package/dist/planner/prompts/planning-prompt.js.map +1 -0
- package/dist/planner/task-planner.d.ts +135 -0
- package/dist/planner/task-planner.js +497 -0
- package/dist/planner/task-planner.js.map +1 -0
- package/dist/planner/token-estimator.d.ts +63 -0
- package/dist/planner/token-estimator.js +295 -0
- package/dist/planner/token-estimator.js.map +1 -0
- package/dist/planner/types.d.ts +669 -0
- package/dist/planner/types.js +213 -0
- package/dist/planner/types.js.map +1 -0
- package/dist/schemas/confirmation-schemas.d.ts +5 -0
- package/dist/schemas/confirmation-schemas.js +7 -0
- package/dist/schemas/confirmation-schemas.js.map +1 -1
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/tool-schemas.d.ts +9 -1
- package/dist/schemas/tool-schemas.js +6 -1
- package/dist/schemas/tool-schemas.js.map +1 -1
- package/dist/tools/bash-output.d.ts +25 -0
- package/dist/tools/bash-output.js +145 -0
- package/dist/tools/bash-output.js.map +1 -0
- package/dist/tools/bash.d.ts +46 -2
- package/dist/tools/bash.js +241 -67
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/search.js +15 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/text-editor.js +4 -2
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/ui/components/chat-history.d.ts +1 -0
- package/dist/ui/components/chat-history.js +125 -41
- package/dist/ui/components/chat-history.js.map +1 -1
- package/dist/ui/components/chat-input.js +10 -3
- package/dist/ui/components/chat-input.js.map +1 -1
- package/dist/ui/components/chat-interface.js +152 -44
- package/dist/ui/components/chat-interface.js.map +1 -1
- package/dist/ui/components/collapsible-tool-result.d.ts +26 -0
- package/dist/ui/components/collapsible-tool-result.js +172 -0
- package/dist/ui/components/collapsible-tool-result.js.map +1 -0
- package/dist/ui/components/command-suggestions.js +2 -1
- package/dist/ui/components/command-suggestions.js.map +1 -1
- package/dist/ui/components/confirmation-dialog.js +25 -36
- package/dist/ui/components/confirmation-dialog.js.map +1 -1
- package/dist/ui/components/index.d.ts +8 -0
- package/dist/ui/components/index.js +9 -0
- package/dist/ui/components/index.js.map +1 -1
- package/dist/ui/components/keyboard-hints.d.ts +35 -0
- package/dist/ui/components/keyboard-hints.js +134 -0
- package/dist/ui/components/keyboard-hints.js.map +1 -0
- package/dist/ui/components/loading-spinner.d.ts +2 -1
- package/dist/ui/components/loading-spinner.js +86 -34
- package/dist/ui/components/loading-spinner.js.map +1 -1
- package/dist/ui/components/phase-progress.d.ts +21 -0
- package/dist/ui/components/phase-progress.js +228 -0
- package/dist/ui/components/phase-progress.js.map +1 -0
- package/dist/ui/components/quick-actions.d.ts +12 -0
- package/dist/ui/components/quick-actions.js +124 -0
- package/dist/ui/components/quick-actions.js.map +1 -0
- package/dist/ui/components/reasoning-display.d.ts +0 -80
- package/dist/ui/components/reasoning-display.js +0 -83
- package/dist/ui/components/reasoning-display.js.map +1 -1
- package/dist/ui/components/status-bar.d.ts +25 -0
- package/dist/ui/components/status-bar.js +125 -0
- package/dist/ui/components/status-bar.js.map +1 -0
- package/dist/ui/components/toast-notification.d.ts +123 -0
- package/dist/ui/components/toast-notification.js +148 -0
- package/dist/ui/components/toast-notification.js.map +1 -0
- package/dist/ui/components/welcome-panel.d.ts +10 -0
- package/dist/ui/components/welcome-panel.js +107 -0
- package/dist/ui/components/welcome-panel.js.map +1 -0
- package/dist/utils/background-task-manager.d.ts +95 -0
- package/dist/utils/background-task-manager.js +330 -0
- package/dist/utils/background-task-manager.js.map +1 -0
- package/dist/utils/confirmation-service.js +8 -3
- package/dist/utils/confirmation-service.js.map +1 -1
- package/dist/utils/history-manager.d.ts +2 -0
- package/dist/utils/history-manager.js +8 -1
- package/dist/utils/history-manager.js.map +1 -1
- package/dist/utils/incremental-analyzer.js +11 -3
- package/dist/utils/incremental-analyzer.js.map +1 -1
- package/dist/utils/message-optimizer.d.ts +1 -0
- package/dist/utils/message-optimizer.js +7 -1
- package/dist/utils/message-optimizer.js.map +1 -1
- package/dist/utils/project-analyzer.js +5 -2
- package/dist/utils/project-analyzer.js.map +1 -1
- package/package.json +2 -1
package/dist/agent/llm-agent.js
CHANGED
|
@@ -2,6 +2,7 @@ import { LLMClient } from "../llm/client.js";
|
|
|
2
2
|
import { getAllGrokTools, getMCPManager, initializeMCPServers, } from "../llm/tools.js";
|
|
3
3
|
import { loadMCPConfig } from "../mcp/config.js";
|
|
4
4
|
import { TextEditorTool, BashTool, TodoTool, SearchTool, } from "../tools/index.js";
|
|
5
|
+
import { BashOutputTool } from "../tools/bash-output.js";
|
|
5
6
|
import { EventEmitter } from "events";
|
|
6
7
|
import { AGENT_CONFIG } from "../constants.js";
|
|
7
8
|
import { createTokenCounter } from "../utils/token-counter.js";
|
|
@@ -13,10 +14,13 @@ import { getUsageTracker } from "../utils/usage-tracker.js";
|
|
|
13
14
|
import { extractErrorMessage } from "../utils/error-handler.js";
|
|
14
15
|
import { getCheckpointManager } from "../checkpoint/index.js";
|
|
15
16
|
import { SubagentOrchestrator } from "./subagent-orchestrator.js";
|
|
17
|
+
import { getTaskPlanner, isComplexRequest, } from "../planner/index.js";
|
|
18
|
+
import { PLANNER_CONFIG } from "../constants.js";
|
|
16
19
|
export class LLMAgent extends EventEmitter {
|
|
17
20
|
llmClient;
|
|
18
21
|
textEditor;
|
|
19
22
|
bash;
|
|
23
|
+
bashOutput;
|
|
20
24
|
todoTool;
|
|
21
25
|
search;
|
|
22
26
|
chatHistory = [];
|
|
@@ -28,6 +32,9 @@ export class LLMAgent extends EventEmitter {
|
|
|
28
32
|
recentToolCalls = new Map(); // Track recent tool calls to detect loops
|
|
29
33
|
checkpointManager;
|
|
30
34
|
subagentOrchestrator;
|
|
35
|
+
taskPlanner;
|
|
36
|
+
currentPlan = null;
|
|
37
|
+
planningEnabled = PLANNER_CONFIG.ENABLED;
|
|
31
38
|
constructor(apiKey, baseURL, model, maxToolRounds) {
|
|
32
39
|
super();
|
|
33
40
|
const manager = getSettingsManager();
|
|
@@ -40,12 +47,14 @@ export class LLMAgent extends EventEmitter {
|
|
|
40
47
|
this.llmClient = new LLMClient(apiKey, modelToUse, baseURL);
|
|
41
48
|
this.textEditor = new TextEditorTool();
|
|
42
49
|
this.bash = new BashTool();
|
|
50
|
+
this.bashOutput = new BashOutputTool();
|
|
43
51
|
this.todoTool = new TodoTool();
|
|
44
52
|
this.search = new SearchTool();
|
|
45
53
|
this.tokenCounter = createTokenCounter(modelToUse);
|
|
46
54
|
this.contextManager = new ContextManager({ model: modelToUse });
|
|
47
55
|
this.checkpointManager = getCheckpointManager();
|
|
48
56
|
this.subagentOrchestrator = new SubagentOrchestrator({ maxConcurrentAgents: 5 });
|
|
57
|
+
this.taskPlanner = getTaskPlanner();
|
|
49
58
|
// Wire up checkpoint callback for automatic checkpoint creation
|
|
50
59
|
this.textEditor.setCheckpointCallback(async (files, description) => {
|
|
51
60
|
await this.checkpointManager.createCheckpoint({
|
|
@@ -185,8 +194,7 @@ export class LLMAgent extends EventEmitter {
|
|
|
185
194
|
// Clean up old entries (keep only last N unique calls)
|
|
186
195
|
if (this.recentToolCalls.size > AGENT_CONFIG.MAX_RECENT_TOOL_CALLS) {
|
|
187
196
|
const firstKey = this.recentToolCalls.keys().next().value;
|
|
188
|
-
|
|
189
|
-
if (firstKey) {
|
|
197
|
+
if (firstKey !== undefined) {
|
|
190
198
|
this.recentToolCalls.delete(firstKey);
|
|
191
199
|
}
|
|
192
200
|
}
|
|
@@ -238,6 +246,509 @@ export class LLMAgent extends EventEmitter {
|
|
|
238
246
|
return true;
|
|
239
247
|
return false;
|
|
240
248
|
}
|
|
249
|
+
// ============================================================================
|
|
250
|
+
// Multi-Phase Planning Integration
|
|
251
|
+
// ============================================================================
|
|
252
|
+
/**
|
|
253
|
+
* Check if a request should trigger multi-phase planning
|
|
254
|
+
*/
|
|
255
|
+
shouldCreatePlan(message) {
|
|
256
|
+
if (!this.planningEnabled)
|
|
257
|
+
return false;
|
|
258
|
+
return isComplexRequest(message);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get the current plan if any
|
|
262
|
+
*/
|
|
263
|
+
getCurrentPlan() {
|
|
264
|
+
return this.currentPlan;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Execute a single phase using the LLM
|
|
268
|
+
*/
|
|
269
|
+
async executePhase(phase, context) {
|
|
270
|
+
const startTime = Date.now();
|
|
271
|
+
const startTokens = this.tokenCounter.countMessageTokens(this.messages);
|
|
272
|
+
const filesModified = [];
|
|
273
|
+
let lastAssistantContent = "";
|
|
274
|
+
// Emit phase started event
|
|
275
|
+
this.emit("phase:started", { phase, planId: context.planId });
|
|
276
|
+
try {
|
|
277
|
+
// Build phase-specific prompt
|
|
278
|
+
const phasePrompt = this.buildPhasePrompt(phase, context);
|
|
279
|
+
// Execute through normal message processing (without recursively planning)
|
|
280
|
+
const savedPlanningState = this.planningEnabled;
|
|
281
|
+
this.planningEnabled = false; // Temporarily disable planning for phase execution
|
|
282
|
+
// Add phase context to messages
|
|
283
|
+
this.messages.push({
|
|
284
|
+
role: "user",
|
|
285
|
+
content: phasePrompt,
|
|
286
|
+
});
|
|
287
|
+
// Execute using the standard tool loop
|
|
288
|
+
const tools = await getAllGrokTools();
|
|
289
|
+
let toolRounds = 0;
|
|
290
|
+
const maxPhaseRounds = Math.min(this.maxToolRounds, 50); // Limit per phase
|
|
291
|
+
while (toolRounds < maxPhaseRounds) {
|
|
292
|
+
const response = await this.llmClient.chat(this.messages, tools);
|
|
293
|
+
const assistantMessage = response.choices[0]?.message;
|
|
294
|
+
if (!assistantMessage)
|
|
295
|
+
break;
|
|
296
|
+
// Capture the assistant's content for phase output
|
|
297
|
+
if (assistantMessage.content) {
|
|
298
|
+
lastAssistantContent = assistantMessage.content;
|
|
299
|
+
}
|
|
300
|
+
// Add to messages
|
|
301
|
+
this.messages.push({
|
|
302
|
+
role: "assistant",
|
|
303
|
+
content: assistantMessage.content || "",
|
|
304
|
+
tool_calls: assistantMessage.tool_calls,
|
|
305
|
+
});
|
|
306
|
+
// Check for tool calls
|
|
307
|
+
if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
|
|
308
|
+
break; // No more tool calls, phase complete
|
|
309
|
+
}
|
|
310
|
+
toolRounds++;
|
|
311
|
+
// Execute tools and track file modifications
|
|
312
|
+
for (const toolCall of assistantMessage.tool_calls) {
|
|
313
|
+
const result = await this.executeTool(toolCall);
|
|
314
|
+
// Track file modifications from text_editor tool
|
|
315
|
+
if (toolCall.function.name === "text_editor" ||
|
|
316
|
+
toolCall.function.name === "str_replace_editor") {
|
|
317
|
+
try {
|
|
318
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
319
|
+
if (args.path && result.success) {
|
|
320
|
+
if (!filesModified.includes(args.path)) {
|
|
321
|
+
filesModified.push(args.path);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
// Ignore parse errors
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
this.messages.push({
|
|
330
|
+
role: "tool",
|
|
331
|
+
tool_call_id: toolCall.id,
|
|
332
|
+
content: result.output || result.error || "No output",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Restore planning state
|
|
337
|
+
this.planningEnabled = savedPlanningState;
|
|
338
|
+
// Prune context if configured
|
|
339
|
+
if (PLANNER_CONFIG.PRUNE_AFTER_PHASE) {
|
|
340
|
+
if (this.contextManager.shouldPrune(this.messages, this.tokenCounter)) {
|
|
341
|
+
this.messages = this.contextManager.pruneMessages(this.messages, this.tokenCounter);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const endTokens = this.tokenCounter.countMessageTokens(this.messages);
|
|
345
|
+
const duration = Date.now() - startTime;
|
|
346
|
+
// Build meaningful output
|
|
347
|
+
const output = lastAssistantContent ||
|
|
348
|
+
`Phase "${phase.name}" completed (${toolRounds} tool rounds, ${filesModified.length} files modified)`;
|
|
349
|
+
// Emit phase completed event
|
|
350
|
+
this.emit("phase:completed", {
|
|
351
|
+
phase,
|
|
352
|
+
planId: context.planId,
|
|
353
|
+
result: { success: true, output, filesModified }
|
|
354
|
+
});
|
|
355
|
+
return {
|
|
356
|
+
phaseId: phase.id,
|
|
357
|
+
success: true,
|
|
358
|
+
output,
|
|
359
|
+
duration,
|
|
360
|
+
tokensUsed: endTokens - startTokens,
|
|
361
|
+
filesModified,
|
|
362
|
+
wasRetry: false,
|
|
363
|
+
retryAttempt: 0,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
const duration = Date.now() - startTime;
|
|
368
|
+
const errorMessage = extractErrorMessage(error);
|
|
369
|
+
// Emit phase failed event
|
|
370
|
+
this.emit("phase:failed", {
|
|
371
|
+
phase,
|
|
372
|
+
planId: context.planId,
|
|
373
|
+
error: errorMessage
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
phaseId: phase.id,
|
|
377
|
+
success: false,
|
|
378
|
+
error: errorMessage,
|
|
379
|
+
duration,
|
|
380
|
+
tokensUsed: 0,
|
|
381
|
+
filesModified,
|
|
382
|
+
wasRetry: false,
|
|
383
|
+
retryAttempt: 0,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Build a prompt for phase execution
|
|
389
|
+
*/
|
|
390
|
+
buildPhasePrompt(phase, context) {
|
|
391
|
+
let prompt = `## Phase ${phase.index + 1}: ${phase.name}\n\n`;
|
|
392
|
+
prompt += `**Objective:** ${phase.description}\n\n`;
|
|
393
|
+
if (phase.objectives.length > 0) {
|
|
394
|
+
prompt += "**Tasks to complete:**\n";
|
|
395
|
+
for (const obj of phase.objectives) {
|
|
396
|
+
prompt += `- ${obj}\n`;
|
|
397
|
+
}
|
|
398
|
+
prompt += "\n";
|
|
399
|
+
}
|
|
400
|
+
if (context.completedPhases.length > 0) {
|
|
401
|
+
prompt += `**Previously completed phases:** ${context.completedPhases.join(", ")}\n\n`;
|
|
402
|
+
}
|
|
403
|
+
prompt += `**Original request:** ${context.originalRequest}\n\n`;
|
|
404
|
+
prompt += "Please complete this phase. Focus only on the objectives listed above.";
|
|
405
|
+
return prompt;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Generate and execute a plan for a complex request
|
|
409
|
+
* Uses TodoWrite for Claude Code-style seamless progress display
|
|
410
|
+
*/
|
|
411
|
+
async *processWithPlanning(message) {
|
|
412
|
+
// Add user message to history
|
|
413
|
+
const userEntry = {
|
|
414
|
+
type: "user",
|
|
415
|
+
content: message,
|
|
416
|
+
timestamp: new Date(),
|
|
417
|
+
};
|
|
418
|
+
this.chatHistory.push(userEntry);
|
|
419
|
+
this.messages.push({ role: "user", content: message });
|
|
420
|
+
// Silent mode: no explicit banner, just start working
|
|
421
|
+
if (!PLANNER_CONFIG.SILENT_MODE) {
|
|
422
|
+
yield {
|
|
423
|
+
type: "content",
|
|
424
|
+
content: "📋 **Analyzing request and creating execution plan...**\n\n",
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
// Generate plan using LLM
|
|
429
|
+
const plan = await this.taskPlanner.generatePlan(message, async (systemPrompt, userPrompt) => {
|
|
430
|
+
const planMessages = [
|
|
431
|
+
{ role: "system", content: systemPrompt },
|
|
432
|
+
{ role: "user", content: userPrompt },
|
|
433
|
+
];
|
|
434
|
+
const response = await this.llmClient.chat(planMessages, []);
|
|
435
|
+
return response.choices[0]?.message?.content || "";
|
|
436
|
+
}, {
|
|
437
|
+
projectType: "typescript", // Could be detected
|
|
438
|
+
});
|
|
439
|
+
if (!plan) {
|
|
440
|
+
yield {
|
|
441
|
+
type: "content",
|
|
442
|
+
content: "Could not generate a plan. Processing as single request...\n\n",
|
|
443
|
+
};
|
|
444
|
+
// Fall back to normal processing - disable planning and retry
|
|
445
|
+
this.planningEnabled = false;
|
|
446
|
+
yield* this.processUserMessageStreamInternal(message);
|
|
447
|
+
this.planningEnabled = true;
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
this.currentPlan = plan;
|
|
451
|
+
// Emit plan created event
|
|
452
|
+
this.emit("plan:created", { plan });
|
|
453
|
+
// Create TodoWrite items for phases (Claude Code-style progress)
|
|
454
|
+
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
455
|
+
// Use TodoWrite to show phases as natural todo items
|
|
456
|
+
const todoItems = plan.phases.map((phase, index) => ({
|
|
457
|
+
id: `phase-${index}`,
|
|
458
|
+
content: phase.name,
|
|
459
|
+
status: index === 0 ? "in_progress" : "pending",
|
|
460
|
+
priority: phase.riskLevel === "high" ? "high" :
|
|
461
|
+
phase.riskLevel === "low" ? "low" : "medium",
|
|
462
|
+
}));
|
|
463
|
+
try {
|
|
464
|
+
await this.todoTool.createTodoList(todoItems);
|
|
465
|
+
}
|
|
466
|
+
catch (todoError) {
|
|
467
|
+
// TodoWrite failure is non-critical, continue execution
|
|
468
|
+
console.warn("TodoWrite create failed:", extractErrorMessage(todoError));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Display explicit plan summary
|
|
473
|
+
yield {
|
|
474
|
+
type: "content",
|
|
475
|
+
content: this.formatPlanSummary(plan),
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
// Execute phases one by one with progress updates
|
|
479
|
+
const phaseResults = [];
|
|
480
|
+
let totalTokensUsed = 0;
|
|
481
|
+
const planStartTime = Date.now();
|
|
482
|
+
for (let i = 0; i < plan.phases.length; i++) {
|
|
483
|
+
const phase = plan.phases[i];
|
|
484
|
+
plan.currentPhaseIndex = i;
|
|
485
|
+
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
486
|
+
// Update TodoWrite: mark current phase as in_progress
|
|
487
|
+
try {
|
|
488
|
+
await this.todoTool.updateTodoList([{
|
|
489
|
+
id: `phase-${i}`,
|
|
490
|
+
status: "in_progress",
|
|
491
|
+
}]);
|
|
492
|
+
}
|
|
493
|
+
catch { /* TodoWrite update is non-critical */ }
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
// Show explicit phase starting banner
|
|
497
|
+
yield {
|
|
498
|
+
type: "content",
|
|
499
|
+
content: `\n**⏳ Phase ${i + 1}/${plan.phases.length}: ${phase.name}**\n`,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
// Execute the phase
|
|
503
|
+
const context = {
|
|
504
|
+
planId: plan.id,
|
|
505
|
+
originalRequest: message,
|
|
506
|
+
completedPhases: phaseResults.filter(r => r.success).map(r => r.phaseId),
|
|
507
|
+
};
|
|
508
|
+
const result = await this.executePhase(phase, context);
|
|
509
|
+
phaseResults.push(result);
|
|
510
|
+
totalTokensUsed += result.tokensUsed;
|
|
511
|
+
// Report phase result
|
|
512
|
+
if (result.success) {
|
|
513
|
+
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
514
|
+
// Update TodoWrite: mark phase as completed
|
|
515
|
+
try {
|
|
516
|
+
await this.todoTool.updateTodoList([{
|
|
517
|
+
id: `phase-${i}`,
|
|
518
|
+
status: "completed",
|
|
519
|
+
}]);
|
|
520
|
+
}
|
|
521
|
+
catch { /* TodoWrite update is non-critical */ }
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
yield {
|
|
525
|
+
type: "content",
|
|
526
|
+
content: `✓ Phase ${i + 1} completed (${Math.ceil(result.duration / 1000)}s)\n`,
|
|
527
|
+
};
|
|
528
|
+
if (result.filesModified.length > 0) {
|
|
529
|
+
yield {
|
|
530
|
+
type: "content",
|
|
531
|
+
content: ` Files modified: ${result.filesModified.join(", ")}\n`,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
if (PLANNER_CONFIG.SILENT_MODE) {
|
|
538
|
+
// Update TodoWrite: mark phase as failed (update content to show failure)
|
|
539
|
+
try {
|
|
540
|
+
await this.todoTool.updateTodoList([{
|
|
541
|
+
id: `phase-${i}`,
|
|
542
|
+
status: "completed", // Mark as done even if failed
|
|
543
|
+
content: `${phase.name} (failed)`,
|
|
544
|
+
}]);
|
|
545
|
+
}
|
|
546
|
+
catch { /* TodoWrite update is non-critical */ }
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
yield {
|
|
550
|
+
type: "content",
|
|
551
|
+
content: `✕ Phase ${i + 1} failed: ${result.error}\n`,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
// Continue with next phase unless abort strategy
|
|
555
|
+
if (phase.fallbackStrategy === "abort") {
|
|
556
|
+
if (!PLANNER_CONFIG.SILENT_MODE) {
|
|
557
|
+
yield {
|
|
558
|
+
type: "content",
|
|
559
|
+
content: `\n⚠️ Plan aborted due to phase failure.\n`,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const totalDuration = Date.now() - planStartTime;
|
|
567
|
+
// Build final result
|
|
568
|
+
const successfulPhases = phaseResults.filter(r => r.success);
|
|
569
|
+
const failedPhases = phaseResults.filter(r => !r.success);
|
|
570
|
+
const allFilesModified = [...new Set(phaseResults.flatMap(r => r.filesModified))];
|
|
571
|
+
const summary = successfulPhases.length === phaseResults.length
|
|
572
|
+
? `All ${phaseResults.length} phases completed successfully. ${allFilesModified.length} files modified.`
|
|
573
|
+
: `${successfulPhases.length}/${phaseResults.length} phases completed. ${failedPhases.length} failed.`;
|
|
574
|
+
const warnings = [];
|
|
575
|
+
for (const result of failedPhases) {
|
|
576
|
+
warnings.push(`Phase ${result.phaseId} failed: ${result.error || "Unknown error"}`);
|
|
577
|
+
}
|
|
578
|
+
const planResult = {
|
|
579
|
+
planId: plan.id,
|
|
580
|
+
success: phaseResults.every(r => r.success),
|
|
581
|
+
phaseResults,
|
|
582
|
+
totalDuration,
|
|
583
|
+
totalTokensUsed,
|
|
584
|
+
summary,
|
|
585
|
+
warnings,
|
|
586
|
+
};
|
|
587
|
+
// Report final results (silent mode shows minimal output)
|
|
588
|
+
if (!PLANNER_CONFIG.SILENT_MODE) {
|
|
589
|
+
yield {
|
|
590
|
+
type: "content",
|
|
591
|
+
content: this.formatPlanResult(planResult),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
// Brief completion message in silent mode
|
|
596
|
+
const successCount = phaseResults.filter(r => r.success).length;
|
|
597
|
+
if (successCount === phaseResults.length) {
|
|
598
|
+
yield {
|
|
599
|
+
type: "content",
|
|
600
|
+
content: `\n✓ All ${phaseResults.length} tasks completed successfully.\n`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
yield {
|
|
605
|
+
type: "content",
|
|
606
|
+
content: `\n⚠️ ${successCount}/${phaseResults.length} tasks completed. Check todo list for details.\n`,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// Emit plan completed event
|
|
611
|
+
this.emit("plan:completed", { plan, result: planResult });
|
|
612
|
+
this.currentPlan = null;
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
// Defensive error extraction to prevent nested failures
|
|
616
|
+
let errorMsg;
|
|
617
|
+
try {
|
|
618
|
+
errorMsg = extractErrorMessage(error);
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
errorMsg = String(error) || "Unknown error";
|
|
622
|
+
}
|
|
623
|
+
yield {
|
|
624
|
+
type: "content",
|
|
625
|
+
content: `\n⚠️ Plan execution error: ${errorMsg}\n`,
|
|
626
|
+
};
|
|
627
|
+
this.emit("plan:failed", { error: errorMsg });
|
|
628
|
+
this.currentPlan = null;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Internal streaming processor (used when planning falls back)
|
|
633
|
+
* Executes the core message processing loop without planning
|
|
634
|
+
*/
|
|
635
|
+
async *processUserMessageStreamInternal(message) {
|
|
636
|
+
// Reset tool call tracking for new message
|
|
637
|
+
this.resetToolCallTracking();
|
|
638
|
+
// Prepare user message and get input tokens
|
|
639
|
+
const inputTokensRef = { value: this.prepareUserMessageForStreaming(message) };
|
|
640
|
+
yield {
|
|
641
|
+
type: "token_count",
|
|
642
|
+
tokenCount: inputTokensRef.value,
|
|
643
|
+
};
|
|
644
|
+
// Yield context warnings if needed
|
|
645
|
+
yield* this.yieldContextWarnings();
|
|
646
|
+
const maxToolRounds = this.maxToolRounds;
|
|
647
|
+
let toolRounds = 0;
|
|
648
|
+
const totalOutputTokensRef = { value: 0 };
|
|
649
|
+
const lastTokenUpdateRef = { value: 0 };
|
|
650
|
+
try {
|
|
651
|
+
// Agent loop - continue until no more tool calls or max rounds reached
|
|
652
|
+
while (toolRounds < maxToolRounds) {
|
|
653
|
+
// Check if operation was cancelled
|
|
654
|
+
if (this.isCancelled()) {
|
|
655
|
+
yield* this.yieldCancellation();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
// Load tools safely
|
|
659
|
+
const tools = await this.loadToolsSafely();
|
|
660
|
+
// Create chat stream
|
|
661
|
+
const stream = this.llmClient.chatStream(this.messages, tools, {
|
|
662
|
+
searchOptions: this.isGrokModel() && this.shouldUseSearchFor(message)
|
|
663
|
+
? { search_parameters: { mode: "auto" } }
|
|
664
|
+
: { search_parameters: { mode: "off" } }
|
|
665
|
+
});
|
|
666
|
+
// Process streaming chunks
|
|
667
|
+
const chunkGen = this.processStreamingChunks(stream, inputTokensRef.value, lastTokenUpdateRef, totalOutputTokensRef);
|
|
668
|
+
let streamResult;
|
|
669
|
+
for await (const chunk of chunkGen) {
|
|
670
|
+
if ('accumulated' in chunk) {
|
|
671
|
+
streamResult = chunk;
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
yield chunk;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (!streamResult) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
// Add assistant message to history
|
|
681
|
+
this.addAssistantMessage(streamResult.accumulated);
|
|
682
|
+
// Handle tool calls if present
|
|
683
|
+
if (streamResult.accumulated.tool_calls?.length > 0) {
|
|
684
|
+
toolRounds++;
|
|
685
|
+
// Check for repetitive tool calls (loop detection)
|
|
686
|
+
const hasRepetitiveCall = streamResult.accumulated.tool_calls.some((tc) => this.isRepetitiveToolCall(tc));
|
|
687
|
+
if (hasRepetitiveCall) {
|
|
688
|
+
yield {
|
|
689
|
+
type: "content",
|
|
690
|
+
content: "\n\n⚠️ Detected repetitive tool calls. Stopping to prevent infinite loop.\n",
|
|
691
|
+
};
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
yield* this.executeToolCalls(streamResult.accumulated.tool_calls, streamResult.yielded, inputTokensRef, totalOutputTokensRef);
|
|
695
|
+
// Continue loop to get next response
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
// No tool calls, we're done
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
const errorMsg = extractErrorMessage(error);
|
|
705
|
+
yield {
|
|
706
|
+
type: "content",
|
|
707
|
+
content: `\n⚠️ Error processing message: ${errorMsg}\n`,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
// Final token count
|
|
711
|
+
yield {
|
|
712
|
+
type: "token_count",
|
|
713
|
+
tokenCount: inputTokensRef.value + totalOutputTokensRef.value,
|
|
714
|
+
};
|
|
715
|
+
yield { type: "done" };
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Format plan summary for display
|
|
719
|
+
*/
|
|
720
|
+
formatPlanSummary(plan) {
|
|
721
|
+
let output = `**📋 Execution Plan Created**\n\n`;
|
|
722
|
+
output += `**Request:** ${plan.originalPrompt.slice(0, 100)}${plan.originalPrompt.length > 100 ? "..." : ""}\n\n`;
|
|
723
|
+
output += `**Phases (${plan.phases.length}):**\n`;
|
|
724
|
+
for (const phase of plan.phases) {
|
|
725
|
+
const riskIcon = phase.riskLevel === "high" ? "⚠️" : phase.riskLevel === "medium" ? "△" : "";
|
|
726
|
+
output += ` ${phase.index + 1}. ${phase.name} ${riskIcon}\n`;
|
|
727
|
+
}
|
|
728
|
+
output += `\n**Estimated Duration:** ~${Math.ceil(plan.estimatedDuration / 60000)} min\n\n`;
|
|
729
|
+
output += "---\n\n";
|
|
730
|
+
return output;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Format plan result for display
|
|
734
|
+
*/
|
|
735
|
+
formatPlanResult(result) {
|
|
736
|
+
let output = "\n---\n\n**📋 Plan Execution Complete**\n\n";
|
|
737
|
+
const successful = result.phaseResults.filter((r) => r.success).length;
|
|
738
|
+
const failed = result.phaseResults.filter((r) => !r.success).length;
|
|
739
|
+
output += `**Results:** ${successful}/${result.phaseResults.length} phases successful`;
|
|
740
|
+
if (failed > 0) {
|
|
741
|
+
output += ` (${failed} failed)`;
|
|
742
|
+
}
|
|
743
|
+
output += "\n";
|
|
744
|
+
if (result.totalDuration) {
|
|
745
|
+
output += `**Duration:** ${Math.ceil(result.totalDuration / 1000)}s\n`;
|
|
746
|
+
}
|
|
747
|
+
if (result.totalTokensUsed) {
|
|
748
|
+
output += `**Tokens Used:** ${result.totalTokensUsed.toLocaleString()}\n`;
|
|
749
|
+
}
|
|
750
|
+
return output;
|
|
751
|
+
}
|
|
241
752
|
async processUserMessage(message) {
|
|
242
753
|
// Reset tool call tracking for new message
|
|
243
754
|
this.resetToolCallTracking();
|
|
@@ -687,6 +1198,13 @@ export class LLMAgent extends EventEmitter {
|
|
|
687
1198
|
async *processUserMessageStream(message) {
|
|
688
1199
|
// Create new abort controller for this request
|
|
689
1200
|
this.abortController = new AbortController();
|
|
1201
|
+
// Check if this is a complex request that should use multi-phase planning
|
|
1202
|
+
if (this.shouldCreatePlan(message)) {
|
|
1203
|
+
// Delegate to planning processor
|
|
1204
|
+
yield* this.processWithPlanning(message);
|
|
1205
|
+
yield { type: "done" };
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
690
1208
|
// Reset tool call tracking for new message
|
|
691
1209
|
this.resetToolCallTracking();
|
|
692
1210
|
// Prepare user message and get input tokens
|
|
@@ -866,7 +1384,12 @@ export class LLMAgent extends EventEmitter {
|
|
|
866
1384
|
case "str_replace_editor":
|
|
867
1385
|
return await this.textEditor.strReplace(args.path, args.old_str, args.new_str, args.replace_all);
|
|
868
1386
|
case "bash":
|
|
869
|
-
return await this.bash.execute(args.command
|
|
1387
|
+
return await this.bash.execute(args.command, {
|
|
1388
|
+
background: args.background,
|
|
1389
|
+
timeout: args.timeout,
|
|
1390
|
+
});
|
|
1391
|
+
case "bash_output":
|
|
1392
|
+
return await this.bashOutput.execute(args.task_id, args.wait, args.timeout);
|
|
870
1393
|
case "create_todo_list":
|
|
871
1394
|
return await this.todoTool.createTodoList(args.todos);
|
|
872
1395
|
case "update_todo_list":
|
|
@@ -963,6 +1486,19 @@ export class LLMAgent extends EventEmitter {
|
|
|
963
1486
|
async executeBashCommand(command) {
|
|
964
1487
|
return await this.bash.execute(command);
|
|
965
1488
|
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Check if a bash command is currently executing
|
|
1491
|
+
*/
|
|
1492
|
+
isBashExecuting() {
|
|
1493
|
+
return this.bash.isExecuting();
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Move currently running bash command to background
|
|
1497
|
+
* Returns task ID if successful, null otherwise
|
|
1498
|
+
*/
|
|
1499
|
+
moveBashToBackground() {
|
|
1500
|
+
return this.bash.moveToBackground();
|
|
1501
|
+
}
|
|
966
1502
|
getCurrentModel() {
|
|
967
1503
|
return this.llmClient.getCurrentModel();
|
|
968
1504
|
}
|