sf-tui + sf-permissions: gate footer-indicator side-effects on ctx.hasUI
Three TUI-only decorations were running their full session-lifecycle handlers even in headless mode, where there is no footer to render into. Most visibly, the emoji extension's AI auto-assign path made a real LLM call to pick a 🚀/✨/🎯 that nothing would ever see. - sf-tui/emoji.ts: session_start and agent_start handlers early-return when !ctx.hasUI. Commands stay registered so /emoji still works if someone runs it, but the lifecycle work (state loading, AI emoji selection, setStatus emission) is skipped. - sf-tui/color-band.ts: session_start and session_switch handlers early-return when !ctx.hasUI. Avoids unnecessary state-file writes and resize-listener attachment in headless runs. - sf-permissions/index.ts:setLevel: guards the setStatus("authority", …) call behind ctx.hasUI. The existing session_start path was already gated — this closes the permission-change code path. Headless stderr was already filtering these keys, so the user-visible output is unchanged. This eliminates the underlying RPC traffic and — more importantly — stops spending LLM tokens on decorative emoji selection in headless runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e1461f45b8
commit
5957d5c2b6
3 changed files with 19 additions and 3 deletions
|
|
@ -205,7 +205,9 @@ function setLevel(
|
|||
if (saveGlobally) {
|
||||
saveGlobalPermission(level);
|
||||
}
|
||||
if (ctx.ui?.setStatus) {
|
||||
// Only emit the footer indicator when there's a real TUI to render into.
|
||||
// In headless mode the "authority" badge has no consumer.
|
||||
if (ctx.hasUI && ctx.ui?.setStatus) {
|
||||
ctx.ui.setStatus("authority", getStatusText(level));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,12 +91,17 @@ export function registerSessionColor(pi: ExtensionAPI): void {
|
|||
|
||||
registerCommands(pi, state);
|
||||
|
||||
// Gate the session-lifecycle work on having a real TUI. The color band is
|
||||
// pure footer decoration — nothing to render into in headless mode, so
|
||||
// skip state-file writes and resize listeners entirely.
|
||||
pi.on("session_start", async (_, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
currentCtx = ctx;
|
||||
initSession(ctx, state, setupResizeListener);
|
||||
});
|
||||
|
||||
pi.on("session_switch", async (event, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
if (event.reason === "new") {
|
||||
currentCtx = ctx;
|
||||
initSession(ctx, state, setupResizeListener);
|
||||
|
|
|
|||
|
|
@ -78,8 +78,17 @@ export function registerSessionEmoji(pi: ExtensionAPI): void {
|
|||
|
||||
registerCommands(pi, state);
|
||||
|
||||
pi.on("session_start", (_, ctx) => initSession(ctx, pi, state));
|
||||
pi.on("agent_start", (_, ctx) => handleAgentStart(ctx, pi, state));
|
||||
// Gate the session-lifecycle work on having a real TUI. Headless mode
|
||||
// (sf headless auto, --print, CI) has no footer to render into, and the
|
||||
// AI auto-assign path would spend tokens choosing an emoji nothing sees.
|
||||
pi.on("session_start", (_, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
return initSession(ctx, pi, state);
|
||||
});
|
||||
pi.on("agent_start", (_, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
return handleAgentStart(ctx, pi, state);
|
||||
});
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue