fix(activity): name a resumed session by its own title in the agents list (#41127)

The Agents list titled a thread with the tab's generated title, the first
prompt that tab saw. After `claude --resume` in a new tab, that is the
message sent after the resume, so the list kept a status note as the
thread's name while the tab and the agent row showed the session's own
name. Rank a named provider title of the pane's own session above the
generated title, as resolveTabNameSource does, and share the session
ownership check with the agent row.
This commit is contained in:
Eotel
2026-11-12 05:46:32 +00:00
committed by GitHub
parent d860f32322
commit abe77d5b63
4 changed files with 65 additions and 10 deletions
@@ -0,0 +1,34 @@
import { describe, expect, it } from 'vitest'
import { getActivityThreadTaskTitle } from './activity-thread-display'
const resumedTab = {
customTitle: null,
aiVaultTitle: {
agent: 'claude' as const,
sessionId: 'session-1',
title: 'Triage lead handover',
kind: 'named' as const
},
generatedTitle: 'Mac restarted at 15 01 so this tab stopped',
title: 'Triage lead handover',
defaultTitle: 'Terminal 2'
}
const resumedEntry = {
prompt: 'How is the rename going?',
stateHistory: [],
agentType: 'claude' as const,
providerSession: { key: 'session_id' as const, id: 'session-1' }
}
describe('getActivityThreadTaskTitle', () => {
it("names a resumed session by its own name, not the tab's first prompt", () => {
expect(
getActivityThreadTaskTitle({
entry: resumedEntry,
tab: resumedTab,
generatedTitlesEnabled: true
})
).toBe('Triage lead handover')
})
})
@@ -4,6 +4,7 @@ import type {
AgentStatusState
} from '../../../../../shared/agent-status/contracts/agent-status-types'
import type { TerminalTab } from '../../../../../shared/workspace-layout/terminal-tab-types'
import { aiVaultTitleOfSession } from '../../../../../shared/ai-vault/contracts/ai-vault-session-title'
import type { Worktree } from '../../../../../shared/worktree/types'
import {
getAgentRowPrimaryText,
@@ -83,8 +84,14 @@ export function getActivityThreadWorkspaceTitle(
}
export function getActivityThreadTaskTitle(args: {
entry: Pick<AgentStatusEntry, 'orchestration' | 'prompt' | 'stateHistory'>
tab: Pick<TerminalTab, 'customTitle' | 'generatedTitle' | 'title' | 'defaultTitle'>
entry: Pick<
AgentStatusEntry,
'orchestration' | 'prompt' | 'stateHistory' | 'agentType' | 'providerSession'
>
tab: Pick<
TerminalTab,
'customTitle' | 'aiVaultTitle' | 'generatedTitle' | 'title' | 'defaultTitle'
>
generatedTitlesEnabled: boolean
}): string {
const customTitle = args.tab.customTitle?.trim()
@@ -97,6 +104,17 @@ export function getActivityThreadTaskTitle(args: {
return orchestrationLabel
}
// Like the tab, a session's own name outranks the first prompt this tab saw, e.g. after a resume.
const sessionTitle = aiVaultTitleOfSession(
args.tab.aiVaultTitle,
args.entry.agentType,
args.entry.providerSession?.id
)
const sessionName = sessionTitle?.kind === 'named' ? sessionTitle.title.trim() : ''
if (sessionName) {
return sessionName
}
// Respect tabAutoGenerateTitle here too so disabled generated titles cannot resurface.
const generatedTitle = args.generatedTitlesEnabled ? args.tab.generatedTitle?.trim() : ''
if (generatedTitle) {
@@ -7,6 +7,7 @@ import { isJcodeIdentityTerminalTitle } from '../title/providers/jcode-terminal-
import { SYNTHETIC_AGENT_TITLE_PROFILES } from '../title/synthetic-agent-title'
import type { TerminalTab } from '../../workspace-layout/terminal-tab-types'
import { resolveTabNameSource } from '../../tab-title-resolution'
import { aiVaultTitleOfSession } from '../../ai-vault/contracts/ai-vault-session-title'
export type ConversationNameTab = Pick<
TerminalTab,
@@ -111,19 +112,12 @@ export function getAgentRowConversationName(
): string | null {
const liveTitle =
paneLiveTitle === undefined ? (tab.title?.trim() ?? '') : (paneLiveTitle?.trim() ?? '')
// Provider titles belong to their session, not every pane in the tab.
const aiVaultTitle = tab.aiVaultTitle
const named = resolveTabNameSource(
{
customTitle: tab.customTitle,
quickCommandLabel: tab.quickCommandLabel,
liveTitle,
sessionTitle:
aiVaultTitle &&
aiVaultTitle.agent === agentType &&
aiVaultTitle.sessionId === providerSessionId
? aiVaultTitle
: null,
sessionTitle: aiVaultTitleOfSession(tab.aiVaultTitle, agentType, providerSessionId),
generatedTitle: tab.generatedTitle
},
generatedTitlesEnabled
@@ -11,6 +11,15 @@ export type AiVaultSessionTitle = {
kind?: AiVaultSessionTitleKind
}
/** A tab's provider title names only the pane running that session, not every pane in the tab. */
export function aiVaultTitleOfSession(
title: AiVaultSessionTitle | null | undefined,
agent: string | null | undefined,
sessionId: string | undefined
): AiVaultSessionTitle | null {
return title && title.agent === agent && title.sessionId === sessionId ? title : null
}
export type AiVaultSessionTitleRequest = {
agent: AiVaultSessionTitle['agent']
sessionId: string