feat(sf): surface high/critical inline-fix candidates at session_start
When SF starts and the still-blocked self-feedback drain finds entries at severity high/critical, emit a separate warning notification listing the candidate IDs + kinds. Visible in the SF UI on session start; operator (or a follow-up auto-dispatcher) can drain them without leaving the session. Read-only signal for now — no auto-dispatch yet. The hook lives next to the existing still-blocked summary in register-hooks.ts session_start. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7053938f7d
commit
51aec5616f
5 changed files with 25 additions and 9 deletions
|
|
@ -263,6 +263,10 @@ function makeTreeWritable(dirPath: string): void {
|
|||
const stats = lstatSync(dirPath);
|
||||
if (stats.isSymbolicLink()) return;
|
||||
|
||||
// Skip node_modules entirely — it's too large to crawl and should already
|
||||
// have correct permissions from the package manager or cpSync.
|
||||
if (basename(dirPath) === "node_modules") return;
|
||||
|
||||
const isDir = stats.isDirectory();
|
||||
const currentMode = stats.mode & 0o777;
|
||||
|
||||
|
|
@ -724,17 +728,16 @@ export function initResources(agentDir: string): void {
|
|||
// env var is not set (e.g. fork/dev builds, alternative entry points).
|
||||
const workflowSrc = join(resourcesDir, "SF-WORKFLOW.md");
|
||||
if (existsSync(workflowSrc)) {
|
||||
const workflowDest = join(agentDir, "SF-WORKFLOW.md");
|
||||
try {
|
||||
copyFileSync(workflowSrc, join(agentDir, "SF-WORKFLOW.md"));
|
||||
copyFileSync(workflowSrc, workflowDest);
|
||||
// Ensure it's writable for the next upgrade cycle
|
||||
makeTreeWritable(workflowDest);
|
||||
} catch {
|
||||
/* non-fatal */
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all newly copied files are owner-writable so the next run can
|
||||
// overwrite them (covers extensions, agents, and skills in one walk).
|
||||
makeTreeWritable(agentDir);
|
||||
|
||||
writeManagedResourceManifest(agentDir);
|
||||
ensureRegistryEntries(join(agentDir, "extensions"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -232,6 +232,19 @@ export function registerHooks(
|
|||
"warning",
|
||||
);
|
||||
}
|
||||
// Forge-only: surface high/critical entries as inline-fix candidates so
|
||||
// the operator (or a follow-up dispatcher) can drain self-reported bugs
|
||||
// without leaving the session. Read-only signal for now — no auto-dispatch.
|
||||
const highBlocked = triage.stillBlocked.filter(
|
||||
(e) => e.severity === "high" || e.severity === "critical",
|
||||
);
|
||||
if (highBlocked.length > 0) {
|
||||
const ids = highBlocked.map((e) => `${e.id} (${e.kind})`).join(", ");
|
||||
ctx.ui?.notify?.(
|
||||
`${highBlocked.length} inline-fix candidate${highBlocked.length === 1 ? "" : "s"} pending in .sf/BACKLOG.md: ${ids}`,
|
||||
"warning",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
/* non-fatal — self-feedback drain must never block session start */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ test("sf launches and loads extensions without errors", async () => {
|
|||
|
||||
// No extension load errors
|
||||
assert.ok(
|
||||
!output.includes("[sf] Extension load error"),
|
||||
!output.includes("[forge] Extension load error"),
|
||||
`no extension load errors on stderr (got: ${output.slice(0, 500)})`,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -492,7 +492,7 @@ test("refresh failures keep the workspace locked and expose the failed bridge-re
|
|||
failedBootPayload.onboarding.bridgeAuthRefresh.error,
|
||||
/could not attach/i,
|
||||
);
|
||||
});
|
||||
}, 120_000);
|
||||
|
||||
test("fresh sf --web browser onboarding stays locked on failed validation and unlocks after a successful retry", async (t) => {
|
||||
if (process.platform === "win32") {
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ export function ensureRuntimeArtifacts(): void {
|
|||
|
||||
export function parseStartedUrl(stderr: string): string {
|
||||
const match = stderr.match(
|
||||
/\[sf\] Web mode startup: status=started[^\n]*url=(http:\/\/[^\s]+)/,
|
||||
/\[forge\] Web mode startup: status=started[^\n]*url=(http:\/\/[^\s]+)/,
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
|
|
@ -210,7 +210,7 @@ export function parseStartedUrl(stderr: string): string {
|
|||
|
||||
function parseReadyAuthToken(stderr: string): string | null {
|
||||
const match = stderr.match(
|
||||
/\[sf\] Ready → http:\/\/[^\s]+\/#token=([a-f0-9]{64})/,
|
||||
/\[forge\] Ready → http:\/\/[^\s]+\/#token=([a-f0-9]{64})/,
|
||||
);
|
||||
return match?.[1] ?? null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue