chore: rebuild CentOS7 release package
This commit is contained in:
1
.planning/.active_plan
Normal file
1
.planning/.active_plan
Normal file
@@ -0,0 +1 @@
|
|||||||
|
codex-app-worker-timeout
|
||||||
7
.planning/codex-app-worker-timeout/findings.md
Normal file
7
.planning/codex-app-worker-timeout/findings.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Codex App worker start 超时排查发现
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
- 用户报告错误:`Codex 任务失败:Codex App worker 请求超时: start`。
|
||||||
|
- 初始判断:问题集中在 `codexapp` worker 的 `start` 请求链路,需确认
|
||||||
|
worker 进程是否启动、`start` 响应是否丢失、超时时间是否过短或未清理。
|
||||||
5
.planning/codex-app-worker-timeout/progress.md
Normal file
5
.planning/codex-app-worker-timeout/progress.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Codex App worker start 超时排查进度
|
||||||
|
|
||||||
|
## Log
|
||||||
|
|
||||||
|
- 2026-07-07T09:51:08+08:00 创建 scoped 计划,避免覆盖根目录旧 hooks 排查记录。
|
||||||
27
.planning/codex-app-worker-timeout/task_plan.md
Normal file
27
.planning/codex-app-worker-timeout/task_plan.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Codex App worker start 超时排查计划
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
定位并修复 `Codex App worker 请求超时: start`,优先确认 `codexapp`
|
||||||
|
worker 启动链路中的超时点、请求/响应匹配和错误传播。
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
- [ ] 确认当前索引与项目结构入口
|
||||||
|
- [ ] 定位 Codex App worker start 超时链路
|
||||||
|
- [ ] 复现或从日志提取超时证据
|
||||||
|
- [ ] 实现最小修复并补回归覆盖
|
||||||
|
- [ ] 运行针对性验证并总结风险
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- 简体中文沟通。
|
||||||
|
- 代码理解优先使用 `codebase-memory-mcp`,再用 `rg` / `nl` 校验。
|
||||||
|
- 不覆盖用户已有未提交改动。
|
||||||
|
- 修改服务后如需重启,必须先确认除当前对话外没有其他 running 会话。
|
||||||
|
- 当前在 WSL 环境;若发现 dotnet 项目,禁止在 WSL 编译。
|
||||||
|
|
||||||
|
## Errors Encountered
|
||||||
|
|
||||||
|
| Error | Attempt | Resolution |
|
||||||
|
|-------|---------|------------|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"updatedAt": "2026-07-02T06:02:53.126Z",
|
"updatedAt": "2026-07-06T09:51:06.234Z",
|
||||||
"replies": []
|
"replies": []
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -8614,7 +8614,10 @@
|
|||||||
msgInput.focus();
|
msgInput.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
abortBtn.addEventListener('click', () => send({ type: 'abort' }));
|
abortBtn.addEventListener('click', () => {
|
||||||
|
if (!currentSessionId) return;
|
||||||
|
send({ type: 'abort', sessionId: currentSessionId });
|
||||||
|
});
|
||||||
if (attachBtn && imageUploadInput) {
|
if (attachBtn && imageUploadInput) {
|
||||||
attachBtn.addEventListener('click', () => imageUploadInput.click());
|
attachBtn.addEventListener('click', () => imageUploadInput.click());
|
||||||
imageUploadInput.addEventListener('change', () => {
|
imageUploadInput.addEventListener('change', () => {
|
||||||
|
|||||||
@@ -364,6 +364,62 @@ function completeTurn(thread, turnId, text, status = 'completed') {
|
|||||||
thread.steers = [];
|
thread.steers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function completeGoalBackgroundTurn(thread, objective) {
|
||||||
|
const turnId = `goal-turn-${crypto.randomUUID()}`;
|
||||||
|
const text = `Goal background output: ${objective}`;
|
||||||
|
thread.activeTurnId = turnId;
|
||||||
|
send({
|
||||||
|
method: 'turn/started',
|
||||||
|
params: {
|
||||||
|
threadId: thread.id,
|
||||||
|
turn: { id: turnId, status: 'running', items: [] },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
send({
|
||||||
|
method: 'item/agentMessage/delta',
|
||||||
|
params: {
|
||||||
|
threadId: thread.id,
|
||||||
|
turnId,
|
||||||
|
itemId: 'goal-agent-msg',
|
||||||
|
delta: text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
send({
|
||||||
|
method: 'item/completed',
|
||||||
|
params: {
|
||||||
|
threadId: thread.id,
|
||||||
|
turnId,
|
||||||
|
completedAtMs: Date.now(),
|
||||||
|
item: {
|
||||||
|
id: 'goal-agent-msg',
|
||||||
|
type: 'agentMessage',
|
||||||
|
text,
|
||||||
|
status: 'completed',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
send({
|
||||||
|
method: 'thread/tokenUsage/updated',
|
||||||
|
params: {
|
||||||
|
threadId: thread.id,
|
||||||
|
turnId,
|
||||||
|
tokenUsage: tokenUsage(text),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
send({
|
||||||
|
method: 'turn/completed',
|
||||||
|
params: {
|
||||||
|
threadId: thread.id,
|
||||||
|
turn: {
|
||||||
|
id: turnId,
|
||||||
|
status: 'completed',
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
thread.activeTurnId = null;
|
||||||
|
}
|
||||||
|
|
||||||
function requestClient(method, params, callback) {
|
function requestClient(method, params, callback) {
|
||||||
const id = `mock-server-request-${nextServerRequestId++}`;
|
const id = `mock-server-request-${nextServerRequestId++}`;
|
||||||
pendingServerRequests.set(id, callback);
|
pendingServerRequests.set(id, callback);
|
||||||
@@ -874,6 +930,9 @@ function handleRequest(message) {
|
|||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
send({ id, result: { goal: thread.goal } });
|
send({ id, result: { goal: thread.goal } });
|
||||||
|
if (params.objective) {
|
||||||
|
setTimeout(() => completeGoalBackgroundTurn(thread, objective), 50);
|
||||||
|
}
|
||||||
}, 250);
|
}, 250);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -738,6 +738,10 @@ function assertSessionSwitchResilienceContract() {
|
|||||||
/const flushedSessionResume = flushedSessionSwitch \? false : flushPendingSessionResume\(\);[\s\S]*?requestSessionResume\(currentSessionId/.test(frontendSource),
|
/const flushedSessionResume = flushedSessionSwitch \? false : flushPendingSessionResume\(\);[\s\S]*?requestSessionResume\(currentSessionId/.test(frontendSource),
|
||||||
'Frontend should resume the current running session after auth without forcing load_session'
|
'Frontend should resume the current running session after auth without forcing load_session'
|
||||||
);
|
);
|
||||||
|
assert(
|
||||||
|
frontendSource.includes("send({ type: 'abort', sessionId: currentSessionId })"),
|
||||||
|
'Frontend abort button should explicitly target the current session'
|
||||||
|
);
|
||||||
assert(
|
assert(
|
||||||
/case 'background_done':[\s\S]*?if \(isNearBottom\(\)\)[\s\S]*?openSession\(msg\.sessionId,\s*\{ forceSync: true, blocking: false \}\)[\s\S]*?send\(\{ type: 'list_sessions' \}\)/.test(frontendSource),
|
/case 'background_done':[\s\S]*?if \(isNearBottom\(\)\)[\s\S]*?openSession\(msg\.sessionId,\s*\{ forceSync: true, blocking: false \}\)[\s\S]*?send\(\{ type: 'list_sessions' \}\)/.test(frontendSource),
|
||||||
'Frontend should not auto-rerender the current session on background_done while the user is reading history'
|
'Frontend should not auto-rerender the current session on background_done while the user is reading history'
|
||||||
@@ -775,6 +779,12 @@ function assertSessionSwitchResilienceContract() {
|
|||||||
assert(serverSource.includes("case 'resume_session':"), 'Server should accept lightweight resume_session requests');
|
assert(serverSource.includes("case 'resume_session':"), 'Server should accept lightweight resume_session requests');
|
||||||
assert(serverSource.includes('function handleResumeSession'), 'Server should implement lightweight running-session resume');
|
assert(serverSource.includes('function handleResumeSession'), 'Server should implement lightweight running-session resume');
|
||||||
assert(serverSource.includes('function attachActiveRuntimeToWs'), 'Server should share runtime re-attach logic without sending session_info first');
|
assert(serverSource.includes('function attachActiveRuntimeToWs'), 'Server should share runtime re-attach logic without sending session_info first');
|
||||||
|
assert(/case 'abort':\s*handleAbort\(ws, msg\);/.test(serverSource), 'Server should pass abort request metadata to handleAbort');
|
||||||
|
assert(/function handleAbort\(ws, msg = \{\}\)/.test(serverSource), 'Server handleAbort should accept the abort request payload');
|
||||||
|
assert(
|
||||||
|
/function bindAbortSessionToWs\(sessionId, ws\)[\s\S]*?entry\.ws = ws/.test(serverSource),
|
||||||
|
'Server abort should rebind the target running session to the current WebSocket before stopping'
|
||||||
|
);
|
||||||
assert(serverSource.includes('WS_HEARTBEAT_MAX_MISSES'), 'Server should tolerate missed WebSocket pongs before terminating');
|
assert(serverSource.includes('WS_HEARTBEAT_MAX_MISSES'), 'Server should tolerate missed WebSocket pongs before terminating');
|
||||||
assert(serverSource.includes('function markWsActivity'), 'Server should mark WebSocket activity on send/message/pong');
|
assert(serverSource.includes('function markWsActivity'), 'Server should mark WebSocket activity on send/message/pong');
|
||||||
assert(
|
assert(
|
||||||
@@ -1796,6 +1806,13 @@ async function main() {
|
|||||||
assert(codexAppGoalRunningList.sessions.some((session) => session.id === codexAppSession.sessionId && session.isRunning), 'Codex App /goal RPC should mark the session running while waiting for app-server');
|
assert(codexAppGoalRunningList.sessions.some((session) => session.id === codexAppSession.sessionId && session.isRunning), 'Codex App /goal RPC should mark the session running while waiting for app-server');
|
||||||
const codexAppGoalSet = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && msg.sessionId === codexAppSession.sessionId && /Goal active/.test(msg.message || '') && /improve benchmark coverage/.test(msg.message || ''));
|
const codexAppGoalSet = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && msg.sessionId === codexAppSession.sessionId && /Goal active/.test(msg.message || '') && /improve benchmark coverage/.test(msg.message || ''));
|
||||||
assert(/Goal active/.test(codexAppGoalSet.message || ''), 'Codex App /goal should set an active goal');
|
assert(/Goal active/.test(codexAppGoalSet.message || ''), 'Codex App /goal should set an active goal');
|
||||||
|
const codexAppGoalBackgroundDelta = await nextMessage(messages, ws, (msg) => (
|
||||||
|
msg.type === 'text_delta' &&
|
||||||
|
msg.sessionId === codexAppSession.sessionId &&
|
||||||
|
/Goal background output: improve benchmark coverage/.test(msg.text || '')
|
||||||
|
), 5000);
|
||||||
|
assert(/Goal background output/.test(codexAppGoalBackgroundDelta.text || ''), 'Codex App /goal background turn should stream through the active session');
|
||||||
|
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId, 5000);
|
||||||
const codexAppGoalIdleList = await nextMessage(messages, ws, (msg) => (
|
const codexAppGoalIdleList = await nextMessage(messages, ws, (msg) => (
|
||||||
msg.type === 'session_list' &&
|
msg.type === 'session_list' &&
|
||||||
Array.isArray(msg.sessions) &&
|
Array.isArray(msg.sessions) &&
|
||||||
@@ -1819,6 +1836,10 @@ async function main() {
|
|||||||
assert(/\/goal <目标描述>/.test(codexAppGoalEmpty.message || ''), 'Codex App /goal should show usage when no goal exists');
|
assert(/\/goal <目标描述>/.test(codexAppGoalEmpty.message || ''), 'Codex App /goal should show usage when no goal exists');
|
||||||
const storedCodexAppAfterGoal = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
|
const storedCodexAppAfterGoal = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
|
||||||
assert(!storedCodexAppAfterGoal.messages.some((message) => message.role === 'user' && /^\/goal/.test(String(message.content || ''))), 'Codex App /goal slash commands should not be persisted as normal user messages');
|
assert(!storedCodexAppAfterGoal.messages.some((message) => message.role === 'user' && /^\/goal/.test(String(message.content || ''))), 'Codex App /goal slash commands should not be persisted as normal user messages');
|
||||||
|
assert(storedCodexAppAfterGoal.messages.some((message) => (
|
||||||
|
message.role === 'assistant' &&
|
||||||
|
/Goal background output: improve benchmark coverage/.test(String(message.content || ''))
|
||||||
|
)), 'Codex App /goal background turn should be routed and persisted as an assistant message');
|
||||||
|
|
||||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp runtime warning prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
ws.send(JSON.stringify({ type: 'message', text: 'codexapp runtime warning prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
||||||
const codexAppRuntimeWarning = await nextMessage(messages, ws, (msg) => (
|
const codexAppRuntimeWarning = await nextMessage(messages, ws, (msg) => (
|
||||||
@@ -2174,11 +2195,13 @@ async function main() {
|
|||||||
targetConversationId: codexAppSession.sessionId,
|
targetConversationId: codexAppSession.sessionId,
|
||||||
content: 'running codexapp target should reject this',
|
content: 'running codexapp target should reject this',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assert(codexAppRunningMcp.status === 400 && codexAppRunningMcp.body?.code === 'target_running', 'MCP cross send should reject running Codex App targets');
|
assert(codexAppRunningMcp.status === 400 && codexAppRunningMcp.body?.code === 'target_running', 'MCP cross send should reject running Codex App targets');
|
||||||
await sleep(150);
|
await sleep(150);
|
||||||
ws.send(JSON.stringify({ type: 'abort' }));
|
ws.send(JSON.stringify({ type: 'detach_view' }));
|
||||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
await sleep(50);
|
||||||
|
ws.send(JSON.stringify({ type: 'abort', sessionId: codexAppSession.sessionId }));
|
||||||
|
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||||
|
|
||||||
const claudeAttachment = await uploadAttachment(port, token, {
|
const claudeAttachment = await uploadAttachment(port, token, {
|
||||||
filename: 'claude-test.png',
|
filename: 'claude-test.png',
|
||||||
|
|||||||
134
server.js
134
server.js
@@ -6309,7 +6309,7 @@ wss.on('connection', (ws, req) => {
|
|||||||
handleComposerSuggestions(ws, msg);
|
handleComposerSuggestions(ws, msg);
|
||||||
break;
|
break;
|
||||||
case 'abort':
|
case 'abort':
|
||||||
handleAbort(ws);
|
handleAbort(ws, msg);
|
||||||
break;
|
break;
|
||||||
case 'new_session':
|
case 'new_session':
|
||||||
handleNewSession(ws, msg);
|
handleNewSession(ws, msg);
|
||||||
@@ -7831,9 +7831,26 @@ function handleDetachView(ws) {
|
|||||||
wsSessionMap.delete(ws);
|
wsSessionMap.delete(ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAbort(ws) {
|
function bindAbortSessionToWs(sessionId, ws) {
|
||||||
const sessionId = wsSessionMap.get(ws);
|
if (!sessionId || !ws) return;
|
||||||
|
const entries = [
|
||||||
|
activeProcesses.get(sessionId),
|
||||||
|
activeCodexAppTurns.get(sessionId),
|
||||||
|
activeCodexAppGoalCommands.get(sessionId),
|
||||||
|
].filter(Boolean);
|
||||||
|
if (entries.length > 0) detachWsFromActiveRuntimes(ws);
|
||||||
|
wsSessionMap.set(ws, sessionId);
|
||||||
|
for (const entry of entries) {
|
||||||
|
entry.ws = ws;
|
||||||
|
entry.wsDisconnectTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAbort(ws, msg = {}) {
|
||||||
|
const requestedSessionId = sanitizeId(msg?.sessionId || '');
|
||||||
|
const sessionId = requestedSessionId || wsSessionMap.get(ws);
|
||||||
if (!sessionId) return;
|
if (!sessionId) return;
|
||||||
|
bindAbortSessionToWs(sessionId, ws);
|
||||||
if (handleCodexAppAbortSession(sessionId, ws)) return;
|
if (handleCodexAppAbortSession(sessionId, ws)) return;
|
||||||
if (cancelCodexAppGoalCommand(sessionId, ws)) return;
|
if (cancelCodexAppGoalCommand(sessionId, ws)) return;
|
||||||
const entry = activeProcesses.get(sessionId);
|
const entry = activeProcesses.get(sessionId);
|
||||||
@@ -8300,9 +8317,17 @@ function detachWsFromActiveRuntimes(ws, options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function codexAppRuntimeThreadId(params = {}) {
|
||||||
|
return params.threadId || params.thread?.id || params.item?.threadId || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function codexAppRuntimeTurnId(params = {}) {
|
||||||
|
return params.turnId || params.turn?.id || params.item?.turnId || null;
|
||||||
|
}
|
||||||
|
|
||||||
function findCodexAppEntryByRuntime(params = {}) {
|
function findCodexAppEntryByRuntime(params = {}) {
|
||||||
const threadId = params.threadId || params.thread?.id || null;
|
const threadId = codexAppRuntimeThreadId(params);
|
||||||
const turnId = params.turnId || params.turn?.id || null;
|
const turnId = codexAppRuntimeTurnId(params);
|
||||||
if (threadId) {
|
if (threadId) {
|
||||||
for (const [sessionId, entry] of activeCodexAppTurns) {
|
for (const [sessionId, entry] of activeCodexAppTurns) {
|
||||||
if (entry.threadId === threadId) return { sessionId, entry };
|
if (entry.threadId === threadId) return { sessionId, entry };
|
||||||
@@ -8316,6 +8341,95 @@ function findCodexAppEntryByRuntime(params = {}) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findCodexAppSessionByThreadId(threadId) {
|
||||||
|
const targetThreadId = String(threadId || '').trim();
|
||||||
|
if (!targetThreadId) return null;
|
||||||
|
try {
|
||||||
|
for (const file of fs.readdirSync(SESSIONS_DIR)) {
|
||||||
|
if (!file.endsWith('.json')) continue;
|
||||||
|
const sessionId = sanitizeId(file.slice(0, -5));
|
||||||
|
if (!sessionId) continue;
|
||||||
|
const session = loadSession(sessionId);
|
||||||
|
if (!session || !isCodexAppSession(session)) continue;
|
||||||
|
if (getRuntimeSessionId(session) === targetThreadId) return { sessionId: session.id, session };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
plog('WARN', 'codex_app_thread_session_lookup_failed', {
|
||||||
|
threadId: targetThreadId,
|
||||||
|
error: err?.message || String(err || ''),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCodexAppAdoptableRuntimeMethod(method) {
|
||||||
|
return method === 'turn/started'
|
||||||
|
|| method === 'item/started'
|
||||||
|
|| method === 'item/agentMessage/delta'
|
||||||
|
|| method === 'item/commandExecution/outputDelta'
|
||||||
|
|| method === 'item/fileChange/patchUpdated'
|
||||||
|
|| method === 'item/mcpToolCall/progress'
|
||||||
|
|| method === 'plan/updated'
|
||||||
|
|| method === 'turn/plan/updated'
|
||||||
|
|| method === 'item/plan/updated'
|
||||||
|
|| method === 'item/todoList/updated'
|
||||||
|
|| method === 'item/reasoning/summaryTextDelta'
|
||||||
|
|| method === 'item/reasoning/textDelta'
|
||||||
|
|| method === 'item/completed'
|
||||||
|
|| method === 'item/commandExecution/requestApproval'
|
||||||
|
|| method === 'item/fileChange/requestApproval'
|
||||||
|
|| method === 'item/permissions/requestApproval'
|
||||||
|
|| method === 'item/tool/requestApproval'
|
||||||
|
|| method === 'item/tool/requestUserInput'
|
||||||
|
|| method === 'item/tool/call';
|
||||||
|
}
|
||||||
|
|
||||||
|
function adoptCodexAppUnroutedTurn(params = {}, method = '') {
|
||||||
|
if (!isCodexAppAdoptableRuntimeMethod(method)) return null;
|
||||||
|
const threadId = codexAppRuntimeThreadId(params);
|
||||||
|
const turnId = codexAppRuntimeTurnId(params);
|
||||||
|
if (!threadId || !turnId) return null;
|
||||||
|
|
||||||
|
const matched = findCodexAppSessionByThreadId(threadId);
|
||||||
|
if (!matched?.session) return null;
|
||||||
|
const existing = activeCodexAppTurns.get(matched.sessionId);
|
||||||
|
if (existing) return { sessionId: matched.sessionId, entry: existing };
|
||||||
|
|
||||||
|
const entry = {
|
||||||
|
ws: findViewingSessionWs(matched.sessionId),
|
||||||
|
agent: 'codexapp',
|
||||||
|
cwd: matched.session.cwd || getDefaultSessionCwd(),
|
||||||
|
threadId,
|
||||||
|
expectedThreadId: threadId,
|
||||||
|
turnId,
|
||||||
|
fullText: '',
|
||||||
|
toolCalls: [],
|
||||||
|
toolOutputDeltas: new Map(),
|
||||||
|
agentMessageItems: new Map(),
|
||||||
|
mcpContext: {},
|
||||||
|
codexRetry: null,
|
||||||
|
lastUsage: null,
|
||||||
|
lastError: null,
|
||||||
|
errorSent: false,
|
||||||
|
crossConversationReplyRequestId: null,
|
||||||
|
retryRequest: null,
|
||||||
|
clientUserMessageId: crypto.randomUUID(),
|
||||||
|
startedAt: new Date().toISOString(),
|
||||||
|
recoveredFromNotification: true,
|
||||||
|
};
|
||||||
|
activeCodexAppTurns.set(matched.sessionId, entry);
|
||||||
|
persistCodexAppTurnState(matched.sessionId, entry, { immediate: true });
|
||||||
|
broadcastSessionList();
|
||||||
|
plog('INFO', 'codex_app_unrouted_turn_adopted', {
|
||||||
|
sessionId: matched.sessionId.slice(0, 8),
|
||||||
|
threadId,
|
||||||
|
turnId,
|
||||||
|
method,
|
||||||
|
hasViewer: !!entry.ws,
|
||||||
|
});
|
||||||
|
return { sessionId: matched.sessionId, entry };
|
||||||
|
}
|
||||||
|
|
||||||
function parseMaybeJsonObject(value) {
|
function parseMaybeJsonObject(value) {
|
||||||
if (value && typeof value === 'object' && !Array.isArray(value)) return value;
|
if (value && typeof value === 'object' && !Array.isArray(value)) return value;
|
||||||
if (typeof value !== 'string') return null;
|
if (typeof value !== 'string') return null;
|
||||||
@@ -8638,10 +8752,12 @@ function processCcwebMcpChildNotification(child, notification) {
|
|||||||
return { changed: false, done: false };
|
return { changed: false, done: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
function findCodexAppRouteByRuntime(params = {}) {
|
function findCodexAppRouteByRuntime(params = {}, method = '') {
|
||||||
const parent = findCodexAppEntryByRuntime(params);
|
const parent = findCodexAppEntryByRuntime(params);
|
||||||
if (parent) return { ...parent, role: 'parent' };
|
if (parent) return { ...parent, role: 'parent' };
|
||||||
const threadId = params.threadId || params.thread?.id || null;
|
const recoveredParent = adoptCodexAppUnroutedTurn(params, method);
|
||||||
|
if (recoveredParent) return { ...recoveredParent, role: 'parent' };
|
||||||
|
const threadId = codexAppRuntimeThreadId(params);
|
||||||
if (threadId && ccwebMcpChildThreads.has(threadId)) {
|
if (threadId && ccwebMcpChildThreads.has(threadId)) {
|
||||||
const child = ccwebMcpChildThreads.get(threadId);
|
const child = ccwebMcpChildThreads.get(threadId);
|
||||||
return {
|
return {
|
||||||
@@ -8655,7 +8771,7 @@ function findCodexAppRouteByRuntime(params = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCodexAppNotification(notification) {
|
function handleCodexAppNotification(notification) {
|
||||||
const routed = findCodexAppRouteByRuntime(notification?.params || {});
|
const routed = findCodexAppRouteByRuntime(notification?.params || {}, notification?.method || '');
|
||||||
if (handleCodexAppMcpStartupStatusNotification(notification, routed)) return;
|
if (handleCodexAppMcpStartupStatusNotification(notification, routed)) return;
|
||||||
if (!routed) {
|
if (!routed) {
|
||||||
plog('INFO', 'codex_app_notification_unrouted', {
|
plog('INFO', 'codex_app_notification_unrouted', {
|
||||||
@@ -9250,7 +9366,7 @@ function resolvePendingCodexAppApprovalsForSession(sessionId) {
|
|||||||
function handleCodexAppServerRequest(request) {
|
function handleCodexAppServerRequest(request) {
|
||||||
const method = request?.method || '';
|
const method = request?.method || '';
|
||||||
const params = request?.params || {};
|
const params = request?.params || {};
|
||||||
const routed = findCodexAppEntryByRuntime(params);
|
const routed = findCodexAppRouteByRuntime(params, method);
|
||||||
const dynamicToolResponse = method === 'item/tool/call'
|
const dynamicToolResponse = method === 'item/tool/call'
|
||||||
? handleCodexAppDynamicToolCall(routed, params)
|
? handleCodexAppDynamicToolCall(routed, params)
|
||||||
: null;
|
: null;
|
||||||
|
|||||||
Reference in New Issue
Block a user