Materialize mobile-only pending session terminals (#6465)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson
2026-06-26 20:16:52 -07:00
committed by GitHub
co-authored by Orca
parent 1f968b92c9
commit bf4ed350ae
2 changed files with 325 additions and 2 deletions
+315
View File
@@ -11811,6 +11811,7 @@ describe('OrcaRuntimeService', () => {
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
ptyId: 'pty-pane-1',
title: 'left',
isActive: false
},
@@ -11819,6 +11820,7 @@ describe('OrcaRuntimeService', () => {
id: 'tab-1::pane:2',
parentTabId: 'tab-1',
leafId: 'pane:2',
ptyId: 'pty-pane-2',
title: 'right',
isActive: true
}
@@ -11826,6 +11828,8 @@ describe('OrcaRuntimeService', () => {
}
]
})
runtime.registerPty('pty-pane-1', TEST_WORKTREE_ID)
runtime.registerPty('pty-pane-2', TEST_WORKTREE_ID)
const activated = await runtime.activateMobileSessionTab(
`id:${TEST_WORKTREE_ID}`,
@@ -11846,6 +11850,317 @@ describe('OrcaRuntimeService', () => {
])
})
it('materializes pending mobile session terminals without focusing desktop clients', async () => {
const persistedPtyId = `${TEST_WORKTREE_ID}@@mobile-only-pty`
const spawn = vi.fn().mockResolvedValue({ id: persistedPtyId })
const focusTerminal = vi.fn()
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
{
id: 'host-tab',
ptyId: persistedPtyId,
worktreeId: TEST_WORKTREE_ID,
title: 'Persisted Terminal',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
terminalLayoutsByTabId: {
'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: persistedPtyId })
}
})
)
const runtime = new OrcaRuntimeService(runtimeStore as never)
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal,
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
runtime.setPtyController({
spawn,
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
const activated = await runtime.activateMobileSessionTab(
`id:${TEST_WORKTREE_ID}`,
'host-tab',
HEADLESS_LEAF_ID,
{ notifyClients: false }
)
expect(focusTerminal).not.toHaveBeenCalled()
expect(spawn).toHaveBeenCalledWith(
expect.objectContaining({
worktreeId: TEST_WORKTREE_ID,
tabId: 'host-tab',
leafId: HEADLESS_LEAF_ID,
sessionId: persistedPtyId,
persistHostSessionBinding: true
})
)
expect(activated.tabs).toEqual([
expect.objectContaining({
id: `host-tab::${HEADLESS_LEAF_ID}`,
isActive: true,
status: 'ready',
terminal: expect.any(String)
})
])
})
it('materializes phone-local pending terminal tabs without stored PTY bindings', async () => {
const spawn = vi.fn().mockResolvedValue({ id: 'fresh-mobile-pty' })
const focusTerminal = vi.fn()
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
{
id: 'host-tab',
ptyId: null,
worktreeId: TEST_WORKTREE_ID,
title: 'Persisted Terminal',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
terminalLayoutsByTabId: {
'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: undefined })
}
})
)
const runtime = new OrcaRuntimeService(runtimeStore as never)
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal,
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
runtime.setPtyController({
spawn,
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
const activated = await runtime.activateMobileSessionTab(
`id:${TEST_WORKTREE_ID}`,
'host-tab',
HEADLESS_LEAF_ID,
{ notifyClients: false }
)
expect(focusTerminal).not.toHaveBeenCalled()
expect(spawn).toHaveBeenCalledWith(
expect.objectContaining({
worktreeId: TEST_WORKTREE_ID,
tabId: 'host-tab',
leafId: HEADLESS_LEAF_ID,
sessionId: expect.stringMatching(/^serve-/),
persistHostSessionBinding: true
})
)
expect(activated.tabs).toEqual([
expect.objectContaining({
id: `host-tab::${HEADLESS_LEAF_ID}`,
status: 'ready',
terminal: expect.any(String)
})
])
})
it('keeps the target group active when phone-local activation materializes a tab', async () => {
const spawn = vi.fn().mockResolvedValue({ id: 'group-target-pty' })
const focusTerminal = vi.fn()
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
activeTabIdByWorktree: { [TEST_WORKTREE_ID]: 'host-tab' },
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
{
id: 'host-tab',
ptyId: null,
worktreeId: TEST_WORKTREE_ID,
title: 'Left',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
},
{
id: 'host-tab-2',
ptyId: null,
worktreeId: TEST_WORKTREE_ID,
title: 'Right',
customTitle: null,
color: null,
sortOrder: 1,
createdAt: 2
}
]
},
terminalLayoutsByTabId: {
'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: undefined }),
'host-tab-2': makeHeadlessTerminalLayout({ [HEADLESS_SECOND_LEAF_ID]: undefined })
},
tabGroups: {
[TEST_WORKTREE_ID]: [
{
id: 'group-left',
worktreeId: TEST_WORKTREE_ID,
activeTabId: 'host-tab',
tabOrder: ['host-tab']
},
{
id: 'group-right',
worktreeId: TEST_WORKTREE_ID,
activeTabId: 'host-tab-2',
tabOrder: ['host-tab-2']
}
]
}
})
)
const runtime = new OrcaRuntimeService(runtimeStore as never)
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal,
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
runtime.setPtyController({
spawn,
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
const activated = await runtime.activateMobileSessionTab(
`id:${TEST_WORKTREE_ID}`,
'host-tab-2',
HEADLESS_SECOND_LEAF_ID,
{ notifyClients: false }
)
expect(focusTerminal).not.toHaveBeenCalled()
expect(activated.activeGroupId).toBe('group-right')
expect(activated.tabGroups).toEqual([
expect.objectContaining({ id: 'group-left', activeTabId: 'host-tab' }),
expect.objectContaining({ id: 'group-right', activeTabId: 'host-tab-2' })
])
expect(activated.activeTabId).toBe(`host-tab-2::${HEADLESS_SECOND_LEAF_ID}`)
})
it('refreshes stale daemon liveness before phone-local terminal materialization', async () => {
const stalePtyId = `${TEST_WORKTREE_ID}@@stale-mobile-pty`
const spawn = vi.fn().mockResolvedValue({ id: stalePtyId })
const listProcesses = vi.fn(async () => [])
const focusTerminal = vi.fn()
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
{
id: 'host-tab',
ptyId: stalePtyId,
worktreeId: TEST_WORKTREE_ID,
title: 'Persisted Terminal',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
},
terminalLayoutsByTabId: {
'host-tab': makeHeadlessTerminalLayout({ [HEADLESS_LEAF_ID]: stalePtyId })
}
})
)
const runtime = new OrcaRuntimeService(runtimeStore as never)
runtime.registerPty(stalePtyId, TEST_WORKTREE_ID)
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal,
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
runtime.setPtyController({
spawn,
write: () => true,
kill: () => true,
getForegroundProcess: async () => null,
listProcesses
})
const activated = await runtime.activateMobileSessionTab(
`id:${TEST_WORKTREE_ID}`,
'host-tab',
HEADLESS_LEAF_ID,
{ notifyClients: false }
)
expect(listProcesses).toHaveBeenCalled()
expect(focusTerminal).not.toHaveBeenCalled()
expect(spawn).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: stalePtyId,
tabId: 'host-tab',
leafId: HEADLESS_LEAF_ID
})
)
expect(activated.tabs).toEqual([
expect.objectContaining({
id: `host-tab::${HEADLESS_LEAF_ID}`,
status: 'ready',
terminal: expect.any(String)
})
])
})
it('closes browser mobile session tabs when addressed by browser workspace id', async () => {
const closeSessionTab = vi.fn()
const runtime = new OrcaRuntimeService(store)
+10 -2
View File
@@ -3665,6 +3665,7 @@ export class OrcaRuntimeService {
const worktreeId =
explicitWorktreeId ?? (await this.resolveWorktreeSelector(worktreeSelector)).id
this.hydrateHeadlessMobileSessionTabsFromWorkspaceSession(worktreeId)
await this.refreshMobileSessionPtyRecords()
const snapshot = this.mobileSessionTabsByWorktree.get(worktreeId)
const directTab = snapshot?.tabs.find((candidate) => candidate.id === tabId)
const tab = leafId
@@ -3692,13 +3693,19 @@ export class OrcaRuntimeService {
)
// Why: serve-created tabs can be visible before any renderer has adopted
// their tab id, so focusing the renderer would silently no-op.
// Phone-local activation also needs this path for inactive restored tabs:
// desktop focus is intentionally suppressed, but the PTY still must exist.
const shouldMaterializePendingTerminal =
publicTab?.type === 'terminal' &&
publicTab.status !== 'ready' &&
(!this.notifier?.focusTerminal ||
(opts.notifyClients === false ||
!this.notifier?.focusTerminal ||
this.shouldMaterializeHeadlessMobileSessionTab(snapshot!, tab))
if (shouldMaterializePendingTerminal) {
const sessionId = tab.ptyId ?? tab.parentLayout?.ptyIdsByLeafId?.[tab.leafId] ?? undefined
const targetGroupId = snapshot?.tabGroups?.find((group) =>
group.tabOrder.includes(tab.parentTabId)
)?.id
try {
await this.createHeadlessMobileSessionTerminal(
worktreeId,
@@ -3712,7 +3719,8 @@ export class OrcaRuntimeService {
leafId: tab.leafId,
sessionId
},
tab.launchAgent
tab.launchAgent,
targetGroupId
)
} catch (err) {
if (sessionId && parseAppSshPtyId(sessionId)) {