chore: rebuild release package

This commit is contained in:
shiyue
2026-07-18 10:14:38 +08:00
parent fa15b54469
commit a76de06c47
112 changed files with 8858 additions and 399 deletions

269
server.js
View File

@@ -1286,6 +1286,8 @@ const MIME_TYPES = {
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
};
@@ -8032,16 +8034,6 @@ function handleMessage(ws, msg, options = {}) {
const normalizedText = textValue.trim();
let normalizedRuntimeText = runtimeTextValue.trim();
const resolvedAttachments = resolveMessageAttachments(attachments);
if (attachments.length > 0 && resolvedAttachments.length === 0) {
return fail('attachment_unavailable', '图片附件已过期或不可用,请重新上传后再发送。');
}
if (!normalizedText && resolvedAttachments.length === 0) {
return fail('empty_message', '消息内容不能为空。');
}
if (sessionId && !hideInHistory) {
cancelCodexCapacityRetry(sessionId);
}
const savedAttachments = resolvedAttachments.map((attachment) => ({
id: attachment.id,
kind: 'image',
@@ -8054,7 +8046,22 @@ function handleMessage(ws, msg, options = {}) {
}));
if (sessionId && activeCodexAppTurns.has(sessionId)) {
return handleCodexAppSteerMessage(ws, msg, options);
return handleCodexAppSteerMessage(ws, msg, {
...options,
resolvedAttachments,
savedAttachments,
});
}
if (attachments.length > 0 && resolvedAttachments.length === 0) {
return fail('attachment_unavailable', '图片附件已过期或不可用,请重新上传后再发送。');
}
if (!normalizedText && resolvedAttachments.length === 0) {
return fail('empty_message', '消息内容不能为空。');
}
if (sessionId && !hideInHistory) {
cancelCodexCapacityRetry(sessionId);
}
if (sessionId && activeCodexAppGoalCommands.has(sessionId)) {
@@ -10181,7 +10188,13 @@ function handleCodexAppSteerMessage(ws, msg, options = {}) {
const fail = (code, message) => {
sendSteerStatus('failed', '插入失败');
wsSend(ws, { type: 'error', code, message });
wsSend(ws, {
type: 'error',
code,
message,
sessionId,
...(clientMessageId ? { clientMessageId } : {}),
});
return { ok: false, code, message };
};
@@ -10192,35 +10205,37 @@ function handleCodexAppSteerMessage(ws, msg, options = {}) {
const textValue = typeof msg.text === 'string' ? msg.text : '';
const normalizedText = textValue.trim();
const attachments = Array.isArray(msg.attachments) ? msg.attachments : [];
if (!normalizedText) return fail('empty_message', '运行中插入内容不能为空。');
const rawAttachments = Array.isArray(msg.attachments) ? msg.attachments : [];
const resolvedAttachments = Array.isArray(options.resolvedAttachments)
? options.resolvedAttachments
: resolveMessageAttachments(rawAttachments);
if (rawAttachments.length > 0 && resolvedAttachments.length === 0) {
return fail('attachment_unavailable', '图片附件已过期或不可用,请重新上传后再发送。');
}
const toSavedAttachment = (attachment) => ({
id: attachment.id,
kind: 'image',
filename: attachment.filename,
mime: attachment.mime,
size: attachment.size,
createdAt: attachment.createdAt,
expiresAt: attachment.expiresAt,
storageState: attachment.storageState,
});
const savedAttachments = Array.isArray(options.savedAttachments)
? options.savedAttachments.map(toSavedAttachment)
: resolvedAttachments.map(toSavedAttachment);
if (!normalizedText && resolvedAttachments.length === 0) return fail('empty_message', '运行中插入内容不能为空。');
if (!options.unknownSlash && normalizedText.startsWith('/')) return fail('codexapp_running_slash_unsupported', 'Codex App 运行中暂不支持 slash 指令插入。');
if (attachments.length > 0) return fail('codexapp_running_attachment_unsupported', 'Codex App 运行中插入暂不支持图片附件。');
if (!entry.threadId || !entry.turnId) return fail('codexapp_turn_not_ready', 'Codex App 当前 turn 尚未准备好,请稍后再插入。');
if (!codexAppClient || !codexAppClient.isRunning()) return fail('codexapp_server_not_running', 'Codex app-server 未运行,无法插入。');
const decoratorResolution = typeof options.runtimeText === 'string'
? { runtimeText: options.runtimeText, mentions: [] }
: resolveComposerDecorators(textValue, session, getSessionAgent(session));
const runtimeTextValue = String(decoratorResolution.runtimeText || '').trim();
if (!runtimeTextValue) return fail('empty_message', '运行中插入内容不能为空。');
const runtimeTextValue = String(decoratorResolution.runtimeText || '');
const normalizedRuntimeText = runtimeTextValue.trim();
if (!normalizedRuntimeText && resolvedAttachments.length === 0) return fail('empty_message', '运行中插入内容不能为空。');
if (msg.mode && ['default', 'plan', 'yolo'].includes(msg.mode)) {
session.permissionMode = msg.mode;
}
const persistedUserMessage = {
role: 'user',
content: textValue,
attachments: [],
timestamp: new Date().toISOString(),
};
if (decoratorResolution.mentions.length > 0) {
persistedUserMessage.composerMentions = decoratorResolution.mentions;
}
session.messages.push(persistedUserMessage);
session.updated = new Date().toISOString();
saveSession(session);
let persistedUserMessage = null;
if (ws) {
detachWsFromActiveRuntimes(ws);
@@ -10228,73 +10243,131 @@ function handleCodexAppSteerMessage(ws, msg, options = {}) {
entry.wsDisconnectTime = null;
wsSessionMap.set(ws, sessionId);
}
if (ws && options.emitUserMessage) {
wsSend(ws, { type: 'session_message', sessionId, message: persistedUserMessage });
}
sendSteerStatus('pending', '引导中...');
const input = codexAppInputFromMessage(runtimeTextValue, []);
const expectedTurnId = entry.turnId;
codexAppClient.request('turn/steer', {
threadId: entry.threadId,
expectedTurnId,
input,
clientUserMessageId: clientMessageId || crypto.randomUUID(),
}, 60000).then(() => {
sendSteerStatus('inserted', '已插入');
wsSend(entry.ws || ws, {
type: 'system_message',
sessionId,
tone: 'info',
transient: true,
autoDismissMs: 5000,
message: `已引导对话: ${previewInlineText(textValue)}`,
});
}).catch((err) => {
if (isCodexAppNoActiveTurnError(err) && activeCodexAppTurns.get(sessionId) === entry) {
// app-server 已确认旧 turn 不存在:先收敛旧输出,再复用已持久化的消息直接启动新 turn。
handleCodexAppTurnComplete(sessionId, {
beforeUserMessage: {
timestamp: persistedUserMessage.timestamp,
content: persistedUserMessage.content,
},
deferPendingCrossConversationFlush: true,
const input = codexAppInputFromMessage(runtimeTextValue, resolvedAttachments);
const steerPreviewText = textValue.trim()
|| savedAttachments.map((attachment) => attachment.filename).filter(Boolean).join(', ')
|| '图片';
const waitForSteerReady = () => new Promise((resolve) => {
const startedAt = Date.now();
const check = () => {
if (activeCodexAppTurns.get(sessionId) !== entry) return resolve(false);
if (entry.threadId && entry.turnId && codexAppClient?.isRunning()) return resolve(true);
if (Date.now() - startedAt >= 2000) return resolve(false);
setTimeout(check, 50);
};
check();
});
waitForSteerReady().then((ready) => {
if (!ready) {
sendSteerStatus('failed', '插入失败');
wsSend(entry.ws || ws, {
type: 'error',
sessionId,
code: entry.threadId && entry.turnId ? 'codexapp_server_not_running' : 'codexapp_turn_not_ready',
transient: true,
autoDismissMs: 7000,
message: entry.threadId && entry.turnId
? 'Codex app-server 未运行,无法插入。'
: 'Codex App 当前 turn 尚未准备好,请稍后再插入。',
});
const refreshedSession = loadSession(sessionId);
const restarted = refreshedSession
&& isCodexAppSession(refreshedSession)
&& !activeCodexAppTurns.has(sessionId)
? handleCodexAppMessage(ws, refreshedSession, runtimeTextValue, [], {
mcpContext: entry.mcpContext || options.mcpContext || {},
})
: null;
if (restarted?.ok) {
wsSend(entry.ws || ws, {
type: 'resume_generating',
sessionId,
text: '',
toolCalls: [],
});
sendSteerStatus('inserted', '已转为新对话');
wsSend(entry.ws || ws, {
type: 'system_message',
sessionId,
tone: 'info',
transient: true,
autoDismissMs: 5000,
message: '检测到上一轮已结束,已自动开始新一轮对话。',
});
return;
}
return;
}
sendSteerStatus('failed', '插入失败');
wsSend(entry.ws || ws, {
type: 'error',
sessionId,
code: 'codexapp_steer_failed',
transient: true,
autoDismissMs: 7000,
message: formatRuntimeError('codex', err?.message || err, { exitCode: null, signal: null }),
const currentSession = loadSession(sessionId);
if (!currentSession || !isCodexAppSession(currentSession)) {
sendSteerStatus('failed', '插入失败');
wsSend(entry.ws || ws, {
type: 'error',
sessionId,
code: 'session_not_found',
transient: true,
autoDismissMs: 7000,
message: 'Codex App 会话不存在。',
});
return;
}
if (msg.mode && ['default', 'plan', 'yolo'].includes(msg.mode)) {
currentSession.permissionMode = msg.mode;
}
persistedUserMessage = {
role: 'user',
content: textValue,
attachments: savedAttachments,
timestamp: new Date().toISOString(),
};
if (decoratorResolution.mentions.length > 0) {
persistedUserMessage.composerMentions = decoratorResolution.mentions;
}
currentSession.messages.push(persistedUserMessage);
currentSession.updated = new Date().toISOString();
saveSession(currentSession);
if (ws && options.emitUserMessage) {
wsSend(ws, { type: 'session_message', sessionId, message: persistedUserMessage });
}
const expectedTurnId = entry.turnId;
codexAppClient.request('turn/steer', {
threadId: entry.threadId,
expectedTurnId,
input,
clientUserMessageId: clientMessageId || crypto.randomUUID(),
}, 60000).then(() => {
sendSteerStatus('inserted', '已插入');
wsSend(entry.ws || ws, {
type: 'system_message',
sessionId,
tone: 'info',
transient: true,
autoDismissMs: 5000,
message: `已引导对话: ${previewInlineText(steerPreviewText)}`,
});
}).catch((err) => {
if (isCodexAppNoActiveTurnError(err) && activeCodexAppTurns.get(sessionId) === entry) {
// app-server 已确认旧 turn 不存在:先收敛旧输出,再复用已持久化的消息直接启动新 turn。
handleCodexAppTurnComplete(sessionId, {
beforeUserMessage: {
timestamp: persistedUserMessage.timestamp,
content: persistedUserMessage.content,
},
deferPendingCrossConversationFlush: true,
});
const refreshedSession = loadSession(sessionId);
const restarted = refreshedSession
&& isCodexAppSession(refreshedSession)
&& !activeCodexAppTurns.has(sessionId)
? handleCodexAppMessage(ws, refreshedSession, runtimeTextValue, resolvedAttachments, {
mcpContext: entry.mcpContext || options.mcpContext || {},
})
: null;
if (restarted?.ok) {
wsSend(entry.ws || ws, {
type: 'resume_generating',
sessionId,
text: '',
toolCalls: [],
});
sendSteerStatus('inserted', '已转为新对话');
wsSend(entry.ws || ws, {
type: 'system_message',
sessionId,
tone: 'info',
transient: true,
autoDismissMs: 5000,
message: '检测到上一轮已结束,已自动开始新一轮对话。',
});
return;
}
}
sendSteerStatus('failed', '插入失败');
wsSend(entry.ws || ws, {
type: 'error',
sessionId,
code: 'codexapp_steer_failed',
transient: true,
autoDismissMs: 7000,
message: formatRuntimeError('codex', err?.message || err, { exitCode: null, signal: null }),
});
});
});