mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
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:
@@ -151,6 +151,7 @@ describe('RuntimeBrowserCommands browser screencast', () => {
|
||||
|
||||
it('creates the first explicit-worktree browser tab without waiting for an existing registration', async () => {
|
||||
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
||||
const webContents = { send: vi.fn() }
|
||||
const send = vi.fn((channel: string, data: { requestId: string }) => {
|
||||
expect(channel).toBe('browser:requestTabCreate')
|
||||
const handler = ipcMainOnMock.mock.calls.find(
|
||||
@@ -161,8 +162,16 @@ describe('RuntimeBrowserCommands browser screencast', () => {
|
||||
reply: { requestId: string; browserPageId?: string; error?: string }
|
||||
) => void)
|
||||
| undefined
|
||||
handler?.({} as never, { requestId: data.requestId, browserPageId: 'page-new' })
|
||||
handler?.({ sender: { send: vi.fn() } } as never, {
|
||||
requestId: data.requestId,
|
||||
error: 'spoofed renderer reply'
|
||||
})
|
||||
handler?.({ sender: webContents } as never, {
|
||||
requestId: data.requestId,
|
||||
browserPageId: 'page-new'
|
||||
})
|
||||
})
|
||||
webContents.send = send
|
||||
const bridge = {
|
||||
getRegisteredTabs: vi.fn(() => new Map([['page-new', 101]])),
|
||||
getActivePageId: vi.fn(() => 'page-new'),
|
||||
@@ -173,7 +182,7 @@ describe('RuntimeBrowserCommands browser screencast', () => {
|
||||
createHost({
|
||||
getAgentBrowserBridge: () => bridge,
|
||||
getAvailableAuthoritativeWindow: vi.fn(() => ({}) as never),
|
||||
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
||||
getAuthoritativeWindow: vi.fn(() => ({ webContents }) as never)
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -1795,10 +1795,10 @@ export class RuntimeBrowserCommands {
|
||||
}, 10_000)
|
||||
|
||||
const handler = (
|
||||
_event: Electron.IpcMainEvent,
|
||||
event: Electron.IpcMainEvent,
|
||||
reply: { requestId: string; browserPageId?: string; error?: string }
|
||||
): void => {
|
||||
if (reply.requestId !== requestId) {
|
||||
if (event.sender !== win.webContents || reply.requestId !== requestId) {
|
||||
return
|
||||
}
|
||||
clearTimeout(timer)
|
||||
|
||||
@@ -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}`, {
|
||||
|
||||
@@ -15373,10 +15373,10 @@ export class OrcaRuntimeService {
|
||||
}, 10_000)
|
||||
|
||||
const handler = (
|
||||
_event: Electron.IpcMainEvent,
|
||||
event: Electron.IpcMainEvent,
|
||||
r: { requestId: string; tabId?: string; title?: string; error?: string }
|
||||
): void => {
|
||||
if (r.requestId !== requestId) {
|
||||
if (event.sender !== win.webContents || r.requestId !== requestId) {
|
||||
return
|
||||
}
|
||||
clearTimeout(timer)
|
||||
@@ -15535,10 +15535,10 @@ export class OrcaRuntimeService {
|
||||
}, 10_000)
|
||||
|
||||
const handler = (
|
||||
_event: Electron.IpcMainEvent,
|
||||
event: Electron.IpcMainEvent,
|
||||
r: { requestId: string; tabId?: string; title?: string; error?: string }
|
||||
): void => {
|
||||
if (r.requestId !== requestId) {
|
||||
if (event.sender !== win.webContents || r.requestId !== requestId) {
|
||||
return
|
||||
}
|
||||
clearTimeout(timer)
|
||||
@@ -15560,6 +15560,7 @@ export class OrcaRuntimeService {
|
||||
...(startupCommand.launchConfig ? { launchConfig: startupCommand.launchConfig } : {}),
|
||||
...(startupCommand.launchAgent ? { launchAgent: startupCommand.launchAgent } : {}),
|
||||
startupCommandDelivery: startupCommand.startupCommandDelivery,
|
||||
source: 'runtime-session',
|
||||
activate: opts.activate
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user