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