@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.
Files changed (136) hide show
  1. package/README.md +85 -9
  2. package/dist/agent/context-manager.js +9 -0
  3. package/dist/agent/context-manager.js.map +1 -1
  4. package/dist/agent/dependency-resolver.js +13 -5
  5. package/dist/agent/dependency-resolver.js.map +1 -1
  6. package/dist/agent/llm-agent.d.ts +50 -0
  7. package/dist/agent/llm-agent.js +539 -3
  8. package/dist/agent/llm-agent.js.map +1 -1
  9. package/dist/agent/subagent-orchestrator.d.ts +4 -0
  10. package/dist/agent/subagent-orchestrator.js +54 -11
  11. package/dist/agent/subagent-orchestrator.js.map +1 -1
  12. package/dist/agent/subagent.js +35 -11
  13. package/dist/agent/subagent.js.map +1 -1
  14. package/dist/checkpoint/manager.js +4 -0
  15. package/dist/checkpoint/manager.js.map +1 -1
  16. package/dist/commands/cache.js +5 -3
  17. package/dist/commands/cache.js.map +1 -1
  18. package/dist/commands/memory.js +21 -16
  19. package/dist/commands/memory.js.map +1 -1
  20. package/dist/commands/plan.d.ts +43 -0
  21. package/dist/commands/plan.js +385 -0
  22. package/dist/commands/plan.js.map +1 -0
  23. package/dist/constants.d.ts +32 -0
  24. package/dist/constants.js +33 -0
  25. package/dist/constants.js.map +1 -1
  26. package/dist/hooks/use-enhanced-input.d.ts +5 -1
  27. package/dist/hooks/use-enhanced-input.js +23 -10
  28. package/dist/hooks/use-enhanced-input.js.map +1 -1
  29. package/dist/hooks/use-input-handler.d.ts +11 -1
  30. package/dist/hooks/use-input-handler.js +270 -2
  31. package/dist/hooks/use-input-handler.js.map +1 -1
  32. package/dist/llm/tools.d.ts +5 -0
  33. package/dist/llm/tools.js +57 -6
  34. package/dist/llm/tools.js.map +1 -1
  35. package/dist/mcp/client.js +19 -12
  36. package/dist/mcp/client.js.map +1 -1
  37. package/dist/planner/dependency-resolver.d.ts +72 -0
  38. package/dist/planner/dependency-resolver.js +272 -0
  39. package/dist/planner/dependency-resolver.js.map +1 -0
  40. package/dist/planner/index.d.ts +12 -0
  41. package/dist/planner/index.js +26 -0
  42. package/dist/planner/index.js.map +1 -0
  43. package/dist/planner/plan-generator.d.ts +74 -0
  44. package/dist/planner/plan-generator.js +244 -0
  45. package/dist/planner/plan-generator.js.map +1 -0
  46. package/dist/planner/plan-storage.d.ts +98 -0
  47. package/dist/planner/plan-storage.js +330 -0
  48. package/dist/planner/plan-storage.js.map +1 -0
  49. package/dist/planner/prompts/planning-prompt.d.ts +41 -0
  50. package/dist/planner/prompts/planning-prompt.js +289 -0
  51. package/dist/planner/prompts/planning-prompt.js.map +1 -0
  52. package/dist/planner/task-planner.d.ts +135 -0
  53. package/dist/planner/task-planner.js +497 -0
  54. package/dist/planner/task-planner.js.map +1 -0
  55. package/dist/planner/token-estimator.d.ts +63 -0
  56. package/dist/planner/token-estimator.js +295 -0
  57. package/dist/planner/token-estimator.js.map +1 -0
  58. package/dist/planner/types.d.ts +669 -0
  59. package/dist/planner/types.js +213 -0
  60. package/dist/planner/types.js.map +1 -0
  61. package/dist/schemas/confirmation-schemas.d.ts +5 -0
  62. package/dist/schemas/confirmation-schemas.js +7 -0
  63. package/dist/schemas/confirmation-schemas.js.map +1 -1
  64. package/dist/schemas/index.d.ts +4 -4
  65. package/dist/schemas/index.js +1 -1
  66. package/dist/schemas/index.js.map +1 -1
  67. package/dist/schemas/tool-schemas.d.ts +9 -1
  68. package/dist/schemas/tool-schemas.js +6 -1
  69. package/dist/schemas/tool-schemas.js.map +1 -1
  70. package/dist/tools/bash-output.d.ts +25 -0
  71. package/dist/tools/bash-output.js +145 -0
  72. package/dist/tools/bash-output.js.map +1 -0
  73. package/dist/tools/bash.d.ts +46 -2
  74. package/dist/tools/bash.js +241 -67
  75. package/dist/tools/bash.js.map +1 -1
  76. package/dist/tools/search.js +15 -2
  77. package/dist/tools/search.js.map +1 -1
  78. package/dist/tools/text-editor.js +4 -2
  79. package/dist/tools/text-editor.js.map +1 -1
  80. package/dist/ui/components/chat-history.d.ts +1 -0
  81. package/dist/ui/components/chat-history.js +125 -41
  82. package/dist/ui/components/chat-history.js.map +1 -1
  83. package/dist/ui/components/chat-input.js +10 -3
  84. package/dist/ui/components/chat-input.js.map +1 -1
  85. package/dist/ui/components/chat-interface.js +152 -44
  86. package/dist/ui/components/chat-interface.js.map +1 -1
  87. package/dist/ui/components/collapsible-tool-result.d.ts +26 -0
  88. package/dist/ui/components/collapsible-tool-result.js +172 -0
  89. package/dist/ui/components/collapsible-tool-result.js.map +1 -0
  90. package/dist/ui/components/command-suggestions.js +2 -1
  91. package/dist/ui/components/command-suggestions.js.map +1 -1
  92. package/dist/ui/components/confirmation-dialog.js +25 -36
  93. package/dist/ui/components/confirmation-dialog.js.map +1 -1
  94. package/dist/ui/components/index.d.ts +8 -0
  95. package/dist/ui/components/index.js +9 -0
  96. package/dist/ui/components/index.js.map +1 -1
  97. package/dist/ui/components/keyboard-hints.d.ts +35 -0
  98. package/dist/ui/components/keyboard-hints.js +134 -0
  99. package/dist/ui/components/keyboard-hints.js.map +1 -0
  100. package/dist/ui/components/loading-spinner.d.ts +2 -1
  101. package/dist/ui/components/loading-spinner.js +86 -34
  102. package/dist/ui/components/loading-spinner.js.map +1 -1
  103. package/dist/ui/components/phase-progress.d.ts +21 -0
  104. package/dist/ui/components/phase-progress.js +228 -0
  105. package/dist/ui/components/phase-progress.js.map +1 -0
  106. package/dist/ui/components/quick-actions.d.ts +12 -0
  107. package/dist/ui/components/quick-actions.js +124 -0
  108. package/dist/ui/components/quick-actions.js.map +1 -0
  109. package/dist/ui/components/reasoning-display.d.ts +0 -80
  110. package/dist/ui/components/reasoning-display.js +0 -83
  111. package/dist/ui/components/reasoning-display.js.map +1 -1
  112. package/dist/ui/components/status-bar.d.ts +25 -0
  113. package/dist/ui/components/status-bar.js +125 -0
  114. package/dist/ui/components/status-bar.js.map +1 -0
  115. package/dist/ui/components/toast-notification.d.ts +123 -0
  116. package/dist/ui/components/toast-notification.js +148 -0
  117. package/dist/ui/components/toast-notification.js.map +1 -0
  118. package/dist/ui/components/welcome-panel.d.ts +10 -0
  119. package/dist/ui/components/welcome-panel.js +107 -0
  120. package/dist/ui/components/welcome-panel.js.map +1 -0
  121. package/dist/utils/background-task-manager.d.ts +95 -0
  122. package/dist/utils/background-task-manager.js +330 -0
  123. package/dist/utils/background-task-manager.js.map +1 -0
  124. package/dist/utils/confirmation-service.js +8 -3
  125. package/dist/utils/confirmation-service.js.map +1 -1
  126. package/dist/utils/history-manager.d.ts +2 -0
  127. package/dist/utils/history-manager.js +8 -1
  128. package/dist/utils/history-manager.js.map +1 -1
  129. package/dist/utils/incremental-analyzer.js +11 -3
  130. package/dist/utils/incremental-analyzer.js.map +1 -1
  131. package/dist/utils/message-optimizer.d.ts +1 -0
  132. package/dist/utils/message-optimizer.js +7 -1
  133. package/dist/utils/message-optimizer.js.map +1 -1
  134. package/dist/utils/project-analyzer.js +5 -2
  135. package/dist/utils/project-analyzer.js.map +1 -1
  136. package/package.json +2 -1
@@ -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
- // Map.keys().next().value is guaranteed to exist when size > 0, but TypeScript doesn't know this
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
  }