From b75ab61575b80eaf9a7c8024ad8de5cde595a571 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:42:17 -0700 Subject: [PATCH] feat(agent-rows): make conversation names the default Always prefer a usable conversation name for sidebar and dashboard agent rows, falling back to the last message when the resolver rejects a title. Remove the preference, settings UI, search copy, and catalog entries. Keep subagent labels unchanged and reject generic Terminal N placeholders so partial tab snapshots cannot hide meaningful prompts. --- .../use-agent-row-conversation-name.test.ts | 27 +++++--------- .../use-agent-row-conversation-name.ts | 22 ++++++------ .../src/components/settings/AgentsPane.tsx | 28 --------------- .../agent-row-conversation-name-copy.ts | 35 ------------------- .../src/components/settings/agents-search.ts | 10 ------ src/renderer/src/i18n/locales/en.json | 13 +------ src/renderer/src/i18n/locales/es.json | 13 +------ src/renderer/src/i18n/locales/ja.json | 13 +------ src/renderer/src/i18n/locales/ko.json | 13 +------ src/renderer/src/i18n/locales/zh.json | 13 +------ .../agent-row-conversation-name.test.ts | 3 ++ src/shared/agent-row-conversation-name.ts | 2 ++ src/shared/constants.ts | 1 - src/shared/types.ts | 4 --- 14 files changed, 29 insertions(+), 168 deletions(-) delete mode 100644 src/renderer/src/components/settings/agent-row-conversation-name-copy.ts diff --git a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts index 56a31366c33..048fa6eac66 100644 --- a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts +++ b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts @@ -34,21 +34,12 @@ beforeEach(() => { }) describe('useAgentRowConversationName', () => { - it('returns null without reading the tab map while the setting is off', () => { - const tabsByWorktree = new Proxy( - {}, - { - get: () => { - throw new Error('inactive rows must not read the tab map') - } - } - ) - storeState.current = { settings: {}, tabsByWorktree } - expect(useAgentRowConversationName(makeAgent())).toBeNull() + it('returns the conversation name by default', () => { + expect(useAgentRowConversationName(makeAgent())).toBe('Patient sync spike') }) - it('returns the conversation name when the setting is on', () => { - storeState.current = { settings: { agentRowsUseConversationName: true }, tabsByWorktree: {} } + it('ignores a retired stored opt-out value', () => { + storeState.current = { settings: { agentRowsUseConversationName: false }, tabsByWorktree: {} } expect(useAgentRowConversationName(makeAgent())).toBe('Patient sync spike') }) @@ -61,7 +52,7 @@ describe('useAgentRowConversationName', () => { } } ) - storeState.current = { settings: { agentRowsUseConversationName: true }, tabsByWorktree } + storeState.current = { settings: {}, tabsByWorktree } expect(useAgentRowConversationName(makeAgent({ rowSource: 'subagent' }))).toBeNull() }) @@ -82,7 +73,7 @@ describe('useAgentRowConversationName', () => { } ) storeState.current = { - settings: { agentRowsUseConversationName: true }, + settings: {}, tabsByWorktree: { 'wt-1': tabs } } @@ -102,7 +93,7 @@ describe('useAgentRowConversationName', () => { it('prefers the live store tab over the stale row snapshot', () => { storeState.current = { - settings: { agentRowsUseConversationName: true }, + settings: {}, // Why: row data patches entries in place and keeps the creation-time tab // snapshot; a rename landing after that must still surface. tabsByWorktree: { @@ -116,10 +107,10 @@ describe('useAgentRowConversationName', () => { const agent = makeAgent({ tab: { customTitle: null, title: '', generatedTitle: 'Fix intake flow' } } as Partial) - storeState.current = { settings: { agentRowsUseConversationName: true }, tabsByWorktree: {} } + storeState.current = { settings: {}, tabsByWorktree: {} } expect(useAgentRowConversationName(agent)).toBeNull() storeState.current = { - settings: { agentRowsUseConversationName: true, tabAutoGenerateTitle: true }, + settings: { tabAutoGenerateTitle: true }, tabsByWorktree: {} } expect(useAgentRowConversationName(agent)).toBe('Fix intake flow') diff --git a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts index 0fa93461a19..5aaecef8506 100644 --- a/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts +++ b/src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts @@ -22,21 +22,19 @@ function getIndexedTab( return tabIndex.get(tabId) } -/** The row's conversation name, or null when the mode is off or nothing usable exists. */ +/** The row's conversation name, or null when nothing usable exists. */ export function useAgentRowConversationName(agent: DashboardAgentRow): string | null { - const mode = useAppStore((s) => { - if (agent.rowSource === 'subagent' || s.settings?.agentRowsUseConversationName !== true) { - return 0 - } - return s.settings?.tabAutoGenerateTitle === true ? 2 : 1 - }) - // Why: the opt-in gate keeps inactive rows off the hot tab map entirely. - const liveTab = useAppStore((s) => - mode === 0 ? undefined : getIndexedTab(s.tabsByWorktree[agent.tab.worktreeId], agent.tab.id) + const isSubagent = agent.rowSource === 'subagent' + const generatedTitlesEnabled = useAppStore( + (s) => !isSubagent && s.settings?.tabAutoGenerateTitle === true ) - if (mode === 0) { + // Why: child rows describe the child, so the parent tab would mislabel them. + const liveTab = useAppStore((s) => + isSubagent ? undefined : getIndexedTab(s.tabsByWorktree[agent.tab.worktreeId], agent.tab.id) + ) + if (isSubagent) { return null } // Why: retained row snapshots need a fallback after their live tab disappears. - return getAgentRowConversationName(liveTab ?? agent.tab, agent.agentType, mode === 2) + return getAgentRowConversationName(liveTab ?? agent.tab, agent.agentType, generatedTitlesEnabled) } diff --git a/src/renderer/src/components/settings/AgentsPane.tsx b/src/renderer/src/components/settings/AgentsPane.tsx index 09ee278c347..99718de46fe 100644 --- a/src/renderer/src/components/settings/AgentsPane.tsx +++ b/src/renderer/src/components/settings/AgentsPane.tsx @@ -22,10 +22,6 @@ import { getAgentGeneratedTabTitlesDescription, getAgentGeneratedTabTitlesTitle } from './agent-generated-tab-title-copy' -import { - getAgentRowConversationNamesDescription, - getAgentRowConversationNamesTitle -} from './agent-row-conversation-name-copy' import { getAgentStatusHooksDescription, getAgentStatusHooksTitle } from './agent-status-hooks-copy' import { SettingsBadge, @@ -837,8 +833,6 @@ export function AgentsPane({ - - @@ -1009,25 +1003,3 @@ export function AgentGeneratedTabTitlesSetting({ ) } - -export function AgentRowConversationNamesSetting({ - settings, - updateSettings -}: AgentsPaneProps): React.JSX.Element { - const enabled = settings.agentRowsUseConversationName === true - return ( -
- - updateSettings({ - agentRowsUseConversationName: !enabled - }) - } - ariaLabel={getAgentRowConversationNamesTitle()} - /> -
- ) -} diff --git a/src/renderer/src/components/settings/agent-row-conversation-name-copy.ts b/src/renderer/src/components/settings/agent-row-conversation-name-copy.ts deleted file mode 100644 index 82032469581..00000000000 --- a/src/renderer/src/components/settings/agent-row-conversation-name-copy.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { translate } from '@/i18n/i18n' -import { searchKeywords } from './settings-search-keywords' - -export function getAgentRowConversationNamesTitle(): string { - return translate( - 'auto.components.settings.agent-row-conversation-name-copy.title', - 'Label agent rows with conversation names' - ) -} - -export function getAgentRowConversationNamesDescription(): string { - return translate( - 'auto.components.settings.agent-row-conversation-name-copy.description', - 'Show conversation names on sidebar and dashboard agent rows instead of the last message. Rows without a usable name keep the last message.' - ) -} - -export function getAgentRowConversationNamesSearchKeywords(): string[] { - return searchKeywords([ - { key: 'auto.components.settings.agents.search.96ba2373b6', fallback: 'agent' }, - { key: 'auto.components.settings.agents.search.conversation', fallback: 'conversation' }, - { key: 'auto.components.settings.agents.search.966890236d', fallback: 'name' }, - { - key: 'auto.components.settings.agents.search.conversationName', - fallback: 'conversation name' - }, - { key: 'auto.components.settings.agents.search.sidebar', fallback: 'sidebar' }, - { key: 'auto.components.settings.agents.search.dashboard', fallback: 'dashboard' }, - { key: 'auto.components.settings.agents.search.activityRow', fallback: 'activity' }, - { key: 'auto.components.settings.agents.search.lastMessage', fallback: 'last message' }, - { key: 'auto.components.settings.agents.search.c64059f50d', fallback: 'prompt' }, - { key: 'auto.components.settings.agents.search.5784ae8c43', fallback: 'rename' }, - { key: 'auto.components.settings.agents.search.rowLabel', fallback: 'label' } - ]) -} diff --git a/src/renderer/src/components/settings/agents-search.ts b/src/renderer/src/components/settings/agents-search.ts index 4e6b952bea5..5e378d732d1 100644 --- a/src/renderer/src/components/settings/agents-search.ts +++ b/src/renderer/src/components/settings/agents-search.ts @@ -9,11 +9,6 @@ import { getAgentGeneratedTabTitlesSearchKeywords, getAgentGeneratedTabTitlesTitle } from './agent-generated-tab-title-copy' -import { - getAgentRowConversationNamesDescription, - getAgentRowConversationNamesSearchKeywords, - getAgentRowConversationNamesTitle -} from './agent-row-conversation-name-copy' import { getAgentStatusHooksDescription, getAgentStatusHooksSearchKeywords, @@ -116,11 +111,6 @@ const getAllAgentsPaneSearchEntries = createLocalizedCatalog(() => [ description: getAgentGeneratedTabTitlesDescription(), keywords: getAgentGeneratedTabTitlesSearchKeywords() }, - { - title: getAgentRowConversationNamesTitle(), - description: getAgentRowConversationNamesDescription(), - keywords: getAgentRowConversationNamesSearchKeywords() - }, { title: getAgentAwakeTitle(), description: getAgentAwakeDescription(), diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 9038eff8fc9..2d72c47052b 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -7301,14 +7301,7 @@ "agentPermissions": "Agent Permissions", "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual.", "agentRuntime": "Agent Runtime", - "agentRuntimeDescription": "Choose whether agents are detected and launched on Windows or in WSL by default.", - "conversation": "conversation", - "conversationName": "conversation name", - "sidebar": "sidebar", - "dashboard": "dashboard", - "activityRow": "activity", - "lastMessage": "last message", - "rowLabel": "label" + "agentRuntimeDescription": "Choose whether agents are detected and launched on Windows or in WSL by default." } }, "appearance": { @@ -9315,10 +9308,6 @@ "title": "Editor Font Family", "description": "Font used by file editors and diff views. Leave empty to follow the terminal font.", "placeholder": "Same as terminal font" - }, - "agent-row-conversation-name-copy": { - "title": "Label agent rows with conversation names", - "description": "Show conversation names on sidebar and dashboard agent rows instead of the last message. Rows without a usable name keep the last message." } }, "right": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 68c8b1d2c06..bd62fbdf397 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -7241,14 +7241,7 @@ "agentPermissions": "Permisos de agentes", "agentPermissionsDescription": "Cambia los permisos predeterminados de los agentes entre Yolo y Manual.", "agentRuntime": "Runtime del agente", - "agentRuntimeDescription": "Elige si los agentes se detectan y ejecutan en Windows o en WSL de forma predeterminada.", - "conversation": "conversation", - "conversationName": "conversation name", - "sidebar": "sidebar", - "dashboard": "dashboard", - "activityRow": "activity", - "lastMessage": "last message", - "rowLabel": "label" + "agentRuntimeDescription": "Elige si los agentes se detectan y ejecutan en Windows o en WSL de forma predeterminada." } }, "appearance": { @@ -9292,10 +9285,6 @@ "title": "Editor Font Family", "description": "Font used by file editors and diff views. Leave empty to follow the terminal font.", "placeholder": "Same as terminal font" - }, - "agent-row-conversation-name-copy": { - "title": "Label agent rows with conversation names", - "description": "Show conversation names on sidebar and dashboard agent rows instead of the last message. Rows without a usable name keep the last message." } }, "right": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index ff0e5ee517f..f1e78a1098f 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -7263,14 +7263,7 @@ "agentPermissions": "Agent Permissions", "agentPermissionsDescription": "Switch agent permission defaults between Yolo and Manual.", "agentRuntime": "Agent のランタイム", - "agentRuntimeDescription": "デフォルトで agents を Windows または WSL のどちらで検出および起動するかを選択します。", - "conversation": "conversation", - "conversationName": "conversation name", - "sidebar": "sidebar", - "dashboard": "dashboard", - "activityRow": "activity", - "lastMessage": "last message", - "rowLabel": "label" + "agentRuntimeDescription": "デフォルトで agents を Windows または WSL のどちらで検出および起動するかを選択します。" } }, "appearance": { @@ -9292,10 +9285,6 @@ "title": "Editor Font Family", "description": "Font used by file editors and diff views. Leave empty to follow the terminal font.", "placeholder": "Same as terminal font" - }, - "agent-row-conversation-name-copy": { - "title": "Label agent rows with conversation names", - "description": "Show conversation names on sidebar and dashboard agent rows instead of the last message. Rows without a usable name keep the last message." } }, "right": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 9d6b71b981a..62906a68abf 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -7226,14 +7226,7 @@ "agentPermissions": "Agent 권한", "agentPermissionsDescription": "agent 권한 기본값을 Yolo와 수동 사이에서 전환합니다.", "agentRuntime": "Agent 런타임", - "agentRuntimeDescription": "기본적으로 Windows 또는 WSL에서 agents를 감지하고 시작할지 선택합니다.", - "conversation": "conversation", - "conversationName": "conversation name", - "sidebar": "sidebar", - "dashboard": "dashboard", - "activityRow": "activity", - "lastMessage": "last message", - "rowLabel": "label" + "agentRuntimeDescription": "기본적으로 Windows 또는 WSL에서 agents를 감지하고 시작할지 선택합니다." } }, "appearance": { @@ -9292,10 +9285,6 @@ "title": "Editor Font Family", "description": "Font used by file editors and diff views. Leave empty to follow the terminal font.", "placeholder": "Same as terminal font" - }, - "agent-row-conversation-name-copy": { - "title": "Label agent rows with conversation names", - "description": "Show conversation names on sidebar and dashboard agent rows instead of the last message. Rows without a usable name keep the last message." } }, "right": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 1138b5afcae..f476ce531a5 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -7226,14 +7226,7 @@ "agentPermissions": "智能体权限", "agentPermissionsDescription": "在 Yolo 和手动之间切换智能体权限默认值。", "agentRuntime": "智能体运行时", - "agentRuntimeDescription": "选择默认在 Windows 还是 WSL 中检测并启动智能体。", - "conversation": "conversation", - "conversationName": "conversation name", - "sidebar": "sidebar", - "dashboard": "dashboard", - "activityRow": "activity", - "lastMessage": "last message", - "rowLabel": "label" + "agentRuntimeDescription": "选择默认在 Windows 还是 WSL 中检测并启动智能体。" } }, "appearance": { @@ -9292,10 +9285,6 @@ "title": "Editor Font Family", "description": "Font used by file editors and diff views. Leave empty to follow the terminal font.", "placeholder": "Same as terminal font" - }, - "agent-row-conversation-name-copy": { - "title": "Label agent rows with conversation names", - "description": "Show conversation names on sidebar and dashboard agent rows instead of the last message. Rows without a usable name keep the last message." } }, "right": { diff --git a/src/shared/agent-row-conversation-name.test.ts b/src/shared/agent-row-conversation-name.test.ts index 0c579fa5a46..d838ff53dcf 100644 --- a/src/shared/agent-row-conversation-name.test.ts +++ b/src/shared/agent-row-conversation-name.test.ts @@ -116,6 +116,9 @@ describe('getAgentRowConversationName', () => { it('rejects empty, glyph-only, and default terminal titles', () => { expect(getAgentRowConversationName(makeTab(), 'claude', false)).toBeNull() expect(getAgentRowConversationName(makeTab({ title: '✳' }), 'claude', false)).toBeNull() + expect( + getAgentRowConversationName(makeTab({ title: 'Terminal 1' }), 'claude', false) + ).toBeNull() expect( getAgentRowConversationName( makeTab({ title: 'Terminal 2', defaultTitle: 'Terminal 2' }), diff --git a/src/shared/agent-row-conversation-name.ts b/src/shared/agent-row-conversation-name.ts index 7e0e441f876..95e5dfcfb28 100644 --- a/src/shared/agent-row-conversation-name.ts +++ b/src/shared/agent-row-conversation-name.ts @@ -38,6 +38,7 @@ const AGENT_IDENTITY_ALIASES_LOWER: Readonly> } const STATUS_WITH_CONTEXT_RE = /^(?:ready|idle|done)(?:\s+\([^)]*\))?$/i +const DEFAULT_TERMINAL_TITLE_RE = /^terminal \d+$/i function isIdentityStatusTitle(titleLower: string, identityLower: string): boolean { return ( @@ -93,6 +94,7 @@ function conversationNameFromLiveTitle( lower === FALLBACK_TAB_TITLE_LOWER || isAgentIdentityStatusTitle(lower, agentType, agentTypeLabelLower) || STATUS_WITH_CONTEXT_RE.test(stripped) || + DEFAULT_TERMINAL_TITLE_RE.test(stripped) || isClaudeManagementTitle(stripped) || isCwdLikeTitle(stripped) ) { diff --git a/src/shared/constants.ts b/src/shared/constants.ts index f80373fcc93..41b290eb06f 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -321,7 +321,6 @@ export function getDefaultSettings(homedir: string): GlobalSettings { agentYoloDefaultsMigrated: true, agentStatusHooksEnabled: true, tabAutoGenerateTitle: false, - agentRowsUseConversationName: false, confirmClosePinnedTab: true, keepComputerAwakeWhileAgentsRun: false, // Why: 'auto' probes keyboard layout so non-US users can type Option chars like @/€/[ out of the box (issue #903). See src/renderer/src/lib/keyboard-layout/*. diff --git a/src/shared/types.ts b/src/shared/types.ts index 3e0c903741c..437f92d6535 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -2868,10 +2868,6 @@ export type GlobalSettings = { dismissedSkillFreshnessNudges?: string[] /** Why: generated tab titles are subjective, so they stay opt-in and manual renames win. */ tabAutoGenerateTitle: boolean - /** Sidebar/dashboard agent rows label with the conversation name (tab rename or - * agent-set title) instead of the last message; rows without a usable name - * keep the last-message label. Opt-in: the live prompt stays the default. */ - agentRowsUseConversationName?: boolean /** Why: pinned tabs can still be closed via keyboard/native-menu; this gates that behind a confirmation. Defaults on. */ confirmClosePinnedTab: boolean /** When true, Orca requests local awake assertions while hook-reported agents are working. */