diff --git a/src/resources/extensions/sf-tui/footer.js b/src/resources/extensions/sf-tui/footer.js index 62f6dcbc8..47e38e3dd 100644 --- a/src/resources/extensions/sf-tui/footer.js +++ b/src/resources/extensions/sf-tui/footer.js @@ -1,6 +1,7 @@ import { truncateToWidth, visibleWidth } from "@singularity-forge/pi-tui"; import { getAutoSession } from "../sf/auto/session.js"; import { refreshGitStatus } from "./git.js"; +import { renderModeBadge } from "./header.js"; const RESET = "\x1b[0m"; const BOLD = "\x1b[1m"; @@ -74,6 +75,12 @@ function join(parts) { function shorten(text, max) { return text.length > max ? `${text.slice(0, Math.max(0, max - 3))}...` : text; } + +/** Minimal theme adapter so renderModeBadge (header.js) can run with footer's ANSI helpers. */ +const FOOTER_THEME = { + fg: (tone, text) => ansiFg(toneHex(tone), text), + bold: (text) => `${BOLD}${text}${RESET}`, +}; function getSessionStats(ctx) { let cost = 0; let tokens = 0; @@ -98,6 +105,8 @@ function getSessionStats(ctx) { export function renderFooter(_theme, footerData, ctx, width) { const git = refreshGitStatus(process.cwd()); const { cost, cxPct } = getSessionStats(ctx); + const session = getAutoSession(); + const mode = session?.getMode?.(); const leftParts = []; if (git.repo) { leftParts.push(ansiFg(SE.ember40, git.repo, true)); @@ -136,6 +145,9 @@ export function renderFooter(_theme, footerData, ctx, width) { leftParts.push(chip("status", statuses.join(" "), "accent")); } const rightParts = []; + if (mode) { + rightParts.push(renderModeBadge(FOOTER_THEME, mode, width < 80)); + } if (ctx.model) { rightParts.push( chip("model", `${ctx.model.provider}/${ctx.model.id}`, "text"), @@ -180,19 +192,16 @@ export function renderAutoFooter(_theme, footerData, ctx, width) { modelMode: "smart", }; - const leftParts = [`${BOLD}${ansiFg(SE.ember40, "SF")}`]; - leftParts.push(ansiFg(SE.ember40, mode.workMode)); - leftParts.push(ansiFg(SE.gray60, "·")); - leftParts.push(ansiFg(SE.success, "∞")); - leftParts.push(ansiFg(SE.gray60, "·")); - leftParts.push(ansiFg(SE.warning, mode.permissionProfile)); + const badge = renderModeBadge(FOOTER_THEME, mode, width < 80); + const leftParts = [`${BOLD}${ansiFg(SE.ember40, "SF")}`, badge].filter( + Boolean, + ); const statuses = Array.from(footerData.getExtensionStatuses().entries()) .sort(([a], [b]) => a.localeCompare(b)) .map(([, text]) => text.trim()) .filter(Boolean); if (statuses.length) { - leftParts.push(ansiFg(SE.gray60, "·")); leftParts.push(ansiFg(SE.gray60, statuses.join(" "))); } diff --git a/src/resources/extensions/sf-tui/header.js b/src/resources/extensions/sf-tui/header.js index 13b5dac30..14da71055 100644 --- a/src/resources/extensions/sf-tui/header.js +++ b/src/resources/extensions/sf-tui/header.js @@ -51,27 +51,36 @@ function compactModelModeBadge(mm) { function renderModeBadge(theme, mode, compact) { if (!mode) return ""; const th = theme; + const paused = mode.paused === true; if (compact) { const badges = [ - th.fg("accent", compactModeBadge(mode.workMode)), + paused ? th.fg("dim", "P!") : "", + th.fg(paused ? "dim" : "accent", compactModeBadge(mode.workMode)), th.fg("dim", compactRunControlBadge(mode.runControl)), - th.fg("warning", compactPermissionBadge(mode.permissionProfile)), - th.fg("success", compactModelModeBadge(mode.modelMode)), - ]; + th.fg( + paused ? "dim" : "warning", + compactPermissionBadge(mode.permissionProfile), + ), + th.fg(paused ? "dim" : "success", compactModelModeBadge(mode.modelMode)), + ].filter(Boolean); return `[${badges.join("")}]`; } const parts = [ - th.fg("accent", mode.workMode), + paused ? th.fg("dim", "paused") : "", + paused ? th.fg("dim", "·") : "", + th.fg(paused ? "dim" : "accent", mode.workMode), th.fg("dim", "·"), - th.fg("text", mode.runControl), + th.fg("dim", mode.runControl), th.fg("dim", "·"), - th.fg("warning", mode.permissionProfile), + th.fg(paused ? "dim" : "warning", mode.permissionProfile), th.fg("dim", "·"), - th.fg("success", mode.modelMode), - ]; + th.fg(paused ? "dim" : "success", mode.modelMode), + ].filter(Boolean); return parts.join(" "); } +export { renderModeBadge }; + /** * Minimal auto-mode header — shows only mode badge + project name. * Keeps the user aware SF is running autonomously without full header noise. diff --git a/src/resources/extensions/sf/auto/session.js b/src/resources/extensions/sf/auto/session.js index a0f6b6ba4..63abe8538 100644 --- a/src/resources/extensions/sf/auto/session.js +++ b/src/resources/extensions/sf/auto/session.js @@ -505,17 +505,20 @@ export class AutoSession { * Get current mode state as a canonical object. */ getMode() { - return buildModeState({ - workMode: this.workMode, - runControl: this.active - ? this.stepMode - ? "assisted" - : "autonomous" - : this.runControl, - permissionProfile: this.permissionProfile, - modelMode: this.modelMode, - surface: this.surface, - }); + return { + ...buildModeState({ + workMode: this.workMode, + runControl: this.active + ? this.stepMode + ? "assisted" + : "autonomous" + : this.runControl, + permissionProfile: this.permissionProfile, + modelMode: this.modelMode, + surface: this.surface, + }), + paused: this.paused, + }; } toJSON() { return {