fix: soft-detect submission + only NEW completion messages count

- countDoneMsgs baseline before submit: lingering 'has been scheduled' chatter from
  the PREVIOUS gen no longer triggers an instant post-gen reload (the self-reload bug)
- promptEchoed: chat echoing the prompt = strongest it-stuck signal, added to the
  post-Enter check; and on retry it means the earlier submit registered -> skip
  re-typing entirely (no double generation / double credits)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-07 18:54:05 +10:00
parent 7fbde35e47
commit 39d133ddfb

View File

@ -221,13 +221,24 @@ function persistentError() {
(e.textContent || '').length < 180); (e.textContent || '').length < 180);
} }
// The agent's end-of-generation chatter. John's law: this message LIES — "scheduled / // The agent's end-of-generation chatter. John's law #1: this message LIES — "scheduled /
// waiting in the queue due to high demand" often means it's actually DONE, and the // waiting in the queue" usually means it's actually DONE (refresh to see it).
// stale page won't show the asset until a refresh. // John's law #2: OLD messages linger in the chat history, so never match absolutely —
function agentSaysDone() { // COUNT matches and only act when a NEW one appears after our submit.
return [...document.querySelectorAll('*')].some(e => visible(e) && function countDoneMsgs() {
/has been scheduled|waiting in the queue|check back in a few|is ready|i'?ve (generated|created)|here('s| is) (your|the) video/i.test(e.textContent || '') && const m = (document.body.innerText || '').match(
(e.textContent || '').length < 240); /has been scheduled|waiting in the queue|check back in a few|is ready|i'?ve (generated|created)|here('s| is) (your|the) video/gi);
return m ? m.length : 0;
}
// The chat panel echoes the submitted prompt — the strongest "it stuck" signal, and
// on a retry it means the PREVIOUS submit registered (so don't type it again).
function promptEchoed(prompt) {
const needle = String(prompt).slice(0, 60).trim();
if (!needle) return false;
return [...document.querySelectorAll('div, p, span')].some(e => visible(e) &&
!e.closest('[contenteditable="true"], textarea') &&
(e.textContent || '').includes(needle) && (e.textContent || '').length < needle.length + 500);
} }
// Flow's "Try again" button, shown beside the "Something went wrong" banner. // Flow's "Try again" button, shown beside the "Something went wrong" banner.
@ -348,25 +359,31 @@ async function executeTask(task) {
if (!input) return await requeueWithReload(task, 'agent input never became ready'); if (!input) return await requeueWithReload(task, 'agent input never became ready');
const before = snapshotAssets(); const before = snapshotAssets();
const doneBase = countDoneMsgs(); // old "finished" chatter lingers — only a NEW one counts
const landed = await typeIntoEditor(input, task.prompt); // Soft-detect first: if the chat already echoes this prompt, the PREVIOUS attempt's
if (!landed) return await requeueWithReload(task, 'prompt did not register in the editor'); // submit registered before its reload — re-typing would double-generate. Skip to waiting.
log('info', `Prompt set (${landed} chars). Submitting…`); const alreadySubmitted = promptEchoed(task.prompt);
await sleep(600); // John's ritual: paste, wait a sec, THEN enter if (alreadySubmitted) {
log('info', 'Prompt already echoed in the session (earlier submit registered) — not re-typing.');
} else {
const landed = await typeIntoEditor(input, task.prompt);
if (!landed) return await requeueWithReload(task, 'prompt did not register in the editor');
log('info', `Prompt set (${landed} chars). Submitting…`);
await sleep(600); // John's ritual: paste, wait a sec, THEN enter
await submitEditor(input); // trusted Enter via the DevTools Protocol — this submits await submitEditor(input); // trusted Enter via the DevTools Protocol — this submits
// Success signal = the square stop button appears (or the box empties). If neither, // "It stuck" = stop square appears, OR the box empties, OR the chat echoes the prompt.
// try the labelled submit button once, then hard-refresh — Enter into a wedged const stuck = () => findStopSquare() || editorText(input).length === 0 || promptEchoed(task.prompt);
// session never recovers on its own. let took = await waitFor(stuck, 6000, 300);
let took = await waitFor(() => findStopSquare() || editorText(input).length === 0, 6000, 300); if (!took) {
if (!took) { const submit = findSubmit(input);
const submit = findSubmit(input); if (submit) { submit.click(); log('info', 'Enter fallback → clicked submit.'); }
if (submit) { submit.click(); log('info', 'Enter fallback → clicked submit.'); } took = await waitFor(stuck, 6000, 300);
took = await waitFor(() => findStopSquare() || editorText(input).length === 0, 6000, 300); }
if (!took) return await requeueWithReload(task, 'submission never stuck (no stop square, no echo, prompt still in box)');
log('info', 'Submitted (soft-detect confirmed). Waiting for generation…');
} }
if (!took) return await requeueWithReload(task, 'submission never stuck (no stop button, prompt still in box)');
log('info', 'Submitted (stop square seen). Waiting for generation…');
const timeoutMs = task.kind === 'image' ? 3 * 60 * 1000 : settings.genTimeoutMs; const timeoutMs = task.kind === 'image' ? 3 * 60 * 1000 : settings.genTimeoutMs;
@ -379,7 +396,7 @@ async function executeTask(task) {
let signal = null; let signal = null;
while (Date.now() - t0 < timeoutMs) { while (Date.now() - t0 < timeoutMs) {
if (snapshotAssets().size > before.size) { signal = 'asset in grid'; break; } if (snapshotAssets().size > before.size) { signal = 'asset in grid'; break; }
if (agentSaysDone()) { signal = 'agent completion message'; break; } if (countDoneMsgs() > doneBase) { signal = 'NEW agent completion message'; break; }
if (Date.now() - t0 > 6000 && persistentError()) { await handleRateLimit(task); return; } if (Date.now() - t0 > 6000 && persistentError()) { await handleRateLimit(task); return; }
if (Date.now() - t0 > 8000 && (findTryAgain() || flowError())) if (Date.now() - t0 > 8000 && (findTryAgain() || flowError()))
return await requeueWithReload(task, 'Flow errored during video generation'); return await requeueWithReload(task, 'Flow errored during video generation');