Repository navigation
Conversation
|
No actionable comments were generated in the recent review. ℹ️ Recent review info
Walkthrough
Priority: Severity of issue fixed: Medium Merge Risk:
|
There was a problem hiding this comment.
ℹ️ No critical issues — one scope question and a small test nitpick.
Reviewed changes
- Source pane recorded on the tab —
TerminalTab.generatedTitlePaneKeyis written alongsidegeneratedTitleinapplyGeneratedTabTitleUpdates, persisted throughterminalTabSchemawith.catch(undefined), and compared byterminalTabEqual. - Single ownership rule —
isGeneratedTabTitleOwnedByPaneinshared/agent-tab-title.tsgates the generated title on the row's pane key, falling back to tab-wide for untagged titles or callers that pass no pane. - Applied at both row producers —
getAgentRowConversationName(worktree-card rows, dashboard rows, board snapshot) andgetActivityThreadTaskTitle(Agents list, Activity page) now pass the row'spaneKey. - Paired web client —
buildMirroredTerminalTabscarries the client-local source pane only when the existing tab id matches the mirrored local id, andterminalTabEqualcompares the new field.
I verified the tests actually pin the behavior: all 7 changed test files pass (85 tests) with the change, and reverting the 10 non-test files makes 8 new assertions fail across 7 files. tc:web passes, and the related activity / web-session-tabs-sync / generated-title / dashboard suites pass (256 tests).
ℹ️ Headless mobile projection still stamps the tab's generated title on every leaf
The PR body says "the host transport never carried generatedTitle", but the headless path does carry it. buildHeadlessMobileSessionTerminalTabs emits one surface per leaf and sets each leaf's title to tab.generatedTitle, so a headless-host split tab still shows one pane's generated title on every leaf — the same class of mislabel this PR fixes for rows. The renderer publication path deliberately scopes the tab-wide title to the launch-agent leaf only, so the two paths disagree.
Technical details
# Headless mobile projection applies the tab's generated title to every leaf
## Affected sites
- `src/main/runtime/mobile-session-terminal-projection.ts:26-31` — `title = customTitle || generatedTitle || title || defaultTitle` is computed per leaf, so all leaves of a split tab get the tab's `generatedTitle`.
- Contrast `src/renderer/src/runtime/sync-runtime-graph/mobile-session-terminal-tabs.ts:78-86` — the renderer path gates the tab-wide title on `tabWideFallbackSafe` (`launchAgentLeafId === leafId`), so only the launch-agent leaf receives it.
## Required outcome
- Decide whether a headless-host mobile surface should follow the same pane ownership as the desktop rows. If yes, the projection needs the leaf id (`tab.id::leafId` already carries it) compared against `generatedTitlePaneKey`; if no, the PR description's "host transport never carried generatedTitle" claim should be corrected.
## Open questions for the human
- Is the mobile per-leaf surface intentionally tab-wide (mirroring the tab strip), or should it match the row behavior this PR establishes?ℹ️ Nitpicks
- The second assertion in
mirrored-generated-title.test.ts("drops a source pane recorded under another tab id so the title is not hidden") passes without the production change — when the field does not exist, no pane key is ever carried. It still guards against an unconditional copy, but the PR's "every new assertion fails without the production change" claim does not hold for it.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Thanks for the review. On the headless projection: On the nitpick: agreed, that guard passes without the change. I've reworded the testing note. |
8490b06 to
b257096
Compare
b257096 to
b782476
Compare
…t produced it The auto-generated tab title comes from one pane's agent prompt but is stored tab-wide, so every agent row in a split tab showed it. Record the pane that produced the title and let only that pane's rows use it: workspace-card and dashboard rows, the board snapshot, and the Agents list. Titles saved before the source was recorded keep today's behavior.
…ace layout model The workspace layout model writes terminal rows from its own fields, so a runtime-started pane that saved the partition dropped generatedTitlePaneKey from every tab in it, and the disk-field table no longer type-checked. Load, save and publish the source pane with the generated title. A generated title set by command names no pane, so it clears the source. Also import the surface-id helper from shared/, as the renderer re-export was removed.
… the rebase A generated rename now replaces an existing title only with replaceExisting, and buildMirroredTerminalTabs takes the live PTY ids before the sort offset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… own pane The chat names a terminal sender as its sidebar row does, but it did not pass the sender's pane, so a split sibling's messages carried the title another pane's prompt produced. Pass the pane key the naming source already resolves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A paired server's frames move a terminal tab to the desktop's surface id, but the title's source pane kept the server's tab id, so no row matched it and the title left every agent row. Translate it both ways with the tab; the leaf is shared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
stablyai#31602 made `rowConversationName` take the chat tab names and unified tabs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
66ae4eb to
4d8b54d
Compare

ELI5
With "Auto-generate tab titles" on, Orca names a tab after the first agent prompt it sees. When a tab is split and each pane runs its own agent, every agent row for that tab shows that one name, so the second agent is listed under the first agent's task. Orca now remembers which pane the name came from, and only that pane's rows use it.
What Changed
TerminalTab.generatedTitlePaneKey(optional) records the pane whose prompt producedgeneratedTitle.applyGeneratedTabTitleUpdatessets it together with the title..catch(undefined), so a malformed value can't drop the tab on restore.shared/workspace-layout/) loads, saves and publishes it with the title, so a runtime-started pane that rewrites the partition keeps it. A generated title set byrenameTabnames no pane, so it clears the source.isGeneratedTabTitleOwnedByPane(tab, paneKey)inshared/agent-tab-title.tsis the single rule:getAgentRowConversationNametakespaneKey.useAgentRowConversationNameandrowConversationNamepass the row's pane key, which covers worktree-card rows, dashboard rows and the board snapshot.getActivityThreadTaskTitlereadsentry.paneKey, which covers the Agents list and the Activity page.orchestrationSenderNamepasses the sender's pane, so a split sibling's orchestration messages don't carry another pane's title.terminalTabEqualcompares the new field.server-layout-frame-ids.ts) translate the source with its tab: the tab id changes between the server's and the desktop's id, and the leaf stays. An unreadable source is dropped, so the title stays tab-wide.Why
generatedTitleis stored once per tab but derived from one pane's prompt.Known limits (out of scope):
ORCA_LAYOUT_OWNER=main, main titles agent tabs throughrenameTabs(Main titles agent tabs and applies phone tab moves itself, and tab moves name the tab they follow (dark, layout-owner build only) #31812), which names no pane, so those titles fall back to today's tab-wide behavior. Carrying the pane through that command can follow once that path ships.Linked Issue
Fixes #20856
Visual Proof
Headless Electron e2e build, two Codex agents,
tabAutoGenerateTitleon.The Agents list matches the worktree-card rows in each case.
Worktree card rows (split tab, both agents start)
Agents list (same scenario)
Left pane names the tab first, then split, then the right agent starts (after)
Testing
agent-generated-tab-title-pane.test.ts: the writer records the source, a later sibling doesn't take it, and a dispatch replacement moves it.workspace-session-schema.test.ts: the field persists, and a malformed value keeps the tab.agent-row-conversation-name.test.ts,use-agent-row-conversation-name.test.ts,dashboard-card-labels.test.ts: the producer keeps the title, a sibling shows its own name, and an omitted pane keeps today's behavior.activity-thread-display.test.ts: the Agents-list title.mirrored-generated-title.test.ts: the web client keeps the source only under the same tab id.runtime-orchestration-sender-names.test.ts: a sender gets the title only from its own pane.server-layout-frame-ids.test.ts: the source follows its tab's id from server to desktop and back.workspace-layout-commands.test.tscovers therenameTabclear.renameTab, where main added the first-title rule (The window shows tab, title, move and divider changes only from main's update, never drawing them itself (dark, layout-owner build only) #31257). Both rules are kept: a generated rename withoutreplaceExistingis still refused, and an accepted one clears the source pane.renameTabintoworkspace-layout-tab-rename.ts, so the source-pane clear moved with it. It also maderowConversationNametake two more arguments, which the dashboard test now passes. The other conflict was the comment cleanup ([comment audit] Complete repository-wide comment cleanup #33249) inactivity-thread-display.ts.pnpm typecheckandpnpm lintpass locally.pnpm run build:desktoppassed through the CLI, Electron and web bundles on the Oct 16 base; I didn't rerun it after this rebase. Its last step (mobile-web) needs themobile/dependencies, which my checkout doesn't have.src/shared,src/main/runtime,src/main/persistence/terminal-topology, and the renderer'sruntime,store,app-shell,lib,components/dashboardandcomponents/activity: 46,608 tests pass. One test fails (structured-claude-thinking-display-refusal.test.ts), and it fails the same way on main on my machine. The run also reports unhandledEnvironmentTeardownErrors from late module loads in store and worktree tests; main reports the same kind when I run those files there.AI Disclosure
I used Claude Code (Claude Opus 5, and Opus 5.5 for the rebase follow-up) to help investigate the bug, write the change and tests, and run adversarial reviews of the diff. I reviewed every change myself and verified it locally, including the e2e scenarios above.
Review
generatedTitle. Old sessions and old clients keep today's behavior. There is no RPC change.layout.subscribestream, as feat(layout): publish each pane's live terminal title on the layout stream #28928 did for the paneliveTitle. Frame decoding checks only the top-level shape, so a reader that doesn't know the field ignores it and keeps today's tab-wide behavior.conversationNamekeeps its shape and meaning. Mobile per-leaf surface titles are not changed here; the headless-host projection (buildHeadlessMobileSessionTerminalTabs) still uses the tab-level title.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)