import { isAbsolute, join, relative, resolve, sep } from "node:path"; import { readFileSync, statSync } from "node:fs"; import { getPiHostMode } from "pi-maestro-settings-core/v1"; import { VERSION, getAgentDir, keyText, type ExtensionAPI, type ExtensionCommandContext, type ExtensionContext, type Theme, type ThemeColor } from "@earendil-works/pi-coding-agent"; import type { TUI } from "@earendil-works/pi-tui"; import { ambientKeysShouldYield, capturingOverlayVisible } from "./capturing-overlay.ts"; import { Key, decodeKittyPrintable, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui"; import { AgentReadStoreRouter, RUNTIME_READ_MODEL_DELTA_EVENT, RUNTIME_READ_MODEL_QUERY_EVENT, RUNTIME_READ_MODEL_SNAPSHOT_EVENT, RUNTIME_READ_MODEL_UNAVAILABLE_EVENT, effectiveAgentStatus, runtimeV2ReadEnabled, type CompletePayload, type MessagePayload, type StartedPayload, } from "./agents-store.ts"; import { AmbientSurfaceCache, nextUiPromptDepth, statusText, titleFor, workingMessage, type AmbientState } from "./ambient.ts"; import { generateTitleWithModel } from "./title-llm.ts"; import { suggestTitle } from "./title-gen.ts"; import { BashBgStore } from "./bash-bg-store.ts"; import { SupervisionStore } from "./supervision-store.ts"; import { MaestroStore } from "./maestro-store.ts"; import { createSidebarController, type SidebarController } from "./sidebar-controller.ts"; import { COCKPIT_SPLIT_PANE_MARKER } from "./split-pane.ts"; import { attachViewportStability, type ViewportStabilityPatch } from "./viewport-stability.ts"; import { patchSummary, reportPatch } from "./patch-health.ts"; import { agentRowsKey, bashJobsKey, memoized, memoizedLines, refId } from "./render-memo.ts"; import { attachCompactionStyle, type CompactionStylePatch } from "./compaction-style.ts"; import { COCKPIT_EDITOR_BOTTOM_MARKER, EDITOR_BOTTOM_WIDGET_KEY, createEditorBottomController, createEditorBottomSentinel, type EditorBottomController, } from "./editor-bottom.ts"; import { createCockpitClaudeEditorFactory, isCockpitClaudeEditorFactory, type CockpitClaudeEditor, type CockpitEditorRouteTarget } from "./claude-editor.ts"; import { InputHistoryStore } from "./editor/input-history-store.ts"; import { detectTerminalCompatibility } from "./terminal-capability.ts"; import { COCKPIT_FULLSCREEN_MARKER, COCKPIT_FULLSCREEN_WIDGET_KEY, createFullscreenController, type FullscreenController, } from "./fullscreen-controller.ts"; import { BashBgOverlay } from "./bash-bg-overlay.ts"; import { renderBashBgSummary } from "./bash-bg-widget.ts"; import { TodoOverlay } from "./todo-overlay.ts"; import { AgentOverlay } from "./agent-overlay.ts"; import { registerQuietTools } from "./quiet-tools.ts"; import { registerTeammateMessageRenderer } from "./teammate-message.ts"; import { registerGuardedEditTool, registerNativeEditPolicy } from "./edit-guard.ts"; import { navigateHostThemeSettings } from "./host-ui.ts"; import { ensureThinkingFolded, readHideThinkingBlock } from "./thinking-fold.ts"; import { ThinkingFoldTimer } from "./thinking-timer.ts"; import { shouldAnimateFrames, shouldAnimateSidebar, shouldRunTick, type TickPolicyState } from "./tick-policy.ts"; import { TodoStore } from "./todo-store.ts"; import { activeAgentTreeRows, makeActiveAgentWidget, makeTodoWidget, terminalRows, visibleAgentRows } from "./stack-widget.ts"; import { agentSessionColor, assignedAgentColor, makeAgentBarWidget, SESSION_BAR_WIDGET_KEY } from "./session-bar.ts"; import { makeSessionDetailWidget, SESSION_DETAIL_WIDGET_KEY } from "./session-detail.ts"; import { EndpointStore, SESSION_HOST_REGISTRY_KEY, isMonitorControlEndpoint, isSessionHostRegistryLike, type CockpitEndpoint, type SessionHostRegistryLike, } from "./endpoint-store.ts"; import { SessionUiState } from "./session-ui-state.ts"; import { nextSessionTabId } from "./session-tabs.ts"; import { renderWindowBar, windowSessionColor } from "./window-bar.ts"; import { buildWindowAutocompleteTargets, createWindowAutocompleteProvider, ownerDisplayToken, resolveWindowRouteInput, type WindowAutocompleteTarget, } from "./window-autocomplete.ts"; import { makeWindowThreadWidget } from "./window-thread-view.ts"; import { panelRows } from "./viewport.ts"; import { createZenBrowseController, type ZenBrowseController } from "./zen-browse.ts"; import { buildZenStack } from "./zen-render.ts"; import { ZenSheet, buildZenMissionSheet, buildZenRunSheet, buildZenSwarmSheet, buildZenTaskSheet, type ZenSheetDocument, } from "./zen-sheet.ts"; import { isLegacyTodoOverlayInput, isLocalInputText, routeAgentInput } from "./input-routing.ts"; import { activeThemeName, ThemePicker } from "./theme-picker.ts"; import { ModelPicker, type ModelPickerEntry } from "./model-picker.ts"; import { getUsageTotals, invalidateUsageCache, renderFooter, setUsageThrottle, type PaintTheme, type WidthUtils } from "./footer.ts"; import { collectExtensionStatuses } from "./extension-status.ts"; import { createUsageSubsystem, type UsageSubsystem } from "./usage/extension.ts"; import { ANIMATION_PERIOD_MS, resolveGlyphs, spinFrame } from "./icons.ts"; import { ensureConfigExists, loadConfig, saveConfig } from "./config.ts"; import { applyRow, buildRows, rowKeyForAccel, type SaveState } from "./settings-view.ts"; import { createCockpitSettingsProvider, registerCockpitSettingsProvider } from "./settings/cockpit-provider.ts"; import { createNativePiSettingsProvider, registerNativePiSettingsProvider } from "./settings/native-pi-provider.ts"; import { SettingsLocaleState, getMaestroUiPreferencesPath } from "./settings/locale-state.ts"; import { SettingsProviderRegistry } from "./settings/registry.ts"; import { showMaestroSettingsShell } from "./settings/settings-shell.ts"; import { bindCockpitTuiLocale, cockpitTuiLocale, tuiStatus, tuiT } from "./tui-i18n.ts"; import { COCKPIT_INPUT_TARGET_EVENT, COCKPIT_MAESTRO_QUERY_EVENT, COCKPIT_PREEMPT_RESIZE_EVENT, COCKPIT_SESSION_LIST_EVENT, COCKPIT_TODO_TOGGLE_EVENT, MAESTRO_TODO_STATE_CHANGED_EVENT, MAESTRO_UI_SNAPSHOT_EVENT, MAESTRO_UI_SNAPSHOT_VERSION, SUPERVISION_EVENT, type CockpitInputTargetV1, type CockpitUiOwnershipV1, } from "./public/v1/events.ts"; import { AGENT_WIDGET_KEY, BASH_BG_QUERY_EVENT, BASH_BG_UPDATE_EVENT, COCKPIT_UI_OWNERSHIP_EVENT, COCKPIT_UI_OWNERSHIP_QUERY_EVENT, DEFAULT_CONFIG, STACK_WIDGET_KEY, TEAMMATE_COMPLETE_EVENT, TEAMMATE_MESSAGE_EVENT, TEAMMATE_STARTED_EVENT, TEAMMATE_OPEN_AGENT_EVENT, TEAMMATE_AGENT_COMMAND_EVENT, TODO_TOOL_NAME, WORKFLOW_STATUS_KEY, type AgentRow, type CockpitConfig, } from "./types.ts"; import type { MailboxHostRegistry } from "pi-maestro-teammate/v1/mailbox"; import type * as ExternalAgentProjectionApi from "pi-maestro-teammate/v1/external-agent-projections"; import { buildCockpitTargetCatalogue, createTargetAutocompleteProvider, formatPendingTargetContext, routeCanonicalTargetInput, type PendingTargetReference, } from "./target-integration.ts"; export { EndpointStore, FABRIC_MONITOR_SNAPSHOT_EVENT, LEGACY_MAIN_ENDPOINT_ID, SESSION_HOST_REGISTRY_EVENT, SESSION_HOST_REGISTRY_KEY, } from "./endpoint-store.ts"; export { SessionUiState } from "./session-ui-state.ts"; export { SESSION_TAB_ACTION_FIRST_WIDTH, nextSessionTabId, orderedSessionTabs, renderSessionTabLine, sessionTabWidth, sessionTabWidthMode, } from "./session-tabs.ts"; export { AGENT_BAR_WIDGET_KEY, makeAgentBarWidget, renderAgentBar } from "./agent-bar.ts"; export { makeWindowBarWidget, renderWindowBar } from "./window-bar.ts"; export { makeWindowThreadWidget, renderWindowThreadView, windowThreadBody } from "./window-thread-view.ts"; const MAILBOX_REGISTRY_KEY = Symbol.for("pi-maestro-teammate.mailbox-registry"); async function listSshHostPickerEntries() { const sshHosts = await import("pi-maestro-teammate/v1/ssh-hosts"); return sshHosts.listSshHostPickerEntries(); } async function activateSshHost(hostId: string): Promise { const sshHosts = await import("pi-maestro-teammate/v1/ssh-hosts"); await sshHosts.activateSshHost(hostId); } const FOOTER_UTILS: WidthUtils = { measure: visibleWidth, clip: truncateToWidth }; const BASH_BG_OVERLAY_KEY = "alt+j"; const TODO_OVERLAY_KEY = "alt+shift+t"; const SIDEBAR_RESIZE_KEY = "ctrl+shift+r"; const SIDEBAR_FOCUS_KEY = "alt+l"; const SESSION_DETAIL_TOGGLE_KEY = "alt+e"; const COCKPIT_STATUS_KEY = "cockpit"; // Claude Code title chrome: a static marker when idle, two braille frames while // a turn runs (screens/REPL.tsx TITLE_STATIC_PREFIX / TITLE_ANIMATION_FRAMES). const TITLE_STATIC = "✳"; const TITLE_FRAMES = ["⠂", "⠐"]; const WIDTH_POLL_INTERVAL_MS = 500; // Static mode keeps token totals fresh enough without recomputing on every message. const USAGE_REFRESH_THROTTLE_MS = 10_000; // Quiet mode's rename of pi's hidden-thinking label. The live thinking timer // derives its final "thoughts · 8.4s" labels from the same word. const quietThinkingLabel = (): string => tuiT("thinking.thoughts"); export type CockpitSurfaceState = "dock" | "widgets" | "disabled"; export function resolveCockpitSurfaceState( enabled: boolean, sidebarMode: CockpitConfig["sidebar"]["mode"], dockEffectiveVisible: boolean, ): CockpitSurfaceState { if (!enabled) return "disabled"; return sidebarMode !== "off" && dockEffectiveVisible ? "dock" : "widgets"; } function isTuiContext(ctx: ExtensionContext): boolean { try { const mode = (ctx as ExtensionContext & { mode?: string }).mode; return ctx.hasUI && (mode === undefined || mode === "tui"); } catch { return false; } } function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); } function formatCwd(cwd: string): string { const home = process.env.HOME || process.env.USERPROFILE; if (!home) return cwd; const rel = relative(resolve(home), resolve(cwd)); const insideHome = rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel)); if (!insideHome) return cwd; return rel === "" ? "~" : `~${sep}${rel}`; } /** * Cheap synchronous git branch read straight from .git/HEAD — no process spawn, * so it is safe on every title refresh. Returns the branch name, "detached" for * a raw HEAD hash, or null outside a repo (including linked worktrees). */ function readGitBranch(cwd: string): string | null { let headPath: string | undefined; try { const gitDir = join(cwd, ".git"); const st = statSync(gitDir); if (st.isDirectory()) { headPath = join(gitDir, "HEAD"); } else if (st.isFile()) { const link = readFileSync(gitDir, "utf8").trim(); if (link.startsWith("gitdir:")) { headPath = join(resolve(cwd, link.slice("gitdir:".length).trim()), "HEAD"); } } if (!headPath) return null; const head = readFileSync(headPath, "utf8").trim(); const branch = /^ref:\s*refs\/heads\/(.+)$/.exec(head); return branch ? branch[1] : "detached"; } catch { return null; } } /** Session summary for the title: the session_info name, else the generated * title, else the short id — the Claude Code chain of sessionTitle ?? haikuTitle. */ function sessionTag(sm: { getSessionName(): string | undefined; getSessionId(): string }, generated: string | undefined): string | undefined { const name = sm.getSessionName(); if (name) return name; if (generated) return generated; const id = sm.getSessionId(); return id ? id.slice(0, 8) : undefined; } /** Active model short id (drops any "provider/" prefix). */ function modelTag(model: { id: string } | undefined): string | undefined { if (!model) return undefined; const last = model.id.split("/").at(-1); return last || undefined; } /** Thinking level tag; "off" and unknown values add no noise. */ function thinkingTag(level: string | undefined): string | undefined { return level === undefined || level === "off" ? undefined : level; } /** Maestro workflow tag: run status when a run is active, else session status. */ function maestroWorkflowTag( workflow: { run: { status: string } | null; session: { status: string } } | null | undefined, ): string | undefined { if (!workflow) return undefined; return workflow.run ? workflow.run.status : workflow.session.status; } /** Maestro knowledge evolution tag: c{consumed}/p{pending}/r{review} when tracked. */ function maestroKnowledgeTag(knowledge: { consumed: number; pending: number; review: number; } | null | undefined): string | undefined { if (!knowledge) return undefined; const parts: string[] = []; if (knowledge.consumed > 0) parts.push(`c${knowledge.consumed}`); if (knowledge.pending > 0) parts.push(`p${knowledge.pending}`); if (knowledge.review > 0) parts.push(`r${knowledge.review}`); return parts.length > 0 ? parts.join("/") : undefined; } /** Extract plain text from an agent message (string content or text blocks). */ function messageText(message: unknown): string | undefined { if (!message || typeof message !== "object") return undefined; const content = (message as { content?: unknown }).content; if (typeof content === "string") return content; if (!Array.isArray(content)) return undefined; const parts: string[] = []; for (const block of content) { if ( block && typeof block === "object" && (block as { type?: unknown }).type === "text" && typeof (block as { text?: unknown }).text === "string" ) { parts.push((block as { text: string }).text); } } return parts.length > 0 ? parts.join("\n") : undefined; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isStartedPayload(value: unknown): value is StartedPayload { return isRecord(value) && typeof value.correlationId === "string" && value.correlationId.length > 0 && typeof value.agent === "string"; } function isProgressPayload(value: unknown): value is MessagePayload { return isRecord(value) && typeof value.correlationId === "string" && value.correlationId.length > 0; } function isCompletePayload(value: unknown): value is CompletePayload { return isRecord(value) && typeof value.correlationId === "string" && value.correlationId.length > 0 && typeof value.exitCode === "number" && Number.isFinite(value.exitCode); } export default function (pi: ExtensionAPI): void { const agentReads = new AgentReadStoreRouter(); let agents = agentReads.current; const endpoints = new EndpointStore({ getLegacyAgents: () => visibleAgentRows(agents.snapshot()) }); const sessionUi = new SessionUiState(); const bashBg = new BashBgStore(); const todos = new TodoStore(); const maestro = new MaestroStore(); const supervision = new SupervisionStore(); let usageSubsystem: UsageSubsystem | undefined; const settingsRegistry = new SettingsProviderRegistry({ on: (event, handler) => pi.events.on(event, handler), emit: (event, payload) => pi.events.emit(event, payload), }); // Registry and provider announcements are re-established at session_start and // torn down at session_shutdown (start/dispose are idempotent), so a host // reload cannot accumulate stale shared-bus listeners from old instances. let settingsProviderDisposer: (() => void) | undefined; let externalAgentProjectionDisposer: (() => void) | undefined; let externalAgentProjectionGeneration = 0; const registerSettingsProvider = (): void => { if (settingsProviderDisposer) return; const eventBus = { on: (event: string, handler: (payload: unknown) => void) => pi.events.on(event, handler), emit: (event: string, payload: unknown) => pi.events.emit(event, payload), }; settingsProviderDisposer = registerCockpitSettingsProvider(eventBus, cockpitSettingsProvider); const nativeDisposer = registerNativePiSettingsProvider(eventBus, nativePiSettingsProvider); const previous = settingsProviderDisposer; settingsProviderDisposer = () => { previous(); nativeDisposer(); }; }; const settingsLocale = new SettingsLocaleState(getMaestroUiPreferencesPath(getAgentDir()), settingsRegistry); let config: CockpitConfig = structuredClone(DEFAULT_CONFIG); let lastCtx: ExtensionContext | undefined; let settingsCommandCtx: ExtensionCommandContext | undefined; let capturedTui: TUI | undefined; // TUI the unconditional viewport-stability hook is currently installed on. // Independent of the split-pane attach: the sidebar can be off while the // thinking label / teammate tree still stream above the visible viewport. let stabilityTui: TUI | undefined; let viewportStabilityPatch: ViewportStabilityPatch | undefined; let compactionStylePatch: CompactionStylePatch | undefined; const ensureViewportStability = (tui: TUI): void => { // Native fullscreen owns a fixed application viewport and has no main-screen // applyLineResets hook. Do not cache that expected miss: the same dynamic TUI // reference can later dispatch to the regular renderer. if (tui.mode === "fullscreen") return; if (stabilityTui === tui && viewportStabilityPatch?.active) return; viewportStabilityPatch?.detach(); const patch = attachViewportStability(tui); reportPatch("viewport-stability", patch.active, patch.reason); viewportStabilityPatch = patch; stabilityTui = patch.active ? tui : undefined; }; const requestCapturedRender = (): void => { try { const tui = capturedTui; if (!tui) return; ensureViewportStability(tui); tui.requestRender(); } catch { // tui may be gone between sessions } }; // True while Cockpit owns a capturing overlay (bash jobs, theme picker, // settings panel). Split-pane resize must yield to the overlay's focus and // refuse to start while one is open; otherwise the resize listener — a // global terminal-input hook — swallows the overlay's first arrow/Enter/Esc. let capturingOverlayActive = false; /** Extra repaint target while the Agent modal is consuming the live store. */ let activeAgentOverlayRender: (() => void) | undefined; let activeAgentOverlay: { finalize(): void } | undefined; let activeZenSheet: { finalize(): void } | undefined; let activeBashBgOverlay: BashBgOverlay | undefined; let activeTodoOverlay: TodoOverlay | undefined; // Finalizer for the currently open legacy settings overlay, so a session // boundary can tear it down even when the host hides it without calling // component.dispose() (MW-2). let activeSettingsOverlay: { finalize(): void } | undefined; let sidebarController: SidebarController | undefined; let zenBrowseController: ZenBrowseController | undefined; let zenNavRows: string[] = []; let editorBottomController: EditorBottomController | undefined; // Whether Cockpit installed its custom editor this session (reload-gated). // Tracks ownership so teardown restores the default editor only when ours. let claudeEditorInstalled = false; let claudeEditorForeignWarned = false; let historyStore: InputHistoryStore | undefined; let historyErrorReported = false; let activeClaudeEditor: CockpitClaudeEditor | undefined; let editorRouteTarget: CockpitEditorRouteTarget | undefined; let sessionTheme: Theme | undefined; // Fullscreen (alternate-screen fixed editor) controller, reload-gated. let fullscreenController: FullscreenController | undefined; /** Disposer for the legacy Alt+Shift+T compatibility hook (per applyUi). */ let todoOverlayShortcutDisposer: (() => void) | undefined; /** Disposer for the session ←/→ navigation hook (per applyUi). */ let sessionBarNavDisposer: (() => void) | undefined; let lastPublishedInputTarget: string | undefined; let dockEffectiveVisible = false; let surfaceState: CockpitSurfaceState = "disabled"; let activeAgentWidgetMounted = false; let running = false; let runningStartedAt: number | undefined; /** True while the current assistant message is still streaming. */ let assistantStreaming = false; /** Latest measured output-token rate label for the working line. */ let tokenRate: string | undefined; /** Last usage sample the rate was computed from. */ let ratePrev: { output: number; at: number } | undefined; let uiPromptDepth = 0; // Auto-generated session title from the first turn (the rule-based / LLM // stand-in for Claude Code's Haiku title). Cleared per session. let aiTitle: string | undefined; let firstUserText: string | undefined; let pendingTargetReference: PendingTargetReference | undefined; let titleRequested = false; let titleFrameIndex = 0; let mainOutputRevision = 0; const v2ReadRequested = runtimeV2ReadEnabled(); let usingRuntimeV2 = false; const requestRuntimeSnapshot = (): void => { if (!v2ReadRequested) return; pi.events.emit(RUNTIME_READ_MODEL_QUERY_EVENT, { version: 2 }); }; // Session fence for async title generation: bumped on session start/shutdown // so a stale request can never write into a newer session (MW-3). let titleGeneration = 0; let titleAbort: AbortController | undefined; const activeTools = new Map(); let tick: ReturnType | undefined; let nowSnapshot = Date.now(); // Persisted rather than toasted: a config that failed to load silently downgrades // the whole session to defaults, so it belongs in a slot that does not scroll away. let configProblem: string | undefined; const ambientSurfaces = new AmbientSurfaceCache(); let quietToolsRegistered = false; // Only legacy wrappers can register before binding. Native registration needs // public getSettings/getAllTools/getActiveTools, unavailable during load. // Initial resumed history may therefore use official renderers on its first // pass; no public pre-history decoration hook exists. ensureConfigExists(); config = loadConfig(); compactionStylePatch = attachCompactionStyle({ isEnabled: () => config.enabled, getTheme: () => lastCtx?.ui.theme, getGlyphs: () => resolveGlyphs(config.icons.mode), }); reportPatch("compaction-style", compactionStylePatch.active, compactionStylePatch.reason); // Legacy edit keeps the frozen/occurrence extension. Native edit keeps every // SDK field untouched and uses a tool_call UTF-8 policy (including nested calls). registerGuardedEditTool(pi); registerNativeEditPolicy(pi); registerTeammateMessageRenderer(pi, () => config.enabled); // Reads config live, so toggling static mode re-throttles without re-registering. setUsageThrottle(() => (config.staticMode ? USAGE_REFRESH_THROTTLE_MS : 0)); if (config.quietMode && getPiHostMode(VERSION) === "legacy") { registerQuietTools(pi, () => config); quietToolsRegistered = true; } // Live elapsed for folded thinking rows — no spinner, just a running timer // while the model thinks, then the settled duration. Pi owns the fold. const thinkingTimer = new ThinkingFoldTimer({ getTui: () => capturedTui, requestRender: requestCapturedRender, getBaseLabel: () => (config.quietMode ? quietThinkingLabel() : undefined), getGlyphs: () => resolveGlyphs(config.icons.mode), isThinkingHidden: () => (lastCtx ? readHideThinkingBlock(lastCtx.cwd) : false), isEnabled: () => config.enabled, isStatic: () => config.staticMode, setGlobalLabel: (label) => { const ctx = lastCtx; if (!ctx || !isTuiContext(ctx)) return; try { ctx.ui.setHiddenThinkingLabel(label); } catch { // non-TUI or mid-teardown } }, reportPatchHealth: (active, reason) => reportPatch("thinking-label", active, reason), }); // The streaming line, the tab title and the footer status slot are all fed from // the same snapshots the widgets read, so they can never disagree with them. // Leading title glyph: spinner frames while a turn runs (advanced by the // tick), a static marker when idle; static mode freezes it. Failure keeps // its own ✗ and ignores the frame entirely. const titleFrame = (isRunning: boolean): string | undefined => { if (isRunning && !config.staticMode) { return TITLE_FRAMES[titleFrameIndex % TITLE_FRAMES.length]; } return TITLE_STATIC; }; /** Compact tokens/s label for the working line. */ function formatTokenRate(perSecond: number): string { if (!Number.isFinite(perSecond) || perSecond <= 0) return ""; return perSecond >= 1000 ? `${(perSecond / 1000).toFixed(1)}k tok/s` : `${Math.round(perSecond)} tok/s`; } /** "esc to interrupt" hint with the resolved app.interrupt key. */ const interruptHint = (): string | undefined => { try { const keys = keyText("app.interrupt"); if (!keys) return undefined; const display = keys.split("/").map((key) => (key === "escape" ? "esc" : key)).join("/"); return tuiT("ambient.interruptHint", { keys: display }); } catch { return undefined; } }; const refreshAmbient = (now = Date.now()): void => { const ctx = lastCtx; if (!ctx || !isTuiContext(ctx)) return; const g = resolveGlyphs(config.icons.mode); try { if (!config.enabled) { ambientSurfaces.setWorkingMessage((message) => ctx.ui.setWorkingMessage(message), undefined); ambientSurfaces.setStatus(COCKPIT_STATUS_KEY, (key, text) => ctx.ui.setStatus(key, text), undefined); return; } const activeTool = [...activeTools.values()].at(-1); const title = config.title; const state: AmbientState = { todos: todos.snapshot(), agents: agents.snapshot(), jobs: bashBg.snapshot(), running, waitingForInput: uiPromptDepth > 0, cwd: title.showCwd ? formatCwd(ctx.sessionManager.getCwd()) : undefined, activeTool: activeTool?.name, workingStartedAt: activeTool?.startedAt ?? runningStartedAt, hideLiveDuration: config.staticMode, separator: ` ${g.separator} `, hint: interruptHint(), tokenRate: assistantStreaming ? tokenRate : undefined, }; ambientSurfaces.setWorkingMessage((message) => ctx.ui.setWorkingMessage(message), workingMessage(state, now)); if (title.enabled) { state.session = title.showSession ? sessionTag(ctx.sessionManager, aiTitle) : undefined; state.model = title.showModel ? modelTag(ctx.model) : undefined; state.thinking = title.showThinking ? thinkingTag(ctx.thinkingLevel) : undefined; state.gitBranch = title.showGit ? (readGitBranch(ctx.cwd) ?? undefined) : undefined; state.maestro = title.showMaestro ? maestroWorkflowTag(maestro.snapshot()?.workflow) : undefined; state.maestroKnowledge = title.showMaestro ? maestroKnowledgeTag(maestro.snapshot()?.workflow?.knowledge) : undefined; state.frame = titleFrame(running && uiPromptDepth === 0); ambientSurfaces.setTitle( (value) => ctx.ui.setTitle(value), titleFor(state, { ok: g.check, fail: g.cross }, g.separator, { maxLength: title.maxLength }), ); } ambientSurfaces.setStatus(COCKPIT_STATUS_KEY, (key, text) => ctx.ui.setStatus(key, text), statusText(configProblem, g.blocked)); } catch { // ambient surfaces are best-effort; never let them break a render } }; const effectiveTodoExpanded = (): boolean => config.todoExpanded; const renderContentKey = (now: number): string => { const agentState = agents.snapshot().map((row) => [row.correlationId, effectiveAgentStatus(row, now)]); const jobs = bashBg.snapshot(); const jobState = jobs.map((job) => { const live = job.status === "running" || job.status === "stopping"; const liveElapsed = !config.staticMode && live ? Math.max(0, Math.floor((now - job.startedAt) / 1000)) : undefined; return [job.id, job.status, liveElapsed]; }); const spinner = jobs.some((job) => job.status === "running" || job.status === "stopping") ? spinFrame(resolveGlyphs(config.icons.mode), now, shouldAnimateFrames(policy())) : ""; // The 250 ms tick uses this key to skip a full tree render when only the // wall clock moved. Todo rows and endpoint selection/content change the // visible UI too, so include lightweight summaries (not full objects). const todoItems = todos.snapshot(); const todoState = todoItems.slice(0, 25).map((item) => [item.id, item.status, item.subject.slice(0, 40)]); const todoCounts: Record = {}; for (const item of todoItems) todoCounts[item.status] = (todoCounts[item.status] ?? 0) + 1; const endpointSnapshot = endpoints.snapshot(); const endpointState = [ endpointSnapshot.contentRevision, endpointSnapshot.mainEndpointId, endpointSnapshot.viewMode, endpointSnapshot.endpoints.map((e) => [e.id, e.status, e.kind, e.outputRevision ?? ""]), ]; return JSON.stringify([spinner, agentState, jobState, todoState, todoCounts, endpointState]); }; // One microtask dirty latch: a burst of store events in the same tick (a // message with many progress rows, a snapshot, a todo hydrate) coalesces into // a single ambient refresh and a single render request per host surface (SB-6). // Tick callers additionally supply the visible clock-content key; equal elapsed // seconds, statuses and spinner text do not schedule another tree render. let renderScheduled = false; let ambientRefreshPending = false; let lastRenderContentKey: string | undefined; let scheduledRenderContentKey: string | undefined; const req = (contentKey?: string, refreshAmbientSurface = true): void => { if (contentKey !== undefined) { if (contentKey === lastRenderContentKey) return; scheduledRenderContentKey = contentKey; } if (refreshAmbientSurface) ambientRefreshPending = true; if (renderScheduled) return; renderScheduled = true; queueMicrotask(() => { renderScheduled = false; const now = Date.now(); nowSnapshot = now; const contentKey = scheduledRenderContentKey; scheduledRenderContentKey = undefined; lastRenderContentKey = contentKey ?? renderContentKey(now); const refreshAmbientSurface = ambientRefreshPending; ambientRefreshPending = false; publishInputTarget(); if (refreshAmbientSurface) refreshAmbient(now); try { requestCapturedRender(); activeAgentOverlayRender?.(); sidebarController?.requestRender(); } catch { // tui may be gone between sessions } }); }; // The usage-bars subsystem is created once `req` exists; its onStatusChange // callback re-renders the footer so the quota bar updates after each poll. // Guarded by config.usage.enabled at every call site so the subsystem is // inert when disabled (its poller/overlay simply never run). usageSubsystem = createUsageSubsystem({ pi, getConfig: () => config, onStatusChange: () => req(), setPollIntervalMs: (ms: number) => { config = { ...config, usage: { ...config.usage, pollIntervalMs: ms } }; const result = saveConfig(config); req(); return result.ok; }, }); const policy = (): TickPolicyState => ({ staticMode: config.staticMode, running, agentActive: agents.hasActive(), bashActive: bashBg.hasActive(), lingering: agentReads.hasPendingCleanup(), ticking: tick !== undefined, }); // True only while a redraw loop is actually running; widgets use it to avoid // painting a frozen spinner frame that reads as a hung UI. const isAnimating = (): boolean => shouldAnimateFrames(policy()); // A failed agent lingers for a while after it completes, and the loop has to // outlive the session that produced it — otherwise the row would sit there // until some unrelated event happened to expire it. Static mode still keeps // that loop alive: failure retention is correctness, not animation. What it // drops is the running/job churn that used to repaint every quarter second. const needsTick = (): boolean => shouldRunTick(policy()); const startTick = (): void => { if (tick) return; tick = setInterval(() => { const now = Date.now(); nowSnapshot = now; activeBashBgOverlay?.tick(now); const agentsPruned = agentReads.prune(now); if (agentsPruned) endpoints.refreshLegacy(); if (needsTick()) { // Static mode keeps the loop alive only so lingering rows can expire // (including the hidden shadow and tombstones). The rows are static, so skip the // full repaint and repaint just once when a prune removed something. if (config.staticMode) { if (agentsPruned) req(); } else { titleFrameIndex += 1; refreshAmbient(now); req(renderContentKey(now), false); } } else { if (agentsPruned) req(); syncTick(); } }, ANIMATION_PERIOD_MS); tick.unref?.(); }; const syncTick = (): void => { if (needsTick()) startTick(); else stopTick(); }; const stopTick = (): void => { if (tick) { clearInterval(tick); tick = undefined; } }; const emitMaestroQuery = (): void => { pi.events.emit(COCKPIT_MAESTRO_QUERY_EVENT, { version: MAESTRO_UI_SNAPSHOT_VERSION }); }; // Config expresses intent; this claim records that Cockpit is actually ready // to own the live UI. Consumers must never suppress their fallback merely // because an enabled config was loaded before Cockpit mounted anything. let uiOwnershipClaimed = false; const uiOwnershipSnapshot = (): CockpitUiOwnershipV1 => { const ownsDock = uiOwnershipClaimed && surfaceState === "dock"; return { todo: uiOwnershipClaimed, agents: uiOwnershipClaimed && config.hideNativeAgents, sessionList: uiOwnershipClaimed, footer: uiOwnershipClaimed, sidebar: ownsDock, goal: ownsDock, todoExpanded: effectiveTodoExpanded(), todoDurationChart: config.todoDurationChart, quiet: uiOwnershipClaimed && config.quietMode, quietSymbols: config.quietSymbols, static: config.staticMode, }; }; const publishUiOwnership = (): void => { pi.events.emit(COCKPIT_UI_OWNERSHIP_EVENT, uiOwnershipSnapshot()); }; // The query responder is extension-lifetime infrastructure, independent from // session data subscriptions. Register it during factory initialization so a // Teammate loaded first can synchronously re-query at session_start. let ownershipQueryDisposer: (() => void) | undefined; const ensureOwnershipQueryResponder = (): void => { ownershipQueryDisposer ??= pi.events.on(COCKPIT_UI_OWNERSHIP_QUERY_EVENT, publishUiOwnership); }; const disposeOwnershipQueryResponder = (): void => { try { ownershipQueryDisposer?.(); } catch { /* best effort */ } ownershipQueryDisposer = undefined; }; ensureOwnershipQueryResponder(); // Factory load does not imply readiness. Advertise the effective released // state immediately so earlier subscribers converge without guessing config. publishUiOwnership(); const setTodoExpanded = (expanded: boolean): void => { if (config.todoExpanded === expanded) return; config = { ...config, todoExpanded: expanded }; saveConfig(config); publishUiOwnership(); req(); }; // Native tool decoration reads config live and restores official rendering // when quiet is off. Legacy wrappers still require /reload to restore shells. const applyQuietMode = (ctx: ExtensionContext, was: boolean, now: boolean): void => { // Broadcast so cross-extension surfaces (e.g. pi-maestro-flow's todo tool // rendering) can follow quiet mode regardless of which path toggled it. publishUiOwnership(); if (now && !was) { if (!quietToolsRegistered) { quietToolsRegistered = registerQuietTools(pi, () => config, VERSION, ctx.cwd); } try { ctx.ui.setHiddenThinkingLabel(quietThinkingLabel()); } catch { /* non-TUI */ } // The label only renames an already-hidden block; the fold itself // rides pi's native toggle, which also persists it. Report what // actually happened instead of promising a fold we could not reach. const folded = ensureThinkingFolded(capturedTui, ctx.cwd, true); ctx.ui.notify( tuiT(folded ? "notice.quietOn" : "notice.quietOnPartial"), "info", ); } else if (!now && was) { try { ctx.ui.setHiddenThinkingLabel(undefined); } catch { /* non-TUI */ } ctx.ui.notify( tuiT(quietToolsRegistered && getPiHostMode(VERSION) === "legacy" ? "notice.quietOff" : "notice.quietOffReady"), "info", ); } }; const sessionListOverlayActive = (): boolean => capturingOverlayActive || ambientKeysShouldYield(capturedTui); const clearWidgets = (ctx: ExtensionContext, clearAgents = true): void => { zenBrowseController?.end(); zenNavRows = []; ctx.ui.setWidget(STACK_WIDGET_KEY, undefined); if (clearAgents) { ctx.ui.setWidget(AGENT_WIDGET_KEY, undefined); activeAgentWidgetMounted = false; } }; const sessionRegistry = (): SessionHostRegistryLike | undefined => { const globals = globalThis as typeof globalThis & Record; const candidate = globals[SESSION_HOST_REGISTRY_KEY]; return isSessionHostRegistryLike(candidate) ? candidate : undefined; }; const disconnectExternalAgentProjections = (): void => { externalAgentProjectionGeneration += 1; try { externalAgentProjectionDisposer?.(); } catch { /* best effort */ } externalAgentProjectionDisposer = undefined; endpoints.setExternalAgents([]); }; const connectExternalAgentProjections = (sessionId: string): void => { disconnectExternalAgentProjections(); const generation = externalAgentProjectionGeneration; void import("pi-maestro-teammate/v1/external-agent-projections").then((api: typeof ExternalAgentProjectionApi) => { if (generation !== externalAgentProjectionGeneration) return; const refresh = (): void => { if (generation !== externalAgentProjectionGeneration) return; endpoints.setExternalAgents(api.collectExternalAgentProjections(sessionId)); }; externalAgentProjectionDisposer = api.registerExternalAgentProjectionDirtyListener(refresh); refresh(); }).catch(() => { // Teammate is an optional peer; Cockpit remains fully functional without it. }); }; const selectedEndpoint = (): CockpitEndpoint | undefined => { const id = sessionUi.selectedId("agent"); return id ? endpoints.get(id) : undefined; }; const selectedWindowEndpoint = (): CockpitEndpoint | undefined => { const id = sessionUi.selectedId("window"); const endpoint = id ? endpoints.get(id) : undefined; return endpoint?.kind === "window" ? endpoint : undefined; }; const selectedAgentCorrelationId = (): string | undefined => { if (sessionUi.mode !== "agent") return undefined; const endpoint = selectedEndpoint(); return endpoint?.kind === "agent" ? endpoint.correlationId : undefined; }; const readEditorDraft = (ctx: ExtensionContext | undefined): string | undefined => { if (!ctx || !isTuiContext(ctx)) return undefined; try { return ctx.ui.getEditorText(); } catch { return undefined; } }; const syncViewingSelection = (): void => { agents.setViewingAgent(sessionUi.mode === "agent" ? selectedAgentCorrelationId() : undefined); }; const reconcileEndpointUi = (): void => { const snapshot = endpoints.snapshot(); const previousMode = sessionUi.mode; const previousId = sessionUi.selectedId(previousMode); const editorDraft = readEditorDraft(lastCtx); if (previousId && editorDraft !== undefined) sessionUi.setDraft(previousId, editorDraft); const agentResult = sessionUi.reconcile("agent", snapshot.endpoints, snapshot.mainEndpointId); const windowResult = sessionUi.reconcile("window", snapshot.windows, snapshot.windows[0]?.id); const nextMode = snapshot.viewMode === "windows" ? "window" : "agent"; sessionUi.setMode(nextMode); if (previousMode !== "window" && nextMode === "window") { const control = snapshot.windows.find(isMonitorControlEndpoint); if (control) sessionUi.select(control.id, "window"); } syncViewingSelection(); const activeResult = nextMode === "window" ? windowResult : agentResult; const activeId = sessionUi.selectedId(nextMode); if ((previousMode !== nextMode || activeResult.selectionChanged) && activeId && lastCtx && isTuiContext(lastCtx)) { try { lastCtx.ui.setEditorText(sessionUi.endpoint(activeId).draft); } catch { /* best effort */ } } else if (previousId === undefined && activeId && editorDraft !== undefined) { sessionUi.setDraft(activeId, editorDraft); } req(); }; endpoints.subscribe(() => reconcileEndpointUi(), { emitCurrent: false }); const selectEndpoint = (id: string): void => { const target = endpoints.get(id); if (!target || target.kind === "window") return; const previousId = sessionUi.selectedId("agent"); const editorDraft = readEditorDraft(lastCtx); if (previousId && editorDraft !== undefined) sessionUi.setDraft(previousId, editorDraft); sessionUi.select(id, "agent"); syncViewingSelection(); if (previousId !== id && lastCtx && isTuiContext(lastCtx)) { try { lastCtx.ui.setEditorText(sessionUi.endpoint(id).draft); } catch { /* best effort */ } } req(); }; const selectWindow = (id: string): void => { const target = endpoints.get(id); if (!target || target.kind !== "window") return; const previousId = sessionUi.selectedId("window"); const editorDraft = readEditorDraft(lastCtx); if (previousId && editorDraft !== undefined) sessionUi.setDraft(previousId, editorDraft); sessionUi.select(id, "window"); if (previousId !== id && lastCtx && isTuiContext(lastCtx)) { try { lastCtx.ui.setEditorText(sessionUi.endpoint(id).draft); } catch { /* best effort */ } } req(); }; const selectAgent = (correlationId: string, toggle = false): void => { const endpoint = endpoints.findAgent(correlationId); if (!endpoint) return; const snapshot = endpoints.snapshot(); selectEndpoint(toggle && sessionUi.selectedId("agent") === endpoint.id ? snapshot.mainEndpointId : endpoint.id); }; const selectedAgentTarget = (): { endpoint: CockpitEndpoint; row?: AgentRow; label: string; color: ThemeColor } | undefined => { if (sessionUi.mode !== "agent") return undefined; const endpoint = selectedEndpoint(); if (!endpoint || endpoint.kind !== "agent" || !endpoint.correlationId) return undefined; const row = visibleAgentRows(agents.snapshot()).find( (candidate) => candidate.correlationId === endpoint.correlationId, ) ?? endpoint.agentRow; return { endpoint, ...(row ? { row } : {}), label: endpoint.label, color: row ? agentSessionColor(row, nowSnapshot) : assignedAgentColor(endpoint.correlationId), }; }; const selectedWindowInputTarget = (): { endpoint: CockpitEndpoint; label: string; color: ThemeColor; sigil: "#" } | undefined => { if (sessionUi.mode !== "window") return undefined; const endpoint = selectedWindowEndpoint(); // #control is the current session itself — no target prompt is shown. if (!endpoint || isMonitorControlEndpoint(endpoint)) return undefined; return { endpoint, label: endpoint.label, color: windowSessionColor(endpoint), sigil: "#", }; }; const selectedHashWindowTargets = new Map(); const windowAutocompleteTargets = () => buildWindowAutocompleteTargets(endpoints.snapshot(), { current: tuiT("window.autocompleteCurrent"), peer: tuiT("window.autocompletePeer"), }); const targetCatalogue = async () => { try { return buildCockpitTargetCatalogue(endpoints.snapshot(), await listSshHostPickerEntries(), true); } catch { return buildCockpitTargetCatalogue(endpoints.snapshot(), [], false); } }; const publishInputTarget = (force = false): void => { const target = sessionUi.mode === "window" ? selectedWindowInputTarget() : selectedAgentTarget(); const sigil = target && "sigil" in target ? target.sigil : "@"; const fingerprint = target ? `${target.endpoint.id}:${sigil}:${target.label}:${target.color}` : "@main"; if (!force && fingerprint === lastPublishedInputTarget) return; lastPublishedInputTarget = fingerprint; if (target && sessionTheme) { editorRouteTarget = { label: target.label, sigil, paint: (text: string) => sessionTheme!.fg(target.color, text), }; } else { editorRouteTarget = undefined; } activeClaudeEditor?.refreshRouteTarget(); const payload: CockpitInputTargetV1 = target ? { version: 1, label: target.label, color: target.color, ...(sigil === "#" ? { sigil } : {}), } : { version: 1 }; pi.events.emit(COCKPIT_INPUT_TARGET_EVENT, payload); }; const ensureEditorBottomController = (ctx: ExtensionContext): EditorBottomController => { if (editorBottomController) return editorBottomController; editorBottomController = createEditorBottomController({ onError: (error) => ctx.ui.notify( tuiT("notice.bottomPinnedDisabled", { message: error instanceof Error ? error.message : String(error) }), "warning", ), }); return editorBottomController; }; const installEditorBottom = (ctx: ExtensionContext): void => { const controller = ensureEditorBottomController(ctx); ctx.ui.setWidget( EDITOR_BOTTOM_WIDGET_KEY, (tui) => { capturedTui = tui; ensureViewportStability(tui); try { controller.attach(tui); reportPatch("editor-bottom", true); } catch (error) { reportPatch("editor-bottom", false, error instanceof Error ? error.message : "attach-error"); } controller.show(); return createEditorBottomSentinel(); }, { placement: "aboveEditor" }, ); }; const clearEditorBottom = (ctx: ExtensionContext): void => { ctx.ui.setWidget(EDITOR_BOTTOM_WIDGET_KEY, undefined); editorBottomController?.hide(); }; // reload-gated: installing the custom editor swaps the live editor (text is // preserved, in-memory history is not), so it only runs at session start and // is never hot-toggled. If another extension owns the editor factory, fail // closed with one warning and leave both gated features inert. const ensureHistoryStore = (ctx: ExtensionContext): void => { const next = new InputHistoryStore(ctx.cwd, { onError: (error) => { if (historyErrorReported) return; historyErrorReported = true; ctx.ui.notify(tuiT("editor.historyUnavailable", { message: errorMessage(error) }), "warning"); }, }); historyStore = next; void next.load().catch(() => undefined); }; const installClaudeEditor = (ctx: ExtensionContext): void => { if (!config.doubleEscapeClearInput && !config.fullscreenInput && !config.historyEnabled) return; sessionTheme = ctx.ui.theme; if (config.historyEnabled) ensureHistoryStore(ctx); const current = ctx.ui.getEditorComponent(); if (isCockpitClaudeEditorFactory(current)) { // Our own factory is already in pi's editor slot — it survives a resume // or reload that cleared the module state but not the slot. Treat it as // installed and never misreport it as a foreign owner. claudeEditorInstalled = true; return; } if (claudeEditorInstalled && current === undefined) { // pi cleared our editor (e.g. resetExtensionUI on a session switch) but // the flag is stale; reinstall below instead of staying inert. claudeEditorInstalled = false; } if (current) { reportPatch("custom-editor", false, "foreign-owner"); if (!claudeEditorForeignWarned) { claudeEditorForeignWarned = true; ctx.ui.notify( tuiT("notice.editorOwned"), "warning", ); } return; } ctx.ui.setEditorComponent(createCockpitClaudeEditorFactory({ doubleEscapeClearInput: config.doubleEscapeClearInput, emitEditorMarkers: () => fullscreenController?.isActive() ?? false, isBusy: () => running || Boolean(activeSettingsOverlay), getEntries: config.historyEnabled ? () => historyStore?.list() ?? [] : undefined, record: config.historyEnabled ? (text) => historyStore?.record(text) : undefined, getRouteTarget: () => editorRouteTarget, workingStatusPaint: (text) => ctx.ui.theme.fg("muted", text), onEditor: (editor) => { activeClaudeEditor = editor; }, onError: (error) => ctx.ui.notify( tuiT("notice.editorError", { message: error instanceof Error ? error.message : String(error) }), "warning", ), })); claudeEditorInstalled = true; reportPatch("custom-editor", true); }; const clearClaudeEditor = (ctx: ExtensionContext): void => { const current = ctx.ui.getEditorComponent(); if (!claudeEditorInstalled && !isCockpitClaudeEditorFactory(current)) return; try { ctx.ui.setEditorComponent(undefined); } catch { // best-effort: teardown must not break Cockpit disposal } claudeEditorInstalled = false; }; const installFullscreen = (ctx: ExtensionContext): void => { if (fullscreenController || !config.fullscreenInput) return; // Markers are emitted by the Cockpit custom editor; without it fullscreen // cannot split the screen, so it stays inert (fail closed like the editor). if (!claudeEditorInstalled) return; const compatibility = detectTerminalCompatibility(); if (!compatibility.compatible) { ctx.ui.notify(tuiT("notice.fullscreenDisabled", { message: compatibility.reason ?? tuiT("notice.unknownError"), }), "warning"); return; } const controller = createFullscreenController({ subscribeInput: (handler) => ctx.ui.onTerminalInput(handler), isCopyOnSelect: () => config.copyOnSelect, onError: (error) => ctx.ui.notify( tuiT("notice.fullscreenDisabled", { message: error instanceof Error ? error.message : String(error) }), "warning", ), }); // The editor reads this controller's live state before emitting its layout // sentinels, so rejected or lost render wrappers can never leak marker text. fullscreenController = controller; ctx.ui.setWidget( COCKPIT_FULLSCREEN_WIDGET_KEY, (tui) => { capturedTui = tui; try { controller.attach(tui); const active = controller.isActive(); reportPatch("fullscreen", active, active ? undefined : "attach-inactive"); } catch (error) { reportPatch("fullscreen", false, error instanceof Error ? error.message : "attach-error"); } return { render: () => [] as string[], invalidate() {} }; }, { placement: "aboveEditor" }, ); }; const clearFullscreen = (ctx: ExtensionContext): void => { ctx.ui.setWidget(COCKPIT_FULLSCREEN_WIDGET_KEY, undefined); const controller = fullscreenController; fullscreenController = undefined; controller?.dispose(); }; const sessionBarHint = () => { if (sessionListOverlayActive()) return undefined; return maestro.snapshot()?.artifact?.available ? { text: tuiT("artifact.hint"), color: "accent" as const } : tuiT(sessionUi.mode === "agent" ? "session.agentListHint" : "session.listHint"); }; const agentRailEndpoints = (): readonly CockpitEndpoint[] => { const snapshot = endpoints.snapshot(); if (config.hideNativeAgents) return snapshot.endpoints; const selectedId = sessionUi.selectedId("agent"); const selected = selectedId ? snapshot.endpoints.find((endpoint) => endpoint.id === selectedId) : undefined; return selected ? [selected] : snapshot.endpoints.filter((endpoint) => endpoint.kind === "root").slice(0, 1); }; const installSessionBar = (ctx: ExtensionContext): void => { ctx.ui.setWidget( SESSION_BAR_WIDGET_KEY, (tui, theme) => { capturedTui = tui; ensureViewportStability(tui); const agentWidget = makeAgentBarWidget({ getEndpoints: agentRailEndpoints, getState: () => sessionUi, getNow: () => nowSnapshot, isMainRunning: () => running, getShortcutHint: sessionBarHint, })(tui, theme); return { render(width: number): string[] { const snapshot = endpoints.snapshot(); return sessionUi.mode === "window" ? renderWindowBar( snapshot.windows, sessionUi, width, theme, { shortcutHint: sessionBarHint() }, ) : agentWidget.render(width); }, invalidate(): void { agentWidget.invalidate(); }, dispose(): void { agentWidget.dispose(); }, }; }, { placement: "aboveEditor" }, ); }; const ensureZenBrowseController = (ctx: ExtensionContext): ZenBrowseController => { if (zenBrowseController) return zenBrowseController; zenBrowseController = createZenBrowseController({ getNavRows: () => zenNavRows, subscribeInput: (handler) => ctx.ui.onTerminalInput(handler), requestRender: () => req(), onActivate: (id) => { void activateZenRow(ctx, id).catch((error) => { ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning"); }); }, shouldYield: () => capturingOverlayActive || ambientKeysShouldYield(capturedTui), onWarning: (message) => ctx.ui.notify(message, "warning"), emptyNotice: () => tuiT("notice.sidebarNothing"), }); return zenBrowseController; }; const syncActiveAgentWidget = (ctx: ExtensionContext): void => { const shouldMount = uiOwnershipClaimed && config.enabled && config.hideNativeAgents && activeAgentTreeRows(agents.snapshot()).length > 0; if (shouldMount === activeAgentWidgetMounted) return; if (!shouldMount) { ctx.ui.setWidget(AGENT_WIDGET_KEY, undefined); activeAgentWidgetMounted = false; return; } ctx.ui.setWidget( AGENT_WIDGET_KEY, (tui, theme) => { capturedTui = tui; ensureViewportStability(tui); return makeActiveAgentWidget({ getAgents: () => agents.snapshot(), getConfig: () => config, })(tui, theme); }, { placement: "belowEditor" }, ); activeAgentWidgetMounted = true; }; const installWidgets = (ctx: ExtensionContext): void => { const browseController = ensureZenBrowseController(ctx); // Clear any stale roster from an older extension generation before deciding // whether the current active set owns the below-editor slot. ctx.ui.setWidget(AGENT_WIDGET_KEY, undefined); activeAgentWidgetMounted = false; ctx.ui.setWidget( STACK_WIDGET_KEY, (tui, theme) => { capturedTui = tui; ensureViewportStability(tui); const todoWidget = makeTodoWidget({ getTodos: () => todos.snapshot(), getConfig: () => config, getExpanded: effectiveTodoExpanded, isAnimating, })(tui, theme); const zenMemo = memoized>(); // stackStyle is dispatched per render, not per install, so a settings // toggle switches the projection live without re-registering widgets. return { render(width: number): string[] { if (config.stackStyle !== "zen") { zenNavRows = []; return todoWidget.render(width); } const agentRows: AgentRow[] = []; const jobs = bashBg.snapshot(); const now = Date.now(); // The wall clock only feeds live durations; when nothing is live a // host-driven frame (assistant stream) can reuse the last build. const needsClock = !config.staticMode && (agentRows.some((row) => row.status === "running" || row.status === "retrying") || jobs.some((job) => job.status === "running" || job.status === "stopping")); const browse = browseController.state(); const memoKey = [ width, refId(theme), config.icons.mode, config.staticMode, config.todoExpanded, effectiveTodoExpanded(), sessionUi.mode, panelRows(terminalRows(tui)), browse?.selectedId ?? "", browse?.expandedId ?? "", needsClock ? Math.floor(now / 1000) : "-", maestro.contentRevision, todos.revision, bashBg.revision, agentRowsKey(agentRows), bashJobsKey(jobs), ].join(";"); const stack = zenMemo(memoKey, () => buildZenStack({ maestro: maestro.snapshot(), todos: todos.snapshot(), agents: agentRows, jobs, config: { ...config, todoExpanded: effectiveTodoExpanded() }, width, theme, now, maxRows: panelRows(terminalRows(tui)), browse, })); zenNavRows = stack.navIds; return stack.lines; }, invalidate(): void { zenMemo.clear(); todoWidget.invalidate(); }, dispose(): void { zenMemo.clear(); todoWidget.dispose(); }, }; }, { placement: "aboveEditor" }, ); syncActiveAgentWidget(ctx); }; const reconcileSurface = (ctx: ExtensionContext): void => { const next = resolveCockpitSurfaceState(config.enabled, config.sidebar.mode, dockEffectiveVisible); if (next === surfaceState) return; if (next === "dock") clearWidgets(ctx, false); else if (next === "disabled") clearWidgets(ctx); else { installWidgets(ctx); // Host widget maps preserve insertion order when an existing key is set. // Todo was just re-added after a dock period, so remove/re-add the bar to // keep the invariant: detail → Todo → session bar → editor. ctx.ui.setWidget(SESSION_BAR_WIDGET_KEY, undefined); installSessionBar(ctx); } surfaceState = next; publishUiOwnership(); req(); }; const ensureSidebarController = (ctx: ExtensionContext): SidebarController => { if (sidebarController) return sidebarController; sidebarController = createSidebarController({ ctx, getMaestroSnapshot: () => maestro.snapshot(), getTodos: () => todos.snapshot(), getAgents: () => [], getJobs: () => bashBg.snapshot(), getConfig: () => config, getHeight: () => capturedTui?.terminal.rows ?? 12, shouldAnimate: () => shouldAnimateSidebar(policy()), onVisibilityChange: (visible) => { dockEffectiveVisible = visible; const activeCtx = lastCtx; if (visible) emitMaestroQuery(); if (activeCtx && isTuiContext(activeCtx)) reconcileSurface(activeCtx); }, onResizeCommit: (width) => { config = { ...config, sidebar: { ...config.sidebar, width } }; const result = saveConfig(config); if (!result.ok) { configProblem = `sidebar width save failed: ${result.error ?? "unknown error"}`; ctx.ui.notify(tuiT("notice.sidebarWidthSaveFailed", { message: result.error ?? tuiT("notice.unknownError"), }), "warning"); } else if (configProblem?.startsWith("sidebar width save failed:")) { configProblem = undefined; } req(); }, onWarning: (message) => ctx.ui.notify(message, "warning"), onError: (error) => { ctx.ui.notify(tuiT("notice.sidebarUnavailable", { message: error instanceof Error ? error.message : String(error), }), "warning"); }, onActivateRow: (id) => { // Enter on an agent row selects it as the shown session (toggle). // The cockpit session bar mirrors the highlight; nothing is written // into the main conversation, so the main agent keeps working. if (!id.startsWith("agent:")) return; const cid = id.slice("agent:".length); selectAgent(cid, true); }, getNavWidth: () => capturedTui?.terminal.columns ?? 80, getTui: () => capturedTui, }); return sidebarController; }; // show() pushes the dock onto pi's overlay stack, and pi closes an overlay // by popping the stack top: a dock pushed while a capturing overlay (the // legacy settings panel, the /maestro-settings shell) is still open gets // popped by that overlay's own close, stranding it open with Esc dead. // Defer the show until the stack is clear; hide() never pushes and stays // immediate. let deferredSidebarSync = false; const syncSidebarMode = (ctx: ExtensionContext): void => { const controller = ensureSidebarController(ctx); controller.setWidth(config.sidebar.width); if (config.sidebar.mode === "off") { deferredSidebarSync = false; dockEffectiveVisible = false; controller.hide(); } else if (capturingOverlayActive || capturingOverlayVisible(capturedTui)) { deferredSidebarSync = true; } else { deferredSidebarSync = false; controller.show(); } reconcileSurface(ctx); }; const flushDeferredSidebarSync = (): void => { if (!deferredSidebarSync) return; deferredSidebarSync = false; const ctx = lastCtx; if (ctx && config.enabled && isTuiContext(ctx)) syncSidebarMode(ctx); }; const disposeLayoutControllers = (): void => { const render = capturedTui?.render as (TUI["render"] & Record) | undefined; const fullscreenIsTop = Boolean(render?.[COCKPIT_FULLSCREEN_MARKER]); const editorBottomIsTop = Boolean(render?.[COCKPIT_EDITOR_BOTTOM_MARKER]); const splitPaneIsTop = Boolean(render?.[COCKPIT_SPLIT_PANE_MARKER]); // Disposal must unwrap in reverse install order (outermost first): the // fullscreen renderer is the outermost Cockpit wrapper, then editor-bottom // (when on top), then split-pane. if (fullscreenIsTop) { fullscreenController?.dispose(); editorBottomController?.dispose(); sidebarController?.dispose(); } else if (editorBottomIsTop && !splitPaneIsTop) { editorBottomController?.dispose(); sidebarController?.dispose(); } else { sidebarController?.dispose(); editorBottomController?.dispose(); } zenBrowseController?.dispose(); zenBrowseController = undefined; zenNavRows = []; sidebarController = undefined; editorBottomController = undefined; fullscreenController = undefined; }; const uninstallUi = (ctx: ExtensionContext): void => { try { activeAgentOverlay?.finalize(); } catch { // best effort } activeAgentOverlay = undefined; activeAgentOverlayRender = undefined; try { activeZenSheet?.finalize(); } catch { // best effort } activeZenSheet = undefined; dockEffectiveVisible = false; clearClaudeEditor(ctx); void historyStore?.flush(); historyStore = undefined; historyErrorReported = false; activeClaudeEditor = undefined; editorRouteTarget = undefined; clearFullscreen(ctx); clearEditorBottom(ctx); disposeLayoutControllers(); clearWidgets(ctx); ctx.ui.setWidget(SESSION_BAR_WIDGET_KEY, undefined); ctx.ui.setWidget(SESSION_DETAIL_WIDGET_KEY, undefined); todoOverlayShortcutDisposer?.(); todoOverlayShortcutDisposer = undefined; sessionBarNavDisposer?.(); sessionBarNavDisposer = undefined; ctx.ui.setFooter(undefined); surfaceState = "disabled"; try { // Leaving these set would strand a title and a status line owned by an // extension that is no longer painting anything. ctx.ui.setWorkingMessage(undefined); ctx.ui.setWorkingIndicator(); ctx.ui.setStatus(COCKPIT_STATUS_KEY, undefined); } catch { // ambient surfaces are best-effort } ambientSurfaces.reset(); stopTick(); viewportStabilityPatch?.detach(); viewportStabilityPatch = undefined; stabilityTui = undefined; lastPublishedInputTarget = "@main"; pi.events.emit(COCKPIT_INPUT_TARGET_EVENT, { version: 1 } satisfies CockpitInputTargetV1); capturedTui = undefined; }; const applyUi = (ctx: ExtensionContext): void => { if (!isTuiContext(ctx)) return; if (!config.enabled) { uninstallUi(ctx); return; } ctx.ui.setWorkingIndicator({ frames: [] }); if (config.pinEditorBottom && !config.fullscreenInput) installEditorBottom(ctx); else clearEditorBottom(ctx); installClaudeEditor(ctx); installFullscreen(ctx); ctx.ui.setFooter((tui, theme, footerData) => { capturedTui = tui; ensureViewportStability(tui); let observedWidth = tui.terminal.columns; const widthTimer = setInterval(() => { try { const nextWidth = tui.terminal.columns; if (!Number.isFinite(nextWidth) || nextWidth <= 0 || nextWidth === observedWidth) return; observedWidth = nextWidth; // Deliberately skip tui.invalidate(): upstream pi 0.83.0 has a bug // where ToolExecutionComponent.updateDisplay() can push undefined // into Container.children (renderer returns undefined for a // "working" tool). Container.invalidate() then does // `child.invalidate?.()` on that undefined child → TypeError → // uncaughtException in this timer callback → pi process exit. // requestRender(true) already clears previousLines and forces a // full re-render with the new width, which is all we need here. tui.requestRender(true); } catch { // Last-resort guard: a synchronous exception in this timer // callback would become an uncaughtException and kill the pi // process. Swallow it so the session survives; the next tick // will retry. } }, WIDTH_POLL_INTERVAL_MS); widthTimer.unref?.(); const unsubscribeBranch = footerData.onBranchChange(() => tui.requestRender()); const footerMemo = memoizedLines(); const component = { render(width: number): string[] { if (Number.isFinite(width) && width > 0) observedWidth = width; const cu = ctx.getContextUsage(); const branch = footerData.getGitBranch(); const extensionStatuses = collectExtensionStatuses(footerData.getExtensionStatuses()); const glyphs = resolveGlyphs(config.icons.mode); const now = nowSnapshot; const bashBgStatus = renderBashBgSummary( bashBg.snapshot(), Math.max(1, width - 1), theme, FOOTER_UTILS, { glyphs, spin: spinFrame(glyphs, now, isAnimating()), now, hideLiveDuration: config.staticMode, }, )[0]; const totals = getUsageTotals(ctx.sessionManager.getEntries()); const usageStatus = config.usage.enabled && config.usage.footer ? usageSubsystem?.getStatus(theme, config.usage.barWidth) : undefined; const maestroWorkflow = maestro.workflow(); // Host-driven frames repaint the footer without touching its data; // fingerprint the rendered inputs and skip the rebuild on them. const footerKey = [ width, refId(theme), config.icons.mode, config.staticMode, ctx.model?.id ?? "no-model", ctx.model?.provider ?? "", pi.getThinkingLevel(), formatCwd(ctx.sessionManager.getCwd()), cu?.percent ?? 0, cu?.tokens ?? 0, cu?.contextWindow ?? ctx.model?.contextWindow ?? 0, refId(totals), config.currency ?? "", config.currencyRate ?? "", branch ?? "", bashBgStatus ?? "", usageStatus ?? "", extensionStatuses.map((status) => `${status.key}:${status.text}`).join("|"), refId(maestroWorkflow), maestro.contentRevision, ].join(";"); return footerMemo(footerKey, () => renderFooter({ width, model: ctx.model?.id ?? "no-model", provider: ctx.model?.provider, thinking: pi.getThinkingLevel(), cwd: formatCwd(ctx.sessionManager.getCwd()), ctxPct: cu?.percent ?? 0, ctxTokens: cu?.tokens ?? 0, ctxWindow: cu?.contextWindow ?? ctx.model?.contextWindow ?? 0, totals, currency: config.currency, currencyRate: config.currencyRate, git: branch ?? undefined, bashBgStatus, usageStatus, workflowStatus: extensionStatuses.find((status) => status.key === WORKFLOW_STATUS_KEY)?.text, maestroWorkflow, extensionStatuses: extensionStatuses.filter((status) => status.key !== WORKFLOW_STATUS_KEY), glyphs, theme, utils: FOOTER_UTILS, })); }, invalidate(): void { footerMemo.clear(); }, dispose(): void { footerMemo.clear(); clearInterval(widthTimer); unsubscribeBranch(); }, }; return component; }); // Install first in aboveEditor order so this fixed session region sits // above Todo; Todo and the session bar are mounted closer to the editor. ctx.ui.setWidget( SESSION_DETAIL_WIDGET_KEY, (tui, theme) => { capturedTui = tui; ensureViewportStability(tui); const agentDetail = makeSessionDetailWidget({ getAgents: () => agents.snapshot(), getViewingId: selectedAgentCorrelationId, getVisible: () => { const endpoint = selectedEndpoint(); return sessionUi.mode === "agent" && endpoint?.kind === "agent" && sessionUi.endpoint(endpoint.id).detail; }, getScroll: () => { const endpoint = selectedEndpoint(); if (!endpoint) return { offset: 0, following: true }; const state = sessionUi.endpoint(endpoint.id); return { offset: state.scroll, following: state.followTail }; }, })(tui, theme); const windowDetail = makeWindowThreadWidget({ getWindow: selectedWindowEndpoint, getEntries: () => endpoints.snapshot().thread, getVisible: () => sessionUi.mode === "window" && Boolean(selectedWindowEndpoint()), getScroll: () => { const endpoint = selectedWindowEndpoint(); if (!endpoint) return { offset: 0, following: true }; const state = sessionUi.endpoint(endpoint.id); return { offset: state.scroll, following: state.followTail }; }, })(tui, theme); return { render(width: number): string[] { return sessionUi.mode === "window" ? windowDetail.render(width) : agentDetail.render(width); }, invalidate(): void { agentDetail.invalidate(); windowDetail.invalidate(); }, dispose(): void { agentDetail.dispose(); windowDetail.dispose(); }, }; }, { placement: "aboveEditor" }, ); syncSidebarMode(ctx); // Re-enabling mid-run must restart live spinner and elapsed updates. syncTick(); // Agent bar: one line of session chips above the input box, selected chip // highlighted (▸) and the line panning horizontally when agents overflow; // window mode swaps in the window bar. Installed on every surface (dock // and widgets), inserted last so it sits closest to the editor. installSessionBar(ctx); // Legacy terminals encode Alt+Shift+T as ESC + uppercase T, which the // host shortcut matcher cannot distinguish without enhanced key reporting. todoOverlayShortcutDisposer?.(); todoOverlayShortcutDisposer = ctx.ui.onTerminalInput((data) => { if (!isLegacyTodoOverlayInput(data) || ambientKeysShouldYield(capturedTui)) return undefined; void openTodoOverlay(ctx).catch((error) => { ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning"); }); return { consume: true }; }); // ←/→ cycles tabs only with an empty composer. Window mode additionally // accepts Alt+←/→ so drafts can be switched without losing cursor arrows. // A capturing modal overlay owns navigation while it is open. sessionBarNavDisposer?.(); sessionBarNavDisposer = ctx.ui.onTerminalInput((data) => { if (ambientKeysShouldYield(capturedTui)) return undefined; const windowPrevious = sessionUi.mode === "window" && matchesKey(data, "alt+left"); const windowNext = sessionUi.mode === "window" && matchesKey(data, "alt+right"); const plainPrevious = data === "\x1b[D"; const plainNext = data === "\x1b[C"; if (!windowPrevious && !windowNext && !plainPrevious && !plainNext) return undefined; const text = ctx.ui.getEditorText(); if (!windowPrevious && !windowNext && text.trim() !== "") return undefined; const snapshot = endpoints.snapshot(); const delta = windowNext || plainNext ? 1 : -1; if (sessionUi.mode === "window") { const next = nextSessionTabId(snapshot.windows, sessionUi.selectedId("window"), delta); if (next) selectWindow(next); } else { const next = nextSessionTabId(snapshot.endpoints, sessionUi.selectedId("agent"), delta); if (next) selectEndpoint(next); } return { consume: true }; }); }; const releaseUiOwnership = (ctx: ExtensionContext): void => { let uninstallError: unknown; try { // The outgoing owner must disappear before native consumers observe the // release; otherwise synchronous listeners can briefly mount both. uninstallUi(ctx); } catch (error) { uninstallError = error; } finally { uiOwnershipClaimed = false; publishUiOwnership(); } if (uninstallError !== undefined) { configProblem = `Cockpit UI uninstall failed: ${errorMessage(uninstallError)}`; try { ctx.ui.notify(configProblem, "warning"); } catch { /* best effort */ } } }; const acquireUiOwnership = (ctx: ExtensionContext): boolean => { if (!config.enabled || !isTuiContext(ctx)) { uiOwnershipClaimed = false; publishUiOwnership(); return false; } // The host EventBus is synchronous: native owners withdraw in this emit // before Cockpit starts mounting its own surfaces. uiOwnershipClaimed = true; publishUiOwnership(); try { applyUi(ctx); return true; } catch (error) { // A partial mount must never strand native UI suppressed. Best-effort // uninstall completes before the released snapshot is published. try { uninstallUi(ctx); } catch { /* retain the original mount failure */ } uiOwnershipClaimed = false; publishUiOwnership(); configProblem = `Cockpit UI install failed: ${errorMessage(error)}`; try { ctx.ui.notify(configProblem, "warning"); } catch { /* best effort */ } return false; } }; const syncActiveAgentSurface = (): boolean => { const ctx = lastCtx; if (!ctx || !isTuiContext(ctx)) return true; try { syncActiveAgentWidget(ctx); return true; } catch (error) { releaseUiOwnership(ctx); configProblem = `Cockpit Agent UI install failed: ${errorMessage(error)}`; try { ctx.ui.notify(configProblem, "warning"); } catch { /* best effort */ } return false; } }; // --- teammate lifecycle (custom event bus; subscribed once for the extension lifetime) --- // Session-title generation: after the first turn settles, ask the configured // model (api-manager provider) with the user message + assistant reply, else // fall back to the offline rule-based suggestTitle. A model ref that cannot // be resolved or fails is never fatal — the title just stays rule-based. const generateTitleWithConfiguredModel = async ( ctx: ExtensionContext, modelRef: string, text: string, signal: AbortSignal, ): Promise => { const slash = modelRef.indexOf("/"); if (slash <= 0 || slash === modelRef.length - 1) return null; const model = ctx.modelRegistry.find(modelRef.slice(0, slash), modelRef.slice(slash + 1)); if (!model) return null; const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); if (!auth.ok) return null; return generateTitleWithModel( { baseUrl: model.baseUrl, modelId: model.id, apiKey: auth.apiKey, headers: auth.headers }, text, signal, ); }; const generateSessionTitle = async ( ctx: ExtensionContext, userText: string, assistantMessage: unknown, refresh: () => void, ): Promise => { const generation = titleGeneration; const fallback = suggestTitle(userText); const assistantText = messageText(assistantMessage); const llmInput = assistantText ? `${userText}\n\nAssistant:\n${assistantText.slice(0, 500)}` : userText; const modelRef = config.title.generationModel; const controller = new AbortController(); titleAbort = controller; const timer = setTimeout(() => controller.abort(), 10_000); let title: string | null = null; try { title = modelRef ? await generateTitleWithConfiguredModel(ctx, modelRef, llmInput, controller.signal) : null; } catch { // Model failures degrade to the rule fallback; never reject the // fire-and-forget caller. title = null; } finally { clearTimeout(timer); if (titleAbort === controller) titleAbort = undefined; } // A session boundary while the request was in flight must discard the // result: the title slot now belongs to the newer session (MW-3). if (generation !== titleGeneration || lastCtx !== ctx) return; if (title) { aiTitle = title; } else if (fallback) { aiTitle = fallback; } else { titleRequested = false; // nothing usable; a later turn may retry return; } refresh(); }; // Shared-bus subscriptions are session-scoped so a host /reload does not // accumulate a second generation of listeners on the same bus: the old // extension instance's disposers run at session_shutdown and session_start // re-registers them. pi.on(...) handlers are extension-owned by the loader // and need no manual cleanup. const busDisposers: Array<() => void> = []; const subscribeBusEvents = (): void => { if (busDisposers.length > 0) return; busDisposers.push( cockpitTuiLocale.subscribe(() => { if (config.quietMode && lastCtx) { try { lastCtx.ui.setHiddenThinkingLabel(quietThinkingLabel()); } catch { /* non-TUI */ } } req(); }), bindCockpitTuiLocale({ on: (event, handler) => pi.events.on(event, handler), }), pi.events.on(TEAMMATE_STARTED_EVENT, (payload) => { // Re-broadcast ownership at the exact moment a native agent widget // would appear; a late-loaded teammate may have missed the // session_start broadcast (and its query may have raced startup). publishUiOwnership(); if (!isStartedPayload(payload) || !agentReads.applyLegacyStarted(payload)) return; syncTick(); if (usingRuntimeV2) return; agents = agentReads.current; endpoints.refreshLegacy(); if (!syncActiveAgentSurface()) return; req(); }), pi.events.on(TEAMMATE_MESSAGE_EVENT, (payload) => { if (!isProgressPayload(payload) || !agentReads.applyLegacyMessage(payload)) return; syncTick(); if (usingRuntimeV2) return; agents = agentReads.current; endpoints.refreshLegacy(); if (!syncActiveAgentSurface()) return; req(); }), pi.events.on(TEAMMATE_COMPLETE_EVENT, (payload) => { // Hidden legacy completions still need row/tombstone expiry while V2 renders. if (!isCompletePayload(payload) || !agentReads.applyLegacyComplete(payload)) return; syncTick(); if (usingRuntimeV2) return; agents = agentReads.current; endpoints.refreshLegacy(); if (!syncActiveAgentSurface()) return; req(); }), pi.events.on(RUNTIME_READ_MODEL_SNAPSHOT_EVENT, (payload) => { if (!v2ReadRequested) return; const applied = agentReads.applyRuntimeSnapshotResult(payload); if (applied === "stale") return; usingRuntimeV2 = applied === "applied"; if (!usingRuntimeV2) return; agents = agentReads.current; endpoints.refreshLegacy(); if (!syncActiveAgentSurface()) return; syncTick(); req(); }), pi.events.on(RUNTIME_READ_MODEL_DELTA_EVENT, (payload) => { if (!v2ReadRequested) return; const applied = agentReads.applyRuntimeDeltaResult(payload); if (applied === "stale") return; if (!usingRuntimeV2 || applied !== "applied") { usingRuntimeV2 = false; agentReads.fallback(); agents = agentReads.current; requestRuntimeSnapshot(); return; } agents = agentReads.current; endpoints.refreshLegacy(); if (!syncActiveAgentSurface()) return; syncTick(); req(); }), pi.events.on(RUNTIME_READ_MODEL_UNAVAILABLE_EVENT, (payload) => { if (!v2ReadRequested || !agentReads.acceptProjectionPayload(payload)) return; usingRuntimeV2 = false; agentReads.fallback(); agents = agentReads.current; endpoints.refreshLegacy(); if (!syncActiveAgentSurface()) return; syncTick(); req(); }), pi.events.on(BASH_BG_UPDATE_EVENT, (payload) => { if (!bashBg.applySnapshot(payload)) return; activeBashBgOverlay?.tick(Date.now()); syncTick(); req(); }), pi.events.on(MAESTRO_UI_SNAPSHOT_EVENT, (payload) => { if (!maestro.applySnapshot(payload)) return; req(); }), pi.events.on(MAESTRO_TODO_STATE_CHANGED_EVENT, (payload) => { if (!payload || typeof payload !== "object" || (payload as { version?: unknown }).version !== 1) return; const ctx = lastCtx; if (!ctx || !todos.hydrateFromEntries(ctx.sessionManager.getEntries())) return; req(); }), pi.events.on(SUPERVISION_EVENT, (payload) => { if (!supervision.applyEvent(payload)) return; req(); }), pi.events.on(COCKPIT_TODO_TOGGLE_EVENT, (payload) => { if (!config.enabled) return; const requested = payload && typeof payload === "object" ? (payload as { expanded?: unknown }).expanded : undefined; setTodoExpanded(typeof requested === "boolean" ? requested : !effectiveTodoExpanded()); }), pi.events.on(TEAMMATE_OPEN_AGENT_EVENT, (payload) => { if (!config.enabled) return; const ctx = lastCtx; if (!ctx) return; const correlationId = payload && typeof payload === "object" ? (payload as { correlationId?: unknown }).correlationId : undefined; void openAgentOverlay(ctx, typeof correlationId === "string" ? correlationId : undefined).catch(() => { // Best-effort: an overlay failure must not break the event bus. }); }), pi.events.on(COCKPIT_PREEMPT_RESIZE_EVENT, () => { // A capturing overlay opened by another extension (teammate attach, // control center, model mapping) must preempt every global input hook. try { sidebarController?.cancelResize(); sidebarController?.endFocus(); zenBrowseController?.end(); } catch { // best effort } }), ); }; const unsubscribeBusEvents = (): void => { for (const off of busDisposers) { try { off(); } catch { // best effort } } busDisposers.length = 0; }; // --- foreground tool label + todo changes --- pi.on("tool_execution_start", (e) => { activeTools.set(e.toolCallId, { name: e.toolName, startedAt: Date.now() }); req(); }); pi.on("tool_execution_end", (e, ctx) => { activeTools.delete(e.toolCallId); if (e.toolName === TODO_TOOL_NAME && !todos.hydrateFromEntries(ctx.sessionManager.getEntries())) { const now = Date.now(); nowSnapshot = now; refreshAmbient(now); return; } req(); }); // --- session + agent lifecycle --- pi.on("session_start", (_e, ctx) => { uiOwnershipClaimed = false; ensureOwnershipQueryResponder(); publishUiOwnership(); lastCtx = ctx; uiPromptDepth = 0; ambientSurfaces.reset(); agentReads.bindSession(ctx.sessionManager.getSessionId()); agents = agentReads.current; usingRuntimeV2 = false; lastPublishedInputTarget = undefined; sessionUi.reset(); mainOutputRevision = 0; endpoints.setMainOutputRevision(undefined); endpoints.connect({ registry: sessionRegistry(), events: { on: (event, handler) => pi.events.on(event, handler) }, sessionId: ctx.sessionManager.getSessionId(), }); connectExternalAgentProjections(ctx.sessionManager.getSessionId()); subscribeBusEvents(); settingsRegistry.start(); registerSettingsProvider(); configProblem = undefined; activeTools.clear(); thinkingTimer.reset(); maestro.clear(); aiTitle = undefined; firstUserText = undefined; pendingTargetReference = undefined; titleRequested = false; titleFrameIndex = 0; titleGeneration += 1; ensureConfigExists(); config = loadConfig((m, l) => { try { ctx.ui.notify(m, l); } catch { // notify unavailable } }); settingsLocale.reload(); settingsRegistry.emitLocale(settingsLocale.locale); selectedHashWindowTargets.clear(); ctx.ui.addAutocompleteProvider((current) => createTargetAutocompleteProvider( createWindowAutocompleteProvider( current, () => config.enabled ? windowAutocompleteTargets() : [], (target) => selectedHashWindowTargets.set(target.token.toLocaleLowerCase("en"), target), ), async () => config.enabled ? targetCatalogue() : buildCockpitTargetCatalogue(endpoints.snapshot(), [], false), )); invalidateUsageCache(); // Native definitions are registered only after settings/ownership binding. // Legacy registration here is a fallback for a changed persisted config. if (config.quietMode && !quietToolsRegistered) { quietToolsRegistered = registerQuietTools(pi, () => config, VERSION, ctx.cwd); } if (config.quietMode) { try { ctx.ui.setHiddenThinkingLabel(quietThinkingLabel()); } catch { // non-TUI mode } } // The theme is deliberately NOT re-applied here. ctx.ui.setTheme writes // through to pi's own settings, so pi already restores the user's choice on // its own. Replaying cockpit's copy would overwrite whatever the user set // through /settings since — including an automatic "light/dark" pair, which // cockpit cannot represent and would silently flatten to a single theme. todos.hydrateFromEntries(ctx.sessionManager.getEntries()); if (config.enabled) acquireUiOwnership(ctx); else releaseUiOwnership(ctx); // applyUi captured the TUI synchronously, so the native toggle is // reachable already; a no-op when cockpit is disabled or non-TUI. if (config.quietMode) ensureThinkingFolded(capturedTui, ctx.cwd, true); pi.events.emit(BASH_BG_QUERY_EVENT, undefined); requestRuntimeSnapshot(); emitMaestroQuery(); if (config.usage.enabled) void usageSubsystem?.start(ctx); req(); }); pi.on("session_shutdown", (e, ctx) => { // Claude Code clears the terminal title on exit (CLEAR_TERMINAL_TITLE) so // the tab does not keep a stale title after the process dies. Only on // quit: reload/new/resume/fork are immediately followed by a new session // that sets its own title, and clearing would flash the tab. if (e.reason === "quit" && lastCtx && isTuiContext(lastCtx) && config.title.enabled) { try { lastCtx.ui.setTitle(""); } catch { // mid-teardown: best-effort } } if (lastCtx) releaseUiOwnership(lastCtx); else { uiOwnershipClaimed = false; publishUiOwnership(); } // A session boundary must tear down an overlay the host hid without // dispose (theme revert, deferred enable, promise settle) (MW-2). try { activeSettingsOverlay?.finalize(); } catch { // best effort } activeSettingsOverlay = undefined; // A session boundary must invalidate any in-flight title request: abort it // and bump the generation so its result is discarded (MW-3). titleAbort?.abort(); titleAbort = undefined; titleGeneration += 1; // Tear down shared-bus subscriptions and the independent query responder so // a host reload cannot accumulate listeners. session_start restores both. unsubscribeBusEvents(); disposeOwnershipQueryResponder(); settingsRegistry.dispose(); try { settingsProviderDisposer?.(); } catch { // best effort } settingsProviderDisposer = undefined; disconnectExternalAgentProjections(); endpoints.disconnect(); compactionStylePatch?.detach(); compactionStylePatch = undefined; lastCtx = undefined; pendingTargetReference = undefined; running = false; runningStartedAt = undefined; uiPromptDepth = 0; activeTools.clear(); thinkingTimer.reset(); invalidateUsageCache(); sessionUi.reset(); agentReads.clear(); agents = agentReads.current; usingRuntimeV2 = false; bashBg.clear(); maestro.clear(); usageSubsystem?.stop(ctx); }); pi.on("before_agent_start", async (event) => { const reference = pendingTargetReference; pendingTargetReference = undefined; if (!reference) return undefined; const context = formatPendingTargetContext(reference, await targetCatalogue()); return context ? { systemPrompt: `${event.systemPrompt}\n\n${context}` } : undefined; }); pi.on("agent_start", () => { running = true; runningStartedAt = Date.now(); assistantStreaming = false; tokenRate = undefined; ratePrev = undefined; startTick(); req(); }); pi.on("agent_end", () => { pendingTargetReference = undefined; running = false; runningStartedAt = undefined; assistantStreaming = false; tokenRate = undefined; ratePrev = undefined; activeTools.clear(); thinkingTimer.stop(); syncTick(); req(); }); pi.on("ui_prompt_start", () => { uiPromptDepth = nextUiPromptDepth(uiPromptDepth, "start"); req(); }); pi.on("ui_prompt_end", () => { const nextDepth = nextUiPromptDepth(uiPromptDepth, "end"); if (nextDepth === uiPromptDepth) return; uiPromptDepth = nextDepth; req(); }); // --- live elapsed for folded thinking rows --- pi.on("message_start", (e) => { if (e.message.role === "assistant") { thinkingTimer.onAssistantMessageStart(); assistantStreaming = true; tokenRate = undefined; ratePrev = undefined; } }); pi.on("message_update", (e) => { thinkingTimer.onAssistantMessageEvent(e.assistantMessageEvent); const output = e.message.role === "assistant" ? e.message.usage?.output : undefined; if (output === undefined || output <= 0) return; const now = Date.now(); if (ratePrev !== undefined && output > ratePrev.output && now > ratePrev.at) { tokenRate = formatTokenRate(((output - ratePrev.output) * 1000) / (now - ratePrev.at)); } ratePrev = { output, at: now }; }); // --- redraw triggers for the footer's live data --- pi.on("message_end", (e, ctx) => { // Settle an interrupted thinking run: message_end fires for aborted and // failed messages too, while the row is still mounted. if (e.message.role === "assistant") { thinkingTimer.onAssistantMessageEnd(); assistantStreaming = false; tokenRate = undefined; ratePrev = undefined; mainOutputRevision += 1; endpoints.setMainOutputRevision(`main:${mainOutputRevision}`); } invalidateUsageCache(); if (isTuiContext(ctx)) req(); }); pi.on("model_select", (_e, ctx) => { if (config.usage.enabled) usageSubsystem?.onModelSelect(ctx, ctx.model); if (isTuiContext(ctx)) req(); }); pi.on("session_compact", (_e, ctx) => { invalidateUsageCache(); if (isTuiContext(ctx)) req(); }); pi.on("session_info_changed", (_e, ctx) => { // A session rename (or the /session name set at startup) changes the // session summary the tab title leads with. if (isTuiContext(ctx)) req(); }); pi.on("thinking_level_select", (_e, ctx) => { if (isTuiContext(ctx)) req(); }); pi.on("input", async (e, ctx) => { const globals = globalThis as typeof globalThis & Record; const registry = sessionRegistry() ?? endpoints.registry; const mailboxRegistry = globals[MAILBOX_REGISTRY_KEY] as MailboxHostRegistry | undefined; const hasImages = (e.images?.length ?? 0) > 0; const interactiveText = e.source === "interactive" && e.text.trim().length > 0; if (config.enabled && e.source === "interactive") { pendingTargetReference = undefined; const canonical = await routeCanonicalTargetInput({ text: e.text, hasImages, getCatalogue: targetCatalogue, activateSsh: activateSshHost, send: async (request) => { const liveRegistry = sessionRegistry() ?? endpoints.registry; const delivery = await (liveRegistry?.send?.({ ...request, source: "user", }) ?? liveRegistry?.router?.route({ ...request, source: "user", })); return delivery ?? { delivered: false, error: tuiT("notice.deliveryRegistryUnavailable") }; }, notify: (message, type) => ctx.ui.notify(message, type), restore: (text) => { const selectedId = sessionUi.selectedId(sessionUi.mode); if (selectedId) sessionUi.setDraft(selectedId, text); ctx.ui.setEditorText(text); }, }); if (canonical?.action === "handled") return { action: "handled" as const }; if (canonical?.action === "transform") { pendingTargetReference = canonical.reference; if (!firstUserText) firstUserText = canonical.text; return { action: "transform" as const, text: canonical.text }; } } const isSynthetic = isLocalInputText(e.text); const hashWindowRoute = config.enabled && e.source === "interactive" && !isSynthetic ? resolveWindowRouteInput(e.text, windowAutocompleteTargets(), selectedHashWindowTargets) : undefined; if (hashWindowRoute) { if (hashWindowRoute.code !== "resolved") { ctx.ui.notify(tuiT(hashWindowRoute.code === "stale" ? "notice.windowTargetUnavailable" : "notice.windowTargetAmbiguous"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } if (!hashWindowRoute.message) { ctx.ui.notify(tuiT("notice.windowMessageRequired"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } if (hashWindowRoute.target.current) { selectedHashWindowTargets.delete(hashWindowRoute.token); if (!firstUserText) firstUserText = hashWindowRoute.message; return { action: "transform" as const, text: hashWindowRoute.message }; } if (hasImages) { ctx.ui.notify(tuiT("notice.imagePeer"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } if (!registry) { ctx.ui.notify(tuiT("notice.peerDeliveryUnavailable"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } const delivery = await (registry.send?.({ selector: hashWindowRoute.target.routeSelector, message: hashWindowRoute.message, mode: e.streamingBehavior === "steer" ? "steer" : "follow_up", source: "user", }) ?? registry.router?.route({ selector: hashWindowRoute.target.routeSelector, message: hashWindowRoute.message, mode: e.streamingBehavior === "steer" ? "steer" : "follow_up", source: "user", })); if (!delivery?.delivered) { ctx.ui.notify(tuiT("notice.peerMessageFailed", { label: hashWindowRoute.target.label, message: delivery?.error ?? tuiT("notice.deliveryRegistryUnavailable"), }), "error"); ctx.ui.setEditorText(e.text); } else { selectedHashWindowTargets.delete(hashWindowRoute.token); } return { action: "handled" as const }; } if (e.source === "interactive" && !hasImages && e.text.trim() === "monitor") { if (!registry?.requestWindowMode) { ctx.ui.notify(tuiT("notice.monitorUnavailable"), "warning"); return { action: "handled" as const }; } await registry.requestWindowMode("enter"); return { action: "handled" as const }; } if (config.enabled && sessionUi.mode === "window" && (interactiveText || hasImages) && !isSynthetic) { const target = selectedWindowEndpoint(); if (!target) { ctx.ui.notify(tuiT("notice.noMonitorWindow"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } if (isMonitorControlEndpoint(target)) { sessionUi.setDraft(target.id, ""); if (!firstUserText && interactiveText) firstUserText = e.text; return { action: "continue" as const }; } if (hasImages) { ctx.ui.notify(tuiT("notice.imagePeer"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } if (!registry) { ctx.ui.notify(tuiT("notice.peerDeliveryUnavailable"), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } sessionUi.setDraft(target.id, e.text); const delivery = await (registry.send?.({ selector: target.routeSelector, message: e.text, mode: e.streamingBehavior === "steer" ? "steer" : "follow_up", source: "user", }) ?? registry.router?.route({ selector: target.routeSelector, message: e.text, mode: e.streamingBehavior === "steer" ? "steer" : "follow_up", source: "user", })); if (!delivery?.delivered) { ctx.ui.notify(tuiT("notice.peerMessageFailed", { label: target.label, message: delivery?.error ?? tuiT("notice.deliveryRegistryUnavailable"), }), "error"); ctx.ui.setEditorText(e.text); } else { sessionUi.setDraft(target.id, ""); } return { action: "handled" as const }; } const selectedReadOnlyAgent = sessionUi.mode === "agent" && selectedEndpoint()?.readOnly === true ? selectedEndpoint() : undefined; if (selectedReadOnlyAgent && (interactiveText || hasImages) && !isSynthetic) { sessionUi.setDraft(selectedReadOnlyAgent.id, e.text); ctx.ui.notify(tuiT("notice.externalAgentReadOnly", { label: selectedReadOnlyAgent.label }), "warning"); ctx.ui.setEditorText(e.text); return { action: "handled" as const }; } const target = selectedAgentTarget(); let restored = false; if (target && interactiveText && !isSynthetic) sessionUi.setDraft(target.endpoint.id, e.text); const action = await routeAgentInput( e, target ? { correlationId: target.endpoint.correlationId!, label: target.label, endpointId: target.endpoint.id, routeSelector: target.endpoint.routeSelector, } : undefined, { sessions: registry, mailbox: mailboxRegistry }, { notify: (message, type) => ctx.ui.notify(message, type), setEditorText: (text) => { restored = true; if (target) sessionUi.setDraft(target.endpoint.id, text); ctx.ui.setEditorText(text); }, }, ); if (action === "handled" && target && !restored) sessionUi.setDraft(target.endpoint.id, ""); if (action === "handled") return { action: "handled" as const }; const selectedId = sessionUi.selectedId("agent"); if (selectedId && interactiveText && !isSynthetic) sessionUi.setDraft(selectedId, ""); if (!firstUserText && interactiveText && !isSynthetic) firstUserText = e.text; return { action: "continue" as const }; }); pi.on("turn_end", (e, ctx) => { // Claude Code generates its title from the first user message; we wait // for the first turn to settle so the title can reflect what the model // actually did, then ask the configured model (or the rule fallback). if (titleRequested || !firstUserText || !isTuiContext(ctx)) return; titleRequested = true; void generateSessionTitle(ctx, firstUserText, e.message, () => req()); }); const enterCapturingOverlay = (): void => { capturingOverlayActive = true; zenBrowseController?.end(); req(); try { sidebarController?.cancelResize(); } catch { // Resize cancellation is best effort; the overlay must still open. } }; const exitCapturingOverlay = (): void => { capturingOverlayActive = false; flushDeferredSidebarSync(); req(); }; const openZenSheet = async ( ctx: ExtensionContext, getDocument: () => ZenSheetDocument | undefined, ): Promise => { if (!ctx.hasUI || capturingOverlayActive) return; let ownedSheet: { finalize(): void } | undefined; enterCapturingOverlay(); try { await ctx.ui.custom((tui, theme, _kb, done) => { let settled = false; const finalize = (): void => { if (settled) return; settled = true; done(undefined); }; ownedSheet = { finalize }; activeZenSheet = ownedSheet; return new ZenSheet({ getDocument, requestRender: () => tui.requestRender(), close: finalize, theme, glyphs: resolveGlyphs(config.icons.mode), getTerminalRows: () => terminalRows(tui), }); }, { overlay: true, overlayOptions: { anchor: "center", width: "72%", maxHeight: "85%" }, }); } finally { if (activeZenSheet === ownedSheet) activeZenSheet = undefined; exitCapturingOverlay(); } }; const activateZenRow = async (ctx: ExtensionContext, id: string): Promise => { if (id === "mission") { await openZenSheet(ctx, () => buildZenMissionSheet(maestro.snapshot())); return; } if (id === "run") { await openZenSheet(ctx, () => buildZenRunSheet(maestro.snapshot())); return; } if (id === "swarm") { await openZenSheet(ctx, () => buildZenSwarmSheet(maestro.snapshot())); return; } if (id.startsWith("task:")) { const taskId = id.slice("task:".length); await openZenSheet(ctx, () => buildZenTaskSheet(todos.snapshot().find((item) => item.id === taskId))); return; } if (id.startsWith("agent:")) { const correlationId = id.slice("agent:".length); selectAgent(correlationId); await openAgentOverlay(ctx, correlationId); return; } if (id.startsWith("job:")) { await openBashBgOverlay(ctx, id.slice("job:".length)); } }; const openAgentOverlay = async (ctx: ExtensionContext, initialCorrelationId?: string): Promise => { if (!ctx.hasUI) return; if (capturingOverlayActive) { ctx.ui.notify(tuiT("notice.closeOverlayAgents"), "warning"); return; } if (visibleAgentRows(agents.snapshot()).length === 0) { ctx.ui.notify(tuiT("notice.noAgents"), "info"); return; } if (initialCorrelationId) selectAgent(initialCorrelationId); let ownedOverlay: { finalize(): void } | undefined; let ownedRender: (() => void) | undefined; enterCapturingOverlay(); try { await ctx.ui.custom((tui, theme, _kb, done) => { let settled = false; const finalize = (): void => { if (settled) return; settled = true; done(undefined); }; ownedOverlay = { finalize }; ownedRender = () => tui.requestRender(); activeAgentOverlay = ownedOverlay; activeAgentOverlayRender = ownedRender; return new AgentOverlay({ getAgents: () => agents.snapshot(), getViewingId: () => initialCorrelationId ?? selectedAgentCorrelationId(), onSelect: (correlationId) => selectAgent(correlationId), onTarget: (correlationId) => selectAgent(correlationId), onCommand: (correlationId, action, message) => { pi.events.emit(TEAMMATE_AGENT_COMMAND_EVENT, { correlationId, action, ...(message !== undefined ? { message } : {}) }); }, requestRender: ownedRender, close: finalize, theme, glyphs: resolveGlyphs(config.icons.mode), getTerminalRows: () => terminalRows(tui), }); }, { overlay: true, overlayOptions: { anchor: "center", width: "94%", maxHeight: "90%" }, }); } finally { if (activeAgentOverlay === ownedOverlay) activeAgentOverlay = undefined; if (activeAgentOverlayRender === ownedRender) activeAgentOverlayRender = undefined; exitCapturingOverlay(); } }; const openSessionList = async (ctx: ExtensionContext): Promise => { if (!ctx.hasUI) return; if (sessionListOverlayActive()) { ctx.ui.notify(tuiT("notice.closeOverlaySessions"), "warning"); return; } const snapshot = endpoints.snapshot(); const mode = sessionUi.mode; const allEntries = mode === "window" ? [...snapshot.windows] : [...snapshot.endpoints]; if (allEntries.length === 0) { ctx.ui.notify(tuiT(mode === "window" ? "notice.noWindows" : "notice.noAgents"), "info"); return; } const selectedId = sessionUi.selectedId(mode); const selectedIndex = allEntries.findIndex((endpoint) => endpoint.id === selectedId); const entries = selectedIndex > 0 ? [allEntries[selectedIndex]!, ...allEntries.slice(0, selectedIndex), ...allEntries.slice(selectedIndex + 1)] : allEntries; const choices = entries.map((endpoint) => { const current = endpoint.id === selectedId ? tuiStatus("selected") : ""; const agentCount = mode === "window" && endpoint.agentCount !== undefined ? tuiT("common.agents", { count: endpoint.agentCount }) : ""; const detail = [current, tuiStatus(endpoint.status), agentCount].filter(Boolean).join(" · "); const sigil = mode === "window" ? "#" : "@"; const showOwner = mode === "window" || endpoint.kind === "root"; const label = showOwner && endpoint.registryEndpoint ? ownerDisplayToken(endpoint.label, endpoint.registryEndpoint.ownerId) : endpoint.label; return `${sigil}${label}${detail ? ` · ${detail}` : ""}`; }); let previewAgent = false; enterCapturingOverlay(); try { const selected = await ctx.ui.select( mode === "window" ? tuiT("window.title") : tuiT("overlay.agents.title"), choices, ); const index = selected === undefined ? -1 : choices.indexOf(selected); const endpoint = index >= 0 ? entries[index] : undefined; if (endpoint) { if (mode === "window") selectWindow(endpoint.id); else { selectEndpoint(endpoint.id); previewAgent = endpoint.kind === "agent" && visibleAgentRows(agents.snapshot()).some((row) => row.correlationId === endpoint.correlationId); } } } finally { exitCapturingOverlay(); } if (previewAgent) await openAgentOverlay(ctx); }; pi.events.on(COCKPIT_SESSION_LIST_EVENT, (payload) => { if (!payload || typeof payload !== "object" || (payload as { version?: unknown }).version !== 1) return; const ctx = lastCtx; if (!config.enabled || !ctx || !isTuiContext(ctx)) return; void openSessionList(ctx).catch((error) => { ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning"); }); }); const openBashBgOverlay = async (ctx: ExtensionContext, initialJobId?: string): Promise => { if (!ctx.hasUI) return; let ownedOverlay: BashBgOverlay | undefined; enterCapturingOverlay(); try { await ctx.ui.custom((tui, theme, _kb, done) => { ownedOverlay = new BashBgOverlay({ getJobs: () => bashBg.snapshot(), requestRender: () => tui.requestRender(), requestRefresh: () => pi.events.emit(BASH_BG_QUERY_EVENT, undefined), close: () => done(undefined), theme, glyphs: resolveGlyphs(config.icons.mode), now: Date.now(), hideLiveDuration: config.staticMode, ...(initialJobId ? { initialJobId } : {}), getTerminalRows: () => terminalRows(tui), }); activeBashBgOverlay = ownedOverlay; return ownedOverlay; }, { overlay: true, overlayOptions: { anchor: "center", width: "92%", maxHeight: "90%" }, }); } finally { if (activeBashBgOverlay === ownedOverlay) activeBashBgOverlay = undefined; exitCapturingOverlay(); } }; const openTodoOverlay = async (ctx: ExtensionContext, initialTodoId?: string): Promise => { if (!ctx.hasUI) return; let ownedOverlay: TodoOverlay | undefined; enterCapturingOverlay(); try { await ctx.ui.custom((tui, theme, _kb, done) => { ownedOverlay = new TodoOverlay({ getTodos: () => todos.snapshot(), requestRender: () => tui.requestRender(), close: () => done(undefined), theme, glyphs: resolveGlyphs(config.icons.mode), ...(initialTodoId ? { initialTodoId } : {}), getTerminalRows: () => terminalRows(tui), }); activeTodoOverlay = ownedOverlay; return ownedOverlay; }, { overlay: true, overlayOptions: { anchor: "center", width: "92%", maxHeight: "90%" }, }); } finally { if (activeTodoOverlay === ownedOverlay) activeTodoOverlay = undefined; exitCapturingOverlay(); } }; // --- /theme: native settings navigation; the preview picker is legacy-only --- const makeThemePicker = ( ctx: ExtensionContext, tui: TUI, theme: Theme, close: () => void, ): ThemePicker => { const active = activeThemeName(theme); return new ThemePicker({ themes: ctx.ui.getAllThemes().map((t) => t.name), // Cockpit's own record is only a fallback: pi's live theme is the truth, // and /settings can have changed it since cockpit last wrote anything. initial: active ?? config.theme, // Resolve a real instance by name. Passing `theme` straight through would // store the Proxy into the very slot the Proxy reads from, and the next // colour lookup would recurse until the stack blew. original: active ? ctx.ui.getTheme(active) : undefined, loadTheme: (name) => ctx.ui.getTheme(name), // Instance form: applies in memory without writing through to pi's // settings, so scrolling the whole list costs the user nothing. previewTheme: (instance) => { ctx.ui.setTheme(instance); }, // Name form: the one call that persists. commitTheme: (name) => { const applied = ctx.ui.setTheme(name); if (applied.success) { config = { ...config, theme: name }; saveConfig(config); } return applied; }, close, requestRender: () => tui.requestRender(), getTerminalRows: () => terminalRows(tui), theme, glyphs: resolveGlyphs(config.icons.mode), }); }; const openThemePicker = async (ctx: ExtensionContext): Promise => { if (!ctx.hasUI || navigateHostThemeSettings(ctx)) return; enterCapturingOverlay(); try { await ctx.ui.custom((tui, theme, _kb, done) => makeThemePicker(ctx, tui, theme, () => done(undefined)), { overlay: true, overlayOptions: { anchor: "center", width: "60%", maxHeight: "90%" }, }); } finally { exitCapturingOverlay(); } }; // pi has no /theme command — the built-in picker is a submenu of /settings. // Native /settings owns the theme and automatic light/dark pairing. The // legacy shortcut retains its picker; it is never entered on unknown hosts. pi.registerCommand("theme", { description: "Theme settings — native /settings navigation; legacy hosts support preview and /theme ", getArgumentCompletions: (prefix) => { if (getPiHostMode(VERSION) !== "legacy") return null; const query = prefix.trim().toLowerCase(); const ctx = lastCtx; if (!ctx || !ctx.hasUI) return null; const matches = ctx.ui.getAllThemes() .map((t) => t.name) .filter((name) => name.toLowerCase().includes(query)); return matches.length > 0 ? matches.map((name) => ({ value: name, label: name })) : null; }, handler: async (args, ctx) => { if (!ctx.hasUI) return; if (navigateHostThemeSettings(ctx)) return; const name = args.trim(); if (name === "") { await openThemePicker(ctx); return; } const applied = ctx.ui.setTheme(name); if (!applied.success) { ctx.ui.notify(tuiT("notice.themeUnavailable", { name, message: applied.error ?? tuiT("notice.notFound"), }), "warning"); return; } config = { ...config, theme: name }; saveConfig(config); ctx.ui.notify(tuiT("notice.themeSelected", { name }), "info"); }, }); const setSidebarMode = ( ctx: ExtensionContext, mode: CockpitConfig["sidebar"]["mode"], ): void => { if (config.sidebar.mode === mode) { ctx.ui.notify(tuiT("notice.sidebarMode", { mode: tuiT(`common.value.${mode}`) }), "info"); return; } config = { ...config, sidebar: { ...config.sidebar, mode } }; const result = saveConfig(config); if (!result.ok) { ctx.ui.notify(tuiT("notice.sidebarModeSaveFailed", { message: result.error ?? tuiT("notice.unknownError"), }), "warning"); } else if (configProblem?.startsWith("sidebar width save failed:")) { configProblem = undefined; } if (config.enabled && isTuiContext(ctx)) syncSidebarMode(ctx); publishUiOwnership(); req(); }; const setStaticMode = (target: boolean): boolean => { if (config.staticMode === target) return true; config = { ...config, staticMode: target }; const result = saveConfig(config); if (!result.ok) { configProblem = `static mode save failed: ${result.error ?? "unknown error"}`; } else if (configProblem?.startsWith("static mode save failed:")) { configProblem = undefined; } // Restart or stop the animation loop for the new mode: turning static on // must kill a running tick, turning it off must revive one mid-run. syncTick(); thinkingTimer.syncMode(); // Broadcast so cross-extension surfaces (e.g. pi-maestro-flow's Goal // panel) freeze their per-second elapsed ticks along with the cockpit. publishUiOwnership(); req(); return result.ok; }; const cockpitSettingsProvider = createCockpitSettingsProvider({ getRuntimeConfig: () => config, applyRuntimeConfig: async (nextConfig, changedKeys) => { const previous = config; config = nextConfig; const ctx = lastCtx; if (!ctx || !isTuiContext(ctx)) return; if (previous.enabled !== nextConfig.enabled) { if (nextConfig.enabled) acquireUiOwnership(ctx); else releaseUiOwnership(ctx); return; } if (!nextConfig.enabled) return; if (previous.staticMode !== nextConfig.staticMode) { syncTick(); thinkingTimer.syncMode(); } if (previous.quietMode !== nextConfig.quietMode) { applyQuietMode(ctx, previous.quietMode, nextConfig.quietMode); } let ownershipPublished = false; if (previous.hideNativeAgents !== nextConfig.hideNativeAgents) { if (nextConfig.hideNativeAgents) { publishUiOwnership(); ownershipPublished = true; if (!syncActiveAgentSurface()) return; } else { if (!syncActiveAgentSurface()) return; publishUiOwnership(); ownershipPublished = true; } } if (previous.pinEditorBottom !== nextConfig.pinEditorBottom) { // pinEditorBottom is ignored inside fullscreen (fullscreen owns the fixed // editor); the gate also covers a live toggle while fullscreen is active. if (nextConfig.pinEditorBottom && !nextConfig.fullscreenInput) installEditorBottom(ctx); else clearEditorBottom(ctx); } if (changedKeys.some((key) => key.startsWith("sidebar."))) syncSidebarMode(ctx); if (previous.stackStyle !== nextConfig.stackStyle && nextConfig.stackStyle !== "zen") { zenBrowseController?.end(); zenNavRows = []; } if (changedKeys.some((key) => key.startsWith("usage."))) { // Master switch: stop the poller when disabled, (re)start when enabled. if (previous.usage.enabled !== nextConfig.usage.enabled) { if (nextConfig.usage.enabled) void usageSubsystem?.start(ctx); else usageSubsystem?.stop(ctx); } // footer/footer-toggle/barWidth change only the rendered string (the // next render reads them fresh); pollIntervalMs change restarts the // poller (0 switches to manual refresh: no timer, no footer bar). if (previous.usage.pollIntervalMs !== nextConfig.usage.pollIntervalMs && nextConfig.usage.enabled) { usageSubsystem?.rescheduleTimer(); usageSubsystem?.refresh(); } } if (!ownershipPublished) publishUiOwnership(); req(); }, getThemeName: () => config.theme || undefined, getThinkingFolded: () => lastCtx ? readHideThinkingBlock(lastCtx.cwd) : undefined, openLegacySettings: async () => { if (settingsCommandCtx) await openSettings(settingsCommandCtx); }, openThemeSettings: async () => { if (settingsCommandCtx) await openThemePicker(settingsCommandCtx); }, toggleThinkingFold: () => { if (!lastCtx) return false; const next = !readHideThinkingBlock(lastCtx.cwd); ensureThinkingFolded(capturedTui, lastCtx.cwd, next); return next; }, }); // The settings provider registers on the first session (registerSettingsProvider // in session_start); its disposer is stored for teardown at session_shutdown. const nativePiSettingsProvider = createNativePiSettingsProvider({ getThemes: () => (lastCtx?.ui.getAllThemes?.() ?? []).map((theme) => theme.name), }); pi.registerShortcut(BASH_BG_OVERLAY_KEY, { description: "Open background Bash jobs — live status, command, cwd, duration and output tail", async handler(ctx) { await openBashBgOverlay(ctx); }, }); pi.registerShortcut(TODO_OVERLAY_KEY, { description: "Open the Todo center — scrollable full list with status, assignee, skills and blocked-by dependencies", async handler(ctx) { if (!config.enabled) { ctx.ui.notify(tuiT("notice.cockpitDisabled"), "warning"); return; } await openTodoOverlay(ctx); }, }); pi.registerShortcut(SIDEBAR_RESIZE_KEY, { description: "Resize the Cockpit sidebar", handler(ctx) { if (!config.enabled) { ctx.ui.notify(tuiT("notice.cockpitDisabled"), "warning"); return; } if (capturingOverlayActive) { ctx.ui.notify(tuiT("notice.closeOverlayResize"), "warning"); return; } sidebarController?.beginResize(); }, }); pi.registerShortcut(SESSION_DETAIL_TOGGLE_KEY, { description: "Show or hide the selected teammate session above Todo", handler(ctx) { if (!config.enabled) { ctx.ui.notify(tuiT("notice.cockpitDisabled"), "warning"); return; } const endpoint = selectedEndpoint(); if (!endpoint) return; const visible = sessionUi.toggleDetail(endpoint.id); ctx.ui.notify(tuiT(visible ? "notice.sessionDetailShown" : "notice.sessionDetailHidden"), "info"); req(); }, }); pi.registerShortcut(SIDEBAR_FOCUS_KEY, { description: "Browse the visible Cockpit surface with the keyboard (↑↓/j/k/Home/End, Enter, Esc)", handler(ctx) { if (!config.enabled) { ctx.ui.notify(tuiT("notice.cockpitDisabled"), "warning"); return; } if (capturingOverlayActive) { ctx.ui.notify(tuiT("notice.closeOverlayBrowse"), "warning"); return; } if (config.stackStyle === "zen" && surfaceState === "widgets") { ensureZenBrowseController(ctx).begin(); return; } sidebarController?.beginFocus(); }, }); pi.registerCommand("maestro-settings", { description: "Open unified Maestro settings for Cockpit, Flow, Teammate and integrations", async handler(_args, ctx) { if (!ctx.hasUI) { ctx.ui.notify(tuiT("notice.settingsRequiresTui"), "warning"); return; } settingsCommandCtx = ctx; enterCapturingOverlay(); try { await showMaestroSettingsShell(ctx, settingsRegistry, settingsLocale); } finally { settingsCommandCtx = undefined; exitCapturingOverlay(); } }, }); // --- /cockpit: legacy settings plus direct surface controls --- pi.registerCommand("cockpit", { description: "Open pi-cockpit settings; /cockpit agents opens the live Agent panel", getArgumentCompletions: (prefix) => { const query = prefix.trim().toLowerCase(); const candidates = [ "quiet", "quiet on", "quiet off", "agents", "bg", "jobs", "todo", "todo expand", "todo collapse", "sidebar", "sidebar auto", "sidebar on", "sidebar off", "sidebar resize", "static", "static on", "static off", ]; const matches = candidates.filter((c) => c.startsWith(query)); return matches.length > 0 ? matches.map((c) => ({ value: c, label: c })) : null; }, handler: async (args, ctx) => { if (!ctx.hasUI) return; const action = args.trim().toLowerCase(); if (action === "agents" || action === "agent") { await openAgentOverlay(ctx); return; } if (action === "bg" || action === "jobs") { await openBashBgOverlay(ctx); return; } if (action === "sidebar") { ctx.ui.notify( tuiT("notice.sidebarDetails", { mode: tuiT(`common.value.${config.sidebar.mode}`), width: config.sidebar.width, density: tuiT(`common.value.${config.sidebar.density}`), }), "info", ); return; } if (action === "sidebar resize") { if (!config.enabled) ctx.ui.notify(tuiT("notice.cockpitDisabled"), "warning"); else sidebarController?.beginResize(); return; } if (action === "sidebar auto" || action === "sidebar on" || action === "sidebar off") { setSidebarMode(ctx, action.slice("sidebar ".length) as CockpitConfig["sidebar"]["mode"]); return; } if (action === "todo" || action === "todo toggle") { setTodoExpanded(!effectiveTodoExpanded()); ctx.ui.notify(tuiT(config.todoExpanded ? "notice.todoExpanded" : "notice.todoCollapsed"), "info"); return; } if (action === "todo expand" || action === "todo collapse") { setTodoExpanded(action.endsWith("expand")); return; } if (action === "quiet" || action === "quiet toggle") { const was = config.quietMode; config = { ...config, quietMode: !was }; saveConfig(config); applyQuietMode(ctx, was, config.quietMode); req(); return; } if (action === "quiet on" || action === "quiet off") { const target = action === "quiet on"; if (config.quietMode === target) return; const was = config.quietMode; config = { ...config, quietMode: target }; saveConfig(config); applyQuietMode(ctx, was, target); req(); return; } if (action === "static" || action === "static toggle") { const ok = setStaticMode(!config.staticMode); ctx.ui.notify( ok ? tuiT(config.staticMode ? "notice.staticOn" : "notice.staticOff") : tuiT("notice.staticSaveFailed"), ok ? "info" : "warning", ); return; } if (action === "static on" || action === "static off") { const target = action === "static on"; if (config.staticMode === target) return; const ok = setStaticMode(target); ctx.ui.notify( ok ? tuiT(target ? "notice.staticOn" : "notice.staticOff") : tuiT("notice.staticSaveFailed"), ok ? "info" : "warning", ); return; } await openSettings(ctx); }, }); // /usage (command key is configurable via config.usage.commandKey; registered // at session_start so a commandKey change takes effect after /reload). const registerUsageCommand = (): void => { const key = config.usage.commandKey || "usage"; pi.registerCommand(key, { description: "Show quota, balance, and spend for configured usage providers", handler: async (_args, ctx) => { if (!config.usage.enabled) { if (ctx.hasUI) ctx.ui.notify(tuiT("notice.usageDisabled"), "warning"); return; } await usageSubsystem?.openCommand(ctx); }, }); }; registerUsageCommand(); pi.registerCommand("supervision", { description: "Supervision: /supervision [events] — unified goal/monitor/advisor telemetry", handler: async (args, ctx) => { const totals = supervision.getTotals(); const wantEvents = args.trim().toLowerCase() === "events"; if (!wantEvents) { const status = supervision.footerStatus(); ctx.ui.notify( [ `SUPERVISION ${status ?? tuiT("supervision.idle")}`, tuiT("supervision.counts", { interventions: totals.interventions, notifications: totals.notifications, verdicts: totals.verdicts, }), tuiT("supervision.help"), ].join("\n"), "info", ); return; } const recent = supervision.recentEvents(10); if (recent.length === 0) { ctx.ui.notify(tuiT("supervision.none"), "info"); return; } const lines = recent.map((event) => { const time = new Date(event.timestamp).toLocaleTimeString(); const marker = event.severity === "blocker" ? "▲" : event.severity === "concern" ? "△" : "·"; const message = (event.message ?? event.kind).slice(0, 100); return `${marker} [${event.source}] ${event.kind} ${time} — ${message}`; }); ctx.ui.notify([tuiT("supervision.recent", { count: recent.length }), ...lines].join("\n"), "info"); }, }); const openSettings = async (ctx: ExtensionCommandContext): Promise => { enterCapturingOverlay(); try { await ctx.ui.custom((tui, theme, _kb, done) => { let settingsCursor = 0; let saveState: SaveState = { kind: "idle" }; let enableAfterClose = false; let settled = false; // Non-null while a text row is being edited: keystrokes build the draft, // Enter commits it through apply(), Esc reverts — the same cancel // contract the theme picker offers, scoped to the single row. let editingText: string | null = null; let textDraft = ""; // Mirror of pi's hideThinkingBlock, read when the panel opens. The // overlay captures input, so Ctrl+T cannot flip it while this is open // and the mirror stays exact until the panel closes. let thinkingHidden = readHideThinkingBlock(ctx.cwd); // The theme row expands in place rather than closing the panel and opening // a second overlay: pi's `theme` is a live Proxy, so both surfaces repaint // in the previewed colours, and Esc lands back on the row it was invoked // from instead of on a panel that looks freshly opened. let sub: ThemePicker | ModelPicker | undefined; const closeSub = (): void => { sub?.dispose(); sub = undefined; tui.requestRender(); }; // Persisting apply shared by cycle rows and the model picker: commits the // row to config, saves it, and runs the live side effects (quiet mode, // sidebar, enable-after-close). apply() routes row-specific hand-offs // first and only falls through here for rows the panel itself owns. const persist = (key: string, textValue?: string): void => { const wasEnabled = config.enabled; const wasQuiet = config.quietMode; const wasStatic = config.staticMode; const wasPinEditorBottom = config.pinEditorBottom; const wasSidebarMode = config.sidebar.mode; config = applyRow(config, key, textValue); saveState = { kind: "saving" }; const result = saveConfig(config); // The panel now reports what actually happened instead of showing // an optimistic value for a write that may never have landed. saveState = result.ok ? { kind: "saved" } : { kind: "failed", message: result.error ?? "unknown error" }; if (result.ok && configProblem?.startsWith("sidebar width save failed:")) { configProblem = undefined; } if (wasEnabled !== config.enabled) { if (config.enabled) { // Creating a non-capturing sidebar while this capturing settings // overlay is still open would put it on top; settings done() would // then close the sidebar. Acquire/install only after settings disposes. enableAfterClose = true; } else { enableAfterClose = false; releaseUiOwnership(ctx); } } else { if (config.enabled && wasPinEditorBottom !== config.pinEditorBottom) { // Fullscreen owns the fixed editor; the legacy pin stays inert there. if (config.pinEditorBottom && !config.fullscreenInput) installEditorBottom(ctx); else clearEditorBottom(ctx); } else if (config.enabled && wasSidebarMode !== config.sidebar.mode) { syncSidebarMode(ctx); } publishUiOwnership(); } if (wasQuiet !== config.quietMode) { applyQuietMode(ctx, wasQuiet, config.quietMode); } if (wasStatic !== config.staticMode) { syncTick(); thinkingTimer.syncMode(); } req(); }; // The model row expands like the theme row: the picker owns the whole // card, Enter saves through persist(), Esc lands back on the panel. The // entries are the /api-manager providers, so what is offered here is // exactly what title-llm can resolve at generation time. const makeModelPicker = (): ModelPicker => { const entries: ModelPickerEntry[] = [ { kind: "model", ref: "", label: tuiT("common.ruleBased") }, ...ctx.modelRegistry.getAvailable().map((m) => ({ kind: "model" as const, ref: `${m.provider}/${m.id}`, label: `${m.provider}/${m.id}`, })), { kind: "custom", label: tuiT("common.customRef") }, ]; return new ModelPicker({ entries, initial: config.title.generationModel ?? "", commit: (ref) => { persist("titleGenerationModel", ref); }, requestCustom: () => { // Hand off to the existing free-text editor so a ref that is not // on the list (yet) can still be typed; resolution against the // registry still happens at generation time. The picker closes // itself after this callback, so the panel repaints into // text-edit mode. editingText = "titleGenerationModel"; textDraft = config.title.generationModel ?? ""; }, close: closeSub, requestRender: () => tui.requestRender(), getTerminalRows: () => terminalRows(tui), theme, glyphs: resolveGlyphs(config.icons.mode), }); }; const apply = (key: string, textValue?: string): void => { if (key === "theme") { if (getPiHostMode(VERSION) !== "legacy") { finalize(); navigateHostThemeSettings(ctx); return; } // Delegate: the picker previews live and reverts on Esc, neither of // which a blind one-key cycle through the name list can do. sub = makeThemePicker(ctx, tui, theme, closeSub); tui.requestRender(); return; } if (key === "thinkingFold") { // Pass-through row: pi owns hideThinkingBlock, so bring it to the // wanted state through the native toggle instead of saving config. // pi persists the flip synchronously through its settingsManager. const target = !thinkingHidden; const ok = ensureThinkingFolded(tui, ctx.cwd, target); if (ok) thinkingHidden = target; saveState = ok ? { kind: "saved" } : { kind: "failed", message: "editor unreachable" }; return; } if (key === "titleGenerationModel") { sub = makeModelPicker(); tui.requestRender(); return; } persist(key, textValue); }; // Idempotent teardown shared by component.dispose() and session shutdown: // the host hides a stale overlay via hideOverlay() without calling // component.dispose(), which would strand a previewed theme, skip the // deferred UI re-install, and leave the custom() promise unsettled (MW-2). const finalize = (): void => { if (settled) return; settled = true; try { sub?.handleInput("\x1b"); } catch { // best effort } sub?.dispose(); if (enableAfterClose && config.enabled) { queueMicrotask(() => { if (lastCtx !== ctx || !config.enabled) return; acquireUiOwnership(ctx); req(); }); } done(undefined); }; activeSettingsOverlay = { finalize }; const ui = { render(width: number): string[] { // The sub-view takes the whole card. It carries its own title and // key hints, so stacking the panel's chrome above it would spend two // rows saying nothing the picker does not already say. if (sub) return sub.render(width); const paint: PaintTheme = theme; const rows = buildRows(config, { thinkingHidden }); settingsCursor = Math.max(0, Math.min(settingsCursor, rows.length - 1)); const w = Math.min(width, 52); const labelWidth = Math.max(...rows.map((r) => visibleWidth(r.label))); const lines = [ paint.fg("text", "pi-cockpit"), paint.fg("borderMuted", "─".repeat(w)), ]; rows.forEach((row, index) => { const selected = index === settingsCursor; const editing = editingText === row.key; const marker = editing ? paint.fg("accent", "›") : selected ? paint.fg("accent", "›") : " "; const pad = " ".repeat(Math.max(0, labelWidth - visibleWidth(row.label))); const label = paint.fg(selected ? "text" : "muted", row.label) + pad; // Text rows show the live draft plus a block cursor while editing; // the stored value is only committed on Enter, so Esc reverts cleanly. const shownValue = editing ? textDraft : row.value; const value = paint.fg("accent", shownValue + (editing ? "▏" : "")); // Cycle rows advertise their next value; the text row shows its edit // affordance, and while editing the commit keys replace the hints. const hint = editing ? paint.fg("dim", tuiT("legacy.selectedEditHelp")) : selected ? paint.fg("dim", ` → ${row.next}`) : ""; lines.push(`${marker} ${paint.fg("dim", row.accel)} ${label} ${value}${hint}`); }); lines.push(""); if (saveState.kind === "saved") lines.push(paint.fg("success", tuiT("legacy.saved"))); else if (saveState.kind === "saving") lines.push(paint.fg("dim", tuiT("legacy.saving"))); else if (saveState.kind === "failed") { lines.push(paint.fg("error", tuiT("legacy.saveFailed", { message: saveState.message }))); // Scoped to cockpit's own rows: the theme is applied through pi, // which persists it regardless of whether this file was written. lines.push(paint.fg("dim", tuiT("legacy.sessionOnly"))); } // The theme row opens /theme; /settings additionally pairs a light // and a dark theme, which neither of cockpit's surfaces can express. // Thinking fold is pi's setting too (Ctrl+T), mirrored live here. lines.push(paint.fg("dim", tuiT("legacy.storageHint"))); lines.push(paint.fg("dim", tuiT("legacy.themePairHint"))); lines.push( editingText ? paint.fg("dim", tuiT("legacy.editHelp")) : paint.fg("dim", tuiT("legacy.help")), ); return lines.map((line) => truncateToWidth(line, width, "…")); }, invalidate(): void {}, handleInput(data: string): void { // While expanded the sub-view owns every key, Esc included — that is // what makes Esc step back to the panel instead of dismissing both. if (sub) { sub.handleInput(data); tui.requestRender(); return; } const rows = buildRows(config, { thinkingHidden }); // Text edit mode: every key types into the draft except the // commit/cancel trio. Multi-byte sequences (arrows, alt chords) // are dropped so escape bytes cannot enter a model ref. if (editingText !== null) { if (matchesKey(data, Key.escape)) { editingText = null; } else if (data === "\r" || data === "\n") { const key = editingText; editingText = null; apply(key, textDraft); } else if (data === "\x7f" || data === "\x08") { textDraft = textDraft.slice(0, -1); } else if (data.length === 1 && data >= " ") { textDraft += data; } tui.requestRender(); return; } if (matchesKey(data, Key.escape)) { done(undefined); return; } if (matchesKey(data, Key.up) || matchesKey(data, Key.down)) { const delta = matchesKey(data, Key.up) ? -1 : 1; settingsCursor = (settingsCursor + delta + rows.length) % rows.length; } else if (matchesKey(data, Key.enter) || data === " " || decodeKittyPrintable(data) === " ") { const row = rows[settingsCursor]; if (row.kind === "info") { // read-only status row — nothing to apply } else if (row.kind === "text") { // Enter on a text row opens the editor with the stored value // (not the "(rule-based)" display string) as the draft. editingText = row.key; textDraft = config.title.generationModel ?? ""; } else { apply(row.key); } } else { const key = rowKeyForAccel(rows, data); if (!key) return; settingsCursor = rows.findIndex((row) => row.key === key); const row = rows[settingsCursor]; if (row.kind === "text") { editingText = row.key; textDraft = config.title.generationModel ?? ""; } else { apply(key); } } tui.requestRender(); }, dispose(): void { // Closing the panel while expanded must not strand a previewed theme // with no way back, so route through the picker's own cancel path. finalize(); }, }; return ui; }, { overlay: true }); } finally { exitCapturingOverlay(); activeSettingsOverlay = undefined; } }; }