fix(self-feedback-drain): route inline-fix dispatch via 'sf headless triage --apply' when SF_HEADLESS=1
The existing dispatch used pi.sendMessage to queue a chat followUp. That works in interactive sf sessions but no chat agent is listening in 'sf headless' / autonomous flows — the message is queued and never delivered, leaving the high/critical blocker active on every iteration. When SF_HEADLESS=1, spawn the same triage-decider → review-code pipeline (via the already-shipped 'sf headless triage --apply' subprocess) instead. The autonomous loop then sees resolved entries via DB on the next gate check, no chat agent required. Forge-only: the dispatcher still only operates in the SF repo itself — `readAllSelfFeedback` for non-forge repos returns the upstream-feedback log (SF developer work), which must not be auto-dispatched from inside consumer projects. Documented that constraint inline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b0ebe7ce18
commit
7e1631618a
1 changed files with 38 additions and 1 deletions
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Consumer: session_start hook in bootstrap/register-hooks.ts.
|
||||
*/
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { execFileSync, spawn } from "node:child_process";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
|
|
@ -207,6 +207,11 @@ function effectiveEffort(entry) {
|
|||
}
|
||||
|
||||
export function selectInlineFixCandidates(basePath) {
|
||||
// Forge-only: in consumer projects, `readAllSelfFeedback` returns the
|
||||
// global upstream-feedback log (~/.sf/agent/upstream-feedback.jsonl),
|
||||
// which lists SF-developer work items. We must not auto-dispatch a
|
||||
// triage agent against those from inside a consumer project — that
|
||||
// would let any downstream project mark SF's own work as resolved.
|
||||
if (!isForgeRepo(basePath)) return [];
|
||||
return readAllSelfFeedback(basePath)
|
||||
.filter((entry) => {
|
||||
|
|
@ -306,6 +311,38 @@ export function dispatchSelfFeedbackInlineFixIfNeeded(basePath, ctx, pi) {
|
|||
// Mode transition is best-effort
|
||||
}
|
||||
writeClaim(basePath, ids);
|
||||
// Headless / autonomous surfaces have no interactive chat agent listening
|
||||
// for `pi.sendMessage` — the followUp gets queued but never delivered, so
|
||||
// the blocker persists across iterations. Route through `sf headless triage
|
||||
// --apply` instead, which dispatches the same triage-decider → review-code
|
||||
// pipeline through SF's own subprocess machinery (router-resolved model,
|
||||
// watchdog, trust gate). Fire-and-forget: the autonomous loop will see the
|
||||
// resolved entries via DB on the next iteration's gate check.
|
||||
if (process.env.SF_HEADLESS === "1") {
|
||||
ctx.ui.notify(
|
||||
`Dispatching self-feedback inline fix via 'sf headless triage --apply' for ${ids.length} high/critical entr${ids.length === 1 ? "y" : "ies"} (headless surface).`,
|
||||
"warning",
|
||||
);
|
||||
const sfBin = process.env.SF_BIN_PATH || process.argv[1];
|
||||
if (!sfBin) {
|
||||
writeFailedClaim(basePath, ids, "SF_BIN_PATH unavailable");
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[sfBin, "headless", "triage", "--apply", "--json"],
|
||||
{ cwd: basePath, stdio: ["ignore", "ignore", "ignore"], detached: true },
|
||||
);
|
||||
child.on("error", (err) => {
|
||||
writeFailedClaim(basePath, ids, getErrorMessage(err));
|
||||
});
|
||||
child.unref();
|
||||
} catch (err) {
|
||||
writeFailedClaim(basePath, ids, getErrorMessage(err));
|
||||
}
|
||||
return candidates.length;
|
||||
}
|
||||
const prompt = buildInlineFixPrompt(candidates);
|
||||
ctx.ui.notify(
|
||||
`Queueing self-feedback inline fix for ${ids.length} high/critical entr${ids.length === 1 ? "y" : "ies"}.`,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue