fix(agent-status): label Cursor by identity, not a bare "cursor" token (#8445)
* fix(agent-status): label Cursor by identity, not a bare "cursor" token
The worktree card, status bar, and mobile all derive an agent label from the
terminal title via getAgentLabel / resolveTerminalTitleAgentType. Both matched
Cursor with `titleHasAgentName(title, 'cursor')`, a whole-token match. But
`cursor` is ordinary editor vocabulary, so a Claude/Codex tab working on Orca's
own code (title like `⠋ preserve cursor visibility across replays`) got
mislabeled as Cursor. The generic braille-spinner Claude fallback even had a
`!lower.includes('cursor')` guard that then dropped the title to no label at
all.
Gate Cursor on its closed identity title set (`isCursorAgentTitle`) instead —
the same predicate @cursor orchestration routing uses. A real cursor-agent
terminal still resolves as Cursor across working/idle/permission; a non-Cursor
tab that merely mentions a text cursor reverts to its true agent. Relax the
braille guard to the same predicate so those titles land on Claude, not null.
Makes display consistent with routing (the follow-up flagged in #8436).
* refactor(agent-status): address review on Cursor identity labeling
- Trim the four Cursor `// Why:` comments in both parallel resolvers
(agent-title-identity.ts, terminal-title-agent-type.ts) to AGENTS.md's
one-to-two-line rule; use identical wording so future drift is visible.
- Add a direct isClaudeAgent assertion in terminal-title-agent-type.test.ts
pinning that file's parallel copy (previously only covered transitively),
plus Cursor Agent / "Cursor - action required" activity-facet assertions.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
4a76a1c894
commit
8abe093e86
|
|
@ -533,6 +533,19 @@ describe('getAgentLabel', () => {
|
|||
expect(getAgentLabel('Aider idle')).toBe('Aider')
|
||||
expect(getAgentLabel('Devin working')).toBe('Devin')
|
||||
})
|
||||
|
||||
// Why: `cursor` is ordinary editor vocabulary in another agent's task title, so a
|
||||
// bare token is not Cursor identity — a Claude tab working on cursor code was being
|
||||
// mislabeled Cursor on the worktree card. Match Cursor's closed title set only.
|
||||
it('labels Cursor by identity, not a bare "cursor" token in another agent title', () => {
|
||||
expect(getAgentLabel('Cursor Agent')).toBe('Cursor')
|
||||
expect(getAgentLabel('⠋ Cursor Agent')).toBe('Cursor')
|
||||
expect(getAgentLabel('Cursor ready')).toBe('Cursor')
|
||||
expect(getAgentLabel('Cursor - action required')).toBe('Cursor')
|
||||
expect(getAgentLabel('⠋ preserve cursor visibility across replays')).toBe('Claude Code')
|
||||
expect(getAgentLabel('⠋ Codex: fix cursor offsets')).toBe('Codex')
|
||||
expect(getAgentLabel('Terminal Cursor and Orca slows down')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isClaudeAgent', () => {
|
||||
|
|
@ -542,6 +555,13 @@ describe('isClaudeAgent', () => {
|
|||
expect(isClaudeAgent('OpenClaude ready')).toBe(false)
|
||||
})
|
||||
|
||||
// Why: a braille Claude title merely mentioning a text cursor is still Claude, so its
|
||||
// prompt-cache paths must fire; only Cursor's own identity titles are excluded.
|
||||
it('counts cursor-mentioning Claude braille titles as Claude, excludes real Cursor', () => {
|
||||
expect(isClaudeAgent('⠋ preserve cursor visibility across replays')).toBe(true)
|
||||
expect(isClaudeAgent('⠋ Cursor Agent')).toBe(false)
|
||||
})
|
||||
|
||||
it('does not classify non-prefix Claude mentions as Claude agent titles', () => {
|
||||
expect(isClaudeAgent('ask claude later')).toBe(false)
|
||||
expect(getAgentLabel('ask claude later')).toBeNull()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
HERMES_AGENT_NAME_RE,
|
||||
containsBrailleSpinner,
|
||||
isClaudeManagementTitle,
|
||||
isCursorAgentTitle,
|
||||
isGeminiTerminalTitle,
|
||||
isPiAgentTitle,
|
||||
titleHasAgentName
|
||||
|
|
@ -30,7 +31,9 @@ export function isClaudeAgent(title: string): boolean {
|
|||
return true
|
||||
}
|
||||
if (containsBrailleSpinner(title)) {
|
||||
return !lower.includes('cursor') && !lower.includes('openclaude')
|
||||
// Why: named non-Claude agents carry braille spinners too. Gate Cursor by its
|
||||
// identity title, not the token, so a Claude title mentioning a cursor stays Claude.
|
||||
return !isCursorAgentTitle(title) && !lower.includes('openclaude')
|
||||
}
|
||||
|
||||
const trimmedTitle = title.trimStart()
|
||||
|
|
@ -93,8 +96,9 @@ export function getAgentLabel(title: string): string | null {
|
|||
if (titleHasAgentName(title, 'aider')) {
|
||||
return 'Aider'
|
||||
}
|
||||
// Why: match explicit names before Claude's generic braille heuristic.
|
||||
if (titleHasAgentName(title, 'cursor')) {
|
||||
// Why: `cursor` is ordinary editor vocabulary, not identity. Match Cursor's closed
|
||||
// title set (mirrors @cursor routing), before `isClaudeAgent` claims the braille frame.
|
||||
if (isCursorAgentTitle(title)) {
|
||||
return 'Cursor'
|
||||
}
|
||||
if (DROID_AGENT_NAME_RE.test(title)) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
isClaudeAgent,
|
||||
isGrokRotatingWorkingTitle,
|
||||
resolveExplicitTerminalTitleAgentType
|
||||
resolveExplicitTerminalTitleAgentType,
|
||||
resolveTerminalTitleAgentType
|
||||
} from './terminal-title-agent-type'
|
||||
|
||||
describe('isGrokRotatingWorkingTitle', () => {
|
||||
|
|
@ -57,4 +59,44 @@ describe('resolveExplicitTerminalTitleAgentType', () => {
|
|||
expect(resolveExplicitTerminalTitleAgentType('Terminal 1')).toBeNull()
|
||||
expect(resolveExplicitTerminalTitleAgentType('zsh')).toBeNull()
|
||||
})
|
||||
|
||||
// Why: `cursor` is ordinary editor vocabulary, so a name token is not identity.
|
||||
// A Claude/Codex tab working on cursor code must not commit to Cursor identity.
|
||||
it('resolves Cursor by its identity titles, never a bare cursor token', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('Cursor Agent')).toBe('cursor')
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠋ Cursor Agent')).toBe('cursor')
|
||||
expect(resolveExplicitTerminalTitleAgentType('Cursor ready')).toBe('cursor')
|
||||
expect(resolveExplicitTerminalTitleAgentType('Cursor - action required')).toBe('cursor')
|
||||
// A Claude tab whose task text mentions a text cursor is not Cursor identity.
|
||||
expect(
|
||||
resolveExplicitTerminalTitleAgentType('⠋ preserve cursor visibility across replays')
|
||||
).toBeNull()
|
||||
expect(resolveExplicitTerminalTitleAgentType('~/cursor-rules')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveTerminalTitleAgentType', () => {
|
||||
// Why: the activity facet keeps Claude's braille prefix as Claude — but only when
|
||||
// the "cursor" it mentions is task text, not Cursor's own identity title.
|
||||
it('labels cursor-mentioning agent tabs by their true agent, real Cursor as cursor', () => {
|
||||
expect(resolveTerminalTitleAgentType('⠋ Cursor Agent')).toBe('cursor')
|
||||
expect(resolveTerminalTitleAgentType('Cursor Agent')).toBe('cursor')
|
||||
expect(resolveTerminalTitleAgentType('Cursor ready')).toBe('cursor')
|
||||
expect(resolveTerminalTitleAgentType('Cursor - action required')).toBe('cursor')
|
||||
expect(resolveTerminalTitleAgentType('⠋ preserve cursor visibility across replays')).toBe(
|
||||
'claude'
|
||||
)
|
||||
expect(resolveTerminalTitleAgentType('⠋ Codex: fix cursor offsets')).toBe('codex')
|
||||
})
|
||||
})
|
||||
|
||||
// Why: this module carries its own isClaudeAgent copy parallel to agent-title-identity.ts;
|
||||
// both got the identical isCursorAgentTitle guard, so pin this copy directly to catch drift.
|
||||
describe('isClaudeAgent', () => {
|
||||
it('excludes real Cursor identity titles, keeps cursor-mentioning Claude braille titles', () => {
|
||||
expect(isClaudeAgent('⠋ Cursor Agent')).toBe(false)
|
||||
expect(isClaudeAgent('Cursor ready')).toBe(false)
|
||||
expect(isClaudeAgent('⠋ preserve cursor visibility across replays')).toBe(true)
|
||||
expect(isClaudeAgent('⠋ OpenClaude')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
HERMES_AGENT_NAME_RE,
|
||||
titleHasAgentName
|
||||
} from './agent-name-token-match'
|
||||
import { isCursorAgentTitle } from './agent-title-core'
|
||||
import {
|
||||
getPiCompatibleSyntheticAgentLabel,
|
||||
isLegacyPiCompatibleTitle
|
||||
|
|
@ -97,9 +98,9 @@ export function isClaudeAgent(title: string): boolean {
|
|||
return true
|
||||
}
|
||||
if (containsBrailleSpinner(title)) {
|
||||
// Why: named non-Claude agents can carry braille spinners too; Claude-only
|
||||
// prompt-cache paths must not fire for those explicit agent titles.
|
||||
return !lower.includes('cursor') && !lower.includes('openclaude')
|
||||
// Why: named non-Claude agents carry braille spinners too. Gate Cursor by its
|
||||
// identity title, not the token, so a Claude title mentioning a cursor stays Claude.
|
||||
return !isCursorAgentTitle(title) && !lower.includes('openclaude')
|
||||
}
|
||||
// Why: permission/action-required Claude titles can omit the usual prefixes.
|
||||
// Token-match so cwd/worktree titles like "claude-scratch" do not become
|
||||
|
|
@ -179,14 +180,9 @@ export function getAgentLabel(title: string): string | null {
|
|||
if (titleHasAgentName(title, 'aider')) {
|
||||
return 'Aider'
|
||||
}
|
||||
// Why: the cursor-agent native title is the literal string "Cursor Agent"
|
||||
// (verified against the 2026.04.17 release) — Orca synthesizes the same
|
||||
// label from hook events so the braille-spinner + agent-name path lights
|
||||
// up working/permission/idle transitions in the renderer. Match before
|
||||
// `isClaudeAgent` because Claude's generic braille heuristic would
|
||||
// otherwise claim every "⠋ Cursor Agent" frame as Claude. Token-match so a
|
||||
// cwd like "~/cursor-rules" can't masquerade as a Cursor agent.
|
||||
if (titleHasAgentName(title, 'cursor')) {
|
||||
// Why: `cursor` is ordinary editor vocabulary, not identity. Match Cursor's closed
|
||||
// title set (mirrors @cursor routing), before `isClaudeAgent` claims the braille frame.
|
||||
if (isCursorAgentTitle(title)) {
|
||||
return 'Cursor'
|
||||
}
|
||||
// Why: synthesized "⠋ Droid" working title needs to be matched before Claude's braille heuristic.
|
||||
|
|
|
|||
Loading…
Reference in New Issue