chore: rebuild CentOS7 release package
This commit is contained in:
@@ -614,6 +614,102 @@ function assertFrontendMcpReloadContract() {
|
||||
assert(source.includes('MCP 启动失败'), 'Frontend should expose a failed startup toast');
|
||||
}
|
||||
|
||||
function assertSessionSwitchResilienceContract() {
|
||||
const frontendSource = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const serverSource = fs.readFileSync(SERVER_PATH, 'utf8');
|
||||
const runtimeSource = fs.readFileSync(path.join(REPO_DIR, 'lib', 'agent-runtime.js'), 'utf8');
|
||||
|
||||
assert(frontendSource.includes('SESSION_LOAD_REQUEST_TIMEOUT_MS'), 'Frontend should define a hard timeout for session load requests');
|
||||
assert(frontendSource.includes('sessionLoadRequestTimer'), 'Frontend should track the session load request timeout timer');
|
||||
assert(frontendSource.includes('function clearPendingSessionSwitchRequest'), 'Frontend should be able to cancel stale pending session switch requests');
|
||||
assert(frontendSource.includes('function scheduleSessionLoadRequestTimeout'), 'Frontend should schedule cancellation for stuck load_session requests');
|
||||
assert(frontendSource.includes('pendingSessionResumeRequest'), 'Frontend should track lightweight running-session resume requests');
|
||||
assert(frontendSource.includes('SESSION_RESUME_FALLBACK_MS'), 'Frontend should keep a compatibility fallback for servers without resume_session');
|
||||
assert(frontendSource.includes('function requestSessionResume'), 'Frontend should request running-session resume without full history reload');
|
||||
assert(frontendSource.includes("type: 'resume_session'"), 'Frontend should use resume_session for reconnecting running conversations');
|
||||
assert(frontendSource.includes("case 'resume_session_result':"), 'Frontend should handle lightweight resume results');
|
||||
assert(frontendSource.includes('recoverCurrent: true'), 'Frontend fallback load_session should preserve the current running view');
|
||||
const visibilityStart = frontendSource.indexOf("document.addEventListener('visibilitychange'");
|
||||
const visibilityEnd = visibilityStart >= 0 ? frontendSource.indexOf("if (!authToken)", visibilityStart) : -1;
|
||||
const visibilitySource = visibilityStart >= 0 && visibilityEnd > visibilityStart
|
||||
? frontendSource.slice(visibilityStart, visibilityEnd)
|
||||
: '';
|
||||
assert(visibilitySource.includes('requestSessionResume(currentSessionId'), 'Visibility restore should use lightweight resume for running conversations');
|
||||
assert(visibilitySource.includes("send({ type: 'list_sessions' });"), 'Visibility restore should only refresh session list for idle conversations');
|
||||
assert(!visibilitySource.includes("type: 'load_session'"), 'Visibility restore must not force load_session and rerender the current conversation');
|
||||
assert(!visibilitySource.includes('beginSessionSwitch('), 'Visibility restore must not force a session switch and scroll to bottom');
|
||||
assert(
|
||||
/else if \(currentSessionId && \(isGenerating \|\| currentSessionRunning\)[\s\S]*?pendingSessionResumeRequest\s*=\s*\{/.test(frontendSource),
|
||||
'Frontend should queue a lightweight resume request for running conversations when WS closes'
|
||||
);
|
||||
assert(
|
||||
/const flushedSessionResume = flushedSessionSwitch \? false : flushPendingSessionResume\(\);[\s\S]*?requestSessionResume\(currentSessionId/.test(frontendSource),
|
||||
'Frontend should resume the current running session after auth without forcing load_session'
|
||||
);
|
||||
assert(
|
||||
/case 'background_done':[\s\S]*?if \(isNearBottom\(\)\)[\s\S]*?openSession\(msg\.sessionId,\s*\{ forceSync: true, blocking: false \}\)[\s\S]*?send\(\{ type: 'list_sessions' \}\)/.test(frontendSource),
|
||||
'Frontend should not auto-rerender the current session on background_done while the user is reading history'
|
||||
);
|
||||
assert(
|
||||
/function startGenerating\(sessionId = currentSessionId, options = \{\}\)[\s\S]*?const shouldFollow = options\.follow !== false[\s\S]*?if \(shouldFollow\)/.test(frontendSource),
|
||||
'Frontend resume_generating should be able to create a streaming bubble without forcing scroll-to-bottom'
|
||||
);
|
||||
assert(
|
||||
/const preserveStreaming = !!\(options\.preserveStreaming[\s\S]*?\(isGenerating \|\| currentSessionRunning \|\| hasStreamingElement\)\)/.test(frontendSource),
|
||||
'Frontend should preserve the current running conversation DOM even when isGenerating was stale'
|
||||
);
|
||||
assert(
|
||||
/case 'session_history_chunk':[\s\S]*?activeSessionLoad\?\.recoverCurrent[\s\S]*?break;/.test(frontendSource),
|
||||
'Frontend should ignore history chunks from recovery fallback to avoid duplicating/redrawing bubbles'
|
||||
);
|
||||
assert(
|
||||
/function flushPendingSessionSwitch\(\)[\s\S]*?return false[\s\S]*?return true/.test(frontendSource),
|
||||
'Frontend flushPendingSessionSwitch should report whether it sent a load_session request'
|
||||
);
|
||||
assert(
|
||||
/pendingSessionSwitchRequest\s*=\s*\{[\s\S]*?blocking:\s*false[\s\S]*?requestId:\s*activeSessionLoad\.requestId/.test(frontendSource),
|
||||
'Frontend should retry load_session after WS close without re-blocking the UI'
|
||||
);
|
||||
assert(
|
||||
frontendSource.includes('!messageRequestId && !currentSessionId && !activeLoad && !pendingNewSession'),
|
||||
'Frontend should not let late requestId-bearing session_info switch an idle/welcome view'
|
||||
);
|
||||
assert(
|
||||
frontendSource.includes('matchesActiveLoadError') && frontendSource.includes('errorRequestId === activeSessionLoad.requestId'),
|
||||
'Frontend should clear active session load errors by requestId'
|
||||
);
|
||||
|
||||
assert(serverSource.includes('SESSION_TRANSPORT_MESSAGE_CONTENT_MAX_CHARS'), 'Server should cap session message size for WebSocket transport');
|
||||
assert(serverSource.includes("case 'resume_session':"), 'Server should accept lightweight resume_session requests');
|
||||
assert(serverSource.includes('function handleResumeSession'), 'Server should implement lightweight running-session resume');
|
||||
assert(serverSource.includes('function attachActiveRuntimeToWs'), 'Server should share runtime re-attach logic without sending session_info first');
|
||||
assert(serverSource.includes('WS_HEARTBEAT_MAX_MISSES'), 'Server should tolerate missed WebSocket pongs before terminating');
|
||||
assert(serverSource.includes('function markWsActivity'), 'Server should mark WebSocket activity on send/message/pong');
|
||||
assert(
|
||||
serverSource.includes('markWsActivity(ws);') && serverSource.includes('markWsActivity(ws);'),
|
||||
'Server should call markWsActivity from WebSocket send and message paths'
|
||||
);
|
||||
assert(
|
||||
/hasRecentActivity[\s\S]*?_ccWebMissedPongs[\s\S]*?ws_heartbeat_terminate/.test(serverSource),
|
||||
'Server heartbeat should consider recent activity and log before terminating stale sockets'
|
||||
);
|
||||
assert(serverSource.includes('function sanitizeMessagesForTransport'), 'Server should sanitize session messages before WebSocket transport');
|
||||
assert(
|
||||
/function splitHistoryMessages\(messages[\s\S]*?const list = sanitizeMessagesForTransport\(messages\)/.test(serverSource),
|
||||
'Server history split should operate on transport-sanitized messages'
|
||||
);
|
||||
assert(
|
||||
/function wsSend\(ws, data\)[\s\S]*?try\s*\{[\s\S]*?JSON\.stringify\(data\)[\s\S]*?catch/.test(serverSource),
|
||||
'Server wsSend should guard JSON serialization failures'
|
||||
);
|
||||
assert(
|
||||
runtimeSource.includes('CC_WEB_RUNTIME_FULL_TEXT_MAX_CHARS') &&
|
||||
runtimeSource.includes('function appendCappedText') &&
|
||||
runtimeSource.includes('entry.fullText = appendCappedText'),
|
||||
'Classic runtime should cap accumulated fullText in memory'
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
assertFrontendGenerationControlsContract();
|
||||
assertFrontendComposerMcpContract();
|
||||
@@ -621,6 +717,7 @@ async function main() {
|
||||
assertFrontendMarkdownLinkContract();
|
||||
assertMockCodexAppPromptUserNotTextTriggered();
|
||||
assertFrontendMcpReloadContract();
|
||||
assertSessionSwitchResilienceContract();
|
||||
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cc-web-regression-'));
|
||||
const configDir = path.join(tempRoot, 'config');
|
||||
@@ -756,6 +853,13 @@ async function main() {
|
||||
const { ws, messages, token } = await connectWs(port, password);
|
||||
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'session_list');
|
||||
ws.send(JSON.stringify({ type: 'load_session', sessionId: 'missing-session', requestId: 'reg-missing-session' }));
|
||||
const missingSessionLoad = await nextMessage(messages, ws, (msg) => (
|
||||
msg.type === 'error' &&
|
||||
msg.code === 'session_not_found' &&
|
||||
msg.sessionId === 'missing-session'
|
||||
));
|
||||
assert(missingSessionLoad.requestId === 'reg-missing-session', 'Missing load_session error should echo requestId for frontend cleanup');
|
||||
|
||||
const pickerRoot = path.join(homeDir, 'picker-root');
|
||||
mkdirp(path.join(pickerRoot, 'alpha'));
|
||||
@@ -1977,7 +2081,11 @@ async function main() {
|
||||
assert(codexAppImportItemAfter?.alreadyImported === true, 'Codex App import listing should mark codexAppThreadId as imported');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'delete_session', sessionId: importedCodexApp.sessionId }));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'session_list' && !msg.sessions.some((s) => s.id === importedCodexApp.sessionId));
|
||||
await nextMessage(messages, ws, (msg) => (
|
||||
msg.type === 'session_list' &&
|
||||
!msg.sessions.some((s) => s.id === importedCodexApp.sessionId) &&
|
||||
!fs.existsSync(importedCodexAppPath)
|
||||
));
|
||||
assert(!fs.existsSync(importedCodexAppPath), 'Deleting Codex App imported session did not remove cc-web session JSON');
|
||||
assert(fs.existsSync(codexAppImportFixture.rolloutPath), 'Deleting Codex App imported session should keep rollout history for recovery');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user