wave-agent-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +32 -0
  2. package/dist/agent.d.ts +96 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +286 -0
  5. package/dist/hooks/executor.d.ts +56 -0
  6. package/dist/hooks/executor.d.ts.map +1 -0
  7. package/dist/hooks/executor.js +312 -0
  8. package/dist/hooks/index.d.ts +17 -0
  9. package/dist/hooks/index.d.ts.map +1 -0
  10. package/dist/hooks/index.js +14 -0
  11. package/dist/hooks/manager.d.ts +90 -0
  12. package/dist/hooks/manager.d.ts.map +1 -0
  13. package/dist/hooks/manager.js +395 -0
  14. package/dist/hooks/matcher.d.ts +49 -0
  15. package/dist/hooks/matcher.d.ts.map +1 -0
  16. package/dist/hooks/matcher.js +147 -0
  17. package/dist/hooks/settings.d.ts +46 -0
  18. package/dist/hooks/settings.d.ts.map +1 -0
  19. package/dist/hooks/settings.js +100 -0
  20. package/dist/hooks/types.d.ts +80 -0
  21. package/dist/hooks/types.d.ts.map +1 -0
  22. package/dist/hooks/types.js +59 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +20 -0
  26. package/dist/managers/aiManager.d.ts +61 -0
  27. package/dist/managers/aiManager.d.ts.map +1 -0
  28. package/dist/managers/aiManager.js +415 -0
  29. package/dist/managers/backgroundBashManager.d.ts +27 -0
  30. package/dist/managers/backgroundBashManager.d.ts.map +1 -0
  31. package/dist/managers/backgroundBashManager.js +166 -0
  32. package/dist/managers/bashManager.d.ts +20 -0
  33. package/dist/managers/bashManager.d.ts.map +1 -0
  34. package/dist/managers/bashManager.js +66 -0
  35. package/dist/managers/mcpManager.d.ts +63 -0
  36. package/dist/managers/mcpManager.d.ts.map +1 -0
  37. package/dist/managers/mcpManager.js +378 -0
  38. package/dist/managers/messageManager.d.ts +85 -0
  39. package/dist/managers/messageManager.d.ts.map +1 -0
  40. package/dist/managers/messageManager.js +265 -0
  41. package/dist/managers/skillManager.d.ts +59 -0
  42. package/dist/managers/skillManager.d.ts.map +1 -0
  43. package/dist/managers/skillManager.js +317 -0
  44. package/dist/managers/slashCommandManager.d.ts +77 -0
  45. package/dist/managers/slashCommandManager.d.ts.map +1 -0
  46. package/dist/managers/slashCommandManager.js +208 -0
  47. package/dist/managers/toolManager.d.ts +23 -0
  48. package/dist/managers/toolManager.d.ts.map +1 -0
  49. package/dist/managers/toolManager.js +79 -0
  50. package/dist/services/aiService.d.ts +28 -0
  51. package/dist/services/aiService.d.ts.map +1 -0
  52. package/dist/services/aiService.js +180 -0
  53. package/dist/services/memory.d.ts +8 -0
  54. package/dist/services/memory.d.ts.map +1 -0
  55. package/dist/services/memory.js +128 -0
  56. package/dist/services/session.d.ts +54 -0
  57. package/dist/services/session.d.ts.map +1 -0
  58. package/dist/services/session.js +196 -0
  59. package/dist/tools/bashTool.d.ts +14 -0
  60. package/dist/tools/bashTool.d.ts.map +1 -0
  61. package/dist/tools/bashTool.js +351 -0
  62. package/dist/tools/deleteFileTool.d.ts +6 -0
  63. package/dist/tools/deleteFileTool.d.ts.map +1 -0
  64. package/dist/tools/deleteFileTool.js +67 -0
  65. package/dist/tools/editTool.d.ts +6 -0
  66. package/dist/tools/editTool.d.ts.map +1 -0
  67. package/dist/tools/editTool.js +168 -0
  68. package/dist/tools/globTool.d.ts +6 -0
  69. package/dist/tools/globTool.d.ts.map +1 -0
  70. package/dist/tools/globTool.js +113 -0
  71. package/dist/tools/grepTool.d.ts +6 -0
  72. package/dist/tools/grepTool.d.ts.map +1 -0
  73. package/dist/tools/grepTool.js +268 -0
  74. package/dist/tools/lsTool.d.ts +6 -0
  75. package/dist/tools/lsTool.d.ts.map +1 -0
  76. package/dist/tools/lsTool.js +160 -0
  77. package/dist/tools/multiEditTool.d.ts +6 -0
  78. package/dist/tools/multiEditTool.d.ts.map +1 -0
  79. package/dist/tools/multiEditTool.js +222 -0
  80. package/dist/tools/readTool.d.ts +6 -0
  81. package/dist/tools/readTool.d.ts.map +1 -0
  82. package/dist/tools/readTool.js +136 -0
  83. package/dist/tools/types.d.ts +35 -0
  84. package/dist/tools/types.d.ts.map +1 -0
  85. package/dist/tools/types.js +4 -0
  86. package/dist/tools/writeTool.d.ts +6 -0
  87. package/dist/tools/writeTool.d.ts.map +1 -0
  88. package/dist/tools/writeTool.js +138 -0
  89. package/dist/types.d.ts +212 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/dist/utils/bashHistory.d.ts +46 -0
  93. package/dist/utils/bashHistory.d.ts.map +1 -0
  94. package/dist/utils/bashHistory.js +236 -0
  95. package/dist/utils/commandArgumentParser.d.ts +34 -0
  96. package/dist/utils/commandArgumentParser.d.ts.map +1 -0
  97. package/dist/utils/commandArgumentParser.js +123 -0
  98. package/dist/utils/constants.d.ts +27 -0
  99. package/dist/utils/constants.d.ts.map +1 -0
  100. package/dist/utils/constants.js +28 -0
  101. package/dist/utils/convertMessagesForAPI.d.ts +9 -0
  102. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -0
  103. package/dist/utils/convertMessagesForAPI.js +189 -0
  104. package/dist/utils/customCommands.d.ts +14 -0
  105. package/dist/utils/customCommands.d.ts.map +1 -0
  106. package/dist/utils/customCommands.js +71 -0
  107. package/dist/utils/fileFilter.d.ts +26 -0
  108. package/dist/utils/fileFilter.d.ts.map +1 -0
  109. package/dist/utils/fileFilter.js +177 -0
  110. package/dist/utils/markdownParser.d.ts +27 -0
  111. package/dist/utils/markdownParser.d.ts.map +1 -0
  112. package/dist/utils/markdownParser.js +109 -0
  113. package/dist/utils/mcpUtils.d.ts +24 -0
  114. package/dist/utils/mcpUtils.d.ts.map +1 -0
  115. package/dist/utils/mcpUtils.js +51 -0
  116. package/dist/utils/messageOperations.d.ts +118 -0
  117. package/dist/utils/messageOperations.d.ts.map +1 -0
  118. package/dist/utils/messageOperations.js +334 -0
  119. package/dist/utils/path.d.ts +25 -0
  120. package/dist/utils/path.d.ts.map +1 -0
  121. package/dist/utils/path.js +109 -0
  122. package/dist/utils/skillParser.d.ts +18 -0
  123. package/dist/utils/skillParser.d.ts.map +1 -0
  124. package/dist/utils/skillParser.js +147 -0
  125. package/dist/utils/stringUtils.d.ts +13 -0
  126. package/dist/utils/stringUtils.d.ts.map +1 -0
  127. package/dist/utils/stringUtils.js +44 -0
  128. package/package.json +51 -0
  129. package/src/agent.ts +405 -0
  130. package/src/hooks/executor.ts +440 -0
  131. package/src/hooks/index.ts +52 -0
  132. package/src/hooks/manager.ts +618 -0
  133. package/src/hooks/matcher.ts +187 -0
  134. package/src/hooks/settings.ts +129 -0
  135. package/src/hooks/types.ts +169 -0
  136. package/src/index.ts +24 -0
  137. package/src/managers/aiManager.ts +573 -0
  138. package/src/managers/backgroundBashManager.ts +203 -0
  139. package/src/managers/bashManager.ts +97 -0
  140. package/src/managers/mcpManager.ts +493 -0
  141. package/src/managers/messageManager.ts +415 -0
  142. package/src/managers/skillManager.ts +404 -0
  143. package/src/managers/slashCommandManager.ts +293 -0
  144. package/src/managers/toolManager.ts +106 -0
  145. package/src/services/aiService.ts +252 -0
  146. package/src/services/memory.ts +149 -0
  147. package/src/services/session.ts +265 -0
  148. package/src/tools/bashTool.ts +402 -0
  149. package/src/tools/deleteFileTool.ts +81 -0
  150. package/src/tools/editTool.ts +192 -0
  151. package/src/tools/globTool.ts +135 -0
  152. package/src/tools/grepTool.ts +326 -0
  153. package/src/tools/lsTool.ts +187 -0
  154. package/src/tools/multiEditTool.ts +268 -0
  155. package/src/tools/readTool.ts +165 -0
  156. package/src/tools/types.ts +47 -0
  157. package/src/tools/writeTool.ts +163 -0
  158. package/src/types.ts +260 -0
  159. package/src/utils/bashHistory.ts +303 -0
  160. package/src/utils/commandArgumentParser.ts +153 -0
  161. package/src/utils/constants.ts +37 -0
  162. package/src/utils/convertMessagesForAPI.ts +236 -0
  163. package/src/utils/customCommands.ts +85 -0
  164. package/src/utils/fileFilter.ts +202 -0
  165. package/src/utils/markdownParser.ts +156 -0
  166. package/src/utils/mcpUtils.ts +81 -0
  167. package/src/utils/messageOperations.ts +506 -0
  168. package/src/utils/path.ts +118 -0
  169. package/src/utils/skillParser.ts +188 -0
  170. package/src/utils/stringUtils.ts +50 -0
@@ -0,0 +1,415 @@
1
+ import { callAgent, compressMessages } from "../services/aiService.js";
2
+ import { getMessagesToCompress } from "../utils/messageOperations.js";
3
+ import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
4
+ import * as memory from "../services/memory.js";
5
+ import { DEFAULT_TOKEN_LIMIT } from "../utils/constants.js";
6
+ export class AIManager {
7
+ constructor(options) {
8
+ this.isLoading = false;
9
+ this.abortController = null;
10
+ this.toolAbortController = null;
11
+ this.isCompressing = false;
12
+ this.messageManager = options.messageManager;
13
+ this.toolManager = options.toolManager;
14
+ this.backgroundBashManager = options.backgroundBashManager;
15
+ this.hookManager = options.hookManager;
16
+ this.logger = options.logger;
17
+ this.workdir = options.workdir;
18
+ this.systemPrompt = options.systemPrompt;
19
+ this.callbacks = options.callbacks ?? {};
20
+ }
21
+ /**
22
+ * Get filtered tool configuration
23
+ */
24
+ getFilteredToolsConfig(allowedTools) {
25
+ const allTools = this.toolManager.getToolsConfig();
26
+ // If no allowedTools specified, return all tools
27
+ if (!allowedTools || allowedTools.length === 0) {
28
+ return allTools;
29
+ }
30
+ // Filter allowed tools
31
+ return allTools.filter((tool) => allowedTools.includes(tool.function.name));
32
+ }
33
+ setIsLoading(isLoading) {
34
+ this.isLoading = isLoading;
35
+ }
36
+ abortAIMessage() {
37
+ // Interrupt AI service
38
+ if (this.abortController) {
39
+ try {
40
+ this.abortController.abort();
41
+ }
42
+ catch (error) {
43
+ this.logger?.error("Failed to abort AI service:", error);
44
+ }
45
+ }
46
+ // Interrupt tool execution
47
+ if (this.toolAbortController) {
48
+ try {
49
+ this.toolAbortController.abort();
50
+ }
51
+ catch (error) {
52
+ this.logger?.error("Failed to abort tool execution:", error);
53
+ }
54
+ }
55
+ this.setIsLoading(false);
56
+ }
57
+ // Helper method to generate compactParams
58
+ generateCompactParams(toolName, toolArgs) {
59
+ try {
60
+ const toolPlugin = this.toolManager
61
+ .list()
62
+ .find((plugin) => plugin.name === toolName);
63
+ if (toolPlugin?.formatCompactParams) {
64
+ const context = {
65
+ workdir: this.workdir,
66
+ };
67
+ return toolPlugin.formatCompactParams(toolArgs, context);
68
+ }
69
+ }
70
+ catch (error) {
71
+ this.logger?.warn("Failed to generate compactParams", error);
72
+ }
73
+ return undefined;
74
+ }
75
+ // Private method to handle token statistics and message compression
76
+ async handleTokenUsageAndCompression(usage, abortController) {
77
+ if (!usage)
78
+ return;
79
+ // Update token statistics - display latest token usage
80
+ this.messageManager.setlatestTotalTokens(usage.total_tokens);
81
+ // Check if token limit exceeded
82
+ const tokenLimit = parseInt(process.env.TOKEN_LIMIT || `${DEFAULT_TOKEN_LIMIT}`, 10);
83
+ if (usage.total_tokens > tokenLimit) {
84
+ this.logger?.info(`Token usage exceeded ${tokenLimit}, compressing messages...`);
85
+ // Check if messages need compression
86
+ const { messagesToCompress, insertIndex } = getMessagesToCompress(this.messageManager.getMessages(), 7);
87
+ // If there are messages to compress, perform compression
88
+ if (messagesToCompress.length > 0) {
89
+ const recentChatMessages = convertMessagesForAPI(messagesToCompress);
90
+ this.setIsCompressing(true);
91
+ try {
92
+ const compressedContent = await compressMessages({
93
+ messages: recentChatMessages,
94
+ abortSignal: abortController.signal,
95
+ });
96
+ // Execute message reconstruction and sessionId update after compression
97
+ this.messageManager.compressMessagesAndUpdateSession(insertIndex, compressedContent);
98
+ this.logger?.info(`Successfully compressed ${messagesToCompress.length} messages and updated session`);
99
+ }
100
+ catch (compressError) {
101
+ this.logger?.error("Failed to compress messages:", compressError);
102
+ }
103
+ finally {
104
+ this.setIsCompressing(false);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ getIsCompressing() {
110
+ return this.isCompressing;
111
+ }
112
+ setIsCompressing(isCompressing) {
113
+ if (this.isCompressing !== isCompressing) {
114
+ this.isCompressing = isCompressing;
115
+ this.callbacks.onCompressionStateChange?.(isCompressing);
116
+ }
117
+ }
118
+ async sendAIMessage(options = {}) {
119
+ const { recursionDepth = 0, model, allowedTools } = options;
120
+ // Only check isLoading for the initial call (recursionDepth === 0)
121
+ if (recursionDepth === 0 && this.isLoading) {
122
+ return;
123
+ }
124
+ // Create new AbortController
125
+ const abortController = new AbortController();
126
+ this.abortController = abortController;
127
+ // Create separate AbortController for tool execution
128
+ const toolAbortController = new AbortController();
129
+ this.toolAbortController = toolAbortController;
130
+ // Only set loading state for the initial call
131
+ if (recursionDepth === 0) {
132
+ this.setIsLoading(true);
133
+ }
134
+ // Get recent message history
135
+ const recentMessages = convertMessagesForAPI(this.messageManager.getMessages());
136
+ try {
137
+ // Get combined memory content
138
+ const combinedMemory = await memory.getCombinedMemoryContent(this.workdir);
139
+ // Call AI service (non-streaming)
140
+ const result = await callAgent({
141
+ messages: recentMessages,
142
+ sessionId: this.messageManager.getSessionId(),
143
+ abortSignal: abortController.signal,
144
+ memory: combinedMemory, // Pass combined memory content
145
+ workdir: this.workdir, // Pass working directory
146
+ tools: this.getFilteredToolsConfig(allowedTools), // Pass filtered tool configuration
147
+ model: model, // Use passed model
148
+ systemPrompt: this.systemPrompt, // Pass custom system prompt
149
+ });
150
+ // Collect content and tool calls
151
+ const content = result.content || "";
152
+ const toolCalls = [];
153
+ if (result.tool_calls) {
154
+ for (const toolCall of result.tool_calls) {
155
+ if (toolCall.type === "function") {
156
+ toolCalls.push(toolCall);
157
+ }
158
+ }
159
+ }
160
+ // Add assistant message at once (including content and tool calls)
161
+ this.messageManager.addAssistantMessage(content, toolCalls);
162
+ if (toolCalls.length > 0) {
163
+ for (const functionToolCall of toolCalls) {
164
+ const toolId = functionToolCall.id || "";
165
+ // Execute tool
166
+ try {
167
+ // Check if already interrupted, skip tool execution if so
168
+ if (abortController.signal.aborted ||
169
+ toolAbortController.signal.aborted) {
170
+ return;
171
+ }
172
+ // Safely parse tool parameters, handle tools without parameters
173
+ let toolArgs = {};
174
+ const argsString = functionToolCall.function?.arguments?.trim();
175
+ if (!argsString || argsString === "") {
176
+ // Tool without parameters, use empty object
177
+ toolArgs = {};
178
+ }
179
+ else {
180
+ try {
181
+ toolArgs = JSON.parse(argsString);
182
+ }
183
+ catch (parseError) {
184
+ // For non-empty but malformed JSON, still throw exception
185
+ const errorMessage = `Failed to parse tool arguments: ${argsString}`;
186
+ this.logger?.error(errorMessage, parseError);
187
+ throw new Error(errorMessage);
188
+ }
189
+ }
190
+ // Set tool start execution state
191
+ const toolName = functionToolCall.function?.name || "";
192
+ const compactParams = this.generateCompactParams(toolName, toolArgs);
193
+ this.messageManager.updateToolBlock({
194
+ toolId,
195
+ args: JSON.stringify(toolArgs, null, 2),
196
+ isRunning: true, // isRunning: true
197
+ name: toolName,
198
+ compactParams,
199
+ });
200
+ try {
201
+ // Execute PreToolUse hooks before tool execution
202
+ await this.executePreToolUseHooks(toolName, toolArgs);
203
+ // Create tool execution context
204
+ const context = {
205
+ abortSignal: toolAbortController.signal,
206
+ backgroundBashManager: this.backgroundBashManager,
207
+ workdir: this.workdir,
208
+ };
209
+ // Execute tool
210
+ const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
211
+ // Update message state - tool execution completed
212
+ this.messageManager.updateToolBlock({
213
+ toolId,
214
+ args: JSON.stringify(toolArgs, null, 2),
215
+ result: toolResult.content ||
216
+ (toolResult.error ? `Error: ${toolResult.error}` : ""),
217
+ success: toolResult.success,
218
+ error: toolResult.error,
219
+ isRunning: false, // isRunning: false
220
+ name: toolName,
221
+ shortResult: toolResult.shortResult,
222
+ compactParams,
223
+ });
224
+ // If tool returns diff information, add diff block
225
+ if (toolResult.success &&
226
+ toolResult.diffResult &&
227
+ toolResult.filePath) {
228
+ this.messageManager.addDiffBlock(toolResult.filePath, toolResult.diffResult);
229
+ }
230
+ // Execute PostToolUse hooks after successful tool completion
231
+ await this.executePostToolUseHooks(toolName, toolArgs, toolResult);
232
+ }
233
+ catch (toolError) {
234
+ const errorMessage = toolError instanceof Error
235
+ ? toolError.message
236
+ : String(toolError);
237
+ this.messageManager.updateToolBlock({
238
+ toolId,
239
+ args: JSON.stringify(toolArgs, null, 2),
240
+ result: `Tool execution failed: ${errorMessage}`,
241
+ success: false,
242
+ error: errorMessage,
243
+ isRunning: false,
244
+ name: toolName,
245
+ compactParams,
246
+ });
247
+ }
248
+ }
249
+ catch (parseError) {
250
+ // Check if it's a parsing error due to interruption
251
+ const isAborted = abortController.signal.aborted ||
252
+ toolAbortController.signal.aborted;
253
+ if (isAborted) {
254
+ // If interrupted, return directly without showing error
255
+ return;
256
+ }
257
+ const errorMessage = parseError instanceof Error
258
+ ? parseError.message
259
+ : String(parseError);
260
+ this.messageManager.addErrorBlock(`Failed to parse tool arguments for ${functionToolCall.function?.name}: ${errorMessage}`);
261
+ }
262
+ }
263
+ }
264
+ // Handle token statistics and message compression
265
+ await this.handleTokenUsageAndCompression(result.usage, abortController);
266
+ // Check if there are tool operations, if so automatically initiate next AI service call
267
+ if (toolCalls.length > 0) {
268
+ // Check interruption status
269
+ const isCurrentlyAborted = abortController.signal.aborted || toolAbortController.signal.aborted;
270
+ // AI service call ends, clear abort controller
271
+ this.abortController = null;
272
+ // Clear tool AbortController after tool execution completes
273
+ this.toolAbortController = null;
274
+ if (!isCurrentlyAborted) {
275
+ // Recursively call AI service, increment recursion depth, and pass same configuration
276
+ await this.sendAIMessage({
277
+ recursionDepth: recursionDepth + 1,
278
+ model,
279
+ allowedTools,
280
+ });
281
+ }
282
+ }
283
+ else {
284
+ // Clear abort controller when no tool operations
285
+ this.abortController = null;
286
+ this.toolAbortController = null;
287
+ }
288
+ }
289
+ catch (error) {
290
+ // Check if error is due to user interrupt operation
291
+ const isAborted = abortController.signal.aborted ||
292
+ toolAbortController.signal.aborted ||
293
+ (error instanceof Error &&
294
+ (error.name === "AbortError" || error.message.includes("aborted")));
295
+ if (!isAborted) {
296
+ this.messageManager.addErrorBlock(error instanceof Error ? error.message : "Unknown error occurred");
297
+ }
298
+ // Reset abort controller on error
299
+ this.abortController = null;
300
+ this.toolAbortController = null;
301
+ }
302
+ finally {
303
+ // Only clear loading state for the initial call
304
+ if (recursionDepth === 0) {
305
+ this.setIsLoading(false);
306
+ // Save session before executing Stop hooks
307
+ await this.messageManager.saveSession();
308
+ // Execute Stop hooks when AI response cycle completes
309
+ await this.executeStopHooks();
310
+ }
311
+ }
312
+ }
313
+ /**
314
+ * Execute Stop hooks when AI response cycle completes
315
+ */
316
+ async executeStopHooks() {
317
+ if (!this.hookManager)
318
+ return;
319
+ try {
320
+ const context = {
321
+ event: "Stop",
322
+ projectDir: this.workdir,
323
+ timestamp: new Date(),
324
+ sessionId: this.messageManager.getSessionId(),
325
+ transcriptPath: this.messageManager.getTranscriptPath(),
326
+ cwd: this.workdir,
327
+ // Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
328
+ };
329
+ const results = await this.hookManager.executeHooks("Stop", context);
330
+ // Log hook execution results for debugging
331
+ if (results.length > 0) {
332
+ this.logger?.debug(`Executed ${results.length} Stop hook(s):`, results.map((r) => ({
333
+ success: r.success,
334
+ duration: r.duration,
335
+ exitCode: r.exitCode,
336
+ timedOut: r.timedOut,
337
+ stderr: r.stderr,
338
+ })));
339
+ }
340
+ }
341
+ catch (error) {
342
+ // Hook execution errors should not interrupt the main workflow
343
+ this.logger?.error("Stop hook execution failed:", error);
344
+ }
345
+ }
346
+ /**
347
+ * Execute PreToolUse hooks before tool execution
348
+ */
349
+ async executePreToolUseHooks(toolName, toolInput) {
350
+ if (!this.hookManager)
351
+ return;
352
+ try {
353
+ const context = {
354
+ event: "PreToolUse",
355
+ projectDir: this.workdir,
356
+ timestamp: new Date(),
357
+ toolName,
358
+ sessionId: this.messageManager.getSessionId(),
359
+ transcriptPath: this.messageManager.getTranscriptPath(),
360
+ cwd: this.workdir,
361
+ toolInput,
362
+ };
363
+ const results = await this.hookManager.executeHooks("PreToolUse", context);
364
+ // Log hook execution results for debugging
365
+ if (results.length > 0) {
366
+ this.logger?.debug(`Executed ${results.length} PreToolUse hook(s) for ${toolName}:`, results.map((r) => ({
367
+ success: r.success,
368
+ duration: r.duration,
369
+ exitCode: r.exitCode,
370
+ timedOut: r.timedOut,
371
+ stderr: r.stderr,
372
+ })));
373
+ }
374
+ }
375
+ catch (error) {
376
+ // Hook execution errors should not interrupt the main workflow
377
+ this.logger?.error("PreToolUse hook execution failed:", error);
378
+ }
379
+ }
380
+ /**
381
+ * Execute PostToolUse hooks after tool completion
382
+ */
383
+ async executePostToolUseHooks(toolName, toolInput, toolResponse) {
384
+ if (!this.hookManager)
385
+ return;
386
+ try {
387
+ const context = {
388
+ event: "PostToolUse",
389
+ projectDir: this.workdir,
390
+ timestamp: new Date(),
391
+ toolName,
392
+ sessionId: this.messageManager.getSessionId(),
393
+ transcriptPath: this.messageManager.getTranscriptPath(),
394
+ cwd: this.workdir,
395
+ toolInput,
396
+ toolResponse,
397
+ };
398
+ const results = await this.hookManager.executeHooks("PostToolUse", context);
399
+ // Log hook execution results for debugging
400
+ if (results.length > 0) {
401
+ this.logger?.debug(`Executed ${results.length} PostToolUse hook(s) for ${toolName}:`, results.map((r) => ({
402
+ success: r.success,
403
+ duration: r.duration,
404
+ exitCode: r.exitCode,
405
+ timedOut: r.timedOut,
406
+ stderr: r.stderr,
407
+ })));
408
+ }
409
+ }
410
+ catch (error) {
411
+ // Hook execution errors should not interrupt the main workflow
412
+ this.logger?.error("PostToolUse hook execution failed:", error);
413
+ }
414
+ }
415
+ }
@@ -0,0 +1,27 @@
1
+ import type { BackgroundShell } from "../types.js";
2
+ export interface BackgroundBashManagerCallbacks {
3
+ onShellsChange?: (shells: BackgroundShell[]) => void;
4
+ }
5
+ export interface BackgroundBashManagerOptions {
6
+ callbacks?: BackgroundBashManagerCallbacks;
7
+ workdir: string;
8
+ }
9
+ export declare class BackgroundBashManager {
10
+ private shells;
11
+ private nextId;
12
+ private callbacks;
13
+ private workdir;
14
+ constructor(options: BackgroundBashManagerOptions);
15
+ private notifyShellsChange;
16
+ startShell(command: string, timeout?: number): string;
17
+ getShell(id: string): BackgroundShell | undefined;
18
+ getAllShells(): BackgroundShell[];
19
+ getOutput(id: string, filter?: string): {
20
+ stdout: string;
21
+ stderr: string;
22
+ status: string;
23
+ } | null;
24
+ killShell(id: string): boolean;
25
+ cleanup(): void;
26
+ }
27
+ //# sourceMappingURL=backgroundBashManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgroundBashManager.d.ts","sourceRoot":"","sources":["../../src/managers/backgroundBashManager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,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"}
@@ -0,0 +1,166 @@
1
+ import { spawn } from "child_process";
2
+ export class BackgroundBashManager {
3
+ constructor(options) {
4
+ this.shells = new Map();
5
+ this.nextId = 1;
6
+ this.callbacks = options.callbacks || {};
7
+ this.workdir = options.workdir;
8
+ }
9
+ notifyShellsChange() {
10
+ this.callbacks.onShellsChange?.(Array.from(this.shells.values()));
11
+ }
12
+ startShell(command, timeout) {
13
+ const id = `bash_${this.nextId++}`;
14
+ const startTime = Date.now();
15
+ const child = spawn(command, {
16
+ shell: true,
17
+ stdio: "pipe",
18
+ cwd: this.workdir,
19
+ env: {
20
+ ...process.env,
21
+ },
22
+ });
23
+ const shell = {
24
+ id,
25
+ process: child,
26
+ command,
27
+ startTime,
28
+ status: "running",
29
+ stdout: "",
30
+ stderr: "",
31
+ };
32
+ this.shells.set(id, shell);
33
+ this.notifyShellsChange();
34
+ // Set up timeout if specified
35
+ let timeoutHandle;
36
+ if (timeout && timeout > 0) {
37
+ timeoutHandle = setTimeout(() => {
38
+ if (shell.status === "running") {
39
+ this.killShell(id);
40
+ }
41
+ }, timeout);
42
+ }
43
+ child.stdout?.on("data", (data) => {
44
+ shell.stdout += data.toString();
45
+ this.notifyShellsChange();
46
+ });
47
+ child.stderr?.on("data", (data) => {
48
+ shell.stderr += data.toString();
49
+ this.notifyShellsChange();
50
+ });
51
+ child.on("exit", (code) => {
52
+ if (timeoutHandle) {
53
+ clearTimeout(timeoutHandle);
54
+ }
55
+ shell.status = "completed";
56
+ shell.exitCode = code ?? 0;
57
+ shell.runtime = Date.now() - startTime;
58
+ this.notifyShellsChange();
59
+ });
60
+ child.on("error", (error) => {
61
+ if (timeoutHandle) {
62
+ clearTimeout(timeoutHandle);
63
+ }
64
+ shell.status = "completed";
65
+ shell.stderr += `\nProcess error: ${error.message}`;
66
+ shell.exitCode = 1;
67
+ shell.runtime = Date.now() - startTime;
68
+ this.notifyShellsChange();
69
+ });
70
+ return id;
71
+ }
72
+ getShell(id) {
73
+ return this.shells.get(id);
74
+ }
75
+ getAllShells() {
76
+ return Array.from(this.shells.values());
77
+ }
78
+ getOutput(id, filter) {
79
+ const shell = this.shells.get(id);
80
+ if (!shell) {
81
+ return null;
82
+ }
83
+ let stdout = shell.stdout;
84
+ let stderr = shell.stderr;
85
+ // Apply regex filter if provided
86
+ if (filter) {
87
+ try {
88
+ const regex = new RegExp(filter);
89
+ stdout = stdout
90
+ .split("\n")
91
+ .filter((line) => regex.test(line))
92
+ .join("\n");
93
+ stderr = stderr
94
+ .split("\n")
95
+ .filter((line) => regex.test(line))
96
+ .join("\n");
97
+ }
98
+ catch {
99
+ // logger.warn(`Invalid filter regex: ${filter}`, error);
100
+ }
101
+ }
102
+ return {
103
+ stdout,
104
+ stderr,
105
+ status: shell.status,
106
+ };
107
+ }
108
+ killShell(id) {
109
+ const shell = this.shells.get(id);
110
+ if (!shell || shell.status !== "running") {
111
+ return false;
112
+ }
113
+ try {
114
+ // Try to kill process group first
115
+ if (shell.process.pid) {
116
+ process.kill(-shell.process.pid, "SIGTERM");
117
+ // Force kill after timeout
118
+ setTimeout(() => {
119
+ if (shell.status === "running" &&
120
+ shell.process.pid &&
121
+ !shell.process.killed) {
122
+ try {
123
+ process.kill(-shell.process.pid, "SIGKILL");
124
+ }
125
+ catch {
126
+ // logger.error("Failed to force kill process:", error);
127
+ }
128
+ }
129
+ }, 1000);
130
+ }
131
+ shell.status = "killed";
132
+ shell.runtime = Date.now() - shell.startTime;
133
+ this.notifyShellsChange();
134
+ return true;
135
+ }
136
+ catch {
137
+ // Fallback to direct process kill
138
+ try {
139
+ shell.process.kill("SIGTERM");
140
+ setTimeout(() => {
141
+ if (!shell.process.killed) {
142
+ shell.process.kill("SIGKILL");
143
+ }
144
+ }, 1000);
145
+ shell.status = "killed";
146
+ shell.runtime = Date.now() - shell.startTime;
147
+ this.notifyShellsChange();
148
+ return true;
149
+ }
150
+ catch {
151
+ // logger.error("Failed to kill child process:", directKillError);
152
+ return false;
153
+ }
154
+ }
155
+ }
156
+ cleanup() {
157
+ // Kill all running shells
158
+ for (const [id, shell] of this.shells) {
159
+ if (shell.status === "running") {
160
+ this.killShell(id);
161
+ }
162
+ }
163
+ this.shells.clear();
164
+ this.notifyShellsChange();
165
+ }
166
+ }
@@ -0,0 +1,20 @@
1
+ import type { MessageManager } from "./messageManager.js";
2
+ export interface BashManagerOptions {
3
+ messageManager: MessageManager;
4
+ workdir: string;
5
+ }
6
+ export interface CommandExecutionResult {
7
+ exitCode: number;
8
+ output: string;
9
+ }
10
+ export declare class BashManager {
11
+ private workdir;
12
+ private messageManager;
13
+ isCommandRunning: boolean;
14
+ private currentProcess;
15
+ constructor(options: BashManagerOptions);
16
+ private setCommandRunning;
17
+ executeCommand(command: string): Promise<number>;
18
+ abortCommand(): void;
19
+ }
20
+ //# sourceMappingURL=bashManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bashManager.d.ts","sourceRoot":"","sources":["../../src/managers/bashManager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAiB;IAChC,gBAAgB,UAAS;IAChC,OAAO,CAAC,cAAc,CAA6B;gBAEvC,OAAO,EAAE,kBAAkB;IAKvC,OAAO,CAAC,iBAAiB;IAIZ,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4DtD,YAAY,IAAI,IAAI;CAO5B"}