mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
fix(ssh): fold in partition tabs on demand, not only at startup
The ssh:<target> plane is LIVE — the CLI and headless surfaces still write it — but the hoist ran only in the Store constructor. A tab created there mid-session never reached local, so a reattach refused it as noTab and its shell ran on unbound with the tab invisible until the app restarted. Same defect class as the original report, with a narrower window. The reattach now re-hoists once on a noTab refusal and retries the bind before concluding no pane owns the shell. No polling, and it costs nothing on the path where local already knows the tab. Mutation-proofed: making the on-demand hoist a no-op reddens the new test.
This commit is contained in:
@@ -6590,6 +6590,25 @@ export class Store {
|
||||
return normalizeExecutionHostId(hostId) ?? LOCAL_EXECUTION_HOST_ID
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-run the partition hoist against current state.
|
||||
*
|
||||
* Why this is not construction-only: `ssh:<target>` is a LIVE plane. A tab the CLI or a headless
|
||||
* surface creates mid-session lands there, `local` never learns of it, and a reattach then
|
||||
* refuses it as `noTab` — the shell runs on unbound and its tab stays invisible until the app
|
||||
* restarts. Re-hoisting on that refusal closes the window without polling.
|
||||
*/
|
||||
hoistSshPartitionsNow(): boolean {
|
||||
const changed = hoistSshPartitionsIntoLocalSession(
|
||||
this.state,
|
||||
this.state.workspaceSessionsByHostId
|
||||
)
|
||||
if (changed) {
|
||||
this.scheduleSave()
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
/** Read once, immediately after a refused `persistPtyBinding`. Clearing on read keeps a stale
|
||||
* reason from being attributed to a later call that never set one. */
|
||||
consumePtyBindingRefusalReason(): PtyBindingRefusalReason | undefined {
|
||||
|
||||
@@ -439,4 +439,40 @@ describe('an SSH pane whose membership was persisted into the ssh partition', ()
|
||||
'a local-only layout edit reached through into the ssh partition'
|
||||
).toBe(PTY)
|
||||
})
|
||||
|
||||
// `ssh:<target>` is a LIVE plane: the CLI and headless surfaces still write it. A tab created
|
||||
// there mid-session is not in `local`, so a reattach refuses it as noTab and its shell runs on
|
||||
// unbound. Re-hoisting on demand closes that window without waiting for a restart.
|
||||
it('folds in a tab the partition gained mid-session, without a restart', async () => {
|
||||
const before = await createStore()
|
||||
before.setWorkspaceSession(paneSession() as never, SSH_PARTITION)
|
||||
before.flushOrThrow()
|
||||
stripHoistMarker()
|
||||
|
||||
const store = await reopenStore()
|
||||
expect(store.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? []).toHaveLength(1)
|
||||
|
||||
// The CLI adds a pane to the partition while the app is running.
|
||||
const grown = paneSession()
|
||||
grown.tabsByWorktree[WORKTREE] = [
|
||||
...grown.tabsByWorktree[WORKTREE],
|
||||
{ ...grown.tabsByWorktree[WORKTREE][0], id: 'tab-cli' }
|
||||
]
|
||||
grown.terminalLayoutsByTabId = {
|
||||
...grown.terminalLayoutsByTabId,
|
||||
'tab-cli': {
|
||||
root: { type: 'leaf' as const, leafId: LEAF_TWO },
|
||||
activeLeafId: LEAF_TWO,
|
||||
expandedLeafId: null,
|
||||
ptyIdsByLeafId: { [LEAF_TWO]: PTY }
|
||||
}
|
||||
} as typeof grown.terminalLayoutsByTabId
|
||||
store.setWorkspaceSession(grown as never, SSH_PARTITION)
|
||||
|
||||
expect(store.hoistSshPartitionsNow(), 'the on-demand hoist reported no change').toBe(true)
|
||||
expect(
|
||||
(store.getWorkspaceSession().tabsByWorktree?.[WORKTREE] ?? []).map((tab) => tab.id).sort(),
|
||||
'a tab the partition gained mid-session stayed invisible until restart'
|
||||
).toEqual(['tab-1', 'tab-cli'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2618,6 +2618,21 @@ export class SshRelaySession {
|
||||
} catch (error) {
|
||||
console.error('[ssh-relay-session] Failed to persist reconnect incarnation:', error)
|
||||
}
|
||||
if (bind?.bound === false && bind.refusalReason === 'noTab') {
|
||||
// The partition is a live plane, so a tab created there mid-session is not in `local` yet.
|
||||
// Fold it in and try once more before concluding no pane owns this shell.
|
||||
if (this.store?.hoistSshPartitionsNow()) {
|
||||
bind = bindPaneShell({
|
||||
store: this.store,
|
||||
worktreeId: lease.worktreeId,
|
||||
tabId,
|
||||
leafId: lease.leafId,
|
||||
ptyId: appPtyId,
|
||||
mayCreate: false,
|
||||
...(incarnationId ? { incarnationId } : {})
|
||||
})
|
||||
}
|
||||
}
|
||||
if (bind?.bound === false && bind.refusalReason === 'noTab') {
|
||||
// No durable tab owns this leaf, so there is nothing to publish it against — registering
|
||||
// would announce a pane that does not exist. Unresolved, not dead: the shell keeps running
|
||||
|
||||
Reference in New Issue
Block a user