Prevent OMP terminals from being mislabeled as Gemini (#7447)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
dbf1da0f44
commit
72a23809fa
|
|
@ -247,9 +247,10 @@ function notifyStoreSubscribers(): void {
|
|||
|
||||
vi.mock('@/lib/agent-status', async (importOriginal) => {
|
||||
const actual = await importOriginal<Record<string, unknown>>()
|
||||
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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 = /(?<![\w./\\-])(?:ready|idle|done)(?![\w-])/i
|
||||
const LEGACY_PI_COMPATIBLE_TITLE_RE = /^\s*(?:[\u2800-\u28ff]\s+)?π\s*(?:[-:]|\s)\s*.+/u
|
||||
|
||||
/**
|
||||
* Resolves the synthetic title profile matching a given agent label.
|
||||
|
|
@ -37,7 +37,7 @@ function getProfileForTitle(title: string): TitleProfileMatch | null {
|
|||
if (labelProfile) {
|
||||
return labelProfile
|
||||
}
|
||||
if (LEGACY_PI_COMPATIBLE_TITLE_RE.test(title)) {
|
||||
if (isLegacyPiCompatibleTitle(title)) {
|
||||
return getProfileForTitleLabel('Pi')
|
||||
}
|
||||
return null
|
||||
|
|
@ -60,7 +60,7 @@ function getSourceTitleStatus(title: string): 'working' | 'permission' | 'idle'
|
|||
if (detectedStatus) {
|
||||
return detectedStatus
|
||||
}
|
||||
if (LEGACY_PI_COMPATIBLE_TITLE_RE.test(title)) {
|
||||
if (isLegacyPiCompatibleTitle(title)) {
|
||||
return 'idle'
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ export type PiCompatibleSyntheticAgentStatus = 'working' | 'permission' | 'idle'
|
|||
const PI_COMPATIBLE_SYNTHETIC_TITLE_RE =
|
||||
/^\s*(?:[\u2800-\u28ff]\s+)?(pi|omp)(?:\s+-\s+action required|\s+(?:ready|idle|done))?\s*$/i
|
||||
const PI_COMPATIBLE_IDLE_RE = /(?<![\w./\\-])(?:ready|idle|done)(?![\w-])/i
|
||||
// Why: legacy Pi/OMP-compatible shells can emit the delimiter before cwd text exists.
|
||||
const LEGACY_PI_COMPATIBLE_TITLE_RE = /^\s*(?:[\u2800-\u28ff]\s+)?π(?:\s*[-:]|\s)\s*.*$/u
|
||||
|
||||
function containsBrailleSpinner(title: string): boolean {
|
||||
for (const char of title) {
|
||||
|
|
@ -47,3 +49,7 @@ export function getPiCompatibleSyntheticAgentStatus(
|
|||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function isLegacyPiCompatibleTitle(title: string): boolean {
|
||||
return LEGACY_PI_COMPATIBLE_TITLE_RE.test(title)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue