diff --git a/autoresearch.jsonl b/autoresearch.jsonl index 45ce2d4fb..1b1996249 100644 --- a/autoresearch.jsonl +++ b/autoresearch.jsonl @@ -2,3 +2,4 @@ {"run": 1, "commit": "15269f4", "metric": 40.0, "metrics": {}, "status": "keep", "description": "baseline measurement", "timestamp": 1778242955776, "segment": 0, "confidence": null, "asi": {"hypothesis": "baseline measurement", "breakdown": "26 errors, 13 warnings, 1 info"}} {"run": 2, "commit": "72e27f9", "metric": 11.0, "metrics": {}, "status": "keep", "description": "auto-fix format + organizeImports: biome check --write src/", "timestamp": 1778243276590, "segment": 0, "confidence": null, "asi": {"hypothesis": "All 26 errors are auto-fixable format/organizeImports; fixing them drops total from 40 to 11", "breakdown": "0 errors, 11 warnings"}} {"run": 3, "commit": "c6ee770", "metric": 0.0, "metrics": {}, "status": "keep", "description": "fix 11 unused imports/variables by removing or prefixing with underscore", "timestamp": 1778243617559, "segment": 0, "confidence": 3.64, "asi": {"hypothesis": "All 11 remaining warnings are unused imports/variables \u2014 removing unused imports and prefixing intentionally kept but unused variables with underscore eliminates all diagnostics", "breakdown": "Removed: injectReasoningGuidance, withQueryTimeout (unused import), getAutoSession, logWarning (2x), debugLog, readFileSync/unlinkSync/writeFileSync. Prefixed: MAX_HISTOGRAM_BUCKETS, REASONING_ASSIST_MAX_CHARS, basePath param."}} +{"run": 4, "commit": "b2bcb922d", "metric": 0.0, "metrics": {}, "status": "keep", "description": "re-fix 74 new diagnostics from 37 subsequent commits: biome --write dropped to 16, manual unused-import/var/param cleanup to 0; fixed web-mode-onboarding test timeout (timeoutMs 120s, AbortSignal 30s, test budget 420s)", "timestamp": 1778403638931, "segment": 0, "confidence": null, "asi": {"hypothesis": "37 new commits introduced 74 diagnostics (57 errors, 17 warnings); auto-fix handles format/import errors, manual prefix/removal handles unsafe unused-import warnings", "breakdown": "0 errors, 0 warnings after fix; all 409 test files pass"}} diff --git a/autoresearch.md b/autoresearch.md index ede0934cb..9752dd7b5 100644 --- a/autoresearch.md +++ b/autoresearch.md @@ -44,4 +44,10 @@ Run until interrupted by the user. ## What's Been Tried - **#2 (auto-fix)**: `biome check --write` — fixed 26 auto-fixable errors (format/organizeImports), dropped diagnostics from 40 to 11. Status: keep. -- **#3 (manual fixes)**: Removed 7 unused imports (`injectReasoningGuidance`, `withQueryTimeout`, `getAutoSession`, `logWarning` x3, `debugLog`, `readFileSync/unlinkSync/writeFileSync`) and prefixed 4 intentionally-unused items with underscore (`_MAX_HISTOGRAM_BUCKETS`, `_REASONING_ASSIST_MAX_CHARS`, `_basePath`, `_withQueryTimeout`). Dropped from 11 to 0. Status: keep. +- **#3 (manual fixes)**: Removed 7 unused imports and prefixed 4 intentionally-unused items with underscore. Dropped from 11 to 0. Status: keep. +- **#4 (regression re-fix)**: 37 new commits introduced 74 diagnostics. `biome check --write` fixed 58 (auto-safe), manual prefix/removal fixed the remaining 16 unsafe warnings across 11 files. Also fixed pre-existing web-mode-onboarding test timeout: added `timeoutMs: 120_000` to `launchPackagedWebHost`, raised `AbortSignal.timeout` on simple fetches 10s→30s, raised test budget 180s→420s. All 409 test files pass. Diagnostics: 0. Status: keep. + +## Lessons +- New development (37 commits) is enough to re-introduce 74 diagnostics. Re-run autoresearch periodically (monthly or after large feature branches land). +- Pattern of new violations: unused imports from refactors, unused function params from stubs, duplicate imports. Auto-fix handles errors; unsafe-fix (unused-import/var) requires manual triage. +- Integration test timeout under parallel load: cold-start Next.js can consume most of a 180s test timeout leaving insufficient budget for multi-step API calls. Fix: bound launch phase separately, raise individual fetch timeouts, increase overall budget to match worst-case sum. diff --git a/src/resources/extensions/bg-shell/process-manager.js b/src/resources/extensions/bg-shell/process-manager.js index 7299afc1d..843660e34 100644 --- a/src/resources/extensions/bg-shell/process-manager.js +++ b/src/resources/extensions/bg-shell/process-manager.js @@ -11,19 +11,21 @@ import { sanitizeCommand, } from "@singularity-forge/pi-coding-agent"; import { rewriteCommandWithRtk } from "../shared/rtk.js"; -import { - addEvent, - pendingAlerts, - pushAlert, - setPendingAlerts, -} from "./bg-events.js"; +import { addEvent, pushAlert } from "./bg-events.js"; import { analyzeLine } from "./output-formatter.js"; import { startPortProbing, transitionToReady } from "./readiness-detector.js"; import { DEAD_PROCESS_TTL, MAX_BUFFER_LINES } from "./types.js"; import { formatUptime, restoreWindowsVTInput } from "./utilities.js"; + // Re-export event/alert helpers so existing consumers (bg-shell-lifecycle.js) // continue to work without changing their import paths. -export { addEvent, MAX_PENDING_ALERTS, pendingAlerts, pushAlert, setPendingAlerts } from "./bg-events.js"; +export { + addEvent, + MAX_PENDING_ALERTS, + pendingAlerts, + pushAlert, + setPendingAlerts, +} from "./bg-events.js"; // ── Process Registry ─────────────────────────────────────────────────────── export const processes = new Map(); export function addOutputLine(bg, stream, line) { diff --git a/src/resources/extensions/sf/bootstrap/register-hooks.js b/src/resources/extensions/sf/bootstrap/register-hooks.js index 8e6946409..db058b241 100644 --- a/src/resources/extensions/sf/bootstrap/register-hooks.js +++ b/src/resources/extensions/sf/bootstrap/register-hooks.js @@ -1,5 +1,5 @@ -import { existsSync, readdirSync } from "node:fs"; import { spawnSync } from "node:child_process"; +import { existsSync, readdirSync } from "node:fs"; import { join, relative, resolve } from "node:path"; import { isToolCallEventType } from "@singularity-forge/pi-coding-agent"; import { resetAskUserQuestionsCache } from "../../ask-user-questions.js"; @@ -69,6 +69,7 @@ import { import { initSessionRecorder } from "../session-recorder.js"; import { deriveState } from "../state.js"; import { countGoogleGeminiCliTokens } from "../token-counter.js"; +import { getSessionTodoCompactionBlock } from "../tools/session-todo-tool.js"; import { parseUnitId } from "../unit-id.js"; import { logWarning as safetyLogWarning } from "../workflow-logger.js"; import { @@ -79,7 +80,6 @@ import { import { handleAgentEnd } from "./agent-end-recovery.js"; import { installNotifyInterceptor } from "./notify-interceptor.js"; import { buildBeforeAgentStartResult } from "./system-context.js"; -import { getSessionTodoCompactionBlock } from "../tools/session-todo-tool.js"; import { checkToolCallLoop, resetToolCallLoopGuard, @@ -212,7 +212,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { resetMemorySleeper(); try { const sid = ctx.sessionManager?.getSessionId?.() ?? ""; - const sfile = ctx.sessionManager?.getSessionFile?.() ?? ""; + const _sfile = ctx.sessionManager?.getSessionFile?.() ?? ""; if (sid) { process.stderr.write(`[forge] session ${sid.slice(0, 8)}\n`); } @@ -676,9 +676,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { workState.length > 0 ? `Work in progress: ${workState.join(". ")}.` : "Session compacted. No active work state."; - const summary = todoBlock - ? `${baseSummary}\n\n${todoBlock}` - : baseSummary; + const summary = todoBlock ? `${baseSummary}\n\n${todoBlock}` : baseSummary; const result = { compaction: { summary, diff --git a/src/resources/extensions/sf/commands-prefs-wizard.js b/src/resources/extensions/sf/commands-prefs-wizard.js index 051f7ad3b..cd21905a7 100644 --- a/src/resources/extensions/sf/commands-prefs-wizard.js +++ b/src/resources/extensions/sf/commands-prefs-wizard.js @@ -23,15 +23,14 @@ import { loadProjectSFPreferences, resolveAllSkillReferences, } from "./preferences.js"; + // Re-export from thin shared module so external importers keep working. export { serializePreferencesToFrontmatter, yamlSafeString, } from "./preferences-serializer.js"; -import { - serializePreferencesToFrontmatter, - yamlSafeString, -} from "./preferences-serializer.js"; + +import { serializePreferencesToFrontmatter } from "./preferences-serializer.js"; /** Extract body content after frontmatter closing delimiter, or null if none. */ function extractBodyAfterFrontmatter(content) { diff --git a/src/resources/extensions/sf/commands-todo.js b/src/resources/extensions/sf/commands-todo.js index 614a479fc..f686e10f1 100644 --- a/src/resources/extensions/sf/commands-todo.js +++ b/src/resources/extensions/sf/commands-todo.js @@ -172,7 +172,7 @@ export function renderTriageMarkdown(result, sourcePath) { section("Unclear Notes", result.unclear_notes), ].join("\n"); } -function renderEvalJsonl(result) { +function _renderEvalJsonl(result) { return ( result.eval_candidates .map((item) => @@ -270,7 +270,7 @@ function detectRecurringPatterns(result) { } return proposals; } -function renderSkillProposals(result) { +function _renderSkillProposals(result) { const proposals = detectRecurringPatterns(result); if (proposals.length === 0) return "\n"; return ( @@ -367,7 +367,7 @@ function normalizedItems(result, createdAt) { for (const item of result.unclear_notes) push("unclear_note", item); return items; } -function renderNormalizedJsonl(result, createdAt) { +function _renderNormalizedJsonl(result, createdAt) { const items = normalizedItems(result, createdAt); return ( items @@ -565,7 +565,14 @@ export async function triageTodoDump(basePath, llmCall, options = {}) { insertTriageEval(crypto.randomUUID(), id, item, createdAt); } for (const item of normalizedItems(result, createdAt)) { - insertTriageItem(crypto.randomUUID(), id, item.kind, item.content, item.evidence, createdAt); + insertTriageItem( + crypto.randomUUID(), + id, + item.kind, + item.content, + item.evidence, + createdAt, + ); } const skillProposals = detectRecurringPatterns(result); for (const skill of skillProposals) { diff --git a/src/resources/extensions/sf/commands/handlers/core.js b/src/resources/extensions/sf/commands/handlers/core.js index 5b2d9935d..32fe8d773 100644 --- a/src/resources/extensions/sf/commands/handlers/core.js +++ b/src/resources/extensions/sf/commands/handlers/core.js @@ -15,6 +15,7 @@ import { setAllExperimentalFlags, setExperimentalFlag, } from "../../experimental.js"; +import { inferPresetName, resolvePreset } from "../../operating-model.js"; import { getGlobalSFPreferencesPath, getProjectSFPreferencesPath, @@ -28,11 +29,6 @@ import { formattedShortcutPair } from "../../shortcut-defs.js"; import { deriveState } from "../../state.js"; import { writeUokDiagnostics } from "../../uok/diagnostic-synthesis.js"; import { projectRoot } from "../context.js"; -import { - SF_MODE_PRESET_NAMES, - inferPresetName, - resolvePreset, -} from "../../operating-model.js"; export function showHelp(ctx, args = "") { const summaryLines = [ "SF — Singularity Forge\n", diff --git a/src/resources/extensions/sf/metrics-central.js b/src/resources/extensions/sf/metrics-central.js index 472f4e826..4b095edc4 100644 --- a/src/resources/extensions/sf/metrics-central.js +++ b/src/resources/extensions/sf/metrics-central.js @@ -432,8 +432,12 @@ function openMetricsDb(basePath) { ) `); db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_name ON metrics(name)`); - db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_session ON metrics(session_id)`); - db.exec(`CREATE INDEX IF NOT EXISTS idx_metrics_name_ts ON metrics(name, timestamp DESC)`); + db.exec( + `CREATE INDEX IF NOT EXISTS idx_metrics_session ON metrics(session_id)`, + ); + db.exec( + `CREATE INDEX IF NOT EXISTS idx_metrics_name_ts ON metrics(name, timestamp DESC)`, + ); _metricsDb = db; } catch (err) { logWarning("metrics-central", `Failed to open metrics.db: ${err.message}`); @@ -450,7 +454,7 @@ function closeMetricsDb() { _metricsDb = null; } -function ensureMetricsTable(db) { +function _ensureMetricsTable(db) { // no-op — metrics.db is set up by openMetricsDb void db; } @@ -513,11 +517,13 @@ function persistMetricsToDb(registry, sessionId, _ignored) { try { const row = _metricsDb?.prepare("SELECT count(*) as n FROM metrics").get(); if (row && row.n > METRICS_DB_ROW_CAP) { - _metricsDb.prepare( - `DELETE FROM metrics WHERE rowid NOT IN ( + _metricsDb + .prepare( + `DELETE FROM metrics WHERE rowid NOT IN ( SELECT rowid FROM metrics ORDER BY timestamp DESC LIMIT ${METRICS_DB_ROW_CAP} )`, - ).run(); + ) + .run(); } } catch (_) { // swallow — prune failure must never surface to the user diff --git a/src/resources/extensions/sf/steerable-autonomous-extension.js b/src/resources/extensions/sf/steerable-autonomous-extension.js index 2a20368d5..2135ec631 100644 --- a/src/resources/extensions/sf/steerable-autonomous-extension.js +++ b/src/resources/extensions/sf/steerable-autonomous-extension.js @@ -9,21 +9,22 @@ * Consumer: index.js → steerableAutonomousExtension(pi) on every startup. */ -import { - handleSteerableModeKey, - SteerableAutonomousPanel, -} from "./steerable-autonomous-panel.js"; -import { SF_MODE_PRESET_NAMES, inferPresetName, resolvePreset } from "./operating-model.js"; import { getAutoSession } from "./auto/session.js"; import { isAutoActive, startAutoDetached } from "./auto.js"; import { projectRoot } from "./commands/context.js"; +import { + inferPresetName, + resolvePreset, + SF_MODE_PRESET_NAMES, +} from "./operating-model.js"; +import { SteerableAutonomousPanel } from "./steerable-autonomous-panel.js"; export default function steerableAutonomousExtension(api) { let panel = null; let isAutonomousActive = false; // Track autonomous mode state - api.on("session_start", async (_, ctx) => { + api.on("session_start", async (_, _ctx) => { isAutonomousActive = false; if (panel) { panel.hide(); @@ -60,7 +61,8 @@ export default function steerableAutonomousExtension(api) { } const current = inferPresetName(s.getMode()) ?? SF_MODE_PRESET_NAMES[0]; const idx = SF_MODE_PRESET_NAMES.indexOf(current); - const nextName = SF_MODE_PRESET_NAMES[(idx + 1) % SF_MODE_PRESET_NAMES.length]; + const nextName = + SF_MODE_PRESET_NAMES[(idx + 1) % SF_MODE_PRESET_NAMES.length]; const preset = resolvePreset(nextName); s.setMode({ workMode: preset.workMode, @@ -70,16 +72,14 @@ export default function steerableAutonomousExtension(api) { }); ctx.ui.notify(`Mode → ${nextName} (${preset.description})`, "info"); } catch { - ctx.ui.notify( - "Mode: use /mode ask|plan|build", - "info", - ); + ctx.ui.notify("Mode: use /mode ask|plan|build", "info"); } }, }); api.registerShortcut("ctrl+y", { - description: "Toggle YOLO mode (build + autonomous + deep + unrestricted; bypass git prompts). If not running, starts the autonomous loop immediately.", + description: + "Toggle YOLO mode (build + autonomous + deep + unrestricted; bypass git prompts). If not running, starts the autonomous loop immediately.", handler: async (ctx) => { const session = getAutoSession(); // Toggle full-autonomy preset in AutoSession (handles mode slam + restore) @@ -109,7 +109,8 @@ export default function steerableAutonomousExtension(api) { // Handle slash command for panel api.registerCommand("steer", { - description: "Open steerable autonomous panel (Shift+Tab during autonomous)", + description: + "Open steerable autonomous panel (Shift+Tab during autonomous)", handler: async (_, ctx) => { if (!isAutonomousActive) { ctx.ui.notify( @@ -132,10 +133,7 @@ export default function steerableAutonomousExtension(api) { // Listen for autonomous mode changes api.on("autonomous_start", async (_, ctx) => { isAutonomousActive = true; - ctx.ui.notify( - "🤖 Autonomous mode active — Shift+Tab to steer", - "info", - ); + ctx.ui.notify("🤖 Autonomous mode active — Shift+Tab to steer", "info"); }); api.on("autonomous_stop", async (_, ctx) => { diff --git a/src/resources/extensions/sf/steerable-autonomous-panel.js b/src/resources/extensions/sf/steerable-autonomous-panel.js index fe3245345..fadd41ef0 100644 --- a/src/resources/extensions/sf/steerable-autonomous-panel.js +++ b/src/resources/extensions/sf/steerable-autonomous-panel.js @@ -7,7 +7,6 @@ */ import { createInterface } from "node:readline"; -import { getEditorKeybindings } from "@singularity-forge/pi-tui"; // ─── Constants ────────────────────────────────────────────────────────────── const PANEL_WIDTH = 60; @@ -216,7 +215,7 @@ const ACTION_HANDLERS = { // Would trigger immediate reassessment }, - close: async (ctx) => { + close: async (_ctx) => { // Just hide the panel }, }; @@ -247,7 +246,7 @@ export class SteerableAutonomousPanel { this.render(); // Set up key listener - this.rl.input.on("keypress", (str, key) => { + this.rl.input.on("keypress", (_str, key) => { this.handleKeyPress(key); }); } diff --git a/src/resources/extensions/sf/tests/auto-runaway-guard.test.mjs b/src/resources/extensions/sf/tests/auto-runaway-guard.test.mjs index 15d87c82f..b9a338f9a 100644 --- a/src/resources/extensions/sf/tests/auto-runaway-guard.test.mjs +++ b/src/resources/extensions/sf/tests/auto-runaway-guard.test.mjs @@ -11,7 +11,6 @@ import assert from "node:assert/strict"; import { test } from "vitest"; import { - clearRunawayGuardState, evaluateRunawayGuard, resetRunawayGuardState, } from "../auto-runaway-guard.js"; @@ -61,7 +60,15 @@ test("progress check returns none regardless of hard-pause conditions", () => { evaluateRunawayGuard( "discuss-milestone", "M001", - { toolCalls: 67, sessionTokens: 1_500_000, elapsedMs: 22 * 60 * 1000, changedFiles: 0, worktreeFingerprint: null, worktreeChangedSinceStart: false, topTools: {} }, + { + toolCalls: 67, + sessionTokens: 1_500_000, + elapsedMs: 22 * 60 * 1000, + changedFiles: 0, + worktreeFingerprint: null, + worktreeChangedSinceStart: false, + topTools: {}, + }, config, now, ); @@ -71,12 +78,24 @@ test("progress check returns none regardless of hard-pause conditions", () => { const r = evaluateRunawayGuard( "discuss-milestone", "M001", - { toolCalls: 67, sessionTokens: 2_000_000, elapsedMs: 25 * 60 * 1000, changedFiles: 1, worktreeFingerprint: null, worktreeChangedSinceStart: false, topTools: {} }, + { + toolCalls: 67, + sessionTokens: 2_000_000, + elapsedMs: 25 * 60 * 1000, + changedFiles: 1, + worktreeFingerprint: null, + worktreeChangedSinceStart: false, + topTools: {}, + }, config, now + 180_000, ); // The progress check fires BEFORE the hard-pause block, returning 'none' - assert.equal(r.action, "none", "progress check should return none even when hardPause conditions are met"); + assert.equal( + r.action, + "none", + "progress check should return none even when hardPause conditions are met", + ); }); test("returns none when changedFiles > 0 despite token growth and 2 warnings", () => { @@ -133,7 +152,11 @@ test("returns none when worktreeChangedSinceStart === true despite token growth" config, now + 180_000, ); - assert.equal(r.action, "none", "should not pause when worktreeChangedSinceStart === true"); + assert.equal( + r.action, + "none", + "should not pause when worktreeChangedSinceStart === true", + ); }); test("returns none when changedFiles is explicitly 0 but worktreeChangedSinceStart is false", () => { @@ -154,7 +177,11 @@ test("returns none when changedFiles is explicitly 0 but worktreeChangedSinceSta const r = evaluateRunawayGuard( "discuss-milestone", "M001", - makeMetrics({ sessionTokens: 2_940_000, changedFiles: 0, worktreeChangedSinceStart: false }), + makeMetrics({ + sessionTokens: 2_940_000, + changedFiles: 0, + worktreeChangedSinceStart: false, + }), config, now + 180_000, ); @@ -199,5 +226,9 @@ test("discuss-milestone with file changes does not get hard-paused", () => { config, now + 180_000, ); - assert.equal(r.action, "none", "discuss-milestone with file changes should not be paused"); + assert.equal( + r.action, + "none", + "discuss-milestone with file changes should not be paused", + ); }); diff --git a/src/resources/extensions/sf/tests/swarm.test.mjs b/src/resources/extensions/sf/tests/swarm.test.mjs index 645943835..d4b75cb83 100644 --- a/src/resources/extensions/sf/tests/swarm.test.mjs +++ b/src/resources/extensions/sf/tests/swarm.test.mjs @@ -12,18 +12,12 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test } from "vitest"; -import { closeDatabase } from "../sf-db.js"; import { _clearSfRootCache } from "../paths.js"; -import { PersistentAgent } from "../uok/persistent-agent.js"; +import { closeDatabase } from "../sf-db.js"; import { AgentSwarm } from "../uok/agent-swarm.js"; -import { - CoordinatorAgent, - WorkerAgent, - ScoutAgent, - ReviewerAgent, - createDefaultSwarm, -} from "../uok/swarm-roles.js"; +import { PersistentAgent } from "../uok/persistent-agent.js"; import { SwarmDispatchLayer } from "../uok/swarm-dispatch.js"; +import { createDefaultSwarm } from "../uok/swarm-roles.js"; // ─── Shared Setup ───────────────────────────────────────────────────────────── @@ -86,7 +80,10 @@ describe("PersistentAgent — identity", () => { describe("PersistentAgent — messaging", () => { test("send_receive_delivers_message", () => { const root = makeProject(); - const agentA = new PersistentAgent(root, { name: "sender", role: "worker" }); + const agentA = new PersistentAgent(root, { + name: "sender", + role: "worker", + }); const agentB = new PersistentAgent(root, { name: "receiver", role: "worker", @@ -132,8 +129,14 @@ describe("PersistentAgent — messaging", () => { name: "broadcaster", role: "coordinator", }); - const agentB = new PersistentAgent(root, { name: "recv-b", role: "worker" }); - const agentC = new PersistentAgent(root, { name: "recv-c", role: "worker" }); + const agentB = new PersistentAgent(root, { + name: "recv-b", + role: "worker", + }); + const agentC = new PersistentAgent(root, { + name: "recv-c", + role: "worker", + }); agentA.broadcast("announcement", {}, ["recv-b", "recv-c"]); diff --git a/src/resources/extensions/sf/tools/session-todo-tool.js b/src/resources/extensions/sf/tools/session-todo-tool.js index a0b0c8d40..6ee67baa8 100644 --- a/src/resources/extensions/sf/tools/session-todo-tool.js +++ b/src/resources/extensions/sf/tools/session-todo-tool.js @@ -34,7 +34,7 @@ function saveTodos(baseDir, todos) { writeFileSync(todoPath(baseDir), JSON.stringify(todos, null, 2), "utf-8"); } -function nextId(todos) { +function nextId(_todos) { // Short base-36 timestamp suffix for readable IDs. return `t${Date.now().toString(36)}`; } @@ -103,9 +103,7 @@ export function executeSessionTodoList(baseDir) { details: { operation: "list", todos: [] }, }; } - const lines = todos.map( - (t) => `[${t.done ? "x" : " "}] ${t.id}: ${t.text}`, - ); + const lines = todos.map((t) => `[${t.done ? "x" : " "}] ${t.id}: ${t.text}`); return { content: [{ type: "text", text: lines.join("\n") }], details: { operation: "list", todos }, diff --git a/src/tests/integration/web-mode-onboarding.test.ts b/src/tests/integration/web-mode-onboarding.test.ts index 7e094f689..25250ca79 100644 --- a/src/tests/integration/web-mode-onboarding.test.ts +++ b/src/tests/integration/web-mode-onboarding.test.ts @@ -518,6 +518,9 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un launchCwd: tempProject, tempHome, browserLogPath, + // Cap launch at 120s so the remaining budget (~180s+) covers API calls + // (valid-key validation triggers a bridge restart = up to 120s itself). + timeoutMs: 120_000, env: { SF_WEB_TEST_FAKE_API_KEY_VALIDATION: "1", ANTHROPIC_API_KEY: "", @@ -574,7 +577,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un const bootBefore = await fetch(`${launch.url}/api/boot`, { method: "GET", headers: { Accept: "application/json", ...auth }, - signal: AbortSignal.timeout(10_000), + signal: AbortSignal.timeout(30_000), }); assert.equal( bootBefore.ok, @@ -598,7 +601,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un providerId: "openai", apiKey: "invalid-demo-key", }), - signal: AbortSignal.timeout(10_000), + signal: AbortSignal.timeout(30_000), }); assert.equal(invalidValidation.status, 422); const invalidPayload = (await invalidValidation.json()) as any; @@ -637,7 +640,7 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un const bootAfter = await fetch(`${launch.url}/api/boot`, { method: "GET", headers: { Accept: "application/json", ...auth }, - signal: AbortSignal.timeout(10_000), + signal: AbortSignal.timeout(30_000), }); const bootAfterText = await bootAfter.clone().text(); assert.equal( @@ -648,4 +651,4 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un const bootAfterPayload = (await bootAfter.json()) as any; assert.equal(bootAfterPayload.onboarding.locked, false); assert.equal(bootAfterPayload.onboarding.lockReason, null); -}, 180_000); +}, 420_000);