mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 08:03:20 +00:00
fix(native-chat): let a structured chat tab be renamed
Renaming a native chat tab accepted the text and silently did nothing: setTabCustomTitle only scanned terminal tabs and only bridged to unified tabs whose contentType was 'terminal', so the agent-session tab it was keyed to never matched. Any label that did land was then re-nulled by the next host snapshot, which preserved color/createdAt/isPinned but not customLabel. Also routes both placeholder sites through one helper so a Claude chat stops falling back to 'Codex Chat'.
This commit is contained in:
Generated
+19
@@ -6,6 +6,9 @@ importers:
|
||||
.:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
'@pnpm/exe':
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
pnpm:
|
||||
specifier: 12.0.0
|
||||
version: 12.0.0
|
||||
@@ -56,6 +59,11 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe@12.0.0':
|
||||
resolution: {integrity: sha512-405vw2qYPPghNxoPRt2cvkGtGNU5gnInDcIe5TztQxe73yUZLqFJN/jCJFGs1xbVaIWDXEVuQ71P8npFZeatZQ==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
pnpm@12.0.0:
|
||||
resolution: {integrity: sha512-ni49w5EZlYaNyUuBdcIXwn6VQI+gO0oidJd48rNPdzt3zdOznt6BcbIvzVO+ajU0Lp+smUimjvWN9kiM6Jp+Zw==}
|
||||
engines: {node: '>=18.*'}
|
||||
@@ -87,6 +95,17 @@ snapshots:
|
||||
'@pnpm/exe.win32-x64@12.0.0':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe@12.0.0':
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
'@pnpm/exe.darwin-x64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64': 12.0.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.0.0
|
||||
'@pnpm/exe.linux-x64': 12.0.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.0.0
|
||||
'@pnpm/exe.win32-arm64': 12.0.0
|
||||
'@pnpm/exe.win32-x64': 12.0.0
|
||||
|
||||
pnpm@12.0.0:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.darwin-arm64': 12.0.0
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
|
||||
import { defaultAgentChatLabel } from '../../shared/agent-session-chat-label'
|
||||
import { OrcaRuntimeWithResolveRecoveredStructuredTuiTranscript } from './orca-runtime-resolve-recovered-structured-tui-transcript'
|
||||
import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire/structured-agent-session-registry'
|
||||
import { collectSavedStructuredAgentSessionIds } from './saved-structured-agent-session-restoration'
|
||||
@@ -105,7 +106,7 @@ export class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRu
|
||||
const tab: RuntimeMobileSessionAgentTab = {
|
||||
type: 'agent-session',
|
||||
id,
|
||||
title: input.agent === 'claude' ? 'Claude Chat' : 'Codex Chat',
|
||||
title: defaultAgentChatLabel(input.agent),
|
||||
sessionId: input.sessionId,
|
||||
agent: input.agent,
|
||||
isActive: input.activate
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { RuntimeMobileSessionTabsResult } from '../../../../shared/runtime-types'
|
||||
import type { Tab } from '../../../../shared/tab-types'
|
||||
import { buildMirroredAgentTabs } from './terminal-surfaces'
|
||||
|
||||
const WORKTREE = 'repo-1::worktree-1'
|
||||
const GROUP = 'group-1'
|
||||
|
||||
function snapshotWith(agent: 'claude' | 'codex', title: string): RuntimeMobileSessionTabsResult {
|
||||
return {
|
||||
worktree: WORKTREE,
|
||||
publicationEpoch: 'epoch-1',
|
||||
snapshotVersion: 1,
|
||||
activeGroupId: GROUP,
|
||||
activeTabId: null,
|
||||
activeTabType: null,
|
||||
tabs: [
|
||||
{
|
||||
type: 'agent-session',
|
||||
id: 'host-tab-1',
|
||||
title,
|
||||
sessionId: `${agent}-1`,
|
||||
agent,
|
||||
isActive: false
|
||||
}
|
||||
]
|
||||
} as RuntimeMobileSessionTabsResult
|
||||
}
|
||||
|
||||
function build(
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
currentUnifiedTabs: readonly Tab[] = []
|
||||
): Tab {
|
||||
const [mirrored] = buildMirroredAgentTabs(
|
||||
snapshot,
|
||||
new Map(),
|
||||
GROUP,
|
||||
0,
|
||||
currentUnifiedTabs,
|
||||
1_000
|
||||
)
|
||||
return mirrored.unifiedTab
|
||||
}
|
||||
|
||||
describe('buildMirroredAgentTabs', () => {
|
||||
it('falls back to the agent-specific placeholder when the host publishes no title', () => {
|
||||
expect(build(snapshotWith('claude', '')).label).toBe('Claude Chat')
|
||||
expect(build(snapshotWith('codex', ' ')).label).toBe('Codex Chat')
|
||||
})
|
||||
|
||||
it('prefers the host title over the placeholder', () => {
|
||||
expect(build(snapshotWith('claude', 'Flaky retry test')).label).toBe('Flaky retry test')
|
||||
})
|
||||
|
||||
it('keeps a manual rename across host snapshots', () => {
|
||||
const snapshot = snapshotWith('codex', 'Codex Chat')
|
||||
const renamed = build(snapshot)
|
||||
const existing: Tab = { ...renamed, customLabel: 'My rename' }
|
||||
expect(build(snapshot, [existing]).customLabel).toBe('My rename')
|
||||
})
|
||||
|
||||
it('leaves customLabel null when the tab was never renamed', () => {
|
||||
expect(build(snapshotWith('codex', 'Codex Chat')).customLabel).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
RuntimeMobileSessionAgentTab
|
||||
} from '../../../../shared/runtime-types'
|
||||
import type { TerminalLayoutSnapshot, TerminalTab } from '../../../../shared/terminal-tab-types'
|
||||
import { defaultAgentChatLabel } from '../../../../shared/agent-session-chat-label'
|
||||
import { sanitizeTerminalLayoutPaneTitlesForLabels } from '@/lib/terminal-pane-title-sanitization'
|
||||
import { resolveTerminalLayoutRoot } from '../remote-terminal-layout-resolution'
|
||||
import { getRemoteRuntimePtyEnvironmentId } from '../runtime-terminal-stream'
|
||||
@@ -73,8 +74,10 @@ export function buildMirroredAgentTabs(
|
||||
worktreeId: snapshot.worktree,
|
||||
contentType: 'agent-session',
|
||||
agentSessionAgent: tab.agent,
|
||||
label: tab.title.trim() || 'Codex Chat',
|
||||
customLabel: null,
|
||||
label: tab.title.trim() || defaultAgentChatLabel(tab.agent),
|
||||
// Why: a manual rename lives only on the client; re-nulling it here made
|
||||
// every host snapshot silently discard the user's title.
|
||||
customLabel: existing?.customLabel ?? null,
|
||||
color: tab.color !== undefined ? tab.color : (existing?.color ?? null),
|
||||
sortOrder: sortOffset + index,
|
||||
createdAt: existing?.createdAt ?? now + sortOffset + index,
|
||||
|
||||
@@ -87,9 +87,12 @@ export function createTerminalTabAttentionActions(
|
||||
scheduleRuntimeGraphSync()
|
||||
return { tabsByWorktree: next }
|
||||
})
|
||||
const item = Object.values(get().unifiedTabsByWorktree)
|
||||
.flat()
|
||||
.find((entry) => entry.contentType === 'terminal' && entry.entityId === tabId)
|
||||
const unified = Object.values(get().unifiedTabsByWorktree).flat()
|
||||
// Why: a structured chat tab has no TerminalTab record, and its rename
|
||||
// arrives keyed by the unified tab id rather than a terminal entityId.
|
||||
const item =
|
||||
unified.find((entry) => entry.contentType === 'terminal' && entry.entityId === tabId) ??
|
||||
unified.find((entry) => entry.contentType === 'agent-session' && entry.id === tabId)
|
||||
if (item) {
|
||||
get().setTabCustomLabel(item.id, title, opts)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/** Placeholder tab label for a structured chat that has no conversation name yet. */
|
||||
export function defaultAgentChatLabel(agent: 'claude' | 'codex' | null | undefined): string {
|
||||
return agent === 'claude' ? 'Claude Chat' : 'Codex Chat'
|
||||
}
|
||||
Reference in New Issue
Block a user