wave-agent-sdk 0.0.7 → 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 (240) hide show
  1. package/dist/agent.d.ts +105 -24
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +438 -53
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -0
  7. package/dist/managers/aiManager.d.ts +18 -7
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +254 -142
  10. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  11. package/dist/managers/backgroundBashManager.js +11 -9
  12. package/dist/managers/hookManager.d.ts +6 -6
  13. package/dist/managers/hookManager.d.ts.map +1 -1
  14. package/dist/managers/hookManager.js +81 -39
  15. package/dist/managers/liveConfigManager.d.ts +95 -0
  16. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  17. package/dist/managers/liveConfigManager.js +442 -0
  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 +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +184 -73
  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 +4 -2
  32. package/dist/managers/subagentManager.d.ts +42 -6
  33. package/dist/managers/subagentManager.d.ts.map +1 -1
  34. package/dist/managers/subagentManager.js +213 -62
  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 +15 -5
  39. package/dist/services/aiService.d.ts.map +1 -1
  40. package/dist/services/aiService.js +446 -77
  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 +69 -0
  45. package/dist/services/fileWatcher.d.ts.map +1 -0
  46. package/dist/services/fileWatcher.js +212 -0
  47. package/dist/services/hook.d.ts +5 -40
  48. package/dist/services/hook.d.ts.map +1 -1
  49. package/dist/services/hook.js +47 -109
  50. package/dist/services/jsonlHandler.d.ts +71 -0
  51. package/dist/services/jsonlHandler.d.ts.map +1 -0
  52. package/dist/services/jsonlHandler.js +236 -0
  53. package/dist/services/memory.d.ts.map +1 -1
  54. package/dist/services/memory.js +33 -11
  55. package/dist/services/session.d.ts +116 -52
  56. package/dist/services/session.d.ts.map +1 -1
  57. package/dist/services/session.js +415 -143
  58. package/dist/tools/bashTool.d.ts.map +1 -1
  59. package/dist/tools/bashTool.js +77 -17
  60. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  61. package/dist/tools/deleteFileTool.js +27 -1
  62. package/dist/tools/editTool.d.ts.map +1 -1
  63. package/dist/tools/editTool.js +33 -8
  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 +30 -10
  69. package/dist/tools/readTool.d.ts.map +1 -1
  70. package/dist/tools/readTool.js +113 -3
  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 +30 -15
  78. package/dist/types/commands.d.ts +4 -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 +45 -0
  86. package/dist/types/core.d.ts.map +1 -1
  87. package/dist/types/environment.d.ts +83 -0
  88. package/dist/types/environment.d.ts.map +1 -0
  89. package/dist/types/environment.js +21 -0
  90. package/dist/types/fileSearch.d.ts +5 -0
  91. package/dist/types/fileSearch.d.ts.map +1 -0
  92. package/dist/types/fileSearch.js +1 -0
  93. package/dist/types/hooks.d.ts +18 -3
  94. package/dist/types/hooks.d.ts.map +1 -1
  95. package/dist/types/hooks.js +8 -8
  96. package/dist/types/index.d.ts +7 -0
  97. package/dist/types/index.d.ts.map +1 -1
  98. package/dist/types/index.js +7 -0
  99. package/dist/types/lsp.d.ts +90 -0
  100. package/dist/types/lsp.d.ts.map +1 -0
  101. package/dist/types/lsp.js +4 -0
  102. package/dist/types/messaging.d.ts +19 -12
  103. package/dist/types/messaging.d.ts.map +1 -1
  104. package/dist/types/permissions.d.ts +35 -0
  105. package/dist/types/permissions.d.ts.map +1 -0
  106. package/dist/types/permissions.js +12 -0
  107. package/dist/types/session.d.ts +15 -0
  108. package/dist/types/session.d.ts.map +1 -0
  109. package/dist/types/session.js +7 -0
  110. package/dist/types/skills.d.ts +1 -0
  111. package/dist/types/skills.d.ts.map +1 -1
  112. package/dist/types/tools.d.ts +35 -0
  113. package/dist/types/tools.d.ts.map +1 -0
  114. package/dist/types/tools.js +4 -0
  115. package/dist/utils/abortUtils.d.ts +34 -0
  116. package/dist/utils/abortUtils.d.ts.map +1 -0
  117. package/dist/utils/abortUtils.js +92 -0
  118. package/dist/utils/bashHistory.d.ts +4 -0
  119. package/dist/utils/bashHistory.d.ts.map +1 -1
  120. package/dist/utils/bashHistory.js +48 -30
  121. package/dist/utils/builtinSubagents.d.ts +7 -0
  122. package/dist/utils/builtinSubagents.d.ts.map +1 -0
  123. package/dist/utils/builtinSubagents.js +65 -0
  124. package/dist/utils/cacheControlUtils.d.ts +96 -0
  125. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  126. package/dist/utils/cacheControlUtils.js +324 -0
  127. package/dist/utils/commandPathResolver.d.ts +52 -0
  128. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  129. package/dist/utils/commandPathResolver.js +145 -0
  130. package/dist/utils/configPaths.d.ts +85 -0
  131. package/dist/utils/configPaths.d.ts.map +1 -0
  132. package/dist/utils/configPaths.js +121 -0
  133. package/dist/utils/constants.d.ts +1 -13
  134. package/dist/utils/constants.d.ts.map +1 -1
  135. package/dist/utils/constants.js +2 -14
  136. package/dist/utils/convertMessagesForAPI.d.ts +2 -1
  137. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  138. package/dist/utils/convertMessagesForAPI.js +39 -18
  139. package/dist/utils/customCommands.d.ts.map +1 -1
  140. package/dist/utils/customCommands.js +66 -21
  141. package/dist/utils/fileSearch.d.ts +14 -0
  142. package/dist/utils/fileSearch.d.ts.map +1 -0
  143. package/dist/utils/fileSearch.js +88 -0
  144. package/dist/utils/fileUtils.d.ts +27 -0
  145. package/dist/utils/fileUtils.d.ts.map +1 -0
  146. package/dist/utils/fileUtils.js +145 -0
  147. package/dist/utils/globalLogger.d.ts +88 -0
  148. package/dist/utils/globalLogger.d.ts.map +1 -0
  149. package/dist/utils/globalLogger.js +120 -0
  150. package/dist/utils/largeOutputHandler.d.ts +15 -0
  151. package/dist/utils/largeOutputHandler.d.ts.map +1 -0
  152. package/dist/utils/largeOutputHandler.js +40 -0
  153. package/dist/utils/markdownParser.d.ts.map +1 -1
  154. package/dist/utils/markdownParser.js +1 -17
  155. package/dist/utils/mcpUtils.d.ts.map +1 -1
  156. package/dist/utils/mcpUtils.js +25 -3
  157. package/dist/utils/messageOperations.d.ts +20 -18
  158. package/dist/utils/messageOperations.d.ts.map +1 -1
  159. package/dist/utils/messageOperations.js +30 -38
  160. package/dist/utils/pathEncoder.d.ts +108 -0
  161. package/dist/utils/pathEncoder.d.ts.map +1 -0
  162. package/dist/utils/pathEncoder.js +279 -0
  163. package/dist/utils/subagentParser.d.ts +2 -2
  164. package/dist/utils/subagentParser.d.ts.map +1 -1
  165. package/dist/utils/subagentParser.js +12 -8
  166. package/dist/utils/tokenCalculation.d.ts +26 -0
  167. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  168. package/dist/utils/tokenCalculation.js +36 -0
  169. package/dist/utils/tokenEstimator.d.ts +39 -0
  170. package/dist/utils/tokenEstimator.d.ts.map +1 -0
  171. package/dist/utils/tokenEstimator.js +55 -0
  172. package/package.json +6 -6
  173. package/src/agent.ts +586 -78
  174. package/src/index.ts +4 -0
  175. package/src/managers/aiManager.ts +341 -192
  176. package/src/managers/backgroundBashManager.ts +11 -9
  177. package/src/managers/hookManager.ts +102 -54
  178. package/src/managers/liveConfigManager.ts +634 -0
  179. package/src/managers/lspManager.ts +434 -0
  180. package/src/managers/messageManager.ts +258 -121
  181. package/src/managers/permissionManager.ts +276 -0
  182. package/src/managers/skillManager.ts +3 -1
  183. package/src/managers/slashCommandManager.ts +5 -3
  184. package/src/managers/subagentManager.ts +295 -76
  185. package/src/managers/toolManager.ts +95 -3
  186. package/src/services/aiService.ts +656 -84
  187. package/src/services/configurationService.ts +762 -0
  188. package/src/services/fileWatcher.ts +300 -0
  189. package/src/services/hook.ts +54 -144
  190. package/src/services/jsonlHandler.ts +303 -0
  191. package/src/services/memory.ts +34 -11
  192. package/src/services/session.ts +522 -173
  193. package/src/tools/bashTool.ts +94 -20
  194. package/src/tools/deleteFileTool.ts +38 -1
  195. package/src/tools/editTool.ts +44 -9
  196. package/src/tools/lspTool.ts +760 -0
  197. package/src/tools/multiEditTool.ts +41 -11
  198. package/src/tools/readTool.ts +127 -3
  199. package/src/tools/skillTool.ts +2 -2
  200. package/src/tools/todoWriteTool.ts +33 -1
  201. package/src/tools/types.ts +15 -9
  202. package/src/tools/writeTool.ts +43 -16
  203. package/src/types/commands.ts +6 -1
  204. package/src/types/config.ts +5 -0
  205. package/src/types/configuration.ts +73 -0
  206. package/src/types/core.ts +55 -0
  207. package/src/types/environment.ts +104 -0
  208. package/src/types/fileSearch.ts +4 -0
  209. package/src/types/hooks.ts +32 -16
  210. package/src/types/index.ts +7 -0
  211. package/src/types/lsp.ts +96 -0
  212. package/src/types/messaging.ts +21 -14
  213. package/src/types/permissions.ts +48 -0
  214. package/src/types/session.ts +20 -0
  215. package/src/types/skills.ts +1 -0
  216. package/src/types/tools.ts +38 -0
  217. package/src/utils/abortUtils.ts +118 -0
  218. package/src/utils/bashHistory.ts +55 -31
  219. package/src/utils/builtinSubagents.ts +71 -0
  220. package/src/utils/cacheControlUtils.ts +475 -0
  221. package/src/utils/commandPathResolver.ts +189 -0
  222. package/src/utils/configPaths.ts +163 -0
  223. package/src/utils/constants.ts +2 -17
  224. package/src/utils/convertMessagesForAPI.ts +44 -18
  225. package/src/utils/customCommands.ts +90 -22
  226. package/src/utils/fileSearch.ts +107 -0
  227. package/src/utils/fileUtils.ts +160 -0
  228. package/src/utils/globalLogger.ts +128 -0
  229. package/src/utils/largeOutputHandler.ts +55 -0
  230. package/src/utils/markdownParser.ts +1 -19
  231. package/src/utils/mcpUtils.ts +34 -3
  232. package/src/utils/messageOperations.ts +47 -53
  233. package/src/utils/pathEncoder.ts +394 -0
  234. package/src/utils/subagentParser.ts +13 -9
  235. package/src/utils/tokenCalculation.ts +43 -0
  236. package/src/utils/tokenEstimator.ts +68 -0
  237. package/dist/utils/configResolver.d.ts +0 -38
  238. package/dist/utils/configResolver.d.ts.map +0 -1
  239. package/dist/utils/configResolver.js +0 -106
  240. package/src/utils/configResolver.ts +0 -142
@@ -1,6 +1,7 @@
1
- import { callAgent, compressMessages } from "../services/aiService.js";
1
+ import { callAgent, compressMessages, } from "../services/aiService.js";
2
2
  import { getMessagesToCompress } from "../utils/messageOperations.js";
3
3
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
4
+ import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
4
5
  import * as memory from "../services/memory.js";
5
6
  export class AIManager {
6
7
  constructor(options) {
@@ -15,11 +16,24 @@ export class AIManager {
15
16
  this.logger = options.logger;
16
17
  this.workdir = options.workdir;
17
18
  this.systemPrompt = options.systemPrompt;
19
+ this.subagentType = options.subagentType; // Store subagent type
20
+ this.stream = options.stream ?? true; // Default to true if not specified
18
21
  this.callbacks = options.callbacks ?? {};
19
- // Store resolved configuration
20
- this.gatewayConfig = options.gatewayConfig;
21
- this.modelConfig = options.modelConfig;
22
- this.tokenLimit = options.tokenLimit;
22
+ // Store configuration getter functions for dynamic resolution
23
+ this.getGatewayConfigFn = options.getGatewayConfig;
24
+ this.getModelConfigFn = options.getModelConfig;
25
+ this.getTokenLimitFn = options.getTokenLimit;
26
+ this.getEnvironmentVarsFn = options.getEnvironmentVars;
27
+ }
28
+ // Getter methods for accessing dynamic configuration
29
+ getGatewayConfig() {
30
+ return this.getGatewayConfigFn();
31
+ }
32
+ getModelConfig() {
33
+ return this.getModelConfigFn();
34
+ }
35
+ getTokenLimit() {
36
+ return this.getTokenLimitFn();
23
37
  }
24
38
  /**
25
39
  * Get filtered tool configuration
@@ -73,45 +87,52 @@ export class AIManager {
73
87
  catch (error) {
74
88
  this.logger?.warn("Failed to generate compactParams", error);
75
89
  }
76
- return undefined;
90
+ return "";
77
91
  }
78
92
  // Private method to handle token statistics and message compression
79
93
  async handleTokenUsageAndCompression(usage, abortController) {
80
94
  if (!usage)
81
95
  return;
82
- // Update token statistics - display latest token usage
83
- this.messageManager.setlatestTotalTokens(usage.total_tokens);
96
+ // Update token statistics - display comprehensive token usage including cache tokens
97
+ const comprehensiveTotalTokens = calculateComprehensiveTotalTokens(usage);
98
+ this.messageManager.setlatestTotalTokens(comprehensiveTotalTokens);
84
99
  // Check if token limit exceeded - use injected configuration
85
- if (usage.total_tokens > this.tokenLimit) {
86
- this.logger?.debug(`Token usage exceeded ${this.tokenLimit}, compressing messages...`);
100
+ if (usage.total_tokens +
101
+ (usage.cache_read_input_tokens || 0) +
102
+ (usage.cache_creation_input_tokens || 0) >
103
+ this.getTokenLimit()) {
104
+ this.logger?.debug(`Token usage exceeded ${this.getTokenLimit()}, compressing messages...`);
87
105
  // Check if messages need compression
88
106
  const { messagesToCompress, insertIndex } = getMessagesToCompress(this.messageManager.getMessages(), 7);
89
107
  // If there are messages to compress, perform compression
90
108
  if (messagesToCompress.length > 0) {
91
109
  const recentChatMessages = convertMessagesForAPI(messagesToCompress);
110
+ // Save session before compression to preserve original messages
111
+ await this.messageManager.saveSession();
92
112
  this.setIsCompressing(true);
93
113
  try {
94
114
  const compressionResult = await compressMessages({
95
- gatewayConfig: this.gatewayConfig,
96
- modelConfig: this.modelConfig,
115
+ gatewayConfig: this.getGatewayConfig(),
116
+ modelConfig: this.getModelConfig(),
97
117
  messages: recentChatMessages,
98
118
  abortSignal: abortController.signal,
99
119
  });
100
- // Execute message reconstruction and sessionId update after compression
101
- this.messageManager.compressMessagesAndUpdateSession(insertIndex, compressionResult.content);
102
120
  // Handle usage tracking for compression operations
121
+ let compressionUsage;
103
122
  if (compressionResult.usage) {
104
- const usage = {
123
+ compressionUsage = {
105
124
  prompt_tokens: compressionResult.usage.prompt_tokens,
106
125
  completion_tokens: compressionResult.usage.completion_tokens,
107
126
  total_tokens: compressionResult.usage.total_tokens,
108
- model: this.modelConfig.fastModel,
127
+ model: this.getModelConfig().fastModel,
109
128
  operation_type: "compress",
110
129
  };
111
- // Notify Agent to add to usage tracking
112
- if (this.callbacks?.onUsageAdded) {
113
- this.callbacks.onUsageAdded(usage);
114
- }
130
+ }
131
+ // Execute message reconstruction and sessionId update after compression
132
+ this.messageManager.compressMessagesAndUpdateSession(insertIndex, compressionResult.content, compressionUsage);
133
+ // Notify Agent to add to usage tracking
134
+ if (compressionUsage && this.callbacks?.onUsageAdded) {
135
+ this.callbacks.onUsageAdded(compressionUsage);
115
136
  }
116
137
  this.logger?.debug(`Successfully compressed ${messagesToCompress.length} messages and updated session`);
117
138
  }
@@ -139,6 +160,8 @@ export class AIManager {
139
160
  if (recursionDepth === 0 && this.isLoading) {
140
161
  return;
141
162
  }
163
+ // Save session in each recursion to ensure message persistence
164
+ await this.messageManager.saveSession();
142
165
  // Only create new AbortControllers for the initial call (recursionDepth === 0)
143
166
  // For recursive calls, reuse existing controllers to maintain abort signal
144
167
  let abortController;
@@ -164,10 +187,13 @@ export class AIManager {
164
187
  try {
165
188
  // Get combined memory content
166
189
  const combinedMemory = await memory.getCombinedMemoryContent(this.workdir);
167
- // Call AI service (non-streaming)
168
- const result = await callAgent({
169
- gatewayConfig: this.gatewayConfig,
170
- modelConfig: this.modelConfig,
190
+ // Track if assistant message has been created
191
+ let assistantMessageCreated = false;
192
+ this.logger?.debug("modelConfig in sendAIMessage", this.getModelConfig());
193
+ // Call AI service with streaming callbacks if enabled
194
+ const callAgentOptions = {
195
+ gatewayConfig: this.getGatewayConfig(),
196
+ modelConfig: this.getModelConfig(),
171
197
  messages: recentMessages,
172
198
  sessionId: this.messageManager.getSessionId(),
173
199
  abortSignal: abortController.signal,
@@ -176,17 +202,73 @@ export class AIManager {
176
202
  tools: this.getFilteredToolsConfig(allowedTools), // Pass filtered tool configuration
177
203
  model: model, // Use passed model
178
204
  systemPrompt: this.systemPrompt, // Pass custom system prompt
179
- });
180
- // Collect content and tool calls
181
- const content = result.content || "";
182
- const toolCalls = [];
183
- if (result.tool_calls) {
184
- for (const toolCall of result.tool_calls) {
185
- this.logger?.debug("ToolCall", toolCall);
186
- if (toolCall.type === "function") {
187
- toolCalls.push(toolCall);
205
+ };
206
+ // Add streaming callbacks only if streaming is enabled
207
+ if (this.stream) {
208
+ callAgentOptions.onContentUpdate = (content) => {
209
+ // Create assistant message on first chunk if not already created
210
+ if (!assistantMessageCreated) {
211
+ this.messageManager.addAssistantMessage();
212
+ assistantMessageCreated = true;
188
213
  }
189
- }
214
+ this.messageManager.updateCurrentMessageContent(content);
215
+ };
216
+ callAgentOptions.onToolUpdate = (toolCall) => {
217
+ // Create assistant message on first tool update if not already created
218
+ if (!assistantMessageCreated) {
219
+ this.messageManager.addAssistantMessage();
220
+ assistantMessageCreated = true;
221
+ }
222
+ // Use parametersChunk as compact param for better performance
223
+ // No need to extract params or generate compact params during streaming
224
+ // Update tool block with streaming parameters using parametersChunk as compact param
225
+ this.messageManager.updateToolBlock({
226
+ id: toolCall.id,
227
+ name: toolCall.name,
228
+ parameters: toolCall.parameters,
229
+ parametersChunk: toolCall.parametersChunk,
230
+ compactParams: toolCall.parameters?.split("\n").pop()?.slice(-30),
231
+ stage: toolCall.stage || "streaming", // Default to streaming if stage not provided
232
+ });
233
+ };
234
+ callAgentOptions.onReasoningUpdate = (reasoning) => {
235
+ // Create assistant message on first reasoning update if not already created
236
+ if (!assistantMessageCreated) {
237
+ this.messageManager.addAssistantMessage();
238
+ assistantMessageCreated = true;
239
+ }
240
+ this.messageManager.updateCurrentMessageReasoning(reasoning);
241
+ };
242
+ }
243
+ const result = await callAgent(callAgentOptions);
244
+ const createdByStreaming = assistantMessageCreated;
245
+ // For non-streaming mode, create assistant message after callAgent returns
246
+ // Also create if streaming mode but no streaming callbacks were called (e.g., when content comes directly in result)
247
+ if (!this.stream ||
248
+ (!assistantMessageCreated &&
249
+ (result.content || result.tool_calls || result.reasoning_content))) {
250
+ this.messageManager.addAssistantMessage();
251
+ assistantMessageCreated = true;
252
+ }
253
+ // Log finish reason and response headers if available
254
+ if (result.finish_reason) {
255
+ this.logger?.debug(`AI response finished with reason: ${result.finish_reason}`);
256
+ }
257
+ if (result.response_headers &&
258
+ Object.keys(result.response_headers).length > 0) {
259
+ this.logger?.debug("AI response headers:", result.response_headers);
260
+ }
261
+ if (result.additionalFields &&
262
+ Object.keys(result.additionalFields).length > 0) {
263
+ this.messageManager.mergeAssistantAdditionalFields(result.additionalFields);
264
+ }
265
+ // Handle result reasoning content from non-streaming mode
266
+ if (result.reasoning_content && !createdByStreaming) {
267
+ this.messageManager.updateCurrentMessageReasoning(result.reasoning_content);
268
+ }
269
+ // Handle result content from non-streaming mode
270
+ if (result.content && !createdByStreaming) {
271
+ this.messageManager.updateCurrentMessageContent(result.content);
190
272
  }
191
273
  // Handle usage tracking for agent operations
192
274
  let usage;
@@ -195,115 +277,136 @@ export class AIManager {
195
277
  prompt_tokens: result.usage.prompt_tokens,
196
278
  completion_tokens: result.usage.completion_tokens,
197
279
  total_tokens: result.usage.total_tokens,
198
- model: model || this.modelConfig.agentModel,
280
+ model: model || this.getModelConfig().agentModel,
199
281
  operation_type: "agent",
282
+ // Preserve cache fields if present
283
+ ...(result.usage.cache_read_input_tokens !== undefined && {
284
+ cache_read_input_tokens: result.usage.cache_read_input_tokens,
285
+ }),
286
+ ...(result.usage.cache_creation_input_tokens !== undefined && {
287
+ cache_creation_input_tokens: result.usage.cache_creation_input_tokens,
288
+ }),
289
+ ...(result.usage.cache_creation && {
290
+ cache_creation: result.usage.cache_creation,
291
+ }),
200
292
  };
201
293
  }
202
- // Add assistant message at once (including content, tool calls, and usage)
203
- this.messageManager.addAssistantMessage(content, toolCalls, usage);
204
- // Notify Agent to add to usage tracking
294
+ // Set usage on the assistant message if available
205
295
  if (usage) {
296
+ const messages = this.messageManager.getMessages();
297
+ const lastMessage = messages[messages.length - 1];
298
+ if (lastMessage && lastMessage.role === "assistant") {
299
+ lastMessage.usage = usage;
300
+ this.messageManager.setMessages(messages);
301
+ }
302
+ // Notify Agent to add to usage tracking
206
303
  if (this.callbacks?.onUsageAdded) {
207
304
  this.callbacks.onUsageAdded(usage);
208
305
  }
209
306
  }
307
+ // Collect tool calls for processing
308
+ const toolCalls = [];
309
+ if (result.tool_calls) {
310
+ for (const toolCall of result.tool_calls) {
311
+ if (toolCall.type === "function") {
312
+ toolCalls.push(toolCall);
313
+ }
314
+ }
315
+ }
210
316
  if (toolCalls.length > 0) {
211
317
  // Execute all tools in parallel using Promise.all
212
318
  const toolExecutionPromises = toolCalls.map(async (functionToolCall) => {
213
319
  const toolId = functionToolCall.id || "";
214
- try {
215
- // Check if already interrupted, skip tool execution if so
216
- if (abortController.signal.aborted ||
217
- toolAbortController.signal.aborted) {
218
- return;
219
- }
220
- // Safely parse tool parameters, handle tools without parameters
221
- let toolArgs = {};
222
- const argsString = functionToolCall.function?.arguments?.trim();
223
- if (!argsString || argsString === "") {
224
- // Tool without parameters, use empty object
225
- toolArgs = {};
226
- }
227
- else {
228
- try {
229
- toolArgs = JSON.parse(argsString);
230
- }
231
- catch (parseError) {
232
- // For non-empty but malformed JSON, still throw exception
233
- const errorMessage = `Failed to parse tool arguments: ${argsString}`;
234
- this.logger?.error(errorMessage, parseError);
235
- throw new Error(errorMessage);
236
- }
237
- }
238
- // Set tool start execution state
239
- const toolName = functionToolCall.function?.name || "";
240
- const compactParams = this.generateCompactParams(toolName, toolArgs);
241
- this.messageManager.updateToolBlock({
242
- id: toolId,
243
- parameters: JSON.stringify(toolArgs, null, 2),
244
- isRunning: true, // isRunning: true
245
- name: toolName,
246
- compactParams,
247
- });
320
+ // Check if already interrupted, skip tool execution if so
321
+ if (abortController.signal.aborted ||
322
+ toolAbortController.signal.aborted) {
323
+ return;
324
+ }
325
+ const toolName = functionToolCall.function?.name || "";
326
+ // Safely parse tool parameters, handle tools without parameters
327
+ let toolArgs = {};
328
+ const argsString = functionToolCall.function?.arguments?.trim();
329
+ if (!argsString || argsString === "") {
330
+ // Tool without parameters, use empty object
331
+ toolArgs = {};
332
+ }
333
+ else {
248
334
  try {
249
- // Execute PreToolUse hooks before tool execution
250
- const shouldExecuteTool = await this.executePreToolUseHooks(toolName, toolArgs, toolId);
251
- // If PreToolUse hooks blocked execution, skip tool execution
252
- if (!shouldExecuteTool) {
253
- this.logger?.info(`Tool ${toolName} execution blocked by PreToolUse hooks`);
254
- return; // Skip this tool and return from this map function
255
- }
256
- // Create tool execution context
257
- const context = {
258
- abortSignal: toolAbortController.signal,
259
- backgroundBashManager: this.backgroundBashManager,
260
- workdir: this.workdir,
261
- };
262
- // Execute tool
263
- const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
264
- // Update message state - tool execution completed
265
- this.messageManager.updateToolBlock({
266
- id: toolId,
267
- parameters: JSON.stringify(toolArgs, null, 2),
268
- result: toolResult.content ||
269
- (toolResult.error ? `Error: ${toolResult.error}` : ""),
270
- success: toolResult.success,
271
- error: toolResult.error,
272
- isRunning: false, // isRunning: false
273
- name: toolName,
274
- shortResult: toolResult.shortResult,
275
- compactParams,
276
- });
277
- // If tool returns diff information, add diff block
278
- if (toolResult.success &&
279
- toolResult.diffResult &&
280
- toolResult.filePath) {
281
- this.messageManager.addDiffBlock(toolResult.filePath, toolResult.diffResult);
282
- }
283
- // Execute PostToolUse hooks after successful tool completion
284
- await this.executePostToolUseHooks(toolId, toolName, toolArgs, toolResult);
335
+ toolArgs = JSON.parse(argsString);
285
336
  }
286
- catch (toolError) {
287
- const errorMessage = toolError instanceof Error
288
- ? toolError.message
289
- : String(toolError);
337
+ catch (parseError) {
338
+ // For non-empty but malformed JSON, still throw exception
339
+ const errorMessage = `Failed to parse tool arguments, finish_reason: ${result.finish_reason}`;
340
+ const fullErrorMessage = `${errorMessage}\nAI response headers:, ${JSON.stringify(result.response_headers)}`;
341
+ this.logger?.error(fullErrorMessage, parseError);
290
342
  this.messageManager.updateToolBlock({
291
343
  id: toolId,
292
- parameters: JSON.stringify(toolArgs, null, 2),
293
- result: `Tool execution failed: ${errorMessage}`,
344
+ parameters: argsString,
345
+ result: errorMessage,
294
346
  success: false,
295
- error: errorMessage,
296
- isRunning: false,
347
+ error: fullErrorMessage,
348
+ stage: "end",
297
349
  name: toolName,
298
- compactParams,
350
+ compactParams: "",
299
351
  });
352
+ return;
353
+ }
354
+ }
355
+ const compactParams = this.generateCompactParams(toolName, toolArgs);
356
+ // Emit running stage for non-streaming tool calls (tool execution about to start)
357
+ this.messageManager.updateToolBlock({
358
+ id: toolId,
359
+ stage: "running",
360
+ name: toolName,
361
+ compactParams,
362
+ parameters: argsString,
363
+ parametersChunk: "",
364
+ });
365
+ try {
366
+ // Execute PreToolUse hooks before tool execution
367
+ const shouldExecuteTool = await this.executePreToolUseHooks(toolName, toolArgs, toolId);
368
+ // If PreToolUse hooks blocked execution, skip tool execution
369
+ if (!shouldExecuteTool) {
370
+ this.logger?.info(`Tool ${toolName} execution blocked by PreToolUse hooks`);
371
+ return; // Skip this tool and return from this map function
300
372
  }
373
+ // Create tool execution context
374
+ const context = {
375
+ abortSignal: toolAbortController.signal,
376
+ backgroundBashManager: this.backgroundBashManager,
377
+ workdir: this.workdir,
378
+ };
379
+ // Execute tool
380
+ const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
381
+ // Update message state - tool execution completed
382
+ this.messageManager.updateToolBlock({
383
+ id: toolId,
384
+ parameters: argsString,
385
+ result: toolResult.content ||
386
+ (toolResult.error ? `Error: ${toolResult.error}` : ""),
387
+ success: toolResult.success,
388
+ error: toolResult.error,
389
+ stage: "end",
390
+ name: toolName,
391
+ shortResult: toolResult.shortResult,
392
+ });
393
+ // Execute PostToolUse hooks after successful tool completion
394
+ await this.executePostToolUseHooks(toolId, toolName, toolArgs, toolResult);
301
395
  }
302
- catch (parseError) {
303
- const errorMessage = parseError instanceof Error
304
- ? parseError.message
305
- : String(parseError);
306
- this.messageManager.addErrorBlock(`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`);
396
+ catch (toolError) {
397
+ const errorMessage = toolError instanceof Error
398
+ ? toolError.message
399
+ : String(toolError);
400
+ this.messageManager.updateToolBlock({
401
+ id: toolId,
402
+ parameters: JSON.stringify(toolArgs, null, 2),
403
+ result: `Tool execution failed: ${errorMessage}`,
404
+ success: false,
405
+ error: errorMessage,
406
+ stage: "end",
407
+ name: toolName,
408
+ compactParams,
409
+ });
307
410
  }
308
411
  });
309
412
  // Wait for all tools to complete execution in parallel
@@ -329,66 +432,71 @@ export class AIManager {
329
432
  this.messageManager.addErrorBlock(error instanceof Error ? error.message : "Unknown error occurred");
330
433
  }
331
434
  finally {
332
- // Only execute Stop hooks for the initial call
435
+ // Only execute cleanup and hooks for the initial call
333
436
  if (recursionDepth === 0) {
334
- // Execute Stop hooks only if the operation was not aborted
437
+ // Save session in each recursion to ensure message persistence
438
+ await this.messageManager.saveSession();
439
+ // Set loading to false first
440
+ this.setIsLoading(false);
441
+ // Clear abort controllers
442
+ this.abortController = null;
443
+ this.toolAbortController = null;
444
+ // Execute Stop/SubagentStop hooks only if the operation was not aborted
335
445
  const isCurrentlyAborted = abortController.signal.aborted || toolAbortController.signal.aborted;
336
446
  if (!isCurrentlyAborted) {
337
447
  const shouldContinue = await this.executeStopHooks();
338
- // If Stop hooks indicate we should continue (due to blocking errors),
448
+ // If Stop/SubagentStop hooks indicate we should continue (due to blocking errors),
339
449
  // restart the AI conversation cycle
340
450
  if (shouldContinue) {
341
- this.logger?.info("Stop hooks indicate issues need fixing, continuing conversation...");
451
+ this.logger?.info(`${this.subagentType ? "SubagentStop" : "Stop"} hooks indicate issues need fixing, continuing conversation...`);
342
452
  // Restart the conversation to let AI fix the issues
343
- // Use recursionDepth = 1 to prevent Stop hooks from running again in continuation
453
+ // Use recursionDepth = 0 to set loading false again for continuation
344
454
  await this.sendAIMessage({
345
- recursionDepth: 1,
455
+ recursionDepth: 0,
346
456
  model,
347
457
  allowedTools,
348
458
  });
349
459
  }
350
460
  }
351
- // Save session after all operations (including continuation) are complete
352
- await this.messageManager.saveSession();
353
- // Clear abort controllers and loading state after all operations are complete
354
- this.abortController = null;
355
- this.toolAbortController = null;
356
- // Set loading to false at the very end, after all operations including continuation
357
- this.setIsLoading(false);
358
461
  }
359
462
  }
360
463
  }
361
464
  /**
362
- * Execute Stop hooks when AI response cycle completes
465
+ * Execute Stop or SubagentStop hooks when AI response cycle completes
466
+ * Uses "SubagentStop" hook name when triggered by a subagent, otherwise uses "Stop"
363
467
  * @returns Promise<boolean> - true if should continue conversation, false if should stop
364
468
  */
365
469
  async executeStopHooks() {
366
470
  if (!this.hookManager)
367
471
  return false;
368
472
  try {
473
+ // Use "SubagentStop" hook name when triggered by a subagent, otherwise use "Stop"
474
+ const hookName = this.subagentType ? "SubagentStop" : "Stop";
369
475
  const context = {
370
- event: "Stop",
476
+ event: hookName,
371
477
  projectDir: this.workdir,
372
478
  timestamp: new Date(),
373
479
  sessionId: this.messageManager.getSessionId(),
374
480
  transcriptPath: this.messageManager.getTranscriptPath(),
375
481
  cwd: this.workdir,
482
+ subagentType: this.subagentType, // Include subagent type in hook context
376
483
  // Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
484
+ env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
377
485
  };
378
- const results = await this.hookManager.executeHooks("Stop", context);
486
+ const results = await this.hookManager.executeHooks(hookName, context);
379
487
  // Process hook results to handle exit codes and appropriate responses
380
488
  let shouldContinue = false;
381
489
  if (results.length > 0) {
382
- const processResult = this.hookManager.processHookResults("Stop", results, this.messageManager);
490
+ const processResult = this.hookManager.processHookResults(hookName, results, this.messageManager);
383
491
  // If hook processing indicates we should block (exit code 2), continue conversation
384
492
  if (processResult.shouldBlock) {
385
- this.logger?.info("Stop hook blocked stopping with error:", processResult.errorMessage);
493
+ this.logger?.info(`${hookName} hook blocked stopping with error:`, processResult.errorMessage);
386
494
  shouldContinue = true;
387
495
  }
388
496
  }
389
497
  // Log hook execution results for debugging
390
498
  if (results.length > 0) {
391
- this.logger?.debug(`Executed ${results.length} Stop hook(s):`, results.map((r) => ({
499
+ this.logger?.debug(`Executed ${results.length} ${hookName} hook(s):`, results.map((r) => ({
392
500
  success: r.success,
393
501
  duration: r.duration,
394
502
  exitCode: r.exitCode,
@@ -400,7 +508,7 @@ export class AIManager {
400
508
  }
401
509
  catch (error) {
402
510
  // Hook execution errors should not interrupt the main workflow
403
- this.logger?.error("Stop hook execution failed:", error);
511
+ this.logger?.error(`${this.subagentType ? "SubagentStop" : "Stop"} hook execution failed:`, error);
404
512
  return false;
405
513
  }
406
514
  }
@@ -421,6 +529,8 @@ export class AIManager {
421
529
  transcriptPath: this.messageManager.getTranscriptPath(),
422
530
  cwd: this.workdir,
423
531
  toolInput,
532
+ subagentType: this.subagentType, // Include subagent type in hook context
533
+ env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
424
534
  };
425
535
  const results = await this.hookManager.executeHooks("PreToolUse", context);
426
536
  // Process hook results to handle exit codes and determine if tool should be blocked
@@ -465,6 +575,8 @@ export class AIManager {
465
575
  cwd: this.workdir,
466
576
  toolInput,
467
577
  toolResponse,
578
+ subagentType: this.subagentType, // Include subagent type in hook context
579
+ env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
468
580
  };
469
581
  const results = await this.hookManager.executeHooks("PostToolUse", context);
470
582
  // Process hook results to handle exit codes and update tool results
@@ -1 +1 @@
1
- {"version":3,"file":"backgroundBashManager.d.ts","sourceRoot":"","sources":["../../src/managers/backgroundBashManager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,8BAA8B;IAC7C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,8BAA8B,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,4BAA4B;IAKjD,OAAO,CAAC,kBAAkB;IAInB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAsErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIjD,YAAY,IAAI,eAAe,EAAE;IAIjC,SAAS,CACd,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,MAAM,GACd;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAiCrD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAmD9B,OAAO,IAAI,IAAI;CAUvB"}
1
+ {"version":3,"file":"backgroundBashManager.d.ts","sourceRoot":"","sources":["../../src/managers/backgroundBashManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,8BAA8B;IAC7C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,8BAA8B,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,qBAAqB;IAChC,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,4BAA4B;IAKjD,OAAO,CAAC,kBAAkB;IAInB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAsErD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAIjD,YAAY,IAAI,eAAe,EAAE;IAIjC,SAAS,CACd,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,MAAM,GACd;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAiCrD,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAmD9B,OAAO,IAAI,IAAI;CAUvB"}
@@ -1,4 +1,6 @@
1
1
  import { spawn } from "child_process";
2
+ import { logger } from "../utils/globalLogger.js";
3
+ import { stripAnsiColors } from "../utils/stringUtils.js";
2
4
  export class BackgroundBashManager {
3
5
  constructor(options) {
4
6
  this.shells = new Map();
@@ -41,11 +43,11 @@ export class BackgroundBashManager {
41
43
  }, timeout);
42
44
  }
43
45
  child.stdout?.on("data", (data) => {
44
- shell.stdout += data.toString();
46
+ shell.stdout += stripAnsiColors(data.toString());
45
47
  this.notifyShellsChange();
46
48
  });
47
49
  child.stderr?.on("data", (data) => {
48
- shell.stderr += data.toString();
50
+ shell.stderr += stripAnsiColors(data.toString());
49
51
  this.notifyShellsChange();
50
52
  });
51
53
  child.on("exit", (code) => {
@@ -62,7 +64,7 @@ export class BackgroundBashManager {
62
64
  clearTimeout(timeoutHandle);
63
65
  }
64
66
  shell.status = "completed";
65
- shell.stderr += `\nProcess error: ${error.message}`;
67
+ shell.stderr += `\nProcess error: ${stripAnsiColors(error.message)}`;
66
68
  shell.exitCode = 1;
67
69
  shell.runtime = Date.now() - startTime;
68
70
  this.notifyShellsChange();
@@ -95,8 +97,8 @@ export class BackgroundBashManager {
95
97
  .filter((line) => regex.test(line))
96
98
  .join("\n");
97
99
  }
98
- catch {
99
- // logger.warn(`Invalid filter regex: ${filter}`, error);
100
+ catch (error) {
101
+ logger.warn(`Invalid filter regex: ${filter}`, error);
100
102
  }
101
103
  }
102
104
  return {
@@ -122,8 +124,8 @@ export class BackgroundBashManager {
122
124
  try {
123
125
  process.kill(-shell.process.pid, "SIGKILL");
124
126
  }
125
- catch {
126
- // logger.error("Failed to force kill process:", error);
127
+ catch (error) {
128
+ logger.error("Failed to force kill process:", error);
127
129
  }
128
130
  }
129
131
  }, 1000);
@@ -147,8 +149,8 @@ export class BackgroundBashManager {
147
149
  this.notifyShellsChange();
148
150
  return true;
149
151
  }
150
- catch {
151
- // logger.error("Failed to kill child process:", directKillError);
152
+ catch (directKillError) {
153
+ logger.error("Failed to kill child process:", directKillError);
152
154
  return false;
153
155
  }
154
156
  }