chore: rebuild CentOS7 release package

This commit is contained in:
shiyue
2026-07-03 08:53:37 +08:00
parent d816ae28b9
commit faf6adceb7
7 changed files with 499 additions and 82 deletions

229
server.js
View File

@@ -105,6 +105,10 @@ const SESSION_MESSAGE_CONTENT_MAX_CHARS = readPositiveIntEnv('CC_WEB_SESSION_MES
const SESSION_TOOL_INPUT_MAX_CHARS = readPositiveIntEnv('CC_WEB_SESSION_TOOL_INPUT_MAX_CHARS', 16 * 1024, { min: 1024 });
const SESSION_TOOL_RESULT_MAX_CHARS = readPositiveIntEnv('CC_WEB_SESSION_TOOL_RESULT_MAX_CHARS', 32 * 1024, { min: 1024 });
const SESSION_MAX_TOOL_CALLS_PER_MESSAGE = readPositiveIntEnv('CC_WEB_SESSION_MAX_TOOL_CALLS_PER_MESSAGE', 80, { min: 1, max: 1000 });
const SESSION_TRANSPORT_MESSAGE_CONTENT_MAX_CHARS = readPositiveIntEnv('CC_WEB_SESSION_TRANSPORT_MESSAGE_CONTENT_MAX_CHARS', 64 * 1024, { min: 4096 });
const SESSION_TRANSPORT_TOOL_INPUT_MAX_CHARS = readPositiveIntEnv('CC_WEB_SESSION_TRANSPORT_TOOL_INPUT_MAX_CHARS', 8 * 1024, { min: 1024 });
const SESSION_TRANSPORT_TOOL_RESULT_MAX_CHARS = readPositiveIntEnv('CC_WEB_SESSION_TRANSPORT_TOOL_RESULT_MAX_CHARS', 16 * 1024, { min: 1024 });
const SESSION_TRANSPORT_MAX_TOOL_CALLS_PER_MESSAGE = readPositiveIntEnv('CC_WEB_SESSION_TRANSPORT_MAX_TOOL_CALLS_PER_MESSAGE', 40, { min: 1, max: 1000 });
const HISTORY_PREFETCH_CHUNKS = readPositiveIntEnv('CC_WEB_HISTORY_PREFETCH_CHUNKS', 3, { min: 0, max: 20 });
const HISTORY_MAX_CHUNKS_PER_LOAD = readPositiveIntEnv('CC_WEB_HISTORY_MAX_CHUNKS_PER_LOAD', 8, { min: 1, max: 100 });
const CODEX_APP_STATE_MAX_BYTES = readPositiveIntEnv('CC_WEB_CODEX_APP_STATE_MAX_BYTES', 2 * 1024 * 1024, { min: 128 * 1024 });
@@ -1276,7 +1280,25 @@ const MIME_TYPES = {
// === Utility Functions ===
function wsSend(ws, data) {
if (ws && ws.readyState === 1) ws.send(JSON.stringify(data));
if (!ws || ws.readyState !== 1) return;
try {
ws.send(JSON.stringify(data));
markWsActivity(ws);
} catch (err) {
plog('WARN', 'ws_send_failed', {
wsId: ws._ccWebId || null,
type: data?.type || null,
sessionId: data?.sessionId ? String(data.sessionId).slice(0, 8) : null,
error: err?.message || String(err || ''),
});
}
}
function markWsActivity(ws) {
if (!ws) return;
ws.isAlive = true;
ws._ccWebLastActivityAt = Date.now();
ws._ccWebMissedPongs = 0;
}
function sanitizeId(id) {
@@ -3508,6 +3530,21 @@ function sanitizeMessagesForPersist(messages, limits = {}) {
return output;
}
function sanitizeMessageForTransport(message) {
return sanitizeMessageForPersist(message, {
contentMaxChars: SESSION_TRANSPORT_MESSAGE_CONTENT_MAX_CHARS,
toolInputMaxChars: SESSION_TRANSPORT_TOOL_INPUT_MAX_CHARS,
toolResultMaxChars: SESSION_TRANSPORT_TOOL_RESULT_MAX_CHARS,
maxToolCalls: SESSION_TRANSPORT_MAX_TOOL_CALLS_PER_MESSAGE,
metaMaxChars: SESSION_TRANSPORT_MESSAGE_CONTENT_MAX_CHARS,
});
}
function sanitizeMessagesForTransport(messages) {
const list = Array.isArray(messages) ? messages : [];
return list.map((message) => sanitizeMessageForTransport(message));
}
function sanitizeSessionForPersist(session, limits = {}) {
const output = {};
const skipKeys = new Set([
@@ -3755,7 +3792,7 @@ function sessionModelLabel(session) {
}
function splitHistoryMessages(messages, options = {}) {
const list = Array.isArray(messages) ? messages : [];
const list = sanitizeMessagesForTransport(messages);
if (list.length <= INITIAL_HISTORY_COUNT) {
return { recentMessages: list, olderChunks: [], historyRemaining: 0, historyBuffered: list.length };
}
@@ -6147,6 +6184,7 @@ const server = http.createServer((req, res) => {
// === WebSocket Server ===
const wss = new WebSocketServer({ noServer: true });
const WS_HEARTBEAT_INTERVAL_MS = 30000;
const WS_HEARTBEAT_MAX_MISSES = readPositiveIntEnv('CC_WEB_WS_HEARTBEAT_MAX_MISSES', 4, { min: 2, max: 20 });
server.on('upgrade', (req, socket, head) => {
let pathname = '';
@@ -6184,14 +6222,17 @@ wss.on('connection', (ws, req) => {
const wsId = crypto.randomBytes(4).toString('hex'); // short id for log correlation
const wsConnectTime = new Date().toISOString();
ws.isAlive = true;
ws._ccWebMissedPongs = 0;
ws._ccWebId = wsId;
markWsActivity(ws);
plog('INFO', 'ws_connect', { wsId });
ws.on('pong', () => {
ws.isAlive = true;
markWsActivity(ws);
});
ws.on('message', (raw) => {
markWsActivity(ws);
let msg;
try {
msg = JSON.parse(raw);
@@ -6244,6 +6285,9 @@ wss.on('connection', (ws, req) => {
case 'load_session':
handleLoadSession(ws, msg);
break;
case 'resume_session':
handleResumeSession(ws, msg);
break;
case 'load_history_page':
handleLoadHistoryPage(ws, msg);
break;
@@ -6339,19 +6383,36 @@ wss.on('connection', (ws, req) => {
// WebSocket 心跳:避免反向代理因空闲连接关闭 /ws。
const wsHeartbeatTimer = setInterval(() => {
const now = Date.now();
for (const client of wss.clients) {
if (client.readyState !== 1) continue;
if (client.isAlive === false) {
client.terminate();
continue;
const lastActivityAt = Number(client._ccWebLastActivityAt || 0);
const hasRecentActivity = lastActivityAt > 0 && now - lastActivityAt < WS_HEARTBEAT_INTERVAL_MS * 2;
if (hasRecentActivity) {
client._ccWebMissedPongs = 0;
} else {
client._ccWebMissedPongs = Number(client._ccWebMissedPongs || 0) + 1;
}
if (client._ccWebMissedPongs >= WS_HEARTBEAT_MAX_MISSES) {
plog('WARN', 'ws_heartbeat_terminate', {
wsId: client._ccWebId || null,
missedPongs: client._ccWebMissedPongs,
lastActivityAgeMs: lastActivityAt ? now - lastActivityAt : null,
});
client.terminate();
continue;
}
} else {
client._ccWebMissedPongs = 0;
}
client.isAlive = false;
if (client.readyState === 1) {
try {
client.ping();
} catch (err) {
plog('WARN', 'ws_ping_failed', { wsId: client._ccWebId || null, error: err.message });
client.terminate();
}
try {
client.ping();
} catch (err) {
plog('WARN', 'ws_ping_failed', { wsId: client._ccWebId || null, error: err.message });
client.terminate();
continue;
}
}
}, WS_HEARTBEAT_INTERVAL_MS);
@@ -7191,7 +7252,7 @@ function createPersistentConversationSession(args = {}, options = {}) {
function buildSessionInfoPayload(session) {
const waitState = crossConversationWaitState(session.id);
const messages = session.messages || [];
const messages = sanitizeMessagesForTransport(session.messages || []);
return {
type: 'session_info',
sessionId: session.id,
@@ -7268,9 +7329,14 @@ function handleLoadHistoryPage(ws, msg = {}) {
const sessionId = sanitizeId(msg.sessionId || '');
const session = loadSession(sessionId);
if (!session) {
return wsSend(ws, { type: 'error', message: 'Session not found' });
return wsSend(ws, attachClientRequestId({
type: 'error',
code: 'session_not_found',
sessionId,
message: 'Session not found',
}, msg));
}
const list = Array.isArray(session.messages) ? session.messages : [];
const list = sanitizeMessagesForTransport(session.messages);
const requestedBefore = Number.parseInt(String(msg.before || ''), 10);
const before = Number.isFinite(requestedBefore)
? Math.max(0, Math.min(list.length, requestedBefore))
@@ -7288,12 +7354,93 @@ function handleLoadHistoryPage(ws, msg = {}) {
});
}
function attachActiveRuntimeToWs(ws, sessionId, source = {}) {
if (activeProcesses.has(sessionId)) {
const entry = activeProcesses.get(sessionId);
entry.ws = ws;
entry.wsDisconnectTime = null; // clear disconnect marker
plog('INFO', 'ws_resume_attach', {
sessionId: sessionId.slice(0, 8),
pid: entry.pid,
responseLen: (entry.fullText || '').length,
});
wsSend(ws, attachClientRequestId({
type: 'resume_generating',
sessionId,
text: truncateTextValue(entry.fullText || '', SESSION_MESSAGE_CONTENT_MAX_CHARS),
toolCalls: sanitizeToolCallsForPersist(entry.toolCalls || []),
}, source));
return true;
}
if (activeCodexAppTurns.has(sessionId)) {
const entry = activeCodexAppTurns.get(sessionId);
entry.ws = ws;
entry.wsDisconnectTime = null;
plog('INFO', 'codex_app_ws_resume_attach', {
sessionId: sessionId.slice(0, 8),
threadId: entry.threadId || null,
turnId: entry.turnId || null,
responseLen: (entry.fullText || '').length,
});
wsSend(ws, attachClientRequestId({
type: 'resume_generating',
sessionId,
text: truncateTextValue(entry.fullText || '', SESSION_MESSAGE_CONTENT_MAX_CHARS),
toolCalls: sanitizeToolCallsForPersist(entry.toolCalls || []),
}, source));
return true;
}
if (activeCodexAppGoalCommands.has(sessionId)) {
const entry = activeCodexAppGoalCommands.get(sessionId);
entry.ws = ws;
entry.wsDisconnectTime = null;
wsSend(ws, attachClientRequestId({
type: 'system_message',
sessionId,
message: '正在同步 Goal...',
}, source));
return true;
}
return false;
}
function handleResumeSession(ws, msg = {}) {
const sessionId = sanitizeId(msg.sessionId || '');
const session = loadSession(sessionId);
if (!session) {
return wsSend(ws, attachClientRequestId({
type: 'error',
code: 'session_not_found',
sessionId,
message: 'Session not found',
}, msg));
}
detachWsFromActiveRuntimes(ws);
wsSessionMap.set(ws, sessionId);
const attached = attachActiveRuntimeToWs(ws, sessionId, msg);
wsSend(ws, attachClientRequestId({
type: 'resume_session_result',
sessionId,
isRunning: isSessionRunning(sessionId),
attached,
}, msg));
}
function handleLoadSession(ws, msg) {
const sessionId = sanitizeId(typeof msg === 'string' ? msg : msg?.sessionId);
reconcilePendingCrossConversationReplies();
const session = loadSession(sessionId);
if (!session) {
return wsSend(ws, { type: 'error', message: 'Session not found' });
return wsSend(ws, attachClientRequestId({
type: 'error',
code: 'session_not_found',
sessionId,
message: 'Session not found',
}, msg));
}
flushPendingCrossConversationReplies(sessionId);
const refreshedSession = loadSession(sessionId) || session;
@@ -7367,44 +7514,8 @@ function handleLoadSession(ws, msg) {
});
}
// Resume streaming if process is still active
if (activeProcesses.has(sessionId)) {
const entry = activeProcesses.get(sessionId);
entry.ws = ws;
entry.wsDisconnectTime = null; // clear disconnect marker
plog('INFO', 'ws_resume_attach', {
sessionId: sessionId.slice(0, 8),
pid: entry.pid,
responseLen: (entry.fullText || '').length,
});
wsSend(ws, {
type: 'resume_generating',
sessionId,
text: truncateTextValue(entry.fullText || '', SESSION_MESSAGE_CONTENT_MAX_CHARS),
toolCalls: sanitizeToolCallsForPersist(entry.toolCalls || []),
});
} else if (activeCodexAppTurns.has(sessionId)) {
const entry = activeCodexAppTurns.get(sessionId);
entry.ws = ws;
entry.wsDisconnectTime = null;
plog('INFO', 'codex_app_ws_resume_attach', {
sessionId: sessionId.slice(0, 8),
threadId: entry.threadId || null,
turnId: entry.turnId || null,
responseLen: (entry.fullText || '').length,
});
wsSend(ws, {
type: 'resume_generating',
sessionId,
text: truncateTextValue(entry.fullText || '', SESSION_MESSAGE_CONTENT_MAX_CHARS),
toolCalls: sanitizeToolCallsForPersist(entry.toolCalls || []),
});
} else if (activeCodexAppGoalCommands.has(sessionId)) {
const entry = activeCodexAppGoalCommands.get(sessionId);
entry.ws = ws;
entry.wsDisconnectTime = null;
wsSend(ws, { type: 'system_message', sessionId, message: '正在同步 Goal...' });
}
// Resume streaming if process is still active.
attachActiveRuntimeToWs(ws, sessionId);
}
function sqlQuote(value) {
@@ -9940,10 +10051,11 @@ function handleImportNativeSession(ws, msg) {
};
saveSession(session);
wsSessionMap.set(ws, id);
const transportMessages = sanitizeMessagesForTransport(session.messages);
wsSend(ws, attachClientRequestId({
type: 'session_info',
sessionId: id,
messages: session.messages,
messages: transportMessages,
title: session.title,
pinnedAt: session.pinnedAt || null,
mode: session.permissionMode,
@@ -9953,6 +10065,8 @@ function handleImportNativeSession(ws, msg) {
totalCost: session.totalCost || 0,
totalUsage: session.totalUsage || null,
updated: session.updated,
historyTotal: transportMessages.length,
historyBaseIndex: 0,
hasUnread: false,
historyPending: false,
isRunning: false,
@@ -10109,10 +10223,11 @@ function handleImportCodexSession(ws, msg) {
saveSession(session);
wsSessionMap.set(ws, id);
const transportMessages = sanitizeMessagesForTransport(session.messages);
wsSend(ws, attachClientRequestId({
type: 'session_info',
sessionId: id,
messages: session.messages,
messages: transportMessages,
title: session.title,
pinnedAt: session.pinnedAt || null,
mode: session.permissionMode,
@@ -10122,6 +10237,8 @@ function handleImportCodexSession(ws, msg) {
totalCost: session.totalCost || 0,
totalUsage: session.totalUsage || null,
updated: session.updated,
historyTotal: transportMessages.length,
historyBaseIndex: 0,
hasUnread: false,
historyPending: false,
isRunning: false,