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,11 @@
1
- import { callAgent, compressMessages } from "../services/aiService.js";
1
+ import {
2
+ callAgent,
3
+ compressMessages,
4
+ type CallAgentOptions,
5
+ } from "../services/aiService.js";
2
6
  import { getMessagesToCompress } from "../utils/messageOperations.js";
3
7
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
8
+ import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
4
9
  import * as memory from "../services/memory.js";
5
10
  import type {
6
11
  Logger,
@@ -30,10 +35,14 @@ export interface AIManagerOptions {
30
35
  callbacks?: AIManagerCallbacks;
31
36
  workdir: string;
32
37
  systemPrompt?: string;
33
- // Resolved configuration
34
- gatewayConfig: GatewayConfig;
35
- modelConfig: ModelConfig;
36
- tokenLimit: number;
38
+ subagentType?: string; // Optional subagent type for hook context
39
+ /**Whether to use streaming mode for AI responses - defaults to true */
40
+ stream?: boolean;
41
+ // Dynamic configuration getters
42
+ getGatewayConfig: () => GatewayConfig;
43
+ getModelConfig: () => ModelConfig;
44
+ getTokenLimit: () => number;
45
+ getEnvironmentVars?: () => Record<string, string>; // Get configuration environment variables for hooks
37
46
  }
38
47
 
39
48
  export class AIManager {
@@ -47,11 +56,14 @@ export class AIManager {
47
56
  private hookManager?: HookManager;
48
57
  private workdir: string;
49
58
  private systemPrompt?: string;
59
+ private subagentType?: string; // Store subagent type for hook context
60
+ private stream: boolean; // Streaming mode flag
50
61
 
51
- // Configuration properties
52
- private gatewayConfig: GatewayConfig;
53
- private modelConfig: ModelConfig;
54
- private tokenLimit: number;
62
+ // Configuration properties (replaced with getter function storage)
63
+ private getGatewayConfigFn: () => GatewayConfig;
64
+ private getModelConfigFn: () => ModelConfig;
65
+ private getTokenLimitFn: () => number;
66
+ private getEnvironmentVarsFn?: () => Record<string, string>;
55
67
 
56
68
  constructor(options: AIManagerOptions) {
57
69
  this.messageManager = options.messageManager;
@@ -61,12 +73,28 @@ export class AIManager {
61
73
  this.logger = options.logger;
62
74
  this.workdir = options.workdir;
63
75
  this.systemPrompt = options.systemPrompt;
76
+ this.subagentType = options.subagentType; // Store subagent type
77
+ this.stream = options.stream ?? true; // Default to true if not specified
64
78
  this.callbacks = options.callbacks ?? {};
65
79
 
66
- // Store resolved configuration
67
- this.gatewayConfig = options.gatewayConfig;
68
- this.modelConfig = options.modelConfig;
69
- this.tokenLimit = options.tokenLimit;
80
+ // Store configuration getter functions for dynamic resolution
81
+ this.getGatewayConfigFn = options.getGatewayConfig;
82
+ this.getModelConfigFn = options.getModelConfig;
83
+ this.getTokenLimitFn = options.getTokenLimit;
84
+ this.getEnvironmentVarsFn = options.getEnvironmentVars;
85
+ }
86
+
87
+ // Getter methods for accessing dynamic configuration
88
+ public getGatewayConfig(): GatewayConfig {
89
+ return this.getGatewayConfigFn();
90
+ }
91
+
92
+ public getModelConfig(): ModelConfig {
93
+ return this.getModelConfigFn();
94
+ }
95
+
96
+ public getTokenLimit(): number {
97
+ return this.getTokenLimitFn();
70
98
  }
71
99
 
72
100
  private isCompressing: boolean = false;
@@ -117,7 +145,7 @@ export class AIManager {
117
145
  private generateCompactParams(
118
146
  toolName: string,
119
147
  toolArgs: Record<string, unknown>,
120
- ): string | undefined {
148
+ ): string {
121
149
  try {
122
150
  const toolPlugin = this.toolManager
123
151
  .list()
@@ -131,23 +159,29 @@ export class AIManager {
131
159
  } catch (error) {
132
160
  this.logger?.warn("Failed to generate compactParams", error);
133
161
  }
134
- return undefined;
162
+ return "";
135
163
  }
136
164
 
137
165
  // Private method to handle token statistics and message compression
138
166
  private async handleTokenUsageAndCompression(
139
- usage: { total_tokens: number } | undefined,
167
+ usage: Usage | undefined,
140
168
  abortController: AbortController,
141
169
  ): Promise<void> {
142
170
  if (!usage) return;
143
171
 
144
- // Update token statistics - display latest token usage
145
- this.messageManager.setlatestTotalTokens(usage.total_tokens);
172
+ // Update token statistics - display comprehensive token usage including cache tokens
173
+ const comprehensiveTotalTokens = calculateComprehensiveTotalTokens(usage);
174
+ this.messageManager.setlatestTotalTokens(comprehensiveTotalTokens);
146
175
 
147
176
  // Check if token limit exceeded - use injected configuration
148
- if (usage.total_tokens > this.tokenLimit) {
177
+ if (
178
+ usage.total_tokens +
179
+ (usage.cache_read_input_tokens || 0) +
180
+ (usage.cache_creation_input_tokens || 0) >
181
+ this.getTokenLimit()
182
+ ) {
149
183
  this.logger?.debug(
150
- `Token usage exceeded ${this.tokenLimit}, compressing messages...`,
184
+ `Token usage exceeded ${this.getTokenLimit()}, compressing messages...`,
151
185
  );
152
186
 
153
187
  // Check if messages need compression
@@ -160,35 +194,40 @@ export class AIManager {
160
194
  if (messagesToCompress.length > 0) {
161
195
  const recentChatMessages = convertMessagesForAPI(messagesToCompress);
162
196
 
197
+ // Save session before compression to preserve original messages
198
+ await this.messageManager.saveSession();
199
+
163
200
  this.setIsCompressing(true);
164
201
  try {
165
202
  const compressionResult = await compressMessages({
166
- gatewayConfig: this.gatewayConfig,
167
- modelConfig: this.modelConfig,
203
+ gatewayConfig: this.getGatewayConfig(),
204
+ modelConfig: this.getModelConfig(),
168
205
  messages: recentChatMessages,
169
206
  abortSignal: abortController.signal,
170
207
  });
171
208
 
172
- // Execute message reconstruction and sessionId update after compression
173
- this.messageManager.compressMessagesAndUpdateSession(
174
- insertIndex,
175
- compressionResult.content,
176
- );
177
-
178
209
  // Handle usage tracking for compression operations
210
+ let compressionUsage: Usage | undefined;
179
211
  if (compressionResult.usage) {
180
- const usage: Usage = {
212
+ compressionUsage = {
181
213
  prompt_tokens: compressionResult.usage.prompt_tokens,
182
214
  completion_tokens: compressionResult.usage.completion_tokens,
183
215
  total_tokens: compressionResult.usage.total_tokens,
184
- model: this.modelConfig.fastModel,
216
+ model: this.getModelConfig().fastModel,
185
217
  operation_type: "compress",
186
218
  };
219
+ }
187
220
 
188
- // Notify Agent to add to usage tracking
189
- if (this.callbacks?.onUsageAdded) {
190
- this.callbacks.onUsageAdded(usage);
191
- }
221
+ // Execute message reconstruction and sessionId update after compression
222
+ this.messageManager.compressMessagesAndUpdateSession(
223
+ insertIndex,
224
+ compressionResult.content,
225
+ compressionUsage,
226
+ );
227
+
228
+ // Notify Agent to add to usage tracking
229
+ if (compressionUsage && this.callbacks?.onUsageAdded) {
230
+ this.callbacks.onUsageAdded(compressionUsage);
192
231
  }
193
232
 
194
233
  this.logger?.debug(
@@ -228,6 +267,9 @@ export class AIManager {
228
267
  return;
229
268
  }
230
269
 
270
+ // Save session in each recursion to ensure message persistence
271
+ await this.messageManager.saveSession();
272
+
231
273
  // Only create new AbortControllers for the initial call (recursionDepth === 0)
232
274
  // For recursive calls, reuse existing controllers to maintain abort signal
233
275
  let abortController: AbortController;
@@ -262,10 +304,15 @@ export class AIManager {
262
304
  this.workdir,
263
305
  );
264
306
 
265
- // Call AI service (non-streaming)
266
- const result = await callAgent({
267
- gatewayConfig: this.gatewayConfig,
268
- modelConfig: this.modelConfig,
307
+ // Track if assistant message has been created
308
+ let assistantMessageCreated = false;
309
+
310
+ this.logger?.debug("modelConfig in sendAIMessage", this.getModelConfig());
311
+
312
+ // Call AI service with streaming callbacks if enabled
313
+ const callAgentOptions: CallAgentOptions = {
314
+ gatewayConfig: this.getGatewayConfig(),
315
+ modelConfig: this.getModelConfig(),
269
316
  messages: recentMessages,
270
317
  sessionId: this.messageManager.getSessionId(),
271
318
  abortSignal: abortController.signal,
@@ -274,19 +321,94 @@ export class AIManager {
274
321
  tools: this.getFilteredToolsConfig(allowedTools), // Pass filtered tool configuration
275
322
  model: model, // Use passed model
276
323
  systemPrompt: this.systemPrompt, // Pass custom system prompt
277
- });
324
+ };
278
325
 
279
- // Collect content and tool calls
280
- const content = result.content || "";
281
- const toolCalls: ChatCompletionMessageFunctionToolCall[] = [];
326
+ // Add streaming callbacks only if streaming is enabled
327
+ if (this.stream) {
328
+ callAgentOptions.onContentUpdate = (content: string) => {
329
+ // Create assistant message on first chunk if not already created
330
+ if (!assistantMessageCreated) {
331
+ this.messageManager.addAssistantMessage();
332
+ assistantMessageCreated = true;
333
+ }
334
+ this.messageManager.updateCurrentMessageContent(content);
335
+ };
336
+ callAgentOptions.onToolUpdate = (toolCall) => {
337
+ // Create assistant message on first tool update if not already created
338
+ if (!assistantMessageCreated) {
339
+ this.messageManager.addAssistantMessage();
340
+ assistantMessageCreated = true;
341
+ }
282
342
 
283
- if (result.tool_calls) {
284
- for (const toolCall of result.tool_calls) {
285
- this.logger?.debug("ToolCall", toolCall);
286
- if (toolCall.type === "function") {
287
- toolCalls.push(toolCall);
343
+ // Use parametersChunk as compact param for better performance
344
+ // No need to extract params or generate compact params during streaming
345
+
346
+ // Update tool block with streaming parameters using parametersChunk as compact param
347
+ this.messageManager.updateToolBlock({
348
+ id: toolCall.id,
349
+ name: toolCall.name,
350
+ parameters: toolCall.parameters,
351
+ parametersChunk: toolCall.parametersChunk,
352
+ compactParams: toolCall.parameters?.split("\n").pop()?.slice(-30),
353
+ stage: toolCall.stage || "streaming", // Default to streaming if stage not provided
354
+ });
355
+ };
356
+ callAgentOptions.onReasoningUpdate = (reasoning: string) => {
357
+ // Create assistant message on first reasoning update if not already created
358
+ if (!assistantMessageCreated) {
359
+ this.messageManager.addAssistantMessage();
360
+ assistantMessageCreated = true;
288
361
  }
289
- }
362
+ this.messageManager.updateCurrentMessageReasoning(reasoning);
363
+ };
364
+ }
365
+
366
+ const result = await callAgent(callAgentOptions);
367
+ const createdByStreaming = assistantMessageCreated;
368
+
369
+ // For non-streaming mode, create assistant message after callAgent returns
370
+ // Also create if streaming mode but no streaming callbacks were called (e.g., when content comes directly in result)
371
+ if (
372
+ !this.stream ||
373
+ (!assistantMessageCreated &&
374
+ (result.content || result.tool_calls || result.reasoning_content))
375
+ ) {
376
+ this.messageManager.addAssistantMessage();
377
+ assistantMessageCreated = true;
378
+ }
379
+
380
+ // Log finish reason and response headers if available
381
+ if (result.finish_reason) {
382
+ this.logger?.debug(
383
+ `AI response finished with reason: ${result.finish_reason}`,
384
+ );
385
+ }
386
+ if (
387
+ result.response_headers &&
388
+ Object.keys(result.response_headers).length > 0
389
+ ) {
390
+ this.logger?.debug("AI response headers:", result.response_headers);
391
+ }
392
+
393
+ if (
394
+ result.additionalFields &&
395
+ Object.keys(result.additionalFields).length > 0
396
+ ) {
397
+ this.messageManager.mergeAssistantAdditionalFields(
398
+ result.additionalFields,
399
+ );
400
+ }
401
+
402
+ // Handle result reasoning content from non-streaming mode
403
+ if (result.reasoning_content && !createdByStreaming) {
404
+ this.messageManager.updateCurrentMessageReasoning(
405
+ result.reasoning_content,
406
+ );
407
+ }
408
+
409
+ // Handle result content from non-streaming mode
410
+ if (result.content && !createdByStreaming) {
411
+ this.messageManager.updateCurrentMessageContent(result.content);
290
412
  }
291
413
 
292
414
  // Handle usage tracking for agent operations
@@ -296,158 +418,173 @@ export class AIManager {
296
418
  prompt_tokens: result.usage.prompt_tokens,
297
419
  completion_tokens: result.usage.completion_tokens,
298
420
  total_tokens: result.usage.total_tokens,
299
- model: model || this.modelConfig.agentModel,
421
+ model: model || this.getModelConfig().agentModel,
300
422
  operation_type: "agent",
423
+ // Preserve cache fields if present
424
+ ...(result.usage.cache_read_input_tokens !== undefined && {
425
+ cache_read_input_tokens: result.usage.cache_read_input_tokens,
426
+ }),
427
+ ...(result.usage.cache_creation_input_tokens !== undefined && {
428
+ cache_creation_input_tokens:
429
+ result.usage.cache_creation_input_tokens,
430
+ }),
431
+ ...(result.usage.cache_creation && {
432
+ cache_creation: result.usage.cache_creation,
433
+ }),
301
434
  };
302
435
  }
303
436
 
304
- // Add assistant message at once (including content, tool calls, and usage)
305
- this.messageManager.addAssistantMessage(content, toolCalls, usage);
306
-
307
- // Notify Agent to add to usage tracking
437
+ // Set usage on the assistant message if available
308
438
  if (usage) {
439
+ const messages = this.messageManager.getMessages();
440
+ const lastMessage = messages[messages.length - 1];
441
+ if (lastMessage && lastMessage.role === "assistant") {
442
+ lastMessage.usage = usage;
443
+ this.messageManager.setMessages(messages);
444
+ }
445
+
446
+ // Notify Agent to add to usage tracking
309
447
  if (this.callbacks?.onUsageAdded) {
310
448
  this.callbacks.onUsageAdded(usage);
311
449
  }
312
450
  }
313
451
 
452
+ // Collect tool calls for processing
453
+ const toolCalls: ChatCompletionMessageFunctionToolCall[] = [];
454
+ if (result.tool_calls) {
455
+ for (const toolCall of result.tool_calls) {
456
+ if (toolCall.type === "function") {
457
+ toolCalls.push(toolCall);
458
+ }
459
+ }
460
+ }
461
+
314
462
  if (toolCalls.length > 0) {
315
463
  // Execute all tools in parallel using Promise.all
316
464
  const toolExecutionPromises = toolCalls.map(
317
465
  async (functionToolCall) => {
318
466
  const toolId = functionToolCall.id || "";
319
467
 
320
- try {
321
- // Check if already interrupted, skip tool execution if so
322
- if (
323
- abortController.signal.aborted ||
324
- toolAbortController.signal.aborted
325
- ) {
468
+ // Check if already interrupted, skip tool execution if so
469
+ if (
470
+ abortController.signal.aborted ||
471
+ toolAbortController.signal.aborted
472
+ ) {
473
+ return;
474
+ }
475
+
476
+ const toolName = functionToolCall.function?.name || "";
477
+ // Safely parse tool parameters, handle tools without parameters
478
+ let toolArgs: Record<string, unknown> = {};
479
+ const argsString = functionToolCall.function?.arguments?.trim();
480
+
481
+ if (!argsString || argsString === "") {
482
+ // Tool without parameters, use empty object
483
+ toolArgs = {};
484
+ } else {
485
+ try {
486
+ toolArgs = JSON.parse(argsString);
487
+ } catch (parseError) {
488
+ // For non-empty but malformed JSON, still throw exception
489
+ const errorMessage = `Failed to parse tool arguments, finish_reason: ${result.finish_reason}`;
490
+ const fullErrorMessage = `${errorMessage}\nAI response headers:, ${JSON.stringify(result.response_headers)}`;
491
+ this.logger?.error(fullErrorMessage, parseError);
492
+ this.messageManager.updateToolBlock({
493
+ id: toolId,
494
+ parameters: argsString,
495
+ result: errorMessage,
496
+ success: false,
497
+ error: fullErrorMessage,
498
+ stage: "end",
499
+ name: toolName,
500
+ compactParams: "",
501
+ });
326
502
  return;
327
503
  }
504
+ }
505
+
506
+ const compactParams = this.generateCompactParams(
507
+ toolName,
508
+ toolArgs,
509
+ );
510
+
511
+ // Emit running stage for non-streaming tool calls (tool execution about to start)
512
+ this.messageManager.updateToolBlock({
513
+ id: toolId,
514
+ stage: "running",
515
+ name: toolName,
516
+ compactParams,
517
+ parameters: argsString,
518
+ parametersChunk: "",
519
+ });
328
520
 
329
- // Safely parse tool parameters, handle tools without parameters
330
- let toolArgs: Record<string, unknown> = {};
331
- const argsString = functionToolCall.function?.arguments?.trim();
332
-
333
- if (!argsString || argsString === "") {
334
- // Tool without parameters, use empty object
335
- toolArgs = {};
336
- } else {
337
- try {
338
- toolArgs = JSON.parse(argsString);
339
- } catch (parseError) {
340
- // For non-empty but malformed JSON, still throw exception
341
- const errorMessage = `Failed to parse tool arguments: ${argsString}`;
342
- this.logger?.error(errorMessage, parseError);
343
- throw new Error(errorMessage);
344
- }
521
+ try {
522
+ // Execute PreToolUse hooks before tool execution
523
+ const shouldExecuteTool = await this.executePreToolUseHooks(
524
+ toolName,
525
+ toolArgs,
526
+ toolId,
527
+ );
528
+
529
+ // If PreToolUse hooks blocked execution, skip tool execution
530
+ if (!shouldExecuteTool) {
531
+ this.logger?.info(
532
+ `Tool ${toolName} execution blocked by PreToolUse hooks`,
533
+ );
534
+ return; // Skip this tool and return from this map function
345
535
  }
346
536
 
347
- // Set tool start execution state
348
- const toolName = functionToolCall.function?.name || "";
349
- const compactParams = this.generateCompactParams(
537
+ // Create tool execution context
538
+ const context: ToolContext = {
539
+ abortSignal: toolAbortController.signal,
540
+ backgroundBashManager: this.backgroundBashManager,
541
+ workdir: this.workdir,
542
+ };
543
+
544
+ // Execute tool
545
+ const toolResult = await this.toolManager.execute(
546
+ functionToolCall.function?.name || "",
547
+ toolArgs,
548
+ context,
549
+ );
550
+
551
+ // Update message state - tool execution completed
552
+ this.messageManager.updateToolBlock({
553
+ id: toolId,
554
+ parameters: argsString,
555
+ result:
556
+ toolResult.content ||
557
+ (toolResult.error ? `Error: ${toolResult.error}` : ""),
558
+ success: toolResult.success,
559
+ error: toolResult.error,
560
+ stage: "end",
561
+ name: toolName,
562
+ shortResult: toolResult.shortResult,
563
+ });
564
+
565
+ // Execute PostToolUse hooks after successful tool completion
566
+ await this.executePostToolUseHooks(
567
+ toolId,
350
568
  toolName,
351
569
  toolArgs,
570
+ toolResult,
352
571
  );
572
+ } catch (toolError) {
573
+ const errorMessage =
574
+ toolError instanceof Error
575
+ ? toolError.message
576
+ : String(toolError);
353
577
 
354
578
  this.messageManager.updateToolBlock({
355
579
  id: toolId,
356
580
  parameters: JSON.stringify(toolArgs, null, 2),
357
- isRunning: true, // isRunning: true
581
+ result: `Tool execution failed: ${errorMessage}`,
582
+ success: false,
583
+ error: errorMessage,
584
+ stage: "end",
358
585
  name: toolName,
359
586
  compactParams,
360
587
  });
361
-
362
- try {
363
- // Execute PreToolUse hooks before tool execution
364
- const shouldExecuteTool = await this.executePreToolUseHooks(
365
- toolName,
366
- toolArgs,
367
- toolId,
368
- );
369
-
370
- // If PreToolUse hooks blocked execution, skip tool execution
371
- if (!shouldExecuteTool) {
372
- this.logger?.info(
373
- `Tool ${toolName} execution blocked by PreToolUse hooks`,
374
- );
375
- return; // Skip this tool and return from this map function
376
- }
377
-
378
- // Create tool execution context
379
- const context: ToolContext = {
380
- abortSignal: toolAbortController.signal,
381
- backgroundBashManager: this.backgroundBashManager,
382
- workdir: this.workdir,
383
- };
384
-
385
- // Execute tool
386
- const toolResult = await this.toolManager.execute(
387
- functionToolCall.function?.name || "",
388
- toolArgs,
389
- context,
390
- );
391
-
392
- // Update message state - tool execution completed
393
- this.messageManager.updateToolBlock({
394
- id: toolId,
395
- parameters: JSON.stringify(toolArgs, null, 2),
396
- result:
397
- toolResult.content ||
398
- (toolResult.error ? `Error: ${toolResult.error}` : ""),
399
- success: toolResult.success,
400
- error: toolResult.error,
401
- isRunning: false, // isRunning: false
402
- name: toolName,
403
- shortResult: toolResult.shortResult,
404
- compactParams,
405
- });
406
-
407
- // If tool returns diff information, add diff block
408
- if (
409
- toolResult.success &&
410
- toolResult.diffResult &&
411
- toolResult.filePath
412
- ) {
413
- this.messageManager.addDiffBlock(
414
- toolResult.filePath,
415
- toolResult.diffResult,
416
- );
417
- }
418
-
419
- // Execute PostToolUse hooks after successful tool completion
420
- await this.executePostToolUseHooks(
421
- toolId,
422
- toolName,
423
- toolArgs,
424
- toolResult,
425
- );
426
- } catch (toolError) {
427
- const errorMessage =
428
- toolError instanceof Error
429
- ? toolError.message
430
- : String(toolError);
431
-
432
- this.messageManager.updateToolBlock({
433
- id: toolId,
434
- parameters: JSON.stringify(toolArgs, null, 2),
435
- result: `Tool execution failed: ${errorMessage}`,
436
- success: false,
437
- error: errorMessage,
438
- isRunning: false,
439
- name: toolName,
440
- compactParams,
441
- });
442
- }
443
- } catch (parseError) {
444
- const errorMessage =
445
- parseError instanceof Error
446
- ? parseError.message
447
- : String(parseError);
448
- this.messageManager.addErrorBlock(
449
- `Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`,
450
- );
451
588
  }
452
589
  },
453
590
  );
@@ -479,70 +616,75 @@ export class AIManager {
479
616
  error instanceof Error ? error.message : "Unknown error occurred",
480
617
  );
481
618
  } finally {
482
- // Only execute Stop hooks for the initial call
619
+ // Only execute cleanup and hooks for the initial call
483
620
  if (recursionDepth === 0) {
484
- // Execute Stop hooks only if the operation was not aborted
621
+ // Save session in each recursion to ensure message persistence
622
+ await this.messageManager.saveSession();
623
+ // Set loading to false first
624
+ this.setIsLoading(false);
625
+
626
+ // Clear abort controllers
627
+ this.abortController = null;
628
+ this.toolAbortController = null;
629
+
630
+ // Execute Stop/SubagentStop hooks only if the operation was not aborted
485
631
  const isCurrentlyAborted =
486
632
  abortController.signal.aborted || toolAbortController.signal.aborted;
487
633
 
488
634
  if (!isCurrentlyAborted) {
489
635
  const shouldContinue = await this.executeStopHooks();
490
636
 
491
- // If Stop hooks indicate we should continue (due to blocking errors),
637
+ // If Stop/SubagentStop hooks indicate we should continue (due to blocking errors),
492
638
  // restart the AI conversation cycle
493
639
  if (shouldContinue) {
494
640
  this.logger?.info(
495
- "Stop hooks indicate issues need fixing, continuing conversation...",
641
+ `${this.subagentType ? "SubagentStop" : "Stop"} hooks indicate issues need fixing, continuing conversation...`,
496
642
  );
497
643
 
498
644
  // Restart the conversation to let AI fix the issues
499
- // Use recursionDepth = 1 to prevent Stop hooks from running again in continuation
645
+ // Use recursionDepth = 0 to set loading false again for continuation
500
646
  await this.sendAIMessage({
501
- recursionDepth: 1,
647
+ recursionDepth: 0,
502
648
  model,
503
649
  allowedTools,
504
650
  });
505
651
  }
506
652
  }
507
-
508
- // Save session after all operations (including continuation) are complete
509
- await this.messageManager.saveSession();
510
-
511
- // Clear abort controllers and loading state after all operations are complete
512
- this.abortController = null;
513
- this.toolAbortController = null;
514
-
515
- // Set loading to false at the very end, after all operations including continuation
516
- this.setIsLoading(false);
517
653
  }
518
654
  }
519
655
  }
520
656
 
521
657
  /**
522
- * Execute Stop hooks when AI response cycle completes
658
+ * Execute Stop or SubagentStop hooks when AI response cycle completes
659
+ * Uses "SubagentStop" hook name when triggered by a subagent, otherwise uses "Stop"
523
660
  * @returns Promise<boolean> - true if should continue conversation, false if should stop
524
661
  */
525
662
  private async executeStopHooks(): Promise<boolean> {
526
663
  if (!this.hookManager) return false;
527
664
 
528
665
  try {
666
+ // Use "SubagentStop" hook name when triggered by a subagent, otherwise use "Stop"
667
+ const hookName = this.subagentType ? "SubagentStop" : "Stop";
668
+
529
669
  const context: ExtendedHookExecutionContext = {
530
- event: "Stop",
670
+ event: hookName,
531
671
  projectDir: this.workdir,
532
672
  timestamp: new Date(),
533
673
  sessionId: this.messageManager.getSessionId(),
534
674
  transcriptPath: this.messageManager.getTranscriptPath(),
535
675
  cwd: this.workdir,
676
+ subagentType: this.subagentType, // Include subagent type in hook context
536
677
  // Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
678
+ env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
537
679
  };
538
680
 
539
- const results = await this.hookManager.executeHooks("Stop", context);
681
+ const results = await this.hookManager.executeHooks(hookName, context);
540
682
 
541
683
  // Process hook results to handle exit codes and appropriate responses
542
684
  let shouldContinue = false;
543
685
  if (results.length > 0) {
544
686
  const processResult = this.hookManager.processHookResults(
545
- "Stop",
687
+ hookName,
546
688
  results,
547
689
  this.messageManager,
548
690
  );
@@ -550,7 +692,7 @@ export class AIManager {
550
692
  // If hook processing indicates we should block (exit code 2), continue conversation
551
693
  if (processResult.shouldBlock) {
552
694
  this.logger?.info(
553
- "Stop hook blocked stopping with error:",
695
+ `${hookName} hook blocked stopping with error:`,
554
696
  processResult.errorMessage,
555
697
  );
556
698
  shouldContinue = true;
@@ -560,7 +702,7 @@ export class AIManager {
560
702
  // Log hook execution results for debugging
561
703
  if (results.length > 0) {
562
704
  this.logger?.debug(
563
- `Executed ${results.length} Stop hook(s):`,
705
+ `Executed ${results.length} ${hookName} hook(s):`,
564
706
  results.map((r) => ({
565
707
  success: r.success,
566
708
  duration: r.duration,
@@ -574,7 +716,10 @@ export class AIManager {
574
716
  return shouldContinue;
575
717
  } catch (error) {
576
718
  // Hook execution errors should not interrupt the main workflow
577
- this.logger?.error("Stop hook execution failed:", error);
719
+ this.logger?.error(
720
+ `${this.subagentType ? "SubagentStop" : "Stop"} hook execution failed:`,
721
+ error,
722
+ );
578
723
  return false;
579
724
  }
580
725
  }
@@ -600,6 +745,8 @@ export class AIManager {
600
745
  transcriptPath: this.messageManager.getTranscriptPath(),
601
746
  cwd: this.workdir,
602
747
  toolInput,
748
+ subagentType: this.subagentType, // Include subagent type in hook context
749
+ env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
603
750
  };
604
751
 
605
752
  const results = await this.hookManager.executeHooks(
@@ -664,6 +811,8 @@ export class AIManager {
664
811
  cwd: this.workdir,
665
812
  toolInput,
666
813
  toolResponse,
814
+ subagentType: this.subagentType, // Include subagent type in hook context
815
+ env: this.getEnvironmentVarsFn?.() || {}, // Include configuration environment variables
667
816
  };
668
817
 
669
818
  const results = await this.hookManager.executeHooks(