wave-agent-sdk 0.0.8 → 0.0.11

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 +351 -137
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -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 +75 -0
  25. package/dist/managers/permissionManager.d.ts.map +1 -0
  26. package/dist/managers/permissionManager.js +368 -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 +109 -11
  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 +39 -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/bashParser.d.ts +24 -0
  120. package/dist/utils/bashParser.d.ts.map +1 -0
  121. package/dist/utils/bashParser.js +413 -0
  122. package/dist/utils/builtinSubagents.d.ts +7 -0
  123. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  124. package/dist/utils/builtinSubagents.js +65 -0
  125. package/dist/utils/cacheControlUtils.d.ts +8 -33
  126. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  127. package/dist/utils/cacheControlUtils.js +83 -126
  128. package/dist/utils/constants.d.ts +0 -12
  129. package/dist/utils/constants.d.ts.map +1 -1
  130. package/dist/utils/constants.js +1 -13
  131. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  132. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  133. package/dist/utils/convertMessagesForAPI.js +33 -14
  134. package/dist/utils/fileSearch.d.ts +14 -0
  135. package/dist/utils/fileSearch.d.ts.map +1 -0
  136. package/dist/utils/fileSearch.js +88 -0
  137. package/dist/utils/fileUtils.d.ts +14 -2
  138. package/dist/utils/fileUtils.d.ts.map +1 -1
  139. package/dist/utils/fileUtils.js +101 -17
  140. package/dist/utils/globalLogger.d.ts +0 -14
  141. package/dist/utils/globalLogger.d.ts.map +1 -1
  142. package/dist/utils/globalLogger.js +0 -16
  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/pathSafety.d.ts +10 -0
  152. package/dist/utils/pathSafety.d.ts.map +1 -0
  153. package/dist/utils/pathSafety.js +23 -0
  154. package/dist/utils/subagentParser.d.ts +2 -2
  155. package/dist/utils/subagentParser.d.ts.map +1 -1
  156. package/dist/utils/subagentParser.js +10 -7
  157. package/package.json +9 -9
  158. package/src/agent.ts +475 -216
  159. package/src/index.ts +3 -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 +480 -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 +126 -13
  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 +52 -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/bashParser.ts +444 -0
  205. package/src/utils/builtinSubagents.ts +71 -0
  206. package/src/utils/cacheControlUtils.ts +106 -171
  207. package/src/utils/constants.ts +1 -16
  208. package/src/utils/convertMessagesForAPI.ts +38 -14
  209. package/src/utils/fileSearch.ts +107 -0
  210. package/src/utils/fileUtils.ts +114 -19
  211. package/src/utils/globalLogger.ts +0 -17
  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/pathSafety.ts +26 -0
  216. package/src/utils/subagentParser.ts +11 -8
  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
@@ -1,6 +1,6 @@
1
- import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addDiffBlockToMessage, addUserMessageToMessages, extractUserInputHistory, addMemoryBlockToMessage, addCommandOutputMessage, updateCommandOutputInMessage, completeCommandInMessage, addSubagentBlockToMessage, updateSubagentBlockInMessage, removeLastUserMessage, } from "../utils/messageOperations.js";
1
+ import { addAssistantMessageToMessages, updateToolBlockInMessage, addErrorBlockToMessage, addUserMessageToMessages, extractUserInputHistory, addMemoryBlockToMessage, addCommandOutputMessage, updateCommandOutputInMessage, completeCommandInMessage, addSubagentBlockToMessage, updateSubagentBlockInMessage, removeLastUserMessage, } from "../utils/messageOperations.js";
2
2
  import { join } from "path";
3
- import { cleanupExpiredSessionsFromJsonl, getLatestSessionFromJsonl, loadSessionFromJsonl, appendMessages, createSession, generateSessionId, SESSION_DIR, } from "../services/session.js";
3
+ import { appendMessages, createSession, generateSessionId, SESSION_DIR, } from "../services/session.js";
4
4
  import { pathEncoder } from "../utils/pathEncoder.js";
5
5
  export class MessageManager {
6
6
  constructor(options) {
@@ -8,14 +8,12 @@ export class MessageManager {
8
8
  this.messages = [];
9
9
  this.latestTotalTokens = 0;
10
10
  this.userInputHistory = [];
11
- this.sessionStartTime = new Date().toISOString();
12
11
  this.workdir = options.workdir;
13
12
  this.encodedWorkdir = pathEncoder.encodeSync(this.workdir); // Cache encoded workdir
14
13
  this.callbacks = options.callbacks;
15
14
  this.logger = options.logger;
16
15
  this.savedMessageCount = 0; // Initialize saved message count tracker
17
16
  this.sessionType = options.sessionType || "main";
18
- this.parentSessionId = options.parentSessionId;
19
17
  this.subagentType = options.subagentType;
20
18
  // Compute and cache the transcript path
21
19
  this.transcriptPath = this.computeTranscriptPath();
@@ -33,9 +31,6 @@ export class MessageManager {
33
31
  getUserInputHistory() {
34
32
  return [...this.userInputHistory];
35
33
  }
36
- getSessionStartTime() {
37
- return this.sessionStartTime;
38
- }
39
34
  getWorkdir() {
40
35
  return this.workdir;
41
36
  }
@@ -71,7 +66,7 @@ export class MessageManager {
71
66
  */
72
67
  async createSessionIfNeeded() {
73
68
  try {
74
- await createSession(this.sessionId, this.workdir, this.sessionType, this.parentSessionId, this.subagentType);
69
+ await createSession(this.sessionId, this.workdir, this.sessionType);
75
70
  }
76
71
  catch (error) {
77
72
  this.logger?.error("Failed to create session:", error);
@@ -99,7 +94,7 @@ export class MessageManager {
99
94
  }
100
95
  // Use JSONL format for new sessions
101
96
  await appendMessages(this.sessionId, unsavedMessages, // Only append new messages
102
- this.workdir);
97
+ this.workdir, this.sessionType);
103
98
  // Update the saved message count
104
99
  this.savedMessageCount = this.messages.length;
105
100
  }
@@ -107,46 +102,6 @@ export class MessageManager {
107
102
  this.logger?.error("Failed to save session:", error);
108
103
  }
109
104
  }
110
- /**
111
- * Handle session restoration logic
112
- */
113
- async handleSessionRestoration(restoreSessionId, continueLastSession) {
114
- // Clean up expired sessions first
115
- cleanupExpiredSessionsFromJsonl(this.workdir).catch((error) => {
116
- this.logger?.warn("Failed to cleanup expired sessions:", error);
117
- });
118
- if (!restoreSessionId && !continueLastSession) {
119
- return;
120
- }
121
- try {
122
- let sessionToRestore = null;
123
- if (restoreSessionId) {
124
- // Use only JSONL format - no legacy support
125
- sessionToRestore = await loadSessionFromJsonl(restoreSessionId, this.workdir);
126
- if (!sessionToRestore) {
127
- console.error(`Session not found: ${restoreSessionId}`);
128
- process.exit(1);
129
- }
130
- }
131
- else if (continueLastSession) {
132
- // Use only JSONL format - no legacy support
133
- sessionToRestore = await getLatestSessionFromJsonl(this.workdir);
134
- if (!sessionToRestore) {
135
- console.error(`No previous session found for workdir: ${this.workdir}`);
136
- process.exit(1);
137
- }
138
- }
139
- if (sessionToRestore) {
140
- console.log(`Restoring session: ${sessionToRestore.id}`);
141
- // Initialize from session data
142
- this.initializeFromSession(sessionToRestore);
143
- }
144
- }
145
- catch (error) {
146
- console.error("Failed to restore session:", error);
147
- process.exit(1);
148
- }
149
- }
150
105
  setlatestTotalTokens(latestTotalTokens) {
151
106
  if (this.latestTotalTokens !== latestTotalTokens) {
152
107
  this.latestTotalTokens = latestTotalTokens;
@@ -165,7 +120,6 @@ export class MessageManager {
165
120
  this.setUserInputHistory([]);
166
121
  this.setSessionId(generateSessionId());
167
122
  this.setlatestTotalTokens(0);
168
- this.sessionStartTime = new Date().toISOString();
169
123
  this.savedMessageCount = 0; // Reset saved message count
170
124
  }
171
125
  // Initialize state from session data
@@ -173,8 +127,6 @@ export class MessageManager {
173
127
  this.setSessionId(sessionData.id);
174
128
  this.setMessages([...sessionData.messages]);
175
129
  this.setlatestTotalTokens(sessionData.metadata.latestTotalTokens);
176
- // Restore the original session start time
177
- this.sessionStartTime = sessionData.metadata.startedAt;
178
130
  // Extract user input history from session messages
179
131
  this.setUserInputHistory(extractUserInputHistory(sessionData.messages));
180
132
  // Set saved message count to the number of loaded messages since they're already saved
@@ -205,36 +157,36 @@ export class MessageManager {
205
157
  this.callbacks.onUserMessageAdded?.(params);
206
158
  // Note: Subagent-specific callbacks are now handled by SubagentManager
207
159
  }
208
- addAssistantMessage(content, toolCalls, usage, metadata) {
209
- const metadataRecord = metadata
210
- ? Object.fromEntries(Object.entries(metadata).filter(([, value]) => value !== undefined))
160
+ addAssistantMessage(content, toolCalls, usage, additionalFields) {
161
+ const additionalFieldsRecord = additionalFields
162
+ ? Object.fromEntries(Object.entries(additionalFields).filter(([, value]) => value !== undefined))
211
163
  : undefined;
212
- const newMessages = addAssistantMessageToMessages(this.messages, content, toolCalls, usage, metadataRecord);
164
+ const newMessages = addAssistantMessageToMessages(this.messages, content, toolCalls, usage, additionalFieldsRecord);
213
165
  this.setMessages(newMessages);
214
166
  this.callbacks.onAssistantMessageAdded?.();
215
167
  // Note: Subagent-specific callbacks are now handled by SubagentManager
216
168
  }
217
- mergeAssistantMetadata(metadata) {
218
- if (!metadata || Object.keys(metadata).length === 0) {
169
+ mergeAssistantAdditionalFields(additionalFields) {
170
+ if (!additionalFields || Object.keys(additionalFields).length === 0) {
219
171
  return;
220
172
  }
221
173
  const newMessages = [...this.messages];
222
174
  for (let i = newMessages.length - 1; i >= 0; i--) {
223
175
  const message = newMessages[i];
224
176
  if (message.role === "assistant") {
225
- const mergedMetadata = {
226
- ...(message.metadata || {}),
177
+ const mergedAdditionalFields = {
178
+ ...(message.additionalFields || {}),
227
179
  };
228
- for (const [key, value] of Object.entries(metadata)) {
180
+ for (const [key, value] of Object.entries(additionalFields)) {
229
181
  if (value === undefined) {
230
182
  continue;
231
183
  }
232
- mergedMetadata[key] = value;
184
+ mergedAdditionalFields[key] = value;
233
185
  }
234
- if (Object.keys(mergedMetadata).length === 0) {
186
+ if (Object.keys(mergedAdditionalFields).length === 0) {
235
187
  return;
236
188
  }
237
- message.metadata = mergedMetadata;
189
+ message.additionalFields = mergedAdditionalFields;
238
190
  this.setMessages(newMessages);
239
191
  return;
240
192
  }
@@ -259,15 +211,6 @@ export class MessageManager {
259
211
  this.callbacks.onToolBlockUpdated?.(params);
260
212
  // Note: Subagent-specific callbacks are now handled by SubagentManager
261
213
  }
262
- addDiffBlock(filePath, diffResult) {
263
- const newMessages = addDiffBlockToMessage({
264
- messages: this.messages,
265
- path: filePath,
266
- diffResult,
267
- });
268
- this.setMessages(newMessages);
269
- this.callbacks.onDiffBlockAdded?.(filePath, JSON.stringify(diffResult));
270
- }
271
214
  addErrorBlock(error) {
272
215
  const newMessages = addErrorBlockToMessage({
273
216
  messages: this.messages,
@@ -408,7 +351,7 @@ export class MessageManager {
408
351
  }
409
352
  else {
410
353
  // Add new text block if none exists
411
- lastMessage.blocks.unshift({
354
+ lastMessage.blocks.push({
412
355
  type: "text",
413
356
  content: newAccumulatedContent,
414
357
  });
@@ -418,6 +361,41 @@ export class MessageManager {
418
361
  // Note: Subagent-specific callbacks are now handled by SubagentManager
419
362
  this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
420
363
  }
364
+ /**
365
+ * Update the current assistant message reasoning during streaming
366
+ * This method updates the last assistant message's reasoning content without creating a new message
367
+ */
368
+ updateCurrentMessageReasoning(newAccumulatedReasoning) {
369
+ if (this.messages.length === 0)
370
+ return;
371
+ const lastMessage = this.messages[this.messages.length - 1];
372
+ if (lastMessage.role !== "assistant")
373
+ return;
374
+ // Get the current reasoning content to calculate the chunk
375
+ const reasoningBlockIndex = lastMessage.blocks.findIndex((block) => block.type === "reasoning");
376
+ const currentReasoning = reasoningBlockIndex >= 0
377
+ ? lastMessage.blocks[reasoningBlockIndex].content || ""
378
+ : "";
379
+ // Calculate the chunk (new content since last update)
380
+ const chunk = newAccumulatedReasoning.slice(currentReasoning.length);
381
+ if (reasoningBlockIndex >= 0) {
382
+ // Update existing reasoning block
383
+ lastMessage.blocks[reasoningBlockIndex] = {
384
+ type: "reasoning",
385
+ content: newAccumulatedReasoning,
386
+ };
387
+ }
388
+ else {
389
+ // Add new reasoning block if none exists
390
+ lastMessage.blocks.push({
391
+ type: "reasoning",
392
+ content: newAccumulatedReasoning,
393
+ });
394
+ }
395
+ // Trigger callbacks with chunk and accumulated reasoning content
396
+ this.callbacks.onAssistantReasoningUpdated?.(chunk, newAccumulatedReasoning);
397
+ this.callbacks.onMessagesChange?.([...this.messages]); // Still need to notify of changes
398
+ }
421
399
  /**
422
400
  * Remove the last user message from the conversation
423
401
  * Used for hook error handling when the user prompt needs to be erased
@@ -0,0 +1,75 @@
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
+ import type { PermissionDecision, ToolPermissionContext, PermissionCallback, PermissionMode } from "../types/permissions.js";
9
+ import type { Logger } from "../types/index.js";
10
+ export interface PermissionManagerOptions {
11
+ /** Logger for debugging permission decisions */
12
+ logger?: Logger;
13
+ /** Configured default permission mode from settings */
14
+ configuredDefaultMode?: PermissionMode;
15
+ /** Allowed rules from settings */
16
+ allowedRules?: string[];
17
+ }
18
+ export declare class PermissionManager {
19
+ private logger?;
20
+ private configuredDefaultMode?;
21
+ private allowedRules;
22
+ private onConfiguredDefaultModeChange?;
23
+ constructor(options?: PermissionManagerOptions);
24
+ /**
25
+ * Set a callback to be notified when the effective permission mode changes due to configuration updates
26
+ */
27
+ setOnConfiguredDefaultModeChange(callback: (mode: PermissionMode) => void): void;
28
+ /**
29
+ * Update the configured default mode (e.g., when configuration reloads)
30
+ */
31
+ updateConfiguredDefaultMode(defaultMode?: PermissionMode): void;
32
+ /**
33
+ * Get all currently allowed rules
34
+ */
35
+ getAllowedRules(): string[];
36
+ /**
37
+ * Update the allowed rules (e.g., when configuration reloads)
38
+ */
39
+ updateAllowedRules(rules: string[]): void;
40
+ /**
41
+ * Get the current effective permission mode for tool execution context
42
+ */
43
+ getCurrentEffectiveMode(cliPermissionMode?: PermissionMode): PermissionMode;
44
+ /**
45
+ * Resolve the effective permission mode based on CLI override and configured default
46
+ */
47
+ resolveEffectivePermissionMode(cliPermissionMode?: PermissionMode): PermissionMode;
48
+ /**
49
+ * Check if a tool execution requires permission and handle the authorization flow
50
+ * Called by individual tools after validation/diff, before real operation
51
+ */
52
+ checkPermission(context: ToolPermissionContext): Promise<PermissionDecision>;
53
+ /**
54
+ * Determine if a tool requires permission checks based on its name
55
+ */
56
+ isRestrictedTool(toolName: string): boolean;
57
+ /**
58
+ * Helper method to create a permission context for CLI integration
59
+ */
60
+ createContext(toolName: string, permissionMode: PermissionMode, callback?: PermissionCallback, toolInput?: Record<string, unknown>): ToolPermissionContext;
61
+ /**
62
+ * Check if a tool call is allowed by persistent rules
63
+ */
64
+ private isAllowedByRule;
65
+ /**
66
+ * Expand a bash command into individual permission rules, filtering out safe commands.
67
+ * Used when saving permissions to the allow list.
68
+ *
69
+ * @param command The full bash command string
70
+ * @param workdir The working directory for path safety checks
71
+ * @returns Array of permission rules in "Bash(cmd)" format
72
+ */
73
+ expandBashRule(command: string, workdir: string): string[];
74
+ }
75
+ //# sourceMappingURL=permissionManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionManager.d.ts","sourceRoot":"","sources":["../../src/managers/permissionManager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EACV,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAYhD,MAAM,WAAW,wBAAwB;IACvC,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,qBAAqB,CAAC,EAAE,cAAc,CAAC;IACvC,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,qBAAqB,CAAC,CAAiB;IAC/C,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,6BAA6B,CAAC,CAAiC;gBAE3D,OAAO,GAAE,wBAA6B;IAMlD;;OAEG;IACI,gCAAgC,CACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GACvC,IAAI;IAIP;;OAEG;IACH,2BAA2B,CAAC,WAAW,CAAC,EAAE,cAAc,GAAG,IAAI;IAyB/D;;OAEG;IACI,eAAe,IAAI,MAAM,EAAE;IAIlC;;OAEG;IACH,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAOzC;;OAEG;IACH,uBAAuB,CAAC,iBAAiB,CAAC,EAAE,cAAc,GAAG,cAAc;IAI3E;;OAEG;IACH,8BAA8B,CAC5B,iBAAiB,CAAC,EAAE,cAAc,GACjC,cAAc;IAuBjB;;;OAGG;IACG,eAAe,CACnB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;IAqF9B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAW3C;;OAEG;IACH,aAAa,CACX,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,cAAc,EAC9B,QAAQ,CAAC,EAAE,kBAAkB,EAC7B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,qBAAqB;IAuExB;;OAEG;IACH,OAAO,CAAC,eAAe;IAiEvB;;;;;;;OAOG;IACI,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;CAiFlE"}
@@ -0,0 +1,368 @@
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
+ import path from "node:path";
9
+ import { RESTRICTED_TOOLS } from "../types/permissions.js";
10
+ import { splitBashCommand, stripEnvVars, stripRedirections, getSmartPrefix, DANGEROUS_COMMANDS, } from "../utils/bashParser.js";
11
+ import { isPathInside } from "../utils/pathSafety.js";
12
+ const SAFE_COMMANDS = ["cd", "ls", "pwd"];
13
+ export class PermissionManager {
14
+ constructor(options = {}) {
15
+ this.allowedRules = [];
16
+ this.logger = options.logger;
17
+ this.configuredDefaultMode = options.configuredDefaultMode;
18
+ this.allowedRules = options.allowedRules || [];
19
+ }
20
+ /**
21
+ * Set a callback to be notified when the effective permission mode changes due to configuration updates
22
+ */
23
+ setOnConfiguredDefaultModeChange(callback) {
24
+ this.onConfiguredDefaultModeChange = callback;
25
+ }
26
+ /**
27
+ * Update the configured default mode (e.g., when configuration reloads)
28
+ */
29
+ updateConfiguredDefaultMode(defaultMode) {
30
+ const oldEffectiveMode = this.getCurrentEffectiveMode();
31
+ this.logger?.debug("Updating configured default permission mode", {
32
+ previous: this.configuredDefaultMode,
33
+ new: defaultMode,
34
+ });
35
+ this.configuredDefaultMode = defaultMode;
36
+ const newEffectiveMode = this.getCurrentEffectiveMode();
37
+ if (oldEffectiveMode !== newEffectiveMode &&
38
+ this.onConfiguredDefaultModeChange) {
39
+ this.logger?.debug("Effective permission mode changed due to configuration update", {
40
+ oldMode: oldEffectiveMode,
41
+ newMode: newEffectiveMode,
42
+ });
43
+ this.onConfiguredDefaultModeChange(newEffectiveMode);
44
+ }
45
+ }
46
+ /**
47
+ * Get all currently allowed rules
48
+ */
49
+ getAllowedRules() {
50
+ return [...this.allowedRules];
51
+ }
52
+ /**
53
+ * Update the allowed rules (e.g., when configuration reloads)
54
+ */
55
+ updateAllowedRules(rules) {
56
+ this.logger?.debug("Updating allowed permission rules", {
57
+ count: rules.length,
58
+ });
59
+ this.allowedRules = rules;
60
+ }
61
+ /**
62
+ * Get the current effective permission mode for tool execution context
63
+ */
64
+ getCurrentEffectiveMode(cliPermissionMode) {
65
+ return this.resolveEffectivePermissionMode(cliPermissionMode);
66
+ }
67
+ /**
68
+ * Resolve the effective permission mode based on CLI override and configured default
69
+ */
70
+ resolveEffectivePermissionMode(cliPermissionMode) {
71
+ // CLI override takes highest precedence
72
+ if (cliPermissionMode !== undefined) {
73
+ this.logger?.debug("Using CLI permission mode override", {
74
+ cliMode: cliPermissionMode,
75
+ configuredDefault: this.configuredDefaultMode,
76
+ });
77
+ return cliPermissionMode;
78
+ }
79
+ // Use configured default mode if available
80
+ if (this.configuredDefaultMode !== undefined) {
81
+ this.logger?.debug("Using configured default permission mode", {
82
+ configuredDefault: this.configuredDefaultMode,
83
+ });
84
+ return this.configuredDefaultMode;
85
+ }
86
+ // Fall back to system default
87
+ this.logger?.debug("Using system default permission mode");
88
+ return "default";
89
+ }
90
+ /**
91
+ * Check if a tool execution requires permission and handle the authorization flow
92
+ * Called by individual tools after validation/diff, before real operation
93
+ */
94
+ async checkPermission(context) {
95
+ this.logger?.debug("Checking permission for tool", {
96
+ toolName: context.toolName,
97
+ permissionMode: context.permissionMode,
98
+ hasCallback: !!context.canUseToolCallback,
99
+ });
100
+ // 1. If bypassPermissions mode, always allow
101
+ if (context.permissionMode === "bypassPermissions") {
102
+ this.logger?.debug("Permission bypassed for tool", {
103
+ toolName: context.toolName,
104
+ });
105
+ return { behavior: "allow" };
106
+ }
107
+ // 1.1 If acceptEdits mode, allow Edit, MultiEdit, Delete, Write
108
+ if (context.permissionMode === "acceptEdits") {
109
+ const autoAcceptedTools = ["Edit", "MultiEdit", "Delete", "Write"];
110
+ if (autoAcceptedTools.includes(context.toolName)) {
111
+ this.logger?.debug("Permission automatically accepted for tool in acceptEdits mode", {
112
+ toolName: context.toolName,
113
+ });
114
+ return { behavior: "allow" };
115
+ }
116
+ }
117
+ // 1.2 Check if tool call matches any allowed rule
118
+ if (this.isAllowedByRule(context)) {
119
+ this.logger?.debug("Permission allowed by persistent rule", {
120
+ toolName: context.toolName,
121
+ });
122
+ return { behavior: "allow" };
123
+ }
124
+ // 2. If not a restricted tool, always allow
125
+ if (!this.isRestrictedTool(context.toolName)) {
126
+ this.logger?.debug("Tool is not restricted, allowing", {
127
+ toolName: context.toolName,
128
+ });
129
+ return { behavior: "allow" };
130
+ }
131
+ // 3. If custom callback provided, call it and return result
132
+ if (context.canUseToolCallback) {
133
+ try {
134
+ this.logger?.debug("Calling custom permission callback for tool", {
135
+ toolName: context.toolName,
136
+ });
137
+ const decision = await context.canUseToolCallback(context);
138
+ this.logger?.debug("Custom callback returned decision", {
139
+ toolName: context.toolName,
140
+ decision,
141
+ });
142
+ return decision;
143
+ }
144
+ catch (error) {
145
+ const errorMessage = error instanceof Error ? error.message : String(error);
146
+ this.logger?.error("Error in permission callback", {
147
+ toolName: context.toolName,
148
+ error: errorMessage,
149
+ });
150
+ return {
151
+ behavior: "deny",
152
+ message: "Error in permission callback",
153
+ };
154
+ }
155
+ }
156
+ // 4. For default mode on restricted tools without callback, integrate with CLI confirmation
157
+ // Note: CLI confirmation integration will be implemented in Phase 2
158
+ this.logger?.warn("No permission callback provided for restricted tool in default mode", {
159
+ toolName: context.toolName,
160
+ });
161
+ return {
162
+ behavior: "deny",
163
+ message: `Tool '${context.toolName}' requires permission approval. No permission callback configured.`,
164
+ };
165
+ }
166
+ /**
167
+ * Determine if a tool requires permission checks based on its name
168
+ */
169
+ isRestrictedTool(toolName) {
170
+ const isRestricted = RESTRICTED_TOOLS.includes(toolName);
171
+ this.logger?.debug("Checking if tool is restricted", {
172
+ toolName,
173
+ isRestricted,
174
+ });
175
+ return isRestricted;
176
+ }
177
+ /**
178
+ * Helper method to create a permission context for CLI integration
179
+ */
180
+ createContext(toolName, permissionMode, callback, toolInput) {
181
+ let suggestedPrefix;
182
+ if (toolName === "Bash" && toolInput?.command) {
183
+ const command = String(toolInput.command);
184
+ const parts = splitBashCommand(command);
185
+ // Only suggest prefix for single commands to avoid confusion with complex chains
186
+ if (parts.length === 1) {
187
+ const processedPart = stripRedirections(stripEnvVars(parts[0]));
188
+ suggestedPrefix = getSmartPrefix(processedPart) ?? undefined;
189
+ }
190
+ }
191
+ const context = {
192
+ toolName,
193
+ permissionMode,
194
+ canUseToolCallback: callback,
195
+ toolInput,
196
+ suggestedPrefix,
197
+ };
198
+ // Set hidePersistentOption for dangerous or out-of-bounds bash commands
199
+ if (toolName === "Bash" && toolInput?.command) {
200
+ const command = String(toolInput.command);
201
+ const workdir = toolInput.workdir;
202
+ const parts = splitBashCommand(command);
203
+ const isDangerous = parts.some((part) => {
204
+ const processedPart = stripRedirections(stripEnvVars(part));
205
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
206
+ if (commandMatch) {
207
+ const cmd = commandMatch[1];
208
+ const args = commandMatch[2]?.trim() || "";
209
+ // Check blacklist
210
+ if (DANGEROUS_COMMANDS.includes(cmd)) {
211
+ return true;
212
+ }
213
+ // Check out-of-bounds for cd and ls
214
+ if (workdir && (cmd === "cd" || cmd === "ls")) {
215
+ const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
216
+ return pathArgs.some((pathArg) => {
217
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
218
+ const absolutePath = path.resolve(workdir, cleanPath);
219
+ return !isPathInside(absolutePath, workdir);
220
+ });
221
+ }
222
+ }
223
+ return false;
224
+ });
225
+ if (isDangerous) {
226
+ context.hidePersistentOption = true;
227
+ }
228
+ }
229
+ this.logger?.debug("Created permission context", {
230
+ toolName,
231
+ permissionMode,
232
+ hasCallback: !!callback,
233
+ hasToolInput: !!toolInput,
234
+ suggestedPrefix,
235
+ });
236
+ return context;
237
+ }
238
+ /**
239
+ * Check if a tool call is allowed by persistent rules
240
+ */
241
+ isAllowedByRule(context) {
242
+ if (context.toolName === "Bash" && context.toolInput?.command) {
243
+ const command = String(context.toolInput.command);
244
+ const parts = splitBashCommand(command);
245
+ if (parts.length === 0)
246
+ return false;
247
+ const workdir = context.toolInput?.workdir;
248
+ return parts.every((part) => {
249
+ const processedPart = stripRedirections(stripEnvVars(part));
250
+ // Check for safe commands
251
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
252
+ if (commandMatch) {
253
+ const cmd = commandMatch[1];
254
+ const args = commandMatch[2]?.trim() || "";
255
+ if (SAFE_COMMANDS.includes(cmd)) {
256
+ if (workdir) {
257
+ if (cmd === "pwd") {
258
+ return true;
259
+ }
260
+ // For cd and ls, check paths
261
+ const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
262
+ if (pathArgs.length === 0) {
263
+ // cd or ls without arguments operates on current dir (workdir)
264
+ return true;
265
+ }
266
+ const allPathsSafe = pathArgs.every((pathArg) => {
267
+ // Remove quotes if present
268
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
269
+ const absolutePath = path.resolve(workdir, cleanPath);
270
+ return isPathInside(absolutePath, workdir);
271
+ });
272
+ if (allPathsSafe) {
273
+ return true;
274
+ }
275
+ }
276
+ }
277
+ }
278
+ const action = `${context.toolName}(${processedPart})`;
279
+ const allowedByRule = this.allowedRules.some((rule) => {
280
+ if (rule.endsWith(":*)")) {
281
+ const prefix = rule.slice(0, -3);
282
+ return action.startsWith(prefix);
283
+ }
284
+ return action === rule;
285
+ });
286
+ if (allowedByRule)
287
+ return true;
288
+ return !this.isRestrictedTool(context.toolName);
289
+ });
290
+ }
291
+ // Add other tools if needed in the future
292
+ return false;
293
+ }
294
+ /**
295
+ * Expand a bash command into individual permission rules, filtering out safe commands.
296
+ * Used when saving permissions to the allow list.
297
+ *
298
+ * @param command The full bash command string
299
+ * @param workdir The working directory for path safety checks
300
+ * @returns Array of permission rules in "Bash(cmd)" format
301
+ */
302
+ expandBashRule(command, workdir) {
303
+ const parts = splitBashCommand(command);
304
+ const rules = [];
305
+ for (const part of parts) {
306
+ const processedPart = stripRedirections(stripEnvVars(part));
307
+ // Check for safe commands
308
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
309
+ let isSafe = false;
310
+ if (commandMatch) {
311
+ const cmd = commandMatch[1];
312
+ const args = commandMatch[2]?.trim() || "";
313
+ if (SAFE_COMMANDS.includes(cmd)) {
314
+ if (cmd === "pwd") {
315
+ isSafe = true;
316
+ }
317
+ else {
318
+ // For cd and ls, check paths
319
+ const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
320
+ if (pathArgs.length === 0) {
321
+ isSafe = true;
322
+ }
323
+ else {
324
+ const allPathsSafe = pathArgs.every((pathArg) => {
325
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
326
+ const absolutePath = path.resolve(workdir, cleanPath);
327
+ return isPathInside(absolutePath, workdir);
328
+ });
329
+ if (allPathsSafe) {
330
+ isSafe = true;
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ if (!isSafe) {
337
+ // Check if command is dangerous or out-of-bounds
338
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
339
+ if (commandMatch) {
340
+ const cmd = commandMatch[1];
341
+ const args = commandMatch[2]?.trim() || "";
342
+ if (DANGEROUS_COMMANDS.includes(cmd)) {
343
+ continue;
344
+ }
345
+ if (cmd === "cd" || cmd === "ls") {
346
+ const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
347
+ const isOutOfBounds = pathArgs.some((pathArg) => {
348
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
349
+ const absolutePath = path.resolve(workdir, cleanPath);
350
+ return !isPathInside(absolutePath, workdir);
351
+ });
352
+ if (isOutOfBounds) {
353
+ continue;
354
+ }
355
+ }
356
+ }
357
+ const smartPrefix = getSmartPrefix(processedPart);
358
+ if (smartPrefix) {
359
+ rules.push(`Bash(${smartPrefix}:*)`);
360
+ }
361
+ else {
362
+ rules.push(`Bash(${processedPart})`);
363
+ }
364
+ }
365
+ }
366
+ return rules;
367
+ }
368
+ }