wave-agent-sdk 0.0.8 → 0.0.10

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 (236) hide show
  1. package/dist/agent.d.ts +92 -23
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +340 -137
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/managers/aiManager.d.ts +14 -36
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +74 -77
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +4 -3
  12. package/dist/managers/hookManager.d.ts +3 -8
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +39 -29
  15. package/dist/managers/liveConfigManager.d.ts +55 -18
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  17. package/dist/managers/liveConfigManager.js +372 -90
  18. package/dist/managers/lspManager.d.ts +43 -0
  19. package/dist/managers/lspManager.d.ts.map +1 -0
  20. package/dist/managers/lspManager.js +326 -0
  21. package/dist/managers/messageManager.d.ts +8 -16
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +52 -74
  24. package/dist/managers/permissionManager.d.ts +66 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +208 -0
  27. package/dist/managers/skillManager.d.ts +1 -0
  28. package/dist/managers/skillManager.d.ts.map +1 -1
  29. package/dist/managers/skillManager.js +2 -1
  30. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  31. package/dist/managers/slashCommandManager.js +0 -1
  32. package/dist/managers/subagentManager.d.ts +8 -23
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +97 -117
  35. package/dist/managers/toolManager.d.ts +38 -1
  36. package/dist/managers/toolManager.d.ts.map +1 -1
  37. package/dist/managers/toolManager.js +66 -2
  38. package/dist/services/aiService.d.ts +3 -1
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +123 -30
  41. package/dist/services/configurationService.d.ts +116 -0
  42. package/dist/services/configurationService.d.ts.map +1 -0
  43. package/dist/services/configurationService.js +585 -0
  44. package/dist/services/fileWatcher.d.ts.map +1 -1
  45. package/dist/services/fileWatcher.js +5 -6
  46. package/dist/services/hook.d.ts +7 -124
  47. package/dist/services/hook.d.ts.map +1 -1
  48. package/dist/services/hook.js +46 -458
  49. package/dist/services/jsonlHandler.d.ts +24 -15
  50. package/dist/services/jsonlHandler.d.ts.map +1 -1
  51. package/dist/services/jsonlHandler.js +67 -88
  52. package/dist/services/memory.d.ts +0 -9
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +2 -49
  55. package/dist/services/session.d.ts +82 -33
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +275 -181
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +72 -13
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +25 -0
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +30 -6
  64. package/dist/tools/lspTool.d.ts +6 -0
  65. package/dist/tools/lspTool.d.ts.map +1 -0
  66. package/dist/tools/lspTool.js +589 -0
  67. package/dist/tools/multiEditTool.d.ts.map +1 -1
  68. package/dist/tools/multiEditTool.js +26 -7
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +111 -2
  71. package/dist/tools/skillTool.js +2 -2
  72. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  73. package/dist/tools/todoWriteTool.js +23 -0
  74. package/dist/tools/types.d.ts +11 -8
  75. package/dist/tools/types.d.ts.map +1 -1
  76. package/dist/tools/writeTool.d.ts.map +1 -1
  77. package/dist/tools/writeTool.js +25 -9
  78. package/dist/types/commands.d.ts +0 -1
  79. package/dist/types/commands.d.ts.map +1 -1
  80. package/dist/types/config.d.ts +4 -0
  81. package/dist/types/config.d.ts.map +1 -1
  82. package/dist/types/configuration.d.ts +69 -0
  83. package/dist/types/configuration.d.ts.map +1 -0
  84. package/dist/types/configuration.js +8 -0
  85. package/dist/types/core.d.ts +10 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +41 -0
  88. package/dist/types/environment.d.ts.map +1 -1
  89. package/dist/types/fileSearch.d.ts +5 -0
  90. package/dist/types/fileSearch.d.ts.map +1 -0
  91. package/dist/types/fileSearch.js +1 -0
  92. package/dist/types/hooks.d.ts +11 -2
  93. package/dist/types/hooks.d.ts.map +1 -1
  94. package/dist/types/hooks.js +1 -7
  95. package/dist/types/index.d.ts +5 -0
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/index.js +5 -0
  98. package/dist/types/lsp.d.ts +90 -0
  99. package/dist/types/lsp.d.ts.map +1 -0
  100. package/dist/types/lsp.js +4 -0
  101. package/dist/types/messaging.d.ts +6 -11
  102. package/dist/types/messaging.d.ts.map +1 -1
  103. package/dist/types/permissions.d.ts +35 -0
  104. package/dist/types/permissions.d.ts.map +1 -0
  105. package/dist/types/permissions.js +12 -0
  106. package/dist/types/session.d.ts +1 -6
  107. package/dist/types/session.d.ts.map +1 -1
  108. package/dist/types/skills.d.ts +1 -0
  109. package/dist/types/skills.d.ts.map +1 -1
  110. package/dist/types/tools.d.ts +35 -0
  111. package/dist/types/tools.d.ts.map +1 -0
  112. package/dist/types/tools.js +4 -0
  113. package/dist/utils/abortUtils.d.ts +34 -0
  114. package/dist/utils/abortUtils.d.ts.map +1 -0
  115. package/dist/utils/abortUtils.js +92 -0
  116. package/dist/utils/bashHistory.d.ts +4 -0
  117. package/dist/utils/bashHistory.d.ts.map +1 -1
  118. package/dist/utils/bashHistory.js +21 -4
  119. package/dist/utils/builtinSubagents.d.ts +7 -0
  120. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  121. package/dist/utils/builtinSubagents.js +65 -0
  122. package/dist/utils/cacheControlUtils.d.ts +8 -33
  123. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  124. package/dist/utils/cacheControlUtils.js +83 -126
  125. package/dist/utils/constants.d.ts +0 -12
  126. package/dist/utils/constants.d.ts.map +1 -1
  127. package/dist/utils/constants.js +1 -13
  128. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  129. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  130. package/dist/utils/convertMessagesForAPI.js +33 -14
  131. package/dist/utils/fileSearch.d.ts +14 -0
  132. package/dist/utils/fileSearch.d.ts.map +1 -0
  133. package/dist/utils/fileSearch.js +88 -0
  134. package/dist/utils/fileUtils.d.ts +14 -2
  135. package/dist/utils/fileUtils.d.ts.map +1 -1
  136. package/dist/utils/fileUtils.js +101 -17
  137. package/dist/utils/globalLogger.d.ts +0 -14
  138. package/dist/utils/globalLogger.d.ts.map +1 -1
  139. package/dist/utils/globalLogger.js +0 -16
  140. package/dist/utils/largeOutputHandler.d.ts +15 -0
  141. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  142. package/dist/utils/largeOutputHandler.js +40 -0
  143. package/dist/utils/markdownParser.d.ts.map +1 -1
  144. package/dist/utils/markdownParser.js +1 -17
  145. package/dist/utils/messageOperations.d.ts +1 -11
  146. package/dist/utils/messageOperations.d.ts.map +1 -1
  147. package/dist/utils/messageOperations.js +7 -24
  148. package/dist/utils/pathEncoder.d.ts +4 -0
  149. package/dist/utils/pathEncoder.d.ts.map +1 -1
  150. package/dist/utils/pathEncoder.js +16 -9
  151. package/dist/utils/subagentParser.d.ts +2 -2
  152. package/dist/utils/subagentParser.d.ts.map +1 -1
  153. package/dist/utils/subagentParser.js +10 -7
  154. package/dist/utils/tokenEstimator.d.ts +39 -0
  155. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  156. package/dist/utils/tokenEstimator.js +55 -0
  157. package/package.json +5 -8
  158. package/src/agent.ts +460 -216
  159. package/src/index.ts +2 -0
  160. package/src/managers/aiManager.ts +107 -111
  161. package/src/managers/backgroundBashManager.ts +4 -3
  162. package/src/managers/hookManager.ts +44 -39
  163. package/src/managers/liveConfigManager.ts +524 -138
  164. package/src/managers/lspManager.ts +434 -0
  165. package/src/managers/messageManager.ts +73 -103
  166. package/src/managers/permissionManager.ts +276 -0
  167. package/src/managers/skillManager.ts +3 -1
  168. package/src/managers/slashCommandManager.ts +1 -2
  169. package/src/managers/subagentManager.ts +116 -159
  170. package/src/managers/toolManager.ts +95 -3
  171. package/src/services/aiService.ts +207 -26
  172. package/src/services/configurationService.ts +762 -0
  173. package/src/services/fileWatcher.ts +5 -6
  174. package/src/services/hook.ts +50 -631
  175. package/src/services/jsonlHandler.ts +84 -100
  176. package/src/services/memory.ts +2 -59
  177. package/src/services/session.ts +338 -213
  178. package/src/tools/bashTool.ts +89 -16
  179. package/src/tools/deleteFileTool.ts +36 -0
  180. package/src/tools/editTool.ts +41 -7
  181. package/src/tools/lspTool.ts +760 -0
  182. package/src/tools/multiEditTool.ts +37 -8
  183. package/src/tools/readTool.ts +125 -2
  184. package/src/tools/skillTool.ts +2 -2
  185. package/src/tools/todoWriteTool.ts +33 -1
  186. package/src/tools/types.ts +15 -9
  187. package/src/tools/writeTool.ts +36 -10
  188. package/src/types/commands.ts +0 -1
  189. package/src/types/config.ts +5 -0
  190. package/src/types/configuration.ts +73 -0
  191. package/src/types/core.ts +11 -0
  192. package/src/types/environment.ts +44 -0
  193. package/src/types/fileSearch.ts +4 -0
  194. package/src/types/hooks.ts +14 -11
  195. package/src/types/index.ts +5 -0
  196. package/src/types/lsp.ts +96 -0
  197. package/src/types/messaging.ts +8 -13
  198. package/src/types/permissions.ts +48 -0
  199. package/src/types/session.ts +3 -8
  200. package/src/types/skills.ts +1 -0
  201. package/src/types/tools.ts +38 -0
  202. package/src/utils/abortUtils.ts +118 -0
  203. package/src/utils/bashHistory.ts +28 -4
  204. package/src/utils/builtinSubagents.ts +71 -0
  205. package/src/utils/cacheControlUtils.ts +106 -171
  206. package/src/utils/constants.ts +1 -16
  207. package/src/utils/convertMessagesForAPI.ts +38 -14
  208. package/src/utils/fileSearch.ts +107 -0
  209. package/src/utils/fileUtils.ts +114 -19
  210. package/src/utils/globalLogger.ts +0 -17
  211. package/src/utils/largeOutputHandler.ts +55 -0
  212. package/src/utils/markdownParser.ts +1 -19
  213. package/src/utils/messageOperations.ts +7 -35
  214. package/src/utils/pathEncoder.ts +24 -9
  215. package/src/utils/subagentParser.ts +11 -8
  216. package/src/utils/tokenEstimator.ts +68 -0
  217. package/dist/constants/events.d.ts +0 -28
  218. package/dist/constants/events.d.ts.map +0 -1
  219. package/dist/constants/events.js +0 -27
  220. package/dist/services/configurationWatcher.d.ts +0 -120
  221. package/dist/services/configurationWatcher.d.ts.map +0 -1
  222. package/dist/services/configurationWatcher.js +0 -439
  223. package/dist/services/memoryStore.d.ts +0 -81
  224. package/dist/services/memoryStore.d.ts.map +0 -1
  225. package/dist/services/memoryStore.js +0 -200
  226. package/dist/types/memoryStore.d.ts +0 -82
  227. package/dist/types/memoryStore.d.ts.map +0 -1
  228. package/dist/types/memoryStore.js +0 -7
  229. package/dist/utils/configResolver.d.ts +0 -65
  230. package/dist/utils/configResolver.d.ts.map +0 -1
  231. package/dist/utils/configResolver.js +0 -210
  232. package/src/constants/events.ts +0 -38
  233. package/src/services/configurationWatcher.ts +0 -622
  234. package/src/services/memoryStore.ts +0 -279
  235. package/src/types/memoryStore.ts +0 -94
  236. package/src/utils/configResolver.ts +0 -302
@@ -2,7 +2,6 @@ import {
2
2
  addAssistantMessageToMessages,
3
3
  updateToolBlockInMessage,
4
4
  addErrorBlockToMessage,
5
- addDiffBlockToMessage,
6
5
  addUserMessageToMessages,
7
6
  extractUserInputHistory,
8
7
  addMemoryBlockToMessage,
@@ -21,9 +20,6 @@ import type { SubagentConfiguration } from "../utils/subagentParser.js";
21
20
  import type { Logger, Message, Usage } from "../types/index.js";
22
21
  import { join } from "path";
23
22
  import {
24
- cleanupExpiredSessionsFromJsonl,
25
- getLatestSessionFromJsonl,
26
- loadSessionFromJsonl,
27
23
  appendMessages,
28
24
  createSession,
29
25
  generateSessionId,
@@ -45,8 +41,9 @@ export interface MessageManagerCallbacks {
45
41
  onAssistantMessageAdded?: () => void;
46
42
  // NEW: Streaming content callback - FR-001: receives chunk and accumulated content
47
43
  onAssistantContentUpdated?: (chunk: string, accumulated: string) => void;
44
+ // NEW: Streaming reasoning callback
45
+ onAssistantReasoningUpdated?: (chunk: string, accumulated: string) => void;
48
46
  onToolBlockUpdated?: (params: AgentToolBlockUpdateParams) => void;
49
- onDiffBlockAdded?: (filePath: string, diffResult: string) => void;
50
47
  onErrorBlockAdded?: (error: string) => void;
51
48
  onCompressBlockAdded?: (insertIndex: number, content: string) => void;
52
49
  onCompressionStateChange?: (isCompressing: boolean) => void;
@@ -80,7 +77,6 @@ export interface MessageManagerOptions {
80
77
  workdir: string;
81
78
  logger?: Logger;
82
79
  sessionType?: "main" | "subagent";
83
- parentSessionId?: string;
84
80
  subagentType?: string;
85
81
  }
86
82
 
@@ -90,7 +86,6 @@ export class MessageManager {
90
86
  private messages: Message[];
91
87
  private latestTotalTokens: number;
92
88
  private userInputHistory: string[];
93
- private sessionStartTime: string;
94
89
  private workdir: string;
95
90
  private encodedWorkdir: string; // Cached encoded workdir
96
91
  private logger?: Logger; // Add optional logger property
@@ -98,7 +93,6 @@ export class MessageManager {
98
93
  private transcriptPath: string; // Cached transcript path
99
94
  private savedMessageCount: number; // Track how many messages have been saved to prevent duplication
100
95
  private sessionType: "main" | "subagent";
101
- private parentSessionId?: string;
102
96
  private subagentType?: string;
103
97
 
104
98
  constructor(options: MessageManagerOptions) {
@@ -106,14 +100,12 @@ export class MessageManager {
106
100
  this.messages = [];
107
101
  this.latestTotalTokens = 0;
108
102
  this.userInputHistory = [];
109
- this.sessionStartTime = new Date().toISOString();
110
103
  this.workdir = options.workdir;
111
104
  this.encodedWorkdir = pathEncoder.encodeSync(this.workdir); // Cache encoded workdir
112
105
  this.callbacks = options.callbacks;
113
106
  this.logger = options.logger;
114
107
  this.savedMessageCount = 0; // Initialize saved message count tracker
115
108
  this.sessionType = options.sessionType || "main";
116
- this.parentSessionId = options.parentSessionId;
117
109
  this.subagentType = options.subagentType;
118
110
 
119
111
  // Compute and cache the transcript path
@@ -137,10 +129,6 @@ export class MessageManager {
137
129
  return [...this.userInputHistory];
138
130
  }
139
131
 
140
- public getSessionStartTime(): string {
141
- return this.sessionStartTime;
142
- }
143
-
144
132
  public getWorkdir(): string {
145
133
  return this.workdir;
146
134
  }
@@ -173,6 +161,7 @@ export class MessageManager {
173
161
  this.savedMessageCount = 0;
174
162
  // Recompute transcript path since session ID changed
175
163
  this.transcriptPath = this.computeTranscriptPath();
164
+
176
165
  this.callbacks.onSessionIdChange?.(sessionId);
177
166
  }
178
167
  }
@@ -182,13 +171,7 @@ export class MessageManager {
182
171
  */
183
172
  private async createSessionIfNeeded(): Promise<void> {
184
173
  try {
185
- await createSession(
186
- this.sessionId,
187
- this.workdir,
188
- this.sessionType,
189
- this.parentSessionId,
190
- this.subagentType,
191
- );
174
+ await createSession(this.sessionId, this.workdir, this.sessionType);
192
175
  } catch (error) {
193
176
  this.logger?.error("Failed to create session:", error);
194
177
  }
@@ -223,6 +206,7 @@ export class MessageManager {
223
206
  this.sessionId,
224
207
  unsavedMessages, // Only append new messages
225
208
  this.workdir,
209
+ this.sessionType,
226
210
  );
227
211
 
228
212
  // Update the saved message count
@@ -232,61 +216,10 @@ export class MessageManager {
232
216
  }
233
217
  }
234
218
 
235
- /**
236
- * Handle session restoration logic
237
- */
238
- public async handleSessionRestoration(
239
- restoreSessionId?: string,
240
- continueLastSession?: boolean,
241
- ): Promise<void> {
242
- // Clean up expired sessions first
243
- cleanupExpiredSessionsFromJsonl(this.workdir).catch((error) => {
244
- this.logger?.warn("Failed to cleanup expired sessions:", error);
245
- });
246
-
247
- if (!restoreSessionId && !continueLastSession) {
248
- return;
249
- }
250
-
251
- try {
252
- let sessionToRestore: SessionData | null = null;
253
-
254
- if (restoreSessionId) {
255
- // Use only JSONL format - no legacy support
256
- sessionToRestore = await loadSessionFromJsonl(
257
- restoreSessionId,
258
- this.workdir,
259
- );
260
- if (!sessionToRestore) {
261
- console.error(`Session not found: ${restoreSessionId}`);
262
- process.exit(1);
263
- }
264
- } else if (continueLastSession) {
265
- // Use only JSONL format - no legacy support
266
- sessionToRestore = await getLatestSessionFromJsonl(this.workdir);
267
- if (!sessionToRestore) {
268
- console.error(
269
- `No previous session found for workdir: ${this.workdir}`,
270
- );
271
- process.exit(1);
272
- }
273
- }
274
-
275
- if (sessionToRestore) {
276
- console.log(`Restoring session: ${sessionToRestore.id}`);
277
-
278
- // Initialize from session data
279
- this.initializeFromSession(sessionToRestore);
280
- }
281
- } catch (error) {
282
- console.error("Failed to restore session:", error);
283
- process.exit(1);
284
- }
285
- }
286
-
287
219
  public setlatestTotalTokens(latestTotalTokens: number): void {
288
220
  if (this.latestTotalTokens !== latestTotalTokens) {
289
221
  this.latestTotalTokens = latestTotalTokens;
222
+
290
223
  this.callbacks.onLatestTotalTokensChange?.(latestTotalTokens);
291
224
  }
292
225
  }
@@ -304,7 +237,6 @@ export class MessageManager {
304
237
  this.setUserInputHistory([]);
305
238
  this.setSessionId(generateSessionId());
306
239
  this.setlatestTotalTokens(0);
307
- this.sessionStartTime = new Date().toISOString();
308
240
  this.savedMessageCount = 0; // Reset saved message count
309
241
  }
310
242
 
@@ -314,9 +246,6 @@ export class MessageManager {
314
246
  this.setMessages([...sessionData.messages]);
315
247
  this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
316
248
 
317
- // Restore the original session start time
318
- this.sessionStartTime = sessionData.metadata.startedAt;
319
-
320
249
  // Extract user input history from session messages
321
250
  this.setUserInputHistory(extractUserInputHistory(sessionData.messages));
322
251
 
@@ -359,11 +288,13 @@ export class MessageManager {
359
288
  content?: string,
360
289
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
361
290
  usage?: Usage,
362
- metadata?: Record<string, unknown>,
291
+ additionalFields?: Record<string, unknown>,
363
292
  ): void {
364
- const metadataRecord = metadata
293
+ const additionalFieldsRecord = additionalFields
365
294
  ? Object.fromEntries(
366
- Object.entries(metadata).filter(([, value]) => value !== undefined),
295
+ Object.entries(additionalFields).filter(
296
+ ([, value]) => value !== undefined,
297
+ ),
367
298
  )
368
299
  : undefined;
369
300
 
@@ -372,7 +303,7 @@ export class MessageManager {
372
303
  content,
373
304
  toolCalls,
374
305
  usage,
375
- metadataRecord,
306
+ additionalFieldsRecord,
376
307
  );
377
308
  this.setMessages(newMessages);
378
309
  this.callbacks.onAssistantMessageAdded?.();
@@ -380,8 +311,10 @@ export class MessageManager {
380
311
  // Note: Subagent-specific callbacks are now handled by SubagentManager
381
312
  }
382
313
 
383
- public mergeAssistantMetadata(metadata: Record<string, unknown>): void {
384
- if (!metadata || Object.keys(metadata).length === 0) {
314
+ public mergeAssistantAdditionalFields(
315
+ additionalFields: Record<string, unknown>,
316
+ ): void {
317
+ if (!additionalFields || Object.keys(additionalFields).length === 0) {
385
318
  return;
386
319
  }
387
320
 
@@ -389,22 +322,22 @@ export class MessageManager {
389
322
  for (let i = newMessages.length - 1; i >= 0; i--) {
390
323
  const message = newMessages[i];
391
324
  if (message.role === "assistant") {
392
- const mergedMetadata = {
393
- ...(message.metadata || {}),
325
+ const mergedAdditionalFields = {
326
+ ...(message.additionalFields || {}),
394
327
  } as Record<string, unknown>;
395
328
 
396
- for (const [key, value] of Object.entries(metadata)) {
329
+ for (const [key, value] of Object.entries(additionalFields)) {
397
330
  if (value === undefined) {
398
331
  continue;
399
332
  }
400
- mergedMetadata[key] = value;
333
+ mergedAdditionalFields[key] = value;
401
334
  }
402
335
 
403
- if (Object.keys(mergedMetadata).length === 0) {
336
+ if (Object.keys(mergedAdditionalFields).length === 0) {
404
337
  return;
405
338
  }
406
339
 
407
- message.metadata = mergedMetadata;
340
+ message.additionalFields = mergedAdditionalFields;
408
341
  this.setMessages(newMessages);
409
342
  return;
410
343
  }
@@ -432,19 +365,6 @@ export class MessageManager {
432
365
  // Note: Subagent-specific callbacks are now handled by SubagentManager
433
366
  }
434
367
 
435
- public addDiffBlock(
436
- filePath: string,
437
- diffResult: Array<{ value: string; added?: boolean; removed?: boolean }>,
438
- ): void {
439
- const newMessages = addDiffBlockToMessage({
440
- messages: this.messages,
441
- path: filePath,
442
- diffResult,
443
- });
444
- this.setMessages(newMessages);
445
- this.callbacks.onDiffBlockAdded?.(filePath, JSON.stringify(diffResult));
446
- }
447
-
448
368
  public addErrorBlock(error: string): void {
449
369
  const newMessages = addErrorBlockToMessage({
450
370
  messages: this.messages,
@@ -640,7 +560,7 @@ export class MessageManager {
640
560
  };
641
561
  } else {
642
562
  // Add new text block if none exists
643
- lastMessage.blocks.unshift({
563
+ lastMessage.blocks.push({
644
564
  type: "text",
645
565
  content: newAccumulatedContent,
646
566
  });
@@ -654,6 +574,56 @@ export class MessageManager {
654
574
  this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
655
575
  }
656
576
 
577
+ /**
578
+ * Update the current assistant message reasoning during streaming
579
+ * This method updates the last assistant message's reasoning content without creating a new message
580
+ */
581
+ public updateCurrentMessageReasoning(newAccumulatedReasoning: string): void {
582
+ if (this.messages.length === 0) return;
583
+
584
+ const lastMessage = this.messages[this.messages.length - 1];
585
+ if (lastMessage.role !== "assistant") return;
586
+
587
+ // Get the current reasoning content to calculate the chunk
588
+ const reasoningBlockIndex = lastMessage.blocks.findIndex(
589
+ (block) => block.type === "reasoning",
590
+ );
591
+ const currentReasoning =
592
+ reasoningBlockIndex >= 0
593
+ ? (
594
+ lastMessage.blocks[reasoningBlockIndex] as {
595
+ type: "reasoning";
596
+ content: string;
597
+ }
598
+ ).content || ""
599
+ : "";
600
+
601
+ // Calculate the chunk (new content since last update)
602
+ const chunk = newAccumulatedReasoning.slice(currentReasoning.length);
603
+
604
+ if (reasoningBlockIndex >= 0) {
605
+ // Update existing reasoning block
606
+ lastMessage.blocks[reasoningBlockIndex] = {
607
+ type: "reasoning",
608
+ content: newAccumulatedReasoning,
609
+ };
610
+ } else {
611
+ // Add new reasoning block if none exists
612
+ lastMessage.blocks.push({
613
+ type: "reasoning",
614
+ content: newAccumulatedReasoning,
615
+ });
616
+ }
617
+
618
+ // Trigger callbacks with chunk and accumulated reasoning content
619
+ this.callbacks.onAssistantReasoningUpdated?.(
620
+ chunk,
621
+ newAccumulatedReasoning,
622
+ );
623
+
624
+ this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
625
+ }
626
+
657
627
  /**
658
628
  * Remove the last user message from the conversation
659
629
  * Used for hook error handling when the user prompt needs to be erased
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Permission Manager for handling tool permission checks
3
+ *
4
+ * This manager provides utilities for checking permissions before tool execution.
5
+ * It implements the permission logic for different modes (default vs bypass) and
6
+ * handles custom callback integration.
7
+ */
8
+
9
+ import type {
10
+ PermissionDecision,
11
+ ToolPermissionContext,
12
+ PermissionCallback,
13
+ PermissionMode,
14
+ } from "../types/permissions.js";
15
+ import { RESTRICTED_TOOLS } from "../types/permissions.js";
16
+ import type { Logger } from "../types/index.js";
17
+
18
+ export interface PermissionManagerOptions {
19
+ /** Logger for debugging permission decisions */
20
+ logger?: Logger;
21
+ /** Configured default permission mode from settings */
22
+ configuredDefaultMode?: PermissionMode;
23
+ /** Allowed rules from settings */
24
+ allowedRules?: string[];
25
+ }
26
+
27
+ export class PermissionManager {
28
+ private logger?: Logger;
29
+ private configuredDefaultMode?: PermissionMode;
30
+ private allowedRules: string[] = [];
31
+ private onConfiguredDefaultModeChange?: (mode: PermissionMode) => void;
32
+
33
+ constructor(options: PermissionManagerOptions = {}) {
34
+ this.logger = options.logger;
35
+ this.configuredDefaultMode = options.configuredDefaultMode;
36
+ this.allowedRules = options.allowedRules || [];
37
+ }
38
+
39
+ /**
40
+ * Set a callback to be notified when the effective permission mode changes due to configuration updates
41
+ */
42
+ public setOnConfiguredDefaultModeChange(
43
+ callback: (mode: PermissionMode) => void,
44
+ ): void {
45
+ this.onConfiguredDefaultModeChange = callback;
46
+ }
47
+
48
+ /**
49
+ * Update the configured default mode (e.g., when configuration reloads)
50
+ */
51
+ updateConfiguredDefaultMode(defaultMode?: PermissionMode): void {
52
+ const oldEffectiveMode = this.getCurrentEffectiveMode();
53
+
54
+ this.logger?.debug("Updating configured default permission mode", {
55
+ previous: this.configuredDefaultMode,
56
+ new: defaultMode,
57
+ });
58
+ this.configuredDefaultMode = defaultMode;
59
+
60
+ const newEffectiveMode = this.getCurrentEffectiveMode();
61
+ if (
62
+ oldEffectiveMode !== newEffectiveMode &&
63
+ this.onConfiguredDefaultModeChange
64
+ ) {
65
+ this.logger?.debug(
66
+ "Effective permission mode changed due to configuration update",
67
+ {
68
+ oldMode: oldEffectiveMode,
69
+ newMode: newEffectiveMode,
70
+ },
71
+ );
72
+ this.onConfiguredDefaultModeChange(newEffectiveMode);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get all currently allowed rules
78
+ */
79
+ public getAllowedRules(): string[] {
80
+ return [...this.allowedRules];
81
+ }
82
+
83
+ /**
84
+ * Update the allowed rules (e.g., when configuration reloads)
85
+ */
86
+ updateAllowedRules(rules: string[]): void {
87
+ this.logger?.debug("Updating allowed permission rules", {
88
+ count: rules.length,
89
+ });
90
+ this.allowedRules = rules;
91
+ }
92
+
93
+ /**
94
+ * Get the current effective permission mode for tool execution context
95
+ */
96
+ getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode {
97
+ return this.resolveEffectivePermissionMode(cliPermissionMode);
98
+ }
99
+
100
+ /**
101
+ * Resolve the effective permission mode based on CLI override and configured default
102
+ */
103
+ resolveEffectivePermissionMode(
104
+ cliPermissionMode?: PermissionMode,
105
+ ): PermissionMode {
106
+ // CLI override takes highest precedence
107
+ if (cliPermissionMode !== undefined) {
108
+ this.logger?.debug("Using CLI permission mode override", {
109
+ cliMode: cliPermissionMode,
110
+ configuredDefault: this.configuredDefaultMode,
111
+ });
112
+ return cliPermissionMode;
113
+ }
114
+
115
+ // Use configured default mode if available
116
+ if (this.configuredDefaultMode !== undefined) {
117
+ this.logger?.debug("Using configured default permission mode", {
118
+ configuredDefault: this.configuredDefaultMode,
119
+ });
120
+ return this.configuredDefaultMode;
121
+ }
122
+
123
+ // Fall back to system default
124
+ this.logger?.debug("Using system default permission mode");
125
+ return "default";
126
+ }
127
+
128
+ /**
129
+ * Check if a tool execution requires permission and handle the authorization flow
130
+ * Called by individual tools after validation/diff, before real operation
131
+ */
132
+ async checkPermission(
133
+ context: ToolPermissionContext,
134
+ ): Promise<PermissionDecision> {
135
+ this.logger?.debug("Checking permission for tool", {
136
+ toolName: context.toolName,
137
+ permissionMode: context.permissionMode,
138
+ hasCallback: !!context.canUseToolCallback,
139
+ });
140
+
141
+ // 1. If bypassPermissions mode, always allow
142
+ if (context.permissionMode === "bypassPermissions") {
143
+ this.logger?.debug("Permission bypassed for tool", {
144
+ toolName: context.toolName,
145
+ });
146
+ return { behavior: "allow" };
147
+ }
148
+
149
+ // 1.1 If acceptEdits mode, allow Edit, MultiEdit, Delete, Write
150
+ if (context.permissionMode === "acceptEdits") {
151
+ const autoAcceptedTools = ["Edit", "MultiEdit", "Delete", "Write"];
152
+ if (autoAcceptedTools.includes(context.toolName)) {
153
+ this.logger?.debug(
154
+ "Permission automatically accepted for tool in acceptEdits mode",
155
+ {
156
+ toolName: context.toolName,
157
+ },
158
+ );
159
+ return { behavior: "allow" };
160
+ }
161
+ }
162
+
163
+ // 1.2 Check if tool call matches any allowed rule
164
+ if (this.isAllowedByRule(context)) {
165
+ this.logger?.debug("Permission allowed by persistent rule", {
166
+ toolName: context.toolName,
167
+ });
168
+ return { behavior: "allow" };
169
+ }
170
+
171
+ // 2. If not a restricted tool, always allow
172
+ if (!this.isRestrictedTool(context.toolName)) {
173
+ this.logger?.debug("Tool is not restricted, allowing", {
174
+ toolName: context.toolName,
175
+ });
176
+ return { behavior: "allow" };
177
+ }
178
+
179
+ // 3. If custom callback provided, call it and return result
180
+ if (context.canUseToolCallback) {
181
+ try {
182
+ this.logger?.debug("Calling custom permission callback for tool", {
183
+ toolName: context.toolName,
184
+ });
185
+ const decision = await context.canUseToolCallback(context);
186
+ this.logger?.debug("Custom callback returned decision", {
187
+ toolName: context.toolName,
188
+ decision,
189
+ });
190
+ return decision;
191
+ } catch (error) {
192
+ const errorMessage =
193
+ error instanceof Error ? error.message : String(error);
194
+ this.logger?.error("Error in permission callback", {
195
+ toolName: context.toolName,
196
+ error: errorMessage,
197
+ });
198
+ return {
199
+ behavior: "deny",
200
+ message: "Error in permission callback",
201
+ };
202
+ }
203
+ }
204
+
205
+ // 4. For default mode on restricted tools without callback, integrate with CLI confirmation
206
+ // Note: CLI confirmation integration will be implemented in Phase 2
207
+ this.logger?.warn(
208
+ "No permission callback provided for restricted tool in default mode",
209
+ {
210
+ toolName: context.toolName,
211
+ },
212
+ );
213
+ return {
214
+ behavior: "deny",
215
+ message: `Tool '${context.toolName}' requires permission approval. No permission callback configured.`,
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Determine if a tool requires permission checks based on its name
221
+ */
222
+ isRestrictedTool(toolName: string): boolean {
223
+ const isRestricted = (RESTRICTED_TOOLS as readonly string[]).includes(
224
+ toolName,
225
+ );
226
+ this.logger?.debug("Checking if tool is restricted", {
227
+ toolName,
228
+ isRestricted,
229
+ });
230
+ return isRestricted;
231
+ }
232
+
233
+ /**
234
+ * Helper method to create a permission context for CLI integration
235
+ */
236
+ createContext(
237
+ toolName: string,
238
+ permissionMode: PermissionMode,
239
+ callback?: PermissionCallback,
240
+ toolInput?: Record<string, unknown>,
241
+ ): ToolPermissionContext {
242
+ const context: ToolPermissionContext = {
243
+ toolName,
244
+ permissionMode,
245
+ canUseToolCallback: callback,
246
+ toolInput,
247
+ };
248
+
249
+ this.logger?.debug("Created permission context", {
250
+ toolName,
251
+ permissionMode,
252
+ hasCallback: !!callback,
253
+ hasToolInput: !!toolInput,
254
+ });
255
+
256
+ return context;
257
+ }
258
+
259
+ /**
260
+ * Check if a tool call is allowed by persistent rules
261
+ */
262
+ private isAllowedByRule(context: ToolPermissionContext): boolean {
263
+ if (context.toolName === "Bash" && context.toolInput?.command) {
264
+ const action = `Bash(${context.toolInput.command})`;
265
+ return this.allowedRules.some((rule) => {
266
+ if (rule.endsWith(":*)")) {
267
+ const prefix = rule.slice(0, -3);
268
+ return action.startsWith(prefix);
269
+ }
270
+ return action === rule;
271
+ });
272
+ }
273
+ // Add other tools if needed in the future
274
+ return false;
275
+ }
276
+ }
@@ -20,6 +20,7 @@ export class SkillManager {
20
20
  private personalSkillsPath: string;
21
21
  private scanTimeout: number;
22
22
  private logger?: Logger;
23
+ private workdir: string;
23
24
 
24
25
  private skillMetadata = new Map<string, SkillMetadata>();
25
26
  private skillContent = new Map<string, Skill>();
@@ -30,6 +31,7 @@ export class SkillManager {
30
31
  options.personalSkillsPath || join(homedir(), ".wave", "skills");
31
32
  this.scanTimeout = options.scanTimeout || 5000;
32
33
  this.logger = options.logger;
34
+ this.workdir = options.workdir || process.cwd();
33
35
  }
34
36
 
35
37
  /**
@@ -123,7 +125,7 @@ export class SkillManager {
123
125
  );
124
126
 
125
127
  const projectCollection = await this.discoverSkillCollection(
126
- process.cwd(),
128
+ this.workdir,
127
129
  "project",
128
130
  );
129
131
 
@@ -218,7 +218,7 @@ export class SlashCommandManager {
218
218
  private async executeCustomCommandInMainAgent(
219
219
  commandName: string,
220
220
  content: string,
221
- config?: { model?: string; allowedTools?: string[] },
221
+ config?: { model?: string },
222
222
  args?: string,
223
223
  ): Promise<void> {
224
224
  try {
@@ -272,7 +272,6 @@ export class SlashCommandManager {
272
272
  // Execute the AI conversation with custom configuration
273
273
  await this.aiManager.sendAIMessage({
274
274
  model: config?.model,
275
- allowedTools: config?.allowedTools,
276
275
  });
277
276
  } catch (error) {
278
277
  this.logger?.error(