chore: rebuild CentOS7 release package
This commit is contained in:
172
server.js
172
server.js
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user