mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
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.
This commit is contained in:
@@ -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<DashboardAgentRow>)
|
||||
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')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
<AgentGeneratedTabTitlesSetting settings={settings} updateSettings={updateSettings} />
|
||||
|
||||
<AgentRowConversationNamesSetting settings={settings} updateSettings={updateSettings} />
|
||||
|
||||
<AgentAwakeSetting settings={settings} updateSettings={updateSettings} />
|
||||
|
||||
<AgentCacheTimerSection settings={settings} updateSettings={updateSettings} />
|
||||
@@ -1009,25 +1003,3 @@ export function AgentGeneratedTabTitlesSetting({
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export function AgentRowConversationNamesSetting({
|
||||
settings,
|
||||
updateSettings
|
||||
}: AgentsPaneProps): React.JSX.Element {
|
||||
const enabled = settings.agentRowsUseConversationName === true
|
||||
return (
|
||||
<section className="space-y-3">
|
||||
<SettingsSwitchRow
|
||||
label={getAgentRowConversationNamesTitle()}
|
||||
description={getAgentRowConversationNamesDescription()}
|
||||
checked={enabled}
|
||||
onChange={() =>
|
||||
updateSettings({
|
||||
agentRowsUseConversationName: !enabled
|
||||
})
|
||||
}
|
||||
ariaLabel={getAgentRowConversationNamesTitle()}
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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' }
|
||||
])
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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' }),
|
||||
|
||||
@@ -38,6 +38,7 @@ const AGENT_IDENTITY_ALIASES_LOWER: Readonly<Record<string, readonly string[]>>
|
||||
}
|
||||
|
||||
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)
|
||||
) {
|
||||
|
||||
@@ -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/*.
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user