改用 HTTP 加载会话历史并更新 CentOS 7 发布包
This commit is contained in:
343
public/app.js
343
public/app.js
@@ -104,7 +104,6 @@
|
||||
const OLD_SESSION_COLLAPSE_MS = OLD_SESSION_COLLAPSE_DAYS * 24 * 60 * 60 * 1000;
|
||||
const SESSION_LOAD_OVERLAY_TIMEOUT_MS = 12_000;
|
||||
const SESSION_LOAD_REQUEST_TIMEOUT_MS = 45_000;
|
||||
const SESSION_RESUME_FALLBACK_MS = 1_500;
|
||||
// session_list 可能在重连/原子写入窗口中暂时缺少当前会话;不要把一次快照抖动当成删除。
|
||||
const SESSION_LIST_MISSING_CONFIRM_MS = 1_500;
|
||||
const WS_CLIENT_HEARTBEAT_INTERVAL_MS = 15_000;
|
||||
@@ -231,7 +230,6 @@
|
||||
let isGenerating = false;
|
||||
let reconnectAttempts = 0;
|
||||
let reconnectTimer = null;
|
||||
let lastWsDisconnectNoticeAt = 0;
|
||||
let isPageUnloading = false;
|
||||
let pendingText = '';
|
||||
let renderTimer = null;
|
||||
@@ -281,7 +279,6 @@
|
||||
};
|
||||
let sessionLoadOverlayTimer = null;
|
||||
let sessionLoadRequestTimer = null;
|
||||
let sessionResumeFallbackTimer = null;
|
||||
let sidebarSwipe = null;
|
||||
let activeComposerToken = null;
|
||||
let composerSuggestionTimer = null;
|
||||
@@ -302,7 +299,11 @@
|
||||
let codexAppApprovalModal = null;
|
||||
let codexAppElicitationModal = null;
|
||||
let pendingNewSessionRequest = null;
|
||||
let pendingSessionSwitchRequest = null;
|
||||
let sessionLoadAbortController = null;
|
||||
let currentSessionViewRequestId = '';
|
||||
let currentSessionHistoryRevision = null;
|
||||
let currentSessionSubscriptionRequestId = '';
|
||||
const sessionCompletionVersions = new Map();
|
||||
let pendingSessionResumeRequest = null;
|
||||
let pendingMissingSessionConfirmation = null;
|
||||
let sessionSwitchRequestSeq = 0;
|
||||
@@ -2083,6 +2084,8 @@
|
||||
}
|
||||
|
||||
function resetOutlineHistoryState(sessionId = null) {
|
||||
activeOutlineHistoryRequest?.controller?.abort();
|
||||
activeOutlineTargetRequest?.controller?.abort();
|
||||
activeOutlineHistoryRequest = null;
|
||||
activeOutlineTargetRequest = null;
|
||||
currentOutlineHistoryState = createOutlineHistoryState(sessionId);
|
||||
@@ -2327,7 +2330,7 @@
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
if (activeOutlineTargetRequest || !ws || ws.readyState !== 1 || !wsAuthenticated) return;
|
||||
if (activeOutlineTargetRequest) return;
|
||||
pendingAdvancedSearchJump = {
|
||||
sessionId: currentSessionId,
|
||||
messageIndex: Math.trunc(normalizedIndex),
|
||||
@@ -2341,13 +2344,7 @@
|
||||
messageIndex: Math.trunc(normalizedIndex),
|
||||
};
|
||||
closeUserOutlinePanel();
|
||||
send({
|
||||
type: 'load_history_page',
|
||||
sessionId: currentSessionId,
|
||||
before: Math.trunc(normalizedIndex) + 1,
|
||||
requestId,
|
||||
purpose: 'outline-target',
|
||||
});
|
||||
fetchHistoryPage(activeOutlineTargetRequest, Math.trunc(normalizedIndex) + 1);
|
||||
}
|
||||
|
||||
function scrollToMessage(anchorId) {
|
||||
@@ -2929,6 +2926,7 @@
|
||||
historyTruncated,
|
||||
historyAvailable: payload.historyAvailable !== false,
|
||||
historySource: payload.historySource || 'snapshot',
|
||||
historyRevision: payload.historyRevision ?? null,
|
||||
historyPending,
|
||||
complete: !historyPending
|
||||
&& !historyTruncated
|
||||
@@ -6953,7 +6951,6 @@
|
||||
// 当前会话正在等待明确的加载/重连响应时,不能把等待中的视图清掉。
|
||||
const hasPendingLoad = !!(
|
||||
activeSessionLoad?.sessionId === sessionId
|
||||
|| pendingSessionSwitchRequest?.sessionId === sessionId
|
||||
|| pendingSessionResumeRequest?.sessionId === sessionId
|
||||
);
|
||||
if (pending.missingCount < 2 || hasPendingLoad) {
|
||||
@@ -7004,6 +7001,10 @@
|
||||
|
||||
function resetChatView(agent) {
|
||||
clearPendingMissingSessionConfirmation();
|
||||
currentSessionViewRequestId = '';
|
||||
currentSessionHistoryRevision = null;
|
||||
currentSessionSubscriptionRequestId = '';
|
||||
pendingSessionResumeRequest = null;
|
||||
setCurrentAgent(agent);
|
||||
closeUserOutlinePanel();
|
||||
closeCcwebPromptOutlinePanel();
|
||||
@@ -7184,25 +7185,11 @@
|
||||
sessionLoadRequestTimer = null;
|
||||
}
|
||||
|
||||
function clearSessionResumeFallbackTimer() {
|
||||
if (!sessionResumeFallbackTimer) return;
|
||||
clearTimeout(sessionResumeFallbackTimer);
|
||||
sessionResumeFallbackTimer = null;
|
||||
}
|
||||
|
||||
function clearPendingSessionSwitchRequest(sessionId, requestId) {
|
||||
if (!pendingSessionSwitchRequest) return;
|
||||
if (sessionId && pendingSessionSwitchRequest.sessionId !== sessionId) return;
|
||||
if (requestId && pendingSessionSwitchRequest.requestId !== requestId) return;
|
||||
pendingSessionSwitchRequest = null;
|
||||
}
|
||||
|
||||
function clearPendingSessionResumeRequest(sessionId, requestId) {
|
||||
if (!pendingSessionResumeRequest) return;
|
||||
if (sessionId && pendingSessionResumeRequest.sessionId !== sessionId) return;
|
||||
if (requestId && pendingSessionResumeRequest.requestId !== requestId) return;
|
||||
pendingSessionResumeRequest = null;
|
||||
clearSessionResumeFallbackTimer();
|
||||
}
|
||||
|
||||
function notifySessionLoadTimeout(sessionId) {
|
||||
@@ -7225,7 +7212,6 @@
|
||||
activeSessionLoad.requestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
clearPendingSessionSwitchRequest(sessionId, requestId);
|
||||
clearSessionLoading(sessionId);
|
||||
notifySessionLoadTimeout(sessionId);
|
||||
}, SESSION_LOAD_REQUEST_TIMEOUT_MS);
|
||||
@@ -7246,6 +7232,8 @@
|
||||
}
|
||||
|
||||
function setSessionLoading(sessionId, options = {}) {
|
||||
sessionLoadAbortController?.abort();
|
||||
sessionLoadAbortController = null;
|
||||
clearSessionLoadOverlayTimer();
|
||||
clearSessionLoadRequestTimer();
|
||||
const loading = !!sessionId;
|
||||
@@ -7258,7 +7246,7 @@
|
||||
requestId,
|
||||
overlayReleased: false,
|
||||
recoverCurrent: options.recoverCurrent === true,
|
||||
targetMessageIndex: Number.isFinite(Number(options.targetMessageIndex))
|
||||
targetMessageIndex: options.targetMessageIndex != null && Number.isFinite(Number(options.targetMessageIndex))
|
||||
? Math.max(0, Number(options.targetMessageIndex))
|
||||
: null,
|
||||
} : null;
|
||||
@@ -7292,6 +7280,7 @@
|
||||
}
|
||||
|
||||
function resetHistoryLoadState(sessionId = null) {
|
||||
activeHistoryPageRequest?.controller?.abort();
|
||||
activeHistoryPageRequest = null;
|
||||
currentHistoryState = {
|
||||
sessionId,
|
||||
@@ -7368,7 +7357,7 @@
|
||||
function requestOlderHistory() {
|
||||
const state = currentHistoryState;
|
||||
if (!state.sessionId || state.sessionId !== currentSessionId || state.cursor <= 0) return;
|
||||
if (activeHistoryPageRequest || !ws || ws.readyState !== 1 || !wsAuthenticated) return;
|
||||
if (activeHistoryPageRequest) return;
|
||||
historyPageRequestSeq += 1;
|
||||
const requestId = `history-page-${Date.now()}-${historyPageRequestSeq}`;
|
||||
activeHistoryPageRequest = {
|
||||
@@ -7378,34 +7367,24 @@
|
||||
};
|
||||
currentHistoryState = { ...state, loading: true, error: '' };
|
||||
renderHistoryLoadControl();
|
||||
send({
|
||||
type: 'load_history_page',
|
||||
sessionId: state.sessionId,
|
||||
before: state.cursor,
|
||||
requestId,
|
||||
});
|
||||
fetchHistoryPage(activeHistoryPageRequest, state.cursor);
|
||||
}
|
||||
|
||||
function requestOutlineHistoryPage() {
|
||||
const state = currentOutlineHistoryState;
|
||||
if (!state.sessionId || state.sessionId !== currentSessionId || state.cursor <= 0) return;
|
||||
if (activeOutlineHistoryRequest || !ws || ws.readyState !== 1 || !wsAuthenticated) return;
|
||||
if (activeOutlineHistoryRequest) return;
|
||||
outlineHistoryRequestSeq += 1;
|
||||
const requestId = `outline-history-${Date.now()}-${outlineHistoryRequestSeq}`;
|
||||
activeOutlineHistoryRequest = {
|
||||
sessionId: state.sessionId,
|
||||
requestId,
|
||||
before: state.cursor,
|
||||
purpose: 'outline',
|
||||
};
|
||||
currentOutlineHistoryState = { ...state, loading: true, error: '' };
|
||||
updateUserOutlinePanel();
|
||||
send({
|
||||
type: 'load_history_page',
|
||||
sessionId: state.sessionId,
|
||||
before: state.cursor,
|
||||
requestId,
|
||||
purpose: 'outline',
|
||||
});
|
||||
fetchHistoryPage(activeOutlineHistoryRequest, state.cursor);
|
||||
}
|
||||
|
||||
function mergeHistoryChunkIntoCachedSnapshot(sessionId, messages, options = {}) {
|
||||
@@ -7529,21 +7508,114 @@
|
||||
|
||||
function requestSessionLoad(sessionId, options = {}) {
|
||||
if (!sessionId) return;
|
||||
pendingSessionSwitchRequest = {
|
||||
sessionId,
|
||||
blocking: options.blocking !== false,
|
||||
label: options.label || '',
|
||||
requestId: options.requestId || activeSessionLoad?.requestId || createSessionSwitchRequestId(sessionId),
|
||||
recoverCurrent: options.recoverCurrent === true,
|
||||
targetMessageIndex: Number.isFinite(Number(options.targetMessageIndex))
|
||||
? Math.max(0, Number(options.targetMessageIndex))
|
||||
: activeSessionLoad?.targetMessageIndex ?? null,
|
||||
};
|
||||
if (ws && ws.readyState === 1 && wsAuthenticated) {
|
||||
flushPendingSessionSwitch();
|
||||
return;
|
||||
const requestId = options.requestId
|
||||
|| (activeSessionLoad?.sessionId === sessionId ? activeSessionLoad.requestId : '')
|
||||
|| createSessionSwitchRequestId(sessionId);
|
||||
const targetMessageIndex = options.targetMessageIndex != null && Number.isFinite(Number(options.targetMessageIndex))
|
||||
? Math.max(0, Number(options.targetMessageIndex))
|
||||
: activeSessionLoad?.targetMessageIndex ?? null;
|
||||
if (!activeSessionLoad || activeSessionLoad.sessionId !== sessionId || activeSessionLoad.requestId !== requestId) {
|
||||
setSessionLoading(sessionId, { ...options, requestId, targetMessageIndex });
|
||||
}
|
||||
if (!ws || ws.readyState > 1) connect();
|
||||
sessionLoadAbortController?.abort();
|
||||
if (activeHistoryPageRequest) {
|
||||
currentHistoryState = { ...currentHistoryState, loading: false };
|
||||
renderHistoryLoadControl();
|
||||
}
|
||||
if (activeOutlineHistoryRequest) currentOutlineHistoryState = { ...currentOutlineHistoryState, loading: false };
|
||||
activeHistoryPageRequest?.controller?.abort();
|
||||
activeOutlineHistoryRequest?.controller?.abort();
|
||||
activeOutlineTargetRequest?.controller?.abort();
|
||||
activeHistoryPageRequest = null;
|
||||
activeOutlineHistoryRequest = null;
|
||||
activeOutlineTargetRequest = null;
|
||||
const previousViewRequestId = currentSessionViewRequestId;
|
||||
currentSessionViewRequestId = requestId;
|
||||
if (options.subscribe !== false) {
|
||||
currentSessionSubscriptionRequestId = '';
|
||||
pendingSessionResumeRequest = null;
|
||||
}
|
||||
const controller = new AbortController();
|
||||
sessionLoadAbortController = controller;
|
||||
clearSessionLoadRequestTimer();
|
||||
const completionVersion = sessionCompletionVersions.get(sessionId) || 0;
|
||||
let applyingSnapshot = false;
|
||||
// 历史查询不等待 WebSocket;控制器和请求代次共同隔离迟到响应。
|
||||
fetchSessionQuery(sessionId, requestId, { signal: controller.signal }).then(payload => {
|
||||
if (sessionLoadAbortController !== controller || currentSessionViewRequestId !== requestId) return;
|
||||
// 已经收到完成事件的页面不能再被更早读出的运行快照覆盖。
|
||||
if ((sessionCompletionVersions.get(sessionId) || 0) !== completionVersion) {
|
||||
requestSessionLoad(sessionId, { ...options, targetMessageIndex,
|
||||
requestId: createSessionSwitchRequestId(sessionId) });
|
||||
return;
|
||||
}
|
||||
applyingSnapshot = true;
|
||||
sessionLoadAbortController = null;
|
||||
currentSessionHistoryRevision = payload.historyRevision ?? null;
|
||||
handleServerMessage(payload);
|
||||
if (currentSessionId !== sessionId || currentSessionViewRequestId !== requestId) return;
|
||||
// 订阅后的历史对账保留最新实时输出,不能用较早的 HTTP 运行快照覆盖它。
|
||||
if (payload.runtime && options.subscribe !== false) {
|
||||
handleServerMessage({ ...payload.runtime, requestId });
|
||||
if (payload.runtime.isRunning === false) finishGenerating(sessionId);
|
||||
}
|
||||
if (options.subscribe !== false) requestSessionResume(sessionId);
|
||||
if (targetMessageIndex !== null) requestOutlineMessageTarget(targetMessageIndex);
|
||||
applyingSnapshot = false;
|
||||
}).catch(error => {
|
||||
if ((!applyingSnapshot && sessionLoadAbortController !== controller) || currentSessionViewRequestId !== requestId) return;
|
||||
currentSessionViewRequestId = previousViewRequestId;
|
||||
clearSessionLoading(sessionId);
|
||||
appendError(error.message || '会话历史加载失败,请重试', { transient: true, autoDismissMs: 9000 });
|
||||
if (currentSessionId && options.subscribe !== false) requestSessionResume(currentSessionId);
|
||||
}).finally(() => {
|
||||
if (sessionLoadAbortController === controller) sessionLoadAbortController = null;
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchSessionQuery(sessionId, requestId, options = {}) {
|
||||
const controller = new AbortController();
|
||||
const abort = () => controller.abort();
|
||||
if (options.signal?.aborted) abort();
|
||||
options.signal?.addEventListener('abort', abort, { once: true });
|
||||
let timedOut = false;
|
||||
const timer = setTimeout(() => { timedOut = true; abort(); }, SESSION_LOAD_REQUEST_TIMEOUT_MS);
|
||||
try {
|
||||
const parameters = new URLSearchParams({ requestId });
|
||||
if (options.before !== undefined) parameters.set('before', String(options.before));
|
||||
const response = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}${options.historyPage ? '/history' : ''}?${parameters}`, {
|
||||
method: 'GET', headers: { Authorization: `Bearer ${authToken}` }, cache: 'no-store', signal: controller.signal,
|
||||
});
|
||||
const payload = await response.json();
|
||||
if (!response.ok) throw new Error(payload.message || '会话历史加载失败,请重试');
|
||||
if (payload.sessionId !== sessionId || payload.requestId !== requestId) throw new Error('会话历史响应与请求不匹配');
|
||||
if (payload.type !== (options.historyPage ? 'session_history_chunk' : 'session_info') || !Array.isArray(payload.messages)) {
|
||||
throw new Error('会话历史响应格式无效');
|
||||
}
|
||||
return payload;
|
||||
} catch (error) {
|
||||
if (timedOut) throw new Error('会话历史 HTTP 请求超时,请重试');
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
options.signal?.removeEventListener('abort', abort);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchHistoryPage(request, before) {
|
||||
request.controller = new AbortController();
|
||||
const viewRequestId = currentSessionViewRequestId;
|
||||
const isActive = () => currentSessionId === request.sessionId
|
||||
&& currentSessionViewRequestId === viewRequestId
|
||||
&& [activeHistoryPageRequest, activeOutlineHistoryRequest, activeOutlineTargetRequest].includes(request);
|
||||
fetchSessionQuery(request.sessionId, request.requestId, {
|
||||
historyPage: true, before, signal: request.controller.signal,
|
||||
}).then(payload => {
|
||||
if (isActive()) handleServerMessage(payload);
|
||||
}).catch(error => {
|
||||
if (isActive()) handleServerMessage({ type: 'error', sessionId: request.sessionId,
|
||||
requestId: request.requestId, message: error.message || '加载更早消息失败,请重试' });
|
||||
});
|
||||
}
|
||||
|
||||
function requestSessionResume(sessionId, options = {}) {
|
||||
@@ -7552,6 +7624,7 @@
|
||||
sessionId,
|
||||
requestId: options.requestId || createSessionSwitchRequestId(sessionId),
|
||||
};
|
||||
currentSessionSubscriptionRequestId = pendingSessionResumeRequest.requestId;
|
||||
if (ws && ws.readyState === 1 && wsAuthenticated) {
|
||||
flushPendingSessionResume();
|
||||
return;
|
||||
@@ -7559,56 +7632,18 @@
|
||||
if (!ws || ws.readyState > 1) connect();
|
||||
}
|
||||
|
||||
function scheduleSessionResumeFallback(sessionId, requestId) {
|
||||
clearSessionResumeFallbackTimer();
|
||||
if (!sessionId || !requestId) return;
|
||||
sessionResumeFallbackTimer = setTimeout(() => {
|
||||
sessionResumeFallbackTimer = null;
|
||||
if (!pendingSessionResumeRequest ||
|
||||
pendingSessionResumeRequest.sessionId !== sessionId ||
|
||||
pendingSessionResumeRequest.requestId !== requestId) {
|
||||
return;
|
||||
}
|
||||
pendingSessionResumeRequest = null;
|
||||
requestSessionLoad(sessionId, {
|
||||
blocking: false,
|
||||
label: '正在恢复运行输出…',
|
||||
requestId: createSessionSwitchRequestId(sessionId),
|
||||
recoverCurrent: true,
|
||||
});
|
||||
}, SESSION_RESUME_FALLBACK_MS);
|
||||
}
|
||||
|
||||
function flushPendingSessionSwitch() {
|
||||
if (!pendingSessionSwitchRequest) return false;
|
||||
if (!ws || ws.readyState !== 1 || !wsAuthenticated) return false;
|
||||
const request = pendingSessionSwitchRequest;
|
||||
pendingSessionSwitchRequest = null;
|
||||
if (!activeSessionLoad) {
|
||||
setSessionLoading(request.sessionId, {
|
||||
blocking: request.blocking,
|
||||
label: request.label || undefined,
|
||||
requestId: request.requestId,
|
||||
recoverCurrent: request.recoverCurrent === true,
|
||||
targetMessageIndex: request.targetMessageIndex,
|
||||
});
|
||||
}
|
||||
ws.send(JSON.stringify({
|
||||
type: 'load_session',
|
||||
sessionId: request.sessionId,
|
||||
requestId: request.requestId,
|
||||
...(Number.isFinite(request.targetMessageIndex) ? { targetMessageIndex: request.targetMessageIndex } : {}),
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
function flushPendingSessionResume() {
|
||||
if (!pendingSessionResumeRequest) return false;
|
||||
if (!ws || ws.readyState !== 1 || !wsAuthenticated) return false;
|
||||
const request = pendingSessionResumeRequest;
|
||||
ws.send(JSON.stringify({ type: 'resume_session', sessionId: request.sessionId, requestId: request.requestId }));
|
||||
scheduleSessionResumeFallback(request.sessionId, request.requestId);
|
||||
return true;
|
||||
try {
|
||||
ws.send(JSON.stringify({ type: 'subscribe_session', sessionId: request.sessionId,
|
||||
requestId: request.requestId, historyRevision: currentSessionHistoryRevision }));
|
||||
return true;
|
||||
} catch {
|
||||
showToast('实时订阅暂未连接,历史仍可查看');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function showCachedSession(sessionId) {
|
||||
@@ -7622,7 +7657,13 @@
|
||||
closeCcwebPromptOutlinePanel();
|
||||
clearSessionLoading();
|
||||
touchSessionCache(sessionId);
|
||||
return applySessionSnapshot(snapshot, { immediate: true, suppressUnreadToast: true });
|
||||
const applied = applySessionSnapshot(snapshot, { immediate: true, suppressUnreadToast: true });
|
||||
if (applied) {
|
||||
currentSessionViewRequestId = createSessionSwitchRequestId(sessionId);
|
||||
currentSessionHistoryRevision = snapshot.historyRevision ?? null;
|
||||
requestSessionResume(sessionId);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
function openSession(sessionId, options = {}) {
|
||||
@@ -8212,55 +8253,14 @@
|
||||
wsAuthenticated = false;
|
||||
wsSupportsClientHeartbeat = false;
|
||||
clearWsHeartbeat();
|
||||
if (activeOutlineHistoryRequest) {
|
||||
activeOutlineHistoryRequest = null;
|
||||
currentOutlineHistoryState = {
|
||||
...currentOutlineHistoryState,
|
||||
loading: false,
|
||||
error: '连接中断,连接恢复后可重试',
|
||||
};
|
||||
if (userOutlinePanel && !userOutlinePanel.hidden) updateUserOutlinePanel();
|
||||
}
|
||||
if (activeOutlineTargetRequest) {
|
||||
activeOutlineTargetRequest = null;
|
||||
pendingAdvancedSearchJump = null;
|
||||
if (!isPageUnloading) {
|
||||
appendError('连接中断,定位消息加载失败;连接恢复后请重新点击该条目。', {
|
||||
transient: true,
|
||||
autoDismissMs: 7000,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (activeHistoryPageRequest) {
|
||||
activeHistoryPageRequest = null;
|
||||
currentHistoryState = {
|
||||
...currentHistoryState,
|
||||
loading: false,
|
||||
error: '连接中断,连接恢复后可重试',
|
||||
};
|
||||
renderHistoryLoadControl();
|
||||
}
|
||||
if (activeSessionLoad?.sessionId && !isPageUnloading) {
|
||||
pendingSessionSwitchRequest = {
|
||||
sessionId: activeSessionLoad.sessionId,
|
||||
blocking: false,
|
||||
label: sessionLoadingLabel?.textContent || '',
|
||||
requestId: activeSessionLoad.requestId || createSessionSwitchRequestId(activeSessionLoad.sessionId),
|
||||
recoverCurrent: activeSessionLoad.recoverCurrent === true,
|
||||
targetMessageIndex: activeSessionLoad.targetMessageIndex,
|
||||
};
|
||||
const now = Date.now();
|
||||
if (now - lastWsDisconnectNoticeAt >= 5000) {
|
||||
lastWsDisconnectNoticeAt = now;
|
||||
showToast('连接中断,正在重连并自动重试会话切换…');
|
||||
}
|
||||
} else if (currentSessionId && (isGenerating || currentSessionRunning) && !isPageUnloading) {
|
||||
// 长连接中断只影响实时订阅,正在进行的 HTTP 首屏和分页继续完成。
|
||||
if (currentSessionId && !isPageUnloading) {
|
||||
pendingSessionResumeRequest = {
|
||||
sessionId: currentSessionId,
|
||||
requestId: createSessionSwitchRequestId(currentSessionId),
|
||||
};
|
||||
currentSessionSubscriptionRequestId = pendingSessionResumeRequest.requestId;
|
||||
}
|
||||
clearSessionLoading();
|
||||
scheduleReconnect();
|
||||
};
|
||||
socket.onerror = () => {};
|
||||
@@ -8359,14 +8359,11 @@
|
||||
app.hidden = false;
|
||||
setUsageDashboardFeatureEnabled(msg.features?.usageStatistics === true);
|
||||
setTaskBoardFeatureEnabled(msg.features?.taskBoard === true);
|
||||
const flushedSessionSwitch = flushPendingSessionSwitch();
|
||||
const flushedSessionResume = flushedSessionSwitch ? false : flushPendingSessionResume();
|
||||
if (!flushedSessionSwitch &&
|
||||
const flushedSessionResume = sessionLoadAbortController ? false : flushPendingSessionResume();
|
||||
if (!sessionLoadAbortController &&
|
||||
!flushedSessionResume &&
|
||||
!pendingSessionSwitchRequest &&
|
||||
!pendingSessionResumeRequest &&
|
||||
currentSessionId &&
|
||||
(isGenerating || currentSessionRunning)) {
|
||||
currentSessionId) {
|
||||
requestSessionResume(currentSessionId, {
|
||||
requestId: createSessionSwitchRequestId(currentSessionId),
|
||||
});
|
||||
@@ -8381,9 +8378,8 @@
|
||||
} else {
|
||||
clearWsHeartbeat();
|
||||
wsSupportsClientHeartbeat = false;
|
||||
pendingSessionSwitchRequest = null;
|
||||
pendingSessionResumeRequest = null;
|
||||
clearSessionResumeFallbackTimer();
|
||||
currentSessionSubscriptionRequestId = '';
|
||||
clearSessionLoading();
|
||||
authToken = null;
|
||||
wsAuthenticated = false;
|
||||
@@ -8841,6 +8837,10 @@
|
||||
break;
|
||||
|
||||
case 'done':
|
||||
if (msg.sessionId) {
|
||||
sessionCompletionVersions.set(msg.sessionId, (sessionCompletionVersions.get(msg.sessionId) || 0) + 1);
|
||||
if (sessionCompletionVersions.size > 2048) sessionCompletionVersions.delete(sessionCompletionVersions.keys().next().value);
|
||||
}
|
||||
if (!isCurrentSessionEvent(msg)) {
|
||||
if (msg.sessionId) {
|
||||
updateCachedSession(msg.sessionId, (snapshot) => { snapshot.isRunning = msg.goalActive === true; });
|
||||
@@ -8927,8 +8927,29 @@
|
||||
}
|
||||
break;
|
||||
|
||||
case 'session_subscribed':
|
||||
if (msg.sessionId !== currentSessionId || String(msg.requestId || '') !== currentSessionSubscriptionRequestId) break;
|
||||
clearPendingSessionResumeRequest(msg.sessionId, msg.requestId);
|
||||
const subscribedMeta = getSessionMeta(msg.sessionId);
|
||||
if (subscribedMeta) subscribedMeta.hasUnread = false;
|
||||
updateCachedSession(msg.sessionId, cached => { cached.hasUnread = false; });
|
||||
renderSessionList();
|
||||
if (msg.historyChanged) {
|
||||
requestSessionLoad(msg.sessionId, {
|
||||
requestId: createSessionSwitchRequestId(msg.sessionId), blocking: false,
|
||||
recoverCurrent: true, subscribe: false,
|
||||
});
|
||||
} else {
|
||||
currentSessionHistoryRevision = msg.historyRevision ?? null;
|
||||
updateCachedSession(msg.sessionId, cached => { cached.historyRevision = currentSessionHistoryRevision; });
|
||||
setCurrentSessionRunningState(!!msg.isRunning);
|
||||
if (!msg.isRunning && isGenerating) finishGenerating(msg.sessionId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'resume_generating':
|
||||
if (!isCurrentSessionEvent(msg)) break;
|
||||
if (msg.requestId && msg.requestId !== currentSessionViewRequestId && msg.requestId !== currentSessionSubscriptionRequestId) break;
|
||||
clearPendingSessionResumeRequest(msg.sessionId || currentSessionId, msg.requestId);
|
||||
// Server has an active process for this session — resume streaming
|
||||
setCurrentSessionRunningState(true);
|
||||
@@ -9002,6 +9023,11 @@
|
||||
break;
|
||||
}
|
||||
const errorRequestId = String(msg.requestId || '');
|
||||
if (errorRequestId && errorRequestId === currentSessionSubscriptionRequestId) {
|
||||
clearPendingSessionResumeRequest(msg.sessionId, errorRequestId);
|
||||
showToast(msg.message || '实时订阅失败,历史仍可查看');
|
||||
break;
|
||||
}
|
||||
const matchesOutlineTargetError = !!(activeOutlineTargetRequest
|
||||
&& (!msg.sessionId || msg.sessionId === activeOutlineTargetRequest.sessionId)
|
||||
&& errorRequestId === activeOutlineTargetRequest.requestId);
|
||||
@@ -9044,9 +9070,6 @@
|
||||
(!msg.sessionId || msg.sessionId === activeSessionLoad.sessionId) &&
|
||||
(!errorRequestId || errorRequestId === activeSessionLoad.requestId));
|
||||
if (!matchesActiveLoadError && !isCurrentSessionEvent(msg)) break;
|
||||
if (matchesActiveLoadError) {
|
||||
clearPendingSessionSwitchRequest(activeSessionLoad.sessionId, activeSessionLoad.requestId);
|
||||
}
|
||||
if (msg.code === 'new_session_cwd_missing' && pendingNewSessionRequest?.cwd) {
|
||||
const request = pendingNewSessionRequest;
|
||||
pendingNewSessionRequest = null;
|
||||
|
||||
Reference in New Issue
Block a user