fix: keep floating tabs local with active runtime

Keep the synthetic floating workspace local while a remote runtime is active, including terminal/browser creation, activation, close, and remote snapshot handling.

Maintainer follow-ups:
- require worktreeId for runtime-session terminal create payloads
- add renderer-backed terminal create reply sender regression coverage
- merge current main and keep the WSL readDir breadcrumb test aligned with main's Windows-only handler coverage
This commit is contained in:
Wolfie
2026-06-30 12:48:26 -07:00
committed by GitHub
parent 01070198fa
commit 62ab2d470e
22 changed files with 535 additions and 268 deletions
+99 -11
View File
@@ -7305,6 +7305,78 @@ describe('OrcaRuntimeService', () => {
)
})
it('accepts renderer-backed terminal create replies only from the target renderer', async () => {
const webContents = { send: vi.fn() }
const send = vi.fn((_channel: string, payload: { requestId: string }) => {
ipcMain.emit(
'terminal:tabCreateReply',
{ sender: { send: vi.fn() } },
{ requestId: payload.requestId, error: 'spoofed renderer reply' }
)
runtime.syncWindowGraph(1, {
tabs: [
{
tabId: 'tab-renderer',
worktreeId: TEST_WORKTREE_ID,
title: 'Renderer Terminal',
activeLeafId: 'pane:1',
layout: null
}
],
leaves: [
{
tabId: 'tab-renderer',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:1',
paneRuntimeId: 1,
ptyId: 'pty-renderer',
paneTitle: null
}
]
})
ipcMain.emit(
'terminal:tabCreateReply',
{ sender: webContents },
{ requestId: payload.requestId, tabId: 'tab-renderer', title: 'Renderer Terminal' }
)
})
webContents.send = send
const runtime = new OrcaRuntimeService(store)
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents
})
await expect(
runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, {
command: 'codex',
rendererBacked: true,
title: 'Renderer Terminal'
})
).resolves.toMatchObject({
handle: expect.stringMatching(/^term_/),
tabId: 'tab-renderer',
title: 'Renderer Terminal',
worktreeId: TEST_WORKTREE_ID,
surface: 'visible'
})
expect(send).toHaveBeenCalledWith(
'terminal:requestTabCreate',
expect.objectContaining({
requestId: expect.any(String),
worktreeId: TEST_WORKTREE_ID,
command: 'codex',
title: 'Renderer Terminal'
})
)
expect(electronMocks.ipcMain.removeListener).toHaveBeenCalledWith(
'terminal:tabCreateReply',
expect.any(Function)
)
})
it('splits visible pty-backed terminal sessions through the parent renderer tab', async () => {
const spawn = vi
.fn()
@@ -15888,7 +15960,13 @@ describe('OrcaRuntimeService', () => {
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const webContents = { send: vi.fn() }
const send = vi.fn((_channel: string, payload: { requestId: string; activate?: boolean }) => {
ipcMain.emit(
'terminal:tabCreateReply',
{ sender: { send: vi.fn() } },
{ requestId: payload.requestId, error: 'spoofed renderer reply' }
)
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [
@@ -15924,7 +16002,7 @@ describe('OrcaRuntimeService', () => {
})
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ sender: webContents },
{
requestId: payload.requestId,
tabId: 'tab-renderer',
@@ -15932,11 +16010,12 @@ describe('OrcaRuntimeService', () => {
}
)
})
webContents.send = send
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
webContents
})
const result = await runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
@@ -15947,7 +16026,8 @@ describe('OrcaRuntimeService', () => {
'terminal:requestTabCreate',
expect.objectContaining({
worktreeId: TEST_WORKTREE_ID,
activate: false
activate: false,
source: 'runtime-session'
})
)
expect(focusTerminal).not.toHaveBeenCalled()
@@ -15971,6 +16051,7 @@ describe('OrcaRuntimeService', () => {
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const webContents = { send: vi.fn() }
const send = vi.fn((_channel: string, payload: { requestId: string }) => {
runtime.syncWindowGraph(1, {
tabs: [],
@@ -16007,15 +16088,16 @@ describe('OrcaRuntimeService', () => {
})
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ sender: webContents },
{ requestId: payload.requestId, tabId: 'tab-renderer', title: 'Terminal' }
)
})
webContents.send = send
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
webContents
})
const [first, second] = await Promise.all([
@@ -16072,20 +16154,22 @@ describe('OrcaRuntimeService', () => {
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const webContents = { send: vi.fn() }
const send = vi.fn((_channel: string, payload: { requestId: string; worktreeId: string }) => {
const parentTabId =
payload.worktreeId === TEST_WORKTREE_ID ? 'tab-renderer-a' : 'tab-renderer-b'
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ sender: webContents },
{ requestId: payload.requestId, tabId: parentTabId, title: 'Terminal' }
)
})
webContents.send = send
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
webContents
})
const firstCreate = runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
@@ -16200,18 +16284,20 @@ describe('OrcaRuntimeService', () => {
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const webContents = { send: vi.fn() }
const send = vi.fn((_channel: string, payload: { requestId: string }) => {
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ sender: webContents },
{ requestId: payload.requestId, tabId: 'tab-pending', title: 'Terminal' }
)
})
webContents.send = send
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
webContents
})
const create = runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
@@ -16309,18 +16395,20 @@ describe('OrcaRuntimeService', () => {
})
// Why: reply with a tabId but never sync a matching surface graph, so
// waitForMobileTerminalSurface times out and the rollback path runs.
const webContents = { send: vi.fn() }
const send = vi.fn((_channel: string, payload: { requestId: string }) => {
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ sender: webContents },
{ requestId: payload.requestId, tabId: 'tab-ghost', title: 'Terminal' }
)
})
webContents.send = send
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
webContents
})
const pending = runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {