feat: improve codex app controls and recovery
This commit is contained in:
@@ -25,6 +25,7 @@ if (args[0] !== 'app-server') {
|
||||
const threads = new Map();
|
||||
const pendingServerRequests = new Map();
|
||||
let nextServerRequestId = 1;
|
||||
let mcpReloadCount = 0;
|
||||
|
||||
function send(message) {
|
||||
process.stdout.write(`${JSON.stringify(message)}\n`);
|
||||
@@ -429,6 +430,20 @@ function startTurn(params) {
|
||||
},
|
||||
});
|
||||
|
||||
if (/runtime warning/i.test(text)) {
|
||||
const message = 'Heads up: Long threads and multiple compactions can cause the model to be less accurate. Start a new thread when possible to keep threads small and targeted.';
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
send({
|
||||
method: 'warning',
|
||||
params: {
|
||||
threadId: thread.id,
|
||||
turnId,
|
||||
message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (/collaboration/i.test(text)) {
|
||||
completeTurn(thread, turnId, `collaboration mode: ${collaborationSummary(params)}`);
|
||||
return { turn: { id: turnId, status: 'running', items: [] } };
|
||||
@@ -516,6 +531,11 @@ function handleRequest(message) {
|
||||
send({ id, result: { data: [{ mode: 'default' }, { mode: 'plan' }] } });
|
||||
return;
|
||||
}
|
||||
if (method === 'config/mcpServer/reload') {
|
||||
mcpReloadCount += 1;
|
||||
send({ id, result: { reloaded: true, reloadCount: mcpReloadCount } });
|
||||
return;
|
||||
}
|
||||
if (method === 'thread/start') {
|
||||
const thread = ensureThread(null, params);
|
||||
send({ id, result: { thread: threadPayload(thread), model: params.model || 'gpt-5.5', cwd: thread.cwd, modelProvider: 'mock', approvalPolicy: params.approvalPolicy || 'never', approvalsReviewer: 'user', sandbox: params.sandbox || 'danger-full-access' } });
|
||||
|
||||
@@ -168,6 +168,20 @@ async function fetchAuthedJson(port, token, pathname) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function postAuthedJson(port, token, pathname, body = {}) {
|
||||
const response = await fetch(`http://127.0.0.1:${port}${pathname}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const payload = await response.json();
|
||||
assert(response.ok && payload.ok, `POST failed for ${pathname}: ${payload.message || response.status}`);
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function callInternalMcp(port, token, payload) {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/internal/mcp`, {
|
||||
method: 'POST',
|
||||
@@ -785,6 +799,22 @@ async function main() {
|
||||
assert(/"hasTopLevelEffort":false/.test(codexAppDefaultCollab.text || ''), 'Codex App collaboration turn should not duplicate effort at top level');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
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) => (
|
||||
msg.type === 'system_message' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
/Long threads and multiple compactions/.test(msg.message || '')
|
||||
));
|
||||
assert(/Long threads and multiple compactions/.test(codexAppRuntimeWarning.message || ''), 'Codex App should surface the first runtime warning');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
await sleep(150);
|
||||
const duplicateRuntimeWarnings = messages.filter((msg) => (
|
||||
msg.type === 'system_message' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
/Long threads and multiple compactions/.test(msg.message || '')
|
||||
));
|
||||
assert(duplicateRuntimeWarnings.length === 0, 'Codex App should suppress duplicate runtime warning banners');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp empty reasoning prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
const storedCodexAppAfterReasoning = JSON.parse(fs.readFileSync(path.join(sessionsDir, `${codexAppSession.sessionId}.json`), 'utf8'));
|
||||
@@ -804,6 +834,10 @@ async function main() {
|
||||
assert(storedCodexApp.messages.some((message) => message.role === 'assistant' && /codexapp tool prompt/.test(String(message.content || ''))), 'Codex App assistant response should be persisted');
|
||||
assert((storedCodexApp.totalUsage?.inputTokens || 0) > 0, 'Codex App token usage should be persisted');
|
||||
|
||||
const reloadMcpResult = await postAuthedJson(port, token, `/api/sessions/${codexAppSession.sessionId}/reload-mcp`);
|
||||
assert(reloadMcpResult.sessionId === codexAppSession.sessionId, 'Codex App MCP reload should return the target session id');
|
||||
assert(reloadMcpResult.result?.reloaded === true, 'Codex App MCP reload should call app-server config/mcpServer/reload');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'codexapp dynamic prompt', sessionId: codexAppSession.sessionId, mode: 'yolo', agent: 'codexapp' }));
|
||||
const codexAppDynamicTool = await nextMessage(messages, ws, (msg) => msg.type === 'tool_end' && msg.sessionId === codexAppSession.sessionId && msg.toolUseId === 'mcp-ccweb-list');
|
||||
assert(codexAppDynamicTool.kind === 'mcp_tool_call', 'Codex App should surface ccweb MCP tool calls');
|
||||
@@ -835,10 +869,17 @@ async function main() {
|
||||
assert(guidedRequest.questions?.[0]?.id === 'choice', 'Codex App should forward request_user_input questions');
|
||||
ws.send(JSON.stringify({
|
||||
type: 'codex_app_user_input_response',
|
||||
action: 'submit',
|
||||
sessionId: codexAppSession.sessionId,
|
||||
requestId: guidedRequest.requestId,
|
||||
answers: { choice: { answers: ['A'] } },
|
||||
}));
|
||||
const guidedSubmitted = await nextMessage(messages, ws, (msg) =>
|
||||
msg.type === 'system_message' &&
|
||||
msg.sessionId === codexAppSession.sessionId &&
|
||||
/已提交.*引导输入/.test(msg.message || '')
|
||||
);
|
||||
assert(/已提交.*引导输入/.test(guidedSubmitted.message || ''), 'Codex App should show guided input submission hint');
|
||||
const guidedDelta = await nextMessage(messages, ws, (msg) => msg.type === 'text_delta' && msg.sessionId === codexAppSession.sessionId && /guided answer: A/.test(msg.text || ''));
|
||||
assert(/guided answer: A/.test(guidedDelta.text || ''), 'Codex App should continue after guided input response');
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === codexAppSession.sessionId);
|
||||
|
||||
Reference in New Issue
Block a user