fix: 修复工具调用确认后卡死问题
- 工具确认按钮添加 e.stopPropagation() 防止事件冒泡到 overlay - 防止重复绑定 overlay click handler (initialized flag) - 新确认打开时自动取消旧确认,防止竞态条件 - 工具执行添加 30 秒超时保护,防止 IPC 挂起 - Agent Loop 添加 5 分钟全局超时 - 每轮循环检查 abort 信号 - 工具执行失败时输出 console.error 便于调试
This commit is contained in:
@@ -7,10 +7,12 @@ import { getToolIcon, formatToolName } from '../services/tool-registry.js';
|
||||
|
||||
let modalEl: HTMLElement | null = null;
|
||||
let resolveConfirm: ((confirmed: boolean) => void) | null = null;
|
||||
let initialized = false;
|
||||
|
||||
export function initToolConfirmModal(): void {
|
||||
modalEl = document.querySelector('#toolConfirmModal')!;
|
||||
if (!modalEl) return;
|
||||
if (!modalEl || initialized) return;
|
||||
initialized = true;
|
||||
|
||||
modalEl.addEventListener('click', (e) => {
|
||||
if (e.target === modalEl) {
|
||||
@@ -24,10 +26,20 @@ export function showToolConfirm(call: ToolCall): Promise<boolean> {
|
||||
if (!modalEl) {
|
||||
modalEl = document.querySelector('#toolConfirmModal')!;
|
||||
if (!modalEl) { resolve(false); return; }
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
modalEl.addEventListener('click', (e) => {
|
||||
if (e.target === modalEl) cancelConfirm();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有未完成的确认,先取消
|
||||
if (resolveConfirm) {
|
||||
const oldResolve = resolveConfirm;
|
||||
resolveConfirm = null;
|
||||
oldResolve(false);
|
||||
}
|
||||
|
||||
resolveConfirm = resolve;
|
||||
|
||||
@@ -68,11 +80,13 @@ export function showToolConfirm(call: ToolCall): Promise<boolean> {
|
||||
|
||||
modalEl.style.display = '';
|
||||
|
||||
const onConfirm = () => {
|
||||
const onConfirm = (e: Event) => {
|
||||
e.stopPropagation();
|
||||
cleanup();
|
||||
resolveConfirm?.(true);
|
||||
};
|
||||
const onCancel = () => {
|
||||
const onCancel = (e: Event) => {
|
||||
e.stopPropagation();
|
||||
cleanup();
|
||||
resolveConfirm?.(false);
|
||||
};
|
||||
|
||||
@@ -86,10 +86,25 @@ export async function runAgentLoop(
|
||||
let loopCount = 0;
|
||||
const maxLoops = 10;
|
||||
const allToolRecords: ToolCallRecord[] = [];
|
||||
const loopStartTime = Date.now();
|
||||
const MAX_LOOP_TIME = 300000; // 5分钟总超时
|
||||
|
||||
while (loopCount < maxLoops) {
|
||||
loopCount++;
|
||||
|
||||
// 全局超时检查
|
||||
if (Date.now() - loopStartTime > MAX_LOOP_TIME) {
|
||||
console.warn('[AgentEngine] 达到最大运行时间(5分钟),自动停止');
|
||||
callbacks.onDone(content || '(达到最大运行时间限制)', allToolRecords);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已中止
|
||||
if (state.get<AbortController | null>(KEYS.ABORT_CONTROLLER)?.signal.aborted) {
|
||||
callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let thinking = '';
|
||||
let content = '';
|
||||
const toolCalls: ToolCall[] = [];
|
||||
@@ -171,10 +186,21 @@ export async function runAgentLoop(
|
||||
}
|
||||
|
||||
for (const call of toolCalls) {
|
||||
// 检查是否已中止
|
||||
if (abortController.signal.aborted) {
|
||||
callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks.onToolCallStart(call);
|
||||
|
||||
if (needsConfirmation(call.function.name)) {
|
||||
const confirmed = await callbacks.onConfirmTool(call);
|
||||
// 确认后再次检查是否已中止
|
||||
if (abortController.signal.aborted) {
|
||||
callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined);
|
||||
return;
|
||||
}
|
||||
if (!confirmed) {
|
||||
const record: ToolCallRecord = {
|
||||
name: call.function.name,
|
||||
@@ -196,7 +222,14 @@ export async function runAgentLoop(
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await executeTool(call.function.name, call.function.arguments);
|
||||
// 工具执行超时保护(30秒)
|
||||
const TOOL_TIMEOUT = 30000;
|
||||
const result = await Promise.race([
|
||||
executeTool(call.function.name, call.function.arguments),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('工具执行超时(30秒)')), TOOL_TIMEOUT)
|
||||
)
|
||||
]);
|
||||
const record: ToolCallRecord = {
|
||||
name: call.function.name,
|
||||
arguments: call.function.arguments,
|
||||
@@ -214,6 +247,7 @@ export async function runAgentLoop(
|
||||
callbacks.onToolCallResult(call.function.name, result, call);
|
||||
} catch (err) {
|
||||
const errorMsg = (err as Error).message;
|
||||
console.error(`[AgentEngine] 工具执行失败 (${call.function.name}):`, errorMsg);
|
||||
const record: ToolCallRecord = {
|
||||
name: call.function.name,
|
||||
arguments: call.function.arguments,
|
||||
|
||||
Reference in New Issue
Block a user