fix: Codex /compact 支持与自动续跑
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
function readStdin() {
|
||||
return new Promise((resolve) => {
|
||||
@@ -14,9 +17,22 @@ function readStdin() {
|
||||
(async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const isResume = args[0] === 'exec' && args[1] === 'resume';
|
||||
const threadId = isResume && args[2] ? args[2] : `mock-${crypto.randomUUID()}`;
|
||||
const threadId = (() => {
|
||||
if (!isResume) return `mock-${crypto.randomUUID()}`;
|
||||
for (let i = args.length - 1; i >= 2; i--) {
|
||||
const arg = args[i];
|
||||
if (arg === '-' || String(arg).startsWith('-')) continue;
|
||||
return arg;
|
||||
}
|
||||
return `mock-${crypto.randomUUID()}`;
|
||||
})();
|
||||
const input = (await readStdin()).trim();
|
||||
const imageCount = args.filter((arg) => arg === '--image').length;
|
||||
const statePath = path.join(os.tmpdir(), `cc-web-mock-codex-${threadId}.json`);
|
||||
let state = {};
|
||||
try {
|
||||
state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||
} catch {}
|
||||
|
||||
process.stdout.write(`${JSON.stringify({ type: 'thread.started', thread_id: threadId })}\n`);
|
||||
process.stdout.write(`${JSON.stringify({ type: 'turn.started' })}\n`);
|
||||
@@ -46,15 +62,36 @@ function readStdin() {
|
||||
})}\n`);
|
||||
}
|
||||
|
||||
if (input === '/compact') {
|
||||
state.compacted = true;
|
||||
fs.writeFileSync(statePath, JSON.stringify(state));
|
||||
}
|
||||
|
||||
if (input === 'trigger codex context limit' && !state.compacted) {
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
type: 'turn.failed',
|
||||
error: { message: 'Context window exceeded. Please use /compact and retry.' },
|
||||
})}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const responseText = input === '/compact'
|
||||
? 'Codex compact finished.'
|
||||
: `Codex mock handled (${imageCount} image): ${input}`;
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
type: 'item.completed',
|
||||
item: {
|
||||
id: 'item_msg',
|
||||
type: 'agent_message',
|
||||
text: `Codex mock handled (${imageCount} image): ${input}`,
|
||||
text: responseText,
|
||||
},
|
||||
})}\n`);
|
||||
|
||||
if (input === 'trigger codex context limit' && state.compacted) {
|
||||
try { fs.unlinkSync(statePath); } catch {}
|
||||
}
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
type: 'turn.completed',
|
||||
usage: { input_tokens: 10, cached_input_tokens: 2, output_tokens: 5 },
|
||||
|
||||
@@ -350,6 +350,28 @@ async function main() {
|
||||
assert(runtimeToml.includes('preferred_auth_method = "apikey"'), 'Codex custom profile should write isolated runtime auth mode');
|
||||
assert(runtimeToml.includes('base_url = "https://example.com/v1"'), 'Codex custom profile should write isolated runtime base_url');
|
||||
|
||||
ws.send(JSON.stringify({ type: 'message', text: '/compact', sessionId: firstMessageSession.sessionId, mode: 'yolo', agent: 'codex' }));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && /正在执行 Codex \/compact/.test(msg.message || ''));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === firstMessageSession.sessionId);
|
||||
const compactDoneMsg = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && /已执行 Codex \/compact/.test(msg.message || ''));
|
||||
assert(/已执行 Codex \/compact/.test(compactDoneMsg.message || ''), 'Codex /compact should complete with Codex-specific status message');
|
||||
|
||||
const autoCompactCwd = path.join(tempRoot, 'codex-auto-compact');
|
||||
mkdirp(autoCompactCwd);
|
||||
ws.send(JSON.stringify({ type: 'new_session', agent: 'codex', cwd: autoCompactCwd, mode: 'yolo' }));
|
||||
const autoCompactSession = await nextMessage(messages, ws, (msg) => msg.type === 'session_info' && msg.agent === 'codex' && msg.cwd === autoCompactCwd);
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'warm up auto compact', sessionId: autoCompactSession.sessionId, mode: 'yolo', agent: 'codex' }));
|
||||
await nextMessage(messages, ws, (msg) => msg.type === 'done' && msg.sessionId === autoCompactSession.sessionId);
|
||||
ws.send(JSON.stringify({ type: 'message', text: 'trigger codex context limit', sessionId: autoCompactSession.sessionId, mode: 'yolo', agent: 'codex' }));
|
||||
const autoCompactStart = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && /正在按 Codex \/compact 自动压缩/.test(msg.message || ''));
|
||||
assert(/Codex \/compact/.test(autoCompactStart.message || ''), 'Codex auto /compact should announce auto compact start');
|
||||
const autoCompactDone = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && /已执行 Codex \/compact/.test(msg.message || ''));
|
||||
assert(/已执行 Codex \/compact/.test(autoCompactDone.message || ''), 'Codex auto /compact should finish compact step');
|
||||
const autoCompactResume = await nextMessage(messages, ws, (msg) => msg.type === 'system_message' && /按 Codex 压缩计划继续执行/.test(msg.message || ''));
|
||||
assert(/继续执行/.test(autoCompactResume.message || ''), 'Codex auto /compact should announce retry');
|
||||
const autoCompactRetryText = await nextMessage(messages, ws, (msg) => msg.type === 'text_delta' && /trigger codex context limit/.test(msg.text || ''), 8000);
|
||||
assert(/trigger codex context limit/.test(autoCompactRetryText.text || ''), 'Codex auto /compact should replay the failed prompt after compact');
|
||||
|
||||
const claudeAttachment = await uploadAttachment(port, token, {
|
||||
filename: 'claude-test.png',
|
||||
mime: 'image/png',
|
||||
|
||||
Reference in New Issue
Block a user