refactor: remove auto-continue from codex runner

Auto-continue was punishing the model for thinking — text-only turns
were treated as failures and re-prompted up to 5 times with aggressive
"execute now" messages. This made Codex overly compliant and suppressed
critical thinking.

Now each turn result is delivered directly. The model decides whether
to think, push back, or execute.
This commit is contained in:
Eyejoker
2026-03-13 22:43:06 +09:00
parent 8bd5f71a95
commit 37188bd98e

View File

@@ -62,8 +62,6 @@ const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
const IPC_POLL_MS = 500;
const MAX_TURNS = 100;
const MAX_AUTO_CONTINUES = 5;
const AUTO_CONTINUE_PROMPT = 'Continue. Execute the task — don\'t just describe what you\'ll do.';
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
@@ -414,12 +412,11 @@ async function executeTurn(
server: CodexAppServer,
threadId: string,
prompt: string,
): Promise<{ result: string; error?: string; turnId: string; hadToolExecution: boolean }> {
): Promise<{ result: string; error?: string; turnId: string }> {
return new Promise((resolve) => {
let agentText = '';
let turnId = '';
let resolved = false;
let hadToolExecution = false;
let ipcPollTimer: ReturnType<typeof setTimeout> | null = null;
const cleanup = () => {
@@ -441,7 +438,6 @@ async function executeTurn(
result: agentText || '',
error: 'Turn timed out after 5 minutes',
turnId,
hadToolExecution,
});
}
}, 5 * 60 * 1000);
@@ -504,9 +500,9 @@ async function executeTurn(
if (status === 'failed') {
const errMsg = (error?.message as string) || 'Turn failed';
resolve({ result: agentText || '', error: errMsg, turnId, hadToolExecution });
resolve({ result: agentText || '', error: errMsg, turnId });
} else {
resolve({ result: agentText, turnId, hadToolExecution });
resolve({ result: agentText, turnId });
}
break;
}
@@ -526,7 +522,6 @@ async function executeTurn(
if (msg.method === 'item/commandExecution/requestApproval' ||
msg.method === 'item/fileChange/requestApproval' ||
msg.method === 'item/permissions/requestApproval') {
hadToolExecution = true;
server.respond(msg.id, { decision: 'accept' });
return;
}
@@ -550,7 +545,6 @@ async function executeTurn(
result: '',
error: `Failed to start turn: ${err.message}`,
turnId: '',
hadToolExecution: false,
});
}
});
@@ -601,7 +595,6 @@ async function main(): Promise<void> {
: await server.startThread();
let turnCount = 0;
let autoContinueCount = 0;
// Main turn loop
while (true) {
@@ -616,13 +609,12 @@ async function main(): Promise<void> {
break;
}
log(`Starting turn ${turnCount}/${MAX_TURNS} (auto-continue: ${autoContinueCount}/${MAX_AUTO_CONTINUES})...`);
log(`Starting turn ${turnCount}/${MAX_TURNS}...`);
const { result, error, hadToolExecution } = await executeTurn(server, threadId, prompt);
const { result, error } = await executeTurn(server, threadId, prompt);
// Check close sentinel
if (shouldClose()) {
// Flush any pending output before exiting
if (result) {
writeOutput({ status: 'success', result, newSessionId: threadId });
}
@@ -630,23 +622,6 @@ async function main(): Promise<void> {
break;
}
// Auto-continue: if the turn produced only text (no tool execution),
// nudge Codex to actually execute instead of just describing plans.
// This mimics `codex exec --full-auto` behavior.
if (!error && !hadToolExecution && result && autoContinueCount < MAX_AUTO_CONTINUES) {
autoContinueCount++;
log(`Turn had no tool execution, auto-continuing (${autoContinueCount}/${MAX_AUTO_CONTINUES})`);
// Still emit the intermediate text so user sees progress
writeOutput({ status: 'success', result, newSessionId: threadId });
prompt = AUTO_CONTINUE_PROMPT;
continue;
}
// Reset auto-continue counter when tools were actually executed
if (hadToolExecution) {
autoContinueCount = 0;
}
if (error) {
log(`Turn error: ${error}`);
writeOutput({
@@ -673,7 +648,6 @@ async function main(): Promise<void> {
log(`Got new message (${nextMessage.length} chars)`);
prompt = nextMessage;
autoContinueCount = 0; // Reset on new user message
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);