diff --git a/src/headless.ts b/src/headless.ts index b393a685b..0e35a6f4e 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -799,7 +799,10 @@ async function runHeadlessOnce( } } - // Query: read-only state snapshot, no RPC child needed + // Query: read-only state snapshot, no RPC child needed. + // ARCHITECTURE NOTE: this intentionally bypasses the SF extension dispatcher + // for performance — no child process, direct DB read. If /query gains new + // behaviour in the extension, mirror it here in headless-query.ts. if (options.command === "query") { const { handleQuery } = await import("./headless-query.js"); const result = await handleQuery(process.cwd()); @@ -807,6 +810,10 @@ async function runHeadlessOnce( } // Doctor: read-only health check, no RPC child needed (#4904 live-regression). + // ARCHITECTURE NOTE: this intentionally bypasses the SF extension dispatcher + // for performance and TTY-independence. The interactive `/doctor` command in + // the extension calls the same runSFDoctor() engine function — keep them in + // sync if doctor.js gains new capabilities. // The interactive `/sf doctor` command lives in the SF extension; this CLI // path lets non-interactive callers (CI, recovery scripts, the live-regression // suite) get the same diagnostic without a TTY. diff --git a/src/resources/extensions/sf/auto/session.js b/src/resources/extensions/sf/auto/session.js index f42a3d39f..f8074289d 100644 --- a/src/resources/extensions/sf/auto/session.js +++ b/src/resources/extensions/sf/auto/session.js @@ -28,6 +28,18 @@ import { import { loadSessionModeState, saveSessionModeState } from "../sf-db.js"; // ─── Constants ─────────────────────────────────────────────────────────────── + +/** + * Detect the current surface from environment variables. + * Each surface sets a distinct env var in the child process it spawns. + * This is the only reliable way to identify surface inside the SF extension, + * because the `ctx.surface` field is not propagated to AutoSession directly. + */ +function detectSurface() { + if (process.env.SF_HEADLESS === "1") return "headless"; + if (process.env.SF_WEB_BRIDGE_TUI === "1") return "web"; + return "tui"; +} export const MAX_UNIT_DISPATCHES = 3; export const STUB_RECOVERY_THRESHOLD = 2; export const MAX_LIFETIME_DISPATCHES = 6; @@ -98,12 +110,14 @@ export class AutoSession { persisted.permissionProfile, ); this.modelMode = resolveModelMode(persisted.modelMode); - this.surface = persisted.surface ?? "tui"; this.modeUpdatedAt = persisted.updatedAt; } } catch { // DB may not be open yet — use defaults } + // Always stamp surface from env: persisted value reflects previous launch's + // surface and should not override the current surface detection. + this.surface = detectSurface(); } // ── Lifecycle ──────────────────────────────────────────────────────────── diff --git a/src/resources/extensions/sf/commands/catalog.js b/src/resources/extensions/sf/commands/catalog.js index 669f2f37d..48bb7bc4b 100644 --- a/src/resources/extensions/sf/commands/catalog.js +++ b/src/resources/extensions/sf/commands/catalog.js @@ -118,7 +118,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [ { cmd: "model", desc: "Switch the active session model or open a picker" }, { cmd: "mode", - desc: "Switch mode: ask (explore/discuss) · plan (structure first) · build (execute autonomously) — or Shift+Tab to cycle", + desc: "Switch mode: ask · plan · build · yolo (full autonomy) — or Shift+Tab to cycle", }, { cmd: "control", desc: "Override run control (manual/assisted/autonomous) — advanced" }, { diff --git a/src/resources/extensions/sf/commands/handlers/core.js b/src/resources/extensions/sf/commands/handlers/core.js index f5acdc19b..552a1e96c 100644 --- a/src/resources/extensions/sf/commands/handlers/core.js +++ b/src/resources/extensions/sf/commands/handlers/core.js @@ -435,6 +435,22 @@ function handleModeCommand(args, ctx) { return true; } const name = parts[0].toLowerCase(); + // "yolo" is a special toggle — not a standard preset + if (name === "yolo") { + const enabled = s.toggleYolo(); + if (ctx.settingsManager && ctx.settingsManager.toggleYOLO) { + ctx.settingsManager.toggleYOLO(); + } + if (enabled) { + ctx.ui.notify( + "🚀 YOLO ON — build · autonomous · deep · unrestricted · no git prompts", + "success", + ); + } else { + ctx.ui.notify("YOLO OFF — mode restored", "info"); + } + return true; + } const preset = resolvePreset(name); if (preset) { // If YOLO is active, exit it first so status and safe-git bypass are cleared