fix(sf): reload after self-feedback inline fixes
This commit is contained in:
parent
a4059e5871
commit
f5290e41aa
5 changed files with 101 additions and 3 deletions
|
|
@ -2185,6 +2185,7 @@ export class AgentSession {
|
|||
})();
|
||||
},
|
||||
getSystemPrompt: () => this.systemPrompt,
|
||||
requestReload: () => {},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1287,6 +1287,11 @@ export class InteractiveMode {
|
|||
})();
|
||||
},
|
||||
getSystemPrompt: () => this.session.systemPrompt,
|
||||
requestReload: () => {
|
||||
setTimeout(() => {
|
||||
void this.handleReloadCommand();
|
||||
}, 0);
|
||||
},
|
||||
});
|
||||
|
||||
// Set up the extension shortcut handler on the default editor
|
||||
|
|
|
|||
|
|
@ -367,12 +367,28 @@ export function registerHooks(
|
|||
// Squash-merge quick-task branch back to the original branch after the
|
||||
// agent turn completes (#2668). cleanupQuickBranch is a no-op when no
|
||||
// quick-return state is pending, so this is safe to call on every turn.
|
||||
pi.on("turn_end", async () => {
|
||||
pi.on("turn_end", async (_event, ctx) => {
|
||||
try {
|
||||
cleanupQuickBranch();
|
||||
} catch {
|
||||
// Best-effort: don't break the turn lifecycle if cleanup fails.
|
||||
}
|
||||
try {
|
||||
const { consumeCompletedInlineFixClaim } = await import(
|
||||
"../self-feedback-drain.js"
|
||||
);
|
||||
const resolvedIds = consumeCompletedInlineFixClaim(process.cwd());
|
||||
if (resolvedIds.length > 0) {
|
||||
const requestReload = (
|
||||
ctx as ExtensionContext & { requestReload?: (reason?: string) => void }
|
||||
).requestReload;
|
||||
requestReload?.(
|
||||
`self-feedback inline fix resolved ${resolvedIds.length} entr${resolvedIds.length === 1 ? "y" : "ies"}`,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// Best-effort: stale code should not break normal turn completion.
|
||||
}
|
||||
});
|
||||
|
||||
pi.on("session_before_compact", async () => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@
|
|||
* Consumer: session_start hook in bootstrap/register-hooks.ts.
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
unlinkSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import type {
|
||||
ExtensionAPI,
|
||||
|
|
@ -56,6 +62,14 @@ function writeClaim(basePath: string, ids: string[]): void {
|
|||
);
|
||||
}
|
||||
|
||||
function clearClaim(basePath: string): void {
|
||||
try {
|
||||
unlinkSync(claimPath(basePath));
|
||||
} catch {
|
||||
/* non-fatal */
|
||||
}
|
||||
}
|
||||
|
||||
function sameIds(a: string[], b: string[]): boolean {
|
||||
return a.length === b.length && a.every((id, idx) => id === b[idx]);
|
||||
}
|
||||
|
|
@ -174,3 +188,31 @@ export function dispatchSelfFeedbackInlineFixIfNeeded(
|
|||
);
|
||||
return candidates.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume a completed inline-fix claim when all claimed entries are resolved.
|
||||
*
|
||||
* Purpose: self-patches must become active without the operator manually
|
||||
* noticing that files changed; this detects the end of the repair turn and lets
|
||||
* the lifecycle hook request a deferred reload.
|
||||
*
|
||||
* Consumer: register-hooks.ts turn_end handler.
|
||||
*/
|
||||
export function consumeCompletedInlineFixClaim(basePath: string): string[] {
|
||||
const claim = readClaim(basePath);
|
||||
if (!claim || claim.ids.length === 0) return [];
|
||||
|
||||
const byId = new Map(
|
||||
[...readAllSelfFeedback(basePath), ...readUpstreamSelfFeedback()].map(
|
||||
(entry) => [entry.id, entry],
|
||||
),
|
||||
);
|
||||
const allResolved = claim.ids.every((id) => {
|
||||
const entry = byId.get(id);
|
||||
return !entry || Boolean(entry.resolvedAt);
|
||||
});
|
||||
if (!allResolved) return [];
|
||||
|
||||
clearClaim(basePath);
|
||||
return claim.ids;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, it } from "vitest";
|
||||
import { recordSelfFeedback } from "../self-feedback.ts";
|
||||
import { markResolved, recordSelfFeedback } from "../self-feedback.ts";
|
||||
import {
|
||||
consumeCompletedInlineFixClaim,
|
||||
dispatchSelfFeedbackInlineFixIfNeeded,
|
||||
selectInlineFixCandidates,
|
||||
} from "../self-feedback-drain.ts";
|
||||
|
|
@ -102,6 +103,39 @@ describe("self-feedback inline drain", () => {
|
|||
assert.match(JSON.stringify(messages[0]), /sf-self-feedback-inline-fix/);
|
||||
});
|
||||
|
||||
it("consumes the claim after the inline-fix entries are resolved", () => {
|
||||
const root = makeForgeProject();
|
||||
const recorded = recordSelfFeedback(
|
||||
{
|
||||
kind: "agent-infrastructure",
|
||||
severity: "high",
|
||||
summary: "Self patch landed but active runtime stayed stale",
|
||||
source: "detector",
|
||||
},
|
||||
root,
|
||||
);
|
||||
assert.ok(recorded);
|
||||
|
||||
const ctx = { ui: { notify() {} } } as any;
|
||||
const pi = { sendMessage() {} } as any;
|
||||
assert.equal(dispatchSelfFeedbackInlineFixIfNeeded(root, ctx, pi), 1);
|
||||
assert.deepEqual(consumeCompletedInlineFixClaim(root), []);
|
||||
|
||||
assert.equal(
|
||||
markResolved(
|
||||
recorded.entry.id,
|
||||
{
|
||||
reason: "verified reload request after inline fix",
|
||||
evidence: { kind: "agent-fix", commitSha: "abc1234" },
|
||||
},
|
||||
root,
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.deepEqual(consumeCompletedInlineFixClaim(root), [recorded.entry.id]);
|
||||
assert.deepEqual(consumeCompletedInlineFixClaim(root), []);
|
||||
});
|
||||
|
||||
it("selects high priority upstream entries filed while sf ran in another repo", () => {
|
||||
const root = makeForgeProject();
|
||||
const sfHome = process.env.SF_HOME!;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue