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 modalEl: HTMLElement | null = null;
|
||||||
let resolveConfirm: ((confirmed: boolean) => void) | null = null;
|
let resolveConfirm: ((confirmed: boolean) => void) | null = null;
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
export function initToolConfirmModal(): void {
|
export function initToolConfirmModal(): void {
|
||||||
modalEl = document.querySelector('#toolConfirmModal')!;
|
modalEl = document.querySelector('#toolConfirmModal')!;
|
||||||
if (!modalEl) return;
|
if (!modalEl || initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
modalEl.addEventListener('click', (e) => {
|
modalEl.addEventListener('click', (e) => {
|
||||||
if (e.target === modalEl) {
|
if (e.target === modalEl) {
|
||||||
@@ -24,10 +26,20 @@ export function showToolConfirm(call: ToolCall): Promise<boolean> {
|
|||||||
if (!modalEl) {
|
if (!modalEl) {
|
||||||
modalEl = document.querySelector('#toolConfirmModal')!;
|
modalEl = document.querySelector('#toolConfirmModal')!;
|
||||||
if (!modalEl) { resolve(false); return; }
|
if (!modalEl) { resolve(false); return; }
|
||||||
|
if (!initialized) {
|
||||||
|
initialized = true;
|
||||||
modalEl.addEventListener('click', (e) => {
|
modalEl.addEventListener('click', (e) => {
|
||||||
if (e.target === modalEl) cancelConfirm();
|
if (e.target === modalEl) cancelConfirm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有未完成的确认,先取消
|
||||||
|
if (resolveConfirm) {
|
||||||
|
const oldResolve = resolveConfirm;
|
||||||
|
resolveConfirm = null;
|
||||||
|
oldResolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
resolveConfirm = resolve;
|
resolveConfirm = resolve;
|
||||||
|
|
||||||
@@ -68,11 +80,13 @@ export function showToolConfirm(call: ToolCall): Promise<boolean> {
|
|||||||
|
|
||||||
modalEl.style.display = '';
|
modalEl.style.display = '';
|
||||||
|
|
||||||
const onConfirm = () => {
|
const onConfirm = (e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
cleanup();
|
cleanup();
|
||||||
resolveConfirm?.(true);
|
resolveConfirm?.(true);
|
||||||
};
|
};
|
||||||
const onCancel = () => {
|
const onCancel = (e: Event) => {
|
||||||
|
e.stopPropagation();
|
||||||
cleanup();
|
cleanup();
|
||||||
resolveConfirm?.(false);
|
resolveConfirm?.(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -86,10 +86,25 @@ export async function runAgentLoop(
|
|||||||
let loopCount = 0;
|
let loopCount = 0;
|
||||||
const maxLoops = 10;
|
const maxLoops = 10;
|
||||||
const allToolRecords: ToolCallRecord[] = [];
|
const allToolRecords: ToolCallRecord[] = [];
|
||||||
|
const loopStartTime = Date.now();
|
||||||
|
const MAX_LOOP_TIME = 300000; // 5分钟总超时
|
||||||
|
|
||||||
while (loopCount < maxLoops) {
|
while (loopCount < maxLoops) {
|
||||||
loopCount++;
|
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 thinking = '';
|
||||||
let content = '';
|
let content = '';
|
||||||
const toolCalls: ToolCall[] = [];
|
const toolCalls: ToolCall[] = [];
|
||||||
@@ -171,10 +186,21 @@ export async function runAgentLoop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const call of toolCalls) {
|
for (const call of toolCalls) {
|
||||||
|
// 检查是否已中止
|
||||||
|
if (abortController.signal.aborted) {
|
||||||
|
callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
callbacks.onToolCallStart(call);
|
callbacks.onToolCallStart(call);
|
||||||
|
|
||||||
if (needsConfirmation(call.function.name)) {
|
if (needsConfirmation(call.function.name)) {
|
||||||
const confirmed = await callbacks.onConfirmTool(call);
|
const confirmed = await callbacks.onConfirmTool(call);
|
||||||
|
// 确认后再次检查是否已中止
|
||||||
|
if (abortController.signal.aborted) {
|
||||||
|
callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
const record: ToolCallRecord = {
|
const record: ToolCallRecord = {
|
||||||
name: call.function.name,
|
name: call.function.name,
|
||||||
@@ -196,7 +222,14 @@ export async function runAgentLoop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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 = {
|
const record: ToolCallRecord = {
|
||||||
name: call.function.name,
|
name: call.function.name,
|
||||||
arguments: call.function.arguments,
|
arguments: call.function.arguments,
|
||||||
@@ -214,6 +247,7 @@ export async function runAgentLoop(
|
|||||||
callbacks.onToolCallResult(call.function.name, result, call);
|
callbacks.onToolCallResult(call.function.name, result, call);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMsg = (err as Error).message;
|
const errorMsg = (err as Error).message;
|
||||||
|
console.error(`[AgentEngine] 工具执行失败 (${call.function.name}):`, errorMsg);
|
||||||
const record: ToolCallRecord = {
|
const record: ToolCallRecord = {
|
||||||
name: call.function.name,
|
name: call.function.name,
|
||||||
arguments: call.function.arguments,
|
arguments: call.function.arguments,
|
||||||
|
|||||||
Reference in New Issue
Block a user