test(renderer): align regressions with durable nested ownership

Update CI-failing expectations for #13341 (process/title no longer reclaim
under active launch), add shell/remote/hook edge cases, and use an
agent-attributable working title in WorktreeCard status coverage.
This commit is contained in:
OrcaWin
2026-08-12 02:18:47 -07:00
parent 4b179a98a3
commit 00686addd1
6 changed files with 66 additions and 14 deletions
@@ -52,8 +52,9 @@ describe('getWorktreeStatus', () => {
livePtyIds
)
).toBe('permission')
// Why: status must be agent-attributable (identity or spinner+launch), not a bare keyword.
expect(
getWorktreeStatus([makeTerminalTab('mimo working')], [{ id: 'browser-1' }], livePtyIds)
getWorktreeStatus([makeTerminalTab('✦ Codex')], [{ id: 'browser-1' }], livePtyIds)
).toBe('working')
})
})
@@ -140,7 +140,8 @@ describe('buildWorktreeAgentRows', () => {
expect(rows[0].agentType).toBe('codex')
})
it('prefers an unrelated live title over the launched tab agent for unknown rows', () => {
// Why (#13341): unknown hook rows still honor launch ownership over a nested child title.
it('keeps launch ownership over an unrelated live title for unknown rows', () => {
const rows = buildWorktreeAgentRows({
tabs: [makeTab('tab-1', { launchAgent: 'omp', title: '\u280b Codex' })],
entries: [
@@ -153,7 +154,7 @@ describe('buildWorktreeAgentRows', () => {
now: 2000
})
expect(rows[0].agentType).toBe('codex')
expect(rows[0].agentType).toBe('omp')
})
it('normalizes live Pi-compatible rows from the launched OMP tab agent', () => {
@@ -203,22 +203,17 @@ export function resolveTitleDerivedAgentType(
const agentType = TITLE_AGENT_LABEL_TO_TYPE[label] ?? 'unknown'
const owner = ownerAgentType && ownerAgentType !== 'unknown' ? ownerAgentType : null
if (agentType === 'claude') {
// Why: Claude's task-title spinner heuristic has no provider identity. In
// split panes it can match arbitrary terminal spinners, so sidebar rows only
// accept Claude when the title itself names Claude.
// Why: spinner heuristic is provider-neutral — require a Claude token.
if (!CLAUDE_AGENT_TOKEN_RE.test(title)) {
return null
}
// Why (#8940 + #13341): Claude mentions never steal ownership, and a nested
// Claude identity frame under a durable non-Claude owner keeps the parent.
// Pane reuse is modeled by clearing launchAgent first (tab-agent path).
// Why (#8940/#13341): never steal a non-Claude owner; reclaim after launch clears.
if (owner && owner !== 'claude') {
return null
}
return agentType
}
// Why (#13341): nested Codex/Gemini/... titles must not rebrand a pane whose
// launch owner is still a different agent — fall through to ownerAgentType.
// Why (#13341): nested child titles keep the durable owner until launch clears.
if (owner && agentType !== 'unknown' && agentType !== owner) {
return null
}
@@ -102,4 +102,57 @@ describe('resolveTabAgentFromSignals — nested child identity (#13341)', () =>
})
).toBe('codex')
})
it('treats shell-foreground as exit so a later process can own the icon', () => {
expect(
resolveTabAgentFromSignals({
hasObservedAgentSignal: true,
isRemote: false,
title: '✳ Claude Code',
hookAgent: null,
processAgent: null,
processShellForeground: true,
launchAgent: 'claude'
})
).toBeNull()
expect(
resolveTabAgentFromSignals({
hasObservedAgentSignal: true,
isRemote: false,
title: '✦ Codex',
hookAgent: null,
processAgent: 'codex',
processShellForeground: false,
launchAgent: undefined
})
).toBe('codex')
})
it('keeps remote parent ownership when only a nested child title is visible', () => {
// Remote panes lack local process evidence; nested title alone must not rebrand.
expect(
resolveTabAgentFromSignals({
hasObservedAgentSignal: true,
isRemote: true,
title: '✦ Codex',
hookAgent: null,
processAgent: null,
launchAgent: 'claude'
})
).toBe('claude')
})
it('lets a live different-group hook outrank launch (explicit takeover)', () => {
// Why: hooks are ground truth; fenced nested hooks keep parent agentType upstream.
expect(
resolveTabAgentFromSignals({
hasObservedAgentSignal: true,
isRemote: false,
title: '✦ Codex',
hookAgent: 'codex',
processAgent: 'codex',
launchAgent: 'claude'
})
).toBe('codex')
})
})
@@ -241,6 +241,7 @@ describe('OpenCode native title tab identity', () => {
launchAgent: 'claude'
})
).toBe('claude')
// Why (#13341): nested process under active launch is not stronger than parent ownership.
expect(
resolveTabAgentFromSignals({
hasObservedAgentSignal: false,
@@ -251,7 +252,7 @@ describe('OpenCode native title tab identity', () => {
sleepingSessionAgent: 'claude',
launchAgent: 'claude'
})
).toBe('codex')
).toBe('claude')
for (const title of [
'OpenCode ready',
@@ -126,7 +126,8 @@ describe('useTabAgent retained completion identity', () => {
expect(latestAgent).toBe('gemini')
})
it('lets an explicit cross-agent title reclaim a retained idle pane', async () => {
// Why (#13341): active launch ownership is durable; nested Claude title does not reclaim.
it('keeps launch ownership over an explicit cross-agent title on a retained idle pane', async () => {
const paneKey = makePaneKey(TAB_ID, FOCUSED_LEAF_ID)
useAppStore.setState({
retainedAgentsByPaneKey: { [paneKey]: retainedEntry(paneKey, 'codex') }
@@ -134,7 +135,7 @@ describe('useTabAgent retained completion identity', () => {
await renderProbe({ ...baseTab, launchAgent: 'codex', title: '✳ Claude Code' })
expect(latestAgent).toBe('claude')
expect(latestAgent).toBe('codex')
})
it('keeps focused launch metadata ahead of sibling retained identity', async () => {