Fix finalization cleanup for pending tool executions

This commit is contained in:
temp
2026-08-23 19:57:17 +08:00
parent bf761e9cd5
commit 3bcf4458c5
13 changed files with 522 additions and 81 deletions
+1 -1
View File
@@ -4428,7 +4428,7 @@ function renderProcessDetails(messageId, processDetails, options) {
}
}
}
if (!timelineOpts.toolStatus && eventType === 'tool_call' && detail.id && toolStatusByProcessDetailId.has(String(detail.id))) {
if (eventType === 'tool_call' && detail.id && toolStatusByProcessDetailId.has(String(detail.id))) {
timelineOpts.toolStatus = toolStatusByProcessDetailId.get(String(detail.id));
}
const itemId = addTimelineItem(timeline, eventType, timelineOpts);
+106 -12
View File
@@ -3372,6 +3372,18 @@ function handleStreamEvent(event, progressElement, progressId,
});
break;
case 'finalization_pending_tools_cancelled': {
const d = event.data || {};
markToolExecutionItemsCancelled(timeline, autoCancelledExecutionIdsFromData(d));
addTimelineItem(timeline, 'progress', {
title: '工具执行已收尾',
message: event.message,
data: d,
expanded: false
});
break;
}
case 'hitl_audit_agent_started': {
const auditData = Object.assign({}, event.data || {}, {
reviewer: 'audit_agent',
@@ -3984,6 +3996,7 @@ function handleStreamEvent(event, progressElement, progressId,
const responseData = event.data || {};
const mcpIds = mergeMcpExecutionIDLists(typeof getMcpIds === 'function' ? (getMcpIds() || []) : [], responseData.mcpExecutionIds || []);
setMcpIds(mcpIds);
markToolExecutionItemsCancelled(timeline, autoCancelledExecutionIdsFromData(responseData));
// 更新对话ID
if (responseData.conversationId) {
@@ -5886,6 +5899,9 @@ function getToolResultDisplayState(data, opts) {
}
return { kind: 'background_running', isError: false, success: false };
}
if (explicitStatus === 'cancelled' || explicitStatus === 'canceled') {
return { kind: 'cancelled', isError: true, success: false };
}
const parts = [];
if (opts.rawText != null) parts.push(String(opts.rawText));
collectToolResultTextParts(data.result, parts, 0);
@@ -5917,6 +5933,13 @@ function getBackgroundRunningToolLabel() {
return '后台执行中';
}
function toolDisplayStatusFromState(displayState) {
if (!displayState) return 'completed';
if (displayState.kind === 'background_running') return 'background_running';
if (displayState.kind === 'cancelled') return 'cancelled';
return displayState.isError ? 'failed' : 'completed';
}
function buildToolResultSectionHtml(data, opts) {
opts = opts || {};
const _t = function (k, o) {
@@ -6158,7 +6181,10 @@ function mergeToolResultIntoCallItem(item, data, options) {
}
item.dataset.toolResultMerged = '1';
item.dataset.toolSuccess = (!displayState.isError && !backgroundRunning) ? '1' : '0';
item.dataset.toolDisplayStatus = backgroundRunning ? 'background_running' : (displayState.isError ? 'failed' : 'completed');
item.dataset.toolDisplayStatus = toolDisplayStatusFromState(displayState);
if (data.executionId != null && String(data.executionId).trim() !== '') {
item.dataset.toolExecutionId = String(data.executionId).trim();
}
item.classList.remove('tool-call-running', 'tool-call-completed', 'tool-call-failed');
item.classList.add(backgroundRunning ? 'tool-call-running' : (displayState.isError ? 'tool-call-failed' : 'tool-call-completed'));
applyToolCallStatus(item, item.dataset.toolDisplayStatus);
@@ -6198,7 +6224,10 @@ function mergeToolResultIntoCallItem(item, data, options) {
item.dataset.toolResultMerged = '1';
item.dataset.toolSuccess = (!displayState.isError && !backgroundRunning) ? '1' : '0';
item.dataset.toolDisplayStatus = backgroundRunning ? 'background_running' : (displayState.isError ? 'failed' : 'completed');
item.dataset.toolDisplayStatus = toolDisplayStatusFromState(displayState);
if (data.executionId != null && String(data.executionId).trim() !== '') {
item.dataset.toolExecutionId = String(data.executionId).trim();
}
item.classList.remove('tool-call-running', 'tool-call-completed', 'tool-call-failed');
item.classList.add(backgroundRunning ? 'tool-call-running' : (displayState.isError ? 'tool-call-failed' : 'tool-call-completed'));
applyToolCallStatus(item, item.dataset.toolDisplayStatus);
@@ -6361,6 +6390,9 @@ function getToolCallStatusPresentation(status) {
if (normalized === 'failed') {
return { status: normalized, itemClass: 'tool-call-failed', badgeClass: 'tool-status-failed', label: translate('timeline.execFailed', '执行失败'), icon: '❌ ' };
}
if (normalized === 'cancelled' || normalized === 'canceled') {
return { status: 'cancelled', itemClass: 'tool-call-failed', badgeClass: 'tool-status-failed', label: translate('tasks.statusCancelled', '已取消'), icon: '⛔ ' };
}
if (normalized === 'result_missing') {
return { status: normalized, itemClass: 'tool-call-incomplete', badgeClass: 'tool-status-incomplete', label: translate('timeline.resultMissing', '结果记录缺失'), icon: '⚠️ ' };
}
@@ -6401,6 +6433,52 @@ function updateToolCallStatus(progressId, toolCallId, status) {
applyToolCallStatus(item, status);
}
function normalizeExecutionIdList(value) {
const input = Array.isArray(value) ? value : (value == null ? [] : [value]);
const seen = new Set();
const out = [];
input.forEach(function (v) {
const s = String(v == null ? '' : v).trim();
if (!s || seen.has(s)) return;
seen.add(s);
out.push(s);
});
return out;
}
function autoCancelledExecutionIdsFromData(data) {
data = data || {};
return normalizeExecutionIdList(data.autoCancelledPendingExecutionIds || data.autoCancelledExecutionIds || []);
}
function markToolExecutionItemsCancelled(root, executionIds) {
const ids = normalizeExecutionIdList(executionIds);
if (!root || ids.length === 0) return 0;
const idSet = new Set(ids);
let count = 0;
root.querySelectorAll('.timeline-item[data-tool-execution-id]').forEach(function (item) {
const execId = String(item.dataset.toolExecutionId || '').trim();
if (!execId || !idSet.has(execId)) return;
item.dataset.toolSuccess = '0';
item.dataset.toolDisplayStatus = 'cancelled';
item.classList.remove('tool-call-running', 'tool-call-completed', 'tool-call-incomplete');
item.classList.add('tool-call-failed');
const state = toolCallDetailStateByItemId.get(item.id);
if (state && state.resultData && typeof state.resultData === 'object') {
state.resultData = Object.assign({}, state.resultData, {
status: 'cancelled',
success: false,
isError: true
});
state.pending = false;
setToolCallDetailState(item, state);
}
applyToolCallStatus(item, 'cancelled');
count++;
});
return count;
}
// 添加时间线项目
function buildWorkflowConditionResultHtml(data) {
const output = (data && data.output) || {};
@@ -6547,17 +6625,23 @@ function addTimelineItem(timeline, type, options) {
const mergedDisplayState = merged ? getToolResultDisplayState(merged) : null;
const mergedBackgroundRunning = mergedDisplayState && mergedDisplayState.kind === 'background_running';
const terminalStatus = String(options.toolStatus || '').toLowerCase();
const forcedStatus = (terminalStatus === 'completed' || terminalStatus === 'failed' || terminalStatus === 'cancelled' || terminalStatus === 'canceled')
? (terminalStatus === 'canceled' ? 'cancelled' : terminalStatus)
: '';
if (merged) {
item.dataset.toolResultMerged = '1';
item.dataset.toolSuccess = (!mergedDisplayState.isError && !mergedBackgroundRunning) ? '1' : '0';
item.dataset.toolDisplayStatus = mergedBackgroundRunning ? 'background_running' : (mergedDisplayState.isError ? 'failed' : 'completed');
item.classList.add(mergedBackgroundRunning ? 'tool-call-running' : (mergedDisplayState.isError ? 'tool-call-failed' : 'tool-call-completed'));
item.dataset.toolSuccess = forcedStatus ? (forcedStatus === 'completed' ? '1' : '0') : ((!mergedDisplayState.isError && !mergedBackgroundRunning) ? '1' : '0');
item.dataset.toolDisplayStatus = forcedStatus || toolDisplayStatusFromState(mergedDisplayState);
if (merged.executionId != null && String(merged.executionId).trim() !== '') {
item.dataset.toolExecutionId = String(merged.executionId).trim();
}
item.classList.add(item.dataset.toolDisplayStatus === 'background_running' ? 'tool-call-running' : (item.dataset.toolDisplayStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed'));
if (d._mergedResultDetailId) {
item.dataset.toolResultDetailId = String(d._mergedResultDetailId);
}
} else if (terminalStatus === 'completed' || terminalStatus === 'failed') {
} else if (terminalStatus === 'completed' || terminalStatus === 'failed' || terminalStatus === 'cancelled' || terminalStatus === 'canceled') {
item.dataset.toolSuccess = terminalStatus === 'completed' ? '1' : '0';
item.dataset.toolDisplayStatus = terminalStatus;
item.dataset.toolDisplayStatus = terminalStatus === 'canceled' ? 'cancelled' : terminalStatus;
item.classList.add(terminalStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed');
} else if (terminalStatus === 'result_missing') {
item.dataset.toolDisplayStatus = 'result_missing';
@@ -6588,7 +6672,10 @@ function addTimelineItem(timeline, type, options) {
}
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
item.dataset.toolSuccess = (!displayState.isError && displayState.kind !== 'background_running') ? '1' : '0';
item.dataset.toolDisplayStatus = displayState.kind === 'background_running' ? 'background_running' : (displayState.isError ? 'failed' : 'completed');
item.dataset.toolDisplayStatus = toolDisplayStatusFromState(displayState);
if (d.executionId != null && String(d.executionId).trim() !== '') {
item.dataset.toolExecutionId = String(d.executionId).trim();
}
}
if (type === 'eino_usage_summary' && options.data) {
const d = options.data;
@@ -6658,10 +6745,14 @@ function addTimelineItem(timeline, type, options) {
const mergedDisplayState = merged ? getToolResultDisplayState(merged) : null;
const mergedBackgroundRunning = mergedDisplayState && mergedDisplayState.kind === 'background_running';
const terminalStatus = String(options.toolStatus || '').toLowerCase();
const hasTerminalStatus = terminalStatus === 'completed' || terminalStatus === 'failed';
const forcedStatus = (terminalStatus === 'completed' || terminalStatus === 'failed' || terminalStatus === 'cancelled' || terminalStatus === 'canceled')
? (terminalStatus === 'canceled' ? 'cancelled' : terminalStatus)
: '';
const hasTerminalStatus = terminalStatus === 'completed' || terminalStatus === 'failed' || terminalStatus === 'cancelled' || terminalStatus === 'canceled';
const hasHistoricalStatus = hasTerminalStatus || terminalStatus === 'result_missing';
if (merged) {
item.classList.add(mergedBackgroundRunning ? 'tool-call-running' : (mergedDisplayState.isError ? 'tool-call-failed' : 'tool-call-completed'));
const statusForClass = forcedStatus || toolDisplayStatusFromState(mergedDisplayState);
item.classList.add(statusForClass === 'background_running' ? 'tool-call-running' : (statusForClass === 'completed' ? 'tool-call-completed' : 'tool-call-failed'));
} else if (hasTerminalStatus) {
item.classList.add(terminalStatus === 'completed' ? 'tool-call-completed' : 'tool-call-failed');
} else if (terminalStatus === 'result_missing') {
@@ -6671,7 +6762,7 @@ function addTimelineItem(timeline, type, options) {
}
setToolCallDetailState(item, {
args: args,
resultData: merged || null,
resultData: (merged && forcedStatus) ? Object.assign({}, merged, { status: forcedStatus, success: forcedStatus === 'completed', isError: forcedStatus !== 'completed' }) : (merged || null),
pending: !merged && !hasHistoricalStatus && !options.skipPendingResult,
processDetailId: options.processDetailId || '',
resultDetailId: data._mergedResultDetailId || (merged && merged.processDetailId) || '',
@@ -6723,7 +6814,10 @@ function addTimelineItem(timeline, type, options) {
payloadDeferred: data._payloadDeferred === true,
payloadLoaded: data._payloadDeferred !== true
});
item.dataset.toolDisplayStatus = displayState.kind === 'background_running' ? 'background_running' : (displayState.isError ? 'failed' : 'completed');
item.dataset.toolDisplayStatus = toolDisplayStatusFromState(displayState);
if (data.executionId != null && String(data.executionId).trim() !== '') {
item.dataset.toolExecutionId = String(data.executionId).trim();
}
item.classList.add(displayState.kind === 'background_running' ? 'tool-call-running' : (displayState.isError ? 'tool-call-failed' : 'tool-call-completed'));
} else if (type === 'cancelled') {
const taskCancelledLabel = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消';