fix(surfaces): stamp correct surface in AutoSession + /mode yolo headless command
Surface stamp: - AutoSession._loadPersistedModeState() now calls detectSurface() to stamp the correct surface (headless/web/tui) from env vars on every startup. Persisted surface value was the previous launch's surface — wrong when switching between TUI and headless on the same project. SF_HEADLESS=1 → 'headless', SF_WEB_BRIDGE_TUI=1 → 'web', else 'tui'. /mode yolo: - handleModeCommand now recognises 'yolo' as a toggleable special case. Headless callers can now run: sf headless --command '/mode yolo' Same behaviour as Ctrl+Y: full-autonomy slam + settingsManager bypass. /mode catalog description updated to list 'yolo' as an option. Documentation: - headless.ts /query and /doctor short-circuits annotated as intentional architecture trade-offs with a note to keep them in sync with the extension. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
38a654d5e4
commit
995a57335b
4 changed files with 40 additions and 3 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 ────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue