From 72a23809fa8d6a4e12b432e9bfe05beac53df635 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:39:38 -0700 Subject: [PATCH] Prevent OMP terminals from being mislabeled as Gemini (#7447) Co-authored-by: Orca --- .../terminal-pane/pty-connection.test.ts | 36 ++++++++++++++++++- src/renderer/src/lib/agent-status.test.ts | 27 ++++++++++++++ src/shared/agent-detection.ts | 27 ++++++++------ src/shared/agent-title-owner.ts | 6 ++-- src/shared/pi-compatible-synthetic-title.ts | 6 ++++ 5 files changed, 87 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/components/terminal-pane/pty-connection.test.ts b/src/renderer/src/components/terminal-pane/pty-connection.test.ts index c8e90d3d6..ae4e0bbeb 100644 --- a/src/renderer/src/components/terminal-pane/pty-connection.test.ts +++ b/src/renderer/src/components/terminal-pane/pty-connection.test.ts @@ -247,9 +247,10 @@ function notifyStoreSubscribers(): void { vi.mock('@/lib/agent-status', async (importOriginal) => { const actual = await importOriginal>() + const isGeminiTerminalTitle = actual.isGeminiTerminalTitle as (title: string) => boolean return { ...actual, - isGeminiTerminalTitle: vi.fn(() => false), + isGeminiTerminalTitle: vi.fn((title: string) => isGeminiTerminalTitle(title)), isClaudeAgent: vi.fn(() => false), detectAgentStatusFromTitle: vi.fn((title: string) => { if (/Claude (working|done)/.test(title)) { @@ -12612,6 +12613,39 @@ describe('connectPanePty', () => { ) }) + it('keeps GPU rendering enabled for OMP titles whose cwd is Gemini', async () => { + const { connectPanePty } = await import('./pty-connection') + const transport = createMockTransport('pty-omp-gemini-cwd') + transportFactoryQueue.push(transport) + enableActiveRuntimeEnvironment() + mockStoreState.tabsByWorktree = { + 'wt-1': [{ id: 'tab-1', ptyId: 'tab-pty', launchAgent: 'omp' }] + } + + const pane = createPane(1) + const manager = createManager(1) + manager.getActivePane.mockReturnValue({ id: 1 }) + const deps = createDeps() + + connectPanePty(pane as never, manager as never, deps as never) + + const titleHandler = createdTransportOptions[0]?.onTitleChange as + | ((title: string, rawTitle: string) => void) + | undefined + if (!titleHandler) { + throw new Error('Expected onTitleChange to be registered') + } + + manager.setPaneGpuRendering.mockClear() + + titleHandler('\u280b Pi', '\u280b π: gemini') + + expect(manager.setPaneGpuRendering).toHaveBeenCalledTimes(1) + expect(manager.setPaneGpuRendering).toHaveBeenCalledWith(1, true) + expect(deps.setRuntimePaneTitle).toHaveBeenCalledWith('tab-1', 1, '\u280b OMP') + expect(deps.updateTabTitle).toHaveBeenCalledWith('tab-1', '\u280b OMP') + }) + it('leaves local IPC OSC 9999 status ownership in the main runtime', async () => { const { connectPanePty } = await import('./pty-connection') const transport = createMockTransport('pty-local') diff --git a/src/renderer/src/lib/agent-status.test.ts b/src/renderer/src/lib/agent-status.test.ts index 0b6bdd30e..5c5d5e96f 100644 --- a/src/renderer/src/lib/agent-status.test.ts +++ b/src/renderer/src/lib/agent-status.test.ts @@ -397,6 +397,22 @@ describe('normalizeTerminalTitle', () => { it('collapses Pi spinner and idle titles to stable labels', () => { expect(normalizeTerminalTitle('⠋ π - my-project')).toBe('⠋ Pi') expect(normalizeTerminalTitle('π - my-project')).toBe('Pi') + expect(normalizeTerminalTitle('⠋ π: my-project')).toBe('⠋ Pi') + expect(normalizeTerminalTitle('π: my-project')).toBe('Pi') + expect(normalizeTerminalTitle('π -')).toBe('Pi') + expect(normalizeTerminalTitle('π:')).toBe('Pi') + expect(normalizeTerminalTitle('π ')).toBe('Pi') + }) + + it('does not collapse Pi-compatible titles whose cwd mentions Gemini', () => { + expect(normalizeTerminalTitle('⠋ π - gemini')).toBe('⠋ Pi') + expect(normalizeTerminalTitle('π - gemini')).toBe('Pi') + expect(normalizeTerminalTitle('⠋ π: gemini')).toBe('⠋ Pi') + expect(normalizeTerminalTitle('π: gemini')).toBe('Pi') + expect(normalizeTerminalTitle('⠋ π gemini')).toBe('⠋ Pi') + expect(normalizeTerminalTitle('π gemini')).toBe('Pi') + expect(normalizeTerminalTitle('⠋ π - gemini-project')).toBe('⠋ Pi') + expect(normalizeTerminalTitle('π - gemini-project')).toBe('Pi') }) }) @@ -409,6 +425,17 @@ describe('isGeminiTerminalTitle', () => { it('does not match other terminal titles', () => { expect(isGeminiTerminalTitle('⠂ Claude Code')).toBe(false) + expect(isGeminiTerminalTitle('⠋ π - gemini')).toBe(false) + expect(isGeminiTerminalTitle('π - gemini')).toBe(false) + expect(isGeminiTerminalTitle('⠋ π: gemini')).toBe(false) + expect(isGeminiTerminalTitle('π: gemini')).toBe(false) + expect(isGeminiTerminalTitle('⠋ π gemini')).toBe(false) + expect(isGeminiTerminalTitle('π gemini')).toBe(false) + expect(isGeminiTerminalTitle('π -')).toBe(false) + expect(isGeminiTerminalTitle('π:')).toBe(false) + expect(isGeminiTerminalTitle('π ')).toBe(false) + expect(isGeminiTerminalTitle('⠋ π - gemini-project')).toBe(false) + expect(isGeminiTerminalTitle('/tmp/gemini/working')).toBe(false) expect(isGeminiTerminalTitle('bash')).toBe(false) }) }) diff --git a/src/shared/agent-detection.ts b/src/shared/agent-detection.ts index 196d6eadb..80f934b3c 100644 --- a/src/shared/agent-detection.ts +++ b/src/shared/agent-detection.ts @@ -16,7 +16,8 @@ import { } from './agent-name-token-match' import { getPiCompatibleSyntheticAgentLabel, - getPiCompatibleSyntheticAgentStatus + getPiCompatibleSyntheticAgentStatus, + isLegacyPiCompatibleTitle } from './pi-compatible-synthetic-title' // Re-export so existing `agent-detection` importers keep working. @@ -96,26 +97,30 @@ export const STRONG_WORKING_KEYWORDS_RE = new RegExp( // compounds like "is-working-cap" that `detectAgentStatusFromTitle` would // correctly refuse to classify as working. const STRONG_WORKING_KEYWORDS_RE_GLOBAL = new RegExp(STRONG_WORKING_KEYWORDS_RE.source, 'gi') -const PI_IDLE_PREFIX = '\u03c0 - ' // π - (Pi titlebar extension idle format) - export function isGeminiTerminalTitle(title: string): boolean { - return ( + // Why: Gemini OSC glyphs are stronger evidence than any cwd/session text. + if ( title.includes(GEMINI_PERMISSION) || title.includes(GEMINI_WORKING) || title.includes(GEMINI_SILENT_WORKING) || - title.includes(GEMINI_IDLE) || - title.toLowerCase().includes('gemini') - ) + title.includes(GEMINI_IDLE) + ) { + return true + } + // Why: Pi/OMP titles include cwd/session text; substring matching made + // paths like "gemini-project" masquerade as Gemini CLI. + if (isPiAgentTitle(title)) { + return false + } + return titleHasAgentName(title, 'gemini') } export function isPiTerminalTitle(title: string): boolean { - return title.startsWith(PI_IDLE_PREFIX) + return isLegacyPiCompatibleTitle(title) && !containsBrailleSpinner(title) } function isPiAgentTitle(title: string): boolean { - return ( - isPiTerminalTitle(title) || (containsBrailleSpinner(title) && title.includes(PI_IDLE_PREFIX)) - ) + return isLegacyPiCompatibleTitle(title) } function containsBrailleSpinner(title: string): boolean { diff --git a/src/shared/agent-title-owner.ts b/src/shared/agent-title-owner.ts index 4e1dfda55..4de8c0cbd 100644 --- a/src/shared/agent-title-owner.ts +++ b/src/shared/agent-title-owner.ts @@ -5,13 +5,13 @@ import { SYNTHETIC_AGENT_TITLE_PROFILES, type SyntheticAgentTitleProfile } from './synthetic-agent-title' +import { isLegacyPiCompatibleTitle } from './pi-compatible-synthetic-title' type TitleProfileMatch = { profile: SyntheticAgentTitleProfile } const COMPATIBLE_IDLE_TITLE_RE = /(?