Files
orca/src/renderer/src/web/web-preload-api.test.ts
T
Jinjing debf4affe7 Display total lines of code change in branch header (#12771)
* Add branch line total chip to source control header

Display the total lines added and removed across a branch from its fork point, measured via `git diff <mergeBase>`. Only computed when the chip is visible (request gate on merge base OID), with 500ms soft deadline to protect status latency and 15s hard timeout. Deduplicated across concurrent pollers and cached alongside line stats. Omitted on failure — always shows exact or nothing, never a partial estimate. Updates throughout the stack: native git status, relay, renderer store/API, and UI components.

* Pin branch line total to app locale

Format line counts using the app's configured locale instead of the system
locale, ensuring consistent cross-platform display and test reliability.

* test: wait for coalescer joins instead of fixed sleep

Hold the diff until the second status pass actually takes the
branch-total coalescer lease instead of using a fixed 400ms sleep.
Fixes timing-dependent flakiness on slow machines.
2026-08-05 14:46:00 -07:00

4682 lines
153 KiB
TypeScript

/* eslint-disable max-lines -- Why: web preload parity tests share module-reset
global setup across namespaces so browser API installation stays realistic. */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { PreloadApi } from '../../../preload/api-types'
import type { FeatureInteractionState } from '../../../shared/feature-interactions'
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
import type { TaskSourceContext } from '../../../shared/task-source-context'
import { MIN_COMPATIBLE_RUNTIME_SERVER_VERSION } from '../../../shared/protocol-version'
const TEST_COMMIT_OID = '0123456789abcdef0123456789abcdef01234567'
class MemoryStorage implements Storage {
private readonly values = new Map<string, string>()
get length(): number {
return this.values.size
}
clear(): void {
this.values.clear()
}
getItem(key: string): string | null {
return this.values.get(key) ?? null
}
key(index: number): string | null {
return Array.from(this.values.keys())[index] ?? null
}
removeItem(key: string): void {
this.values.delete(key)
}
setItem(key: string, value: string): void {
this.values.set(key, value)
}
}
function installBrowserGlobals(userAgent = 'Linux'): {
window: Window & typeof globalThis
storage: MemoryStorage
} {
const storage = new MemoryStorage()
const windowStub = {
localStorage: storage,
location: {
protocol: 'http:',
reload: vi.fn()
},
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
atob: (value: string) => Buffer.from(value, 'base64').toString('binary'),
btoa: (value: string) => Buffer.from(value, 'binary').toString('base64')
} as unknown as Window & typeof globalThis
vi.stubGlobal('window', windowStub)
vi.stubGlobal('navigator', { userAgent, hardwareConcurrency: 8 })
return { window: windowStub, storage }
}
function installExecCommandClipboardDocument(execCommandResult = true): {
appendChild: ReturnType<typeof vi.fn>
createElement: ReturnType<typeof vi.fn>
execCommand: ReturnType<typeof vi.fn>
setData: ReturnType<typeof vi.fn>
} {
const listeners: ((event: unknown) => void)[] = []
const setData = vi.fn()
const createElement = vi.fn()
const appendChild = vi.fn()
const execCommand = vi.fn((command: string) => {
if (command === 'copy') {
for (const listener of listeners.slice()) {
listener({
clipboardData: { setData },
preventDefault: vi.fn(),
stopImmediatePropagation: vi.fn()
})
}
}
return execCommandResult
})
vi.stubGlobal('document', {
execCommand,
addEventListener: vi.fn((type: string, listener: (event: unknown) => void) => {
if (type === 'copy') {
listeners.push(listener)
}
}),
removeEventListener: vi.fn((type: string, listener: (event: unknown) => void) => {
if (type === 'copy') {
listeners.splice(listeners.indexOf(listener), 1)
}
}),
createElement,
body: { appendChild }
})
return { appendChild, createElement, execCommand, setData }
}
async function installApi(userAgent?: string): Promise<{
api: PreloadApi
storage: MemoryStorage
window: Window & typeof globalThis
}> {
const globals = installBrowserGlobals(userAgent)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
return {
api: globals.window.api,
storage: globals.storage,
window: globals.window
}
}
function writeStoredRuntimeEnvironment(storage: Storage, environmentId = 'web-env-1'): void {
storage.setItem(
'orca.web.runtimeEnvironment.v1',
JSON.stringify({
id: environmentId,
name: 'Test runtime',
createdAt: 1,
updatedAt: 1,
lastUsedAt: null,
runtimeId: null,
preferredEndpointId: `ws-${environmentId}`,
endpoints: [
{
id: `ws-${environmentId}`,
kind: 'websocket',
label: 'WebSocket',
endpoint: 'ws://127.0.0.1:1234',
deviceToken: 'token',
publicKeyB64: 'public-key'
}
]
})
)
}
function encodePairingCode(overrides: Record<string, unknown> = {}): string {
return Buffer.from(
JSON.stringify({
v: 2,
endpoint: 'wss://server.example:443',
deviceToken: 'server-token',
publicKeyB64: 'server-key',
...overrides
}),
'utf8'
).toString('base64url')
}
function trackPromiseSettled(promise: Promise<unknown>): () => boolean {
let settled = false
void promise.then(
() => {
settled = true
},
() => {
settled = true
}
)
return () => settled
}
function installClipboardImageBase64(contentBase64: string): void {
vi.stubGlobal(
'FileReader',
class {
result: string | ArrayBuffer | null = null
error: DOMException | null = null
onload: (() => void) | null = null
onerror: (() => void) | null = null
readAsDataURL(blob: Blob): void {
this.result = `data:${blob.type};base64,${contentBase64}`
this.onload?.()
}
}
)
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: {
readText: vi.fn().mockResolvedValue(''),
read: vi.fn().mockResolvedValue([
{
types: ['image/png'],
getType: vi.fn().mockResolvedValue(new Blob(['ignored'], { type: 'image/png' }))
}
])
}
})
}
describe('web before-unload persistence', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('persists final UI and host-partitioned sessions synchronously', async () => {
const { api, storage } = await installApi('Linux')
api.app.stageBeforeUnloadSync({
sessions: [
{ state: { activeWorktreeId: 'local-worktree' } as never },
{
state: { activeWorktreeId: 'remote-worktree' } as never,
hostId: 'runtime:web-env-1'
}
],
ui: { activeView: 'settings' }
})
expect(JSON.parse(storage.getItem('orca.web.workspaceSession.v1') ?? '{}')).toMatchObject({
activeWorktreeId: 'local-worktree'
})
expect(
JSON.parse(storage.getItem('orca.web.workspaceSession.v1.runtime:web-env-1') ?? '{}')
).toMatchObject({ activeWorktreeId: 'remote-worktree' })
expect(JSON.parse(storage.getItem('orca.web.ui.v1') ?? '{}')).toMatchObject({
activeView: 'settings'
})
})
})
describe('web runtime environment identity', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('does not resolve an old server selector through a differently keyed server', async () => {
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Server B',
pairingCode: encodePairingCode({ publicKeyB64: 'server-b-key' })
})
await expect(
globals.window.api.runtimeEnvironments.resolve({ selector: 'web-server-a' })
).rejects.toThrow('Unknown Orca runtime environment: web-server-a')
})
it('keeps pairing state separate from generic Active Server settings writes', async () => {
const globals = installBrowserGlobals('Linux')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const paired = await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Windows 2',
pairingCode: encodePairingCode({ publicKeyB64: 'windows-2-key' })
})
const settings = await globals.window.api.settings.set({ activeRuntimeEnvironmentId: null })
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toMatchObject([
{ id: paired.environment.id, name: 'Windows 2' }
])
expect(settings.activeRuntimeEnvironmentId).toBeNull()
expect(globals.window.api.settings.getSync()?.activeRuntimeEnvironmentId).toBeNull()
expect(JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}')).not.toHaveProperty(
'activeRuntimeEnvironmentId'
)
await expect(
globals.window.api.runtimeEnvironments.remove({ selector: paired.environment.id })
).resolves.toMatchObject({ removed: { id: paired.environment.id } })
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toEqual([])
})
it('persists an explicit Active Server choice across unrelated web settings writes', async () => {
const globals = installBrowserGlobals('Linux')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const paired = await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Windows 2',
pairingCode: encodePairingCode({ publicKeyB64: 'windows-2-key' })
})
await globals.window.api.settings.setActiveRuntimeEnvironmentPreference({
environmentId: 'Windows 2'
})
await globals.window.api.settings.set({ terminalFontSize: 15 })
expect(JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}')).toMatchObject({
activeRuntimeEnvironmentId: paired.environment.id,
terminalFontSize: 15
})
await globals.window.api.settings.setActiveRuntimeEnvironmentPreference({
environmentId: null
})
await globals.window.api.settings.set({ terminalFontSize: 16 })
expect(JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}')).toMatchObject({
activeRuntimeEnvironmentId: null,
terminalFontSize: 16
})
})
it('rejects an unknown explicit Active Server choice without corrupting the preference', async () => {
const globals = installBrowserGlobals('Linux')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const paired = await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Windows 2',
pairingCode: encodePairingCode({ publicKeyB64: 'windows-2-key' })
})
await globals.window.api.settings.setActiveRuntimeEnvironmentPreference({
environmentId: paired.environment.id
})
await expect(
globals.window.api.settings.setActiveRuntimeEnvironmentPreference({
environmentId: 'unknown-server'
})
).rejects.toThrow('Unknown Orca runtime environment: unknown-server')
expect(JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}')).toMatchObject({
activeRuntimeEnvironmentId: paired.environment.id
})
})
it('keeps old selectors only when re-pairing proves the same server key', async () => {
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const paired = await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Server A again',
pairingCode: encodePairingCode({ publicKeyB64: 'public-key' })
})
await expect(
globals.window.api.runtimeEnvironments.resolve({ selector: 'web-server-a' })
).resolves.toMatchObject({ id: paired.environment.id, name: 'Server A again' })
})
it('ignores malformed persisted compatibility ids when resolving selectors', async () => {
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const stored = JSON.parse(
globals.storage.getItem('orca.web.runtimeEnvironment.v1') ?? '{}'
) as Record<string, unknown>
stored.compatibleEnvironmentIds = { old: 'web-server-old' }
globals.storage.setItem('orca.web.runtimeEnvironment.v1', JSON.stringify(stored))
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.runtimeEnvironments.resolve({ selector: 'web-server-old' })
).rejects.toThrow('Unknown Orca runtime environment: web-server-old')
})
it('keeps pairing while manual disconnect fences passive reconnects', async () => {
const calls: string[] = []
const close = vi.fn()
let clientCount = 0
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
constructor() {
clientCount += 1
}
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
calls.push(method)
return Promise.resolve({
id: method,
ok: true,
result: { runtimeId: 'runtime-1' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {
close()
}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.runtimeEnvironments.getStatus({ selector: 'web-server-a' })
).resolves.toMatchObject({ ok: true })
await globals.window.api.runtimeEnvironments.disconnect({ selector: 'web-server-a' })
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toMatchObject([
{ id: 'web-server-a' }
])
await expect(
globals.window.api.runtimeEnvironments.getStatus({ selector: 'web-server-a' })
).resolves.toMatchObject({
ok: false,
error: { code: 'runtime_manually_disconnected' }
})
await expect(
globals.window.api.runtimeEnvironments.call({
selector: 'web-server-a',
method: 'repos.list'
})
).resolves.toMatchObject({
ok: false,
error: { code: 'runtime_manually_disconnected' }
})
await expect(
globals.window.api.runtimeEnvironments.subscribe(
{ selector: 'web-server-a', method: 'terminal.subscribe' },
{ onResponse: vi.fn() }
)
).rejects.toThrow('runtime_manually_disconnected')
expect(clientCount).toBe(1)
expect(calls).toEqual(['status.get'])
expect(close).toHaveBeenCalledOnce()
await expect(
globals.window.api.runtimeEnvironments.connect({ selector: 'web-server-a' })
).resolves.toMatchObject({ ok: true })
expect(clientCount).toBe(2)
expect(calls).toEqual(['status.get', 'status.get'])
})
it('fences a web runtime response that completes after manual disconnect', async () => {
let resolveCall!: (response: RuntimeRpcResponse<unknown>) => void
const pendingCall = new Promise<RuntimeRpcResponse<unknown>>((resolve) => {
resolveCall = resolve
})
const call = vi.fn(() => pendingCall)
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call = call
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const status = globals.window.api.runtimeEnvironments.getStatus({
selector: 'web-server-a'
})
await vi.waitFor(() => expect(call).toHaveBeenCalledOnce())
await globals.window.api.runtimeEnvironments.disconnect({ selector: 'web-server-a' })
resolveCall({
id: 'status.get',
ok: true,
result: { runtimeId: 'runtime-1' },
_meta: { runtimeId: 'runtime-1' }
})
await expect(status).resolves.toMatchObject({
ok: false,
error: { code: 'runtime_manually_disconnected' }
})
})
it.each(['active runtime', 'selected environment'] as const)(
'returns a disconnect envelope when a queued %s call disconnects',
async (route) => {
const pending: ((response: RuntimeRpcResponse<unknown>) => void)[] = []
const call = vi.fn(
(method: string) =>
new Promise<RuntimeRpcResponse<unknown>>((resolve) => {
pending.push((response) => resolve({ ...response, id: method }))
})
)
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call = call
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const invoke = (): Promise<RuntimeRpcResponse<unknown>> =>
route === 'active runtime'
? globals.window.api.runtime.call({ method: 'repos.list' })
: globals.window.api.runtimeEnvironments.call({
selector: 'web-server-a',
method: 'repos.list'
})
const activeCalls = Array.from({ length: 8 }, invoke)
await vi.waitFor(() => expect(call).toHaveBeenCalledTimes(8))
const queuedCall = invoke()
expect(call).toHaveBeenCalledTimes(8)
await globals.window.api.runtimeEnvironments.disconnect({ selector: 'web-server-a' })
pending[0]?.({
id: 'repos.list',
ok: true,
result: {},
_meta: { runtimeId: 'runtime-1' }
})
await expect(queuedCall).resolves.toMatchObject({
ok: false,
error: { code: 'runtime_manually_disconnected' }
})
expect(call).toHaveBeenCalledTimes(8)
for (const resolve of pending.slice(1)) {
resolve({
id: 'repos.list',
ok: true,
result: {},
_meta: { runtimeId: 'runtime-1' }
})
}
await expect(Promise.all(activeCalls)).resolves.toEqual(
Array.from({ length: 8 }, () =>
expect.objectContaining({
ok: false,
error: expect.objectContaining({ code: 'runtime_manually_disconnected' })
})
)
)
}
)
it('keeps the current host when verification rejects an incompatible replacement', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: 'status',
ok: true,
result: { runtimeProtocolVersion: MIN_COMPATIBLE_RUNTIME_SERVER_VERSION - 1 },
_meta: { runtimeId: 'runtime-old' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const previousStored = globals.storage.getItem('orca.web.runtimeEnvironment.v1')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.runtimeEnvironments.verifyAndAddFromPairingCode({
name: 'Incompatible server',
pairingCode: encodePairingCode()
})
).resolves.toMatchObject({ ok: false, kind: 'protocol-incompatible' })
expect(globals.storage.getItem('orca.web.runtimeEnvironment.v1')).toBe(previousStored)
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toMatchObject([
{ id: 'web-server-a' }
])
})
it('keeps the current host when browser storage rejects a verified replacement', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: 'status',
ok: true,
result: {
runtimeId: 'runtime-new',
rendererGraphEpoch: 1,
graphStatus: 'ready',
authoritativeWindowId: 1,
liveTabCount: 0,
liveLeafCount: 0,
runtimeProtocolVersion: MIN_COMPATIBLE_RUNTIME_SERVER_VERSION
},
_meta: { runtimeId: 'runtime-new' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
vi.spyOn(globals.storage, 'setItem').mockImplementation(() => {
throw new Error('Browser storage is full.')
})
await expect(
globals.window.api.runtimeEnvironments.verifyAndAddFromPairingCode({
name: 'Verified replacement',
pairingCode: encodePairingCode()
})
).resolves.toMatchObject({
ok: false,
kind: 'environment-save-failed',
message: 'Orca verified the host but could not save it. Check browser storage and try again.'
})
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toMatchObject([
{ id: 'web-server-a' }
])
})
it('requires an explicit loopback override and persists the SSH dependency', async () => {
const call = vi.fn().mockResolvedValue({
id: 'status',
ok: true,
result: {
runtimeId: 'runtime-new',
rendererGraphEpoch: 1,
graphStatus: 'ready',
authoritativeWindowId: 1,
liveTabCount: 0,
liveLeafCount: 0,
runtimeProtocolVersion: MIN_COMPATIBLE_RUNTIME_SERVER_VERSION
},
_meta: { runtimeId: 'runtime-new' }
})
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call = call
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const pairingCode = encodePairingCode({ endpoint: 'ws://127.0.0.1:6768' })
await expect(
globals.window.api.runtimeEnvironments.verifyAndAddFromPairingCode({
name: 'Tunnel server',
pairingCode
})
).resolves.toMatchObject({ ok: false, kind: 'host-unreachable' })
expect(call).not.toHaveBeenCalled()
await expect(
globals.window.api.runtimeEnvironments.verifyAndAddFromPairingCode({
name: 'Tunnel server',
pairingCode,
allowLoopback: true
})
).resolves.toMatchObject({
ok: true,
environment: { connectionDependency: 'ssh-tunnel' }
})
expect(call).toHaveBeenCalledOnce()
expect(
JSON.parse(globals.storage.getItem('orca.web.runtimeEnvironment.v1') ?? '{}')
).toMatchObject({ connectionDependency: 'ssh-tunnel' })
})
it('returns a structured failure when the browser client cannot be constructed', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
constructor() {
throw new Error('Invalid public key: expected 32 bytes, got 3')
}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.runtimeEnvironments.verifyAndAddFromPairingCode({
name: 'Broken server',
pairingCode: encodePairingCode()
})
).resolves.toMatchObject({ ok: false, kind: 'access-link-invalid' })
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toMatchObject([
{ id: 'web-server-a' }
])
})
it('classifies coded browser authorization failures without relying on copy', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.reject(
Object.assign(new Error('Access grant rejected.'), { code: 'unauthorized' })
)
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.runtimeEnvironments.verifyAndAddFromPairingCode({
name: 'Expired server',
pairingCode: encodePairingCode()
})
).resolves.toMatchObject({
ok: false,
kind: 'access-link-invalid',
message: 'Access grant rejected.'
})
})
})
describe('web browser-local port capability', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('returns an explicit unavailable scan instead of an undefined fallback payload', async () => {
const { api } = await installApi('Linux')
await expect(api.workspacePorts.scan({})).resolves.toMatchObject({
platform: 'linux',
ports: [],
unavailableReason: 'Workspace port scanning is unavailable for browser-local workspaces.'
})
})
})
function installClipboardImageBlob(blob: Blob): {
getType: ReturnType<typeof vi.fn>
read: ReturnType<typeof vi.fn>
} {
const getType = vi.fn().mockResolvedValue(blob)
const read = vi.fn().mockResolvedValue([
{
types: [blob.type || 'image/png'],
getType
}
])
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: {
readText: vi.fn().mockResolvedValue(''),
read
}
})
return { getType, read }
}
describe('web keybindings preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('returns snapshots and persists customized bindings in browser storage', async () => {
const { api, storage } = await installApi('Linux')
const initial = await api.keybindings.get()
expect(initial.platform).toBe('linux')
expect(initial.overrides).toEqual({})
const updated = await api.keybindings.setAction({
actionId: 'worktree.palette',
bindings: ['Ctrl+Alt+J']
})
expect(updated.overrides['worktree.palette']).toEqual(['Ctrl+Alt+J'])
expect(storage.getItem('orca.web.keybindings.v1')).toContain('worktree.palette')
const disabled = await api.keybindings.setAction({
actionId: 'worktree.palette',
bindings: []
})
expect(disabled.overrides['worktree.palette']).toEqual([])
const reset = await api.keybindings.setAction({
actionId: 'worktree.palette',
bindings: null
})
expect(reset.overrides['worktree.palette']).toBeUndefined()
}, 15_000)
it('rejects conflicts before mutating browser storage', async () => {
const { api } = await installApi('Linux')
await api.keybindings.setAction({
actionId: 'worktree.palette',
bindings: ['Ctrl+Alt+J']
})
await expect(
api.keybindings.setAction({
actionId: 'worktree.quickOpen',
bindings: ['Ctrl+Alt+J']
})
).rejects.toThrow('conflicts')
const snapshot = await api.keybindings.get()
expect(snapshot.overrides['worktree.palette']).toEqual(['Ctrl+Alt+J'])
expect(snapshot.overrides['worktree.quickOpen']).toBeUndefined()
})
it('notifies listeners when web keybindings change', async () => {
const { api } = await installApi('Linux')
const listener = vi.fn()
const unsubscribe = api.keybindings.onChanged(listener)
await api.keybindings.setAction({
actionId: 'worktree.palette',
bindings: ['Ctrl+Alt+J']
})
expect(listener).toHaveBeenCalledWith(
expect.objectContaining({
overrides: expect.objectContaining({ 'worktree.palette': ['Ctrl+Alt+J'] })
})
)
unsubscribe()
})
})
describe('web settings preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('migrates first-work branch auto-rename on for stored legacy web settings once', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({ autoRenameBranchFromWork: false })
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
autoRenameBranchFromWork?: boolean
autoRenameBranchFromWorkDefaultedOn?: boolean
}
expect(settings.autoRenameBranchFromWork).toBe(true)
expect(settings.autoRenameBranchFromWorkDefaultedOn).toBe(true)
expect(stored.autoRenameBranchFromWork).toBe(true)
expect(stored.autoRenameBranchFromWorkDefaultedOn).toBe(true)
})
it('migrates inherited terminal bar cursor defaults for stored web settings once', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem('orca.web.settings.v1', JSON.stringify({ terminalCursorStyle: 'bar' }))
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
terminalCursorStyle?: string
terminalCursorStyleDefaultedToBlock?: boolean
}
expect(settings.terminalCursorStyle).toBe('block')
expect(settings.terminalCursorStyleDefaultedToBlock).toBe(true)
expect(stored.terminalCursorStyle).toBe('block')
expect(stored.terminalCursorStyleDefaultedToBlock).toBe(true)
})
it('preserves terminal cursor choices after the web block-default migration', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({
terminalCursorStyle: 'bar',
terminalCursorStyleDefaultedToBlock: true
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
expect(settings.terminalCursorStyle).toBe('bar')
expect(settings.terminalCursorStyleDefaultedToBlock).toBe(true)
})
it('migrates OSC 52 clipboard writes on for stored web settings once', async () => {
// Why: the web store is a second, independent settings store — the constants-level
// default flip only reaches profiles that never persisted the old `false` (#10567).
const globals = installBrowserGlobals('Linux')
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({ terminalAllowOsc52Clipboard: false })
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
terminalAllowOsc52Clipboard?: boolean
terminalAllowOsc52ClipboardDefaultedOnForAllUsers?: boolean
}
expect(settings.terminalAllowOsc52Clipboard).toBe(true)
expect(settings.terminalAllowOsc52ClipboardDefaultedOnForAllUsers).toBe(true)
expect(stored.terminalAllowOsc52Clipboard).toBe(true)
expect(stored.terminalAllowOsc52ClipboardDefaultedOnForAllUsers).toBe(true)
})
it('arms the OSC 52 notice in the web UI store when the flip overrides a persisted off', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({ terminalAllowOsc52Clipboard: false })
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.settings.get()
const storedUi = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as {
osc52ClipboardDefaultOnNoticePending?: boolean
}
expect(storedUi.osc52ClipboardDefaultOnNoticePending).toBe(true)
})
it('does not arm the OSC 52 notice for a web profile with no persisted value', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem('orca.web.settings.v1', JSON.stringify({ terminalFontSize: 15 }))
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.settings.get()
const storedUi = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as {
osc52ClipboardDefaultOnNoticePending?: boolean
}
expect(storedUi.osc52ClipboardDefaultOnNoticePending).not.toBe(true)
})
it('arms the OSC 52 notice when ui.get is the read that runs the migration', async () => {
// Why seed after install: readLocalWebUIState must run the settings migration before it
// snapshots the UI blob, and only a ui.get that is itself the first settings read can
// show that. Reading first returns a pre-arm state every caller then writes back — and
// the stamp means nothing can raise the arm again. Another tab populating localStorage
// after this one loaded is the shape that reaches it.
const globals = installBrowserGlobals('Linux')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({ terminalAllowOsc52Clipboard: false })
)
const ui = await globals.window.api.ui.get()
expect(ui.osc52ClipboardDefaultOnNoticePending).toBe(true)
})
it('preserves OSC 52 clipboard web opt-outs after migration', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({
terminalAllowOsc52Clipboard: false,
terminalAllowOsc52ClipboardDefaultedOnForAllUsers: true
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
expect(settings.terminalAllowOsc52Clipboard).toBe(false)
expect(settings.terminalAllowOsc52ClipboardDefaultedOnForAllUsers).toBe(true)
})
it('preserves first-work branch auto-rename web opt-outs after migration', async () => {
const globals = installBrowserGlobals('Linux')
globals.storage.setItem(
'orca.web.settings.v1',
JSON.stringify({
autoRenameBranchFromWork: false,
autoRenameBranchFromWorkDefaultedOn: true
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
autoRenameBranchFromWork?: boolean
autoRenameBranchFromWorkDefaultedOn?: boolean
}
expect(settings.autoRenameBranchFromWork).toBe(false)
expect(settings.autoRenameBranchFromWorkDefaultedOn).toBe(true)
expect(stored.autoRenameBranchFromWork).toBe(false)
expect(stored.autoRenameBranchFromWorkDefaultedOn).toBe(true)
})
it('stamps the first-work branch auto-rename guard for web setting updates', async () => {
const { api, storage } = await installApi('Linux')
const settings = await api.settings.set({ autoRenameBranchFromWork: false })
const stored = JSON.parse(storage.getItem('orca.web.settings.v1') ?? '{}') as {
autoRenameBranchFromWork?: boolean
autoRenameBranchFromWorkDefaultedOn?: boolean
}
expect(settings.autoRenameBranchFromWork).toBe(false)
expect(settings.autoRenameBranchFromWorkDefaultedOn).toBe(true)
expect(stored.autoRenameBranchFromWork).toBe(false)
expect(stored.autoRenameBranchFromWorkDefaultedOn).toBe(true)
})
it('hydrates compact worktree cards from paired runtime settings', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
settings: {
compactWorktreeCards: true,
activeRuntimeEnvironmentId: 'host-internal-default'
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
compactWorktreeCards?: boolean
}
expect(settings.compactWorktreeCards).toBe(true)
expect(settings.activeRuntimeEnvironmentId).toBeNull()
expect(stored.compactWorktreeCards).toBe(true)
expect(stored).not.toHaveProperty('activeRuntimeEnvironmentId')
expect(runtimeCalls).toEqual([{ method: 'settings.get', params: undefined }])
}, 15_000)
it('hydrates new worktree card style from a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { settings: { experimentalNewWorktreeCardStyle: true } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
experimentalNewWorktreeCardStyle?: boolean
}
expect(settings.experimentalNewWorktreeCardStyle).toBe(true)
expect(stored.experimentalNewWorktreeCardStyle).toBe(true)
expect(runtimeCalls).toEqual([{ method: 'settings.get', params: undefined }])
})
it('hydrates MiniMax usage settings from a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
settings: {
minimaxGroupId: 'group-42',
minimaxUsageModels: 'general,abab6.5'
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
minimaxGroupId?: string
minimaxUsageModels?: string
}
expect(settings.minimaxGroupId).toBe('group-42')
expect(settings.minimaxUsageModels).toBe('general,abab6.5')
expect(stored.minimaxGroupId).toBe('group-42')
expect(stored.minimaxUsageModels).toBe('general,abab6.5')
expect(runtimeCalls).toEqual([{ method: 'settings.get', params: undefined }])
})
it('hydrates bot-author overrides from paired runtime settings', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: 'call-1',
ok: true,
result: { settings: { prBotAuthorOverrides: [' GretelFlux ', 'gretelflux'] } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.get()
expect(settings.prBotAuthorOverrides).toEqual(['gretelflux'])
expect(runtimeCalls).toEqual([{ method: 'settings.get', params: undefined }])
})
it('forwards compact worktree card updates to a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
settings: {
compactWorktreeCards: true,
activeRuntimeEnvironmentId: 'host-internal-default'
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.set({ compactWorktreeCards: true })
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
compactWorktreeCards?: boolean
}
expect(settings.compactWorktreeCards).toBe(true)
expect(settings.activeRuntimeEnvironmentId).toBeNull()
expect(stored.compactWorktreeCards).toBe(true)
expect(stored).not.toHaveProperty('activeRuntimeEnvironmentId')
expect(runtimeCalls).toEqual([
{ method: 'settings.update', params: { compactWorktreeCards: true } }
])
}, 15_000)
it('forwards new worktree card style updates to a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { settings: { experimentalNewWorktreeCardStyle: true } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.set({
experimentalNewWorktreeCardStyle: true
})
expect(settings.experimentalNewWorktreeCardStyle).toBe(true)
expect(runtimeCalls).toEqual([
{ method: 'settings.update', params: { experimentalNewWorktreeCardStyle: true } }
])
})
it('forwards MiniMax usage setting updates to a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
settings: {
minimaxGroupId: 'group-42',
minimaxUsageModels: 'general,abab6.5'
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.set({
minimaxGroupId: 'group-42',
minimaxUsageModels: 'general,abab6.5'
})
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
minimaxGroupId?: string
minimaxUsageModels?: string
}
expect(settings.minimaxGroupId).toBe('group-42')
expect(settings.minimaxUsageModels).toBe('general,abab6.5')
expect(stored.minimaxGroupId).toBe('group-42')
expect(stored.minimaxUsageModels).toBe('general,abab6.5')
expect(runtimeCalls).toEqual([
{
method: 'settings.update',
params: {
minimaxGroupId: 'group-42',
minimaxUsageModels: 'general,abab6.5'
}
}
])
})
it('forwards normalized bot-author overrides to a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: 'call-1',
ok: true,
result: { settings: { prBotAuthorOverrides: ['gretelflux'] } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.set({
prBotAuthorOverrides: [' GretelFlux ', 'gretelflux']
})
expect(settings.prBotAuthorOverrides).toEqual(['gretelflux'])
expect(runtimeCalls).toEqual([
{ method: 'settings.update', params: { prBotAuthorOverrides: ['gretelflux'] } }
])
})
it('atomically updates a bot-author override through a paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: 'call-1',
ok: true,
result: { settings: { prBotAuthorOverrides: ['gretelflux'] } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const settings = await globals.window.api.settings.updatePRBotAuthorOverride({
author: 'gretelflux',
isBot: true
})
expect(settings.prBotAuthorOverrides).toEqual(['gretelflux'])
expect(runtimeCalls).toEqual([
{
method: 'settings.updatePRBotAuthorOverride',
params: { author: 'gretelflux', isBot: true }
}
])
})
it('does not claim a paired bot-author update succeeded when the runtime rejects it', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.reject(new Error('runtime unavailable'))
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.settings.updatePRBotAuthorOverride({
author: 'gretelflux',
isBot: true
})
).rejects.toThrow('runtime unavailable')
const stored = JSON.parse(globals.storage.getItem('orca.web.settings.v1') ?? '{}') as {
prBotAuthorOverrides?: string[]
}
expect(stored.prBotAuthorOverrides).toBeUndefined()
})
})
describe('web native chat preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('forwards validated lifecycle metadata from reads and stream frames', async () => {
const lifecycle = { state: 'completed', turnId: 'turn-1', timestamp: 42 } as const
const message = {
id: 'a-1',
role: 'assistant' as const,
blocks: [{ type: 'text' as const, text: 'done' }],
timestamp: 42,
source: 'transcript' as const
}
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: 'read-1',
ok: true,
result: { messages: [message], lifecycle },
_meta: { runtimeId: 'runtime-1' }
})
}
subscribe(
_method: string,
_params: unknown,
callbacks: { onResponse: (response: RuntimeRpcResponse<unknown>) => void }
): Promise<{ unsubscribe: () => void }> {
callbacks.onResponse({
id: 'stream-1',
ok: true,
result: {
type: 'snapshot',
messages: [message],
hasMore: false,
lifecycle
},
_meta: { runtimeId: 'runtime-1' }
})
return Promise.resolve({ unsubscribe: vi.fn() })
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.nativeChat.readSession('claude', 'session-1')).resolves.toEqual(
{
messages: [message],
lifecycle
}
)
const frames: unknown[] = []
globals.window.api.nativeChat.subscribe(
{ subscriptionId: 'sub-1', agent: 'claude', sessionId: 'session-1' },
(frame) => frames.push(frame)
)
await Promise.resolve()
expect(frames).toEqual([
{
type: 'snapshot',
messages: [message],
hasMore: false,
lifecycle
}
])
})
})
describe('web MiniMax preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
})
it('exposes desktop-only MiniMax credential reads as unconfigured and rejects saves', async () => {
const { api } = await installApi('Linux')
await expect(api.minimaxCredentials.getStatus()).resolves.toEqual({ configured: false })
await expect(api.minimaxCredentials.saveCookie('_token=abc')).rejects.toThrow(/desktop app/i)
await expect(api.minimaxCredentials.clearCookie()).resolves.toEqual({ configured: false })
})
})
describe('web AI Vault preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('routes session scans through the paired runtime host', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const scanResult = {
sessions: [],
issues: [],
scannedAt: '2026-07-04T00:00:00.000Z'
}
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: scanResult,
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.aiVault.listSessions({
executionHostScope: 'all',
limit: 25,
force: true,
scopePaths: ['/srv/app']
})
).resolves.toEqual(scanResult)
expect(runtimeCalls).toEqual([
{
method: 'aiVault.listSessions',
params: {
limit: 25,
force: true,
scopePaths: ['/srv/app'],
executionHostId: 'runtime:web-env-1'
}
}
])
})
it('returns unavailable history for explicit non-runtime host scopes', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { sessions: [], issues: [], scannedAt: '2026-07-04T00:00:00.000Z' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.aiVault.listSessions({ executionHostScope: 'local' })
).resolves.toEqual({
sessions: [],
issues: [
expect.objectContaining({
executionHostId: 'local',
agent: 'codex'
})
],
scannedAt: expect.any(String)
})
expect(runtimeCalls).toEqual([])
})
})
describe('web UI preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.useRealTimers()
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('writes bounded clipboard text through the browser clipboard API', async () => {
const globals = installBrowserGlobals('Linux')
const writeText = vi.fn().mockResolvedValue(undefined)
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: { writeText }
})
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.writeClipboardText('copy me')).resolves.toBeUndefined()
await expect(
globals.window.api.ui.writeTerminalClipboardText('terminal copy')
).resolves.toBeUndefined()
expect(writeText.mock.calls).toEqual([['copy me'], ['terminal copy']])
})
it('copies through execCommand when navigator.clipboard is unavailable (insecure context)', async () => {
const globals = installBrowserGlobals('Linux')
vi.stubGlobal('navigator', { userAgent: 'Linux', hardwareConcurrency: 8 })
const clipboard = installExecCommandClipboardDocument()
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.writeClipboardText('copy me')).resolves.toBeUndefined()
expect(clipboard.setData).toHaveBeenCalledWith('text/plain', 'copy me')
expect(clipboard.execCommand).toHaveBeenCalledWith('copy')
expect(clipboard.createElement).not.toHaveBeenCalled()
expect(clipboard.appendChild).not.toHaveBeenCalled()
})
it('rejects instead of silently succeeding when no clipboard write path exists', async () => {
const globals = installBrowserGlobals('Linux')
vi.stubGlobal('navigator', { userAgent: 'Linux', hardwareConcurrency: 8 })
vi.stubGlobal('document', {
activeElement: null,
createElement: vi.fn(() => ({
value: '',
readOnly: false,
style: {} as Record<string, string>,
select: vi.fn(),
remove: vi.fn()
})),
execCommand: vi.fn().mockReturnValue(false),
body: { appendChild: vi.fn() }
})
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.writeClipboardText('copy me')).rejects.toThrow(
'Clipboard write is unavailable in this browser context'
)
await expect(globals.window.api.ui.writeTerminalClipboardText('copy me')).rejects.toThrow(
'Clipboard write is unavailable in this browser context'
)
})
it('falls back to execCommand when the browser clipboard write is permission-gated', async () => {
const globals = installBrowserGlobals('Linux')
const writeText = vi.fn().mockRejectedValue(new DOMException('denied', 'NotAllowedError'))
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: { writeText }
})
const clipboard = installExecCommandClipboardDocument()
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.writeClipboardText('copy me')).resolves.toBeUndefined()
expect(writeText).toHaveBeenCalledWith('copy me')
expect(clipboard.setData).toHaveBeenCalledWith('text/plain', 'copy me')
expect(clipboard.createElement).not.toHaveBeenCalled()
expect(clipboard.appendChild).not.toHaveBeenCalled()
})
it('yields while reading accepted large browser clipboard text', async () => {
vi.useFakeTimers()
const text = 'é'.repeat(300_000)
const globals = installBrowserGlobals('Linux')
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: { readText: vi.fn().mockResolvedValue(text) }
})
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const result = globals.window.api.ui.readClipboardText({ maxBytes: text.length * 3 })
const isSettled = trackPromiseSettled(result)
await Promise.resolve()
expect(isSettled()).toBe(false)
await vi.runOnlyPendingTimersAsync()
await expect(result).resolves.toBe(text)
})
it('yields before writing accepted large browser clipboard text', async () => {
vi.useFakeTimers()
const text = 'é'.repeat(300_000)
const globals = installBrowserGlobals('Linux')
const writeText = vi.fn().mockResolvedValue(undefined)
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: { writeText }
})
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const result = globals.window.api.ui.writeClipboardText(text)
const isSettled = trackPromiseSettled(result)
await Promise.resolve()
expect(isSettled()).toBe(false)
expect(writeText).not.toHaveBeenCalled()
await vi.runOnlyPendingTimersAsync()
await expect(result).resolves.toBeUndefined()
expect(writeText).toHaveBeenCalledWith(text)
})
it('rejects oversized clipboard text writes before calling the browser clipboard API', async () => {
const globals = installBrowserGlobals('Linux')
const writeText = vi.fn().mockResolvedValue(undefined)
vi.stubGlobal('navigator', {
userAgent: 'Linux',
hardwareConcurrency: 8,
clipboard: { writeText }
})
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.ui.writeClipboardText('copied-secret-token-value'.repeat(900_000))
).rejects.toThrow('Clipboard text is too large to copy safely.')
await expect(
globals.window.api.ui.writeTerminalClipboardText('copied-secret-token-value'.repeat(900_000))
).rejects.toThrow('Clipboard text is too large to copy safely.')
expect(writeText).not.toHaveBeenCalled()
})
it('saves browser clipboard images through bounded upload chunks', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'clipboard.startImageUpload') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { uploadId: 'upload-1' },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'clipboard.appendImageUploadChunk') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { receivedBase64Length: runtimeCalls.length },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: 'C:\\Users\\alice\\AppData\\Local\\Temp\\orca-paste-image.png',
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { CLIPBOARD_IMAGE_UPLOAD_CHUNK_BASE64_CHARS, installWebPreloadApi } =
await import('./web-preload-api')
const contentBase64 = `${'A'.repeat(CLIPBOARD_IMAGE_UPLOAD_CHUNK_BASE64_CHARS)}AAAA`
installClipboardImageBase64(contentBase64)
installWebPreloadApi()
await expect(
globals.window.api.ui.saveClipboardImageAsTempFile({ connectionId: 'ssh-1' })
).resolves.toBe('C:\\Users\\alice\\AppData\\Local\\Temp\\orca-paste-image.png')
expect(runtimeCalls).toEqual([
{
method: 'clipboard.startImageUpload',
params: {
expectedBase64Length: contentBase64.length,
connectionId: 'ssh-1'
}
},
{
method: 'clipboard.appendImageUploadChunk',
params: {
uploadId: 'upload-1',
offset: 0,
contentBase64: 'A'.repeat(CLIPBOARD_IMAGE_UPLOAD_CHUNK_BASE64_CHARS)
}
},
{
method: 'clipboard.appendImageUploadChunk',
params: {
uploadId: 'upload-1',
offset: CLIPBOARD_IMAGE_UPLOAD_CHUNK_BASE64_CHARS,
contentBase64: 'AAAA'
}
},
{
method: 'clipboard.commitImageUpload',
params: { uploadId: 'upload-1' }
}
])
})
it('falls back to one-shot clipboard save for small payloads when the host lacks upload RPCs', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'clipboard.startImageUpload') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'method_not_found', message: 'Unknown method' },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: '/tmp/orca-paste-image.png',
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
installClipboardImageBase64(Buffer.from('png-bytes').toString('base64'))
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.ui.saveClipboardImageAsTempFile({ connectionId: null })
).resolves.toBe('/tmp/orca-paste-image.png')
expect(runtimeCalls).toEqual([
{
method: 'clipboard.startImageUpload',
params: {
expectedBase64Length: Buffer.from('png-bytes').toString('base64').length,
connectionId: null
}
},
{
method: 'clipboard.saveImageAsTempFile',
params: {
contentBase64: Buffer.from('png-bytes').toString('base64'),
connectionId: null
}
}
])
})
it('does not send large one-shot fallback frames when upload RPCs are missing', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'method_not_found', message: 'Unknown method' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { CLIPBOARD_IMAGE_SINGLE_FRAME_FALLBACK_BASE64_CHARS, installWebPreloadApi } =
await import('./web-preload-api')
installClipboardImageBase64('A'.repeat(CLIPBOARD_IMAGE_SINGLE_FRAME_FALLBACK_BASE64_CHARS + 4))
installWebPreloadApi()
await expect(globals.window.api.ui.saveClipboardImageAsTempFile()).rejects.toThrow(
'Unknown method'
)
expect(runtimeCalls).toHaveLength(1)
expect(runtimeCalls[0]?.method).toBe('clipboard.startImageUpload')
})
it('aborts best-effort when append fails', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'clipboard.startImageUpload') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { uploadId: 'upload-1' },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'clipboard.appendImageUploadChunk') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'runtime_error', message: 'bad chunk' },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { aborted: true },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
installClipboardImageBase64('AAAA')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.saveClipboardImageAsTempFile()).rejects.toThrow('bad chunk')
expect(runtimeCalls.map((call) => call.method)).toEqual([
'clipboard.startImageUpload',
'clipboard.appendImageUploadChunk',
'clipboard.abortImageUpload'
])
})
it('aborts best-effort when commit fails', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'clipboard.startImageUpload') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { uploadId: 'upload-1' },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'clipboard.commitImageUpload') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'runtime_error', message: 'save failed' },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { aborted: true },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
installClipboardImageBase64('AAAA')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.saveClipboardImageAsTempFile()).rejects.toThrow(
'save failed'
)
expect(runtimeCalls.map((call) => call.method)).toEqual([
'clipboard.startImageUpload',
'clipboard.appendImageUploadChunk',
'clipboard.commitImageUpload',
'clipboard.abortImageUpload'
])
})
it('rejects oversized converted clipboard images before starting an upload', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: null,
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
installClipboardImageBase64('A'.repeat(24 * 1024 * 1024 + 4))
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ui.saveClipboardImageAsTempFile()).rejects.toThrow(
'Clipboard image is too large'
)
expect(runtimeCalls).toEqual([])
})
it('rejects oversized clipboard image source blobs before FileReader or upload work', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const readAsDataURL = vi.fn(() => {
throw new Error('FileReader should not receive oversized clipboard image data')
})
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: null,
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { MAX_CLIPBOARD_IMAGE_SOURCE_BYTES, installWebPreloadApi } =
await import('./web-preload-api')
const clipboard = installClipboardImageBlob(
new Blob([new Uint8Array(MAX_CLIPBOARD_IMAGE_SOURCE_BYTES + 1)], { type: 'image/png' })
)
vi.stubGlobal(
'FileReader',
class {
readAsDataURL = readAsDataURL
}
)
installWebPreloadApi()
await expect(globals.window.api.ui.saveClipboardImageAsTempFile()).rejects.toThrow(
'Clipboard image is too large'
)
expect(clipboard.read).toHaveBeenCalledTimes(1)
expect(clipboard.getType).toHaveBeenCalledTimes(1)
expect(readAsDataURL).not.toHaveBeenCalled()
expect(runtimeCalls).toEqual([])
})
it('rejects oversized decoded clipboard images before canvas conversion', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const close = vi.fn()
const readAsDataURL = vi.fn(() => {
throw new Error('FileReader should not receive oversized decoded image data')
})
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: null,
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { MAX_CLIPBOARD_IMAGE_PIXELS, installWebPreloadApi } = await import('./web-preload-api')
installClipboardImageBlob(new Blob(['small'], { type: 'image/jpeg' }))
vi.stubGlobal(
'createImageBitmap',
vi.fn().mockResolvedValue({
close,
height: 1,
width: MAX_CLIPBOARD_IMAGE_PIXELS + 1
})
)
vi.stubGlobal(
'FileReader',
class {
readAsDataURL = readAsDataURL
}
)
installWebPreloadApi()
await expect(globals.window.api.ui.saveClipboardImageAsTempFile()).rejects.toThrow(
'Clipboard image is too large'
)
expect(close).toHaveBeenCalledTimes(1)
expect(readAsDataURL).not.toHaveBeenCalled()
expect(runtimeCalls).toEqual([])
})
it('migrates missing right sidebar visibility from the effective web legacy default', async () => {
const { api } = await installApi('Linux')
const ui = await api.ui.get()
expect(ui.rightSidebarOpen).toBe(false)
})
it('keeps explicit local right sidebar visibility over the legacy default', async () => {
const { api, storage } = await installApi('Linux')
storage.setItem('orca.web.ui.v1', JSON.stringify({ rightSidebarOpen: true }))
const ui = await api.ui.get()
expect(ui.rightSidebarOpen).toBe(true)
})
it('seeds missing local card display properties from runtime-backed compact settings when ui.get is unavailable', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'settings.get') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { settings: { compactWorktreeCards: true } },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'method_not_found', message: 'Unknown method' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.settings.get()
const ui = await globals.window.api.ui.get()
expect(ui.worktreeCardProperties).toEqual(['status', 'unread'])
expect(ui.worktreeCardProperties).not.toContain('ports')
expect(ui.worktreeCardProperties).not.toContain('inline-agents')
expect(runtimeCalls.map((call) => call.method)).toEqual(['settings.get', 'ui.get'])
})
it('preserves explicit local card display properties when compact fallback settings are present', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'settings.get') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { settings: { compactWorktreeCards: true } },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'method_not_found', message: 'Unknown method' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({ worktreeCardProperties: ['status', 'pr'] })
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.settings.get()
const ui = await globals.window.api.ui.get()
expect(ui.worktreeCardProperties).toEqual(['status', 'unread', 'pr'])
expect(ui.worktreeCardProperties).not.toContain('ports')
expect(ui.worktreeCardProperties).not.toContain('inline-agents')
expect(runtimeCalls.map((call) => call.method)).toEqual(['settings.get', 'ui.get'])
})
it('keeps newer feature interaction counts when runtime responses resolve out of order', async () => {
const pending: ((response: RuntimeRpcResponse<unknown>) => void)[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return new Promise((resolve) => {
pending.push((response) =>
resolve({
...response,
id: method,
_meta: { runtimeId: 'runtime-1' }
})
)
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const first = globals.window.api.ui.recordFeatureInteraction('tasks')
const second = globals.window.api.ui.recordFeatureInteraction('tasks')
pending[1]({
id: 'second',
ok: true,
result: {
ui: {
featureInteractions: {
tasks: { firstInteractedAt: 100, interactionCount: 2 }
}
}
},
_meta: { runtimeId: 'runtime-1' }
})
await second
pending[0]({
id: 'first',
ok: true,
result: {
ui: {
featureInteractions: {
tasks: { firstInteractedAt: 100, interactionCount: 1 }
}
}
},
_meta: { runtimeId: 'runtime-1' }
})
await first
const stored = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as {
featureInteractions?: FeatureInteractionState
}
expect(stored.featureInteractions?.tasks).toEqual({
firstInteractedAt: 100,
interactionCount: 2
})
})
it('keeps newer local feature interactions when ui.get returns stale host state', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: method,
ok: true,
result: {
ui: {
featureInteractions: {
tasks: { firstInteractedAt: 100, interactionCount: 1 },
ports: { firstInteractedAt: 300, interactionCount: 1 }
}
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({
featureInteractions: {
tasks: { firstInteractedAt: 50, interactionCount: 3 }
}
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const ui = await globals.window.api.ui.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as {
featureInteractions?: FeatureInteractionState
}
expect(ui.featureInteractions?.tasks).toEqual({
firstInteractedAt: 50,
interactionCount: 3
})
expect(stored.featureInteractions?.tasks).toEqual({
firstInteractedAt: 50,
interactionCount: 3
})
expect(stored.featureInteractions?.ports).toEqual({
firstInteractedAt: 300,
interactionCount: 1
})
})
it('union-merges local contextual tour seen ids when ui.get returns stale host state', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: method,
ok: true,
result: {
ui: {
contextualToursSeenIds: ['browser', 'unknown']
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({
contextualToursSeenIds: ['tasks', 'browser']
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const ui = await globals.window.api.ui.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as {
contextualToursSeenIds?: string[]
}
expect(ui.contextualToursSeenIds).toEqual(['tasks', 'browser'])
expect(stored.contextualToursSeenIds).toEqual(['tasks', 'browser'])
})
it('keeps the local OSC 52 notice armed when ui.get returns an unmigrated host', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: method,
ok: true,
// Why false: the host store always projects this key, so a plain spread
// would overwrite the arm the web client's own settings migration raised.
result: { ui: { osc52ClipboardDefaultOnNoticePending: false } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({ osc52ClipboardDefaultOnNoticePending: true })
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const ui = await globals.window.api.ui.get()
expect(ui.osc52ClipboardDefaultOnNoticePending).toBe(true)
await globals.window.api.ui.set({ osc52ClipboardDefaultOnNoticePending: false })
const cleared = await globals.window.api.ui.get()
expect(cleared.osc52ClipboardDefaultOnNoticePending).toBe(false)
})
it('keeps the local OSC 52 notice armed when recordFeatureInteraction returns an unmigrated host', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: method,
ok: true,
result: { ui: { osc52ClipboardDefaultOnNoticePending: false } },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({ osc52ClipboardDefaultOnNoticePending: true })
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const ui = await globals.window.api.ui.recordFeatureInteraction('tasks')
expect(ui.osc52ClipboardDefaultOnNoticePending).toBe(true)
})
it('does not keep a local shadow copy of main-owned feature telemetry markers', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: method,
ok: true,
result: { ui: {} },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({
featureInteractionTelemetryBuckets: { tasks: 'count_1000_plus' }
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.ui.set({
featureInteractionTelemetryBuckets: { tasks: 'count_500_999' }
} as never)
const ui = await globals.window.api.ui.get()
const stored = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as Record<
string,
unknown
>
expect('featureInteractionTelemetryBuckets' in (ui as Record<string, unknown>)).toBe(false)
expect(stored.featureInteractionTelemetryBuckets).toBeUndefined()
})
it('union-merges local contextual tour seen ids when recordFeatureInteraction returns stale host state', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: method,
ok: true,
result: {
ui: {
contextualToursSeenIds: ['browser']
}
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
globals.storage.setItem(
'orca.web.ui.v1',
JSON.stringify({
contextualToursSeenIds: ['tasks']
})
)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const ui = await globals.window.api.ui.recordFeatureInteraction('tasks')
const stored = JSON.parse(globals.storage.getItem('orca.web.ui.v1') ?? '{}') as {
contextualToursSeenIds?: string[]
}
expect(ui.contextualToursSeenIds).toEqual(['tasks', 'browser'])
expect(stored.contextualToursSeenIds).toEqual(['tasks', 'browser'])
})
it('proxies host skill discovery and computer-use permission APIs for paired web clients', async () => {
const calls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params: unknown): Promise<RuntimeRpcResponse<unknown>> {
calls.push({ method, params })
if (method === 'skills.discover') {
return Promise.resolve({
id: method,
ok: true,
result: {
skills: [
{
id: 'home:computer-use',
name: 'computer-use',
description: null,
providers: ['agent-skills'],
sourceKind: 'home',
sourceLabel: 'Home',
rootPath: '/skills',
directoryPath: '/skills/computer-use',
skillFilePath: '/skills/computer-use/SKILL.md',
installed: true,
fileCount: 1,
updatedAt: null
}
],
sources: [],
scannedAt: 123
},
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'computer.permissionsStatus') {
return Promise.resolve({
id: method,
ok: true,
result: {
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
helperUnavailableReason: null,
permissions: [
{ id: 'accessibility', status: 'granted' },
{ id: 'screenshots', status: 'granted' }
]
},
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'computer.permissions') {
return Promise.resolve({
id: method,
ok: true,
result: {
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
permissionId:
params && typeof params === 'object' ? (params as { id?: string }).id : undefined,
openedSettings: true,
launchedHelper: true,
nextStep: null
},
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: method,
ok: true,
result: {},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.skills.discover({ cwd: '/repo/worktree' })
).resolves.toMatchObject({
skills: [{ name: 'computer-use', installed: true }],
scannedAt: 123
})
const permissionsStatus = await globals.window.api.computerUsePermissions.getStatus()
expect(permissionsStatus.helperUnavailableReason).toBeNull()
expect(permissionsStatus.permissions).toContainEqual({ id: 'accessibility', status: 'granted' })
await expect(
globals.window.api.computerUsePermissions.openSetup({ id: 'accessibility' })
).resolves.toMatchObject({
openedSettings: true,
launchedHelper: true,
permissionId: 'accessibility'
})
expect(calls).toEqual(
expect.arrayContaining([
{ method: 'skills.discover', params: { cwd: '/repo/worktree' } },
{ method: 'computer.permissionsStatus', params: {} },
{ method: 'computer.permissions', params: { id: 'accessibility' } }
])
)
})
it('rejects paired web skill discovery failures instead of returning an empty scan', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
if (method === 'skills.discover') {
return Promise.reject(new Error('runtime disconnected'))
}
return Promise.resolve({
id: method,
ok: true,
result: {},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.skills.discover({ cwd: '/repo' })).rejects.toThrow(
'runtime disconnected'
)
})
it('rejects paired web computer-use status failures instead of marking the helper unavailable', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
if (method === 'computer.permissionsStatus') {
return Promise.reject(new Error('runtime disconnected'))
}
return Promise.resolve({
id: method,
ok: true,
result: {},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.computerUsePermissions.getStatus()).rejects.toThrow(
'runtime disconnected'
)
})
})
describe('web repos preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('rejects desktop host-scoped reorders in paired web clients', async () => {
const { api } = await installApi('Linux')
await expect(
api.repos.reorderForHost({ hostId: 'ssh:target', orderedIds: ['repo-1'] })
).rejects.toThrow('Host-scoped project reordering is unavailable in paired web clients.')
})
it('attributes a server-local catalog to the paired runtime that returned it', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: 'repo-list',
ok: true,
result: {
repos: [
{
id: 'repo-1',
path: '/srv/repo',
displayName: 'repo',
badgeColor: '#000',
addedAt: 1,
executionHostId: 'local'
}
]
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.repos.list()).resolves.toMatchObject([
{ id: 'repo-1', executionHostId: 'runtime:web-server-a' }
])
})
it('does not reassign an in-flight catalog when the browser pairs to another server', async () => {
let resolveCatalog!: (response: RuntimeRpcResponse<unknown>) => void
const pendingCatalog = new Promise<RuntimeRpcResponse<unknown>>((resolve) => {
resolveCatalog = resolve
})
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return pendingCatalog
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const catalogPromise = globals.window.api.repos.list()
const pairingCode = encodePairingCode({
endpoint: 'wss://server-b.example:443',
deviceToken: 'server-b-token',
publicKeyB64: 'server-b-key'
})
const paired = await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Server B',
pairingCode
})
resolveCatalog({
id: 'repo-list',
ok: true,
result: {
repos: [
{
id: 'repo-a',
path: '/srv/a',
displayName: 'A',
badgeColor: '#000',
addedAt: 1,
executionHostId: 'local'
}
]
},
_meta: { runtimeId: 'runtime-a' }
})
await expect(catalogPromise).resolves.toMatchObject([
{ id: 'repo-a', executionHostId: 'runtime:web-server-a' }
])
await expect(globals.window.api.runtimeEnvironments.list()).resolves.toMatchObject([
{ id: paired.environment.id, name: 'Server B' }
])
})
it.each([
['/home/alice', '/home/alice/orca/projects'],
['/', '/orca/projects'],
['C:\\', 'C:\\orca\\projects']
])(
'resolves the default create-project parent from runtime host home %s',
async (resolvedPath, expectedParent) => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: method,
ok: true,
result: { resolvedPath, entries: [] },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.repos.getDefaultCreateProjectParent()).resolves.toBe(
expectedParent
)
expect(runtimeCalls).toEqual([{ method: 'files.browseServerDir', params: { path: '~' } }])
}
)
})
describe('web worktree preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('forwards force and archive-hook intent through worktree removal', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { removed: true },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.worktrees.remove({
worktreeId: 'repo-1::/workspace/locked',
force: true,
skipArchive: false
})
await globals.window.api.worktrees.remove({
worktreeId: 'repo-1::/workspace/dirty',
force: true,
skipArchive: true
})
expect(runtimeCalls).toEqual([
{
method: 'worktree.rm',
params: {
worktree: 'id:repo-1::/workspace/locked',
force: true,
runHooks: true
}
},
{
method: 'worktree.rm',
params: {
worktree: 'id:repo-1::/workspace/dirty',
force: true,
runHooks: false
}
}
])
})
it.each(['web-server-a', 'web-server-b'])(
'attributes server-local worktrees to their own paired runtime %s',
async (environmentId) => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: 'worktree-list',
ok: true,
result: {
worktrees: [
{
id: 'repo-1::/srv/repo',
repoId: 'repo-1',
path: '/srv/repo',
hostId: 'local'
},
{
id: 'repo-2::/ssh/repo',
repoId: 'repo-2',
path: '/ssh/repo',
hostId: 'ssh:hub-private-target'
}
]
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, environmentId)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.worktrees.list({ repoId: 'repo-1' })).resolves.toMatchObject([
{
id: 'repo-1::/srv/repo',
hostId: 'local',
runtimeOwnerEnvironmentId: environmentId
},
{
id: 'repo-2::/ssh/repo',
hostId: 'ssh:hub-private-target',
runtimeOwnerEnvironmentId: environmentId
}
])
}
)
it('does not let a stale listAll response repopulate the next server cache', async () => {
let resolveServerA: ((response: RuntimeRpcResponse<unknown>) => void) | undefined
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
constructor(private readonly offer: { publicKeyB64: string }) {}
call(): Promise<RuntimeRpcResponse<unknown>> {
if (this.offer.publicKeyB64 === 'public-key') {
return new Promise((resolve) => {
resolveServerA = resolve
})
}
return Promise.resolve({
id: 'server-b-list',
ok: true,
result: { worktrees: [{ id: 'worktree-b', repoId: 'repo-b', path: '/srv/b' }] },
_meta: { runtimeId: 'runtime-b' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const serverAList = globals.window.api.worktrees.listAll()
await vi.waitFor(() => expect(resolveServerA).toBeTypeOf('function'))
const paired = await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Server B',
pairingCode: encodePairingCode({ publicKeyB64: 'server-b-key' })
})
resolveServerA?.({
id: 'server-a-list',
ok: true,
result: { worktrees: [{ id: 'worktree-a', repoId: 'repo-a', path: '/srv/a' }] },
_meta: { runtimeId: 'runtime-a' }
})
await expect(serverAList).rejects.toThrow(
'The paired Orca server changed while the request was in progress.'
)
await expect(globals.window.api.worktrees.listAll()).resolves.toMatchObject([
{ id: 'worktree-b', runtimeOwnerEnvironmentId: paired.environment.id }
])
})
it('preserves runtime-routed detected-worktree host ownership in the compatibility shape', async () => {
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(): Promise<RuntimeRpcResponse<unknown>> {
return Promise.resolve({
id: 'detected-list',
ok: true,
result: {
repoId: 'repo-1',
authoritative: true,
source: 'git',
worktrees: [
{ id: 'repo-1::/srv/repo', repoId: 'repo-1', path: '/srv/repo', hostId: 'local' },
{
id: 'repo-1::/ssh/repo',
repoId: 'repo-1',
path: '/ssh/repo',
hostId: 'ssh:hub-private-target'
}
]
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-env-1')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.worktrees.listDetected({ repoId: 'repo-1' })
).resolves.toMatchObject({
repoId: 'repo-1',
authoritative: true,
worktrees: [
{
hostId: 'local',
runtimeOwnerEnvironmentId: 'web-env-1'
},
{
hostId: 'ssh:hub-private-target',
runtimeOwnerEnvironmentId: 'web-env-1'
}
]
})
})
it('falls back to legacy worktree.list when detectedList is unavailable', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const worktree = {
id: 'repo-1::/workspace/repo',
repoId: 'repo-1',
path: '/workspace/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true,
displayName: 'repo',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
workspaceStatus: 'todo'
}
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'worktree.detectedList') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: {
code: 'method_not_found',
message: 'Unknown method: worktree.detectedList'
},
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { worktrees: [worktree], totalCount: 1, truncated: false },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const result = await globals.window.api.worktrees.listDetected({ repoId: 'repo-1' })
expect(result).toMatchObject({
repoId: 'repo-1',
authoritative: true,
source: 'session-fallback',
worktrees: [
{
id: worktree.id,
runtimeOwnerEnvironmentId: 'web-env-1',
ownership: 'orca-managed',
visible: true
}
]
})
expect(runtimeCalls).toEqual([
{ method: 'worktree.detectedList', params: { repo: 'repo-1' } },
{ method: 'worktree.list', params: { repo: 'repo-1', limit: 10_000 } }
])
})
it('does not run a legacy detected-worktree fallback against a newly paired server', async () => {
const runtimeCalls: string[] = []
let resolveDetected: ((response: RuntimeRpcResponse<unknown>) => void) | undefined
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push(method)
return new Promise((resolve) => {
resolveDetected = resolve
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage, 'web-server-a')
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const detected = globals.window.api.worktrees.listDetected({ repoId: 'repo-1' })
await vi.waitFor(() => expect(resolveDetected).toBeTypeOf('function'))
await globals.window.api.runtimeEnvironments.addFromPairingCode({
name: 'Server B',
pairingCode: encodePairingCode({ publicKeyB64: 'server-b-key' })
})
resolveDetected?.({
id: 'detected-list',
ok: false,
error: { code: 'method_not_found', message: 'Unknown method: worktree.detectedList' },
_meta: { runtimeId: 'runtime-a' }
})
await expect(detected).rejects.toThrow(
'The paired Orca server changed while the request was in progress.'
)
expect(runtimeCalls).toEqual(['worktree.detectedList'])
})
it('forwards review compare-base fields through runtime worktree calls', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'worktree.resolvePrBase') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { baseBranch: TEST_COMMIT_OID, compareBaseRef: 'refs/remotes/origin/main' },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'worktree.resolveMrBase') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
baseBranch: 'origin/source',
compareBaseRef: 'refs/remotes/origin/release'
},
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
worktree: { id: 'repo-1::/workspace/review', path: '/workspace/review' }
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.worktrees.create({
repoId: 'repo-1',
name: 'review-pr-42',
baseBranch: TEST_COMMIT_OID,
compareBaseRef: 'refs/remotes/origin/main',
setupDecision: 'inherit',
createdWithAgent: 'codex',
startup: {
command: "codex 'summarize repo'",
env: { ORCA_AGENT_MODE: 'direct' },
launchConfig: {
agentCommand: 'codex',
agentArgs: '--model gpt-5',
agentEnv: { ORCA_AGENT_MODE: 'direct' }
},
startupCommandDelivery: 'shell-ready'
}
})
await globals.window.api.worktrees.resolvePrBase({
repoId: 'repo-1',
prNumber: 42,
headRefName: 'feature/fix',
baseRefName: 'main',
isCrossRepository: true
})
await globals.window.api.worktrees.resolveMrBase({
repoId: 'repo-1',
mrIid: 7,
sourceBranch: 'feature/mr',
targetBranch: 'release',
isCrossRepository: false
})
expect(runtimeCalls).toEqual([
{
method: 'worktree.create',
params: expect.objectContaining({
repo: 'repo-1',
baseBranch: TEST_COMMIT_OID,
compareBaseRef: 'refs/remotes/origin/main',
createdWithAgent: 'codex',
startupCommand: "codex 'summarize repo'",
startupEnv: { ORCA_AGENT_MODE: 'direct' },
startupLaunchConfig: {
agentCommand: 'codex',
agentArgs: '--model gpt-5',
agentEnv: { ORCA_AGENT_MODE: 'direct' }
},
startupCommandDelivery: 'shell-ready',
activate: true
})
},
{
method: 'worktree.resolvePrBase',
params: {
repo: 'repo-1',
prNumber: 42,
headRefName: 'feature/fix',
baseRefName: 'main',
isCrossRepository: true
}
},
{
method: 'worktree.resolveMrBase',
params: {
repo: 'repo-1',
mrIid: 7,
sourceBranch: 'feature/mr',
targetBranch: 'release',
isCrossRepository: false
}
}
])
})
it('encodes explicit push target clears for runtime worktree updates', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
worktree: { id: 'repo-1::/workspace/review', path: '/workspace/review' }
},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.worktrees.updateMeta({
worktreeId: 'repo-1::/workspace/review',
updates: { linkedPR: null, pushTarget: undefined }
})
expect(runtimeCalls).toEqual([
{
method: 'worktree.set',
params: {
worktree: 'id:repo-1::/workspace/review',
linkedPR: null,
pushTarget: null
}
}
])
})
})
describe('web SSH preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('preserves full and partial authority states from the paired runtime', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
const state =
method === 'ssh.connect'
? {
targetId: 'ssh-1',
status: 'connected',
error: null,
reconnectAttempt: 0,
providerEpoch: 'web-provider-epoch',
connectionGeneration: 23
}
: {
targetId: 'ssh-1',
status: 'connected',
error: null,
reconnectAttempt: 0,
providerEpoch: 'partial-provider-epoch'
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { state },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.ssh.connect({ targetId: 'ssh-1' })).resolves.toMatchObject({
providerEpoch: 'web-provider-epoch',
connectionGeneration: 23
})
const partial = await globals.window.api.ssh.getState({ targetId: 'ssh-1' })
expect(partial).toMatchObject({ providerEpoch: 'partial-provider-epoch' })
expect(partial).not.toHaveProperty('connectionGeneration')
expect(runtimeCalls).toEqual([
{ method: 'ssh.connect', params: { targetId: 'ssh-1' } },
{ method: 'ssh.getState', params: { targetId: 'ssh-1' } }
])
})
})
describe('web file preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('rejects native save-dialog downloads in paired web clients', async () => {
const { api } = await installApi('Linux')
await expect(
api.fs.downloadFile({ filePath: '/workspace/repo/file.txt', connectionId: 'ssh-1' })
).rejects.toThrow('Remote file download is unavailable in paired web clients.')
await expect(
api.fs.downloadFolder({ dirPath: '/workspace/repo/src', connectionId: 'ssh-1' })
).rejects.toThrow('Remote folder download is unavailable in paired web clients.')
})
it('rejects SSH clone requests in paired web clients', async () => {
const { api } = await installApi('Linux')
await expect(
api.repos.cloneRemote({
connectionId: 'ssh-1',
url: 'https://github.com/stablyai/orca.git',
destination: '/workspace'
})
).rejects.toThrow('SSH clone is unavailable in paired web clients.')
})
it('returns false for runtime missing-path errors from fs.pathExists', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const worktree = {
id: 'wt-1',
repoId: 'repo-1',
path: '/workspace/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true,
displayName: 'repo',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
workspaceStatus: 'todo'
}
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'repo.list') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { repos: [{ id: 'repo-1' }] },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'worktree.detectedList') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { repoId: 'repo-1', authoritative: true, worktrees: [worktree] },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'ENOENT', message: 'ENOENT: no such file' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.fs.pathExists({ filePath: '/workspace/repo/untitled.md' })
).resolves.toBe(false)
expect(runtimeCalls).toEqual([
{ method: 'repo.list', params: undefined },
{ method: 'worktree.detectedList', params: { repo: 'repo-1' } },
{ method: 'files.stat', params: { worktree: 'id:wt-1', relativePath: 'untitled.md' } }
])
})
})
describe('web git preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('routes remote commit URL requests through the runtime git API', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const worktree = {
id: 'wt-1',
repoId: 'repo-1',
path: '/workspace/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true,
displayName: 'repo',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
workspaceStatus: 'todo'
}
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'repo.list') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { repos: [{ id: 'repo-1' }] },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'worktree.detectedList') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { repoId: 'repo-1', authoritative: true, worktrees: [worktree] },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'git.remoteCommitUrl') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: `https://git.example.com/project/commit/${TEST_COMMIT_OID}`,
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: false,
error: { code: 'unexpected_method', message: `Unexpected method: ${method}` },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(
globals.window.api.git.remoteCommitUrl({
worktreePath: '/workspace/repo',
sha: TEST_COMMIT_OID
})
).resolves.toBe(`https://git.example.com/project/commit/${TEST_COMMIT_OID}`)
expect(runtimeCalls).toEqual([
{ method: 'repo.list', params: undefined },
{ method: 'worktree.detectedList', params: { repo: 'repo-1' } },
{ method: 'git.remoteCommitUrl', params: { worktree: 'id:wt-1', sha: TEST_COMMIT_OID } }
])
})
it('sends the branch line total merge base only when the chip asked for one', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
const worktree = {
id: 'wt-1',
repoId: 'repo-1',
path: '/workspace/repo',
head: 'abc123',
branch: 'refs/heads/main',
isBare: false,
isMainWorktree: true,
displayName: 'repo',
comment: '',
linkedIssue: null,
linkedPR: null,
linkedLinearIssue: null,
linkedGitLabMR: null,
linkedGitLabIssue: null,
isArchived: false,
isUnread: false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0,
workspaceStatus: 'todo'
}
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'repo.list') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { repos: [{ id: 'repo-1' }] },
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'worktree.detectedList') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { repoId: 'repo-1', authoritative: true, worktrees: [worktree] },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { entries: [], conflictOperation: 'unknown' },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await globals.window.api.git.status({
worktreePath: '/workspace/repo',
branchLineTotalMergeBase: TEST_COMMIT_OID
})
await globals.window.api.git.status({ worktreePath: '/workspace/repo' })
const statusCalls = runtimeCalls.filter((call) => call.method === 'git.status')
// Why: strict — `toEqual` would pass on a forwarded `branchLineTotalMergeBase: undefined`,
// which is exactly what the conditional spread must avoid sending.
expect(statusCalls).toStrictEqual([
{
method: 'git.status',
params: {
worktree: 'id:wt-1',
includeIgnored: undefined,
bypassEffectiveUpstreamNegativeCache: undefined,
reuseLineStats: undefined,
branchLineTotalMergeBase: TEST_COMMIT_OID
}
},
{
method: 'git.status',
params: {
worktree: 'id:wt-1',
includeIgnored: undefined,
bypassEffectiveUpstreamNegativeCache: undefined,
reuseLineStats: undefined
}
}
])
})
})
describe('web GitHub preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('keeps the web GitHub preload key set in parity with the preload contract', async () => {
const { api } = await installApi('Linux')
expect(Object.keys(api.gh).sort()).toEqual(
[
'addIssueComment',
'addIssueCommentBySlug',
'addPRReviewComment',
'addPRReviewCommentReply',
'checkOrcaStarred',
'clearProjectItemField',
'countWorkItems',
'createIssue',
'deleteIssueCommentBySlug',
'diagnoseAuth',
'enqueuePRRefresh',
'getProjectViewTable',
'issue',
'listAccessibleProjects',
'listAssignableUsers',
'listAssignableUsersBySlug',
'listIssueTypesBySlug',
'listIssues',
'listLabels',
'listLabelsBySlug',
'listProjectViews',
'listWorkItems',
'mergePR',
'notifyWorkItemMutated',
'onPRRefreshEvent',
'onWorkItemMutated',
'prCheckDetails',
'prChecks',
'prComments',
'prFileContents',
'prForBranch',
'projectWorkItemDetailsBySlug',
'rateLimit',
'refreshPRNow',
'removePRReviewers',
'repoSlug',
'repoUpstream',
'reportVisiblePRRefreshCandidates',
'rerunPRChecks',
'requestPRReviewers',
'resolveProjectRef',
'resolveReviewThread',
'setPRAutoMerge',
'setPRFileViewed',
'starOrca',
'updateIssue',
'updateIssueBySlug',
'updateIssueCommentBySlug',
'updateIssueTypeBySlug',
'updatePRState',
'updatePRTitle',
'updateProjectItemField',
'updatePullRequestBySlug',
'viewer',
'workItem',
'workItemByOwnerRepo',
'workItemDetails'
].sort()
)
})
it('routes every runtime-backed GitHub method through the expected RPC method', async () => {
type GitHubApi = NonNullable<PreloadApi['gh']>
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { ok: true, items: [], count: 0 },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { GITHUB_WEB_RPC_METHODS, installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const api = globals.window.api
const repoPath = '/workspace/repo'
const withRepo = (params: Record<string, unknown>): Record<string, unknown> => ({
...params,
repo: repoPath
})
const routeCases: {
key: keyof GitHubApi
args?: unknown
expectedMethod: string
expectedParams: unknown
}[] = [
{
key: 'repoSlug',
args: { repoPath },
expectedMethod: 'github.repoSlug',
expectedParams: withRepo({ repoPath })
},
{
key: 'repoUpstream',
args: { repoPath },
expectedMethod: 'github.repoUpstream',
expectedParams: withRepo({ repoPath })
},
{
key: 'prForBranch',
args: { repoPath, branch: 'feature', linkedPRNumber: 3, fallbackPRNumber: null },
expectedMethod: 'github.prForBranch',
expectedParams: withRepo({
repoPath,
branch: 'feature',
linkedPRNumber: 3,
fallbackPRNumber: null
})
},
{
key: 'issue',
args: { repoPath, number: 7 },
expectedMethod: 'github.issue',
expectedParams: withRepo({ repoPath, number: 7 })
},
{
key: 'workItem',
args: { repoPath, number: 7, type: 'issue' },
expectedMethod: 'github.workItem',
expectedParams: withRepo({ repoPath, number: 7, type: 'issue' })
},
{
key: 'workItemByOwnerRepo',
args: { repoPath, owner: 'acme', repo: 'orca', number: 7, type: 'pr' },
expectedMethod: 'github.workItemByOwnerRepo',
expectedParams: withRepo({
repoPath,
owner: 'acme',
ownerRepo: 'orca',
number: 7,
type: 'pr'
})
},
{
key: 'workItemDetails',
args: { repoPath, number: 7, type: 'issue' },
expectedMethod: 'github.workItemDetails',
expectedParams: withRepo({ repoPath, number: 7, type: 'issue' })
},
{
key: 'prFileContents',
args: {
repoPath,
prNumber: 7,
path: 'src/app.ts',
status: 'modified',
headSha: 'head',
baseSha: 'base'
},
expectedMethod: 'github.prFileContents',
expectedParams: withRepo({
repoPath,
prNumber: 7,
path: 'src/app.ts',
status: 'modified',
headSha: 'head',
baseSha: 'base'
})
},
{
key: 'listIssues',
args: { repoPath, limit: 10 },
expectedMethod: 'github.listIssues',
expectedParams: withRepo({ repoPath, limit: 10 })
},
{
key: 'createIssue',
args: { repoPath, title: 'Bug', body: 'Details' },
expectedMethod: 'github.createIssue',
expectedParams: withRepo({ repoPath, title: 'Bug', body: 'Details' })
},
{
key: 'countWorkItems',
args: { repoPath, query: 'is:open' },
expectedMethod: 'github.countWorkItems',
expectedParams: withRepo({ repoPath, query: 'is:open' })
},
{
key: 'listWorkItems',
args: { repoPath, limit: 20, query: 'is:pr', page: 2, noCache: true },
expectedMethod: 'github.listWorkItems',
expectedParams: withRepo({
repoPath,
limit: 20,
query: 'is:pr',
page: 2,
noCache: true
})
},
{
key: 'prChecks',
args: { repoPath, prNumber: 7, headSha: 'head', noCache: true },
expectedMethod: 'github.prChecks',
expectedParams: withRepo({ repoPath, prNumber: 7, headSha: 'head', noCache: true })
},
{
key: 'prCheckDetails',
args: { repoPath, checkRunId: 1, checkName: 'test' },
expectedMethod: 'github.prCheckDetails',
expectedParams: withRepo({ repoPath, checkRunId: 1, checkName: 'test' })
},
{
key: 'rerunPRChecks',
args: { repoPath, prNumber: 7, failedOnly: true },
expectedMethod: 'github.rerunPRChecks',
expectedParams: withRepo({ repoPath, prNumber: 7, failedOnly: true })
},
{
key: 'prComments',
args: { repoPath, prNumber: 7, noCache: true },
expectedMethod: 'github.prComments',
expectedParams: withRepo({ repoPath, prNumber: 7, noCache: true })
},
{
key: 'resolveReviewThread',
args: { repoPath, threadId: 'thread-1', resolve: true },
expectedMethod: 'github.resolveReviewThread',
expectedParams: withRepo({ repoPath, threadId: 'thread-1', resolve: true })
},
{
key: 'setPRFileViewed',
args: { repoPath, prNumber: 7, pullRequestId: 'PR_kw', path: 'src/app.ts', viewed: true },
expectedMethod: 'github.setPRFileViewed',
expectedParams: withRepo({
repoPath,
prNumber: 7,
pullRequestId: 'PR_kw',
path: 'src/app.ts',
viewed: true
})
},
{
key: 'updatePRTitle',
args: { repoPath, prNumber: 7, title: 'New title' },
expectedMethod: 'github.updatePRTitle',
expectedParams: withRepo({ repoPath, prNumber: 7, title: 'New title' })
},
{
key: 'mergePR',
args: { repoPath, prNumber: 7, method: 'squash' },
expectedMethod: 'github.mergePR',
expectedParams: withRepo({ repoPath, prNumber: 7, method: 'squash' })
},
{
key: 'setPRAutoMerge',
args: { repoPath, prNumber: 7, enabled: true, method: 'squash' },
expectedMethod: 'github.setPRAutoMerge',
expectedParams: withRepo({ repoPath, prNumber: 7, enabled: true, method: 'squash' })
},
{
key: 'updatePRState',
args: { repoPath, prNumber: 7, updates: { state: 'closed' } },
expectedMethod: 'github.updatePRState',
expectedParams: withRepo({ repoPath, prNumber: 7, updates: { state: 'closed' } })
},
{
key: 'requestPRReviewers',
args: { repoPath, prNumber: 7, reviewers: ['alice'] },
expectedMethod: 'github.requestPRReviewers',
expectedParams: withRepo({ repoPath, prNumber: 7, reviewers: ['alice'] })
},
{
key: 'removePRReviewers',
args: { repoPath, prNumber: 7, reviewers: ['alice'] },
expectedMethod: 'github.removePRReviewers',
expectedParams: withRepo({ repoPath, prNumber: 7, reviewers: ['alice'] })
},
{
key: 'updateIssue',
args: { repoPath, number: 7, updates: { state: 'closed' } },
expectedMethod: 'github.updateIssue',
expectedParams: withRepo({ repoPath, number: 7, updates: { state: 'closed' } })
},
{
key: 'addIssueComment',
args: { repoPath, number: 7, body: 'Fixed', type: 'issue' },
expectedMethod: 'github.addIssueComment',
expectedParams: withRepo({ repoPath, number: 7, body: 'Fixed', type: 'issue' })
},
{
key: 'addPRReviewCommentReply',
args: { repoPath, prNumber: 7, commentId: 9, body: 'Reply' },
expectedMethod: 'github.addPRReviewCommentReply',
expectedParams: withRepo({ repoPath, prNumber: 7, commentId: 9, body: 'Reply' })
},
{
key: 'addPRReviewComment',
args: {
repoPath,
prNumber: 7,
commitId: 'head',
path: 'src/app.ts',
line: 12,
body: 'Fix'
},
expectedMethod: 'github.addPRReviewComment',
expectedParams: withRepo({
repoPath,
prNumber: 7,
commitId: 'head',
path: 'src/app.ts',
line: 12,
body: 'Fix'
})
},
{
key: 'listLabels',
args: { repoPath },
expectedMethod: 'github.listLabels',
expectedParams: withRepo({ repoPath })
},
{
key: 'listAssignableUsers',
args: { repoPath },
expectedMethod: 'github.listAssignableUsers',
expectedParams: withRepo({ repoPath })
},
{
key: 'rateLimit',
args: { force: true },
expectedMethod: 'github.rateLimit',
expectedParams: { force: true }
},
{
key: 'listAccessibleProjects',
args: { host: 'ghe.example.com' },
expectedMethod: 'github.project.listAccessible',
expectedParams: { host: 'ghe.example.com' }
},
{
key: 'resolveProjectRef',
args: { input: 'acme/1' },
expectedMethod: 'github.project.resolveRef',
expectedParams: { input: 'acme/1' }
},
{
key: 'listProjectViews',
args: { owner: 'acme', ownerType: 'organization', projectNumber: 1 },
expectedMethod: 'github.project.listViews',
expectedParams: { owner: 'acme', ownerType: 'organization', projectNumber: 1 }
},
{
key: 'getProjectViewTable',
args: { owner: 'acme', ownerType: 'organization', projectNumber: 1 },
expectedMethod: 'github.project.viewTable',
expectedParams: { owner: 'acme', ownerType: 'organization', projectNumber: 1 }
},
{
key: 'projectWorkItemDetailsBySlug',
args: { owner: 'acme', repo: 'orca', number: 7, type: 'issue' },
expectedMethod: 'github.project.workItemDetailsBySlug',
expectedParams: { owner: 'acme', repo: 'orca', number: 7, type: 'issue' }
},
{
key: 'updateProjectItemField',
args: { projectId: 'PVT', itemId: 'PVTI', fieldId: 'field', value: 'done' },
expectedMethod: 'github.project.updateItemField',
expectedParams: { projectId: 'PVT', itemId: 'PVTI', fieldId: 'field', value: 'done' }
},
{
key: 'clearProjectItemField',
args: { projectId: 'PVT', itemId: 'PVTI', fieldId: 'field' },
expectedMethod: 'github.project.clearItemField',
expectedParams: { projectId: 'PVT', itemId: 'PVTI', fieldId: 'field' }
},
{
key: 'updateIssueBySlug',
args: { owner: 'acme', repo: 'orca', number: 7, updates: { title: 'New' } },
expectedMethod: 'github.project.updateIssueBySlug',
expectedParams: { owner: 'acme', repo: 'orca', number: 7, updates: { title: 'New' } }
},
{
key: 'updatePullRequestBySlug',
args: { owner: 'acme', repo: 'orca', number: 7, updates: { title: 'New' } },
expectedMethod: 'github.project.updatePullRequestBySlug',
expectedParams: { owner: 'acme', repo: 'orca', number: 7, updates: { title: 'New' } }
},
{
key: 'addIssueCommentBySlug',
args: { owner: 'acme', repo: 'orca', number: 7, body: 'Fixed' },
expectedMethod: 'github.project.addIssueCommentBySlug',
expectedParams: { owner: 'acme', repo: 'orca', number: 7, body: 'Fixed' }
},
{
key: 'updateIssueCommentBySlug',
args: { owner: 'acme', repo: 'orca', commentId: 9, body: 'Edited' },
expectedMethod: 'github.project.updateIssueCommentBySlug',
expectedParams: { owner: 'acme', repo: 'orca', commentId: 9, body: 'Edited' }
},
{
key: 'deleteIssueCommentBySlug',
args: { owner: 'acme', repo: 'orca', commentId: 9 },
expectedMethod: 'github.project.deleteIssueCommentBySlug',
expectedParams: { owner: 'acme', repo: 'orca', commentId: 9 }
},
{
key: 'listLabelsBySlug',
args: { owner: 'acme', repo: 'orca' },
expectedMethod: 'github.project.listLabelsBySlug',
expectedParams: { owner: 'acme', repo: 'orca' }
},
{
key: 'listAssignableUsersBySlug',
args: { owner: 'acme', repo: 'orca', seedLogins: ['alice'] },
expectedMethod: 'github.project.listAssignableUsersBySlug',
expectedParams: { owner: 'acme', repo: 'orca', seedLogins: ['alice'] }
},
{
key: 'listIssueTypesBySlug',
args: { owner: 'acme', repo: 'orca' },
expectedMethod: 'github.project.listIssueTypesBySlug',
expectedParams: { owner: 'acme', repo: 'orca' }
},
{
key: 'updateIssueTypeBySlug',
args: { owner: 'acme', repo: 'orca', number: 7, issueTypeId: 'it-1' },
expectedMethod: 'github.project.updateIssueTypeBySlug',
expectedParams: { owner: 'acme', repo: 'orca', number: 7, issueTypeId: 'it-1' }
}
]
expect(routeCases.map((routeCase) => routeCase.key).sort()).toEqual(
Object.keys(GITHUB_WEB_RPC_METHODS).sort()
)
for (const routeCase of routeCases) {
const method = api.gh[routeCase.key] as (args?: unknown) => Promise<unknown>
await method(routeCase.args)
}
await api.gh.refreshPRNow({
candidate: {
cacheKey: 'repo-1:feature',
repoKind: 'git',
repoId: 'repo-1',
repoPath,
branch: 'feature',
currentHeadOid: 'head-oid',
linkedPRNumber: null,
fallbackPRNumber: 9,
fallbackPRSource: 'pr-cache'
}
})
expect(runtimeCalls).toEqual([
...routeCases.map((routeCase) => ({
method: routeCase.expectedMethod,
params: routeCase.expectedParams
})),
{
method: 'github.prForBranch',
params: {
repoPath,
repoId: 'repo-1',
repo: 'id:repo-1',
branch: 'feature',
linkedPRNumber: null,
fallbackPRNumber: 9,
currentHeadOid: 'head-oid',
acceptMergedFallbackPR: true
}
}
])
})
})
describe('web GitLab preload API', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
vi.doUnmock('electron')
})
it('keeps the web GitLab preload key set in parity with desktop preload', async () => {
vi.doMock('electron', () => ({
ipcRenderer: { invoke: vi.fn() }
}))
const globals = installBrowserGlobals('Linux')
const { glApi } = (await import(
new URL('../../../preload/gitlab.ts', import.meta.url).href
)) as {
glApi: Record<string, unknown>
}
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
expect(Object.keys(globals.window.api.gl).sort()).toEqual(Object.keys(glApi).sort())
})
it('routes every runtime-backed GitLab method through the expected RPC method', async () => {
type GitLabApi = NonNullable<PreloadApi['gl']>
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { ok: true, items: [] },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { GITLAB_WEB_RPC_METHODS, installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const api = globals.window.api
const repoPath = '/workspace/repo'
const routeCases: {
key: keyof typeof GITLAB_WEB_RPC_METHODS
invoke: (gl: GitLabApi) => Promise<unknown>
expectedMethod: string
expectedParams: unknown
}[] = [
{
key: 'diagnoseAuth',
invoke: (gl) => gl.diagnoseAuth(),
expectedMethod: 'gitlab.diagnoseAuth',
expectedParams: undefined
},
{
key: 'rateLimit',
invoke: (gl) => gl.rateLimit({ force: true, host: 'gitlab.example.com' }),
expectedMethod: 'gitlab.rateLimit',
expectedParams: { force: true, host: 'gitlab.example.com' }
},
{
key: 'listMRs',
invoke: (gl) => gl.listMRs({ repoPath, state: 'opened', page: 1, perPage: 50 }),
expectedMethod: 'gitlab.listMRs',
expectedParams: { repoPath, repo: repoPath, state: 'opened', page: 1, perPage: 50 }
},
{
key: 'listWorkItems',
invoke: (gl) => gl.listWorkItems({ repoPath, state: 'closed', page: 2, perPage: 25 }),
expectedMethod: 'gitlab.listWorkItems',
expectedParams: { repoPath, repo: repoPath, state: 'closed', page: 2, perPage: 25 }
},
{
key: 'listIssues',
invoke: (gl) => gl.listIssues({ repoPath, state: 'all', assignee: '@me', limit: 30 }),
expectedMethod: 'gitlab.listIssues',
expectedParams: { repoPath, repo: repoPath, state: 'all', assignee: '@me', limit: 30 }
},
{
key: 'createIssue',
invoke: (gl) => gl.createIssue({ repoPath, title: 'Bug', body: 'Details' }),
expectedMethod: 'gitlab.createIssue',
expectedParams: { repoPath, repo: repoPath, title: 'Bug', body: 'Details' }
},
{
key: 'updateIssue',
invoke: (gl) => gl.updateIssue({ repoPath, number: 7, updates: { state: 'closed' } }),
expectedMethod: 'gitlab.updateIssue',
expectedParams: { repoPath, repo: repoPath, number: 7, updates: { state: 'closed' } }
},
{
key: 'addIssueComment',
invoke: (gl) => gl.addIssueComment({ repoPath, number: 7, body: 'Fixed' }),
expectedMethod: 'gitlab.addIssueComment',
expectedParams: { repoPath, repo: repoPath, number: 7, body: 'Fixed' }
},
{
key: 'listLabels',
invoke: (gl) => gl.listLabels({ repoPath }),
expectedMethod: 'gitlab.listLabels',
expectedParams: { repoPath, repo: repoPath }
},
{
key: 'todos',
invoke: (gl) => gl.todos({ repoPath }),
expectedMethod: 'gitlab.todos',
expectedParams: { repoPath, repo: repoPath }
},
{
key: 'workItemDetails',
invoke: (gl) => gl.workItemDetails({ repoPath, iid: 8, type: 'mr' }),
expectedMethod: 'gitlab.workItemDetails',
expectedParams: { repoPath, repo: repoPath, iid: 8, type: 'mr' }
},
{
key: 'closeMR',
invoke: (gl) => gl.closeMR({ repoPath, iid: 8 }),
expectedMethod: 'gitlab.updateMRState',
expectedParams: { repoPath, repo: repoPath, iid: 8, state: 'closed' }
},
{
key: 'reopenMR',
invoke: (gl) => gl.reopenMR({ repoPath, iid: 8 }),
expectedMethod: 'gitlab.updateMRState',
expectedParams: { repoPath, repo: repoPath, iid: 8, state: 'opened' }
},
{
key: 'mergeMR',
invoke: (gl) => gl.mergeMR({ repoPath, iid: 8, method: 'squash' }),
expectedMethod: 'gitlab.mergeMR',
expectedParams: { repoPath, repo: repoPath, iid: 8, method: 'squash' }
},
{
key: 'updateMR',
invoke: (gl) => gl.updateMR({ repoPath, iid: 8, updates: { title: 'New title' } }),
expectedMethod: 'gitlab.updateMR',
expectedParams: { repoPath, repo: repoPath, iid: 8, updates: { title: 'New title' } }
},
{
key: 'updateMRReviewers',
invoke: (gl) => gl.updateMRReviewers({ repoPath, iid: 8, reviewerIds: [1, 2] }),
expectedMethod: 'gitlab.updateMRReviewers',
expectedParams: { repoPath, repo: repoPath, iid: 8, reviewerIds: [1, 2] }
},
{
key: 'addMRComment',
invoke: (gl) => gl.addMRComment({ repoPath, iid: 8, body: 'Ship it' }),
expectedMethod: 'gitlab.addMRComment',
expectedParams: { repoPath, repo: repoPath, iid: 8, body: 'Ship it' }
},
{
key: 'addMRInlineComment',
invoke: (gl) =>
gl.addMRInlineComment({
repoPath,
iid: 8,
input: {
body: 'Please fix',
path: 'src/app.ts',
line: 12,
baseSha: 'base',
startSha: 'start',
headSha: 'head'
}
}),
expectedMethod: 'gitlab.addMRInlineComment',
expectedParams: {
repoPath,
repo: repoPath,
iid: 8,
input: {
body: 'Please fix',
path: 'src/app.ts',
line: 12,
baseSha: 'base',
startSha: 'start',
headSha: 'head'
}
}
},
{
key: 'resolveMRDiscussion',
invoke: (gl) =>
gl.resolveMRDiscussion({
repoPath,
iid: 8,
discussionId: 'discussion-1',
resolved: true
}),
expectedMethod: 'gitlab.resolveMRDiscussion',
expectedParams: {
repoPath,
repo: repoPath,
iid: 8,
discussionId: 'discussion-1',
resolved: true
}
},
{
key: 'jobTrace',
invoke: (gl) => gl.jobTrace({ repoPath, jobId: 99 }),
expectedMethod: 'gitlab.jobTrace',
expectedParams: { repoPath, repo: repoPath, jobId: 99 }
},
{
key: 'retryJob',
invoke: (gl) => gl.retryJob({ repoPath, jobId: 99 }),
expectedMethod: 'gitlab.retryJob',
expectedParams: { repoPath, repo: repoPath, jobId: 99 }
},
{
key: 'workItemByPath',
invoke: (gl) =>
gl.workItemByPath({
repoPath,
host: 'gitlab.example.com',
path: 'group/project',
iid: 7,
type: 'issue'
}),
expectedMethod: 'gitlab.workItemByPath',
expectedParams: {
repoPath,
repo: repoPath,
host: 'gitlab.example.com',
path: 'group/project',
iid: 7,
type: 'issue'
}
}
]
expect(routeCases.map((routeCase) => routeCase.key).sort()).toEqual(
Object.keys(GITLAB_WEB_RPC_METHODS).sort()
)
for (const routeCase of routeCases) {
await routeCase.invoke(api.gl)
}
expect(runtimeCalls).toEqual(
routeCases.map((routeCase) => ({
method: routeCase.expectedMethod,
params: routeCase.expectedParams
}))
)
})
it('routes GitLab repo selectors through repo id when provided', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: method === 'gitlab.workItemDetails' ? null : { ok: true, items: [] },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const api = globals.window.api
const sourceContext: TaskSourceContext = {
kind: 'task-source',
provider: 'gitlab',
projectId: 'gitlab:gitlab.example.com/group/project',
hostId: 'runtime:web-env-1',
repoId: 'repo-gitlab-runtime',
providerIdentity: {
provider: 'gitlab',
projectId: '42',
namespace: 'group',
project: 'project',
webUrl: 'https://gitlab.example.com/group/project'
}
}
await api.gl.listIssues({
repoPath: '/workspace/repo',
repoId: 'repo-gitlab-runtime',
sourceContext,
state: 'opened'
})
await api.gl.updateMR({
repoPath: '/workspace/repo',
repoId: 'repo-gitlab-runtime',
sourceContext,
iid: 9,
updates: { title: 'New title' }
})
await api.gl.workItemDetails({
repoPath: '/workspace/repo',
repoId: 'repo-gitlab-runtime',
sourceContext,
iid: 9,
type: 'mr'
})
expect(runtimeCalls).toEqual([
{
method: 'gitlab.listIssues',
params: {
repoPath: '/workspace/repo',
repoId: 'repo-gitlab-runtime',
sourceContext,
repo: 'id:repo-gitlab-runtime',
state: 'opened'
}
},
{
method: 'gitlab.updateMR',
params: {
repoPath: '/workspace/repo',
repoId: 'repo-gitlab-runtime',
sourceContext,
repo: 'id:repo-gitlab-runtime',
iid: 9,
updates: { title: 'New title' }
}
},
{
method: 'gitlab.workItemDetails',
params: {
repoPath: '/workspace/repo',
repoId: 'repo-gitlab-runtime',
sourceContext,
repo: 'id:repo-gitlab-runtime',
iid: 9,
type: 'mr'
}
}
])
})
it('exposes the GitLab task methods used by the shared Tasks page', async () => {
const runtimeCalls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params?: unknown): Promise<RuntimeRpcResponse<unknown>> {
runtimeCalls.push({ method, params })
if (method === 'gitlab.listMRs') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
items: [{ id: 'mr-1', type: 'mr', number: 1 }]
},
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'gitlab.listIssues') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: {
items: [{ id: 'issue-2', type: 'issue', number: 2 }]
},
_meta: { runtimeId: 'runtime-1' }
})
}
if (method === 'gitlab.workItemByPath') {
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { id: 'issue-7', type: 'issue', number: 7 },
_meta: { runtimeId: 'runtime-1' }
})
}
return Promise.resolve({
id: `call-${runtimeCalls.length}`,
ok: true,
result: { ok: true },
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
const api = globals.window.api
const mergeRequests = await api.gl.listMRs({
repoPath: '/workspace/repo',
state: 'opened',
page: 1,
perPage: 50
})
const issues = await api.gl.listIssues({
repoPath: '/workspace/repo',
state: 'opened',
assignee: '@me',
limit: 50
})
const item = await api.gl.workItemByPath({
repoPath: '/workspace/repo',
host: 'gitlab.example.com',
path: 'group/project',
iid: 7,
type: 'issue'
})
await api.gl.closeMR({ repoPath: '/workspace/repo', iid: 7 })
expect(mergeRequests.items).toEqual([{ id: 'mr-1', type: 'mr', number: 1 }])
expect(issues.items).toEqual([{ id: 'issue-2', type: 'issue', number: 2 }])
expect(item).toEqual({ id: 'issue-7', type: 'issue', number: 7 })
expect(runtimeCalls.map((call) => call.method)).not.toContain('gitlab.listWorkItems')
expect(runtimeCalls).toEqual([
{
method: 'gitlab.listMRs',
params: {
repoPath: '/workspace/repo',
repo: '/workspace/repo',
state: 'opened',
page: 1,
perPage: 50
}
},
{
method: 'gitlab.listIssues',
params: {
repoPath: '/workspace/repo',
repo: '/workspace/repo',
state: 'opened',
assignee: '@me',
limit: 50
}
},
{
method: 'gitlab.workItemByPath',
params: {
repoPath: '/workspace/repo',
repo: '/workspace/repo',
host: 'gitlab.example.com',
path: 'group/project',
iid: 7,
type: 'issue'
}
},
{
method: 'gitlab.updateMRState',
params: {
repoPath: '/workspace/repo',
repo: '/workspace/repo',
iid: 7,
state: 'closed'
}
}
])
})
})