mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* fix(terminal): the PTY owner answers OSC 10/11 for the terminal's whole life Codex and Claude's `theme: auto` ask the terminal for its foreground and background colours (OSC 10/11) and pick their colours from the reply. Orca answered in the process that owns the PTY only for agent launches and only for 5 s; after that the query was handed to whichever viewer was attached. On Windows ConPTY the owner kept swallowing the query but stopped answering it, so a Codex started from an older shell tab lost its message shading (#22332). On a headless `orca serve` host no viewer existed yet, so a Codex started before anyone attached got no reply at all (#22500). The owner (in-process provider, terminal daemon, SSH relay) now answers every OSC 10/11 query for the PTY's whole life and strips it, so no downstream view ever sees one to answer twice. It answers from, in order: the host-wide viewer theme pushed to that process, the creating viewer's colours sent at spawn (now for every PTY, not only agents), and Orca's default dark theme. The desktop pushes its renderer theme to every owner on change and on (re)connect: a daemon request gated on protocol v38, and an SSH relay notification that older relays ignore. The answer-once rule, the 5 s colour window and the colour-authority handoff are removed; Kitty keyboard queries keep their startup window. Viewer-side answerers (renderer xterm, main's hidden-pane model responder, the mobile webview) stay as the fallback for older owners, which still hand queries off; they are never reached for a new owner. * fix(terminal): answer OSC 10/11 with the colours the pane is really painted with Review follow-ups to the lifetime PTY-owner colour answerer. - The theme catalog moves to src/shared so the renderer and the PTY owners read one source; the owner's last-resort default is derived from it rather than copied. - Main seeds every owner from the host's saved theme settings (light or dark, custom themes, colour overrides) at startup, so a headless host and a desktop pane that queries before the renderer's first push are not told dark to a light-theme user. The renderer's push replaces it. - Colours an app sets with OSC 10/11, and clears with OSC 110/111, are tracked per terminal and reported back, as a viewer paints them; a theme change drops them, as a viewer's theme apply does. - A terminal a paired client created with its own colours answers with those, not the host's theme (`colorSource: 'remote-viewer'` on the spawn intent), so a light client on a dark host is told light. - After the 5 s startup window a reply's echo is watched for 512 bytes instead of 256 KB, and a torn query candidate is released after 500 ms rather than held indefinitely. * fix(terminal): keep the long echo watch for relayed replies; one theme lookup The 512-byte post-startup echo watch now applies only to replies the PTY owner produced itself. A viewer's reply relayed through answerLiveQueryReply keeps the 256 KB watch, because a cooked-mode app can keep printing after it queries and the echo then trails that output. The renderer's getTerminalTheme now calls the shared lookupTerminalTheme, so the custom-vs-built-in theme lookup exists once. * perf(terminal): scan colour overrides in one pass over each PTY chunk Two indexOf searches per OSC went quadratic on long runs of ST-terminated hyperlinks, and the tracker now sees every chunk of every terminal. * fix(terminal): one host viewer colour value, set by whichever viewer acted last A paired client's colours reached the host only as frozen spawn colours on terminal.create, tagged remote-viewer. UI-started agent sessions on a headless host answered OSC 10/11 with the host's saved theme, and a client's theme flip never reached panes it had created. The host now holds one viewer colour value that every PTY owner answers with. The desktop renderer's push, a window focus on the host, the new terminal.setViewerColors RPC, and terminal.create colours from older clients all set it; equal values do not re-notify daemons or relays. The remote-viewer tag (colorSource / terminalColorQuerySource / spawnFromRemoteViewer) is gone; it never shipped in a release. * fix(terminal): paired clients push their terminal colours on connect, change and focus The renderer publisher now hands each published fg/bg to subscribers. A new remote-runtime-terminal-color-push module calls terminal.setViewerColors on every host this client is connected to when it connects (or the host restarts), when the colours change, and when the window gains focus. A host that answers method_not_found or forbidden is not asked again until it reconnects. The app shell also republishes terminal view attributes on settings and system theme changes, so a theme change reaches main and paired hosts with no terminal pane open. * fix(terminal): a host with its own window answers OSC 10/11 with its own theme Round 1 kept one host-wide viewer colour value set by whichever viewer acted last, so a paired client's push (reconnect after sleep, a dusk theme flip) took over the host desktop's own panes until its window regained focus. The value is now derived: this host's renderer colours when a local window has pushed, otherwise the last paired client's push (terminal.setViewerColors or terminal.create colours), otherwise the saved theme. Only a headless host takes a client's theme. The window-focus reassert and the identical-re-push takeover are gone; owners are notified only when the derived value changes. * perf(terminal): scan only OSC starts for colour queries once the Kitty window closes The PTY owner answers OSC 10/11 for the terminal's whole life, and it tried every ESC as a query start: a 240 KB SGR-heavy read cost about 2 ms and 256 KB of bare ESC about 15 ms, long after startup. Once the Kitty query window closes only an OSC colour query can match, so the scan jumps between ESC ] starts, plus a trailing lone ESC so a query torn right after its ESC still resolves on the next read. Output and replies are unchanged.
81 lines
3.3 KiB
TypeScript
81 lines
3.3 KiB
TypeScript
import type { GlobalSettings } from './global-settings-types'
|
|
import {
|
|
normalizeTerminalCustomThemes,
|
|
parseCustomTerminalThemeSelection,
|
|
terminalCustomThemeToXtermTheme
|
|
} from './terminal-custom-themes'
|
|
import type { TerminalOscColorQueryReplyColors } from './terminal-osc-color-reply'
|
|
import { TERMINAL_THEME_CATALOG } from './terminal-themes'
|
|
import type { TerminalThemeMap } from './terminal-themes/types'
|
|
|
|
export const DEFAULT_TERMINAL_THEME_DARK = 'Ghostty Default Style Dark'
|
|
export const DEFAULT_TERMINAL_THEME_LIGHT = 'Builtin Tango Light'
|
|
|
|
export type TerminalThemeSelectionSettings = Pick<
|
|
GlobalSettings,
|
|
'theme' | 'terminalThemeDark' | 'terminalUseSeparateLightTheme' | 'terminalThemeLight'
|
|
>
|
|
|
|
export type TerminalThemeSelection = {
|
|
mode: 'dark' | 'light'
|
|
useLightVariant: boolean
|
|
themeName: string
|
|
}
|
|
|
|
/** Which terminal theme the app's appearance settings select; one rule for the renderer and main. */
|
|
export function selectTerminalTheme(
|
|
settings: TerminalThemeSelectionSettings,
|
|
systemPrefersDark: boolean
|
|
): TerminalThemeSelection {
|
|
const mode = settings.theme === 'system' ? (systemPrefersDark ? 'dark' : 'light') : settings.theme
|
|
const useLightVariant = mode === 'light' && settings.terminalUseSeparateLightTheme
|
|
const themeName = useLightVariant
|
|
? settings.terminalThemeLight || DEFAULT_TERMINAL_THEME_LIGHT
|
|
: settings.terminalThemeDark || DEFAULT_TERMINAL_THEME_DARK
|
|
return { mode, useLightVariant, themeName }
|
|
}
|
|
|
|
/** A selection names a user-imported theme or a built-in one; one lookup for both. */
|
|
export function lookupTerminalTheme(
|
|
settings: Pick<GlobalSettings, 'terminalCustomThemes'> | undefined,
|
|
selection: string
|
|
): TerminalThemeMap[string] | null {
|
|
const customId = parseCustomTerminalThemeSelection(selection)
|
|
const custom =
|
|
customId && settings
|
|
? normalizeTerminalCustomThemes(settings.terminalCustomThemes).find(
|
|
({ id }) => id === customId
|
|
)
|
|
: undefined
|
|
return custom
|
|
? terminalCustomThemeToXtermTheme(custom)
|
|
: (TERMINAL_THEME_CATALOG[selection] ?? null)
|
|
}
|
|
|
|
function themeColors(
|
|
selection: string,
|
|
settings: Pick<GlobalSettings, 'terminalCustomThemes'>
|
|
): TerminalOscColorQueryReplyColors | null {
|
|
const theme = lookupTerminalTheme(settings, selection)
|
|
return theme ? { foreground: theme.foreground, background: theme.background } : null
|
|
}
|
|
|
|
/**
|
|
* The foreground/background the host's saved settings paint a terminal with, for answering
|
|
* OSC 10/11 before (or without) a renderer. Opacity is ignored: a colour query reports the
|
|
* opaque theme colour.
|
|
*/
|
|
export function resolveConfiguredTerminalColors(
|
|
settings: TerminalThemeSelectionSettings &
|
|
Pick<GlobalSettings, 'terminalCustomThemes' | 'terminalColorOverrides'>,
|
|
systemPrefersDark: boolean
|
|
): TerminalOscColorQueryReplyColors {
|
|
const { themeName, useLightVariant } = selectTerminalTheme(settings, systemPrefersDark)
|
|
const fallback = useLightVariant ? DEFAULT_TERMINAL_THEME_LIGHT : DEFAULT_TERMINAL_THEME_DARK
|
|
const base: TerminalOscColorQueryReplyColors =
|
|
themeColors(themeName, settings) ?? themeColors(fallback, settings) ?? {}
|
|
const foreground = settings.terminalColorOverrides?.foreground ?? base.foreground
|
|
const background = settings.terminalColorOverrides?.background ?? base.background
|
|
return { ...(foreground ? { foreground } : {}), ...(background ? { background } : {}) }
|
|
}
|