agent-Specialization/static/src/composables/useLegacySocket.ts

1766 lines
61 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
/**
* WebSocket 连接管理(遗留代码)
*
* 注意聊天流式事件ai_message_start, thinking_*, text_*, tool_*)已废弃
* 现在使用 REST API 轮询模式(/api/tasks处理聊天消息
*
* 保留的功能:
* - 系统事件connect, disconnect, error
* - 配额事件quota_update, quota_notice, quota_exceeded
* - 对话事件conversation_changed, conversation_resolved, conversation_loaded
* - 子智能体事件sub_agent_waiting
* - Token统计token_update
*
* 待清理:
* - 流式文本处理相关代码streamingState, pendingToolEvents等
* - 聊天事件处理器中的复杂逻辑
*/
import { io as createSocketClient } from 'socket.io-client';
import { renderLatexInRealtime } from './useMarkdownRenderer';
import { getMessageVisibility, messageStartsWork } from '../utils/messageVisibility';
export async function initializeLegacySocket(ctx: any) {
try {
const SOCKET_DEBUG_LOGS_ENABLED = true;
const socketLog = (...args: any[]) => {
if (!SOCKET_DEBUG_LOGS_ENABLED) {
return;
}
console.log(...args);
};
socketLog('初始化WebSocket连接...');
const socketOptions = {
transports: ['websocket', 'polling'],
autoConnect: false
};
ctx.socket = createSocketClient('/', socketOptions);
// 可开关的逐字打印功能,默认关闭
const STREAMING_ENABLED = false;
const STREAMING_CHAR_DELAY = 22;
const STREAMING_FINALIZE_DELAY = 1000;
const STREAMING_DEBUG = false;
const STREAMING_DEBUG_HISTORY_LIMIT = 2000;
const streamingState = {
buffer: [] as string[],
timer: null as number | null,
completionTimer: null as number | null,
apiCompleted: false,
pendingCompleteContent: '' as string,
renderedText: '' as string,
activeMessageIndex: null as number | null,
activeTextAction: null as any,
ignoreThinking: false
};
const pendingToolEvents: Array<{ event: string; data: any; handler: () => void }> = [];
const startIntentTyping = (action: any, intentText: any) => {
if (!action || !action.tool) {
return;
}
if (typeof intentText !== 'string') {
return;
}
const text = intentText.trim();
if (!text) {
return;
}
if (action.tool.intent_full === text) {
if (console && console.debug) {
console.debug('[tool_intent] skip (same text)', {
id: action?.id || action?.tool?.id,
name: action?.tool?.name,
text
});
}
return;
}
if (typeof console !== 'undefined' && console.debug) {
console.debug('[tool_intent] typing start', {
id: action?.id || action?.tool?.id,
name: action?.tool?.name,
text
});
}
if (action.tool.intent_full === text && action.tool.intent_rendered === text) {
return;
}
// 清理旧定时器
if (action.tool.intent_typing_timer) {
clearInterval(action.tool.intent_typing_timer);
action.tool.intent_typing_timer = null;
}
action.tool.intent_full = text;
action.tool.intent_rendered = '';
const duration = 1000;
const step = Math.max(10, Math.floor(duration / Math.max(1, text.length)));
let index = 0;
action.tool.intent_typing_timer = window.setInterval(() => {
action.tool.intent_rendered = text.slice(0, index + 1);
if (index === 0 && console && console.debug) {
console.debug('[tool_intent] typing tick', {
id: action?.id || action?.tool?.id,
name: action?.tool?.name,
next: action.tool.intent_rendered
});
}
index += 1;
if (index >= text.length) {
clearInterval(action.tool.intent_typing_timer);
action.tool.intent_typing_timer = null;
action.tool.intent_rendered = text;
if (console && console.debug) {
console.debug('[tool_intent] typing done', {
id: action?.id || action?.tool?.id,
name: action?.tool?.name,
full: text
});
}
}
if (typeof ctx?.$forceUpdate === 'function') {
ctx.$forceUpdate();
}
}, step);
};
const resetPendingToolEvents = () => {
pendingToolEvents.length = 0;
};
const flushPendingToolEvents = () => {
if (!pendingToolEvents.length) {
return;
}
const queue = pendingToolEvents.splice(0);
queue.forEach((item) => {
try {
item.handler();
} catch (error) {
console.warn('延后工具事件处理失败:', item.event, error);
}
});
};
const snapshotStreamingState = () => ({
bufferLength: streamingState.buffer.length,
timerActive: streamingState.timer !== null,
completionTimerActive: streamingState.completionTimer !== null,
apiCompleted: streamingState.apiCompleted,
pendingLength: (streamingState.pendingCompleteContent || '').length,
renderedLength: streamingState.renderedText.length,
currentMessageIndex:
typeof ctx?.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null,
messagesLength: Array.isArray(ctx?.messages) ? ctx.messages.length : null,
streamingMessage: !!ctx?.streamingMessage
});
const streamingDebugHistory: Array<any> = [];
const sanitizeBubbleText = (text: unknown) => {
if (typeof text !== 'string') {
return '';
}
return text.replace(/\r/g, '');
};
const getActiveMessage = () => {
if (streamingState.activeMessageIndex === null) {
return null;
}
const messages = ctx?.messages;
if (!Array.isArray(messages)) {
return null;
}
return messages[streamingState.activeMessageIndex] || null;
};
const ensureActiveMessageBinding = () => {
if (
streamingState.activeMessageIndex === null &&
typeof ctx?.currentMessageIndex === 'number' &&
ctx.currentMessageIndex >= 0
) {
streamingState.activeMessageIndex = ctx.currentMessageIndex;
}
if (typeof ctx?.currentMessageIndex !== 'number' || ctx.currentMessageIndex < 0) {
if (streamingState.activeMessageIndex !== null) {
ctx.currentMessageIndex = streamingState.activeMessageIndex;
}
}
if (!ctx.streamingMessage) {
ctx.streamingMessage = true;
}
const msg = getActiveMessage();
if (!msg && Array.isArray(ctx?.messages) && ctx.messages.length) {
streamingState.activeMessageIndex = ctx.messages.length - 1;
}
return getActiveMessage();
};
const ensureActiveTextAction = () => {
const msg = getActiveMessage();
if (!msg || !Array.isArray(msg.actions) || !msg.actions.length) {
return null;
}
const known = streamingState.activeTextAction;
if (known && msg.actions.includes(known)) {
return known;
}
for (let i = msg.actions.length - 1; i >= 0; i--) {
const action = msg.actions[i];
if (action && action.type === 'text') {
streamingState.activeTextAction = action;
return action;
}
}
return null;
};
const fallbackAppendToActiveMessage = (text: string) => {
const msg = ensureActiveMessageBinding();
if (!msg) {
return null;
}
if (typeof msg.streamingText !== 'string') {
msg.streamingText = '';
}
msg.streamingText += text;
const action = ensureActiveTextAction();
if (!action) {
return null;
}
if (typeof action.content !== 'string') {
action.content = '';
}
action.content += text;
action.streaming = action.streaming !== false;
streamingState.activeTextAction = action;
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
renderLatexInRealtime();
return action;
};
const completeActiveTextAction = (fullContent: string) => {
const msg = ensureActiveMessageBinding();
if (!msg) {
return false;
}
const action = ensureActiveTextAction();
if (action) {
action.streaming = false;
const fallback =
(typeof fullContent === 'string' && fullContent.length ? fullContent : action.content) ||
'';
action.content = fallback;
}
msg.streamingText = '';
msg.currentStreamingType = null;
return !!action;
};
const logStreamingDebug = (event: string, detail?: any) => {
if (!STREAMING_DEBUG) {
return;
}
const payload = typeof detail === 'undefined' ? snapshotStreamingState() : detail;
const entry = {
event,
detail: payload,
conversationId: ctx.currentConversationId || null,
timestamp: Date.now()
};
streamingDebugHistory.push(entry);
if (streamingDebugHistory.length > STREAMING_DEBUG_HISTORY_LIMIT) {
streamingDebugHistory.shift();
}
try {
window.__streamingDebugLogs = streamingDebugHistory.slice();
} catch (error) {
// ignore
}
console.log('[streaming-debug]', event, payload);
try {
if (ctx.socket && typeof ctx.socket.emit === 'function') {
ctx.socket.emit('client_stream_debug_log', entry);
}
} catch (error) {
console.warn('上报 streaming debug 日志失败:', error);
}
};
const markStreamingIdleIfPossible = (source: string) => {
try {
if (!ctx) {
return;
}
if (typeof ctx.maybeResetStreamingState === 'function') {
const reset = ctx.maybeResetStreamingState(source);
if (reset) {
logStreamingDebug('streaming_idle_reset', { source });
}
return;
}
if (!ctx.streamingMessage) {
return;
}
const hasPending =
typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false;
if (!hasPending) {
ctx.streamingMessage = false;
ctx.stopRequested = false;
logStreamingDebug('streaming_idle_reset:fallback', { source });
}
} catch (error) {
console.warn('自动结束流式状态失败:', error);
}
};
const stopStreamingTimer = () => {
if (streamingState.timer !== null) {
clearTimeout(streamingState.timer);
streamingState.timer = null;
logStreamingDebug('stopStreamingTimer', snapshotStreamingState());
}
};
const stopCompletionTimer = () => {
if (streamingState.completionTimer !== null) {
clearTimeout(streamingState.completionTimer);
streamingState.completionTimer = null;
logStreamingDebug('stopCompletionTimer', snapshotStreamingState());
}
};
const finalizeStreamingText = (options?: { force?: boolean; allowIncomplete?: boolean }) => {
const forceFlush = !!options?.force;
const allowIncomplete = !!options?.allowIncomplete;
logStreamingDebug('finalizeStreamingText:start', {
forceFlush,
allowIncomplete,
snapshot: snapshotStreamingState()
});
if (!forceFlush) {
if (streamingState.buffer.length) {
logStreamingDebug('finalizeStreamingText:blocked-buffer', snapshotStreamingState());
return false;
}
if (!allowIncomplete && !streamingState.apiCompleted) {
logStreamingDebug(
'finalizeStreamingText:blocked-api-incomplete',
snapshotStreamingState()
);
return false;
}
}
stopStreamingTimer();
stopCompletionTimer();
if (forceFlush && streamingState.buffer.length) {
const remainder = streamingState.buffer.join('');
streamingState.buffer.length = 0;
if (remainder) {
applyTextChunk(remainder);
logStreamingDebug('finalizeStreamingText:force-flush-buffer', {
flushedChars: remainder.length
});
}
}
const pendingText = streamingState.pendingCompleteContent || '';
const renderedText = streamingState.renderedText || '';
let finalText = pendingText || renderedText || '';
let remainderToAppend = '';
if (!pendingText) {
finalText = renderedText;
} else if (!renderedText) {
finalText = pendingText;
remainderToAppend = pendingText;
} else if (pendingText.length < renderedText.length) {
// 后端返回的最终内容比已渲染的还短,避免覆盖已展示的字符
finalText = renderedText;
} else if (pendingText.startsWith(renderedText)) {
finalText = pendingText;
remainderToAppend = pendingText.slice(renderedText.length);
} else {
finalText = pendingText;
}
logStreamingDebug('finalizeStreamingText:resolved-final-text', {
pendingLength: pendingText.length,
renderedLength: renderedText.length,
remainderToAppendLength: remainderToAppend.length,
finalLength: finalText.length
});
if (remainderToAppend) {
applyTextChunk(remainderToAppend);
}
streamingState.pendingCompleteContent = '';
streamingState.apiCompleted = false;
streamingState.renderedText = '';
ctx.chatCompleteTextAction(finalText || '');
completeActiveTextAction(finalText || '');
ctx.$forceUpdate();
// 注意:不在这里重置 streamingMessage因为可能还有工具调用在进行
// streamingMessage 只在 task_complete 事件时重置
logStreamingDebug('finalizeStreamingText:complete', snapshotStreamingState());
streamingState.activeMessageIndex = null;
streamingState.activeTextAction = null;
markStreamingIdleIfPossible('finalizeStreamingText');
flushPendingToolEvents();
return true;
};
const scheduleFinalizationAfterDrain = () => {
if (streamingState.buffer.length) {
logStreamingDebug(
'scheduleFinalizationAfterDrain:buffer-not-empty',
snapshotStreamingState()
);
return;
}
if (streamingState.completionTimer !== null) {
logStreamingDebug(
'scheduleFinalizationAfterDrain:already-scheduled',
snapshotStreamingState()
);
return;
}
logStreamingDebug('scheduleFinalizationAfterDrain:scheduled', snapshotStreamingState());
streamingState.completionTimer = window.setTimeout(() => {
streamingState.completionTimer = null;
logStreamingDebug('scheduleFinalizationAfterDrain:timer-fired', snapshotStreamingState());
finalizeStreamingText({ allowIncomplete: true });
}, STREAMING_FINALIZE_DELAY);
};
const resetStreamingBuffer = () => {
stopStreamingTimer();
stopCompletionTimer();
streamingState.buffer.length = 0;
streamingState.apiCompleted = false;
streamingState.pendingCompleteContent = '';
streamingState.renderedText = '';
streamingState.activeMessageIndex = null;
streamingState.activeTextAction = null;
resetPendingToolEvents();
logStreamingDebug('resetStreamingBuffer', snapshotStreamingState());
};
const applyTextChunk = (text: string) => {
if (!text) {
return null;
}
ensureActiveMessageBinding();
let action = ctx.chatAppendTextChunk(text);
if (action) {
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
renderLatexInRealtime();
} else {
action = fallbackAppendToActiveMessage(text);
}
streamingState.renderedText += text;
const appended = !!action;
logStreamingDebug('applyTextChunk', {
chunkLength: text.length,
appended,
snapshot: snapshotStreamingState()
});
if (!appended) {
logStreamingDebug('applyTextChunk:missing-target', {
chunkLength: text.length,
currentMessageIndex:
typeof ctx?.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null,
messagesLength: Array.isArray(ctx?.messages) ? ctx.messages.length : null
});
}
return action;
};
const drainStreamingBufferImmediately = () => {
if (!streamingState.buffer.length) {
return;
}
const chunk = streamingState.buffer.join('');
streamingState.buffer.length = 0;
applyTextChunk(chunk);
};
const scheduleStreamingFlush = () => {
const shouldFlushImmediately = typeof document !== 'undefined' && document.hidden === true;
if (shouldFlushImmediately) {
stopStreamingTimer();
drainStreamingBufferImmediately();
if (streamingState.apiCompleted) {
finalizeStreamingText({ force: true });
} else {
scheduleFinalizationAfterDrain();
}
return;
}
if (streamingState.timer !== null) {
logStreamingDebug('scheduleStreamingFlush:timer-exists', snapshotStreamingState());
return;
}
if (!streamingState.buffer.length) {
logStreamingDebug('scheduleStreamingFlush:no-buffer', snapshotStreamingState());
return;
}
logStreamingDebug('scheduleStreamingFlush:start', snapshotStreamingState());
const process = () => {
streamingState.timer = null;
logStreamingDebug('scheduleStreamingFlush:tick', snapshotStreamingState());
if (!streamingState.buffer.length) {
logStreamingDebug('scheduleStreamingFlush:buffer-empty', snapshotStreamingState());
return;
}
const piece = streamingState.buffer.shift();
if (piece) {
applyTextChunk(piece);
}
if (streamingState.buffer.length) {
scheduleStreamingFlush();
} else {
logStreamingDebug('scheduleStreamingFlush:buffer-drained', snapshotStreamingState());
scheduleFinalizationAfterDrain();
}
};
streamingState.timer = window.setTimeout(process, STREAMING_CHAR_DELAY);
};
const enqueueStreamingContent = (text: string) => {
if (!text) {
return;
}
stopCompletionTimer();
if (!STREAMING_ENABLED) {
// 关闭逐字模式:整块入缓冲并立即刷新,保持与 chunk 顺序一致
streamingState.pendingCompleteContent = '';
streamingState.buffer.length = 0;
streamingState.apiCompleted = false;
applyTextChunk(text);
return;
}
logStreamingDebug('enqueueStreamingContent', { incomingLength: text.length });
for (const ch of Array.from(text)) {
streamingState.buffer.push(ch);
}
logStreamingDebug('enqueueStreamingContent:buffered', snapshotStreamingState());
scheduleStreamingFlush();
};
const scheduleHistoryReload = (delay = 0, options: { force?: boolean } = {}) => {
const { force = false } = options;
if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') {
return;
}
setTimeout(() => {
const targetConversationId = ctx.currentConversationId;
if (!targetConversationId || targetConversationId.startsWith('temp_')) {
return;
}
try {
if (!force) {
const loadingSame =
!!ctx.historyLoading && ctx.historyLoadingFor === targetConversationId;
const historyReady =
ctx.lastHistoryLoadedConversationId === targetConversationId &&
Array.isArray(ctx.messages) &&
ctx.messages.length > 0;
if (loadingSame || historyReady) {
socketLog('跳过重复历史加载(scheduleHistoryReload)');
return;
}
}
ctx.fetchAndDisplayHistory({ force });
if (typeof ctx.fetchConversationTokenStatistics === 'function') {
ctx.fetchConversationTokenStatistics();
}
if (typeof ctx.updateCurrentContextTokens === 'function') {
ctx.updateCurrentContextTokens();
}
} catch (error) {
console.warn('重新加载对话历史失败:', error);
}
}, delay);
};
const assignSocketToken = async () => {
if (typeof window.requestSocketToken !== 'function') {
console.warn('缺少 requestSocketToken(),无法获取实时连接凭证');
return false;
}
try {
const freshToken = await window.requestSocketToken();
ctx.socket.auth = { socket_token: freshToken };
return true;
} catch (error) {
console.error('获取 WebSocket token 失败:', error);
return false;
}
};
if (ctx.socket.io && typeof ctx.socket.io.on === 'function') {
ctx.socket.io.on('reconnect_attempt', async () => {
await assignSocketToken();
});
}
let hasConnectedOnce = false;
// 连接事件
ctx.socket.on('connect', () => {
const isReconnect = hasConnectedOnce;
const historyLoadingSame =
!!ctx.historyLoading && ctx.historyLoadingFor === ctx.currentConversationId;
const historyReady =
ctx.lastHistoryLoadedConversationId === ctx.currentConversationId &&
Array.isArray(ctx.messages) &&
ctx.messages.length > 0;
ctx.isConnected = true;
socketLog('WebSocket已连接');
// 首次连接且历史已在加载/已就绪时,避免重复清空与重复动画
if (!isReconnect && (historyLoadingSame || historyReady)) {
socketLog('初次连接已存在历史,跳过重复重置与加载');
hasConnectedOnce = true;
return;
}
hasConnectedOnce = true;
ctx.resetAllStates(isReconnect ? 'socket:reconnect' : 'socket:connect');
scheduleHistoryReload(200, { force: isReconnect });
});
ctx.socket.on('disconnect', () => {
ctx.isConnected = false;
socketLog('WebSocket已断开');
// 断线时也重置状态,防止状态混乱
ctx.resetAllStates('socket:disconnect');
});
ctx.socket.on('connect_error', (error) => {
console.error('WebSocket连接错误:', error.message);
});
ctx.socket.on('quota_update', (data) => {
if (data && data.quotas) {
ctx.resourceSetUsageQuota({ quotas: data.quotas });
} else {
ctx.fetchUsageQuota();
}
});
ctx.socket.on('quota_notice', (data) => {
ctx.showQuotaToast(data || {});
ctx.fetchUsageQuota();
});
ctx.socket.on('quota_exceeded', (data) => {
ctx.showQuotaToast(data || {});
ctx.fetchUsageQuota();
});
ctx.socket.on('reconnect_attempt', async () => {
await assignSocketToken();
});
const ready = await assignSocketToken();
if (!ready) {
console.error('无法获取实时连接凭证WebSocket 初始化中止。');
return;
}
ctx.socket.connect();
// ==========================================
// Token统计WebSocket事件处理修复版
// ==========================================
ctx.socket.on('token_update', (data) => {
socketLog('收到token更新事件:', data);
// 只处理当前对话的token更新
if (data.conversation_id === ctx.currentConversationId) {
// 更新累计统计(使用后端提供的准确字段名)
ctx.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0;
ctx.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0;
ctx.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0;
socketLog(
`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`
);
const hasContextTokens = typeof data.current_context_tokens === 'number';
if (hasContextTokens && typeof ctx.resourceSetCurrentContextTokens === 'function') {
ctx.resourceSetCurrentContextTokens(data.current_context_tokens);
} else {
// 同时更新当前上下文Token关键修复
ctx.updateCurrentContextTokens();
}
ctx.$forceUpdate();
}
});
// 上下文过长提示
ctx.socket.on('context_warning', (data) => {
const title = data?.title || '上下文过长';
const message = data?.message || '当前对话上下文接近上限,建议使用压缩功能。';
if (typeof ctx.uiPushToast === 'function') {
ctx.uiPushToast({
title,
message,
type: data?.type || 'warning',
duration: data?.duration || 6000
});
}
});
ctx.socket.on('todo_updated', (data) => {
socketLog('收到todo更新事件:', data);
// 初始化期间不修改 currentConversationId避免与 bootstrapRoute 冲突
if (ctx.initialRouteResolved && data && data.conversation_id) {
ctx.currentConversationId = data.conversation_id;
}
ctx.fileSetTodoList((data && data.todo_list) || null);
});
// 系统就绪
ctx.socket.on('system_ready', (data) => {
ctx.projectPath = data.project_path || '';
ctx.agentVersion = data.version || ctx.agentVersion;
ctx.thinkingMode = !!data.thinking_mode;
if (data.run_mode) {
ctx.runMode = data.run_mode;
} else {
ctx.runMode = ctx.thinkingMode ? 'thinking' : 'fast';
}
socketLog('系统就绪:', data);
// 系统就绪后立即加载对话列表
ctx.loadConversationsList();
});
ctx.socket.on('tool_settings_updated', (data) => {
socketLog('收到工具设置更新:', data);
if (data && Array.isArray(data.categories)) {
ctx.applyToolSettingsSnapshot(data.categories);
}
});
// ==========================================
// 对话管理相关Socket事件
// ==========================================
// 监听对话变更事件
ctx.socket.on('conversation_changed', (data) => {
socketLog('对话已切换:', data);
console.log('[conv-trace] socket:conversation_changed', data);
// 初始化期间不修改 currentConversationId避免与 bootstrapRoute 冲突
if (ctx.initialRouteResolved) {
ctx.currentConversationId = data.conversation_id;
ctx.currentConversationTitle = data.title || '';
// 更新对话列表中的标题
if (data.title && Array.isArray(ctx.conversations)) {
const conv = ctx.conversations.find((c) => c && c.id === data.conversation_id);
if (conv) {
conv.title = data.title;
}
}
ctx.promoteConversationToTop(data.conversation_id);
if (data.cleared) {
// 对话被清空
ctx.logMessageState?.('socket:conversation_changed-clearing', { event: data });
ctx.messages = [];
ctx.logMessageState?.('socket:conversation_changed-cleared', { event: data });
ctx.currentConversationId = null;
ctx.currentConversationTitle = '';
// 重置Token统计
ctx.resetTokenStatistics();
history.replaceState({}, '', '/new');
}
// 刷新对话列表
ctx.loadConversationsList();
ctx.fetchTodoList();
ctx.fetchSubAgents();
}
});
ctx.socket.on('conversation_resolved', (data) => {
if (!data || !data.conversation_id) {
return;
}
const convId = data.conversation_id;
console.log('[conv-trace] socket:conversation_resolved', data);
// 初始化期间不修改 currentConversationId避免与 bootstrapRoute 冲突
if (ctx.initialRouteResolved) {
ctx.currentConversationId = convId;
if (data.title) {
ctx.currentConversationTitle = data.title;
}
ctx.promoteConversationToTop(convId);
const pathFragment = ctx.stripConversationPrefix(convId);
const currentPath = window.location.pathname.replace(/^\/+/, '');
if (data.created) {
history.pushState({ conversationId: convId }, '', `/${pathFragment}`);
} else if (currentPath !== pathFragment) {
history.replaceState({ conversationId: convId }, '', `/${pathFragment}`);
}
}
});
// 监听对话加载事件
ctx.socket.on('conversation_loaded', (data) => {
socketLog('对话已加载:', data);
if (ctx.skipConversationLoadedEvent) {
ctx.skipConversationLoadedEvent = false;
socketLog('跳过重复的 conversation_loaded 处理');
return;
}
const targetConversationId = (data && data.conversation_id) || ctx.currentConversationId;
const historyLoadingSame =
!!targetConversationId &&
!!ctx.historyLoading &&
ctx.historyLoadingFor === targetConversationId;
const historyReady =
!!targetConversationId &&
ctx.lastHistoryLoadedConversationId === targetConversationId &&
Array.isArray(ctx.messages) &&
ctx.messages.length > 0;
const forceReload = !!(data && data.force_reload);
if (!forceReload && (historyLoadingSame || historyReady)) {
socketLog('conversation_loaded: 历史已加载/加载中,跳过重复刷新');
} else {
if (data.clear_ui) {
// 清理当前UI状态准备显示历史内容
ctx.resetAllStates('socket:conversation_loaded');
}
// 延迟获取并显示历史对话内容
setTimeout(() => {
ctx.fetchAndDisplayHistory({ force: forceReload });
}, 300);
}
// 延迟获取Token统计累计+当前上下文)
setTimeout(() => {
ctx.fetchConversationTokenStatistics();
ctx.updateCurrentContextTokens();
ctx.fetchTodoList();
}, 500);
});
// 监听对话列表更新事件
ctx.socket.on('conversation_list_update', (data) => {
socketLog('对话列表已更新:', data);
// 刷新对话列表
ctx.conversationsOffset = 0;
ctx.hasMoreConversations = false;
ctx.loadingMoreConversations = false;
ctx.loadConversationsList();
});
// 监听状态更新事件
ctx.socket.on('status_update', (status) => {
ctx.applyStatusSnapshot(status);
// 只有在初始化完成后,才允许 status_update 修改 currentConversationId
// 避免与 bootstrapRoute 冲突
if (status.conversation && status.conversation.current_id) {
if (ctx.initialRouteResolved && !ctx.currentConversationId) {
// 初始化完成且当前没有对话ID才设置
ctx.currentConversationId = status.conversation.current_id;
}
}
if (typeof status.run_mode === 'string') {
ctx.runMode = status.run_mode;
} else if (typeof status.thinking_mode !== 'undefined') {
ctx.runMode = status.thinking_mode ? 'thinking' : 'fast';
}
});
// 用户消息(后台子智能体完成后自动触发)
ctx.socket.on('user_message', (data) => {
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (!data) {
return;
}
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过user_message(对话不匹配)', data.conversation_id);
return;
}
const message = (data.message || data.content || '').trim();
if (!message) {
return;
}
const incomingImages = Array.isArray(data.images) ? data.images : [];
const incomingVideos = Array.isArray(data.videos) ? data.videos : [];
const incomingMediaRefs = Array.isArray(data.media_refs)
? data.media_refs
: Array.isArray(data.mediaRefs)
? data.mediaRefs
: [];
const source = String(data?.message_source || data?.metadata?.message_source || 'user')
.trim()
.toLowerCase();
const eventMetadata = {
...(data?.metadata || {}),
message_source: source
};
if (Object.prototype.hasOwnProperty.call(data || {}, 'visibility')) {
eventMetadata.visibility = data.visibility;
}
if (Object.prototype.hasOwnProperty.call(data || {}, 'starts_work')) {
eventMetadata.starts_work = data.starts_work;
}
eventMetadata.visibility = getMessageVisibility({ role: 'user', content: message, metadata: eventMetadata, ...data });
eventMetadata.starts_work = messageStartsWork({ role: 'user', content: message, metadata: eventMetadata, ...data });
const isAutoUserMessage = !!(
data?.is_auto_generated ||
data?.metadata?.is_auto_generated ||
data?.auto_message_type ||
data?.metadata?.auto_message_type ||
data?.sub_agent_notice ||
data?.background_command_notice
);
if (
isAutoUserMessage &&
eventMetadata.starts_work === true &&
typeof ctx.markLatestUserWorkCompleted === 'function'
) {
ctx.markLatestUserWorkCompleted();
}
const last =
Array.isArray(ctx.messages) && ctx.messages.length
? ctx.messages[ctx.messages.length - 1]
: null;
const isLikelyOptimisticEcho = !!(
last &&
last.role === 'user' &&
String(last.content || '').trim() === message &&
JSON.stringify(last.images || []) === JSON.stringify(incomingImages || []) &&
JSON.stringify(last.videos || []) === JSON.stringify(incomingVideos || [])
);
if (isLikelyOptimisticEcho) {
last.media_refs = incomingMediaRefs;
last.metadata = {
...(last.metadata || {}),
...eventMetadata,
media_refs: incomingMediaRefs
};
} else {
ctx.chatAddUserMessage(message, incomingImages, incomingVideos, incomingMediaRefs, source, eventMetadata);
}
ctx.taskInProgress = true;
ctx.streamingMessage = false;
ctx.stopRequested = false;
if (data?.sub_agent_notice && data?.task_id && ctx.usePollingMode) {
if (typeof data?.has_running_sub_agents === 'boolean') {
ctx.waitingForSubAgent = data.has_running_sub_agents;
} else if (typeof data?.remaining_count === 'number') {
ctx.waitingForSubAgent = data.remaining_count > 0;
}
(async () => {
try {
const { useTaskStore } = await import('../stores/task');
const taskStore = useTaskStore();
taskStore.resumeTask(data.task_id);
} catch (error) {
console.warn('恢复任务轮询失败', error);
}
})();
}
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
});
// AI消息开始已废弃改用 REST API 轮询)
ctx.socket.on('ai_message_start', () => {
// 只处理子智能体模式
if (ctx.waitingForSubAgent) {
ctx.waitingForSubAgent = false;
ctx.taskInProgress = false;
}
});
// 思考流开始
ctx.socket.on('thinking_start', () => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
socketLog('思考开始 (轮询模式,跳过)');
return;
}
socketLog('思考开始');
const ignoreThinking = ctx.runMode === 'fast' || ctx.thinkingMode === false;
streamingState.ignoreThinking = ignoreThinking;
if (ignoreThinking) {
ctx.monitorEndModelOutput();
return;
}
ctx.monitorShowThinking();
const result = ctx.chatStartThinkingAction();
if (result && result.blockId) {
const blockId = result.blockId;
ctx.chatExpandBlock(blockId);
// 开始思考时尝试滚动到底部,但不改变用户锁定选择
ctx.scrollToBottom();
ctx.chatSetThinkingLock(blockId, true);
ctx.$forceUpdate();
}
});
// 思考内容块
ctx.socket.on('thinking_chunk', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (streamingState.ignoreThinking) {
return;
}
const thinkingAction = ctx.chatAppendThinkingChunk(data.content);
if (thinkingAction) {
ctx.$forceUpdate();
ctx.$nextTick(() => {
if (thinkingAction && thinkingAction.blockId) {
ctx.scrollThinkingToBottom(thinkingAction.blockId);
}
ctx.conditionalScrollToBottom();
});
}
ctx.monitorShowThinking();
});
// 思考结束
ctx.socket.on('thinking_end', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
socketLog('思考结束 (轮询模式,跳过)');
return;
}
socketLog('思考结束');
if (streamingState.ignoreThinking) {
streamingState.ignoreThinking = false;
return;
}
const blockId = ctx.chatCompleteThinkingAction(data.full_content);
if (blockId) {
setTimeout(() => {
ctx.chatCollapseBlock(blockId);
ctx.chatSetThinkingLock(blockId, false);
ctx.$forceUpdate();
}, 1000);
ctx.$nextTick(() => ctx.scrollThinkingToBottom(blockId));
}
ctx.$forceUpdate();
ctx.monitorEndModelOutput();
});
// 文本流开始
ctx.socket.on('text_start', () => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
socketLog('文本开始 (轮询模式,跳过)');
return;
}
console.log('[DEBUG] 收到 text_start 事件:', {
currentConversationId: ctx.currentConversationId,
messagesCount: ctx.messages.length,
currentMessageIndex: ctx.currentMessageIndex
});
socketLog('文本开始');
logStreamingDebug('socket:text_start');
finalizeStreamingText({ force: true });
resetStreamingBuffer();
const action = ctx.chatStartTextAction();
streamingState.activeMessageIndex =
typeof ctx.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null;
streamingState.activeTextAction = action || ensureActiveTextAction();
ensureActiveMessageBinding();
ctx.$forceUpdate();
console.log('[DEBUG] text_start 处理完成');
});
// 文本内容块
ctx.socket.on('text_chunk', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
console.log('[DEBUG] 收到 text_chunk 事件:', {
conversation_id: data?.conversation_id,
currentConversationId: ctx.currentConversationId,
chunkLength: (data?.content || '').length,
index: data?.index
});
logStreamingDebug('socket:text_chunk', {
index: data?.index ?? null,
elapsed: data?.elapsed ?? null,
chunkLength: (data?.content || '').length,
snapshot: snapshotStreamingState()
});
try {
ctx.socket.emit('client_chunk_log', {
conversation_id: ctx.currentConversationId,
index: data?.index ?? null,
elapsed: data?.elapsed ?? null,
length: (data?.content || '').length,
ts: Date.now()
});
} catch (error) {
console.warn('上报chunk日志失败:', error);
}
if (data && typeof data.content === 'string' && data.content.length) {
if (STREAMING_ENABLED) {
enqueueStreamingContent(data.content);
} else {
// 关闭逐字模式时直接追加当前chunk
applyTextChunk(data.content);
}
const speech = sanitizeBubbleText(data.content);
if (speech) {
ctx.monitorShowSpeech(speech);
}
}
});
// 文本结束
ctx.socket.on('text_end', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
socketLog('文本结束 (轮询模式,跳过)');
return;
}
socketLog('文本结束');
logStreamingDebug('socket:text_end', {
finalLength: (data?.full_content || '').length,
snapshot: snapshotStreamingState()
});
if (!STREAMING_ENABLED) {
// 已逐块追加,这里保证动作收尾
const full = data?.full_content || '';
if (full) {
ctx.chatCompleteTextAction(full);
} else {
ctx.chatCompleteTextAction('');
}
streamingState.buffer.length = 0;
streamingState.pendingCompleteContent = '';
streamingState.renderedText = '';
streamingState.apiCompleted = false;
ctx.monitorEndModelOutput();
flushPendingToolEvents();
return;
}
streamingState.apiCompleted = true;
streamingState.pendingCompleteContent = data?.full_content || '';
const hidden = typeof document !== 'undefined' && document.hidden === true;
if (hidden) {
stopStreamingTimer();
drainStreamingBufferImmediately();
finalizeStreamingText({ force: true });
} else if (!streamingState.buffer.length) {
scheduleFinalizationAfterDrain();
} else {
scheduleStreamingFlush();
}
ctx.monitorEndModelOutput();
flushPendingToolEvents();
});
// 工具提示事件(可选)
ctx.socket.on('tool_hint', (data) => {
socketLog('工具提示:', data.name);
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过tool_hint(对话不匹配)', data.conversation_id);
return;
}
// 可以在这里添加提示UI
});
// 工具准备中事件 - 实时显示
ctx.socket.on('tool_preparing', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (ctx.dropToolEvents) {
return;
}
console.log('[DEBUG] 收到 tool_preparing 事件:', {
name: data.name,
id: data.id,
conversation_id: data?.conversation_id,
currentConversationId: ctx.currentConversationId,
match: data?.conversation_id === ctx.currentConversationId
});
socketLog('工具准备中:', data.name);
if (typeof console !== 'undefined' && console.debug) {
console.debug('[tool_intent] preparing', {
id: data?.id,
name: data?.name,
intent: data?.intent,
convo: data?.conversation_id
});
}
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
console.log('[DEBUG] 跳过 tool_preparing (对话不匹配)');
socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id);
return;
}
const msg = ctx.chatEnsureAssistantMessage();
if (!msg) {
console.log('[DEBUG] tool_preparing: 无法获取assistant消息');
return;
}
if (msg.awaitingFirstContent) {
msg.awaitingFirstContent = false;
msg.generatingLabel = '';
}
const action = {
id: data.id,
type: 'tool',
tool: {
id: data.id,
name: data.name,
arguments: {},
argumentSnapshot: null,
argumentLabel: '',
status: 'preparing',
result: null,
message: data.message || `准备调用 ${data.name}...`,
intent_full: data.intent || '',
intent_rendered: data.intent || ''
},
timestamp: Date.now()
};
msg.actions.push(action);
ctx.preparingTools.set(data.id, action);
ctx.toolRegisterAction(action, data.id);
ctx.toolTrackAction(data.name, action);
if (data.intent) {
startIntentTyping(action, data.intent);
}
console.log('[DEBUG] tool_preparing 处理完成action已添加');
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
if (ctx.monitorPreviewTool) {
ctx.monitorPreviewTool(data);
}
});
// 工具意图(流式增量)事件
ctx.socket.on('tool_intent', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (ctx.dropToolEvents) {
return;
}
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
return;
}
if (typeof console !== 'undefined' && console.debug) {
console.debug('[tool_intent] update', {
id: data?.id,
name: data?.name,
intent: data?.intent,
convo: data?.conversation_id
});
}
const target =
ctx.toolFindAction(data.id, data.preparing_id, data.execution_id) ||
ctx.toolGetLatestAction(data.name);
if (target) {
startIntentTyping(target, data.intent);
}
ctx.$forceUpdate();
});
// 工具状态更新事件 - 实时显示详细状态
ctx.socket.on('tool_status', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (ctx.dropToolEvents) {
return;
}
socketLog('工具状态:', data);
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过tool_status(对话不匹配)', data.conversation_id);
return;
}
const target = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
if (target) {
target.tool.statusDetail = data.detail;
target.tool.statusType = data.status;
if (data.intent) {
startIntentTyping(target, data.intent);
}
ctx.$forceUpdate();
return;
}
const fallbackAction = ctx.toolGetLatestAction(data.tool);
if (fallbackAction) {
fallbackAction.tool.statusDetail = data.detail;
fallbackAction.tool.statusType = data.status;
ctx.toolRegisterAction(fallbackAction, data.execution_id || data.id || data.preparing_id);
if (data.intent) {
startIntentTyping(fallbackAction, data.intent);
}
ctx.$forceUpdate();
}
});
// 工具开始(从准备转为执行)
ctx.socket.on('tool_start', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (ctx.dropToolEvents) {
return;
}
socketLog('工具开始执行:', data.name);
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过tool_start(对话不匹配)', data.conversation_id);
return;
}
const handler = () => {
let action = null;
if (data.preparing_id && ctx.preparingTools.has(data.preparing_id)) {
action = ctx.preparingTools.get(data.preparing_id);
ctx.preparingTools.delete(data.preparing_id);
} else {
action = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
}
if (!action) {
const msg = ctx.chatEnsureAssistantMessage();
if (!msg) {
return;
}
action = {
id: data.id,
type: 'tool',
tool: {
id: data.id,
name: data.name,
arguments: {},
argumentSnapshot: null,
argumentLabel: '',
status: 'running',
result: null
},
timestamp: Date.now()
};
msg.actions.push(action);
}
action.tool.status = 'running';
action.tool.arguments = data.arguments;
action.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
action.tool.argumentLabel = ctx.buildToolLabel(action.tool.argumentSnapshot);
action.tool.message = null;
action.tool.id = data.id;
action.tool.executionId = data.id;
if (data.arguments && data.arguments.intent) {
startIntentTyping(action, data.arguments.intent);
}
ctx.toolRegisterAction(action, data.id);
ctx.toolTrackAction(data.name, action);
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
ctx.monitorQueueTool(data);
if (data.name === 'view_video' && typeof ctx.uiPushToast === 'function') {
ctx.uiPushToast({
title: '视频读取中',
message: '读取视频需要较长时间,请耐心等待',
type: 'info',
duration: 5000
});
}
};
handler();
});
// 更新action工具完成
ctx.socket.on('update_action', (data) => {
// 轮询模式下跳过 WebSocket 事件
if (ctx.usePollingMode && !ctx.waitingForSubAgent) {
return;
}
if (ctx.dropToolEvents) {
return;
}
socketLog('更新action:', data.id, 'status:', data.status);
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过update_action(对话不匹配)', data.conversation_id);
return;
}
const handler = () => {
let targetAction = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id);
if (!targetAction && data.preparing_id && ctx.preparingTools.has(data.preparing_id)) {
targetAction = ctx.preparingTools.get(data.preparing_id);
}
if (!targetAction) {
outer: for (const message of ctx.messages) {
if (!message.actions) continue;
for (const action of message.actions) {
if (action.type !== 'tool') continue;
const matchByExecution =
action.tool.executionId && action.tool.executionId === data.id;
const matchByToolId = action.tool.id === data.id;
const matchByPreparingId = action.id === data.preparing_id;
if (matchByExecution || matchByToolId || matchByPreparingId) {
targetAction = action;
break outer;
}
}
}
}
if (targetAction) {
if (data.status) {
targetAction.tool.status = data.status;
}
if (data.result !== undefined) {
targetAction.tool.result = data.result;
}
if (
targetAction.tool &&
targetAction.tool.name === 'trigger_easter_egg' &&
data.result !== undefined
) {
const eggPromise = ctx.handleEasterEggPayload(data.result);
if (eggPromise && typeof eggPromise.catch === 'function') {
eggPromise.catch((error) => {
console.warn('彩蛋处理异常:', error);
});
}
}
// 个性化设置工具主题切换处理在 taskPolling.ts 中(当前使用轮询模式)
if (data.message !== undefined) {
targetAction.tool.message = data.message;
}
if (data.arguments && data.arguments.intent) {
startIntentTyping(targetAction, data.arguments.intent);
}
if (data.awaiting_content) {
targetAction.tool.awaiting_content = true;
} else if (data.status === 'completed') {
targetAction.tool.awaiting_content = false;
}
if (!targetAction.tool.executionId && (data.execution_id || data.id)) {
targetAction.tool.executionId = data.execution_id || data.id;
}
if (data.arguments) {
targetAction.tool.arguments = data.arguments;
targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments);
targetAction.tool.argumentLabel = ctx.buildToolLabel(
targetAction.tool.argumentSnapshot
);
if (data.arguments.intent) {
startIntentTyping(targetAction, data.arguments.intent);
}
}
ctx.toolRegisterAction(targetAction, data.execution_id || data.id);
if (
data.status &&
['completed', 'failed', 'error'].includes(data.status) &&
!data.awaiting_content
) {
ctx.toolUnregisterAction(targetAction);
if (data.id) {
ctx.preparingTools.delete(data.id);
}
if (data.preparing_id) {
ctx.preparingTools.delete(data.preparing_id);
}
}
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
}
if (data.status === 'completed') {
setTimeout(() => {
ctx.updateCurrentContextTokens();
}, 500);
try {
ctx.fileFetchTree();
} catch (error) {
console.warn('刷新文件树失败', error);
}
}
ctx.monitorResolveTool(data);
};
handler();
});
ctx.socket.on('append_payload', (data) => {
if (ctx.dropToolEvents) {
return;
}
socketLog('收到append_payload事件:', data);
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过append_payload(对话不匹配)', data.conversation_id);
return;
}
ctx.chatAddAppendPayloadAction({
path: data.path || '未知文件',
forced: !!data.forced,
success: data.success === undefined ? true : !!data.success,
lines: data.lines ?? null,
bytes: data.bytes ?? null
});
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
});
ctx.socket.on('modify_payload', (data) => {
if (ctx.dropToolEvents) {
return;
}
socketLog('收到modify_payload事件:', data);
if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) {
socketLog('跳过modify_payload(对话不匹配)', data.conversation_id);
return;
}
ctx.chatAddModifyPayloadAction({
path: data.path || '未知文件',
total: data.total ?? null,
completed: data.completed || [],
failed: data.failed || [],
forced: !!data.forced
});
ctx.$forceUpdate();
ctx.conditionalScrollToBottom();
});
// 停止请求确认
ctx.socket.on('stop_requested', (data) => {
socketLog('停止请求已接收:', data.message);
// 可以显示提示信息
try {
if (typeof ctx.clearPendingTools === 'function') {
ctx.clearPendingTools('socket:stop_requested');
}
} catch (error) {
console.warn('清理未完成工具失败', error);
}
});
// 任务停止
ctx.socket.on('task_stopped', (data) => {
socketLog('任务已停止:', data.message);
ctx.taskInProgress = false;
ctx.scheduleResetAfterTask('socket:task_stopped', { preserveMonitorWindows: true });
});
// 任务完成重点更新Token统计
ctx.socket.on('task_complete', (data) => {
console.log('[DEBUG] 收到 task_complete 事件:', data);
console.log('[DEBUG] 当前状态 (before task_complete):', {
taskInProgress: ctx.taskInProgress,
streamingMessage: ctx.streamingMessage,
has_running_sub_agents: data.has_running_sub_agents
});
socketLog('任务完成', data);
// 如果有运行中的子智能体,不重置任务状态
if (!data.has_running_sub_agents) {
console.log('[DEBUG] 没有运行中的子智能体,重置任务状态');
if (ctx.waitingForSubAgent) {
ctx.waitingForSubAgent = false;
}
ctx.taskInProgress = false;
ctx.scheduleResetAfterTask('socket:task_complete', { preserveMonitorWindows: true });
} else {
console.log('[DEBUG] 有运行中的子智能体,保持任务状态');
}
console.log('[DEBUG] 当前状态 (after task_complete):', {
taskInProgress: ctx.taskInProgress,
streamingMessage: ctx.streamingMessage
});
resetPendingToolEvents();
// 任务完成后立即更新Token统计关键修复
if (ctx.currentConversationId) {
ctx.updateCurrentContextTokens();
ctx.fetchConversationTokenStatistics();
}
});
// 子智能体等待状态
ctx.socket.on('sub_agent_waiting', (data) => {
console.log('[DEBUG] 收到 sub_agent_waiting 事件:', data);
console.log('[DEBUG] 当前状态 (before sub_agent_waiting):', {
taskInProgress: ctx.taskInProgress,
streamingMessage: ctx.streamingMessage,
stopRequested: ctx.stopRequested
});
socketLog('等待子智能体完成:', data);
// 设置标志:有子智能体在运行,阻止状态重置
ctx.waitingForSubAgent = true;
// 保持任务进行中状态
ctx.taskInProgress = true;
ctx.streamingMessage = false;
ctx.stopRequested = false;
console.log('[DEBUG] 当前状态 (after sub_agent_waiting):', {
taskInProgress: ctx.taskInProgress,
streamingMessage: ctx.streamingMessage,
stopRequested: ctx.stopRequested,
waitingForSubAgent: ctx.waitingForSubAgent
});
// 显示等待提示
if (typeof ctx.appendSystemAction === 'function') {
const taskList = data.tasks
.map((t: any) => `子智能体${t.agent_id} (${t.summary || '无描述'})`)
.join('、');
ctx.appendSystemAction(`⏳ 等待 ${data.count} 个后台子智能体完成:${taskList}`);
}
ctx.$forceUpdate();
console.log('[DEBUG] sub_agent_waiting 处理完成');
});
// 聚焦文件更新
ctx.socket.on('focused_files_update', (data) => {
ctx.focusSetFiles(data || {});
// 聚焦文件变化时更新当前上下文Token关键修复
if (ctx.currentConversationId) {
setTimeout(() => {
ctx.updateCurrentContextTokens();
}, 500);
}
});
// 文件树更新
ctx.socket.on('file_tree_update', (data) => {
ctx.fileSetTreeFromResponse(data);
// 文件树变化时也可能影响上下文
if (ctx.currentConversationId) {
setTimeout(() => {
ctx.updateCurrentContextTokens();
}, 500);
}
});
// 系统消息
ctx.socket.on('system_message', (data) => {
if (!data || !data.content) {
return;
}
ctx.appendSystemAction(data.content);
});
// 错误处理
ctx.socket.on('error', (data) => {
const msg = data?.message || '发生未知错误';
const code = data?.status_code;
const errType = data?.error_type;
const errCode = data?.error_code;
const dumpPath = data?.request_dump;
const baseUrl = data?.base_url;
const modelId = data?.model_id;
const conversationId = data?.conversation_id;
const taskId = data?.task_id;
const shouldRetry = Boolean(data?.retry);
const retryIn = Number(data?.retry_in) || 5;
const retryAttempt = Number(data?.attempt) || 1;
const retryMax = Number(data?.max_attempts) || retryAttempt;
const detailParts = [
dumpPath ? `请求记录: ${dumpPath}` : '',
baseUrl ? `接口: ${baseUrl}` : '',
modelId ? `模型: ${modelId}` : '',
conversationId ? `对话ID: ${conversationId}` : '',
taskId ? `任务ID: ${taskId}` : ''
].filter(Boolean);
const detailText = detailParts.length ? `\n${detailParts.join('\n')}` : '';
if (typeof ctx.uiPushToast === 'function') {
ctx.uiPushToast({
title: code ? `API错误 ${code}` : 'API错误',
message: `${errType ? `${errType}${errCode ? `(${errCode})` : ''}: ${msg}` : msg}${detailText}`,
type: 'error',
duration: 6000
});
if (shouldRetry) {
ctx.uiPushToast({
title: '即将重试',
message: `将在 ${retryIn} 秒后重试(第 ${retryAttempt}/${retryMax} 次)\n错误${msg}`,
type: 'info',
duration: Math.max(retryIn, 1) * 1000
});
}
}
if (shouldRetry) {
// 错误后保持停止按钮态,用户可手动停止或等待自动重试
ctx.stopRequested = false;
ctx.taskInProgress = true;
ctx.streamingMessage = true;
return;
}
if (typeof ctx.appendSystemAction === 'function') {
ctx.appendSystemAction(
`${code ? `[API ${code}] ` : '[API] '}${errType ? `${errType}${errCode ? `(${errCode})` : ''}: ` : ''}${msg}${detailText}`
);
}
// 最后一次报错:恢复输入状态并清理提示动画
const msgIndex = typeof ctx.currentMessageIndex === 'number' ? ctx.currentMessageIndex : -1;
if (msgIndex >= 0 && Array.isArray(ctx.messages)) {
const currentMessage = ctx.messages[msgIndex];
if (currentMessage && currentMessage.role === 'assistant') {
currentMessage.awaitingFirstContent = false;
currentMessage.generatingLabel = '';
}
}
if (typeof ctx.chatClearThinkingLocks === 'function') {
ctx.chatClearThinkingLocks();
}
ctx.streamingMessage = false;
ctx.stopRequested = false;
ctx.taskInProgress = false;
});
// 命令结果
ctx.socket.on('command_result', (data) => {
if (data.command === 'clear' && data.success) {
ctx.logMessageState?.('socket:command_result-clear', { data });
ctx.messages = [];
ctx.logMessageState?.('socket:command_result-cleared', { data });
ctx.currentMessageIndex = -1;
ctx.chatClearExpandedBlocks();
// 清除对话时重置Token统计
ctx.resetTokenStatistics();
} else if (data.command === 'status' && data.success) {
ctx.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`);
} else if (!data.success) {
ctx.addSystemMessage(`命令失败: ${data.message}`);
}
});
} catch (error) {
console.error('Socket初始化失败:', error);
}
}