Extract + unit-test the phantom auto-continue injection path

Move the storeMessage → enqueueMessageCheck side effect out of the delivery
callback into scheduleAutoContinueForPhantomFinal so the injection path is
directly unit-tested: an auto-continue outcome for a delivered final injects the
corrective nudge as an ipc_injected_human message and re-enqueues a turn, while
a non-auto-continue outcome or an undelivered final does nothing. Behavior is
identical to the inline version — this only adds test coverage and a no-op
refactor, so no redeploy is required.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-08-25 23:12:18 +09:00
parent bc9ba1c2d2
commit 007e95fe40
3 changed files with 127 additions and 21 deletions

View File

@@ -1,3 +1,5 @@
import type { NewMessage } from './types.js';
// Guard against "phantom notification" dead-end finals.
//
// Agents sometimes end a turn with a sentence like "I'll wait for the build to
@@ -109,3 +111,39 @@ export function resolvePhantomFinalOutcome(args: {
export function _resetPhantomAutoContinueForTests(): void {
phantomAutoContinueStreak.clear();
}
/**
* Perform the auto-continue side effect after a final was delivered: inject the
* corrective nudge as an inbound instruction and re-enqueue a turn so the agent
* runs again and finishes the work. No-op unless the outcome was `auto-continue`
* AND the final actually delivered. Returns true when it scheduled a re-run.
*
* Extracted from the delivery callback so the storeMessage → enqueueMessageCheck
* path is unit-testable without standing up a full turn.
*/
export function scheduleAutoContinueForPhantomFinal(args: {
outcomeKind: PhantomFinalOutcome['kind'];
delivered: boolean;
chatJid: string;
groupFolder: string;
runId: string;
storeMessage: (message: NewMessage) => void;
enqueueMessageCheck: (chatJid: string, groupFolder: string) => void;
}): boolean {
if (args.outcomeKind !== 'auto-continue' || !args.delivered) {
return false;
}
args.storeMessage({
id: `phantom-continue-${args.runId}-${Date.now().toString(36)}`,
chat_jid: args.chatJid,
sender: 'ejclaw-system',
sender_name: 'EJClaw',
content: PHANTOM_CONTINUE_NUDGE,
timestamp: new Date().toISOString(),
is_from_me: false,
is_bot_message: false,
message_source_kind: 'ipc_injected_human',
});
args.enqueueMessageCheck(args.chatJid, args.groupFolder);
return true;
}