open-agent-sdk 0.1.0-alpha.0
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/LICENSE +21 -0
- package/README.md +284 -0
- package/README.zh.md +285 -0
- package/dist/agent/agent-definition.d.ts +107 -0
- package/dist/agent/agent-definition.d.ts.map +1 -0
- package/dist/agent/agent-definition.js +90 -0
- package/dist/agent/agent-definition.js.map +1 -0
- package/dist/agent/react-loop.d.ts +117 -0
- package/dist/agent/react-loop.d.ts.map +1 -0
- package/dist/agent/react-loop.js +674 -0
- package/dist/agent/react-loop.js.map +1 -0
- package/dist/agent/subagent-runner.d.ts +67 -0
- package/dist/agent/subagent-runner.d.ts.map +1 -0
- package/dist/agent/subagent-runner.js +168 -0
- package/dist/agent/subagent-runner.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/inputs.d.ts +56 -0
- package/dist/hooks/inputs.d.ts.map +1 -0
- package/dist/hooks/inputs.js +150 -0
- package/dist/hooks/inputs.js.map +1 -0
- package/dist/hooks/manager.d.ts +63 -0
- package/dist/hooks/manager.d.ts.map +1 -0
- package/dist/hooks/manager.js +137 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/types.d.ts +191 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +6 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/errors.d.ts +26 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +43 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +13 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +50 -0
- package/dist/mcp/manager.d.ts.map +1 -0
- package/dist/mcp/manager.js +170 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/mcp/server-registry.d.ts +48 -0
- package/dist/mcp/server-registry.d.ts.map +1 -0
- package/dist/mcp/server-registry.js +121 -0
- package/dist/mcp/server-registry.js.map +1 -0
- package/dist/mcp/tool-adapter.d.ts +42 -0
- package/dist/mcp/tool-adapter.d.ts.map +1 -0
- package/dist/mcp/tool-adapter.js +89 -0
- package/dist/mcp/tool-adapter.js.map +1 -0
- package/dist/mcp/types.d.ts +74 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +21 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/permissions/index.d.ts +3 -0
- package/dist/permissions/index.d.ts.map +1 -0
- package/dist/permissions/index.js +4 -0
- package/dist/permissions/index.js.map +1 -0
- package/dist/permissions/manager.d.ts +40 -0
- package/dist/permissions/manager.d.ts.map +1 -0
- package/dist/permissions/manager.js +115 -0
- package/dist/permissions/manager.js.map +1 -0
- package/dist/permissions/types.d.ts +124 -0
- package/dist/permissions/types.d.ts.map +1 -0
- package/dist/permissions/types.js +25 -0
- package/dist/permissions/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +18 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +126 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/base.d.ts +85 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +36 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/google.d.ts +12 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +123 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/openai.d.ts +12 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +110 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/session/factory.d.ts +156 -0
- package/dist/session/factory.d.ts.map +1 -0
- package/dist/session/factory.js +311 -0
- package/dist/session/factory.js.map +1 -0
- package/dist/session/index.d.ts +8 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +7 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/session.d.ts +144 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +319 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/storage.d.ts +105 -0
- package/dist/session/storage.d.ts.map +1 -0
- package/dist/session/storage.js +148 -0
- package/dist/session/storage.js.map +1 -0
- package/dist/tools/ask-user-question.d.ts +31 -0
- package/dist/tools/ask-user-question.d.ts.map +1 -0
- package/dist/tools/ask-user-question.js +66 -0
- package/dist/tools/ask-user-question.js.map +1 -0
- package/dist/tools/bash-output.d.ts +22 -0
- package/dist/tools/bash-output.d.ts.map +1 -0
- package/dist/tools/bash-output.js +43 -0
- package/dist/tools/bash-output.js.map +1 -0
- package/dist/tools/bash.d.ts +36 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +161 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +24 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +83 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/glob.d.ts +22 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +248 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +312 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/kill-bash.d.ts +19 -0
- package/dist/tools/kill-bash.d.ts.map +1 -0
- package/dist/tools/kill-bash.js +64 -0
- package/dist/tools/kill-bash.js.map +1 -0
- package/dist/tools/read.d.ts +26 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +87 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/registry.d.ts +32 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +91 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/task-create.d.ts +22 -0
- package/dist/tools/task-create.d.ts.map +1 -0
- package/dist/tools/task-create.js +42 -0
- package/dist/tools/task-create.js.map +1 -0
- package/dist/tools/task-get.d.ts +19 -0
- package/dist/tools/task-get.d.ts.map +1 -0
- package/dist/tools/task-get.js +38 -0
- package/dist/tools/task-get.js.map +1 -0
- package/dist/tools/task-list.d.ts +18 -0
- package/dist/tools/task-list.d.ts.map +1 -0
- package/dist/tools/task-list.js +27 -0
- package/dist/tools/task-list.js.map +1 -0
- package/dist/tools/task-storage.d.ts +6 -0
- package/dist/tools/task-storage.d.ts.map +1 -0
- package/dist/tools/task-storage.js +83 -0
- package/dist/tools/task-storage.js.map +1 -0
- package/dist/tools/task-update.d.ts +28 -0
- package/dist/tools/task-update.d.ts.map +1 -0
- package/dist/tools/task-update.js +118 -0
- package/dist/tools/task-update.js.map +1 -0
- package/dist/tools/task.d.ts +80 -0
- package/dist/tools/task.d.ts.map +1 -0
- package/dist/tools/task.js +99 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/web-fetch.d.ts +21 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +124 -0
- package/dist/tools/web-fetch.js.map +1 -0
- package/dist/tools/web-search.d.ts +20 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +127 -0
- package/dist/tools/web-search.js.map +1 -0
- package/dist/tools/write.d.ts +22 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +46 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/types/messages.d.ts +138 -0
- package/dist/types/messages.d.ts.map +1 -0
- package/dist/types/messages.js +88 -0
- package/dist/types/messages.js.map +1 -0
- package/dist/types/task.d.ts +29 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +5 -0
- package/dist/types/task.js.map +1 -0
- package/dist/types/tools.d.ts +56 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +25 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/uuid.d.ts +3 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +5 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReAct (Reasoning + Acting) loop implementation
|
|
3
|
+
* Core agent logic for tool use and reasoning
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from '../utils/logger';
|
|
6
|
+
import { generateUUID } from '../utils/uuid';
|
|
7
|
+
import { createUserMessage, createSystemMessage, createAssistantMessage, createToolResultMessage, createCompactBoundaryMessage, } from '../types/messages';
|
|
8
|
+
import { HookManager } from '../hooks/manager';
|
|
9
|
+
import { createPreToolUseInput, createPostToolUseInput, createSessionStartInput, createSessionEndInput, createPermissionRequestInput, createPostToolUseFailureInput, createUserPromptSubmitInput, createStopInput, createPreCompactInput, } from '../hooks/inputs';
|
|
10
|
+
import { PermissionManager } from '../permissions/manager';
|
|
11
|
+
export class ReActLoop {
|
|
12
|
+
provider;
|
|
13
|
+
toolRegistry;
|
|
14
|
+
config;
|
|
15
|
+
sessionId;
|
|
16
|
+
hookManager;
|
|
17
|
+
permissionManager;
|
|
18
|
+
constructor(provider, toolRegistry, config, sessionId) {
|
|
19
|
+
this.provider = provider;
|
|
20
|
+
this.toolRegistry = toolRegistry;
|
|
21
|
+
this.config = {
|
|
22
|
+
maxTurns: config.maxTurns,
|
|
23
|
+
systemPrompt: config.systemPrompt,
|
|
24
|
+
allowedTools: config.allowedTools,
|
|
25
|
+
cwd: config.cwd ?? process.cwd(),
|
|
26
|
+
env: config.env ?? {},
|
|
27
|
+
abortController: config.abortController,
|
|
28
|
+
permissionMode: config.permissionMode,
|
|
29
|
+
allowDangerouslySkipPermissions: config.allowDangerouslySkipPermissions,
|
|
30
|
+
canUseTool: config.canUseTool,
|
|
31
|
+
mcpServers: config.mcpServers,
|
|
32
|
+
hooks: config.hooks,
|
|
33
|
+
};
|
|
34
|
+
this.sessionId = sessionId ?? generateUUID();
|
|
35
|
+
// Initialize HookManager
|
|
36
|
+
if (config.hooks instanceof HookManager) {
|
|
37
|
+
this.hookManager = config.hooks;
|
|
38
|
+
}
|
|
39
|
+
else if (config.hooks) {
|
|
40
|
+
this.hookManager = new HookManager(config.hooks);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.hookManager = new HookManager();
|
|
44
|
+
}
|
|
45
|
+
// Initialize PermissionManager
|
|
46
|
+
this.permissionManager = new PermissionManager({
|
|
47
|
+
mode: config.permissionMode ?? 'default',
|
|
48
|
+
allowDangerouslySkipPermissions: config.allowDangerouslySkipPermissions ?? false,
|
|
49
|
+
canUseTool: config.canUseTool,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the permission manager instance
|
|
54
|
+
* Used for testing and inspection
|
|
55
|
+
*/
|
|
56
|
+
getPermissionManager() {
|
|
57
|
+
return this.permissionManager;
|
|
58
|
+
}
|
|
59
|
+
async run(userPrompt) {
|
|
60
|
+
const messages = [];
|
|
61
|
+
// Add system message metadata if system prompt is configured
|
|
62
|
+
// The actual system prompt content is passed via ChatOptions to the provider
|
|
63
|
+
if (this.config.systemPrompt) {
|
|
64
|
+
messages.push(createSystemMessage(this.provider.getModel(), this.provider.constructor.name.toLowerCase().replace('provider', ''), this.config.allowedTools ?? this.toolRegistry.getAll().map((t) => t.name), this.config.cwd ?? process.cwd(), this.sessionId, generateUUID(), {
|
|
65
|
+
permissionMode: this.config.permissionMode,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
// Add user message
|
|
69
|
+
messages.push(createUserMessage(userPrompt, this.sessionId, generateUUID()));
|
|
70
|
+
// Trigger UserPromptSubmit hook
|
|
71
|
+
const userPromptSubmitInput = createUserPromptSubmitInput(this.sessionId, this.config.cwd ?? process.cwd(), userPrompt);
|
|
72
|
+
await this.hookManager.emit('UserPromptSubmit', userPromptSubmitInput, undefined);
|
|
73
|
+
let turnCount = 0;
|
|
74
|
+
let totalInputTokens = 0;
|
|
75
|
+
let totalOutputTokens = 0;
|
|
76
|
+
// Get allowed tools
|
|
77
|
+
const availableTools = this.config.allowedTools
|
|
78
|
+
? this.toolRegistry.getAllowedTools(this.config.allowedTools)
|
|
79
|
+
: this.toolRegistry.getAll();
|
|
80
|
+
const toolDefinitions = availableTools.map((tool) => ({
|
|
81
|
+
type: 'function',
|
|
82
|
+
function: {
|
|
83
|
+
name: tool.name,
|
|
84
|
+
description: tool.description,
|
|
85
|
+
parameters: tool.parameters,
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
88
|
+
const toolContext = {
|
|
89
|
+
cwd: this.config.cwd,
|
|
90
|
+
env: this.config.env,
|
|
91
|
+
abortController: this.config.abortController,
|
|
92
|
+
provider: this.provider,
|
|
93
|
+
};
|
|
94
|
+
while (turnCount < this.config.maxTurns) {
|
|
95
|
+
// Check for abort
|
|
96
|
+
if (this.config.abortController?.signal.aborted) {
|
|
97
|
+
return {
|
|
98
|
+
result: 'Operation aborted',
|
|
99
|
+
messages,
|
|
100
|
+
turnCount,
|
|
101
|
+
usage: {
|
|
102
|
+
input_tokens: totalInputTokens,
|
|
103
|
+
output_tokens: totalOutputTokens,
|
|
104
|
+
},
|
|
105
|
+
isError: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
turnCount++;
|
|
109
|
+
// Check for abort again after incrementing turn count
|
|
110
|
+
// This prevents race conditions where abort was triggered between the start of the loop and turnCount++
|
|
111
|
+
if (this.config.abortController?.signal.aborted) {
|
|
112
|
+
return {
|
|
113
|
+
result: 'Operation aborted',
|
|
114
|
+
messages,
|
|
115
|
+
turnCount,
|
|
116
|
+
usage: {
|
|
117
|
+
input_tokens: totalInputTokens,
|
|
118
|
+
output_tokens: totalOutputTokens,
|
|
119
|
+
},
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Call LLM
|
|
124
|
+
let assistantMessage;
|
|
125
|
+
try {
|
|
126
|
+
assistantMessage = await this.callLLM(messages, toolDefinitions, (tokens) => {
|
|
127
|
+
totalInputTokens += tokens.input;
|
|
128
|
+
totalOutputTokens += tokens.output;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// Handle abort error from provider
|
|
133
|
+
if (error instanceof Error && error.message === 'Operation aborted') {
|
|
134
|
+
return {
|
|
135
|
+
result: 'Operation aborted',
|
|
136
|
+
messages,
|
|
137
|
+
turnCount,
|
|
138
|
+
usage: {
|
|
139
|
+
input_tokens: totalInputTokens,
|
|
140
|
+
output_tokens: totalOutputTokens,
|
|
141
|
+
},
|
|
142
|
+
isError: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
messages.push(assistantMessage);
|
|
148
|
+
// Check for auto-compaction after each LLM call
|
|
149
|
+
if (this.config.autoCompactThreshold !== undefined &&
|
|
150
|
+
totalInputTokens > this.config.autoCompactThreshold) {
|
|
151
|
+
logger.debug('[ReActLoop] Auto-compaction triggered:', {
|
|
152
|
+
threshold: this.config.autoCompactThreshold,
|
|
153
|
+
currentTokens: totalInputTokens,
|
|
154
|
+
});
|
|
155
|
+
const compactResult = await this.compact(messages, 'auto', totalInputTokens);
|
|
156
|
+
if (compactResult.summaryGenerated) {
|
|
157
|
+
// Replace messages with compacted version
|
|
158
|
+
messages.length = 0;
|
|
159
|
+
messages.push(...compactResult.messages);
|
|
160
|
+
logger.debug('[ReActLoop] Auto-compaction completed:', {
|
|
161
|
+
preservedRounds: compactResult.preservedRounds,
|
|
162
|
+
newMessageCount: messages.length,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Check if assistant wants to use tools
|
|
167
|
+
const assistantToolCalls = assistantMessage.message.tool_calls;
|
|
168
|
+
if (assistantToolCalls && assistantToolCalls.length > 0) {
|
|
169
|
+
// Execute tools and add results
|
|
170
|
+
for (const toolCall of assistantToolCalls) {
|
|
171
|
+
const result = await this.executeTool(toolCall, availableTools, toolContext);
|
|
172
|
+
messages.push(createToolResultMessage(toolCall.id, toolCall.function.name, result.content, result.isError, this.sessionId, generateUUID()));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// No tool calls - agent produced final answer
|
|
177
|
+
// Trigger Stop hook — allows hooks to request continuation via { continue: true }
|
|
178
|
+
const shouldContinue = await this.emitStopHook();
|
|
179
|
+
if (shouldContinue) {
|
|
180
|
+
// Hook requested continuation — keep looping
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const textContent = assistantMessage.message.content.find((c) => c.type === 'text');
|
|
184
|
+
return {
|
|
185
|
+
result: textContent?.text ?? '',
|
|
186
|
+
messages,
|
|
187
|
+
turnCount,
|
|
188
|
+
usage: {
|
|
189
|
+
input_tokens: totalInputTokens,
|
|
190
|
+
output_tokens: totalOutputTokens,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Max turns reached
|
|
196
|
+
return {
|
|
197
|
+
result: 'Maximum turns reached without completion',
|
|
198
|
+
messages,
|
|
199
|
+
turnCount,
|
|
200
|
+
usage: {
|
|
201
|
+
input_tokens: totalInputTokens,
|
|
202
|
+
output_tokens: totalOutputTokens,
|
|
203
|
+
},
|
|
204
|
+
isError: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Run the ReAct loop with streaming output
|
|
209
|
+
* Yields events for assistant messages, tool results, usage stats, and completion
|
|
210
|
+
* @param userPrompt - The current user message content
|
|
211
|
+
* @param history - Previous conversation messages (optional)
|
|
212
|
+
*/
|
|
213
|
+
async *runStream(userPrompt, history = []) {
|
|
214
|
+
// Trigger SessionStart hook
|
|
215
|
+
const sessionStartInput = createSessionStartInput(this.sessionId, this.config.cwd ?? process.cwd(), history.length > 0 ? 'resume' : 'startup');
|
|
216
|
+
await this.hookManager.emit('SessionStart', sessionStartInput, undefined);
|
|
217
|
+
// Trigger UserPromptSubmit hook
|
|
218
|
+
const userPromptSubmitInput = createUserPromptSubmitInput(this.sessionId, this.config.cwd ?? process.cwd(), userPrompt);
|
|
219
|
+
await this.hookManager.emit('UserPromptSubmit', userPromptSubmitInput, undefined);
|
|
220
|
+
// Check if history already has a system message (metadata)
|
|
221
|
+
const hasSystemInHistory = history.some((msg) => msg.type === 'system');
|
|
222
|
+
const messages = [
|
|
223
|
+
// Add system message metadata if system prompt is configured and not already in history
|
|
224
|
+
// The actual system prompt content is passed via ChatOptions to the provider
|
|
225
|
+
...(this.config.systemPrompt && !hasSystemInHistory
|
|
226
|
+
? [
|
|
227
|
+
createSystemMessage(this.provider.getModel(), this.provider.constructor.name.toLowerCase().replace('provider', ''), this.config.allowedTools ?? this.toolRegistry.getAll().map((t) => t.name), this.config.cwd ?? process.cwd(), this.sessionId, generateUUID(), {
|
|
228
|
+
permissionMode: this.config.permissionMode,
|
|
229
|
+
}),
|
|
230
|
+
]
|
|
231
|
+
: []),
|
|
232
|
+
// Add history messages
|
|
233
|
+
...history,
|
|
234
|
+
// Add current user message
|
|
235
|
+
createUserMessage(userPrompt, this.sessionId, generateUUID()),
|
|
236
|
+
];
|
|
237
|
+
logger.debug('[ReActLoop] Total messages:', messages.length);
|
|
238
|
+
logger.debug('[ReActLoop] Messages:', JSON.stringify(messages, null, 2));
|
|
239
|
+
let turnCount = 0;
|
|
240
|
+
let totalInputTokens = 0;
|
|
241
|
+
let totalOutputTokens = 0;
|
|
242
|
+
// Get allowed tools
|
|
243
|
+
const availableTools = this.config.allowedTools
|
|
244
|
+
? this.toolRegistry.getAllowedTools(this.config.allowedTools)
|
|
245
|
+
: this.toolRegistry.getAll();
|
|
246
|
+
const toolDefinitions = availableTools.map((tool) => ({
|
|
247
|
+
type: 'function',
|
|
248
|
+
function: {
|
|
249
|
+
name: tool.name,
|
|
250
|
+
description: tool.description,
|
|
251
|
+
parameters: tool.parameters,
|
|
252
|
+
},
|
|
253
|
+
}));
|
|
254
|
+
const toolContext = {
|
|
255
|
+
cwd: this.config.cwd,
|
|
256
|
+
env: this.config.env,
|
|
257
|
+
abortController: this.config.abortController,
|
|
258
|
+
provider: this.provider,
|
|
259
|
+
};
|
|
260
|
+
while (turnCount < this.config.maxTurns) {
|
|
261
|
+
// Check for abort
|
|
262
|
+
if (this.config.abortController?.signal.aborted) {
|
|
263
|
+
yield {
|
|
264
|
+
type: 'done',
|
|
265
|
+
result: 'Operation aborted',
|
|
266
|
+
};
|
|
267
|
+
// Trigger SessionEnd hook on abort
|
|
268
|
+
const sessionEndInput = createSessionEndInput(this.sessionId, this.config.cwd ?? process.cwd(), 'abort');
|
|
269
|
+
await this.hookManager.emit('SessionEnd', sessionEndInput, undefined);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
turnCount++;
|
|
273
|
+
// Call LLM
|
|
274
|
+
let assistantMessage;
|
|
275
|
+
try {
|
|
276
|
+
assistantMessage = await this.callLLM(messages, toolDefinitions, (tokens) => {
|
|
277
|
+
totalInputTokens += tokens.input;
|
|
278
|
+
totalOutputTokens += tokens.output;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
// Handle abort error from provider
|
|
283
|
+
if (error instanceof Error && error.message === 'Operation aborted') {
|
|
284
|
+
yield { type: 'done', result: 'Operation aborted' };
|
|
285
|
+
// Trigger SessionEnd hook on abort
|
|
286
|
+
const sessionEndInput = createSessionEndInput(this.sessionId, this.config.cwd ?? process.cwd(), 'abort');
|
|
287
|
+
await this.hookManager.emit('SessionEnd', sessionEndInput, undefined);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
messages.push(assistantMessage);
|
|
293
|
+
yield { type: 'assistant', message: assistantMessage };
|
|
294
|
+
// Check for auto-compaction after each LLM call
|
|
295
|
+
if (this.config.autoCompactThreshold !== undefined &&
|
|
296
|
+
totalInputTokens > this.config.autoCompactThreshold) {
|
|
297
|
+
logger.debug('[ReActLoop] Auto-compaction triggered in stream:', {
|
|
298
|
+
threshold: this.config.autoCompactThreshold,
|
|
299
|
+
currentTokens: totalInputTokens,
|
|
300
|
+
});
|
|
301
|
+
const compactResult = await this.compact(messages, 'auto', totalInputTokens);
|
|
302
|
+
if (compactResult.summaryGenerated) {
|
|
303
|
+
// Replace messages with compacted version
|
|
304
|
+
messages.length = 0;
|
|
305
|
+
messages.push(...compactResult.messages);
|
|
306
|
+
logger.debug('[ReActLoop] Auto-compaction completed in stream:', {
|
|
307
|
+
preservedRounds: compactResult.preservedRounds,
|
|
308
|
+
newMessageCount: messages.length,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Check if assistant wants to use tools
|
|
313
|
+
const assistantToolCalls = assistantMessage.message.tool_calls;
|
|
314
|
+
if (assistantToolCalls && assistantToolCalls.length > 0) {
|
|
315
|
+
// Execute tools and add results
|
|
316
|
+
for (const toolCall of assistantToolCalls) {
|
|
317
|
+
const result = await this.executeTool(toolCall, availableTools, toolContext);
|
|
318
|
+
const toolResultMessage = createToolResultMessage(toolCall.id, toolCall.function.name, result.content, result.isError, this.sessionId, generateUUID());
|
|
319
|
+
messages.push(toolResultMessage);
|
|
320
|
+
yield { type: 'tool_result', message: toolResultMessage };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
// No tool calls - agent produced final answer
|
|
325
|
+
// Trigger Stop hook — allows hooks to request continuation via { continue: true }
|
|
326
|
+
const shouldContinue = await this.emitStopHook();
|
|
327
|
+
if (shouldContinue) {
|
|
328
|
+
// Hook requested continuation — keep looping
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const textContent = assistantMessage.message.content.find((c) => c.type === 'text');
|
|
332
|
+
const result = textContent?.text ?? '';
|
|
333
|
+
yield { type: 'usage', usage: { input_tokens: totalInputTokens, output_tokens: totalOutputTokens } };
|
|
334
|
+
yield { type: 'done', result };
|
|
335
|
+
// Trigger SessionEnd hook on successful completion
|
|
336
|
+
const sessionEndInput = createSessionEndInput(this.sessionId, this.config.cwd ?? process.cwd(), 'completed');
|
|
337
|
+
await this.hookManager.emit('SessionEnd', sessionEndInput, undefined);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Max turns reached
|
|
342
|
+
yield { type: 'usage', usage: { input_tokens: totalInputTokens, output_tokens: totalOutputTokens } };
|
|
343
|
+
yield { type: 'done', result: 'Maximum turns reached without completion' };
|
|
344
|
+
// Trigger SessionEnd hook
|
|
345
|
+
const sessionEndInput = createSessionEndInput(this.sessionId, this.config.cwd ?? process.cwd(), 'max_turns_reached');
|
|
346
|
+
await this.hookManager.emit('SessionEnd', sessionEndInput, undefined);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get the hook manager instance
|
|
350
|
+
* Used for testing and inspection
|
|
351
|
+
*/
|
|
352
|
+
getHookManager() {
|
|
353
|
+
return this.hookManager;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Compact conversation history to reduce token usage.
|
|
357
|
+
* Generates a summary of older messages and preserves recent rounds.
|
|
358
|
+
*
|
|
359
|
+
* @param messages - Current conversation messages
|
|
360
|
+
* @param trigger - What triggered the compaction ('manual' or 'auto')
|
|
361
|
+
* @param preTokens - Token count before compaction
|
|
362
|
+
* @returns Compacted messages and metadata
|
|
363
|
+
*/
|
|
364
|
+
async compact(messages, trigger, preTokens) {
|
|
365
|
+
const preserveRecentRounds = this.config.preserveRecentRounds ?? 2;
|
|
366
|
+
// Separate system messages from conversation messages
|
|
367
|
+
const systemInitMsg = messages.find((m) => m.type === 'system' && 'subtype' in m && m.subtype === 'init');
|
|
368
|
+
// Get conversation messages (non-system)
|
|
369
|
+
const conversationMessages = messages.filter((m) => m.type !== 'system' || ('subtype' in m && m.subtype !== 'init'));
|
|
370
|
+
// Group messages into rounds (user -> assistant -> optional tool results)
|
|
371
|
+
const rounds = [];
|
|
372
|
+
let currentRound = [];
|
|
373
|
+
for (const msg of conversationMessages) {
|
|
374
|
+
if (msg.type === 'user') {
|
|
375
|
+
// Start a new round
|
|
376
|
+
if (currentRound.length > 0) {
|
|
377
|
+
rounds.push(currentRound);
|
|
378
|
+
}
|
|
379
|
+
currentRound = [msg];
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// Add to current round (assistant or tool_result)
|
|
383
|
+
currentRound.push(msg);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Don't forget the last round
|
|
387
|
+
if (currentRound.length > 0) {
|
|
388
|
+
rounds.push(currentRound);
|
|
389
|
+
}
|
|
390
|
+
// Determine which rounds to preserve and which to summarize
|
|
391
|
+
const totalRounds = rounds.length;
|
|
392
|
+
const roundsToPreserve = Math.min(preserveRecentRounds, totalRounds);
|
|
393
|
+
const roundsToSummarize = totalRounds - roundsToPreserve;
|
|
394
|
+
if (roundsToSummarize <= 0) {
|
|
395
|
+
// Nothing to compact
|
|
396
|
+
return {
|
|
397
|
+
messages,
|
|
398
|
+
preTokens,
|
|
399
|
+
trigger,
|
|
400
|
+
preservedRounds: totalRounds,
|
|
401
|
+
summaryGenerated: false,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
// Trigger PreCompact hook
|
|
405
|
+
const preCompactInput = createPreCompactInput(this.sessionId, this.config.cwd ?? process.cwd(), trigger, null // custom_instructions - can be modified by hooks in future
|
|
406
|
+
);
|
|
407
|
+
const preCompactResults = await this.hookManager.emit('PreCompact', preCompactInput, undefined);
|
|
408
|
+
// Check if any hook blocked the compaction
|
|
409
|
+
for (const result of preCompactResults) {
|
|
410
|
+
if (result && typeof result === 'object' && 'stopReason' in result) {
|
|
411
|
+
const syncResult = result;
|
|
412
|
+
if (syncResult.stopReason) {
|
|
413
|
+
logger.debug('[ReActLoop] PreCompact hook blocked compaction:', syncResult.stopReason);
|
|
414
|
+
return {
|
|
415
|
+
messages,
|
|
416
|
+
preTokens,
|
|
417
|
+
trigger,
|
|
418
|
+
preservedRounds: totalRounds,
|
|
419
|
+
summaryGenerated: false,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
// Messages to summarize (older rounds)
|
|
425
|
+
const messagesToSummarize = rounds.slice(0, roundsToSummarize).flat();
|
|
426
|
+
// Generate summary
|
|
427
|
+
const summary = await this.generateSummary(messagesToSummarize);
|
|
428
|
+
// Create compact boundary message
|
|
429
|
+
const boundaryMessage = createCompactBoundaryMessage(this.sessionId, generateUUID(), trigger, preTokens);
|
|
430
|
+
// Create summary message as an assistant message
|
|
431
|
+
const summaryMessage = createAssistantMessage([{ type: 'text', text: `Summary of previous conversation:\n${summary}` }], this.sessionId, generateUUID());
|
|
432
|
+
// Messages to preserve (recent rounds)
|
|
433
|
+
const preservedMessages = rounds.slice(roundsToSummarize).flat();
|
|
434
|
+
// Build compacted message list
|
|
435
|
+
const compactedMessages = [
|
|
436
|
+
...(systemInitMsg ? [systemInitMsg] : []),
|
|
437
|
+
boundaryMessage,
|
|
438
|
+
summaryMessage,
|
|
439
|
+
...preservedMessages,
|
|
440
|
+
];
|
|
441
|
+
return {
|
|
442
|
+
messages: compactedMessages,
|
|
443
|
+
preTokens,
|
|
444
|
+
trigger,
|
|
445
|
+
preservedRounds: roundsToPreserve,
|
|
446
|
+
summaryGenerated: true,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Generate a summary of conversation messages using the LLM.
|
|
451
|
+
*/
|
|
452
|
+
async generateSummary(messages) {
|
|
453
|
+
const summaryPrompt = `Please summarize the following conversation history, keeping key information:
|
|
454
|
+
1. The user's original request/goal
|
|
455
|
+
2. Major steps completed so far
|
|
456
|
+
3. Important file modifications or code changes
|
|
457
|
+
4. Current pending tasks or unfinished work
|
|
458
|
+
5. Any significant errors and their solutions
|
|
459
|
+
|
|
460
|
+
Conversation history:
|
|
461
|
+
${JSON.stringify(messages, null, 2)}
|
|
462
|
+
|
|
463
|
+
Generate a concise but comprehensive summary.`;
|
|
464
|
+
try {
|
|
465
|
+
// Use the provider to generate summary
|
|
466
|
+
const chatOptions = {
|
|
467
|
+
systemInstruction: 'You are a helpful assistant that summarizes conversations concisely.',
|
|
468
|
+
};
|
|
469
|
+
// Create a minimal message list for summary generation
|
|
470
|
+
const summaryMessages = [
|
|
471
|
+
createUserMessage(summaryPrompt, this.sessionId, generateUUID()),
|
|
472
|
+
];
|
|
473
|
+
const stream = this.provider.chat(summaryMessages, [], this.config.abortController?.signal, chatOptions);
|
|
474
|
+
let summary = '';
|
|
475
|
+
for await (const chunk of stream) {
|
|
476
|
+
if (chunk.type === 'content' && chunk.delta) {
|
|
477
|
+
summary += chunk.delta;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return summary.trim() || 'No summary available.';
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
logger.warn('[ReActLoop] Failed to generate summary:', error);
|
|
484
|
+
return 'Summary generation failed. Continuing with preserved context.';
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Emit the Stop hook and check if any handler requests continuation.
|
|
489
|
+
* Returns true if the loop should continue (hook returned { continue: true }).
|
|
490
|
+
*/
|
|
491
|
+
async emitStopHook() {
|
|
492
|
+
const stopInput = createStopInput(this.sessionId, this.config.cwd ?? process.cwd(), true // stop_hook_active
|
|
493
|
+
);
|
|
494
|
+
const results = await this.hookManager.emit('Stop', stopInput, undefined);
|
|
495
|
+
// Check if any hook result requests continuation
|
|
496
|
+
for (const result of results) {
|
|
497
|
+
if (result && typeof result === 'object' && 'continue' in result) {
|
|
498
|
+
const syncResult = result;
|
|
499
|
+
if (syncResult.continue === true) {
|
|
500
|
+
logger.debug('[ReActLoop] Stop hook requested continuation');
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
async callLLM(messages, tools, onUsage) {
|
|
508
|
+
// Pass system prompt via ChatOptions, not in messages
|
|
509
|
+
const chatOptions = {
|
|
510
|
+
systemInstruction: this.config.systemPrompt,
|
|
511
|
+
};
|
|
512
|
+
const stream = this.provider.chat(messages, tools, this.config.abortController?.signal, chatOptions);
|
|
513
|
+
let content = '';
|
|
514
|
+
const toolCalls = new Map();
|
|
515
|
+
let inputTokens = 0;
|
|
516
|
+
let outputTokens = 0;
|
|
517
|
+
for await (const chunk of stream) {
|
|
518
|
+
switch (chunk.type) {
|
|
519
|
+
case 'content':
|
|
520
|
+
if (chunk.delta) {
|
|
521
|
+
content += chunk.delta;
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
case 'tool_call':
|
|
525
|
+
if (chunk.tool_call) {
|
|
526
|
+
const existing = toolCalls.get(chunk.tool_call.id);
|
|
527
|
+
if (existing) {
|
|
528
|
+
existing.function.arguments += chunk.tool_call.arguments;
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
toolCalls.set(chunk.tool_call.id, {
|
|
532
|
+
id: chunk.tool_call.id,
|
|
533
|
+
type: 'function',
|
|
534
|
+
function: {
|
|
535
|
+
name: chunk.tool_call.name,
|
|
536
|
+
arguments: chunk.tool_call.arguments,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
break;
|
|
542
|
+
case 'usage':
|
|
543
|
+
if (chunk.usage) {
|
|
544
|
+
inputTokens = chunk.usage.input_tokens;
|
|
545
|
+
outputTokens = chunk.usage.output_tokens;
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
case 'error':
|
|
549
|
+
if (chunk.error) {
|
|
550
|
+
throw new Error(chunk.error);
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
onUsage({ input: inputTokens, output: outputTokens });
|
|
556
|
+
const contentBlocks = content
|
|
557
|
+
? [{ type: 'text', text: content }]
|
|
558
|
+
: [];
|
|
559
|
+
return createAssistantMessage(contentBlocks, this.sessionId, generateUUID(), null, toolCalls.size > 0 ? Array.from(toolCalls.values()) : undefined);
|
|
560
|
+
}
|
|
561
|
+
async executeTool(toolCall, availableTools, context) {
|
|
562
|
+
const tool = availableTools.find((t) => t.name === toolCall.function.name);
|
|
563
|
+
if (!tool) {
|
|
564
|
+
return {
|
|
565
|
+
content: `Error: Tool "${toolCall.function.name}" not found`,
|
|
566
|
+
isError: true,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
let args;
|
|
570
|
+
try {
|
|
571
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
572
|
+
}
|
|
573
|
+
catch (error) {
|
|
574
|
+
return {
|
|
575
|
+
content: `Error: Invalid JSON arguments - ${error instanceof Error ? error.message : String(error)}`,
|
|
576
|
+
isError: true,
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const cwd = this.config.cwd ?? process.cwd();
|
|
580
|
+
// Special handling for AskUserQuestion tool
|
|
581
|
+
if (toolCall.function.name === 'AskUserQuestion') {
|
|
582
|
+
// AskUserQuestion requires canUseTool callback
|
|
583
|
+
if (!this.config.canUseTool) {
|
|
584
|
+
return {
|
|
585
|
+
content: 'Error: AskUserQuestion requires a canUseTool callback to be configured. The tool cannot function without user interaction capability.',
|
|
586
|
+
isError: true,
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
// If canUseTool exists, it will be handled by the normal permission flow
|
|
590
|
+
// The canUseTool callback will fill in the answers via updatedInput
|
|
591
|
+
}
|
|
592
|
+
// Trigger PreToolUse hook
|
|
593
|
+
const preToolInput = createPreToolUseInput(this.sessionId, cwd, toolCall.function.name, args);
|
|
594
|
+
const preToolResults = await this.hookManager.emitForTool('PreToolUse', preToolInput, toolCall.function.name, toolCall.id);
|
|
595
|
+
// Check if any PreToolUse hook denied the tool
|
|
596
|
+
const hookDenial = preToolResults.find((r) => r !== null && r !== undefined && typeof r === 'object' && 'hookSpecificOutput' in r &&
|
|
597
|
+
r.hookSpecificOutput !== undefined &&
|
|
598
|
+
r.hookSpecificOutput?.hookEventName === 'PreToolUse' &&
|
|
599
|
+
r.hookSpecificOutput?.permissionDecision === 'deny');
|
|
600
|
+
if (hookDenial) {
|
|
601
|
+
const errorMsg = hookDenial.hookSpecificOutput?.permissionDecisionReason || 'Tool denied by PreToolUse hook';
|
|
602
|
+
// Trigger PermissionRequest hook
|
|
603
|
+
const permissionRequestInput = createPermissionRequestInput(this.sessionId, cwd, toolCall.function.name, args);
|
|
604
|
+
await this.hookManager.emit('PermissionRequest', permissionRequestInput, toolCall.id);
|
|
605
|
+
return {
|
|
606
|
+
content: `Error: ${errorMsg}`,
|
|
607
|
+
isError: true,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
// Apply any input modifications from PreToolUse hooks
|
|
611
|
+
let modifiedInput = args;
|
|
612
|
+
const inputModification = preToolResults.find((r) => r !== null && r !== undefined && typeof r === 'object' && 'hookSpecificOutput' in r &&
|
|
613
|
+
r.hookSpecificOutput !== undefined &&
|
|
614
|
+
r.hookSpecificOutput?.hookEventName === 'PreToolUse' &&
|
|
615
|
+
r.hookSpecificOutput?.updatedInput !== undefined);
|
|
616
|
+
if (inputModification?.hookSpecificOutput?.updatedInput) {
|
|
617
|
+
modifiedInput = inputModification.hookSpecificOutput.updatedInput;
|
|
618
|
+
}
|
|
619
|
+
// Check permissions using PermissionManager
|
|
620
|
+
// For AskUserQuestion, add 60-second timeout
|
|
621
|
+
let permissionResult;
|
|
622
|
+
if (toolCall.function.name === 'AskUserQuestion') {
|
|
623
|
+
const timeoutMs = 60_000;
|
|
624
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('AskUserQuestion timed out after 60 seconds')), timeoutMs));
|
|
625
|
+
try {
|
|
626
|
+
permissionResult = await Promise.race([
|
|
627
|
+
this.permissionManager.checkPermission(toolCall.function.name, modifiedInput, { signal: this.config.abortController?.signal ?? new AbortController().signal }),
|
|
628
|
+
timeoutPromise,
|
|
629
|
+
]);
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
return {
|
|
633
|
+
content: `Error: ${error instanceof Error ? error.message : 'AskUserQuestion timed out'}`,
|
|
634
|
+
isError: true,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
permissionResult = await this.permissionManager.checkPermission(toolCall.function.name, modifiedInput, { signal: this.config.abortController?.signal ?? new AbortController().signal });
|
|
640
|
+
}
|
|
641
|
+
if (!permissionResult.approved) {
|
|
642
|
+
// Trigger PermissionRequest hook on denial
|
|
643
|
+
const permissionRequestInput = createPermissionRequestInput(this.sessionId, cwd, toolCall.function.name, modifiedInput);
|
|
644
|
+
await this.hookManager.emit('PermissionRequest', permissionRequestInput, toolCall.id);
|
|
645
|
+
return {
|
|
646
|
+
content: `Error: ${permissionResult.error || 'Permission denied'}`,
|
|
647
|
+
isError: true,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
// Use modified input from permission check (if any)
|
|
651
|
+
const finalInput = permissionResult.updatedInput ?? modifiedInput;
|
|
652
|
+
try {
|
|
653
|
+
const result = await tool.handler(finalInput, context);
|
|
654
|
+
// Trigger PostToolUse hook
|
|
655
|
+
const postToolInput = createPostToolUseInput(this.sessionId, cwd, toolCall.function.name, finalInput, result);
|
|
656
|
+
await this.hookManager.emitForTool('PostToolUse', postToolInput, toolCall.function.name, toolCall.id);
|
|
657
|
+
return {
|
|
658
|
+
content: JSON.stringify(result),
|
|
659
|
+
isError: false,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
664
|
+
// Trigger PostToolUseFailure hook
|
|
665
|
+
const postToolFailureInput = createPostToolUseFailureInput(this.sessionId, cwd, toolCall.function.name, finalInput, errorMessage);
|
|
666
|
+
await this.hookManager.emit('PostToolUseFailure', postToolFailureInput, toolCall.id);
|
|
667
|
+
return {
|
|
668
|
+
content: `Error: ${errorMessage}`,
|
|
669
|
+
isError: true,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
//# sourceMappingURL=react-loop.js.map
|