feat: improve codex app controls and recovery

This commit is contained in:
shiyue
2026-06-15 13:22:36 +08:00
parent 3a4006b7d3
commit ed3238fa49
8 changed files with 448 additions and 29 deletions

View File

@@ -2015,6 +2015,69 @@ function getRuntimeSessionId(session) {
return session.claudeSessionId || null;
}
async function handleReloadMcpApi(req, res, rawSessionId) {
const token = extractBearerToken(req);
if (!token || !activeTokens.has(token)) {
return jsonResponse(res, 401, { ok: false, message: 'Not authenticated' });
}
const sessionId = sanitizeId(rawSessionId || '');
if (!sessionId) {
return jsonResponse(res, 400, { ok: false, code: 'missing_session_id', message: '缺少会话 ID' });
}
const session = loadSession(sessionId);
if (!session) {
return jsonResponse(res, 404, { ok: false, code: 'session_not_found', message: '会话不存在' });
}
if (!isCodexAppSession(session)) {
return jsonResponse(res, 400, {
ok: false,
code: 'reload_mcp_unsupported_agent',
message: '重载 MCP 仅支持 Codex App 会话。旧 Codex 会话请重启本地 Codex 后再继续。',
});
}
try {
const clientResult = getCodexAppClient();
if (clientResult.error) {
return jsonResponse(res, 500, {
ok: false,
code: 'codexapp_client_unavailable',
message: clientResult.error,
});
}
const client = clientResult.client;
await client.start();
const result = typeof client.reloadMcpServers === 'function'
? await client.reloadMcpServers()
: await client.request('config/mcpServer/reload', {}, 30000);
plog('INFO', 'codex_app_mcp_reload_requested', {
sessionId: sessionId.slice(0, 8),
threadId: getRuntimeSessionId(session) || null,
});
return jsonResponse(res, 200, {
ok: true,
sessionId,
threadId: getRuntimeSessionId(session) || null,
result: result || {},
});
} catch (err) {
const unsupported = err?.code === -32601 || /not found|unknown|unsupported|method/i.test(String(err?.message || ''));
return jsonResponse(res, unsupported ? 501 : 500, {
ok: false,
code: unsupported ? 'codexapp_reload_mcp_unsupported' : 'codexapp_reload_mcp_failed',
message: unsupported
? `当前 Codex app-server 不支持重载 MCP请重启 Codex App。${err?.message ? `${err.message}` : ''}`
: `重载 MCP 失败: ${err?.message || err}`,
});
}
}
function setRuntimeSessionId(session, runtimeId) {
if (!session) return;
const agent = getSessionAgent(session);
@@ -2900,6 +2963,11 @@ const server = http.createServer((req, res) => {
return handleInternalMcpApi(req, res);
}
const reloadMcpMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/reload-mcp$/);
if (req.method === 'POST' && reloadMcpMatch) {
return handleReloadMcpApi(req, res, decodeURIComponent(reloadMcpMatch[1] || ''));
}
if (req.method === 'POST' && url.pathname === '/api/attachments') {
const token = extractBearerToken(req);
if (!token || !activeTokens.has(token)) {
@@ -4554,7 +4622,16 @@ function handleCodexAppUserInputResponse(ws, msg = {}) {
pendingCodexAppUserInputs.delete(requestId);
clearTimeout(pending.timer);
pending.resolve(normalizeCodexAppUserInputAnswers(msg.answers || {}));
const action = String(msg.action || 'submit').trim();
const isCancel = action === 'cancel';
if (!isCancel) {
wsSend(ws, {
type: 'system_message',
sessionId: pending.sessionId,
message: '已提交 Codex App 引导输入。',
});
}
pending.resolve(isCancel ? { answers: {} } : normalizeCodexAppUserInputAnswers(msg.answers || {}));
}
function resolvePendingCodexAppUserInputsForSession(sessionId) {