wave-agent-sdk 0.4.0 → 0.6.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 (190) hide show
  1. package/dist/agent.d.ts +42 -11
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +114 -115
  4. package/dist/constants/prompts.d.ts +18 -14
  5. package/dist/constants/prompts.d.ts.map +1 -1
  6. package/dist/constants/prompts.js +130 -54
  7. package/dist/constants/tools.d.ts +6 -3
  8. package/dist/constants/tools.d.ts.map +1 -1
  9. package/dist/constants/tools.js +6 -3
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +1 -0
  13. package/dist/managers/MemoryRuleManager.js +1 -1
  14. package/dist/managers/aiManager.d.ts +5 -3
  15. package/dist/managers/aiManager.d.ts.map +1 -1
  16. package/dist/managers/aiManager.js +57 -20
  17. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  18. package/dist/managers/backgroundBashManager.js +1 -0
  19. package/dist/managers/backgroundTaskManager.d.ts +35 -0
  20. package/dist/managers/backgroundTaskManager.d.ts.map +1 -0
  21. package/dist/managers/backgroundTaskManager.js +255 -0
  22. package/dist/managers/foregroundTaskManager.d.ts +9 -0
  23. package/dist/managers/foregroundTaskManager.d.ts.map +1 -0
  24. package/dist/managers/foregroundTaskManager.js +21 -0
  25. package/dist/managers/liveConfigManager.d.ts +1 -1
  26. package/dist/managers/lspManager.d.ts.map +1 -1
  27. package/dist/managers/lspManager.js +3 -1
  28. package/dist/managers/mcpManager.d.ts.map +1 -1
  29. package/dist/managers/messageManager.d.ts +26 -12
  30. package/dist/managers/messageManager.d.ts.map +1 -1
  31. package/dist/managers/messageManager.js +138 -64
  32. package/dist/managers/permissionManager.d.ts.map +1 -1
  33. package/dist/managers/permissionManager.js +26 -22
  34. package/dist/managers/planManager.d.ts +1 -1
  35. package/dist/managers/planManager.d.ts.map +1 -1
  36. package/dist/managers/planManager.js +2 -2
  37. package/dist/managers/pluginManager.d.ts.map +1 -1
  38. package/dist/managers/pluginManager.js +3 -2
  39. package/dist/managers/slashCommandManager.d.ts +6 -0
  40. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  41. package/dist/managers/slashCommandManager.js +8 -2
  42. package/dist/managers/subagentManager.d.ts +15 -2
  43. package/dist/managers/subagentManager.d.ts.map +1 -1
  44. package/dist/managers/subagentManager.js +153 -39
  45. package/dist/managers/toolManager.d.ts +18 -1
  46. package/dist/managers/toolManager.d.ts.map +1 -1
  47. package/dist/managers/toolManager.js +29 -5
  48. package/dist/services/GitService.d.ts.map +1 -1
  49. package/dist/services/GitService.js +6 -2
  50. package/dist/services/MarketplaceService.d.ts +2 -2
  51. package/dist/services/MarketplaceService.d.ts.map +1 -1
  52. package/dist/services/MarketplaceService.js +18 -11
  53. package/dist/services/MemoryRuleService.d.ts +1 -1
  54. package/dist/services/MemoryRuleService.d.ts.map +1 -1
  55. package/dist/services/MemoryRuleService.js +13 -2
  56. package/dist/services/aiService.d.ts +0 -1
  57. package/dist/services/aiService.d.ts.map +1 -1
  58. package/dist/services/aiService.js +4 -140
  59. package/dist/services/memory.d.ts +0 -3
  60. package/dist/services/memory.d.ts.map +1 -1
  61. package/dist/services/memory.js +1 -60
  62. package/dist/services/session.d.ts +15 -1
  63. package/dist/services/session.d.ts.map +1 -1
  64. package/dist/services/session.js +57 -1
  65. package/dist/services/taskManager.d.ts +21 -0
  66. package/dist/services/taskManager.d.ts.map +1 -0
  67. package/dist/services/taskManager.js +158 -0
  68. package/dist/tools/askUserQuestion.d.ts.map +1 -1
  69. package/dist/tools/askUserQuestion.js +39 -25
  70. package/dist/tools/bashTool.d.ts +0 -8
  71. package/dist/tools/bashTool.d.ts.map +1 -1
  72. package/dist/tools/bashTool.js +48 -172
  73. package/dist/tools/editTool.d.ts.map +1 -1
  74. package/dist/tools/editTool.js +8 -6
  75. package/dist/tools/exitPlanMode.d.ts.map +1 -1
  76. package/dist/tools/exitPlanMode.js +25 -1
  77. package/dist/tools/globTool.d.ts.map +1 -1
  78. package/dist/tools/globTool.js +8 -2
  79. package/dist/tools/grepTool.d.ts.map +1 -1
  80. package/dist/tools/grepTool.js +17 -6
  81. package/dist/tools/lsTool.d.ts.map +1 -1
  82. package/dist/tools/lsTool.js +3 -1
  83. package/dist/tools/multiEditTool.d.ts.map +1 -1
  84. package/dist/tools/multiEditTool.js +7 -6
  85. package/dist/tools/readTool.d.ts.map +1 -1
  86. package/dist/tools/readTool.js +16 -1
  87. package/dist/tools/taskManagementTools.d.ts +6 -0
  88. package/dist/tools/taskManagementTools.d.ts.map +1 -0
  89. package/dist/tools/taskManagementTools.js +453 -0
  90. package/dist/tools/taskOutputTool.d.ts +3 -0
  91. package/dist/tools/taskOutputTool.d.ts.map +1 -0
  92. package/dist/tools/taskOutputTool.js +173 -0
  93. package/dist/tools/taskStopTool.d.ts +3 -0
  94. package/dist/tools/taskStopTool.d.ts.map +1 -0
  95. package/dist/tools/taskStopTool.js +71 -0
  96. package/dist/tools/taskTool.d.ts.map +1 -1
  97. package/dist/tools/taskTool.js +110 -63
  98. package/dist/tools/types.d.ts +12 -0
  99. package/dist/tools/types.d.ts.map +1 -1
  100. package/dist/tools/writeTool.d.ts.map +1 -1
  101. package/dist/tools/writeTool.js +9 -1
  102. package/dist/types/index.d.ts +1 -0
  103. package/dist/types/index.d.ts.map +1 -1
  104. package/dist/types/index.js +1 -0
  105. package/dist/types/marketplace.d.ts +1 -0
  106. package/dist/types/marketplace.d.ts.map +1 -1
  107. package/dist/types/messaging.d.ts +3 -8
  108. package/dist/types/messaging.d.ts.map +1 -1
  109. package/dist/types/processes.d.ts +29 -4
  110. package/dist/types/processes.d.ts.map +1 -1
  111. package/dist/types/tasks.d.ts +13 -0
  112. package/dist/types/tasks.d.ts.map +1 -0
  113. package/dist/types/tasks.js +1 -0
  114. package/dist/types/tools.d.ts +4 -1
  115. package/dist/types/tools.d.ts.map +1 -1
  116. package/dist/utils/builtinSubagents.d.ts.map +1 -1
  117. package/dist/utils/builtinSubagents.js +38 -1
  118. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  119. package/dist/utils/cacheControlUtils.js +18 -12
  120. package/dist/utils/constants.d.ts +0 -4
  121. package/dist/utils/constants.d.ts.map +1 -1
  122. package/dist/utils/constants.js +0 -4
  123. package/dist/utils/convertMessagesForAPI.js +2 -2
  124. package/dist/utils/editUtils.d.ts +2 -11
  125. package/dist/utils/editUtils.d.ts.map +1 -1
  126. package/dist/utils/editUtils.js +52 -79
  127. package/dist/utils/messageOperations.d.ts +5 -36
  128. package/dist/utils/messageOperations.d.ts.map +1 -1
  129. package/dist/utils/messageOperations.js +9 -98
  130. package/dist/utils/nameGenerator.d.ts +1 -1
  131. package/dist/utils/nameGenerator.d.ts.map +1 -1
  132. package/dist/utils/nameGenerator.js +19 -3
  133. package/package.json +5 -5
  134. package/src/agent.ts +157 -134
  135. package/src/constants/prompts.ts +156 -65
  136. package/src/constants/tools.ts +6 -3
  137. package/src/index.ts +1 -0
  138. package/src/managers/MemoryRuleManager.ts +1 -1
  139. package/src/managers/aiManager.ts +77 -35
  140. package/src/managers/backgroundBashManager.ts +1 -0
  141. package/src/managers/backgroundTaskManager.ts +305 -0
  142. package/src/managers/foregroundTaskManager.ts +27 -0
  143. package/src/managers/lspManager.ts +3 -1
  144. package/src/managers/mcpManager.ts +6 -3
  145. package/src/managers/messageManager.ts +185 -75
  146. package/src/managers/permissionManager.ts +33 -28
  147. package/src/managers/planManager.ts +2 -2
  148. package/src/managers/pluginManager.ts +4 -3
  149. package/src/managers/slashCommandManager.ts +15 -2
  150. package/src/managers/subagentManager.ts +194 -35
  151. package/src/managers/toolManager.ts +48 -6
  152. package/src/services/GitService.ts +6 -2
  153. package/src/services/MarketplaceService.ts +30 -12
  154. package/src/services/MemoryRuleService.ts +18 -6
  155. package/src/services/aiService.ts +3 -145
  156. package/src/services/memory.ts +1 -73
  157. package/src/services/session.ts +73 -0
  158. package/src/services/taskManager.ts +188 -0
  159. package/src/tools/askUserQuestion.ts +51 -29
  160. package/src/tools/bashTool.ts +63 -196
  161. package/src/tools/editTool.ts +9 -18
  162. package/src/tools/exitPlanMode.ts +26 -2
  163. package/src/tools/globTool.ts +10 -2
  164. package/src/tools/grepTool.ts +17 -6
  165. package/src/tools/lsTool.ts +3 -1
  166. package/src/tools/multiEditTool.ts +7 -18
  167. package/src/tools/readTool.ts +17 -1
  168. package/src/tools/taskManagementTools.ts +498 -0
  169. package/src/tools/taskOutputTool.ts +196 -0
  170. package/src/tools/taskStopTool.ts +78 -0
  171. package/src/tools/taskTool.ts +136 -74
  172. package/src/tools/types.ts +13 -0
  173. package/src/tools/writeTool.ts +9 -2
  174. package/src/types/index.ts +1 -0
  175. package/src/types/marketplace.ts +1 -0
  176. package/src/types/messaging.ts +2 -9
  177. package/src/types/processes.ts +39 -4
  178. package/src/types/tasks.ts +13 -0
  179. package/src/types/tools.ts +4 -1
  180. package/src/utils/builtinSubagents.ts +47 -1
  181. package/src/utils/cacheControlUtils.ts +26 -18
  182. package/src/utils/constants.ts +0 -5
  183. package/src/utils/convertMessagesForAPI.ts +2 -2
  184. package/src/utils/editUtils.ts +65 -103
  185. package/src/utils/messageOperations.ts +12 -136
  186. package/src/utils/nameGenerator.ts +20 -3
  187. package/dist/tools/todoWriteTool.d.ts +0 -6
  188. package/dist/tools/todoWriteTool.d.ts.map +0 -1
  189. package/dist/tools/todoWriteTool.js +0 -220
  190. package/src/tools/todoWriteTool.ts +0 -257
@@ -3,7 +3,6 @@ import {
3
3
  updateToolBlockInMessage,
4
4
  addErrorBlockToMessage,
5
5
  addUserMessageToMessages,
6
- extractUserInputHistory,
7
6
  addCommandOutputMessage,
8
7
  updateCommandOutputInMessage,
9
8
  completeCommandInMessage,
@@ -26,13 +25,13 @@ import {
26
25
  SESSION_DIR,
27
26
  } from "../services/session.js";
28
27
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
28
+ import type { MemoryRuleManager } from "./MemoryRuleManager.js";
29
29
  import { pathEncoder } from "../utils/pathEncoder.js";
30
30
 
31
31
  export interface MessageManagerCallbacks {
32
32
  onMessagesChange?: (messages: Message[]) => void;
33
33
  onSessionIdChange?: (sessionId: string) => void;
34
34
  onLatestTotalTokensChange?: (latestTotalTokens: number) => void;
35
- onUserInputHistoryChange?: (history: string[]) => void;
36
35
  onUsagesChange?: (usages: Usage[]) => void;
37
36
  // Incremental callback
38
37
  onUserMessageAdded?: (params: UserMessageParams) => void;
@@ -44,7 +43,7 @@ export interface MessageManagerCallbacks {
44
43
  onAssistantReasoningUpdated?: (chunk: string, accumulated: string) => void;
45
44
  onToolBlockUpdated?: (params: AgentToolBlockUpdateParams) => void;
46
45
  onErrorBlockAdded?: (error: string) => void;
47
- onCompressBlockAdded?: (insertIndex: number, content: string) => void;
46
+ onCompressBlockAdded?: (content: string) => void;
48
47
  onCompressionStateChange?: (isCompressing: boolean) => void;
49
48
  onMemoryBlockAdded?: (
50
49
  content: string,
@@ -57,6 +56,7 @@ export interface MessageManagerCallbacks {
57
56
  onUpdateCommandOutputMessage?: (command: string, output: string) => void;
58
57
  onCompleteCommandMessage?: (command: string, exitCode: number) => void;
59
58
  onSlashCommandsChange?: (commands: SlashCommand[]) => void;
59
+ onInfoBlockAdded?: (content: string) => void;
60
60
  // Rewind callbacks
61
61
  onShowRewind?: () => void;
62
62
  // Subagent callbacks
@@ -72,6 +72,10 @@ export interface MessageManagerCallbacks {
72
72
  subagentId: string,
73
73
  status: "active" | "completed" | "error" | "aborted",
74
74
  ) => void;
75
+ onFileHistoryBlockAdded?: (
76
+ snapshots: import("../types/reversion.js").FileSnapshot[],
77
+ ) => void;
78
+ onSubagentTaskStopRequested?: (subagentId: string) => void;
75
79
  }
76
80
 
77
81
  export interface MessageManagerOptions {
@@ -80,14 +84,16 @@ export interface MessageManagerOptions {
80
84
  logger?: Logger;
81
85
  sessionType?: "main" | "subagent";
82
86
  subagentType?: string;
87
+ memoryRuleManager?: MemoryRuleManager;
83
88
  }
84
89
 
85
90
  export class MessageManager {
86
91
  // Private state properties
87
92
  private sessionId: string;
93
+ private rootSessionId: string;
94
+ private parentSessionId?: string;
88
95
  private messages: Message[];
89
96
  private latestTotalTokens: number;
90
- private userInputHistory: string[];
91
97
  private workdir: string;
92
98
  private encodedWorkdir: string; // Cached encoded workdir
93
99
  private logger?: Logger; // Add optional logger property
@@ -95,14 +101,15 @@ export class MessageManager {
95
101
  private transcriptPath: string; // Cached transcript path
96
102
  private savedMessageCount: number; // Track how many messages have been saved to prevent duplication
97
103
  private filesInContext: Set<string> = new Set(); // Track files mentioned in the conversation
104
+ private memoryRuleManager?: MemoryRuleManager;
98
105
  private sessionType: "main" | "subagent";
99
106
  private subagentType?: string;
100
107
 
101
108
  constructor(options: MessageManagerOptions) {
102
109
  this.sessionId = generateSessionId();
110
+ this.rootSessionId = this.sessionId;
103
111
  this.messages = [];
104
112
  this.latestTotalTokens = 0;
105
- this.userInputHistory = [];
106
113
  this.workdir = options.workdir;
107
114
  this.encodedWorkdir = pathEncoder.encodeSync(this.workdir); // Cache encoded workdir
108
115
  this.callbacks = options.callbacks;
@@ -110,6 +117,7 @@ export class MessageManager {
110
117
  this.savedMessageCount = 0; // Initialize saved message count tracker
111
118
  this.sessionType = options.sessionType || "main";
112
119
  this.subagentType = options.subagentType;
120
+ this.memoryRuleManager = options.memoryRuleManager;
113
121
 
114
122
  // Compute and cache the transcript path
115
123
  this.transcriptPath = this.computeTranscriptPath();
@@ -120,6 +128,14 @@ export class MessageManager {
120
128
  return this.sessionId;
121
129
  }
122
130
 
131
+ public getRootSessionId(): string {
132
+ return this.rootSessionId;
133
+ }
134
+
135
+ public getParentSessionId(): string | undefined {
136
+ return this.parentSessionId;
137
+ }
138
+
123
139
  public getMessages(): Message[] {
124
140
  return [...this.messages];
125
141
  }
@@ -128,10 +144,6 @@ export class MessageManager {
128
144
  return this.latestTotalTokens;
129
145
  }
130
146
 
131
- public getUserInputHistory(): string[] {
132
- return [...this.userInputHistory];
133
- }
134
-
135
147
  public getWorkdir(): string {
136
148
  return this.workdir;
137
149
  }
@@ -151,6 +163,30 @@ export class MessageManager {
151
163
  return this.transcriptPath;
152
164
  }
153
165
 
166
+ /**
167
+ * Get combined memory content (project memory + user memory + modular rules)
168
+ */
169
+ public async getCombinedMemory(): Promise<string> {
170
+ const memory = await import("../services/memory.js");
171
+ let combined = await memory.getCombinedMemoryContent(this.workdir);
172
+
173
+ if (this.memoryRuleManager) {
174
+ const filesInContext = this.getFilesInContext();
175
+ const activeRules = this.memoryRuleManager.getActiveRules(filesInContext);
176
+ if (activeRules.length > 0) {
177
+ this.logger?.debug(
178
+ `Active modular rules (${activeRules.length}): ${activeRules.map((r) => r.id).join(", ")}`,
179
+ );
180
+ if (combined) {
181
+ combined += "\n\n";
182
+ }
183
+ combined += activeRules.map((r) => r.content).join("\n\n");
184
+ }
185
+ }
186
+
187
+ return combined;
188
+ }
189
+
154
190
  /**
155
191
  * Compute the transcript path using cached encoded workdir
156
192
  * Called during construction and when sessionId changes
@@ -218,6 +254,8 @@ export class MessageManager {
218
254
  unsavedMessages, // Only append new messages
219
255
  this.workdir,
220
256
  this.sessionType,
257
+ this.rootSessionId,
258
+ this.parentSessionId,
221
259
  );
222
260
 
223
261
  // Update the saved message count
@@ -235,18 +273,13 @@ export class MessageManager {
235
273
  }
236
274
  }
237
275
 
238
- public setUserInputHistory(userInputHistory: string[]): void {
239
- this.userInputHistory = [...userInputHistory];
240
- this.callbacks.onUserInputHistoryChange?.(this.userInputHistory);
241
- }
242
-
243
276
  /**
244
- * Clear messages and input history
277
+ * Clear messages
245
278
  */
246
279
  public clearMessages(): void {
247
280
  this.setMessages([]);
248
- this.setUserInputHistory([]);
249
281
  this.setSessionId(generateSessionId());
282
+ this.rootSessionId = this.sessionId;
250
283
  this.setlatestTotalTokens(0);
251
284
  this.savedMessageCount = 0; // Reset saved message count
252
285
  }
@@ -268,36 +301,17 @@ export class MessageManager {
268
301
  // Initialize state from session data
269
302
  public initializeFromSession(sessionData: SessionData): void {
270
303
  this.setSessionId(sessionData.id);
304
+ this.rootSessionId = sessionData.rootSessionId || sessionData.id;
305
+ this.parentSessionId = sessionData.parentSessionId;
271
306
  this.setMessages([...sessionData.messages]);
272
307
  this.updateFilesInContext(sessionData.messages);
273
308
  this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
274
309
 
275
- // Extract user input history from session messages
276
- this.setUserInputHistory(extractUserInputHistory(sessionData.messages));
277
-
278
310
  // Set saved message count to the number of loaded messages since they're already saved
279
311
  // This must be done after setSessionId which resets it to 0
280
312
  this.savedMessageCount = sessionData.messages.length;
281
313
  }
282
314
 
283
- // Add to input history
284
- public addToInputHistory(input: string): void {
285
- // Avoid adding duplicate inputs
286
- if (
287
- this.userInputHistory.length > 0 &&
288
- this.userInputHistory[this.userInputHistory.length - 1] === input
289
- ) {
290
- return;
291
- }
292
- // Limit history records, keep the latest 100
293
- this.setUserInputHistory([...this.userInputHistory, input].slice(-100));
294
- }
295
-
296
- // Clear input history
297
- public clearInputHistory(): void {
298
- this.setUserInputHistory([]);
299
- }
300
-
301
315
  // Encapsulated message operation functions
302
316
  public addUserMessage(params: UserMessageParams): void {
303
317
  const newMessages = addUserMessageToMessages({
@@ -373,17 +387,7 @@ export class MessageManager {
373
387
  public updateToolBlock(params: AgentToolBlockUpdateParams): void {
374
388
  const newMessages = updateToolBlockInMessage({
375
389
  messages: this.messages,
376
- id: params.id,
377
- parameters: params.parameters,
378
- result: params.result,
379
- success: params.success,
380
- error: params.error,
381
- stage: params.stage,
382
- name: params.name,
383
- shortResult: params.shortResult,
384
- images: params.images,
385
- compactParams: params.compactParams,
386
- parametersChunk: params.parametersChunk,
390
+ ...params,
387
391
  });
388
392
  this.setMessages(newMessages);
389
393
  this.callbacks.onToolBlockUpdated?.(params);
@@ -400,15 +404,27 @@ export class MessageManager {
400
404
  this.callbacks.onErrorBlockAdded?.(error);
401
405
  }
402
406
 
407
+ public addInfoBlock(content: string): void {
408
+ const lastMessage = this.messages[this.messages.length - 1];
409
+ if (lastMessage && lastMessage.role === "assistant") {
410
+ lastMessage.blocks.push({
411
+ type: "info",
412
+ content,
413
+ } as unknown as import("../types/index.js").MessageBlock);
414
+ this.setMessages([...this.messages]);
415
+ this.callbacks.onInfoBlockAdded?.(content);
416
+ }
417
+ }
418
+
403
419
  /**
404
- * Compress messages and update session, delete compressed messages, only keep compressed messages and subsequent messages
420
+ * Compress messages and update session, delete compressed messages, only keep compressed messages and last 3 messages
405
421
  */
406
422
  public compressMessagesAndUpdateSession(
407
- insertIndex: number,
408
423
  compressedContent: string,
409
424
  usage?: Usage,
410
425
  ): void {
411
- const currentMessages = this.messages;
426
+ // Get last 3 messages to preserve
427
+ const lastThreeMessages = this.messages.slice(-3);
412
428
 
413
429
  // Create compressed message
414
430
  const compressMessage: Message = {
@@ -423,24 +439,24 @@ export class MessageManager {
423
439
  ...(usage && { usage }),
424
440
  };
425
441
 
426
- // Convert negative index to positive index
427
- const actualIndex =
428
- insertIndex < 0 ? currentMessages.length + insertIndex : insertIndex;
429
-
430
- // Build new message array: keep compressed message and all messages from actualIndex onwards
431
- const newMessages: Message[] = [
432
- compressMessage,
433
- ...currentMessages.slice(actualIndex),
434
- ];
442
+ // Build new message array: keep the compressed message and last 3 messages
443
+ const newMessages: Message[] = [compressMessage, ...lastThreeMessages];
435
444
 
436
- // Update sessionId
445
+ // Update sessionId and parentSessionId
446
+ const oldSessionId = this.sessionId;
437
447
  this.setSessionId(generateSessionId());
448
+ this.parentSessionId = oldSessionId;
449
+
450
+ // Trigger task list update if this is the main session to ensure continuity
451
+ if (this.sessionType === "main") {
452
+ this.callbacks.onSessionIdChange?.(this.sessionId);
453
+ }
438
454
 
439
455
  // Set new message list
440
456
  this.setMessages(newMessages);
441
457
 
442
- // Trigger compression callback, insertIndex remains unchanged
443
- this.callbacks.onCompressBlockAdded?.(insertIndex, compressedContent);
458
+ // Trigger compression callback
459
+ this.callbacks.onCompressBlockAdded?.(compressedContent);
444
460
  }
445
461
 
446
462
  public addFileHistoryBlock(
@@ -455,6 +471,7 @@ export class MessageManager {
455
471
  snapshots,
456
472
  } as unknown as import("../types/index.js").MessageBlock);
457
473
  this.setMessages([...this.messages]);
474
+ this.callbacks.onFileHistoryBlockAdded?.(snapshots);
458
475
  }
459
476
  }
460
477
 
@@ -494,12 +511,13 @@ export class MessageManager {
494
511
  subagentName: string,
495
512
  sessionId: string,
496
513
  configuration: SubagentConfiguration,
497
- status: "active" | "completed" | "error" = "active",
514
+ status: "active" | "completed" | "error" | "aborted" = "active",
498
515
  parameters: {
499
516
  description: string;
500
517
  prompt: string;
501
518
  subagent_type: string;
502
519
  },
520
+ runInBackground?: boolean,
503
521
  ): void {
504
522
  const params: AddSubagentBlockParams = {
505
523
  messages: this.messages,
@@ -508,6 +526,7 @@ export class MessageManager {
508
526
  sessionId,
509
527
  status,
510
528
  configuration,
529
+ runInBackground,
511
530
  };
512
531
  const updatedMessages = addSubagentBlockToMessage(params);
513
532
  this.setMessages(updatedMessages);
@@ -519,6 +538,7 @@ export class MessageManager {
519
538
  updates: Partial<{
520
539
  status: "active" | "completed" | "error" | "aborted";
521
540
  sessionId: string;
541
+ runInBackground: boolean;
522
542
  }>,
523
543
  ): void {
524
544
  const updatedMessages = updateSubagentBlockInMessage(
@@ -532,7 +552,9 @@ export class MessageManager {
532
552
  subagentId,
533
553
  status: updates.status || "active",
534
554
  };
535
- this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.status);
555
+ if (updates.status) {
556
+ this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.status);
557
+ }
536
558
  }
537
559
 
538
560
  /**
@@ -657,6 +679,14 @@ export class MessageManager {
657
679
  this.setMessages(newMessages);
658
680
  }
659
681
 
682
+ public async getFullMessageThread(): Promise<{
683
+ messages: Message[];
684
+ sessionIds: string[];
685
+ }> {
686
+ const { loadFullMessageThread } = await import("../services/session.js");
687
+ return loadFullMessageThread(this.sessionId, this.workdir);
688
+ }
689
+
660
690
  /**
661
691
  * Truncate history to a specific index and revert file changes.
662
692
  * @param index - The index of the user message to truncate to.
@@ -666,30 +696,110 @@ export class MessageManager {
666
696
  index: number,
667
697
  reversionManager?: import("./reversionManager.js").ReversionManager,
668
698
  ): Promise<void> {
669
- if (index < 0 || index >= this.messages.length) {
699
+ const { messages, sessionIds } = await this.getFullMessageThread();
700
+
701
+ if (index < 0 || index >= messages.length) {
670
702
  throw new Error(`Invalid message index: ${index}`);
671
703
  }
672
704
 
673
- // Identify messages to be removed
674
- const messagesToRemove = this.messages.slice(index);
705
+ // Find which session the index belongs to
706
+ let targetSessionId = this.sessionId;
707
+ let targetIndexInSession = index;
708
+
709
+ // We need to be careful here because loadFullMessageThread might have removed "compress" blocks
710
+ // Let's re-calculate based on the actual messages returned.
711
+ // Actually, it's easier to just load sessions one by one again or keep track of counts.
712
+
713
+ // For simplicity, let's assume we want to truncate the WHOLE thread.
714
+ // If the index is in a previous session, we need to:
715
+ // 1. Load that session.
716
+ // 2. Truncate it.
717
+ // 3. Make it the current session.
718
+ // 4. Delete/Invalidate subsequent sessions.
719
+
720
+ // To correctly map 'index' to a session, we need to know the message count of each session
721
+ // as they appear in the concatenated 'messages' array.
722
+
723
+ let remainingIndex = index;
724
+ const { loadSessionFromJsonl } = await import("../services/session.js");
725
+
726
+ for (const sid of sessionIds) {
727
+ const sessionData = await loadSessionFromJsonl(sid, this.workdir);
728
+ if (!sessionData) continue;
729
+
730
+ const sessionMessages = sessionData.messages;
731
+ // If this is not the first session in the thread, it might have a compress block at the start
732
+ // that was removed in getFullMessageThread.
733
+ const hasCompressBlock = sessionMessages[0]?.blocks.some(
734
+ (b) => b.type === "compress",
735
+ );
736
+ const effectiveMessages =
737
+ hasCompressBlock && sid !== sessionIds[0]
738
+ ? sessionMessages.slice(1)
739
+ : sessionMessages;
740
+
741
+ if (remainingIndex < effectiveMessages.length) {
742
+ targetSessionId = sid;
743
+ targetIndexInSession = hasCompressBlock
744
+ ? remainingIndex + 1
745
+ : remainingIndex;
746
+ break;
747
+ }
748
+ remainingIndex -= effectiveMessages.length;
749
+ }
750
+
751
+ // Load the target session to perform truncation
752
+ const targetSessionData = await loadSessionFromJsonl(
753
+ targetSessionId,
754
+ this.workdir,
755
+ );
756
+ if (!targetSessionData)
757
+ throw new Error(`Target session ${targetSessionId} not found`);
758
+
759
+ // Identify messages to be removed (from the whole thread)
760
+ const messagesToRemove = messages.slice(index);
675
761
  const messageIdsToRemove = messagesToRemove
676
762
  .map((m) => m.id as string)
677
763
  .filter((id) => !!id);
678
764
 
679
765
  // Revert file changes if manager is provided
680
766
  if (reversionManager && messageIdsToRemove.length > 0) {
681
- await reversionManager.revertTo(messageIdsToRemove, this.messages);
767
+ await reversionManager.revertTo(messageIdsToRemove, messages);
682
768
  }
683
769
 
684
- // Truncate messages in memory
685
- const newMessages = this.messages.slice(0, index);
686
- this.setMessages(newMessages);
770
+ // Truncate messages in the target session
771
+ const newMessagesInSession = targetSessionData.messages.slice(
772
+ 0,
773
+ targetIndexInSession,
774
+ );
775
+
776
+ // Identify subagent tasks to stop
777
+ for (const message of messagesToRemove) {
778
+ for (const block of message.blocks) {
779
+ if (block.type === "subagent" && block.subagentId) {
780
+ this.callbacks.onSubagentTaskStopRequested?.(block.subagentId);
781
+ }
782
+ }
783
+ }
687
784
 
688
- // Update persistence: rewrite the session file
689
- await this.rewriteSessionFile(newMessages);
785
+ // Update target session file
786
+ this.sessionId = targetSessionId;
787
+ this.rootSessionId = targetSessionData.rootSessionId || targetSessionId;
788
+ this.parentSessionId = targetSessionData.parentSessionId;
789
+ this.transcriptPath = this.computeTranscriptPath();
790
+
791
+ await this.rewriteSessionFile(newMessagesInSession);
792
+
793
+ // Update in-memory messages to the truncated session messages
794
+ // We do NOT include ancestor messages here to avoid exceeding context limits.
795
+ // The 'compress' block at the start of the session (if any) already summarizes them.
796
+ this.setMessages(newMessagesInSession);
690
797
 
691
798
  // Update saved message count
692
- this.savedMessageCount = newMessages.length;
799
+ this.savedMessageCount = newMessagesInSession.length;
800
+
801
+ // Notify session ID change if it changed
802
+ this.callbacks.onSessionIdChange?.(this.sessionId);
693
803
  }
694
804
 
695
805
  /**
@@ -237,13 +237,7 @@ export class PermissionManager {
237
237
  * Get the current effective permission mode for tool execution context
238
238
  */
239
239
  getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode {
240
- const mode = this.resolveEffectivePermissionMode(cliPermissionMode);
241
- this.logger?.debug("getCurrentEffectiveMode", {
242
- cliPermissionMode,
243
- configuredDefaultMode: this.configuredDefaultMode,
244
- resolvedMode: mode,
245
- });
246
- return mode;
240
+ return this.resolveEffectivePermissionMode(cliPermissionMode);
247
241
  }
248
242
 
249
243
  /**
@@ -329,40 +323,30 @@ export class PermissionManager {
329
323
  workdir,
330
324
  );
331
325
  if (!isInside) {
332
- this.logger?.warn(
333
- "File operation outside the Safe Zone in acceptEdits mode",
326
+ this.logger?.info(
327
+ "File operation outside the Safe Zone in acceptEdits mode, falling back to manual confirmation",
334
328
  {
335
329
  toolName: context.toolName,
336
330
  targetPath,
337
331
  resolvedPath,
338
332
  },
339
333
  );
340
- return {
341
- behavior: "deny",
342
- message: `Tool '${context.toolName}' attempted to modify a file outside the Safe Zone: ${targetPath}. Operations outside the Safe Zone always require manual confirmation.`,
343
- };
334
+ // Fall through to normal permission check flow to trigger confirmation prompt
335
+ } else {
336
+ this.logger?.debug(
337
+ "Permission automatically accepted for tool in acceptEdits mode",
338
+ {
339
+ toolName: context.toolName,
340
+ },
341
+ );
342
+ return { behavior: "allow" };
344
343
  }
345
344
  }
346
-
347
- this.logger?.debug(
348
- "Permission automatically accepted for tool in acceptEdits mode",
349
- {
350
- toolName: context.toolName,
351
- },
352
- );
353
- return { behavior: "allow" };
354
345
  }
355
346
  }
356
347
 
357
348
  // 1.3 If plan mode, allow Read-only tools and Edit/Write for plan file
358
349
  if (context.permissionMode === "plan") {
359
- if (context.toolName === BASH_TOOL_NAME) {
360
- return {
361
- behavior: "deny",
362
- message: "Bash commands are not allowed in plan mode.",
363
- };
364
- }
365
-
366
350
  const writeTools = [
367
351
  EDIT_TOOL_NAME,
368
352
  MULTI_EDIT_TOOL_NAME,
@@ -498,6 +482,27 @@ export class PermissionManager {
498
482
  suggestedPrefix,
499
483
  };
500
484
 
485
+ // Set hidePersistentOption for out-of-bounds file operations
486
+ const fileTools = [
487
+ EDIT_TOOL_NAME,
488
+ MULTI_EDIT_TOOL_NAME,
489
+ DELETE_FILE_TOOL_NAME,
490
+ WRITE_TOOL_NAME,
491
+ ];
492
+ if (fileTools.includes(toolName)) {
493
+ const targetPath = (toolInput?.file_path || toolInput?.target_file) as
494
+ | string
495
+ | undefined;
496
+ const workdir = toolInput?.workdir as string | undefined;
497
+
498
+ if (targetPath) {
499
+ const { isInside } = this.isInsideSafeZone(targetPath, workdir);
500
+ if (!isInside) {
501
+ context.hidePersistentOption = true;
502
+ }
503
+ }
504
+ }
505
+
501
506
  // Set hidePersistentOption for dangerous or out-of-bounds bash commands
502
507
  if (toolName === BASH_TOOL_NAME && toolInput?.command) {
503
508
  const command = String(toolInput.command);
@@ -17,7 +17,7 @@ export class PlanManager {
17
17
  /**
18
18
  * Ensures the plan directory exists and generates a new plan file path with a random name
19
19
  */
20
- public async getOrGeneratePlanFilePath(): Promise<{
20
+ public async getOrGeneratePlanFilePath(seed?: string): Promise<{
21
21
  path: string;
22
22
  name: string;
23
23
  }> {
@@ -30,7 +30,7 @@ export class PlanManager {
30
30
  );
31
31
  throw error;
32
32
  }
33
- const name = generateRandomName();
33
+ const name = generateRandomName(seed);
34
34
  const filePath = path.join(this.planDir, `${name}.md`);
35
35
  this.logger?.info(`Generated plan file path: ${filePath}`);
36
36
  return { path: filePath, name };
@@ -149,9 +149,7 @@ export class PluginManager {
149
149
  * @param configs Array of plugin configurations
150
150
  */
151
151
  async loadPlugins(configs: PluginConfig[]): Promise<void> {
152
- // Load installed plugins from marketplace first
153
- await this.loadInstalledPlugins();
154
-
152
+ // Load plugins from configuration (e.g. --plugin-dir) first to give them higher priority
155
153
  for (const config of configs) {
156
154
  if (config.type !== "local") {
157
155
  this.logger?.warn(`Unsupported plugin type: ${config.type}`);
@@ -164,6 +162,9 @@ export class PluginManager {
164
162
 
165
163
  await this.loadSinglePlugin(absolutePath);
166
164
  }
165
+
166
+ // Load installed plugins from marketplace
167
+ await this.loadInstalledPlugins();
167
168
  }
168
169
 
169
170
  /**
@@ -1,5 +1,7 @@
1
1
  import type { MessageManager } from "./messageManager.js";
2
2
  import type { AIManager } from "./aiManager.js";
3
+ import type { BackgroundTaskManager } from "./backgroundTaskManager.js";
4
+ import type { TaskManager } from "../services/taskManager.js";
3
5
  import type {
4
6
  SlashCommand,
5
7
  CustomSlashCommand,
@@ -26,6 +28,8 @@ const execAsync = promisify(exec);
26
28
  export interface SlashCommandManagerOptions {
27
29
  messageManager: MessageManager;
28
30
  aiManager: AIManager;
31
+ backgroundTaskManager: BackgroundTaskManager;
32
+ taskManager: TaskManager;
29
33
  workdir: string;
30
34
  logger?: Logger;
31
35
  }
@@ -35,12 +39,16 @@ export class SlashCommandManager {
35
39
  private customCommands = new Map<string, CustomSlashCommand>();
36
40
  private messageManager: MessageManager;
37
41
  private aiManager: AIManager;
42
+ private backgroundTaskManager: BackgroundTaskManager;
43
+ private taskManager: TaskManager;
38
44
  private workdir: string;
39
45
  private logger?: Logger;
40
46
 
41
47
  constructor(options: SlashCommandManagerOptions) {
42
48
  this.messageManager = options.messageManager;
43
49
  this.aiManager = options.aiManager;
50
+ this.backgroundTaskManager = options.backgroundTaskManager;
51
+ this.taskManager = options.taskManager;
44
52
  this.workdir = options.workdir;
45
53
  this.logger = options.logger;
46
54
 
@@ -57,8 +65,13 @@ export class SlashCommandManager {
57
65
  handler: () => {
58
66
  // Clear chat messages
59
67
  this.messageManager.clearMessages();
60
- // Clear terminal screen
61
- process.stdout.write("\x1Bc");
68
+
69
+ // Reset task list if WAVE_TASK_LIST_ID is not set
70
+ if (!process.env.WAVE_TASK_LIST_ID) {
71
+ const newTaskListId = this.messageManager.getRootSessionId();
72
+ this.taskManager.setTaskListId(newTaskListId);
73
+ this.taskManager.emit("tasksChange", newTaskListId);
74
+ }
62
75
  },
63
76
  });
64
77