wave-agent-sdk 0.0.6 → 0.0.8
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.
- package/dist/agent.d.ts +32 -20
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +209 -24
- package/dist/constants/events.d.ts +28 -0
- package/dist/constants/events.d.ts.map +1 -0
- package/dist/constants/events.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/managers/aiManager.d.ts +34 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +248 -132
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +7 -6
- package/dist/managers/hookManager.d.ts +13 -16
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +81 -44
- package/dist/managers/liveConfigManager.d.ts +58 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +160 -0
- package/dist/managers/messageManager.d.ts +41 -24
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +168 -49
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +9 -3
- package/dist/managers/subagentManager.d.ts +51 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +190 -19
- package/dist/services/aiService.d.ts +13 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +350 -74
- package/dist/services/configurationWatcher.d.ts +120 -0
- package/dist/services/configurationWatcher.d.ts.map +1 -0
- package/dist/services/configurationWatcher.js +439 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +213 -0
- package/dist/services/hook.d.ts +91 -9
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +393 -43
- package/dist/services/jsonlHandler.d.ts +62 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +257 -0
- package/dist/services/memory.d.ts +9 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +81 -12
- package/dist/services/memoryStore.d.ts +81 -0
- package/dist/services/memoryStore.d.ts.map +1 -0
- package/dist/services/memoryStore.js +200 -0
- package/dist/services/session.d.ts +64 -49
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +310 -132
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +5 -4
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +2 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +3 -2
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +4 -3
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +2 -1
- package/dist/tools/todoWriteTool.d.ts.map +1 -1
- package/dist/tools/todoWriteTool.js +3 -10
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +5 -6
- package/dist/types/commands.d.ts +4 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/core.d.ts +35 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +42 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/hooks.d.ts +8 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/memoryStore.d.ts +82 -0
- package/dist/types/memoryStore.d.ts.map +1 -0
- package/dist/types/memoryStore.js +7 -0
- package/dist/types/messaging.d.ts +21 -9
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/messaging.js +5 -1
- package/dist/types/session.d.ts +20 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +27 -26
- package/dist/utils/cacheControlUtils.d.ts +121 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +367 -0
- package/dist/utils/commandPathResolver.d.ts +52 -0
- package/dist/utils/commandPathResolver.d.ts.map +1 -0
- package/dist/utils/commandPathResolver.js +145 -0
- package/dist/utils/configPaths.d.ts +85 -0
- package/dist/utils/configPaths.d.ts.map +1 -0
- package/dist/utils/configPaths.js +121 -0
- package/dist/utils/configResolver.d.ts +37 -10
- package/dist/utils/configResolver.d.ts.map +1 -1
- package/dist/utils/configResolver.js +127 -23
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +8 -13
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileUtils.d.ts +15 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +61 -0
- package/dist/utils/globalLogger.d.ts +102 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +136 -0
- package/dist/utils/hookMatcher.d.ts +1 -6
- package/dist/utils/hookMatcher.d.ts.map +1 -1
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +27 -27
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +46 -36
- package/dist/utils/pathEncoder.d.ts +104 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +272 -0
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +2 -1
- package/dist/utils/tokenCalculation.d.ts +26 -0
- package/dist/utils/tokenCalculation.d.ts.map +1 -0
- package/dist/utils/tokenCalculation.js +36 -0
- package/package.json +6 -3
- package/src/agent.ts +301 -37
- package/src/constants/events.ts +38 -0
- package/src/index.ts +2 -0
- package/src/managers/aiManager.ts +325 -173
- package/src/managers/backgroundBashManager.ts +7 -6
- package/src/managers/hookManager.ts +106 -84
- package/src/managers/liveConfigManager.ts +248 -0
- package/src/managers/messageManager.ts +237 -100
- package/src/managers/slashCommandManager.ts +9 -7
- package/src/managers/subagentManager.ts +284 -22
- package/src/services/aiService.ts +474 -83
- package/src/services/configurationWatcher.ts +622 -0
- package/src/services/fileWatcher.ts +301 -0
- package/src/services/hook.ts +538 -47
- package/src/services/jsonlHandler.ts +319 -0
- package/src/services/memory.ts +92 -12
- package/src/services/memoryStore.ts +279 -0
- package/src/services/session.ts +381 -157
- package/src/tools/bashTool.ts +5 -4
- package/src/tools/deleteFileTool.ts +2 -1
- package/src/tools/editTool.ts +3 -2
- package/src/tools/multiEditTool.ts +4 -3
- package/src/tools/readTool.ts +2 -1
- package/src/tools/todoWriteTool.ts +3 -11
- package/src/tools/writeTool.ts +7 -6
- package/src/types/commands.ts +6 -0
- package/src/types/core.ts +44 -0
- package/src/types/environment.ts +60 -0
- package/src/types/hooks.ts +21 -8
- package/src/types/index.ts +2 -0
- package/src/types/memoryStore.ts +94 -0
- package/src/types/messaging.ts +21 -10
- package/src/types/session.ts +25 -0
- package/src/utils/bashHistory.ts +27 -27
- package/src/utils/cacheControlUtils.ts +540 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/configResolver.ts +182 -22
- package/src/utils/constants.ts +1 -1
- package/src/utils/convertMessagesForAPI.ts +8 -14
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileUtils.ts +65 -0
- package/src/utils/globalLogger.ts +145 -0
- package/src/utils/hookMatcher.ts +1 -12
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +77 -60
- package/src/utils/pathEncoder.ts +379 -0
- package/src/utils/subagentParser.ts +2 -1
- package/src/utils/tokenCalculation.ts +43 -0
- package/src/types/index.ts.backup +0 -357
|
@@ -1,6 +1,7 @@
|
|
|
1
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,12 +16,60 @@ 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
|
|
18
20
|
this.callbacks = options.callbacks ?? {};
|
|
19
21
|
// Store resolved configuration
|
|
20
22
|
this.gatewayConfig = options.gatewayConfig;
|
|
21
23
|
this.modelConfig = options.modelConfig;
|
|
22
24
|
this.tokenLimit = options.tokenLimit;
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Update gateway configuration at runtime for live config reload
|
|
28
|
+
* @param newConfig - New gateway configuration
|
|
29
|
+
*/
|
|
30
|
+
updateGatewayConfig(newConfig) {
|
|
31
|
+
this.logger?.info(`Live Config: Updating AIManager gateway config - baseURL: ${newConfig.baseURL}`);
|
|
32
|
+
this.gatewayConfig = newConfig;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Update model configuration at runtime for live config reload
|
|
36
|
+
* @param newConfig - New model configuration
|
|
37
|
+
*/
|
|
38
|
+
updateModelConfig(newConfig) {
|
|
39
|
+
this.logger?.info(`Live Config: Updating AIManager model config - agent: ${newConfig.agentModel}, fast: ${newConfig.fastModel}`);
|
|
40
|
+
this.modelConfig = newConfig;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Update token limit at runtime for live config reload
|
|
44
|
+
* @param newLimit - New token limit
|
|
45
|
+
*/
|
|
46
|
+
updateTokenLimit(newLimit) {
|
|
47
|
+
this.logger?.info(`Live Config: Updating AIManager token limit: ${newLimit}`);
|
|
48
|
+
this.tokenLimit = newLimit;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Update all configurations at once for live config reload
|
|
52
|
+
* @param newGatewayConfig - New gateway configuration
|
|
53
|
+
* @param newModelConfig - New model configuration
|
|
54
|
+
* @param newTokenLimit - New token limit
|
|
55
|
+
*/
|
|
56
|
+
updateConfiguration(newGatewayConfig, newModelConfig, newTokenLimit) {
|
|
57
|
+
this.logger?.info("Live Config: Updating all AIManager configuration");
|
|
58
|
+
this.gatewayConfig = newGatewayConfig;
|
|
59
|
+
this.modelConfig = newModelConfig;
|
|
60
|
+
this.tokenLimit = newTokenLimit;
|
|
61
|
+
this.logger?.info(`Live Config: Configuration updated - model: ${newModelConfig.agentModel}, tokenLimit: ${newTokenLimit}`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get current configuration for debugging
|
|
65
|
+
*/
|
|
66
|
+
getCurrentConfiguration() {
|
|
67
|
+
return {
|
|
68
|
+
gatewayConfig: { ...this.gatewayConfig },
|
|
69
|
+
modelConfig: { ...this.modelConfig },
|
|
70
|
+
tokenLimit: this.tokenLimit,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
24
73
|
/**
|
|
25
74
|
* Get filtered tool configuration
|
|
26
75
|
*/
|
|
@@ -73,22 +122,28 @@ export class AIManager {
|
|
|
73
122
|
catch (error) {
|
|
74
123
|
this.logger?.warn("Failed to generate compactParams", error);
|
|
75
124
|
}
|
|
76
|
-
return
|
|
125
|
+
return "";
|
|
77
126
|
}
|
|
78
127
|
// Private method to handle token statistics and message compression
|
|
79
128
|
async handleTokenUsageAndCompression(usage, abortController) {
|
|
80
129
|
if (!usage)
|
|
81
130
|
return;
|
|
82
|
-
// Update token statistics - display
|
|
83
|
-
|
|
131
|
+
// Update token statistics - display comprehensive token usage including cache tokens
|
|
132
|
+
const comprehensiveTotalTokens = calculateComprehensiveTotalTokens(usage);
|
|
133
|
+
this.messageManager.setlatestTotalTokens(comprehensiveTotalTokens);
|
|
84
134
|
// Check if token limit exceeded - use injected configuration
|
|
85
|
-
if (usage.total_tokens
|
|
135
|
+
if (usage.total_tokens +
|
|
136
|
+
(usage.cache_read_input_tokens || 0) +
|
|
137
|
+
(usage.cache_creation_input_tokens || 0) >
|
|
138
|
+
this.tokenLimit) {
|
|
86
139
|
this.logger?.debug(`Token usage exceeded ${this.tokenLimit}, compressing messages...`);
|
|
87
140
|
// Check if messages need compression
|
|
88
141
|
const { messagesToCompress, insertIndex } = getMessagesToCompress(this.messageManager.getMessages(), 7);
|
|
89
142
|
// If there are messages to compress, perform compression
|
|
90
143
|
if (messagesToCompress.length > 0) {
|
|
91
144
|
const recentChatMessages = convertMessagesForAPI(messagesToCompress);
|
|
145
|
+
// Save session before compression to preserve original messages
|
|
146
|
+
await this.messageManager.saveSession();
|
|
92
147
|
this.setIsCompressing(true);
|
|
93
148
|
try {
|
|
94
149
|
const compressionResult = await compressMessages({
|
|
@@ -97,21 +152,22 @@ export class AIManager {
|
|
|
97
152
|
messages: recentChatMessages,
|
|
98
153
|
abortSignal: abortController.signal,
|
|
99
154
|
});
|
|
100
|
-
// Execute message reconstruction and sessionId update after compression
|
|
101
|
-
this.messageManager.compressMessagesAndUpdateSession(insertIndex, compressionResult.content);
|
|
102
155
|
// Handle usage tracking for compression operations
|
|
156
|
+
let compressionUsage;
|
|
103
157
|
if (compressionResult.usage) {
|
|
104
|
-
|
|
158
|
+
compressionUsage = {
|
|
105
159
|
prompt_tokens: compressionResult.usage.prompt_tokens,
|
|
106
160
|
completion_tokens: compressionResult.usage.completion_tokens,
|
|
107
161
|
total_tokens: compressionResult.usage.total_tokens,
|
|
108
162
|
model: this.modelConfig.fastModel,
|
|
109
163
|
operation_type: "compress",
|
|
110
164
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
165
|
+
}
|
|
166
|
+
// Execute message reconstruction and sessionId update after compression
|
|
167
|
+
this.messageManager.compressMessagesAndUpdateSession(insertIndex, compressionResult.content, compressionUsage);
|
|
168
|
+
// Notify Agent to add to usage tracking
|
|
169
|
+
if (compressionUsage && this.callbacks?.onUsageAdded) {
|
|
170
|
+
this.callbacks.onUsageAdded(compressionUsage);
|
|
115
171
|
}
|
|
116
172
|
this.logger?.debug(`Successfully compressed ${messagesToCompress.length} messages and updated session`);
|
|
117
173
|
}
|
|
@@ -139,6 +195,8 @@ export class AIManager {
|
|
|
139
195
|
if (recursionDepth === 0 && this.isLoading) {
|
|
140
196
|
return;
|
|
141
197
|
}
|
|
198
|
+
// Save session in each recursion to ensure message persistence
|
|
199
|
+
await this.messageManager.saveSession();
|
|
142
200
|
// Only create new AbortControllers for the initial call (recursionDepth === 0)
|
|
143
201
|
// For recursive calls, reuse existing controllers to maintain abort signal
|
|
144
202
|
let abortController;
|
|
@@ -164,7 +222,9 @@ export class AIManager {
|
|
|
164
222
|
try {
|
|
165
223
|
// Get combined memory content
|
|
166
224
|
const combinedMemory = await memory.getCombinedMemoryContent(this.workdir);
|
|
167
|
-
//
|
|
225
|
+
// Add assistant message first (for streaming updates)
|
|
226
|
+
this.messageManager.addAssistantMessage();
|
|
227
|
+
// Call AI service with streaming callbacks
|
|
168
228
|
const result = await callAgent({
|
|
169
229
|
gatewayConfig: this.gatewayConfig,
|
|
170
230
|
modelConfig: this.modelConfig,
|
|
@@ -176,16 +236,39 @@ export class AIManager {
|
|
|
176
236
|
tools: this.getFilteredToolsConfig(allowedTools), // Pass filtered tool configuration
|
|
177
237
|
model: model, // Use passed model
|
|
178
238
|
systemPrompt: this.systemPrompt, // Pass custom system prompt
|
|
239
|
+
// Streaming callbacks
|
|
240
|
+
onContentUpdate: (content) => {
|
|
241
|
+
this.messageManager.updateCurrentMessageContent(content);
|
|
242
|
+
},
|
|
243
|
+
onToolUpdate: (toolCall) => {
|
|
244
|
+
// Use parametersChunk as compact param for better performance
|
|
245
|
+
// No need to extract params or generate compact params during streaming
|
|
246
|
+
this.logger?.debug("Tool streaming update:", toolCall);
|
|
247
|
+
// Update tool block with streaming parameters using parametersChunk as compact param
|
|
248
|
+
this.messageManager.updateToolBlock({
|
|
249
|
+
id: toolCall.id,
|
|
250
|
+
name: toolCall.name,
|
|
251
|
+
parameters: toolCall.parameters,
|
|
252
|
+
parametersChunk: toolCall.parametersChunk,
|
|
253
|
+
compactParams: toolCall.parameters?.split("\n").pop()?.slice(-30),
|
|
254
|
+
stage: toolCall.stage || "streaming", // Default to streaming if stage not provided
|
|
255
|
+
});
|
|
256
|
+
},
|
|
179
257
|
});
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
258
|
+
// Log finish reason and response headers if available
|
|
259
|
+
if (result.finish_reason) {
|
|
260
|
+
this.logger?.debug(`AI response finished with reason: ${result.finish_reason}`);
|
|
261
|
+
}
|
|
262
|
+
if (result.response_headers &&
|
|
263
|
+
Object.keys(result.response_headers).length > 0) {
|
|
264
|
+
this.logger?.debug("AI response headers:", result.response_headers);
|
|
265
|
+
}
|
|
266
|
+
if (result.metadata && Object.keys(result.metadata).length > 0) {
|
|
267
|
+
this.messageManager.mergeAssistantMetadata(result.metadata);
|
|
268
|
+
}
|
|
269
|
+
// Handle result content from non-streaming mode
|
|
270
|
+
if (result.content) {
|
|
271
|
+
this.messageManager.updateCurrentMessageContent(result.content);
|
|
189
272
|
}
|
|
190
273
|
// Handle usage tracking for agent operations
|
|
191
274
|
let usage;
|
|
@@ -196,113 +279,140 @@ export class AIManager {
|
|
|
196
279
|
total_tokens: result.usage.total_tokens,
|
|
197
280
|
model: model || this.modelConfig.agentModel,
|
|
198
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
|
+
}),
|
|
199
292
|
};
|
|
200
293
|
}
|
|
201
|
-
//
|
|
202
|
-
this.messageManager.addAssistantMessage(content, toolCalls, usage);
|
|
203
|
-
// Notify Agent to add to usage tracking
|
|
294
|
+
// Set usage on the assistant message if available
|
|
204
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
|
|
205
303
|
if (this.callbacks?.onUsageAdded) {
|
|
206
304
|
this.callbacks.onUsageAdded(usage);
|
|
207
305
|
}
|
|
208
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
|
+
}
|
|
209
316
|
if (toolCalls.length > 0) {
|
|
210
317
|
// Execute all tools in parallel using Promise.all
|
|
211
318
|
const toolExecutionPromises = toolCalls.map(async (functionToolCall) => {
|
|
212
319
|
const toolId = functionToolCall.id || "";
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
try {
|
|
228
|
-
toolArgs = JSON.parse(argsString);
|
|
229
|
-
}
|
|
230
|
-
catch (parseError) {
|
|
231
|
-
// For non-empty but malformed JSON, still throw exception
|
|
232
|
-
const errorMessage = `Failed to parse tool arguments: ${argsString}`;
|
|
233
|
-
this.logger?.error(errorMessage, parseError);
|
|
234
|
-
throw new Error(errorMessage);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
// Set tool start execution state
|
|
238
|
-
const toolName = functionToolCall.function?.name || "";
|
|
239
|
-
const compactParams = this.generateCompactParams(toolName, toolArgs);
|
|
240
|
-
this.messageManager.updateToolBlock({
|
|
241
|
-
toolId,
|
|
242
|
-
args: JSON.stringify(toolArgs, null, 2),
|
|
243
|
-
isRunning: true, // isRunning: true
|
|
244
|
-
name: toolName,
|
|
245
|
-
compactParams,
|
|
246
|
-
});
|
|
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 {
|
|
247
334
|
try {
|
|
248
|
-
|
|
249
|
-
const shouldExecuteTool = await this.executePreToolUseHooks(toolName, toolArgs, toolId);
|
|
250
|
-
// If PreToolUse hooks blocked execution, skip tool execution
|
|
251
|
-
if (!shouldExecuteTool) {
|
|
252
|
-
this.logger?.info(`Tool ${toolName} execution blocked by PreToolUse hooks`);
|
|
253
|
-
return; // Skip this tool and return from this map function
|
|
254
|
-
}
|
|
255
|
-
// Create tool execution context
|
|
256
|
-
const context = {
|
|
257
|
-
abortSignal: toolAbortController.signal,
|
|
258
|
-
backgroundBashManager: this.backgroundBashManager,
|
|
259
|
-
workdir: this.workdir,
|
|
260
|
-
};
|
|
261
|
-
// Execute tool
|
|
262
|
-
const toolResult = await this.toolManager.execute(functionToolCall.function?.name || "", toolArgs, context);
|
|
263
|
-
// Update message state - tool execution completed
|
|
264
|
-
this.messageManager.updateToolBlock({
|
|
265
|
-
toolId,
|
|
266
|
-
args: JSON.stringify(toolArgs, null, 2),
|
|
267
|
-
result: toolResult.content ||
|
|
268
|
-
(toolResult.error ? `Error: ${toolResult.error}` : ""),
|
|
269
|
-
success: toolResult.success,
|
|
270
|
-
error: toolResult.error,
|
|
271
|
-
isRunning: false, // isRunning: false
|
|
272
|
-
name: toolName,
|
|
273
|
-
shortResult: toolResult.shortResult,
|
|
274
|
-
compactParams,
|
|
275
|
-
});
|
|
276
|
-
// If tool returns diff information, add diff block
|
|
277
|
-
if (toolResult.success &&
|
|
278
|
-
toolResult.diffResult &&
|
|
279
|
-
toolResult.filePath) {
|
|
280
|
-
this.messageManager.addDiffBlock(toolResult.filePath, toolResult.diffResult);
|
|
281
|
-
}
|
|
282
|
-
// Execute PostToolUse hooks after successful tool completion
|
|
283
|
-
await this.executePostToolUseHooks(toolId, toolName, toolArgs, toolResult);
|
|
335
|
+
toolArgs = JSON.parse(argsString);
|
|
284
336
|
}
|
|
285
|
-
catch (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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);
|
|
289
342
|
this.messageManager.updateToolBlock({
|
|
290
|
-
toolId,
|
|
291
|
-
|
|
292
|
-
result:
|
|
343
|
+
id: toolId,
|
|
344
|
+
parameters: argsString,
|
|
345
|
+
result: errorMessage,
|
|
293
346
|
success: false,
|
|
294
|
-
error:
|
|
295
|
-
|
|
347
|
+
error: fullErrorMessage,
|
|
348
|
+
stage: "end",
|
|
296
349
|
name: toolName,
|
|
297
|
-
compactParams,
|
|
350
|
+
compactParams: "",
|
|
298
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
|
|
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
|
+
// If tool returns diff information, add diff block
|
|
394
|
+
if (toolResult.success &&
|
|
395
|
+
toolResult.diffResult &&
|
|
396
|
+
toolResult.filePath) {
|
|
397
|
+
this.messageManager.addDiffBlock(toolResult.filePath, toolResult.diffResult);
|
|
299
398
|
}
|
|
399
|
+
// Execute PostToolUse hooks after successful tool completion
|
|
400
|
+
await this.executePostToolUseHooks(toolId, toolName, toolArgs, toolResult);
|
|
300
401
|
}
|
|
301
|
-
catch (
|
|
302
|
-
const errorMessage =
|
|
303
|
-
?
|
|
304
|
-
: String(
|
|
305
|
-
this.messageManager.
|
|
402
|
+
catch (toolError) {
|
|
403
|
+
const errorMessage = toolError instanceof Error
|
|
404
|
+
? toolError.message
|
|
405
|
+
: String(toolError);
|
|
406
|
+
this.messageManager.updateToolBlock({
|
|
407
|
+
id: toolId,
|
|
408
|
+
parameters: JSON.stringify(toolArgs, null, 2),
|
|
409
|
+
result: `Tool execution failed: ${errorMessage}`,
|
|
410
|
+
success: false,
|
|
411
|
+
error: errorMessage,
|
|
412
|
+
stage: "end",
|
|
413
|
+
name: toolName,
|
|
414
|
+
compactParams,
|
|
415
|
+
});
|
|
306
416
|
}
|
|
307
417
|
});
|
|
308
418
|
// Wait for all tools to complete execution in parallel
|
|
@@ -328,66 +438,70 @@ export class AIManager {
|
|
|
328
438
|
this.messageManager.addErrorBlock(error instanceof Error ? error.message : "Unknown error occurred");
|
|
329
439
|
}
|
|
330
440
|
finally {
|
|
331
|
-
// Only execute
|
|
441
|
+
// Only execute cleanup and hooks for the initial call
|
|
332
442
|
if (recursionDepth === 0) {
|
|
333
|
-
//
|
|
443
|
+
// Save session in each recursion to ensure message persistence
|
|
444
|
+
await this.messageManager.saveSession();
|
|
445
|
+
// Set loading to false first
|
|
446
|
+
this.setIsLoading(false);
|
|
447
|
+
// Clear abort controllers
|
|
448
|
+
this.abortController = null;
|
|
449
|
+
this.toolAbortController = null;
|
|
450
|
+
// Execute Stop/SubagentStop hooks only if the operation was not aborted
|
|
334
451
|
const isCurrentlyAborted = abortController.signal.aborted || toolAbortController.signal.aborted;
|
|
335
452
|
if (!isCurrentlyAborted) {
|
|
336
453
|
const shouldContinue = await this.executeStopHooks();
|
|
337
|
-
// If Stop hooks indicate we should continue (due to blocking errors),
|
|
454
|
+
// If Stop/SubagentStop hooks indicate we should continue (due to blocking errors),
|
|
338
455
|
// restart the AI conversation cycle
|
|
339
456
|
if (shouldContinue) {
|
|
340
|
-
this.logger?.info("Stop hooks indicate issues need fixing, continuing conversation
|
|
457
|
+
this.logger?.info(`${this.subagentType ? "SubagentStop" : "Stop"} hooks indicate issues need fixing, continuing conversation...`);
|
|
341
458
|
// Restart the conversation to let AI fix the issues
|
|
342
|
-
// Use recursionDepth =
|
|
459
|
+
// Use recursionDepth = 0 to set loading false again for continuation
|
|
343
460
|
await this.sendAIMessage({
|
|
344
|
-
recursionDepth:
|
|
461
|
+
recursionDepth: 0,
|
|
345
462
|
model,
|
|
346
463
|
allowedTools,
|
|
347
464
|
});
|
|
348
465
|
}
|
|
349
466
|
}
|
|
350
|
-
// Save session after all operations (including continuation) are complete
|
|
351
|
-
await this.messageManager.saveSession();
|
|
352
|
-
// Clear abort controllers and loading state after all operations are complete
|
|
353
|
-
this.abortController = null;
|
|
354
|
-
this.toolAbortController = null;
|
|
355
|
-
// Set loading to false at the very end, after all operations including continuation
|
|
356
|
-
this.setIsLoading(false);
|
|
357
467
|
}
|
|
358
468
|
}
|
|
359
469
|
}
|
|
360
470
|
/**
|
|
361
|
-
* Execute Stop hooks when AI response cycle completes
|
|
471
|
+
* Execute Stop or SubagentStop hooks when AI response cycle completes
|
|
472
|
+
* Uses "SubagentStop" hook name when triggered by a subagent, otherwise uses "Stop"
|
|
362
473
|
* @returns Promise<boolean> - true if should continue conversation, false if should stop
|
|
363
474
|
*/
|
|
364
475
|
async executeStopHooks() {
|
|
365
476
|
if (!this.hookManager)
|
|
366
477
|
return false;
|
|
367
478
|
try {
|
|
479
|
+
// Use "SubagentStop" hook name when triggered by a subagent, otherwise use "Stop"
|
|
480
|
+
const hookName = this.subagentType ? "SubagentStop" : "Stop";
|
|
368
481
|
const context = {
|
|
369
|
-
event:
|
|
482
|
+
event: hookName,
|
|
370
483
|
projectDir: this.workdir,
|
|
371
484
|
timestamp: new Date(),
|
|
372
485
|
sessionId: this.messageManager.getSessionId(),
|
|
373
486
|
transcriptPath: this.messageManager.getTranscriptPath(),
|
|
374
487
|
cwd: this.workdir,
|
|
488
|
+
subagentType: this.subagentType, // Include subagent type in hook context
|
|
375
489
|
// Stop hooks don't need toolName, toolInput, toolResponse, or userPrompt
|
|
376
490
|
};
|
|
377
|
-
const results = await this.hookManager.executeHooks(
|
|
491
|
+
const results = await this.hookManager.executeHooks(hookName, context);
|
|
378
492
|
// Process hook results to handle exit codes and appropriate responses
|
|
379
493
|
let shouldContinue = false;
|
|
380
494
|
if (results.length > 0) {
|
|
381
|
-
const processResult = this.hookManager.processHookResults(
|
|
495
|
+
const processResult = this.hookManager.processHookResults(hookName, results, this.messageManager);
|
|
382
496
|
// If hook processing indicates we should block (exit code 2), continue conversation
|
|
383
497
|
if (processResult.shouldBlock) {
|
|
384
|
-
this.logger?.info(
|
|
498
|
+
this.logger?.info(`${hookName} hook blocked stopping with error:`, processResult.errorMessage);
|
|
385
499
|
shouldContinue = true;
|
|
386
500
|
}
|
|
387
501
|
}
|
|
388
502
|
// Log hook execution results for debugging
|
|
389
503
|
if (results.length > 0) {
|
|
390
|
-
this.logger?.debug(`Executed ${results.length}
|
|
504
|
+
this.logger?.debug(`Executed ${results.length} ${hookName} hook(s):`, results.map((r) => ({
|
|
391
505
|
success: r.success,
|
|
392
506
|
duration: r.duration,
|
|
393
507
|
exitCode: r.exitCode,
|
|
@@ -399,7 +513,7 @@ export class AIManager {
|
|
|
399
513
|
}
|
|
400
514
|
catch (error) {
|
|
401
515
|
// Hook execution errors should not interrupt the main workflow
|
|
402
|
-
this.logger?.error("Stop hook execution failed
|
|
516
|
+
this.logger?.error(`${this.subagentType ? "SubagentStop" : "Stop"} hook execution failed:`, error);
|
|
403
517
|
return false;
|
|
404
518
|
}
|
|
405
519
|
}
|
|
@@ -420,12 +534,14 @@ export class AIManager {
|
|
|
420
534
|
transcriptPath: this.messageManager.getTranscriptPath(),
|
|
421
535
|
cwd: this.workdir,
|
|
422
536
|
toolInput,
|
|
537
|
+
subagentType: this.subagentType, // Include subagent type in hook context
|
|
423
538
|
};
|
|
424
539
|
const results = await this.hookManager.executeHooks("PreToolUse", context);
|
|
425
540
|
// Process hook results to handle exit codes and determine if tool should be blocked
|
|
426
541
|
let shouldContinue = true;
|
|
427
542
|
if (results.length > 0) {
|
|
428
|
-
const processResult = this.hookManager.processHookResults("PreToolUse", results, this.messageManager, toolId
|
|
543
|
+
const processResult = this.hookManager.processHookResults("PreToolUse", results, this.messageManager, toolId, // Pass toolId for proper PreToolUse blocking error handling
|
|
544
|
+
JSON.stringify(toolInput || {}, null, 2));
|
|
429
545
|
shouldContinue = !processResult.shouldBlock;
|
|
430
546
|
}
|
|
431
547
|
// Log hook execution results for debugging
|
|
@@ -463,12 +579,12 @@ export class AIManager {
|
|
|
463
579
|
cwd: this.workdir,
|
|
464
580
|
toolInput,
|
|
465
581
|
toolResponse,
|
|
582
|
+
subagentType: this.subagentType, // Include subagent type in hook context
|
|
466
583
|
};
|
|
467
584
|
const results = await this.hookManager.executeHooks("PostToolUse", context);
|
|
468
585
|
// Process hook results to handle exit codes and update tool results
|
|
469
586
|
if (results.length > 0) {
|
|
470
|
-
|
|
471
|
-
this.hookManager.processHookResults("PostToolUse", results, this.messageManager, toolId, originalToolResult);
|
|
587
|
+
this.hookManager.processHookResults("PostToolUse", results, this.messageManager, toolId);
|
|
472
588
|
}
|
|
473
589
|
// Log hook execution results for debugging
|
|
474
590
|
if (results.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backgroundBashManager.d.ts","sourceRoot":"","sources":["../../src/managers/backgroundBashManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backgroundBashManager.d.ts","sourceRoot":"","sources":["../../src/managers/backgroundBashManager.ts"],"names":[],"mappings":"AAEA,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,5 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
+
import { logger } from "../utils/globalLogger.js";
|
|
2
3
|
export class BackgroundBashManager {
|
|
3
4
|
constructor(options) {
|
|
4
5
|
this.shells = new Map();
|
|
@@ -95,8 +96,8 @@ export class BackgroundBashManager {
|
|
|
95
96
|
.filter((line) => regex.test(line))
|
|
96
97
|
.join("\n");
|
|
97
98
|
}
|
|
98
|
-
catch {
|
|
99
|
-
|
|
99
|
+
catch (error) {
|
|
100
|
+
logger.warn(`Invalid filter regex: ${filter}`, error);
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
return {
|
|
@@ -122,8 +123,8 @@ export class BackgroundBashManager {
|
|
|
122
123
|
try {
|
|
123
124
|
process.kill(-shell.process.pid, "SIGKILL");
|
|
124
125
|
}
|
|
125
|
-
catch {
|
|
126
|
-
|
|
126
|
+
catch (error) {
|
|
127
|
+
logger.error("Failed to force kill process:", error);
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
}, 1000);
|
|
@@ -147,8 +148,8 @@ export class BackgroundBashManager {
|
|
|
147
148
|
this.notifyShellsChange();
|
|
148
149
|
return true;
|
|
149
150
|
}
|
|
150
|
-
catch {
|
|
151
|
-
|
|
151
|
+
catch (directKillError) {
|
|
152
|
+
logger.error("Failed to kill child process:", directKillError);
|
|
152
153
|
return false;
|
|
153
154
|
}
|
|
154
155
|
}
|