Files
orca/src/main/runtime/orca-runtime-tests/mobile-session-tabs.spec.ts
T
Neil eedca5ee1c fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b) (#30811)
* fix(persistence): worktree meta and lineage writes take the row's host, not the id (R4-2b)

* fix(persistence): review fixes for host-targeted worktree writes

* test: SAFETY notes on partial runtime store mocks

* test: drop imports duplicated by the rebase onto #30797
2026-10-19 11:39:54 -07:00

642 lines
19 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import {
OrcaRuntimeService,
electronMocks,
getDefaultWorkspaceSession
} from '../orca-runtime-test-mocks.spec'
import type { WorktreeMeta } from '../orca-runtime-test-mocks.spec'
import {
TEST_REPO_ID,
TEST_WINDOW_ID,
TEST_WORKTREE_ID,
makeWorktreeMeta,
store
} from '../orca-runtime-test-fixtures.spec'
import {
hostWorktreeTarget,
type HostWorktreeTarget
} from '../../../shared/worktree/host-qualified-identity'
describe('OrcaRuntimeService', () => {
it('retires exited saved PTY bindings instead of publishing a pending ghost', async () => {
const runtime = new OrcaRuntimeService(store)
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:1',
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
title: 'Terminal 1',
ptyId: 'daemon-pty-1',
isActive: true
}
]
}
]
})
await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
runtime.onPtyExit('daemon-pty-1', 0)
const result = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
expect(result).toMatchObject({
activeGroupId: null,
activeTabId: null,
activeTabType: null,
tabs: []
})
})
it('resolves mobile terminal surfaces by exact split leaf', async () => {
const runtime = new OrcaRuntimeService(store)
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
title: 'Terminal 1',
activeLeafId: 'pane:2',
layout: null
}
],
leaves: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:1',
paneRuntimeId: 1,
ptyId: 'pty-1',
paneTitle: 'left'
},
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:2',
paneRuntimeId: 2,
ptyId: 'pty-2',
paneTitle: 'right'
}
],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:2',
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
title: 'Terminal 1',
isActive: false
},
{
type: 'terminal',
id: 'tab-1::pane:2',
parentTabId: 'tab-1',
leafId: 'pane:2',
title: 'Terminal 1',
isActive: true
}
]
}
]
})
const result = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
expect(result.tabs).toHaveLength(2)
expect(result.tabs).toEqual([
expect.objectContaining({ id: 'tab-1::pane:1', title: 'left', status: 'ready' }),
expect.objectContaining({ id: 'tab-1::pane:2', title: 'right', status: 'ready' })
])
const [left, right] = result.tabs
expect(left?.type).toBe('terminal')
expect(right?.type).toBe('terminal')
if (left?.type === 'terminal' && right?.type === 'terminal') {
expect(left.terminal).not.toBe(right.terminal)
}
})
it('keeps published mobile terminal handles usable across renderer graph epochs', async () => {
const runtime = new OrcaRuntimeService(store)
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
title: 'Terminal 1',
activeLeafId: 'pane:1',
layout: null
}
],
leaves: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:1',
paneRuntimeId: 1,
ptyId: 'pty-1',
paneTitle: 'Terminal 1'
}
],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:1',
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
title: 'Terminal 1',
isActive: true
}
]
}
]
})
const result = await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
const tab = result.tabs[0]
expect(tab?.type).toBe('terminal')
if (tab?.type !== 'terminal' || tab.status !== 'ready') {
throw new Error('expected ready terminal tab')
}
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
title: 'Terminal 1',
activeLeafId: 'pane:1',
layout: null
}
],
leaves: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:1',
paneRuntimeId: 1,
ptyId: 'pty-1',
paneTitle: 'Terminal 1'
}
],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-2',
snapshotVersion: 2,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:1',
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
title: 'Terminal 1',
isActive: true
}
]
}
]
})
runtime.onPtyData('pty-1', 'after graph sync\n', 100)
await expect(runtime.readTerminal(tab.terminal)).resolves.toMatchObject({
handle: tab.terminal,
tail: ['after graph sync']
})
})
it('closes the matching mobile terminal UUID leaf without closing the whole tab', async () => {
const closeTerminal = vi.fn()
const closeTerminalPane = vi.fn()
const kill = vi.fn(() => true)
const runtime = new OrcaRuntimeService(store)
runtime.setPtyController({
spawn: vi.fn(),
write: () => true,
kill,
getForegroundProcess: async () => null
})
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal: vi.fn(),
closeTerminal,
closeTerminalPane,
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const leftLeafId = '11111111-1111-4111-8111-111111111111'
const rightLeafId = '22222222-2222-4222-8222-222222222222'
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
title: 'Terminal 1',
activeLeafId: rightLeafId,
layout: null
}
],
leaves: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: leftLeafId,
paneRuntimeId: 1,
ptyId: 'pty-left',
paneTitle: 'left'
},
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: rightLeafId,
paneRuntimeId: 2,
ptyId: 'pty-right',
paneTitle: 'right'
}
],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: `tab-1::${rightLeafId}`,
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: `tab-1::${rightLeafId}`,
parentTabId: 'tab-1',
leafId: rightLeafId,
title: 'right',
isActive: true
}
]
}
]
})
await runtime.closeMobileSessionTab(`id:${TEST_WORKTREE_ID}`, `tab-1::${rightLeafId}`)
expect(kill).toHaveBeenCalledWith('pty-right')
// Only the leaf-addressed notice for the closed pane; never a whole-tab close.
expect(closeTerminalPane).toHaveBeenCalledExactlyOnceWith('tab-1', rightLeafId)
expect(closeTerminal).not.toHaveBeenCalled()
})
it('closes the whole mobile terminal tab when addressed by parent tab id', async () => {
const closeTerminal = vi.fn()
const kill = vi.fn(() => true)
const runtime = new OrcaRuntimeService(store)
runtime.setPtyController({
spawn: vi.fn(),
write: () => true,
kill,
getForegroundProcess: async () => null
})
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal: vi.fn(),
closeTerminal,
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
runtime.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
title: 'Terminal 1',
activeLeafId: 'pane:1',
layout: null
}
],
leaves: [
{
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:1',
paneRuntimeId: 1,
ptyId: 'pty-1',
paneTitle: 'Terminal 1'
}
],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:1',
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
title: 'Terminal 1',
isActive: true
}
]
}
]
})
await runtime.closeMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'tab-1')
expect(closeTerminal).toHaveBeenCalledWith('tab-1')
expect(kill).not.toHaveBeenCalled()
})
it('activates the active split leaf when addressed by parent tab id', async () => {
const focusTerminal = vi.fn()
const runtime = new OrcaRuntimeService(store)
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.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:2',
activeTabType: 'terminal',
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
title: 'left',
isActive: false
},
{
type: 'terminal',
id: 'tab-1::pane:2',
parentTabId: 'tab-1',
leafId: 'pane:2',
title: 'right',
isActive: true
}
]
}
]
})
await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'tab-1')
expect(focusTerminal).toHaveBeenCalledWith('tab-1', TEST_WORKTREE_ID, 'pane:2')
})
it('activates mobile session tabs without focusing desktop clients when requested', async () => {
const focusTerminal = vi.fn()
const runtime = new OrcaRuntimeService(store)
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.attachWindow(1)
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: 'tab-1::pane:2',
activeTabType: 'terminal',
tabGroups: [{ id: 'group-1', activeTabId: 'tab-1', tabOrder: ['tab-1'] }],
tabs: [
{
type: 'terminal',
id: 'tab-1::pane:1',
parentTabId: 'tab-1',
leafId: 'pane:1',
ptyId: 'pty-pane-1',
title: 'left',
isActive: false
},
{
type: 'terminal',
id: 'tab-1::pane:2',
parentTabId: 'tab-1',
leafId: 'pane:2',
ptyId: 'pty-pane-2',
title: 'right',
isActive: true
}
]
}
]
})
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}`,
'tab-1::pane:1',
undefined,
{ notifyClients: false }
)
expect(focusTerminal).not.toHaveBeenCalled()
expect(activated).toMatchObject({
activeTabId: 'tab-1::pane:1',
activeTabType: 'terminal',
tabGroups: [expect.objectContaining({ id: 'group-1', activeTabId: 'tab-1' })]
})
expect(activated.tabs).toEqual([
expect.objectContaining({ id: 'tab-1::pane:1', isActive: true }),
expect.objectContaining({ id: 'tab-1::pane:2', isActive: false })
])
})
it('clears unread metadata on mobile worktree activation without focusing desktop clients', async () => {
const metaById: Record<string, WorktreeMeta> = {
[TEST_WORKTREE_ID]: makeWorktreeMeta({ isUnread: true })
}
const setWorktreeMeta = vi.fn(
({ id: worktreeId }: HostWorktreeTarget, meta: Partial<WorktreeMeta>) => {
metaById[worktreeId] = { ...(metaById[worktreeId] ?? makeWorktreeMeta()), ...meta }
return metaById[worktreeId]
}
)
const activateWorktree = vi.fn()
const worktreesChanged = vi.fn()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a partial store mock; the code under test reads only the members it defines.
const runtime = new OrcaRuntimeService({
...store,
getAllWorktreeMeta: () => metaById,
getWorktreeMeta: (worktreeId: string) => metaById[worktreeId],
getWorktreeMetaForHost: (worktreeId: string) => metaById[worktreeId],
setWorktreeMeta
} as never)
runtime.setNotifier({
worktreesChanged,
reposChanged: vi.fn(),
activateWorktree,
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal: vi.fn(),
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
runtime.attachWindow(TEST_WINDOW_ID)
runtime.markGraphReady(TEST_WINDOW_ID)
await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, { notifyClients: false })
expect(setWorktreeMeta).toHaveBeenCalledWith(
hostWorktreeTarget({ id: TEST_WORKTREE_ID, hostId: 'local' }),
{ isUnread: false }
)
expect(metaById[TEST_WORKTREE_ID]?.isUnread).toBe(false)
expect(worktreesChanged).toHaveBeenCalledWith(TEST_REPO_ID)
expect(activateWorktree).not.toHaveBeenCalled()
})
it('wakes slept agents on the host renderer when a phone activates a worktree', async () => {
// Seed isUnread:false so the unread-clear branch stays quiet, isolating the mobile slept-agent wake.
const metaById: Record<string, WorktreeMeta> = {
[TEST_WORKTREE_ID]: makeWorktreeMeta({ isUnread: false })
}
const activateWorktree = vi.fn()
const resumeSleepingAgents = vi.fn()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a partial store mock; the code under test reads only the members it defines.
const runtime = new OrcaRuntimeService({
...store,
getAllWorktreeMeta: () => metaById,
getWorktreeMeta: (worktreeId: string) => metaById[worktreeId],
getWorktreeMetaForHost: (worktreeId: string) => metaById[worktreeId],
getWorkspaceSession: () => ({
...getDefaultWorkspaceSession(),
sleepingAgentSessionsByPaneKey: {
'tab-1:leaf-1': {
paneKey: 'tab-1:leaf-1',
tabId: 'tab-1',
worktreeId: TEST_WORKTREE_ID,
agent: 'codex',
providerSession: { key: 'session_id', id: 'session-1' },
prompt: 'test',
state: 'done',
capturedAt: 1,
updatedAt: 1,
origin: 'worktree-sleep'
}
}
})
} as never)
runtime.setNotifier({
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree,
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
focusTerminal: vi.fn(),
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
resumeSleepingAgents,
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
// A renderer must be attached to receive the wake; headless serve reports 'unsupported-headless' instead.
electronMocks.BrowserWindow.fromId.mockReturnValue({ isDestroyed: () => false } as never)
runtime.attachWindow(TEST_WINDOW_ID)
runtime.markGraphReady(TEST_WINDOW_ID)
const result = await runtime.activateManagedWorktree(`id:${TEST_WORKTREE_ID}`, {
notifyClients: false,
clientKind: 'mobile'
})
// INV-2: mobile wake never navigates the desktop (no activateWorktree); it routes through the renderer's navigation-free wake.
expect(resumeSleepingAgents).toHaveBeenCalledWith(TEST_WORKTREE_ID)
expect(activateWorktree).not.toHaveBeenCalled()
expect(result.sleepingAgentWake).toBe('requested')
})
})