chore: rebuild CentOS7 release package

This commit is contained in:
shiyue
2026-06-26 11:17:47 +08:00
parent c387c92e4b
commit 756b9651f9
7 changed files with 1267 additions and 16 deletions

172
server.js
View File

@@ -4689,6 +4689,77 @@ function hasRuntimeOutput(entry) {
return Array.isArray(entry.toolCalls) && entry.toolCalls.length > 0;
}
function retryTailText(value, maxChars) {
const text = String(value || '').trim();
if (!text || text.length <= maxChars) return text;
const marker = '[cc-web: 前文过长,下面仅保留尾部]\n';
return `${marker}${text.slice(Math.max(0, text.length - Math.max(0, maxChars - marker.length)))}`;
}
function retryPreviewValue(value, maxChars) {
if (value === null || value === undefined) return '';
if (typeof value === 'string') return retryTailText(value, maxChars);
try {
return retryTailText(JSON.stringify(sanitizePersistValue(value, {
maxString: maxChars,
maxDepth: 4,
maxArray: 20,
maxKeys: 40,
}), null, 2), maxChars);
} catch {
return retryTailText(String(value), maxChars);
}
}
function buildCodexRetryToolSummary(toolCalls) {
const list = Array.isArray(toolCalls) ? toolCalls.filter(Boolean).slice(-8) : [];
if (list.length === 0) return '';
return list.map((tool, index) => {
const name = tool.name || tool.kind || tool.id || `tool-${index + 1}`;
const status = tool.done ? 'done' : (tool.status || tool.meta?.status || 'inProgress');
const lines = [`${index + 1}. ${name} (${status})`];
if (tool.input !== undefined && tool.input !== null) {
lines.push(` input: ${retryPreviewValue(tool.input, 1200)}`);
}
if (tool.result !== undefined && tool.result !== null) {
lines.push(` result: ${retryPreviewValue(tool.result, 2400)}`);
}
return lines.join('\n');
}).join('\n');
}
function shouldUseCodexAppContinuationRetry(entry) {
return (entry?.agent || '') === 'codexapp' && !!(entry.turnId || hasRuntimeOutput(entry));
}
function buildCodexAppContinuationRetryText(entry, retryRequest, rawError) {
const original = retryPreviewValue(
retryRequest.originalRuntimeText || retryRequest.runtimeText || retryRequest.originalText || retryRequest.text || '',
5000,
);
const partialText = retryPreviewValue(entry.fullText || '', 7000);
const toolSummary = buildCodexRetryToolSummary(entry.toolCalls || []);
const errorText = retryPreviewValue(rawError || entry.lastError || '', 1200);
const parts = [
'继续上一轮被临时服务或网络错误中断的 Codex App 任务。',
'不要从头重做,不要重复已经完成的命令、工具调用或文件修改;请基于现有线程上下文和下面 cc-web 已观察到的中断前状态继续执行。不要在回复中复述这段内部重试说明。',
];
if (original) {
parts.push(`原始用户请求(仅用于理解目标,不要当成新请求从头执行):\n${original}`);
}
if (partialText) {
parts.push(`cc-web 已观察到的中断前助手输出(尾部):\n${partialText}`);
}
if (toolSummary) {
parts.push(`cc-web 已观察到的工具/执行摘要:\n${toolSummary}`);
}
if (errorText) {
parts.push(`中断原因:\n${errorText}`);
}
parts.push('请从中断处继续完成剩余工作。');
return parts.filter(Boolean).join('\n\n');
}
function getCodexRetryConfig() {
return normalizeCodexRetryConfig(loadCodexConfig().retry);
}
@@ -4723,7 +4794,7 @@ function scheduleCodexCapacityRetry(sessionId, entry, rawError) {
cancelCodexCapacityRetry(sessionId);
return false;
}
const attempts = (previous?.attempts || 0) + 1;
const attempts = (previous?.attempts || entry.codexRetry?.attempt || 0) + 1;
if (retryConfig.mode === 'limited' && attempts > retryConfig.maxAttempts) {
cancelCodexCapacityRetry(sessionId);
return false;
@@ -4731,14 +4802,32 @@ function scheduleCodexCapacityRetry(sessionId, entry, rawError) {
const delayMs = codexTransientRetryDelayMs(retryConfig);
if (previous?.timer) clearTimeout(previous.timer);
const expectedThreadId = retryRequest.expectedThreadId
|| entry.expectedThreadId
|| entry.codexRetry?.expectedThreadId
|| entry.threadId
|| previous?.expectedThreadId
|| null;
const originalText = retryRequest.originalText || retryRequest.text || retryRequest.runtimeText || '';
const originalRuntimeText = retryRequest.originalRuntimeText || retryRequest.runtimeText || retryRequest.text || '';
const useContinuationRetry = shouldUseCodexAppContinuationRetry(entry);
const continuationText = useContinuationRetry
? buildCodexAppContinuationRetryText(entry, retryRequest, rawError)
: '';
const retryRuntimeText = continuationText || originalRuntimeText || originalText;
const retryText = retryRuntimeText || originalText;
const retry = {
text: retryRequest.text || retryRequest.runtimeText || '',
runtimeText: retryRequest.runtimeText || retryRequest.text || '',
text: retryText,
runtimeText: retryRuntimeText,
originalText,
originalRuntimeText,
mode: retryRequest.mode || 'yolo',
agent: retryRequest.agent || entry.agent || 'codex',
attachments: Array.isArray(retryRequest.attachments) ? retryRequest.attachments : [],
attachments: useContinuationRetry ? [] : (Array.isArray(retryRequest.attachments) ? retryRequest.attachments : []),
mcpContext: retryRequest.mcpContext || {},
expectedThreadId,
useContinuationRetry,
attempts,
retryMode: retryConfig.mode,
timer: null,
@@ -4756,17 +4845,22 @@ function scheduleCodexCapacityRetry(sessionId, entry, rawError) {
return;
}
if (activeProcesses.has(sessionId) || activeCodexAppTurns.has(sessionId)) {
pendingCodexCapacityRetries.delete(sessionId);
plog('WARN', 'codex_capacity_retry_skipped_busy', {
sessionId: sessionId.slice(0, 8),
attempt: latest.attempts,
});
if (latest.ws && latest.ws.readyState === 1) sendSessionList(latest.ws);
return;
}
pendingCodexCapacityRetries.delete(sessionId);
const ws = latest.ws && latest.ws.readyState === 1 ? latest.ws : null;
plog('INFO', 'codex_capacity_retry_start', {
sessionId: sessionId.slice(0, 8),
attempt: latest.attempts,
expectedThreadId: latest.expectedThreadId ? String(latest.expectedThreadId).slice(0, 24) : null,
continuation: !!latest.useContinuationRetry,
});
handleMessage(ws, {
type: 'message',
@@ -4779,6 +4873,17 @@ function scheduleCodexCapacityRetry(sessionId, entry, rawError) {
hideInHistory: true,
runtimeText: latest.runtimeText,
mcpContext: latest.mcpContext,
codexRetry: latest.agent === 'codexapp'
? {
isAutoRetry: true,
attempt: latest.attempts,
retryMode: latest.retryMode,
expectedThreadId: latest.expectedThreadId || null,
originalText: latest.originalText || latest.text || '',
originalRuntimeText: latest.originalRuntimeText || latest.runtimeText || '',
useContinuationRetry: !!latest.useContinuationRetry,
}
: null,
skipPendingCrossConversationFlush: true,
});
}, delayMs);
@@ -4790,16 +4895,19 @@ function scheduleCodexCapacityRetry(sessionId, entry, rawError) {
maxAttempts: retryConfig.mode === 'limited' ? retryConfig.maxAttempts : null,
retryMode: retryConfig.mode,
delayMs,
expectedThreadId: expectedThreadId ? String(expectedThreadId).slice(0, 24) : null,
continuation: useContinuationRetry,
error: String(rawError || '').slice(0, 300),
});
if (entry.ws) {
const attemptText = retryConfig.mode === 'forever'
? `${attempts}`
: `${attempts}/${retryConfig.maxAttempts}`;
const continuationText = useContinuationRetry ? ',将从中断处继续' : '';
wsSend(entry.ws, {
type: 'system_message',
sessionId,
message: `Codex 服务暂时繁忙,${retryConfig.intervalSeconds} 秒后自动重试(${attemptText})。`,
message: `Codex 服务暂时繁忙,${retryConfig.intervalSeconds} 秒后自动重试(${attemptText}${continuationText})。`,
});
}
return true;
@@ -6869,6 +6977,7 @@ function handleMessage(ws, msg, options = {}) {
return handleCodexAppMessage(ws, session, runtimeTextValue, resolvedAttachments, {
mcpContext: options.mcpContext || {},
crossConversation: options.crossConversation || null,
codexRetry: options.codexRetry || null,
});
}
@@ -8236,6 +8345,19 @@ function handleCodexAppMessage(ws, session, runtimeTextValue, resolvedAttachment
return { ok: false, code: 'empty_message', message: '消息内容不能为空。' };
}
const codexRetry = options.codexRetry && typeof options.codexRetry === 'object'
? {
isAutoRetry: !!options.codexRetry.isAutoRetry,
attempt: Number.isFinite(Number(options.codexRetry.attempt)) ? Number(options.codexRetry.attempt) : null,
retryMode: options.codexRetry.retryMode || null,
expectedThreadId: options.codexRetry.expectedThreadId || null,
originalText: options.codexRetry.originalText || null,
originalRuntimeText: options.codexRetry.originalRuntimeText || null,
useContinuationRetry: !!options.codexRetry.useContinuationRetry,
}
: null;
const currentThreadId = getRuntimeSessionId(session);
const expectedThreadId = codexRetry?.expectedThreadId || currentThreadId || null;
const retryAttachments = resolvedAttachments.map((attachment) => ({
id: attachment.id,
kind: 'image',
@@ -8250,24 +8372,30 @@ function handleCodexAppMessage(ws, session, runtimeTextValue, resolvedAttachment
ws,
agent: 'codexapp',
cwd: session.cwd || getDefaultSessionCwd(),
threadId: getRuntimeSessionId(session),
threadId: expectedThreadId,
expectedThreadId,
turnId: null,
fullText: '',
toolCalls: [],
toolOutputDeltas: new Map(),
agentMessageItems: new Map(),
mcpContext: options.mcpContext || {},
codexRetry,
lastUsage: null,
lastError: null,
errorSent: false,
crossConversationReplyRequestId: options.crossConversation?.replyRequestId || null,
retryRequest: {
text: runtimeTextValue,
runtimeText: runtimeTextValue,
text: codexRetry?.originalText || runtimeTextValue,
runtimeText: codexRetry?.originalRuntimeText || runtimeTextValue,
originalText: codexRetry?.originalText || runtimeTextValue,
originalRuntimeText: codexRetry?.originalRuntimeText || runtimeTextValue,
lastRetryText: runtimeTextValue,
mode: session.permissionMode || 'yolo',
agent: 'codexapp',
attachments: retryAttachments,
mcpContext: options.mcpContext || {},
expectedThreadId,
},
clientUserMessageId: crypto.randomUUID(),
startedAt: new Date().toISOString(),
@@ -8293,11 +8421,33 @@ async function startCodexAppTurn(sessionId, input) {
const client = clientResult.client;
await client.start();
let threadId = getRuntimeSessionId(session);
const currentThreadId = getRuntimeSessionId(session);
const expectedThreadId = entry.expectedThreadId
|| entry.codexRetry?.expectedThreadId
|| entry.retryRequest?.expectedThreadId
|| entry.threadId
|| currentThreadId
|| null;
let threadId = expectedThreadId || currentThreadId;
const threadParams = codexAppThreadParams(session, { mcpContext: entry.mcpContext || {} });
if (threadId) {
const resumed = await client.request('thread/resume', { ...threadParams, threadId }, 60000);
threadId = resumed?.thread?.id || threadId;
const requestedThreadId = threadId;
const resumed = await client.request('thread/resume', { ...threadParams, threadId: requestedThreadId }, 60000);
const resumedThreadId = resumed?.thread?.id || requestedThreadId;
if (expectedThreadId && resumedThreadId !== expectedThreadId) {
const expectedShort = String(expectedThreadId).slice(0, 24);
const actualShort = String(resumedThreadId).slice(0, 24);
plog('WARN', 'codex_app_thread_resume_mismatch', {
sessionId: sessionId.slice(0, 8),
expectedThreadId: expectedShort,
actualThreadId: actualShort,
autoRetry: !!entry.codexRetry?.isAutoRetry,
retryAttempt: entry.codexRetry?.attempt || null,
});
const prefix = entry.codexRetry?.isAutoRetry ? 'Codex App 自动重试' : 'Codex App';
throw new Error(`${prefix}恢复到不同线程,已停止以避免上下文丢失(期望 ${expectedShort},实际 ${actualShort})。`);
}
threadId = resumedThreadId;
} else {
const started = await client.request('thread/start', { ...threadParams, sessionStartSource: 'startup' }, 60000);
threadId = started?.thread?.id || null;