From 41276a7b7a87ae7300e397cc93926bd07140022a Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sun, 17 May 2026 00:50:03 +0200 Subject: [PATCH] feat(auto/loop): mid-loop self-feedback inline-fix dispatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bootstrap drains the triage queue once at session_start (headless.ts: 647 "[headless] autonomous: draining self-feedback triage queue first..."). Entries filed DURING the autonomous run previously sat until the next sf restart — defeating the self-heal thesis for long-running sessions like the 3-day dogfood the user is running now. dispatchSelfFeedbackInlineFixIfNeeded already exists in the extension (self-feedback-drain.js:277) and is wired into bootstrap/register- hooks at session_start. It selects high/critical candidates, debounces via a claim file (so concurrent invocations skip), and on the headless surface spawns a child `sf headless triage --apply` fire-and-forget — the autonomous loop continues unblocked while triage runs in a child. Hook it into the auto-loop top-of-iteration so it fires every MID_LOOP_TRIAGE_INTERVAL=5 iterations. The dispatcher's own claim-file debounce prevents re-dispatch of in-flight entries; pre-bootstrap- drained entries get re-evaluated only when something new shows up. Also ignores scripts/tmp-check-test-imports in biome — the check- test-imports.test.mjs self-test creates regression fixtures there and they triggered formatter errors on dirty exits. Co-Authored-By: Claude Opus 4.7 (1M context) --- biome.json | 3 ++- src/resources/extensions/sf/auto/loop.js | 31 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/biome.json b/biome.json index 33207c298..0ec473c77 100644 --- a/biome.json +++ b/biome.json @@ -16,7 +16,8 @@ "!!**/rust-engine/npm", "!!**/*.min.js", "!!packages/coding-agent/src/core/export-html/template.css", - "!!src/resources/skills/create-sf-extension/templates" + "!!src/resources/skills/create-sf-extension/templates", + "!!scripts/tmp-check-test-imports" ] }, "formatter": { diff --git a/src/resources/extensions/sf/auto/loop.js b/src/resources/extensions/sf/auto/loop.js index 7279589bd..360b0e67b 100644 --- a/src/resources/extensions/sf/auto/loop.js +++ b/src/resources/extensions/sf/auto/loop.js @@ -18,6 +18,7 @@ import { resolveEngine } from "../engine-resolver.js"; import { getErrorMessage } from "../error-utils.js"; import { NOTICE_KIND } from "../notification-store.js"; import { sfRoot } from "../paths.js"; +import { dispatchSelfFeedbackInlineFixIfNeeded } from "../self-feedback-drain.js"; import { recordSelfFeedback } from "../self-feedback.js"; import { getDatabase } from "../sf-db.js"; import { @@ -613,6 +614,12 @@ export async function autoLoop(ctx, pi, s, deps) { recentUnits: persisted.recentUnits, stuckRecoveryAttempts: persisted.stuckRecoveryAttempts, consecutiveFinalizeTimeouts: 0, + // Iteration at which the autonomous loop last invoked the + // self-feedback inline-fix dispatcher. Used by the mid-loop + // triage-drain hook below to avoid spamming the dispatcher on + // every iteration while still picking up entries filed during + // the run (which previously sat until next session_start). + lastInlineFixDispatchIteration: 0, }; let consecutiveErrors = 0; let consecutiveCooldowns = 0; @@ -632,6 +639,30 @@ export async function autoLoop(ctx, pi, s, deps) { } iteration++; debugLog("autoLoop", { phase: "loop-top", iteration }); + // ── Mid-loop self-feedback inline-fix dispatcher ────────────────── + // Bootstrap drains the triage queue once at session_start (see + // src/headless.ts:647). Entries filed DURING the autonomous run + // previously sat until the next sf restart — defeats the + // self-heal thesis for long-running sessions. Re-invoke the + // inline-fix dispatcher every MID_LOOP_TRIAGE_INTERVAL iterations + // so any high/critical entry filed mid-run gets a triage pass + // without waiting for restart. The dispatcher already debounces + // via claim file (see dispatchSelfFeedbackInlineFixIfNeeded) and + // is fire-and-forget on the headless surface, so this is safe to + // call on every Nth iteration. + const MID_LOOP_TRIAGE_INTERVAL = 5; + if ( + iteration > 1 && + iteration - loopState.lastInlineFixDispatchIteration >= + MID_LOOP_TRIAGE_INTERVAL + ) { + try { + dispatchSelfFeedbackInlineFixIfNeeded(s.basePath, ctx, pi); + } catch { + // Never block the loop on triage-dispatch failure. + } + loopState.lastInlineFixDispatchIteration = iteration; + } // ── Halt watchdog: detect idle/stuck iterations ── const { stuck, elapsedMs } = watchdog.check(ctx, iteration); if (stuck) {