import { afterEach, describe, expect, it, vi } from 'vitest' import { detectAgentStatusFromTitle, extractAllOscTitles, extractLastOscTitle, getAgentLabel, isCursorAgentTitle, MAX_OSC_TITLE_CHARS, normalizeTerminalTitle } from './agent-detection' import { hasCompatibleAgentTitleIdentity, normalizeCompatibleAgentStatusEntryForOwner, normalizeCompatibleAgentTitleForOwner, resolveCompatibleAgentTypeForOwner } from './agent-title-owner' import { SYNTHETIC_AGENT_TITLE_PROFILES } from './synthetic-agent-title' afterEach(() => { vi.restoreAllMocks() }) describe('OSC title extraction', () => { it('extracts the last OSC title from BEL-terminated PTY data', () => { expect(extractLastOscTitle('\x1b]0;First\x07noise\x1b]2;Second\x07')).toBe('Second') }) it('extracts all OSC titles including ST-terminated titles', () => { expect(extractAllOscTitles('\x1b]0;First\x1b\\noise\x1b]2;Second\x07')).toEqual([ 'First', 'Second' ]) }) it('ignores incomplete OSC titles until a later chunk supplies the terminator', () => { expect(extractAllOscTitles('\x1b]0;Incomplete title')).toEqual([]) expect(extractLastOscTitle('\x1b]0;Incomplete title')).toBeNull() }) it('recovers when an abandoned incomplete OSC title is followed by a fresh title', () => { const data = '\x1b]0;abandoned\x1b]0;Fresh title\x07' expect(extractLastOscTitle(data)).toBe('Fresh title') expect(extractAllOscTitles(data)).toEqual(['Fresh title']) }) it('scans large PTY chunks without regex match iteration', () => { const matchAll = vi.spyOn(String.prototype, 'matchAll') const data = `${'pasted terminal noise \x1b]x;ignored\x07 '.repeat(10_000)}\x1b]0;Agent working\x07` expect(extractLastOscTitle(data)).toBe('Agent working') expect(extractAllOscTitles(data).at(-1)).toBe('Agent working') expect(matchAll).not.toHaveBeenCalled() }) it('caps oversized OSC titles before downstream title processing', () => { const title = `${'a'.repeat(MAX_OSC_TITLE_CHARS)}${'b'.repeat(10_000)}` const data = `before\x1b]0;${title}\x07after` const extracted = extractLastOscTitle(data) expect(extracted).toHaveLength(MAX_OSC_TITLE_CHARS) expect(extracted?.startsWith('a'.repeat(MAX_OSC_TITLE_CHARS / 2))).toBe(true) expect(extracted?.endsWith('b'.repeat(MAX_OSC_TITLE_CHARS / 2))).toBe(true) expect(extractAllOscTitles(data)).toEqual([extracted]) }) }) describe('MiMo title detection', () => { it.each([ ['MiMo Code', 'idle'], ['mimo ready', 'idle'], ['mimo working', 'working'], ['\u280b MiMo Code', 'working'] ] as const)('classifies %s', (title, expectedStatus) => { expect(getAgentLabel(title)).toBe('MiMo Code') expect(detectAgentStatusFromTitle(title)).toBe(expectedStatus) }) it.each(['~/mimo/working', 'mimo-code-fixtures ready'])( 'does not classify path or hyphen false positive %s', (title) => { expect(getAgentLabel(title)).toBeNull() expect(detectAgentStatusFromTitle(title)).toBeNull() } ) }) describe('Pi-compatible title detection', () => { it.each([ ['\u280b OMP', 'OMP', 'working'], ['OMP ready', 'OMP', 'idle'], ['OMP', 'OMP', 'idle'], ['OMP - action required', 'OMP', 'permission'], ['\u280b Pi', 'Pi', 'working'], ['Pi ready', 'Pi', 'idle'], // Why: normalizeTerminalTitle collapses idle π frames to bare "Pi"; re-detection // from stored lastOscTitle must still classify idle, not neutral. ['Pi', 'Pi', 'idle'], ['Pi - action required', 'Pi', 'permission'] ] as const)('classifies synthesized %s', (title, expectedLabel, expectedStatus) => { expect(getAgentLabel(title)).toBe(expectedLabel) expect(detectAgentStatusFromTitle(title)).toBe(expectedStatus) }) it('re-detects status after display-title normalization for Pi idle frames', () => { expect(normalizeTerminalTitle('π - my-project')).toBe('Pi') expect(detectAgentStatusFromTitle(normalizeTerminalTitle('π - my-project'))).toBe('idle') expect(detectAgentStatusFromTitle(normalizeTerminalTitle('\u280b π - my-project'))).toBe( 'working' ) }) it.each([ ['\u280b Pi', 'omp', '\u280b OMP'], ['Pi ready', 'omp', 'OMP ready'], ['Pi - action required', 'omp', 'OMP - action required'], ['π - tmp', 'omp', 'OMP ready'], ['π: tmp', 'omp', 'OMP ready'], ['\u280b π: tmp', 'omp', '\u280b OMP'], ['\u280b π - tmp', 'omp', '\u280b OMP'], ['\u280b OMP', 'pi', '\u280b Pi'], ['lucky-echidna | \u283c π - Diagnose Orca terminal title flicker - test', 'omp', '\u280b OMP'], ['lucky-echidna | Pi ready', 'omp', 'OMP ready'], ['Codex | Pi ready', 'omp', 'OMP ready'], // Why: the wrapped whole reads as a braille Claude title, but the re-ownable // synthetic pane suffix must still win. ['lucky-echidna | ⠋ OMP', 'omp', '⠋ OMP'], ['lucky-echidna | \u283c π - Diagnose | test', 'omp', '\u280b OMP'] ] as const)('normalizes %s to the authoritative %s owner', (title, owner, expectedTitle) => { expect(normalizeCompatibleAgentTitleForOwner(title, owner)).toBe(expectedTitle) }) it('preserves Pi-compatible custom titles and unrelated owners', () => { expect(normalizeCompatibleAgentTitleForOwner('Fix pi bugs', 'omp')).toBe('Fix pi bugs') // Why: a wrapped title with no re-ownable identity in any " | " segment // passes through untouched instead of collapsing to the owner label. expect(normalizeCompatibleAgentTitleForOwner('lucky-echidna | Fix pi bugs', 'omp')).toBe( 'lucky-echidna | Fix pi bugs' ) expect(normalizeCompatibleAgentTitleForOwner('xxPi ready', 'omp')).toBe('xxPi ready') expect(normalizeCompatibleAgentTitleForOwner('\u280b Pi', 'codex')).toBe('\u280b Pi') }) it('identifies titles whose compatible identity can be re-owned', () => { expect(hasCompatibleAgentTitleIdentity('Pi ready')).toBe(true) expect(hasCompatibleAgentTitleIdentity('π - tmp')).toBe(true) expect(hasCompatibleAgentTitleIdentity('\u280b OMP')).toBe(true) expect(hasCompatibleAgentTitleIdentity('Fix pi bugs')).toBe(false) expect(hasCompatibleAgentTitleIdentity('\u280b Codex')).toBe(false) }) it('normalizes Pi-compatible status identity and terminal title to the owner', () => { const status = normalizeCompatibleAgentStatusEntryForOwner( { state: 'working', prompt: '', updatedAt: 1, stateStartedAt: 1, agentType: 'pi', paneKey: 'tab-1:leaf-1', terminalTitle: '\u280b Pi', stateHistory: [] }, 'omp' ) expect(status.agentType).toBe('omp') expect(status.terminalTitle).toBe('\u280b OMP') expect(resolveCompatibleAgentTypeForOwner('codex', 'omp')).toBe('codex') }) it.each(['~/omp/working', 'omp-harness ready', '~/pi/working', 'pi-scratch ready'])( 'does not classify path or hyphen false positive %s', (title) => { expect(getAgentLabel(title)).toBeNull() expect(detectAgentStatusFromTitle(title)).toBeNull() } ) }) describe('Cursor agent title identity', () => { // Why: the accepted vocabulary is the set of labels Orca actually synthesizes for Cursor. // Pin it to that profile so renaming a label there cannot silently drop @cursor to zero // recipients (and desync the auto-Enter suppression that shares this predicate). it('accepts every label Orca synthesizes for Cursor', () => { const profile = SYNTHETIC_AGENT_TITLE_PROFILES.cursor for (const label of [ profile.workingLabel, `⠋ ${profile.workingLabel}`, profile.idleLabel, profile.permissionLabel ]) { expect(isCursorAgentTitle(label)).toBe(true) } }) it.each([ 'Cursor Agent', ' cursor agent ', '⠋ Cursor Agent', '⣿ Cursor Agent', 'Cursor ready', 'Cursor - action required' ])('accepts the native or Orca-synthesized Cursor title %j', (title) => { expect(isCursorAgentTitle(title)).toBe(true) }) // Why: "cursor" is ordinary editor vocabulary in another agent's task-summary title, // so a whole-token name match is not Cursor identity. it.each([ '⠋ fix the text cursor blink', '✳ Fix the text cursor blink', '. fix cursor position', '* cursor rendering done', 'Terminal Cursor and Orca slows down', 'cursor-agent', 'cursor.exe', '~/cursor-rules', '', null, undefined ])('rejects the non-Cursor title %j', (title) => { expect(isCursorAgentTitle(title)).toBe(false) }) })