mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
fix(terminal): do not let a stranded binding block another tab's relay wake
The binder index read the raw map, so a binding whose leaf had already left the tree — one that reattaches nothing — was enough to withhold a different tab's remote session restore, and the user silently lost it. The index now counts only leaves the layout owns, which is the same rule the ownership lookup applies.
This commit is contained in:
+24
@@ -204,6 +204,30 @@ describe('hydrating the STA-7961 duplicate binding', () => {
|
||||
await expect(rowPtyIdsAfterReconnect(session)).resolves.toEqual([SHARED_PTY_ID, null])
|
||||
})
|
||||
|
||||
it('still wakes a relay session only a stranded binding names', async () => {
|
||||
// The split tab's map still names the pty at a leaf its tree dropped, so it reattaches
|
||||
// nothing. Blocking on that would cost the single tab a real remote restore for no gain.
|
||||
const strandedPty = 'repo1::/wt-1@@relay-stranded'
|
||||
const base = duplicateLeafSession()
|
||||
const splitLayout = base.terminalLayoutsByTabId[SPLIT_TAB_ID]!
|
||||
const session: WorkspaceSessionState = {
|
||||
...base,
|
||||
remoteSessionIdsByTabId: { [SINGLE_TAB_ID]: strandedPty },
|
||||
terminalLayoutsByTabId: {
|
||||
...base.terminalLayoutsByTabId,
|
||||
[SPLIT_TAB_ID]: {
|
||||
...splitLayout,
|
||||
ptyIdsByLeafId: {
|
||||
...splitLayout.ptyIdsByLeafId,
|
||||
'ac1f6d20-1f3e-4c58-8f2b-0a9e7d4c3b15': strandedPty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(hydrate(session).pendingReconnectPtyIdByTabId[SINGLE_TAB_ID]).toBe(strandedPty)
|
||||
})
|
||||
|
||||
it('still wakes a relay session nothing else binds', async () => {
|
||||
const session = {
|
||||
...duplicateLeafSession(),
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { TerminalLayoutSnapshot } from '../../../../shared/terminal-tab-types'
|
||||
import type { Worktree } from '../../../../shared/worktree/types'
|
||||
import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types'
|
||||
import { collectOwnedLeafIds } from '@/components/terminal-pane/terminal-layout-leaf-claims'
|
||||
import { buildByIdIndex, buildWorktreeByIdIndex } from '../slices/worktree-by-id-index'
|
||||
import { resolvePrimaryLayoutPtyId } from './terminal-pty-identities'
|
||||
|
||||
@@ -81,9 +82,19 @@ export function buildWorkspaceTerminalReconnectPlan({
|
||||
// Why indexed: the relay wake handle is a separate fact from the layout, but it must not hand a
|
||||
// tab the very PTY another tab's healed layout binds — that is the duplicate mount reached
|
||||
// through a second door. A wake nothing else binds is untouched, which is every normal one.
|
||||
//
|
||||
// Why owned leaves only: a binding whose leaf left the tree reattaches nothing, so blocking on
|
||||
// it would cost a real remote session its restore for no gain. And why an id match is enough:
|
||||
// the row, the leaf binding and the relay handle are all written from one spawn id
|
||||
// (`updateTabPtyId`) and rewritten together by `ssh-target-id-migration.ts`, so the index
|
||||
// cannot miss on id form.
|
||||
const tabIdsBindingPtyId = new Map<string, Set<string>>()
|
||||
for (const [tabId, layout] of Object.entries(layoutsByTabId)) {
|
||||
for (const ptyId of Object.values(layout.ptyIdsByLeafId ?? {})) {
|
||||
const ownedLeafIds = collectOwnedLeafIds(layout)
|
||||
for (const [leafId, ptyId] of Object.entries(layout.ptyIdsByLeafId ?? {})) {
|
||||
if (!ptyId || !ownedLeafIds.has(leafId)) {
|
||||
continue
|
||||
}
|
||||
const binders = tabIdsBindingPtyId.get(ptyId)
|
||||
if (binders) {
|
||||
binders.add(tabId)
|
||||
|
||||
Reference in New Issue
Block a user