wave-agent-sdk 0.6.1 → 0.6.3

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 (73) hide show
  1. package/dist/agent.d.ts +0 -5
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +3 -67
  4. package/dist/constants/subagents.d.ts +5 -0
  5. package/dist/constants/subagents.d.ts.map +1 -0
  6. package/dist/constants/subagents.js +4 -0
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +9 -25
  9. package/dist/managers/backgroundTaskManager.d.ts.map +1 -1
  10. package/dist/managers/backgroundTaskManager.js +4 -0
  11. package/dist/managers/messageManager.d.ts +0 -18
  12. package/dist/managers/messageManager.d.ts.map +1 -1
  13. package/dist/managers/messageManager.js +1 -36
  14. package/dist/managers/reversionManager.d.ts.map +1 -1
  15. package/dist/managers/reversionManager.js +23 -2
  16. package/dist/managers/slashCommandManager.js +1 -1
  17. package/dist/managers/subagentManager.d.ts +4 -14
  18. package/dist/managers/subagentManager.d.ts.map +1 -1
  19. package/dist/managers/subagentManager.js +35 -109
  20. package/dist/{constants/prompts.d.ts → prompts/index.d.ts} +7 -8
  21. package/dist/prompts/index.d.ts.map +1 -0
  22. package/dist/{constants/prompts.js → prompts/index.js} +127 -11
  23. package/dist/services/aiService.js +1 -1
  24. package/dist/services/taskManager.d.ts +5 -1
  25. package/dist/services/taskManager.d.ts.map +1 -1
  26. package/dist/services/taskManager.js +6 -0
  27. package/dist/tools/bashTool.d.ts.map +1 -1
  28. package/dist/tools/bashTool.js +34 -29
  29. package/dist/tools/exitPlanMode.js +1 -1
  30. package/dist/tools/taskManagementTools.d.ts.map +1 -1
  31. package/dist/tools/taskManagementTools.js +8 -0
  32. package/dist/tools/taskTool.d.ts.map +1 -1
  33. package/dist/tools/taskTool.js +32 -2
  34. package/dist/tools/types.d.ts +2 -0
  35. package/dist/tools/types.d.ts.map +1 -1
  36. package/dist/types/messaging.d.ts +1 -11
  37. package/dist/types/messaging.d.ts.map +1 -1
  38. package/dist/types/processes.d.ts +5 -0
  39. package/dist/types/processes.d.ts.map +1 -1
  40. package/dist/utils/builtinSubagents.d.ts.map +1 -1
  41. package/dist/utils/builtinSubagents.js +28 -50
  42. package/dist/utils/editUtils.d.ts.map +1 -1
  43. package/dist/utils/editUtils.js +2 -2
  44. package/dist/utils/gitUtils.d.ts +7 -0
  45. package/dist/utils/gitUtils.d.ts.map +1 -0
  46. package/dist/utils/gitUtils.js +24 -0
  47. package/dist/utils/messageOperations.d.ts +1 -23
  48. package/dist/utils/messageOperations.d.ts.map +1 -1
  49. package/dist/utils/messageOperations.js +0 -49
  50. package/package.json +1 -1
  51. package/src/agent.ts +3 -89
  52. package/src/constants/subagents.ts +4 -0
  53. package/src/managers/aiManager.ts +9 -25
  54. package/src/managers/backgroundTaskManager.ts +5 -0
  55. package/src/managers/messageManager.ts +0 -80
  56. package/src/managers/reversionManager.ts +26 -2
  57. package/src/managers/slashCommandManager.ts +1 -1
  58. package/src/managers/subagentManager.ts +42 -145
  59. package/src/{constants/prompts.ts → prompts/index.ts} +136 -13
  60. package/src/services/aiService.ts +1 -1
  61. package/src/services/taskManager.ts +8 -1
  62. package/src/tools/bashTool.ts +35 -30
  63. package/src/tools/exitPlanMode.ts +1 -1
  64. package/src/tools/taskManagementTools.ts +18 -0
  65. package/src/tools/taskTool.ts +39 -1
  66. package/src/tools/types.ts +2 -0
  67. package/src/types/messaging.ts +0 -12
  68. package/src/types/processes.ts +5 -0
  69. package/src/utils/builtinSubagents.ts +41 -51
  70. package/src/utils/editUtils.ts +2 -6
  71. package/src/utils/gitUtils.ts +24 -0
  72. package/src/utils/messageOperations.ts +1 -93
  73. package/dist/constants/prompts.d.ts.map +0 -1
@@ -8,20 +8,19 @@ import type {
8
8
  ModelConfig,
9
9
  Usage,
10
10
  } from "../types/index.js";
11
- import type { SessionData } from "../services/session.js";
12
11
  import { AIManager } from "./aiManager.js";
13
12
  import { MessageManager } from "./messageManager.js";
14
13
  import { ToolManager } from "./toolManager.js";
15
14
  import { HookManager } from "./hookManager.js";
16
- import {
17
- UserMessageParams,
18
- type AgentToolBlockUpdateParams,
19
- } from "../utils/messageOperations.js";
20
15
  import {
21
16
  addConsolidatedAbortListener,
22
17
  createAbortPromise,
23
18
  } from "../utils/abortUtils.js";
24
19
  import { BackgroundTaskManager } from "./backgroundTaskManager.js";
20
+ import {
21
+ UserMessageParams,
22
+ type AgentToolBlockUpdateParams,
23
+ } from "../utils/messageOperations.js";
25
24
 
26
25
  export interface SubagentManagerCallbacks {
27
26
  // Granular subagent message callbacks (015-subagent-message-callbacks)
@@ -66,14 +65,15 @@ export interface SubagentInstance {
66
65
  toolManager: ToolManager;
67
66
  status: "initializing" | "active" | "completed" | "error" | "aborted";
68
67
  messages: Message[];
68
+ lastTools: string[]; // Track last two tool names
69
69
  subagentType: string; // Store the subagent type for hook context
70
70
  backgroundTaskId?: string; // ID of the background task if transitioned
71
+ onUpdate?: () => void; // Optional callback for real-time updates
71
72
  }
72
73
 
73
74
  export interface SubagentManagerOptions {
74
75
  workdir: string;
75
76
  parentToolManager: ToolManager;
76
- parentMessageManager: MessageManager;
77
77
  taskManager: import("../services/taskManager.js").TaskManager;
78
78
  callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
79
79
  logger?: Logger;
@@ -93,7 +93,6 @@ export class SubagentManager {
93
93
 
94
94
  private workdir: string;
95
95
  private parentToolManager: ToolManager;
96
- private parentMessageManager: MessageManager;
97
96
  private taskManager: import("../services/taskManager.js").TaskManager;
98
97
  private callbacks?: SubagentManagerCallbacks; // Use SubagentManagerCallbacks instead of parentCallbacks
99
98
  private logger?: Logger;
@@ -109,7 +108,6 @@ export class SubagentManager {
109
108
  constructor(options: SubagentManagerOptions) {
110
109
  this.workdir = options.workdir;
111
110
  this.parentToolManager = options.parentToolManager;
112
- this.parentMessageManager = options.parentMessageManager;
113
111
  this.taskManager = options.taskManager;
114
112
  this.callbacks = options.callbacks; // Store SubagentManagerCallbacks
115
113
  this.logger = options.logger;
@@ -176,6 +174,7 @@ export class SubagentManager {
176
174
  subagent_type: string;
177
175
  },
178
176
  runInBackground?: boolean,
177
+ onUpdate?: () => void,
179
178
  ): Promise<SubagentInstance> {
180
179
  if (!this.parentToolManager) {
181
180
  throw new Error(
@@ -248,22 +247,13 @@ export class SubagentManager {
248
247
  toolManager,
249
248
  status: "initializing",
250
249
  messages: [],
250
+ lastTools: [], // Initialize lastTools
251
251
  subagentType: parameters.subagent_type, // Store the subagent type
252
+ onUpdate,
252
253
  };
253
254
 
254
255
  this.instances.set(subagentId, instance);
255
256
 
256
- // Create subagent block in parent message manager
257
- this.parentMessageManager.addSubagentBlock(
258
- subagentId,
259
- configuration.name,
260
- messageManager.getSessionId(),
261
- configuration,
262
- "active",
263
- parameters,
264
- runInBackground,
265
- );
266
-
267
257
  return instance;
268
258
  }
269
259
 
@@ -287,9 +277,6 @@ export class SubagentManager {
287
277
 
288
278
  // Set status to active and update parent
289
279
  this.updateInstanceStatus(instance.subagentId, "active");
290
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
291
- status: "active",
292
- });
293
280
 
294
281
  if (runInBackground && this.backgroundTaskManager) {
295
282
  const taskId = this.backgroundTaskManager.generateId();
@@ -303,7 +290,11 @@ export class SubagentManager {
303
290
  description: instance.configuration.description,
304
291
  stdout: "",
305
292
  stderr: "",
306
- onStop: () => instance.aiManager.abortAIMessage(),
293
+ subagentId: instance.subagentId,
294
+ onStop: () => {
295
+ instance.aiManager.abortAIMessage();
296
+ this.cleanupInstance(instance.subagentId);
297
+ },
307
298
  });
308
299
 
309
300
  instance.backgroundTaskId = taskId;
@@ -341,9 +332,6 @@ export class SubagentManager {
341
332
  return await this.internalExecute(instance, prompt, abortSignal);
342
333
  } catch (error) {
343
334
  this.updateInstanceStatus(instance.subagentId, "error");
344
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
345
- status: "error",
346
- });
347
335
  throw error;
348
336
  }
349
337
  }
@@ -369,16 +357,15 @@ export class SubagentManager {
369
357
  description: instance.configuration.description,
370
358
  stdout: "",
371
359
  stderr: "",
372
- onStop: () => instance.aiManager.abortAIMessage(),
360
+ subagentId: instance.subagentId,
361
+ onStop: () => {
362
+ instance.aiManager.abortAIMessage();
363
+ this.cleanupInstance(instance.subagentId);
364
+ },
373
365
  });
374
366
 
375
367
  instance.backgroundTaskId = taskId;
376
368
 
377
- // Update parent message manager to reflect background status
378
- this.parentMessageManager.updateSubagentBlock(subagentId, {
379
- runInBackground: true,
380
- });
381
-
382
369
  return taskId;
383
370
  }
384
371
 
@@ -395,9 +382,8 @@ export class SubagentManager {
395
382
  () => {
396
383
  // Update status to aborted
397
384
  this.updateInstanceStatus(instance.subagentId, "aborted");
398
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
399
- status: "aborted",
400
- });
385
+ // Cleanup instance immediately on abort
386
+ this.cleanupInstance(instance.subagentId);
401
387
  },
402
388
  () => {
403
389
  // Abort the AI execution
@@ -474,9 +460,6 @@ export class SubagentManager {
474
460
 
475
461
  // Update status to completed and update parent
476
462
  this.updateInstanceStatus(instance.subagentId, "completed");
477
- this.parentMessageManager.updateSubagentBlock(instance.subagentId, {
478
- status: "completed",
479
- });
480
463
 
481
464
  // If this was transitioned to background, update the background task
482
465
  if (instance.backgroundTaskId && this.backgroundTaskManager) {
@@ -580,99 +563,6 @@ export class SubagentManager {
580
563
  this.instances.clear();
581
564
  }
582
565
 
583
- /**
584
- * Restore subagent instances from saved session data
585
- * This method is called during agent initialization to restore previous subagent states
586
- */
587
- async restoreSubagentSessions(
588
- subagentSessions: Array<{
589
- sessionData: SessionData;
590
- subagentId: string;
591
- configuration: SubagentConfiguration;
592
- }>,
593
- ): Promise<void> {
594
- for (const { sessionData, subagentId, configuration } of subagentSessions) {
595
- try {
596
- // Use the configuration from the SubagentBlock
597
-
598
- // Create the subagent instance without executing - just restore the state
599
-
600
- // Create MessageManager for the restored subagent
601
- const subagentCallbacks = this.createSubagentCallbacks(subagentId);
602
- const messageManager = new MessageManager({
603
- callbacks: subagentCallbacks,
604
- workdir: this.workdir,
605
- logger: this.logger,
606
- sessionType: "subagent",
607
- subagentType: configuration.name, // Use configuration name for restored sessions
608
- memoryRuleManager: this.memoryRuleManager,
609
- });
610
-
611
- // Use the parent tool manager
612
- const toolManager = this.parentToolManager;
613
-
614
- // Determine model to use
615
- let modelToUse: string;
616
- const parentModelConfig = this.getModelConfig();
617
-
618
- if (!configuration.model || configuration.model === "inherit") {
619
- modelToUse = parentModelConfig.agentModel;
620
- } else if (configuration.model === "fastModel") {
621
- modelToUse = parentModelConfig.fastModel;
622
- } else {
623
- modelToUse = configuration.model;
624
- }
625
-
626
- // Create AIManager for the restored subagent
627
- const aiManager = new AIManager({
628
- messageManager,
629
- toolManager,
630
- taskManager: this.taskManager,
631
- logger: this.logger,
632
- workdir: this.workdir,
633
- systemPrompt: configuration.systemPrompt,
634
- subagentType: configuration.name, // Use configuration name as subagent type for restored instances
635
- hookManager: this.hookManager,
636
- permissionManager: this.parentToolManager.getPermissionManager(),
637
- getGatewayConfig: this.getGatewayConfig,
638
- getModelConfig: () => ({
639
- ...parentModelConfig,
640
- agentModel: modelToUse,
641
- }),
642
- getMaxInputTokens: this.getMaxInputTokens,
643
- getLanguage: this.getLanguage,
644
- callbacks: {
645
- onUsageAdded: this.onUsageAdded,
646
- },
647
- });
648
-
649
- // Create restored instance
650
- const instance: SubagentInstance = {
651
- subagentId,
652
- configuration,
653
- aiManager,
654
- messageManager,
655
- toolManager,
656
- status: "completed", // Restored sessions are considered completed
657
- messages: sessionData.messages,
658
- subagentType: configuration.name, // Use configuration name as subagent type for restored instances
659
- };
660
-
661
- // IMPORTANT: Store instance in map BEFORE calling setMessages
662
- // This ensures the callback can find the instance
663
- this.instances.set(subagentId, instance);
664
-
665
- messageManager.initializeFromSession(sessionData);
666
- } catch (error) {
667
- this.logger?.warn(
668
- `Failed to restore subagent session ${subagentId}:`,
669
- error,
670
- );
671
- // Continue with other sessions even if one fails
672
- }
673
- }
674
- }
675
-
676
566
  /**
677
567
  * Create subagent callbacks for a specific subagent ID
678
568
  * Extracted to reuse in both create and restore flows
@@ -715,6 +605,20 @@ export class SubagentManager {
715
605
  },
716
606
 
717
607
  onToolBlockUpdated: (params: AgentToolBlockUpdateParams) => {
608
+ // Track last two tool names when a tool starts running
609
+ if (params.stage === "running" && params.name) {
610
+ const instance = this.instances.get(subagentId);
611
+ if (instance) {
612
+ // Add to lastTools if it's different from the last one or we want to show duplicates
613
+ // Based on "ToolA, ToolB" requirement, we'll just keep the last two
614
+ instance.lastTools.push(params.name);
615
+ if (instance.lastTools.length > 2) {
616
+ instance.lastTools.shift();
617
+ }
618
+ instance.onUpdate?.();
619
+ }
620
+ }
621
+
718
622
  // Forward tool block updates to parent via SubagentManager callbacks
719
623
  if (this.callbacks?.onSubagentToolBlockUpdated) {
720
624
  this.callbacks.onSubagentToolBlockUpdated(subagentId, params);
@@ -726,6 +630,8 @@ export class SubagentManager {
726
630
  const instance = this.instances.get(subagentId);
727
631
  if (instance) {
728
632
  instance.messages = messages;
633
+ // Trigger the onUpdate callback if provided
634
+ instance.onUpdate?.();
729
635
  // Forward subagent message changes to parent via callbacks
730
636
  if (this.callbacks?.onSubagentMessagesChange) {
731
637
  this.callbacks.onSubagentMessagesChange(subagentId, messages);
@@ -733,26 +639,17 @@ export class SubagentManager {
733
639
  }
734
640
  },
735
641
 
736
- onSessionIdChange: (newSessionId: string) => {
737
- // Update the subagent block with the new session ID
738
- this.parentMessageManager.updateSubagentBlock(subagentId, {
739
- sessionId: newSessionId,
740
- });
741
- },
742
-
743
642
  onLatestTotalTokensChange: (tokens: number) => {
643
+ const instance = this.instances.get(subagentId);
644
+ if (instance) {
645
+ // Trigger the onUpdate callback if provided
646
+ instance.onUpdate?.();
647
+ }
744
648
  // Forward latest total tokens to parent via SubagentManager callbacks
745
649
  if (this.callbacks?.onSubagentLatestTotalTokensChange) {
746
650
  this.callbacks.onSubagentLatestTotalTokensChange(subagentId, tokens);
747
651
  }
748
652
  },
749
-
750
- onFileHistoryBlockAdded: (
751
- snapshots: import("../types/reversion.js").FileSnapshot[],
752
- ) => {
753
- // Forward file history blocks to parent MessageManager
754
- this.parentMessageManager.addFileHistoryBlock(snapshots);
755
- },
756
653
  };
757
654
  }
758
655
  }
@@ -1,4 +1,10 @@
1
+ import * as os from "node:os";
1
2
  import { ToolPlugin } from "../tools/types.js";
3
+ import { isGitRepository } from "../utils/gitUtils.js";
4
+ import {
5
+ EXPLORE_SUBAGENT_TYPE,
6
+ PLAN_SUBAGENT_TYPE,
7
+ } from "../constants/subagents.js";
2
8
  import {
3
9
  ASK_USER_QUESTION_TOOL_NAME,
4
10
  BASH_TOOL_NAME,
@@ -11,7 +17,8 @@ import {
11
17
  TASK_UPDATE_TOOL_NAME,
12
18
  TASK_LIST_TOOL_NAME,
13
19
  WRITE_TOOL_NAME,
14
- } from "./tools.js";
20
+ EXIT_PLAN_MODE_TOOL_NAME,
21
+ } from "../constants/tools.js";
15
22
 
16
23
  export const BASE_SYSTEM_PROMPT = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
17
24
 
@@ -23,8 +30,9 @@ The user will primarily request you perform software engineering tasks. This inc
23
30
  - Don't add features, refactor code, or make "improvements" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
24
31
  - Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.
25
32
  - Don't create helpers, utilities, or abstractions for one-time operations. Don't design for hypothetical future requirements. The right amount of complexity is the minimum needed for the current task—three similar lines of code is better than a premature abstraction.
26
- - Avoid backwards-compatibility hacks like renaming unused \`_vars\`, re-exporting types, adding \`// removed\` comments for removed code, etc. If something is unused, delete it completely.
33
+ - Avoid backwards-compatibility hacks like renaming unused \`_vars\`, re-exporting types, adding \`// removed\` comments for removed code, etc. If something is unused, delete it completely.`;
27
34
 
35
+ export const TOOL_POLICY = `
28
36
  # Tool usage policy
29
37
  - You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency.
30
38
  - However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
@@ -39,17 +47,90 @@ It is critical that you mark tasks as completed as soon as you are done with a t
39
47
  export function buildPlanModePrompt(
40
48
  planFilePath: string,
41
49
  planExists: boolean,
50
+ isSubagent: boolean = false,
42
51
  ): string {
43
52
  const planFileInfo = planExists
44
53
  ? `A plan file already exists at ${planFilePath}. You can read it and make incremental edits using the ${EDIT_TOOL_NAME} tool if you need to.`
45
54
  : `No plan file exists yet. You should create your plan at ${planFilePath} using the ${WRITE_TOOL_NAME} tool if you need to.`;
46
55
 
47
- return `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received (for example, to make edits). Instead, you should:
56
+ if (isSubagent) {
57
+ return `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits, run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received (for example, to make edits). Instead, you should:
58
+
59
+ ## Plan File Info:
60
+ ${planFileInfo}
61
+ You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
62
+ Answer the user's query comprehensively, using the ${ASK_USER_QUESTION_TOOL_NAME} tool if you need to ask the user clarifying questions. If you do use the ${ASK_USER_QUESTION_TOOL_NAME}, make sure to ask all clarifying questions you need to fully understand the user's intent before proceeding.`;
63
+ }
64
+
65
+ return `Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.
48
66
 
49
67
  ## Plan File Info:
50
68
  ${planFileInfo}
51
69
  You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.
52
- Answer the user's query comprehensively. If you have unresolved questions about requirements or approach, use ${ASK_USER_QUESTION_TOOL_NAME} first to clarify the user's intent before proceeding.`;
70
+
71
+ ## Plan Workflow
72
+
73
+ ### Phase 1: Initial Understanding
74
+ Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the Task subagent type with subagent_type=${EXPLORE_SUBAGENT_TYPE}.
75
+
76
+ 1. Focus on understanding the user's request and the code associated with their request. Actively search for existing functions, utilities, and patterns that can be reused — avoid proposing new code when suitable implementations already exist.
77
+
78
+ 2. **Launch up to 3 ${EXPLORE_SUBAGENT_TYPE} agents IN PARALLEL** (single message, multiple tool calls) to efficiently explore the codebase.
79
+ - Use 1 agent when the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change.
80
+ - Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.
81
+ - Quality over quantity - 3 agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)
82
+ - If using multiple agents: Provide each agent with a specific search focus or area to explore. Example: One agent searches for existing implementations, another explores related components, a third investigating testing patterns
83
+
84
+ ### Phase 2: Design
85
+ Goal: Design an implementation approach.
86
+
87
+ Launch Task agent(s) with subagent_type=${PLAN_SUBAGENT_TYPE} to design the implementation based on the user's intent and your exploration results from Phase 1.
88
+
89
+ You can launch up to 3 agent(s) in parallel.
90
+
91
+ **Guidelines:**
92
+ - **Default**: Launch at least 1 Plan agent for most tasks - it helps validate your understanding and consider alternatives
93
+ - **Skip agents**: Only for truly trivial tasks (typo fixes, single-line changes, simple renames)
94
+ - **Multiple agents**: Use up to 3 agents for complex tasks that benefit from different perspectives
95
+
96
+ Examples of when to use multiple agents:
97
+ - The task touches multiple parts of the codebase
98
+ - It's a large refactor or architectural change
99
+ - There are many edge cases to consider
100
+ - You'd benefit from exploring different approaches
101
+
102
+ Example perspectives by task type:
103
+ - New feature: simplicity vs performance vs maintainability
104
+ - Bug fix: root cause vs workaround vs prevention
105
+ - Refactoring: minimal change vs clean architecture
106
+
107
+ In the agent prompt:
108
+ - Provide comprehensive background context from Phase 1 exploration including filenames and code path traces
109
+ - Describe requirements and constraints
110
+ - Request a detailed implementation plan
111
+
112
+ ### Phase 3: Review
113
+ Goal: Review the plan(s) from Phase 2 and ensure alignment with the user's intentions.
114
+ 1. Read the critical files identified by agents to deepen your understanding
115
+ 2. Ensure that the plans align with the user's original request
116
+ 3. Use ${ASK_USER_QUESTION_TOOL_NAME} to clarify any remaining questions with the user
117
+
118
+ ### Phase 4: Final Plan
119
+ Goal: Write your final plan to the plan file (the only file you can edit).
120
+ - Begin with a **Context** section: explain why this change is being made — the problem or need it addresses, what prompted it, and the intended outcome
121
+ - Include only your recommended approach, not all alternatives
122
+ - Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively
123
+ - Include the paths of critical files to be modified
124
+ - Reference existing functions and utilities you found that should be reused, with their file paths
125
+ - Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)
126
+
127
+ ### Phase 5: Call ${EXIT_PLAN_MODE_TOOL_NAME}
128
+ At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call ${EXIT_PLAN_MODE_TOOL_NAME} to indicate to the user that you are done planning.
129
+ This is critical - your turn should only end with either using the ${ASK_USER_QUESTION_TOOL_NAME} tool OR calling ${EXIT_PLAN_MODE_TOOL_NAME}. Do not stop unless it's for these 2 reasons
130
+
131
+ **Important:** Use ${ASK_USER_QUESTION_TOOL_NAME} ONLY to clarify requirements or choose between approaches. Use ${EXIT_PLAN_MODE_TOOL_NAME} to request plan approval. Do NOT ask about plan approval in any other way - no text questions, no AskUserQuestion. Phrases like "Is this plan okay?", "Should I proceed?", "How does this plan look?", "Any changes before we start?", or similar MUST use ${EXIT_PLAN_MODE_TOOL_NAME}.
132
+
133
+ NOTE: At any point in time through this workflow you should feel free to ask the user questions or clarifications using the ${ASK_USER_QUESTION_TOOL_NAME} tool. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.`;
53
134
  }
54
135
 
55
136
  export const DEFAULT_SYSTEM_PROMPT = BASE_SYSTEM_PROMPT;
@@ -84,6 +165,44 @@ Guidelines:
84
165
  - In your final response always share relevant file names and code snippets. Any file paths you return in your response MUST be absolute. Do NOT use relative paths.
85
166
  - For clear communication, avoid using emojis.`;
86
167
 
168
+ export const EXPLORE_SUBAGENT_SYSTEM_PROMPT = `You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
169
+
170
+ === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
171
+ This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
172
+ - Creating new files (no Write, touch, or file creation of any kind)
173
+ - Modifying existing files (no Edit operations)
174
+ - Deleting files (no rm or deletion)
175
+ - Moving or copying files (no mv or cp)
176
+ - Creating temporary files anywhere, including /tmp
177
+ - Using redirect operators (>, >>, |) or heredocs to write to files
178
+ - Running ANY commands that change system state
179
+
180
+ Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.
181
+
182
+ Your strengths:
183
+ - Rapidly finding files using glob patterns
184
+ - Searching code and text with powerful regex patterns
185
+ - Reading and analyzing file contents
186
+ - Using Language Server Protocol (LSP) for deep code intelligence (definitions, references, etc.)
187
+
188
+ Guidelines:
189
+ - Use ${GLOB_TOOL_NAME} for broad file pattern matching
190
+ - Use ${GREP_TOOL_NAME} for searching file contents with regex
191
+ - Use ${READ_TOOL_NAME} when you know the specific file path you need to read
192
+ - Use LSP for code intelligence features like finding definitions, references, implementations, and symbols. This is especially useful for understanding complex code relationships.
193
+ - Use ${BASH_TOOL_NAME} ONLY for read-only operations (ls, git status, git log, git diff, find, cat, head, tail)
194
+ - NEVER use ${BASH_TOOL_NAME} for: mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install, or any file creation/modification
195
+ - Adapt your search approach based on the thoroughness level specified by the caller
196
+ - Return file paths as absolute paths in your final response
197
+ - For clear communication, avoid using emojis
198
+ - Communicate your final report directly as a regular message - do NOT attempt to create files
199
+
200
+ NOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:
201
+ - Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations
202
+ - Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files
203
+
204
+ Complete the user's search request efficiently and report your findings clearly.`;
205
+
87
206
  export const PLAN_SUBAGENT_SYSTEM_PROMPT = `You are a software architect and planning specialist. Your role is to explore the codebase and design implementation plans.
88
207
 
89
208
  === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
@@ -186,12 +305,9 @@ export function buildSystemPrompt(
186
305
  tools: ToolPlugin[],
187
306
  options: {
188
307
  workdir?: string;
189
- isGitRepo?: string;
190
- platform?: string;
191
- osVersion?: string;
192
- today?: string;
193
308
  memory?: string;
194
309
  language?: string;
310
+ isSubagent?: boolean;
195
311
  planMode?: {
196
312
  planFilePath: string;
197
313
  planExists: boolean;
@@ -210,6 +326,8 @@ export function buildSystemPrompt(
210
326
  prompt += TASK_MANAGEMENT_POLICY;
211
327
  }
212
328
 
329
+ prompt += `\n\n${TOOL_POLICY}`;
330
+
213
331
  for (const tool of tools) {
214
332
  if (tool.prompt) {
215
333
  prompt += tool.prompt();
@@ -221,19 +339,24 @@ export function buildSystemPrompt(
221
339
  }
222
340
 
223
341
  if (options.planMode) {
224
- prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists)}`;
342
+ prompt += `\n\n${buildPlanModePrompt(options.planMode.planFilePath, options.planMode.planExists, options.isSubagent)}`;
225
343
  }
226
344
 
227
345
  if (options.workdir) {
346
+ const isGitRepo = isGitRepository(options.workdir);
347
+ const platform = os.platform();
348
+ const osVersion = `${os.type()} ${os.release()}`;
349
+ const today = new Date().toISOString().split("T")[0];
350
+
228
351
  prompt += `
229
352
 
230
353
  Here is useful information about the environment you are running in:
231
354
  <env>
232
355
  Working directory: ${options.workdir}
233
- Is directory a git repo: ${options.isGitRepo || "No"}
234
- Platform: ${options.platform || ""}
235
- OS Version: ${options.osVersion || ""}
236
- Today's date: ${options.today || new Date().toISOString().split("T")[0]}
356
+ Is directory a git repo: ${isGitRepo}
357
+ Platform: ${platform}
358
+ OS Version: ${osVersion}
359
+ Today's date: ${today}
237
360
  </env>
238
361
  `;
239
362
  }
@@ -21,7 +21,7 @@ import * as os from "os";
21
21
  import * as fs from "fs";
22
22
  import * as path from "path";
23
23
 
24
- import { COMPRESS_MESSAGES_SYSTEM_PROMPT } from "../constants/prompts.js";
24
+ import { COMPRESS_MESSAGES_SYSTEM_PROMPT } from "../prompts/index.js";
25
25
 
26
26
  /**
27
27
  * Interface for debug data saved during 400 errors
@@ -27,7 +27,7 @@ export class TaskManager extends EventEmitter {
27
27
  return join(this.baseDir, this.taskListId);
28
28
  }
29
29
 
30
- private getTaskPath(taskId: string): string {
30
+ public getTaskPath(taskId: string): string {
31
31
  return join(this.getSessionDir(), `${taskId}.json`);
32
32
  }
33
33
 
@@ -185,4 +185,11 @@ export class TaskManager extends EventEmitter {
185
185
 
186
186
  return (Math.max(...ids) + 1).toString();
187
187
  }
188
+
189
+ /**
190
+ * Refreshes the task list by re-reading tasks from disk and emitting a change event.
191
+ */
192
+ public async refreshTasks(): Promise<void> {
193
+ this.emit("tasksChange", this.taskListId);
194
+ }
188
195
  }
@@ -24,7 +24,34 @@ export const bashTool: ToolPlugin = {
24
24
  type: "function",
25
25
  function: {
26
26
  name: BASH_TOOL_NAME,
27
- description: `Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
27
+ description: "Run shell command",
28
+ parameters: {
29
+ type: "object",
30
+ properties: {
31
+ command: {
32
+ type: "string",
33
+ description: "The command to execute",
34
+ },
35
+ timeout: {
36
+ type: "number",
37
+ description: "Optional timeout in milliseconds (max 600000)",
38
+ },
39
+ description: {
40
+ type: "string",
41
+ description:
42
+ "Clear, concise description of what this command does in 5-10 words.",
43
+ },
44
+ run_in_background: {
45
+ type: "boolean",
46
+ description: `Set to true to run this command in the background. Use ${TASK_OUTPUT_TOOL_NAME} to read the output later.`,
47
+ },
48
+ },
49
+ required: ["command"],
50
+ },
51
+ },
52
+ },
53
+ prompt: () => `
54
+ Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
28
55
 
29
56
  IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
30
57
 
@@ -69,33 +96,7 @@ Usage notes:
69
96
  <bad-example>
70
97
  cd /foo/bar && pytest tests
71
98
  </bad-example>
72
- `,
73
- parameters: {
74
- type: "object",
75
- properties: {
76
- command: {
77
- type: "string",
78
- description: "The command to execute",
79
- },
80
- timeout: {
81
- type: "number",
82
- description: "Optional timeout in milliseconds (max 600000)",
83
- },
84
- description: {
85
- type: "string",
86
- description:
87
- "Clear, concise description of what this command does in 5-10 words.",
88
- },
89
- run_in_background: {
90
- type: "boolean",
91
- description: `Set to true to run this command in the background. Use ${TASK_OUTPUT_TOOL_NAME} to read the output later.`,
92
- },
93
- },
94
- required: ["command"],
95
- },
96
- },
97
- },
98
- prompt: () => `
99
+
99
100
  - Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
100
101
  - When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls in parallel.`,
101
102
  execute: async (
@@ -306,13 +307,17 @@ Usage notes:
306
307
 
307
308
  child.stdout?.on("data", (data) => {
308
309
  if (!isAborted && !isBackgrounded) {
309
- outputBuffer += stripAnsiColors(data.toString());
310
+ const chunk = stripAnsiColors(data.toString());
311
+ outputBuffer += chunk;
312
+ context.onShortResultUpdate?.(chunk.trim().split("\n").pop() || "");
310
313
  }
311
314
  });
312
315
 
313
316
  child.stderr?.on("data", (data) => {
314
317
  if (!isAborted && !isBackgrounded) {
315
- errorBuffer += stripAnsiColors(data.toString());
318
+ const chunk = stripAnsiColors(data.toString());
319
+ errorBuffer += chunk;
320
+ context.onShortResultUpdate?.(chunk.trim().split("\n").pop() || "");
316
321
  }
317
322
  });
318
323
 
@@ -93,7 +93,7 @@ Ensure your plan is complete and unambiguous:
93
93
  if (permissionResult.behavior === "deny") {
94
94
  return {
95
95
  success: false,
96
- content: permissionResult.message || "Plan rejected by user",
96
+ content: `User feedback: ${permissionResult.message || "Plan rejected by user"}. Please update your proposal accordingly.`,
97
97
  error: permissionResult.message ? undefined : "Plan rejected by user",
98
98
  };
99
99
  }