test: remove redundant browser and transport coverage (#33757)

* test: remove redundant browser and transport coverage

* test: retire deleted suites from reliability manifest

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-25 20:46:39 -07:00
committed by GitHub
co-authored by OrcaWin
parent 7b5f50d917
commit 852dfcd282
64 changed files with 23 additions and 3567 deletions
-4
View File
@@ -497,11 +497,9 @@
1 test-no-real-time-wait:tests/e2e/artificial-opencode-revisit-pressure-scenario.ts
1 test-no-real-time-wait:tests/e2e/artificial-opencode-scroll-scenario.ts
1 test-no-real-time-wait:tests/e2e/browser-guest-crash-recovery.spec.ts
1 test-no-real-time-wait:tests/e2e/browser-settings-preview.spec.ts
1 test-no-real-time-wait:tests/e2e/browser-tab.spec.ts
9 test-no-real-time-wait:tests/e2e/chinese-ime-chat-input-repro.spec.ts
2 test-no-real-time-wait:tests/e2e/cli-floating-file-open-position.spec.ts
1 test-no-real-time-wait:tests/e2e/client-hosted-browser-tooltip-preview.spec.ts
1 test-no-real-time-wait:tests/e2e/codex-ctrl-c-status.spec.ts
4 test-no-real-time-wait:tests/e2e/combined-diff-scroll-restore.spec.ts
1 test-no-real-time-wait:tests/e2e/computer-windows.e2e.ts
@@ -1366,9 +1364,7 @@
1 test-timeout-needs-reason:tests/e2e/agent-session-log-tail-stability.spec.ts
1 test-timeout-needs-reason:tests/e2e/browser-agent-input-focus-isolation-native.spec.ts
5 test-timeout-needs-reason:tests/e2e/browser-agent-input-focus-isolation.spec.ts
1 test-timeout-needs-reason:tests/e2e/browser-settings-preview.spec.ts
1 test-timeout-needs-reason:tests/e2e/cli-agent-unopened-workspace-resume.spec.ts
1 test-timeout-needs-reason:tests/e2e/client-hosted-browser-tooltip-preview.spec.ts
1 test-timeout-needs-reason:tests/e2e/combined-diff-invalidation-freeze-repro.spec.ts
1 test-timeout-needs-reason:tests/e2e/completed-worker-retirement-resume.spec.ts
2 test-timeout-needs-reason:tests/e2e/cross-version-wire/agent-session-clear-old-client.unit.test.ts
File diff suppressed because one or more lines are too long
@@ -2,7 +2,6 @@ import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
mobileNativeChatComposerPlaceholder,
useMobileNativeChatInputLease,
useSettledMobileNativeChatInputLock
} from './use-mobile-native-chat-input-lease'
@@ -110,25 +109,3 @@ describe('useSettledMobileNativeChatInputLock', () => {
expect(settled).toBeNull()
})
})
describe("the phone chat composer's placeholder", () => {
it('says a message runs after the stop while the chat reads Stopping', () => {
expect(mobileNativeChatComposerPlaceholder(null, 'queue')).toBe(
'Queue a message to run after the stop'
)
expect(mobileNativeChatComposerPlaceholder(null, 'send')).toBe(
'Send a message to run after the stop'
)
})
it('reads as usual otherwise', () => {
expect(mobileNativeChatComposerPlaceholder(null, undefined)).toBe(
'Ask anything, @ to mention files, / for commands'
)
})
it('says why the composer is locked first', () => {
expect(mobileNativeChatComposerPlaceholder('disconnected', 'queue')).toBe('Reconnecting…')
expect(mobileNativeChatComposerPlaceholder('waiting', 'queue')).toBe('Waiting for terminal…')
})
})
@@ -1,83 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import {
AGENT_BROWSER_IDLE_TIMEOUT_MS,
createAgentBrowserProcessEnvironment
} from './agent-browser-process-environment'
vi.mock('node:fs', () => ({ mkdirSync: vi.fn(), chmodSync: vi.fn() }))
describe('agent-browser process environment', () => {
it('bounds Unix socket paths independently of a long profile path', () => {
const { env, ownsSocketDirectory } = createAgentBrowserProcessEnvironment({
inheritedEnv: { PATH: '/bin' },
platform: 'darwin',
userDataPath: `/private/var/folders/${'long-profile-segment/'.repeat(12)}`
})
const socketDirectory = env.AGENT_BROWSER_SOCKET_DIR
expect(ownsSocketDirectory).toBe(true)
expect(socketDirectory).toMatch(/^\/tmp\/orca-ab-[0-9a-f]{16}$/)
expect(
`${socketDirectory}/orca-tab-00000000-0000-4000-8000-000000000000.sock`.length
).toBeLessThan(104)
})
it('preserves explicit overrides and leaves Windows socket routing unchanged', () => {
const configured = createAgentBrowserProcessEnvironment({
inheritedEnv: { AGENT_BROWSER_SOCKET_DIR: '/custom/socket-dir' },
platform: 'linux',
userDataPath: '/profile'
})
expect(configured.env.AGENT_BROWSER_SOCKET_DIR).toBe('/custom/socket-dir')
expect(configured.ownsSocketDirectory).toBe(false)
const windows = createAgentBrowserProcessEnvironment({
inheritedEnv: { PATH: 'C:\\Windows' },
platform: 'win32',
userDataPath: 'C:\\Users\\Orca'
})
expect(windows.env.AGENT_BROWSER_SOCKET_DIR).toBeUndefined()
expect(windows.env.PATH).toBe('C:\\Windows')
expect(windows.ownsSocketDirectory).toBe(false)
})
it.each<NodeJS.Platform>(['darwin', 'linux', 'win32'])(
'bounds daemon idle lifetime on %s',
(platform) => {
const { env } = createAgentBrowserProcessEnvironment({
inheritedEnv: { PATH: '/bin' },
platform,
userDataPath: '/profile'
})
expect(env.AGENT_BROWSER_IDLE_TIMEOUT_MS).toBe(String(AGENT_BROWSER_IDLE_TIMEOUT_MS))
}
)
it('never cuts a command short: idle timeout exceeds the bridge exec timeout', () => {
expect(AGENT_BROWSER_IDLE_TIMEOUT_MS).toBeGreaterThan(90_000)
})
it('honors an explicit idle timeout from the environment', () => {
const { env } = createAgentBrowserProcessEnvironment({
inheritedEnv: { AGENT_BROWSER_IDLE_TIMEOUT_MS: '5000' },
platform: 'darwin',
userDataPath: '/profile'
})
expect(env.AGENT_BROWSER_IDLE_TIMEOUT_MS).toBe('5000')
})
it('still bounds the daemon when the socket directory cannot be created', async () => {
const fs = await import('node:fs')
vi.mocked(fs.mkdirSync).mockImplementationOnce(() => {
throw new Error('EACCES')
})
const { env, ownsSocketDirectory } = createAgentBrowserProcessEnvironment({
inheritedEnv: { PATH: '/bin' },
platform: 'linux',
userDataPath: '/profile'
})
expect(env.AGENT_BROWSER_SOCKET_DIR).toBeUndefined()
expect(ownsSocketDirectory).toBe(false)
expect(env.AGENT_BROWSER_IDLE_TIMEOUT_MS).toBe(String(AGENT_BROWSER_IDLE_TIMEOUT_MS))
})
})
@@ -1,50 +0,0 @@
import { beforeEach, expect, it, vi } from 'vitest'
import { join } from 'node:path'
const { lstatSync } = vi.hoisted(() => ({ lstatSync: vi.fn() }))
vi.mock('node:fs', () => ({ lstatSync }))
import { canSkipAgentBrowserSessionReset } from './agent-browser-session-reset'
const owned = {
ownsSocketDirectory: true,
socketDirectory: '/tmp/orca-ab-profile',
sessionName: 'orca-tab-page'
}
const socketPath = join(owned.socketDirectory, 'orca-tab-page.sock')
beforeEach(() => {
lstatSync.mockReset()
})
it('skips an absent owned socket', () => {
lstatSync.mockImplementation(() => {
throw Object.assign(new Error('No socket'), { code: 'ENOENT' })
})
expect(canSkipAgentBrowserSessionReset(owned)).toBe(true)
expect(lstatSync).toHaveBeenCalledWith(socketPath)
})
it('requires reset when a socket or symlink exists', () => {
lstatSync.mockReturnValue({})
expect(canSkipAgentBrowserSessionReset(owned)).toBe(false)
expect(lstatSync).toHaveBeenCalledWith(socketPath)
})
it.each(['EACCES', 'EIO', 'ENOTDIR'])('requires reset for %s', (code) => {
lstatSync.mockImplementation(() => {
throw Object.assign(new Error('Socket inspection failed'), { code })
})
expect(canSkipAgentBrowserSessionReset(owned)).toBe(false)
expect(lstatSync).toHaveBeenCalledWith(socketPath)
})
it.each([
{ ownsSocketDirectory: false },
{ socketDirectory: undefined },
{ sessionName: '../other' },
{ sessionName: 'has space' },
{ sessionName: '' }
])('requires reset without an owned Unix socket address: %j', (override) => {
expect(canSkipAgentBrowserSessionReset({ ...owned, ...override })).toBe(false)
expect(lstatSync).not.toHaveBeenCalled()
})
@@ -21,8 +21,7 @@ vi.mock('electron', () => ({
import {
AGENT_INPUT_FOCUS_GUARD_CHANNEL,
AGENT_INPUT_FOCUS_GUARD_READY_CHANNEL,
guardPaneFocusForAgentInput,
isFocusTakingCdpInput
guardPaneFocusForAgentInput
} from './agent-input-focus-guard'
function fakeRenderer(id: number, answer: boolean) {
@@ -113,14 +112,3 @@ describe('guardPaneFocusForAgentInput', () => {
expect(ipcListeners.get(AGENT_INPUT_FOCUS_GUARD_READY_CHANNEL)).toEqual([])
})
})
describe('isFocusTakingCdpInput', () => {
it('matches presses only', () => {
expect(isFocusTakingCdpInput('Input.dispatchMouseEvent', { type: 'mousePressed' })).toBe(true)
expect(isFocusTakingCdpInput('Input.dispatchMouseEvent', { type: 'mouseReleased' })).toBe(false)
expect(isFocusTakingCdpInput('Input.dispatchMouseEvent', { type: 'mouseMoved' })).toBe(false)
expect(isFocusTakingCdpInput('Input.dispatchTouchEvent', { type: 'touchStart' })).toBe(true)
expect(isFocusTakingCdpInput('Input.synthesizeTapGesture', {})).toBe(true)
expect(isFocusTakingCdpInput('Input.dispatchKeyEvent', { type: 'keyDown' })).toBe(false)
})
})
@@ -36,18 +36,6 @@ function negotiatedTransport(
return transport
}
function boundTransport(
availability: 'unsupported' | 'unavailable'
): BrowserClientFileChannelTransport {
const transport = new BrowserClientFileChannelTransport()
transport.bind({
fileChannelNegotiated: false,
fileChannelAvailability: availability,
sendFileChannelRequest: async () => ({ ok: true, result: {}, _meta: {} }) as never
})
return transport
}
function remoteRoute(outcome: ReturnType<BrowserClientDownloadRelay['route']>) {
expect(outcome.kind).toBe('remote')
return (outcome as { kind: 'remote'; route: BrowserClientDownloadRoute }).route
@@ -117,23 +105,6 @@ describe('BrowserClientDownloadRelay', () => {
expect(removed).toContain(path.dirname(route.stagingPath))
})
it('removes the per-transfer staging directory when the transfer is aborted', async () => {
const { filesystem, removed } = memoryFilesystem(Buffer.from('abc'))
const relay = new BrowserClientDownloadRelay({
stagingRoot: '/tmp/staging',
hostLabel: 'build-box',
transport: negotiatedTransport(() => ({ released: true })),
resolvePage: () => page,
filesystem
})
const route = remoteRoute(relay.route({ guestWebContentsId: 7 }))
await route.abort()
expect(removed).toEqual([path.dirname(route.stagingPath)])
expect(path.dirname(route.stagingPath)).not.toBe('/tmp/staging')
})
it('leaves nothing on disk under the real staging root once transfers settle', async () => {
const root = await realpath(await mkdtemp(path.join(tmpdir(), 'orca-download-relay-')))
const stagingRoot = path.join(root, 'downloads')
@@ -165,42 +136,6 @@ describe('BrowserClientDownloadRelay', () => {
}
})
it('keeps the local fallback when the host never offered the file channel', () => {
const relay = new BrowserClientDownloadRelay({
stagingRoot: '/tmp/staging',
hostLabel: 'build-box',
transport: boundTransport('unsupported'),
resolvePage: () => page,
filesystem: memoryFilesystem(Buffer.alloc(0)).filesystem
})
expect(relay.route({ guestWebContentsId: 7 })).toEqual({ kind: 'local-fallback' })
})
it('reports an owned page whose channel is momentarily gone as unavailable', () => {
const relay = new BrowserClientDownloadRelay({
stagingRoot: '/tmp/staging',
hostLabel: 'build-box',
transport: boundTransport('unavailable'),
resolvePage: () => page,
filesystem: memoryFilesystem(Buffer.alloc(0)).filesystem
})
expect(relay.route({ guestWebContentsId: 7 })).toEqual({ kind: 'unavailable' })
})
it('claims no ownership of a WebContents that is not one of its pages', () => {
const relay = new BrowserClientDownloadRelay({
stagingRoot: '/tmp/staging',
hostLabel: 'build-box',
transport: negotiatedTransport(() => ({ accepted: true })),
resolvePage: () => undefined,
filesystem: memoryFilesystem(Buffer.alloc(0)).filesystem
})
expect(relay.route({ guestWebContentsId: 7 })).toEqual({ kind: 'unowned' })
})
it('stops the commit and never sends the final chunk once the transfer is aborted', async () => {
const writes: { final: boolean }[] = []
const aborts: unknown[] = []
@@ -1,45 +1,9 @@
import { pairedRuntimeConnectionFromOffer } from '../../shared/paired-runtime-connection'
import { describe, expect, it } from 'vitest'
import { BrowserClientHostAttachParams } from '../../shared/browser-client-host-protocol'
import { createBrowserClientHostAttachRequest } from './browser-client-host-attach-request'
import { sameBrowserClientHostLeaseAuthority } from './browser-client-host-command-authority'
import { BrowserClientFileChannelTransport } from './browser-client-file-channel-transport'
import { PairedRuntimeBrowserClientHost } from './paired-runtime-browser-client-host'
const TEST_CONNECTION = pairedRuntimeConnectionFromOffer({
endpoint: 'ws://127.0.0.1:1',
deviceToken: 'token',
publicKeyB64: 'key'
})
const leaseOptions = {
connection: TEST_CONNECTION,
authorityRuntimeId: 'runtime-1',
browserHostClientId: 'host-1',
hostCapabilities: ['webview'],
pageCommandProtocolVersion: 1 as const,
onPageCommand: () => ({ status: 'completed' }) as never
}
describe('browser file channel negotiation', () => {
it('requests the file channel only alongside the command protocol', () => {
expect(
createBrowserClientHostAttachRequest({
...leaseOptions,
fileChannelProtocolVersion: 1
}).params.fileChannelProtocolVersion
).toBe(1)
expect(
createBrowserClientHostAttachRequest({
...leaseOptions,
onPageCommand: undefined,
fileChannelProtocolVersion: 1
}).params.fileChannelProtocolVersion
).toBeUndefined()
})
it('rejects an attach that asks for the file channel without command negotiation', () => {
expect(
BrowserClientHostAttachParams.safeParse({
@@ -51,37 +15,6 @@ describe('browser file channel negotiation', () => {
).toBe(false)
})
it('keeps the same authority when a reconnect renegotiates the file channel', () => {
const negotiated = {
authorityRuntimeId: 'runtime-1',
authorityEpoch: 'epoch-1',
browserHostClientId: 'host-1',
browserHostGeneration: 1,
pageCommandProtocolVersion: 1 as const,
fileChannelProtocolVersion: 1 as const
}
expect(sameBrowserClientHostLeaseAuthority(negotiated, negotiated)).toBe(true)
expect(
sameBrowserClientHostLeaseAuthority(negotiated, {
...negotiated,
fileChannelProtocolVersion: undefined
})
).toBe(true)
expect(
sameBrowserClientHostLeaseAuthority(negotiated, {
...negotiated,
browserHostGeneration: 2
})
).toBe(false)
expect(
sameBrowserClientHostLeaseAuthority(negotiated, {
...negotiated,
pageCommandProtocolVersion: undefined
})
).toBe(false)
})
it('reports the channel unavailable until a negotiated lease binds it', async () => {
const transport = new BrowserClientFileChannelTransport()
expect(transport.available).toBe(false)
@@ -116,38 +49,4 @@ describe('browser file channel negotiation', () => {
expect(transport.available).toBe(false)
expect(transport.availability).toBe('unavailable')
})
it('separates a host that never offered the channel from one whose lease is gone', () => {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the test reads the host's private channel state; a renamed field fails the test.
const host = new PairedRuntimeBrowserClientHost({
connection: TEST_CONNECTION,
authorityRuntimeId: 'runtime-1',
browserHostClientId: 'host-1',
hostCapabilities: ['webview'],
fileChannelProtocolVersion: 1,
handler: () => Promise.reject(new Error('unused'))
}) as unknown as {
closed: boolean
authority: { fileChannelProtocolVersion?: 1 } | null
lease: {
authority: { fileChannelProtocolVersion?: 1 } | null
fileChannelActive: boolean
}
fileChannelAvailability: string
}
expect(host.fileChannelAvailability).toBe('unavailable')
host.authority = { fileChannelProtocolVersion: undefined }
expect(host.fileChannelAvailability).toBe('unsupported')
host.authority = { fileChannelProtocolVersion: 1 }
host.lease.authority = { fileChannelProtocolVersion: 1 }
// Match acceptAuthority’s lease update.
host.lease.fileChannelActive = true
expect(host.fileChannelAvailability).toBe('negotiated')
host.closed = true
expect(host.fileChannelAvailability).toBe('unavailable')
})
})
@@ -1,71 +0,0 @@
import { pairedRuntimeConnectionFromOffer } from '../../shared/paired-runtime-connection'
import { describe, expect, it, vi } from 'vitest'
import type { PairingOffer } from '../../shared/pairing'
import { createBrowserClientHostAttachRequest } from './browser-client-host-attach-request'
const pairing = {
v: 2,
endpoint: 'ws://127.0.0.1:6768',
deviceToken: 'device-token',
publicKeyB64: 'public-key',
pairedDeviceId: 'device-a',
scope: 'runtime'
} as PairingOffer
describe('browser client host attach request', () => {
it('omits unencodable inventory without changing legacy page-command negotiation', () => {
const onPageCommand = vi.fn(() => ({ status: 'completed' as const }))
const attach = createBrowserClientHostAttachRequest({
connection: pairedRuntimeConnectionFromOffer(pairing),
authorityRuntimeId: 'runtime-a',
browserHostClientId: 'host-a',
hostCapabilities: ['webview'],
pageCommandProtocolVersion: 1,
onPageCommand,
pageInventoryProtocolVersion: 1,
leaseReconnectProtocolVersion: 1,
getPageInventory: () => [
{
authorityRuntimeId: 'runtime-old',
authorityEpoch: 'epoch-old',
browserHostClientId: 'host-a',
browserHostGeneration: 2,
browserPageId: 'page-a',
pageHostGeneration: 3,
browserProfileId: '\0'.repeat(256),
executionHostKey: 'native:runtime-old:1',
state: 'active'
}
]
})
expect(attach.pageCommandProtocolVersion).toBe(1)
expect(attach.pageInventoryProtocolVersion).toBeUndefined()
expect(attach.params).toMatchObject({ pageCommandProtocolVersion: 1 })
expect(attach.params).not.toHaveProperty('pageInventoryProtocolVersion')
expect(attach.params).not.toHaveProperty('pageInventory')
expect(attach.params).not.toHaveProperty('leaseReconnectProtocolVersion')
})
it('includes reconnect negotiation only beside an encoded inventory snapshot', () => {
const attach = createBrowserClientHostAttachRequest({
connection: pairedRuntimeConnectionFromOffer(pairing),
authorityRuntimeId: 'runtime-a',
browserHostClientId: 'host-a',
hostCapabilities: ['webview'],
pageInventoryProtocolVersion: 1,
leaseReconnectProtocolVersion: 1,
getPageInventory: () => []
})
expect(attach).toMatchObject({
pageInventoryProtocolVersion: 1,
leaseReconnectProtocolVersion: 1,
params: {
pageInventoryProtocolVersion: 1,
pageInventory: [],
leaseReconnectProtocolVersion: 1
}
})
})
})
@@ -1,73 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE } from '../../shared/browser-client-host-protocol'
import { isBrowserClientHostAuthorityReplaced } from './browser-client-host-authority-replacement'
import { BrowserClientHostAuthorityReplacementWait } from './browser-client-host-authority-replacement-wait'
function errorWithCode(message: string, code: unknown): Error {
return Object.assign(new Error(message), { code })
}
describe('browser client host authority replacement', () => {
it('recognizes the structured mismatch code a current runtime sends', () => {
const rejected = errorWithCode(
'lease attach named a retired runtime',
BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE
)
expect(isBrowserClientHostAuthorityReplaced(rejected)).toBe(true)
})
it('recognizes the mismatch when a new client talks to a runtime older than the typed code', () => {
const rejected = new Error(BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE)
expect('code' in rejected).toBe(false)
expect(isBrowserClientHostAuthorityReplaced(rejected)).toBe(true)
})
it('rejects a host error that carries an unrelated code and an unrelated message', () => {
const unrelated = errorWithCode('browser host process exited', 'runtime_error')
expect(isBrowserClientHostAuthorityReplaced(unrelated)).toBe(false)
})
it('rejects non-Error rejections, including a bare object carrying the mismatch code', () => {
expect(isBrowserClientHostAuthorityReplaced(undefined)).toBe(false)
expect(isBrowserClientHostAuthorityReplaced(null)).toBe(false)
expect(isBrowserClientHostAuthorityReplaced(BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE)).toBe(
false
)
expect(
isBrowserClientHostAuthorityReplaced({ code: BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE })
).toBe(false)
})
it('rejects a non-string code that a loose comparison could coerce', () => {
expect(isBrowserClientHostAuthorityReplaced(errorWithCode('host rejected attach', 503))).toBe(
false
)
expect(
isBrowserClientHostAuthorityReplaced(
errorWithCode('host rejected attach', {
toString: () => BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE
})
)
).toBe(false)
})
it('rejects a message that merely contains the mismatch code inside a longer sentence', () => {
const quoted = new Error(`failed: ${BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE} occurred`)
expect(isBrowserClientHostAuthorityReplaced(quoted)).toBe(false)
})
it('pins the mismatch code string', () => {
// Older clients compare their fallback error message to this exact wire value.
expect(BROWSER_CLIENT_HOST_AUTHORITY_MISMATCH_CODE).toBe(
'browser_client_host_authority_mismatch'
)
})
})
describe('BrowserClientHostAuthorityReplacementWait', () => {
beforeEach(() => {
vi.useFakeTimers()
@@ -1,136 +0,0 @@
import { tmpdir } from 'node:os'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { KnownRuntimeEnvironment } from '../../shared/runtime-environments'
vi.mock('electron', () => ({
app: { getPath: () => tmpdir() },
ipcMain: { on: vi.fn(), removeListener: vi.fn() }
}))
type CompositionOptions = {
createExecutor: (
input: unknown,
options: {
retainNetworkRoute: () => Promise<unknown>
onPageUnavailable: (browserPageId: string, pageHostGeneration: number) => void
}
) => unknown
}
const compositionOptions: CompositionOptions[] = []
const recordedUrlParams: unknown[] = []
vi.mock('./paired-runtime-browser-client-host', () => ({
PairedRuntimeBrowserClientHost: class {}
}))
vi.mock('./browser-client-file-channel-transport', () => ({
BrowserClientFileChannelTransport: class {
bind(): void {}
}
}))
vi.mock('./browser-client-page-command-executor', () => ({
BrowserClientPageCommandExecutor: class {
readonly recordPublishedPageUrl = (params: unknown): void => {
recordedUrlParams.push(params)
}
findPageByWebContentsId(): undefined {
return undefined
}
}
}))
vi.mock('./paired-runtime-browser-client-host-composition', () => ({
PairedRuntimeBrowserClientHostComposition: class {
constructor(options: CompositionOptions) {
compositionOptions.push(options)
}
start(): Promise<unknown> {
return Promise.resolve({ authority: 'lease-a' })
}
close(): Promise<boolean> {
return Promise.resolve(true)
}
whenClosed(): Promise<void> {
return Promise.resolve()
}
}
}))
const ENVIRONMENT_ID = 'environment-a'
const metadata = {
browserHostClientId: 'host-a',
browserHostGeneration: 3,
browserPageId: 'page-a',
pageHostGeneration: 7,
revision: 2,
url: 'https://example.internal/where-the-user-went',
title: 'Where the user went',
loading: false,
canGoBack: true,
canGoForward: false
}
function pairedEnvironment(): KnownRuntimeEnvironment {
return {
id: ENVIRONMENT_ID,
name: 'Environment A',
createdAt: 1,
updatedAt: 1,
lastUsedAt: null,
runtimeId: null,
preferredEndpointId: 'endpoint-a',
endpoints: [
{
id: 'endpoint-a',
endpoint: 'ws://127.0.0.1:9999',
deviceToken: 'token-a',
publicKeyB64: 'key-a'
}
]
} as KnownRuntimeEnvironment
}
async function startHost() {
vi.resetModules()
compositionOptions.length = 0
recordedUrlParams.length = 0
const runtime = await import('./paired-runtime-browser-client-host-runtime')
runtime.configurePairedRuntimeBrowserClientHostsForOrcaProfile({ orcaProfileId: 'profile-a' })
await runtime.startPairedRuntimeBrowserClientHost({
environment: pairedEnvironment(),
authorityRuntimeId: 'runtime-a'
})
const options = compositionOptions[0]
if (!options) {
throw new Error('client host composition was never constructed')
}
const { publishBrowserClientPageMetadata } =
await import('./browser-client-page-metadata-transport')
return { options, publishBrowserClientPageMetadata }
}
afterEach(() => {
vi.resetModules()
})
describe('published-url observation wiring', () => {
// Adoption restores inventory URLs, so user navigations must reach this executor.
it('routes a page-metadata publish into the executor that owns the page', async () => {
const { options, publishBrowserClientPageMetadata } = await startHost()
options.createExecutor(
{ orcaProfileId: 'profile-a' },
{ retainNetworkRoute: () => Promise.resolve({}), onPageUnavailable: () => {} }
)
await publishBrowserClientPageMetadata(ENVIRONMENT_ID, metadata).catch(() => undefined)
expect(recordedUrlParams).toEqual([metadata])
})
})
@@ -9,137 +9,6 @@ import {
describe('BrowserClientPageCommandExecutor', () => {
beforeEach(() => vi.restoreAllMocks())
it('prepares the exact route and blank guest before granting navigation', async () => {
const { dependencies, executor, order, renderer } = createHarness()
const command = createCommand('createPage')
await expect(executor.handle(command, new AbortController().signal)).resolves.toEqual({
status: 'completed'
})
expect(order).toEqual([
'retain-route',
'prepare-page',
'mount-page',
'claim-guest',
'register-guest',
'grant-navigation',
'bind-guest'
])
expect(dependencies.guestBinding.bind).toHaveBeenCalledWith({
registration: {
partition,
browserPageId: 'page-a',
pageHostGeneration: 7,
rendererWebContentsId: 11,
webContentsId: 41
},
browserProfileId: 'profile-a'
})
expect(dependencies.retainNetworkRoute).toHaveBeenCalledWith(
'execution-host-a',
expect.any(AbortSignal)
)
expect(dependencies.routeSessions.preparePage).toHaveBeenCalledWith({
identity: {
orcaProfileId: 'orca-profile-a',
browserProfileId: 'profile-a',
authorityConnectionIdentity: 'authority-record-a',
executionHostIdentity: 'execution-host-record-a'
},
compatibleIdentity: {
orcaProfileId: 'orca-profile-a',
browserProfileId: 'profile-a',
authorityConnectionIdentity: 'pairing-authority-record-a',
executionHostIdentity: 'execution-host-record-a'
},
legacyIdentity: {
orcaProfileId: 'orca-profile-a',
browserProfileId: 'profile-a',
authorityConnectionIdentity: 'legacy-authority-record-a',
executionHostIdentity: 'legacy-execution-host-record-a'
},
storageScope: 'a'.repeat(64),
browserPageId: 'page-a',
pageHostGeneration: 7,
rendererWebContentsId: 11,
proxyEndpoint: { host: '127.0.0.1', port: 43123 }
})
expect(renderer.mountPage).toHaveBeenCalledWith(
{
partition,
browserPageId: 'page-a',
pageHostGeneration: 7
},
expect.any(AbortSignal)
)
expect(dependencies.routeWebContents.registerGuest).toHaveBeenCalledWith({
partition,
browserPageId: 'page-a',
pageHostGeneration: 7,
rendererWebContentsId: 11,
webContentsId: 41
})
})
it('loads a normalized URL only through the retained exact guest', async () => {
const { dependencies, executor } = createHarness()
await executor.handle(createCommand('createPage'), new AbortController().signal)
const lifecycleClaim = dependencies.routeWebContents.claimGuestLifecycle.mock.results[0]?.value
await expect(
executor.handle(createCommand('navigate'), new AbortController().signal)
).resolves.toEqual({ status: 'completed' })
expect(dependencies.routeWebContents.navigateGuest).toHaveBeenCalledWith(
lifecycleClaim,
'https://example.internal/path'
)
expect(executor.snapshotPageInventory()).toEqual([
{
authorityRuntimeId: 'runtime-a',
authorityEpoch: 'epoch-a',
browserHostClientId: 'client-a',
browserHostGeneration: 3,
browserPageId: 'page-a',
pageHostGeneration: 7,
browserProfileId: 'profile-a',
executionHostKey: 'execution-host-a',
state: 'active',
currentUrl: 'https://example.internal/path'
}
])
})
it('executes one automation envelope against the exact retained page', async () => {
const { dependencies, executor } = createHarness()
await executor.handle(createCommand('createPage'), new AbortController().signal)
const command = createCommand('navigate', {
commandId: 'automation-a',
command: {
type: 'automation',
method: 'browser.click',
params: { element: 'Submit' }
}
})
await expect(executor.handle(command, new AbortController().signal)).resolves.toEqual({
status: 'completed',
value: { clicked: true }
})
expect(dependencies.executeAutomation).toHaveBeenCalledWith(
{
browserPageId: 'page-a',
pageHostGeneration: 7,
browserProfileId: 'profile-a',
method: 'browser.click',
params: { element: 'Submit' },
registration: expect.objectContaining({ browserPageId: 'page-a', webContentsId: 41 })
},
expect.any(AbortSignal)
)
})
it('keeps legacy-valid command identities when inventory cannot encode them', async () => {
const { executor } = createHarness()
const browserProfileId = '\0'.repeat(256)
@@ -605,8 +474,4 @@ describe('BrowserClientPageCommandExecutor', () => {
resolveRoute(route)
await expect(first).resolves.toEqual({ status: 'completed' })
})
it('rejects a page limit above the attach inventory maximum', () => {
expect(() => createHarness({ maxPages: 257 })).toThrow('browser_client_page_limit_invalid')
})
})
@@ -1,9 +1,7 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { RemoteRuntimeClientError } from '../../shared/remote-runtime-client-error'
import type { RuntimeRpcResponse } from '../../shared/runtime-rpc-envelope'
import { BROWSER_CLIENT_FILE_CHANNEL_REQUEST_TIMEOUT_MS } from './browser-client-file-channel-transport'
import {
BROWSER_CLIENT_PAGE_METADATA_REQUEST_TIMEOUT_MS,
BrowserClientPageMetadataTransport,
publishBrowserClientPageMetadata,
registerBrowserClientPageMetadataTransport,
@@ -36,31 +34,6 @@ afterEach(() => {
})
describe('browser client page metadata transport', () => {
it('waits at least as long as anything else sharing the lease connection', () => {
expect(BROWSER_CLIENT_PAGE_METADATA_REQUEST_TIMEOUT_MS).toBeGreaterThanOrEqual(
BROWSER_CLIENT_FILE_CHANNEL_REQUEST_TIMEOUT_MS
)
expect(BROWSER_CLIENT_PAGE_METADATA_REQUEST_TIMEOUT_MS).toBeGreaterThanOrEqual(30_000)
})
it('sends the publish over the bound lease and reports the acknowledgement', async () => {
const sendPageMetadataRequest = vi.fn().mockResolvedValue(answered({ accepted: true }))
const transport = new BrowserClientPageMetadataTransport()
transport.bind({ sendPageMetadataRequest })
await expect(transport.publish(PARAMS)).resolves.toEqual({ accepted: true })
expect(sendPageMetadataRequest).toHaveBeenCalledWith(PARAMS, expect.any(Number))
})
it('carries an un-accepted acknowledgement back to the caller', async () => {
const transport = new BrowserClientPageMetadataTransport()
transport.bind({
sendPageMetadataRequest: () => Promise.resolve(answered({ accepted: false }))
})
await expect(transport.publish(PARAMS)).resolves.toEqual({ accepted: false })
})
it('rejects a runtime error and an unreadable acknowledgement', async () => {
const failing = new BrowserClientPageMetadataTransport()
failing.bind({
@@ -148,29 +121,3 @@ describe('browser client page metadata routing', () => {
})
})
})
describe('published-url observation', () => {
it('reports every publish to the observer', async () => {
const observeCurrentUrl = vi.fn()
const transport = new BrowserClientPageMetadataTransport(observeCurrentUrl)
transport.bind({
sendPageMetadataRequest: vi.fn().mockResolvedValue(answered({ accepted: true }))
})
await transport.publish(PARAMS)
await transport.publish({ ...PARAMS, revision: 3, url: 'https://example.internal/again' })
expect(observeCurrentUrl.mock.calls).toEqual([
[PARAMS],
[{ ...PARAMS, revision: 3, url: 'https://example.internal/again' }]
])
})
it('observes the publish even when no lease is bound to send it', async () => {
const observeCurrentUrl = vi.fn()
const transport = new BrowserClientPageMetadataTransport(observeCurrentUrl)
await expect(transport.publish(PARAMS)).rejects.toBeInstanceOf(RemoteRuntimeClientError)
expect(observeCurrentUrl).toHaveBeenCalledWith(PARAMS)
})
})
@@ -1,111 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
BROWSER_CLIENT_HOST_PAGE_INVENTORY_URL_MAX_LENGTH,
type BrowserClientHostedPageInventory
} from '../../shared/browser-client-host-protocol'
import { BrowserClientPageMetadataParams } from '../../shared/browser-client-page-metadata-protocol'
import { recordBrowserClientPagePublishedUrl } from './browser-client-page-inventory'
const HOST_CLIENT_ID = 'client-a'
const PAGE_ID = 'page-a'
const PAGE_HOST_GENERATION = 4
const inventory = (
overrides: Partial<BrowserClientHostedPageInventory> = {}
): BrowserClientHostedPageInventory => ({
authorityRuntimeId: 'runtime-a',
authorityEpoch: 'epoch-a',
browserHostClientId: HOST_CLIENT_ID,
browserHostGeneration: 3,
browserPageId: PAGE_ID,
pageHostGeneration: PAGE_HOST_GENERATION,
browserProfileId: 'profile-a',
executionHostKey: `native:runtime-a:1`,
state: 'active',
...overrides
})
const tracked = (
entry: BrowserClientHostedPageInventory = inventory(),
generation = PAGE_HOST_GENERATION
) => new Map([[entry.browserPageId, { generation, inventory: entry }]])
const metadata = (overrides: Partial<BrowserClientPageMetadataParams> = {}) => ({
browserHostClientId: HOST_CLIENT_ID,
browserHostGeneration: 3,
browserPageId: PAGE_ID,
pageHostGeneration: PAGE_HOST_GENERATION,
revision: 1,
url: 'https://remote.example/after-navigation',
title: 'After navigation',
loading: false,
canGoBack: true,
canGoForward: false,
...overrides
})
describe('recordBrowserClientPagePublishedUrl', () => {
it('records the published url on the matching page', () => {
const pages = tracked()
recordBrowserClientPagePublishedUrl(pages, metadata())
expect(pages.get(PAGE_ID)?.inventory.currentUrl).toBe('https://remote.example/after-navigation')
})
it('ignores metadata whose pageHostGeneration does not match the tracked page', () => {
const pages = tracked(inventory({ currentUrl: 'https://remote.example/opened-at' }))
recordBrowserClientPagePublishedUrl(pages, metadata({ pageHostGeneration: 99 }))
expect(pages.get(PAGE_ID)?.inventory.currentUrl).toBe('https://remote.example/opened-at')
})
it('ignores metadata for a page id the map does not track', () => {
const pages = tracked(inventory({ currentUrl: 'https://remote.example/opened-at' }))
expect(() =>
recordBrowserClientPagePublishedUrl(pages, metadata({ browserPageId: 'page-unknown' }))
).not.toThrow()
expect(pages.get(PAGE_ID)?.inventory.currentUrl).toBe('https://remote.example/opened-at')
expect(pages.has('page-unknown')).toBe(false)
})
it.each([
['an empty object', {}],
['undefined', undefined],
['a string', 'browser.clientHost.pageMetadata'],
['a non-string url', metadata({ url: 42 as unknown as string })],
['a generation below the schema floor', metadata({ pageHostGeneration: 0 })]
])('ignores params that fail schema validation: %s', (_label, params) => {
const pages = tracked(inventory({ currentUrl: 'https://remote.example/opened-at' }))
expect(() => recordBrowserClientPagePublishedUrl(pages, params)).not.toThrow()
expect(pages.get(PAGE_ID)?.inventory.currentUrl).toBe('https://remote.example/opened-at')
})
it('stores a url exactly at the inventory url cap', () => {
const pages = tracked()
const url = `https://remote.example/${'a'.repeat(
BROWSER_CLIENT_HOST_PAGE_INVENTORY_URL_MAX_LENGTH - 'https://remote.example/'.length
)}`
recordBrowserClientPagePublishedUrl(pages, metadata({ url }))
expect(pages.get(PAGE_ID)?.inventory.currentUrl).toHaveLength(
BROWSER_CLIENT_HOST_PAGE_INVENTORY_URL_MAX_LENGTH
)
})
// The metadata schema rejects oversized URLs before the inventory’s URL-drop fallback.
it('rejects an over-cap url at schema validation rather than recording it', () => {
const pages = tracked(inventory({ currentUrl: 'https://remote.example/opened-at' }))
const url = 'h'.repeat(BROWSER_CLIENT_HOST_PAGE_INVENTORY_URL_MAX_LENGTH + 1)
expect(BrowserClientPageMetadataParams.safeParse(metadata({ url })).success).toBe(false)
recordBrowserClientPagePublishedUrl(pages, metadata({ url }))
expect(pages.get(PAGE_ID)?.inventory.currentUrl).toBe('https://remote.example/opened-at')
})
})
@@ -248,32 +248,6 @@ describe('browser client page reconciliation adapters', () => {
])
})
it('restores a host-owned profile with the metadata the host sent', async () => {
const { executor, dependencies } = createHarness()
const profile = {
id: 'remote-profile',
label: 'Remote profile',
scope: 'isolated' as const
}
const restore = command('restorePage', {
command: {
type: 'restorePage',
browserProfileId: profile.id,
profile,
executionHostKey: 'execution-host-a'
}
})
await expect(executor.handle(restore, new AbortController().signal)).resolves.toEqual({
status: 'completed'
})
expect(dependencies.routeSessions.preparePage).toHaveBeenCalledWith(
expect.objectContaining({
identity: expect.objectContaining({ browserProfileId: profile.id }),
profileMetadata: profile
})
)
})
it('destroys a rekeyed guest instead of restoring authority after renderer failure', async () => {
const { executor, renderer, routeWebContents } = createHarness()
await executor.handle(command('createPage'), new AbortController().signal)
@@ -41,16 +41,6 @@ describe('browser client page unavailability', () => {
expect(renderer.retirePage).not.toHaveBeenCalled()
expect(executor.snapshotPageInventory()).toEqual([])
})
it('releases the page availability watch when the page retires', async () => {
const { executor, releaseAvailabilityWatch } = harness()
await executor.handle(createCommand(), new AbortController().signal)
expect(releaseAvailabilityWatch).not.toHaveBeenCalled()
await expect(executor.retirePage('page-a', 7)).resolves.toBe(true)
expect(releaseAvailabilityWatch).toHaveBeenCalledOnce()
})
})
function harness() {
@@ -6,10 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { BrowserClientHostCommandEvent } from '../../shared/browser-client-host-protocol'
import { BrowserClientFileChannelTransport } from './browser-client-file-channel-transport'
import { BrowserClientPageCommandExecutor } from './browser-client-page-command-executor'
import {
BROWSER_CLIENT_UPLOAD_STAGING_MAX_COMMANDS_PER_PAGE,
BrowserClientUploadStaging
} from './browser-client-upload-staging'
import { BrowserClientUploadStaging } from './browser-client-upload-staging'
import type { BrowserRoutePageGuestIdentity } from './browser-route-page-authority'
const partition = `persist:orca-browser-v1-${'a'.repeat(64)}`
@@ -153,23 +150,6 @@ function negotiatedTransport(contents: string): BrowserClientFileChannelTranspor
}
describe('client-placed browser.upload routing', () => {
it('never forwards the remote paths verbatim to the local automation runtime', async () => {
const transport = negotiatedTransport('remote-bytes')
const { executor, automationCalls } = createHarness({ fileChannel: transport })
await executor.handle(createPage, new AbortController().signal)
const result = await executor.handle(
uploadCommand(['docs/report.pdf']),
new AbortController().signal
)
expect(result).toEqual({ status: 'completed', value: { uploaded: true } })
const forwarded = automationCalls[0].params.files as string[]
expect(forwarded).not.toContain('docs/report.pdf')
expect(path.basename(forwarded[0])).toBe('report.pdf')
expect(forwarded[0].startsWith(stagingRoot)).toBe(true)
})
it('fails the command instead of resolving remote paths on this desktop when unnegotiated', async () => {
const { executor, executeAutomation } = createHarness()
await executor.handle(createPage, new AbortController().signal)
@@ -202,26 +182,6 @@ describe('client-placed browser.upload routing', () => {
expect(await readdir(stagingRoot)).toHaveLength(0)
})
it('evicts a page oldest-first once it exceeds the staged-command budget', async () => {
const transport = negotiatedTransport('remote-bytes')
const { executor, uploadStaging } = createHarness({ fileChannel: transport })
await executor.handle(createPage, new AbortController().signal)
for (let sequence = 2; sequence <= 8; sequence += 1) {
await executor.handle(
uploadCommand(['docs/report.pdf'], sequence),
new AbortController().signal
)
}
expect(uploadStaging.activeStagingCount()).toBe(
BROWSER_CLIENT_UPLOAD_STAGING_MAX_COMMANDS_PER_PAGE
)
expect(await readdir(stagingRoot)).toHaveLength(
BROWSER_CLIENT_UPLOAD_STAGING_MAX_COMMANDS_PER_PAGE
)
})
it('retires the guest and releases the route when the staged copies cannot be removed', async () => {
const transport = negotiatedTransport('remote-bytes')
const { executor, retireRendererPage, releaseRouteSession, releaseNetworkRoute } =
@@ -88,31 +88,6 @@ beforeEach(() => {
})
describe('importCookiesIntoClientRoutePartition', () => {
it('imports into a main-derived route partition, never the server profile partition', async () => {
const result = await importCookiesIntoClientRoutePartition(request)
expect(result).toMatchObject({ ok: true, profileId: 'default' })
expect(requireRouteBrowserProfileMock).toHaveBeenCalledWith('default')
const [source, partition] = importCookiesFromBrowserMock.mock.calls[0] ?? []
expect(source).toBe(chrome)
expect(partition).toMatch(/^persist:orca-browser-v1-[a-f0-9]{64}$/)
expect(bindingStore.set).toHaveBeenCalledWith(
partition,
expect.stringMatching(/^[a-f0-9]{64}$/),
routeIdentity.storageScope,
expect.stringMatching(/^[a-f0-9]{64}$/)
)
expect(recordClientRouteImportSourceMock).toHaveBeenCalledWith({
environmentId: 'env-a',
profileId: 'default',
source: {
browserFamily: 'chrome',
profileName: 'Person 1',
importedAt: expect.any(Number)
}
})
})
it('defers to the server-side import when the desktop hosts no pages for the server', async () => {
getRouteIdentityMock.mockReturnValue(null)
@@ -137,16 +112,6 @@ describe('importCookiesIntoClientRoutePartition', () => {
expect(detectInstalledBrowsersMock).not.toHaveBeenCalled()
})
it('reselects a non-default desktop browser profile', async () => {
const secondary = { ...chrome, selectedProfile: 'Profile 2' }
selectBrowserProfileMock.mockReturnValue(secondary)
await importCookiesIntoClientRoutePartition({ ...request, browserProfile: 'Profile 2' })
expect(selectBrowserProfileMock).toHaveBeenCalledWith(chrome, 'Profile 2')
expect(importCookiesFromBrowserMock.mock.calls[0]?.[0]).toBe(secondary)
})
it('leaves the profile source untouched when the import fails', async () => {
importCookiesFromBrowserMock.mockResolvedValue({ ok: false, reason: 'locked database' })
@@ -1,8 +1,7 @@
import { existsSync } from 'node:fs'
import { mkdtemp, readFile, readdir, realpath, rm } from 'node:fs/promises'
import { mkdtemp, realpath, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import type { BrowserClientHostCommandEvent } from '../../shared/browser-client-host-protocol'
import { BrowserClientFileChannelTransport } from './browser-client-file-channel-transport'
@@ -62,79 +61,6 @@ function transportReturning(
}
describe('executeBrowserClientUploadCommand', () => {
it('rewrites remote paths to staged copies that outlive the command until the page is released', async () => {
const staging = new BrowserClientUploadStaging(stagingRoot)
const { transport, calls } = transportReturning(
new Map([
[
'docs/report.pdf',
[{ contentBase64: Buffer.from('hello').toString('base64'), bytesRead: 5, eof: true }]
]
])
)
const run = vi.fn().mockResolvedValue({ uploaded: true })
const result = await executeBrowserClientUploadCommand({
event: uploadEvent(['docs/report.pdf']),
params: { element: '#f', files: ['docs/report.pdf'] },
fileChannel: transport,
staging,
run
})
expect(result).toEqual({ uploaded: true })
const rewritten = run.mock.calls[0][0] as { files: string[] }
expect(rewritten.files).toHaveLength(1)
expect(path.basename(rewritten.files[0])).toBe('report.pdf')
expect(rewritten.files[0]).not.toBe('docs/report.pdf')
expect(calls).toHaveLength(1)
expect(await readFile(rewritten.files[0], 'utf8')).toBe('hello')
expect(staging.activeStagingCount()).toBe(1)
expect(await staging.releasePage('page-1')).toBe(1)
expect(await readdir(stagingRoot)).toHaveLength(0)
})
it('fails closed and stages nothing when the file channel was not negotiated', async () => {
const staging = new BrowserClientUploadStaging(stagingRoot)
const transport = new BrowserClientFileChannelTransport()
const run = vi.fn()
await expect(
executeBrowserClientUploadCommand({
event: uploadEvent(['/etc/passwd']),
params: { element: '#f', files: ['/etc/passwd'] },
fileChannel: transport,
staging,
run
})
).rejects.toThrow('browser_client_file_channel_unsupported')
expect(run).not.toHaveBeenCalled()
expect(staging.activeStagingCount()).toBe(0)
expect(existsSync(stagingRoot)).toBe(false)
})
it('removes staged files when the upload itself fails', async () => {
const staging = new BrowserClientUploadStaging(stagingRoot)
const { transport } = transportReturning(
new Map([['a.txt', [{ contentBase64: '', bytesRead: 0, eof: true }]]])
)
await expect(
executeBrowserClientUploadCommand({
event: uploadEvent(['a.txt']),
params: { element: '#f', files: ['a.txt'] },
fileChannel: transport,
staging,
run: async () => {
throw new Error('element not found')
}
})
).rejects.toThrow('element not found')
expect(staging.activeStagingCount()).toBe(0)
expect(await readdir(stagingRoot)).toHaveLength(0)
})
it('reports the upload failure, not the cleanup failure, when both fail', async () => {
const staging = new BrowserClientUploadStaging(stagingRoot, {
mkdir: async () => {},
@@ -1,9 +1,8 @@
import { existsSync } from 'node:fs'
import type * as NodeFsPromises from 'node:fs/promises'
import { mkdir, mkdtemp, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import {
BROWSER_CLIENT_UPLOAD_STAGING_MAX_BYTES_PER_PAGE,
@@ -11,19 +10,6 @@ import {
BrowserClientUploadStaging
} from './browser-client-upload-staging'
const nodeRemovals = vi.hoisted(() => ({ rm: [] as { target: unknown; options: unknown }[] }))
vi.mock('node:fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof NodeFsPromises>()
return {
...actual,
rm: (target: unknown, options: unknown) => {
nodeRemovals.rm.push({ target, options })
return (actual.rm as (t: unknown, o: unknown) => Promise<void>)(target, options)
}
}
})
let stagingRoot = ''
beforeEach(async () => {
@@ -308,24 +294,4 @@ describe('BrowserClientUploadStaging', () => {
// A later release needs the retained directory record to retry cleanup.
expect(staging.activeStagingCount()).toBe(1)
})
it('retries the staged removal so a briefly held file is not orphaned', async () => {
const staging = new BrowserClientUploadStaging(stagingRoot)
const staged = await staging.stage({
browserPageId: 'page-1',
pageHostGeneration: 1,
files: [{ remotePath: 'a.txt', contents: Buffer.from('a') }]
})
const directory = staging.stagedDirectory(staged.stagingId)
nodeRemovals.rm.length = 0
expect(await staging.release(staged.stagingId)).toBe(true)
expect(nodeRemovals.rm).toEqual([
{
target: directory,
options: { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }
}
])
})
})
@@ -1,11 +1,7 @@
import { describe, expect, it } from 'vitest'
import type { BrowserClientHostCommandEvent } from '../../shared/browser-client-host-protocol'
import {
BROWSER_CLIENT_FILE_CHANNEL_READ_METHOD,
fetchBrowserClientUploadFiles,
readBrowserClientUploadPaths
} from './browser-client-upload-transfer'
import { fetchBrowserClientUploadFiles } from './browser-client-upload-transfer'
const event = {
authorityRuntimeId: 'runtime-1',
@@ -17,37 +13,6 @@ const event = {
} as BrowserClientHostCommandEvent
describe('fetchBrowserClientUploadFiles', () => {
it('reassembles a multi-chunk remote file and stamps the page authority on every read', async () => {
const requests: { method: string; params: Record<string, unknown> }[] = []
const chunks = ['abc', 'de']
const files = await fetchBrowserClientUploadFiles({
request: async (method, params) => {
requests.push({ method, params: params as Record<string, unknown> })
const chunk = chunks.shift() ?? ''
return {
contentBase64: Buffer.from(chunk).toString('base64'),
bytesRead: chunk.length,
totalBytes: 5,
eof: chunks.length === 0
}
},
event,
remotePaths: ['docs/a.txt']
})
expect(files).toEqual([{ remotePath: 'docs/a.txt', contents: Buffer.from('abcde') }])
expect(requests).toHaveLength(2)
expect(requests[0].method).toBe(BROWSER_CLIENT_FILE_CHANNEL_READ_METHOD)
expect(requests[0].params).toMatchObject({
fileChannelProtocolVersion: 1,
browserPageId: 'page-1',
pageHostGeneration: 3,
workspaceRelativePath: 'docs/a.txt',
offset: 0
})
expect(requests[1].params).toMatchObject({ offset: 3 })
})
it('stops instead of looping forever on a host that never reports eof', async () => {
await expect(
fetchBrowserClientUploadFiles({
@@ -83,22 +48,3 @@ describe('fetchBrowserClientUploadFiles', () => {
).rejects.toThrow('browser_client_upload_file_count_exceeded')
})
})
describe('readBrowserClientUploadPaths', () => {
it('accepts a list of non-empty path strings', () => {
expect(readBrowserClientUploadPaths({ files: ['a.txt', 'b/c.txt'] })).toEqual([
'a.txt',
'b/c.txt'
])
})
it('rejects a missing, non-array, or non-string files payload', () => {
expect(() => readBrowserClientUploadPaths({})).toThrow('browser_client_upload_files_required')
expect(() => readBrowserClientUploadPaths({ files: 'a.txt' })).toThrow(
'browser_client_upload_files_required'
)
expect(() => readBrowserClientUploadPaths({ files: [''] })).toThrow(
'browser_client_upload_files_required'
)
})
})
@@ -1,216 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
browserAuthorityExecutionHostStorageIdentity,
browserNetworkExecutionHostStorageIdentity,
legacyBrowserNativeExecutionHostStorageIdentity,
legacyBrowserNetworkExecutionHostStorageIdentity
} from './browser-execution-host-storage-identity'
import { browserNetworkExecutionHostKey } from './browser-network-execution-route'
const storageKey = 'a'.repeat(64)
const otherStorageKey = 'b'.repeat(64)
describe('browserNetworkExecutionHostStorageIdentity', () => {
it('ignores the per-boot components the route key fences on', () => {
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'runtime-a', revision: 1 },
storageKey
)
).toBe(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'runtime-a', revision: 2 },
storageKey
)
)
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-a', revision: 1, distro: 'Ubuntu' },
storageKey
)
).toBe(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-a', revision: 2, distro: 'Ubuntu' },
storageKey
)
)
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'ssh', targetId: 'ssh-1', providerEpoch: 'epoch-a', connectionGeneration: 1 },
storageKey
)
).toBe(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'ssh', targetId: 'ssh-1', providerEpoch: 'epoch-b', connectionGeneration: 2 },
storageKey
)
)
})
it('survives the authority runtime restarting under a new runtimeId', () => {
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'runtime-a', revision: 1 },
storageKey
)
).toBe(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'runtime-b', revision: 9 },
storageKey
)
)
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-a', revision: 1, distro: 'Ubuntu' },
storageKey
)
).toBe(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-b', revision: 9, distro: 'Ubuntu' },
storageKey
)
)
})
it('separates every boundary that changes storage or egress', () => {
const identities = [
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'runtime-a', revision: 1 },
storageKey
),
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'runtime-a', revision: 1 },
otherStorageKey
),
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-a', revision: 1, distro: 'Ubuntu' },
storageKey
),
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-a', revision: 1, distro: 'Debian' },
storageKey
),
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'runtime-a', revision: 1, distro: 'Ubuntu' },
otherStorageKey
),
browserNetworkExecutionHostStorageIdentity(
{ kind: 'ssh', targetId: 'ssh-1', providerEpoch: 'epoch-a', connectionGeneration: 1 },
storageKey
),
browserNetworkExecutionHostStorageIdentity(
{ kind: 'ssh', targetId: 'ssh-2', providerEpoch: 'epoch-a', connectionGeneration: 1 },
storageKey
)
]
expect(new Set(identities).size).toBe(identities.length)
})
it('keeps delimiter-bearing components structurally distinct', () => {
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'a', revision: 1, distro: 'b","c' },
'a'
)
).not.toBe(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'a', revision: 1, distro: 'c' },
'a","b'
)
)
})
// These serialized identities name persisted partitions, including migration sources.
it('pins the storage identity of every host kind, current and pre-migration', () => {
const tag = '"orca-browser-execution-host-storage",1'
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'native', runtimeId: 'r', revision: 1 },
storageKey
)
).toBe(`[${tag},"authority","${storageKey}"]`)
expect(browserAuthorityExecutionHostStorageIdentity(storageKey)).toBe(
`[${tag},"authority","${storageKey}"]`
)
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'wsl', runtimeId: 'r', revision: 1, distro: 'Ubuntu' },
storageKey
)
).toBe(`[${tag},"authority-wsl","${storageKey}","Ubuntu"]`)
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'ssh', targetId: 'ssh-1', providerEpoch: 'e', connectionGeneration: 1 },
storageKey
)
).toBe(`[${tag},"ssh","ssh-1"]`)
expect(legacyBrowserNativeExecutionHostStorageIdentity('r')).toBe(`[${tag},"native","r"]`)
expect(
legacyBrowserNetworkExecutionHostStorageIdentity({
kind: 'native',
runtimeId: 'r',
revision: 1
})
).toBe(`[${tag},"native","r"]`)
expect(
legacyBrowserNetworkExecutionHostStorageIdentity({
kind: 'wsl',
runtimeId: 'r',
revision: 1,
distro: 'Ubuntu'
})
).toBe(`[${tag},"wsl","r","Ubuntu"]`)
expect(
legacyBrowserNetworkExecutionHostStorageIdentity({
kind: 'ssh',
targetId: 'ssh-1',
providerEpoch: 'e',
connectionGeneration: 1
})
).toBe(`[${tag},"ssh","ssh-1"]`)
})
it('keeps pre-migration WSL distros apart on one runtime', () => {
expect(
legacyBrowserNetworkExecutionHostStorageIdentity({
kind: 'wsl',
runtimeId: 'r',
revision: 1,
distro: 'Ubuntu'
})
).not.toBe(
legacyBrowserNetworkExecutionHostStorageIdentity({
kind: 'wsl',
runtimeId: 'r',
revision: 1,
distro: 'Debian'
})
)
})
it('never lets an SSH target share the paired server machine identity', () => {
const shared = 'ssh-1700000000-aaa111'
expect(
browserNetworkExecutionHostStorageIdentity(
{ kind: 'ssh', targetId: shared, providerEpoch: 'e', connectionGeneration: 1 },
shared
)
).not.toBe(browserAuthorityExecutionHostStorageIdentity(shared))
})
it('is never mistaken for a route fencing key', () => {
const host = {
kind: 'native',
runtimeId: 'runtime-a',
revision: 7
} as const
expect(browserNetworkExecutionHostStorageIdentity(host, storageKey)).not.toBe(
browserNetworkExecutionHostKey(host)
)
})
})
@@ -127,11 +127,6 @@ describe('browser external app launch', () => {
expect(renderer.send).not.toHaveBeenCalled()
})
it('names no site when the asking frame is unknown', () => {
ask(OKTA_URL, null)
expect(sentRequests(renderer)[0].details).toMatchObject({ origin: null })
})
it('refuses a page with no window on this computer', () => {
resolveTarget = () => null
expect(ask()).toBe(false)
@@ -1,65 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
googleAuthUserAgent,
isGoogleAuthUrl,
setUserAgentHeader,
stripClientHints
} from './browser-google-auth-ua'
describe('isGoogleAuthUrl', () => {
it('matches the Google/YouTube sign-in hosts exactly', () => {
expect(isGoogleAuthUrl('https://accounts.google.com/')).toBe(true)
expect(isGoogleAuthUrl('https://accounts.google.com/v3/signin/identifier')).toBe(true)
expect(isGoogleAuthUrl('https://accounts.youtube.com/signin')).toBe(true)
expect(isGoogleAuthUrl('https://ACCOUNTS.GOOGLE.COM/')).toBe(true)
})
it('does not match post-auth app subdomains or lookalikes', () => {
expect(isGoogleAuthUrl('https://myaccount.google.com/')).toBe(false)
expect(isGoogleAuthUrl('https://mail.google.com/')).toBe(false)
expect(isGoogleAuthUrl('https://accounts.google.com.evil.test/')).toBe(false)
expect(isGoogleAuthUrl('https://google.com/')).toBe(false)
expect(isGoogleAuthUrl('not a url')).toBe(false)
})
})
describe('googleAuthUserAgent', () => {
it('produces an internally consistent Firefox UA for the host platform', () => {
const ua = googleAuthUserAgent()
expect(ua).toMatch(/^Mozilla\/5\.0 \(.+; rv:\d+\.0\) Gecko\/20100101 Firefox\/\d+\.0$/)
expect(ua).not.toContain('Chrome')
expect(ua).not.toContain('Electron')
})
})
describe('stripClientHints', () => {
it('removes every sec-ch-ua* header regardless of case, keeps others', () => {
const headers: Record<string, string> = {
'sec-ch-ua': 'a',
'Sec-CH-UA-Platform': 'b',
'sec-ch-ua-full-version-list': 'c',
'User-Agent': 'ua',
Accept: 'text/html'
}
stripClientHints(headers)
expect(Object.keys(headers).some((k) => k.toLowerCase().startsWith('sec-ch-ua'))).toBe(false)
expect(headers['User-Agent']).toBe('ua')
expect(headers.Accept).toBe('text/html')
})
})
describe('setUserAgentHeader', () => {
it('overwrites an existing user-agent header in place, preserving its casing', () => {
const headers: Record<string, string> = { 'user-agent': 'old' }
setUserAgentHeader(headers, 'new')
expect(headers['user-agent']).toBe('new')
expect(Object.keys(headers)).toEqual(['user-agent'])
})
it('adds a canonical header when none exists', () => {
const headers: Record<string, string> = {}
setUserAgentHeader(headers, 'new')
expect(headers['User-Agent']).toBe('new')
})
})
@@ -1,317 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { screenGetCursorScreenPointMock } = vi.hoisted(() => ({
screenGetCursorScreenPointMock: vi.fn(() => ({ x: 0, y: 0 }))
}))
vi.mock('electron', () => ({
screen: { getCursorScreenPoint: screenGetCursorScreenPointMock }
}))
import { setupGuestContextMenu } from './browser-guest-context-menu'
describe('setupGuestContextMenu', () => {
const browserTabId = 'tab-1'
let rendererSendMock: ReturnType<typeof vi.fn>
let guestOnMock: ReturnType<typeof vi.fn>
let guestOffMock: ReturnType<typeof vi.fn>
function makeGuest(overrides: Record<string, unknown> = {}) {
return {
getURL: vi.fn(() => 'https://example.com'),
canGoBack: vi.fn(() => true),
canGoForward: vi.fn(() => false),
navigationHistory: {
canGoBack: vi.fn(() => true),
canGoForward: vi.fn(() => false)
},
on: guestOnMock,
off: guestOffMock,
...overrides
} as unknown as Electron.WebContents
}
function makeRenderer() {
return { send: rendererSendMock } as unknown as Electron.WebContents
}
beforeEach(() => {
rendererSendMock = vi.fn()
guestOnMock = vi.fn()
guestOffMock = vi.fn()
screenGetCursorScreenPointMock.mockReturnValue({ x: 0, y: 0 })
})
function triggerContextMenu(
_guest: Electron.WebContents,
params: Partial<Electron.ContextMenuParams>
) {
const handler = guestOnMock.mock.calls.find((call) => call[0] === 'context-menu')?.[1] as
| ((event: unknown, params: Electron.ContextMenuParams) => void)
| undefined
expect(handler).toBeTypeOf('function')
handler!({}, { x: 0, y: 0, linkURL: '', ...params } as Electron.ContextMenuParams)
}
it('passes through guest viewport coordinates (params.x/y) to the renderer', () => {
const guest = makeGuest()
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 150, y: 275 })
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:context-menu-requested',
expect.objectContaining({ x: 150, y: 275 })
)
})
it('includes navigation state and page URL alongside coordinates', () => {
screenGetCursorScreenPointMock.mockReturnValue({ x: 500, y: 375 })
const guest = makeGuest({
getURL: vi.fn(() => 'https://test.dev/page'),
navigationHistory: {
canGoBack: vi.fn(() => true),
canGoForward: vi.fn(() => true)
}
})
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 50, y: 75, linkURL: 'https://test.dev/link' })
expect(rendererSendMock).toHaveBeenCalledWith('browser:context-menu-requested', {
browserPageId: browserTabId,
x: 50,
y: 75,
screenX: 500,
screenY: 375,
pageUrl: 'https://test.dev/page',
linkUrl: 'https://test.dev/link',
selectionText: '',
canGoBack: true,
canGoForward: true,
editable: null,
image: null
})
})
it('forwards the native selection text so the renderer can offer Copy', () => {
const guest = makeGuest()
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 10, y: 20, selectionText: 'copied selection' })
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:context-menu-requested',
expect.objectContaining({ selectionText: 'copied selection' })
)
})
it('reads navigation state from navigationHistory', () => {
const deprecatedCanGoBack = vi.fn(() => false)
const deprecatedCanGoForward = vi.fn(() => false)
const guest = makeGuest({
canGoBack: deprecatedCanGoBack,
canGoForward: deprecatedCanGoForward,
navigationHistory: {
canGoBack: vi.fn(() => true),
canGoForward: vi.fn(() => true)
}
})
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 50, y: 75 })
expect(deprecatedCanGoBack).not.toHaveBeenCalled()
expect(deprecatedCanGoForward).not.toHaveBeenCalled()
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:context-menu-requested',
expect.objectContaining({ canGoBack: true, canGoForward: true })
)
})
it('describes the editable field and image under the cursor', () => {
const renderer = makeRenderer()
setupGuestContextMenu({ browserTabId, guest: makeGuest(), resolveRenderer: () => renderer })
triggerContextMenu(makeGuest(), {
isEditable: true,
editFlags: {
canUndo: true,
canRedo: false,
canCut: true,
canCopy: true,
canPaste: false,
canSelectAll: true,
canDelete: true,
canEditRichly: false
},
misspelledWord: 'quikc',
dictionarySuggestions: ['quick', 'quirk', 'quack', 'quiche', 'quince', 'quinoa'],
mediaType: 'image',
srcURL: 'https://cdn.test/a.png',
hasImageContents: true
})
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:context-menu-requested',
expect.objectContaining({
editable: {
canUndo: true,
canRedo: false,
canCut: true,
canCopy: true,
canPaste: false,
canSelectAll: true,
spelling: {
suggestions: ['quick', 'quirk', 'quack', 'quiche', 'quince'],
misspelledWord: 'quikc'
}
},
image: { url: 'https://cdn.test/a.png', canCopy: true }
})
)
})
it('does not send when renderer is unavailable', () => {
const guest = makeGuest()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => null
})
triggerContextMenu(guest, { x: 100, y: 200 })
expect(rendererSendMock).not.toHaveBeenCalled()
})
it('cleans up context-menu listener on teardown', () => {
const guest = makeGuest()
const cleanup = setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => makeRenderer()
})
cleanup()
expect(guestOffMock).toHaveBeenCalledWith('context-menu', expect.any(Function))
})
describe('dismiss handler', () => {
function triggerMouseEvent(button: string, type: string = 'mouseDown') {
const beforeMouseHandler = guestOnMock.mock.calls.find(
(call) => call[0] === 'before-mouse-event'
)?.[1] as ((event: unknown, mouse: { type: string; button: string }) => void) | undefined
expect(beforeMouseHandler).toBeTypeOf('function')
beforeMouseHandler!({}, { type, button })
}
it('dismisses context menu on left-click', () => {
const guest = makeGuest()
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 100, y: 200 })
rendererSendMock.mockClear()
triggerMouseEvent('left')
expect(rendererSendMock).toHaveBeenCalledWith('browser:context-menu-dismissed', {
browserPageId: browserTabId
})
})
it('does not dismiss context menu on right-click', () => {
const guest = makeGuest()
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 100, y: 200 })
rendererSendMock.mockClear()
triggerMouseEvent('right')
expect(rendererSendMock).not.toHaveBeenCalledWith(
'browser:context-menu-dismissed',
expect.anything()
)
})
it('dismisses context menu on middle-click', () => {
const guest = makeGuest()
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 100, y: 200 })
rendererSendMock.mockClear()
triggerMouseEvent('middle')
expect(rendererSendMock).toHaveBeenCalledWith('browser:context-menu-dismissed', {
browserPageId: browserTabId
})
})
it('ignores non-mouseDown events', () => {
const guest = makeGuest()
const renderer = makeRenderer()
setupGuestContextMenu({
browserTabId,
guest,
resolveRenderer: () => renderer
})
triggerContextMenu(guest, { x: 100, y: 200 })
rendererSendMock.mockClear()
triggerMouseEvent('left', 'mouseMove')
expect(rendererSendMock).not.toHaveBeenCalled()
})
})
})
@@ -9,14 +9,10 @@ vi.mock('electron', () => ({
webContents: { fromId: vi.fn() }
}))
import {
resolveGuestMouseWheelZoomDirection,
setupGuestMouseWheelZoomForwarding
} from './browser-guest-wheel-zoom'
import { setupGuestMouseWheelZoomForwarding } from './browser-guest-wheel-zoom'
import { setupGuestShortcutForwarding } from './browser-guest-shortcut-forwarding'
const zoomTo = (direction: 'in' | 'out' | 'reset') => ({ browserPageId: 'tab-1', direction })
const historyTo = (direction: 'back' | 'forward') => ({ browserPageId: 'tab-1', direction })
describe('guest mouse wheel browser zoom', () => {
const browserTabId = 'tab-1'
@@ -64,37 +60,6 @@ describe('guest mouse wheel browser zoom', () => {
guestOffMock = vi.fn()
})
it('resolves ctrl wheel direction from guest mouse input', () => {
expect(resolveGuestMouseWheelZoomDirection(mouseWheel({ deltaY: -120 }), 'win32')).toBe('in')
expect(resolveGuestMouseWheelZoomDirection(mouseWheel({ deltaY: 120 }), 'linux')).toBe('out')
})
it('allows command wheel only on macOS', () => {
const commandWheel = mouseWheel({ modifiers: ['cmd'], deltaY: -120 })
expect(resolveGuestMouseWheelZoomDirection(commandWheel, 'darwin')).toBe('in')
expect(resolveGuestMouseWheelZoomDirection(commandWheel, 'win32')).toBeNull()
})
it('ignores non-zoom wheel input', () => {
expect(
resolveGuestMouseWheelZoomDirection(mouseWheel({ modifiers: [], deltaY: -120 }), 'linux')
).toBeNull()
expect(
resolveGuestMouseWheelZoomDirection(mouseWheel({ modifiers: ['ctrl', 'alt'] }), 'linux')
).toBeNull()
expect(
resolveGuestMouseWheelZoomDirection(mouseWheel({ modifiers: ['ctrl', 'shift'] }), 'linux')
).toBeNull()
expect(resolveGuestMouseWheelZoomDirection(mouseWheel({ deltaY: 0 }), 'linux')).toBeNull()
expect(
resolveGuestMouseWheelZoomDirection(
{ type: 'mouseMove', x: 0, y: 0, modifiers: ['ctrl'] },
'linux'
)
).toBeNull()
})
it('forwards ctrl wheel to browser page zoom and consumes the guest wheel event', () => {
setupGuestMouseWheelZoomForwarding({
browserTabId,
@@ -123,18 +88,6 @@ describe('guest mouse wheel browser zoom', () => {
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(rendererSendMock).not.toHaveBeenCalled()
})
it('cleans up the mouse wheel listener on teardown', () => {
const cleanup = setupGuestMouseWheelZoomForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer()
})
cleanup()
expect(guestOffMock).toHaveBeenCalledWith('before-mouse-event', expect.any(Function))
})
})
describe('setupGuestShortcutForwarding', () => {
@@ -250,151 +203,6 @@ describe('setupGuestShortcutForwarding', () => {
}
})
it('forwards browser page zoom shortcuts from focused guest pages', () => {
setupGuestShortcutForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer()
})
const zoomInPreventDefault = triggerBeforeInput({ code: 'Equal', key: '=' })
const shiftedPlusPreventDefault = triggerBeforeInput({
code: 'Equal',
key: '+',
shift: true
})
const zoomOutPreventDefault = triggerBeforeInput({ code: 'Minus', key: '-' })
const numpadSubtractPreventDefault = triggerBeforeInput({ code: 'NumpadSubtract', key: '-' })
const resetPreventDefault = triggerBeforeInput({ code: 'Digit0', key: '0' })
const repeatPreventDefault = triggerBeforeInput({
code: 'NumpadAdd',
key: '+',
isAutoRepeat: true
})
expect(zoomInPreventDefault).toHaveBeenCalledTimes(1)
expect(shiftedPlusPreventDefault).toHaveBeenCalledTimes(1)
expect(zoomOutPreventDefault).toHaveBeenCalledTimes(1)
expect(numpadSubtractPreventDefault).toHaveBeenCalledTimes(1)
expect(resetPreventDefault).toHaveBeenCalledTimes(1)
expect(repeatPreventDefault).toHaveBeenCalledTimes(1)
expect(rendererSendMock).toHaveBeenNthCalledWith(1, 'ui:zoomBrowserPage', zoomTo('in'))
expect(rendererSendMock).toHaveBeenNthCalledWith(2, 'ui:zoomBrowserPage', zoomTo('in'))
expect(rendererSendMock).toHaveBeenNthCalledWith(3, 'ui:zoomBrowserPage', zoomTo('out'))
expect(rendererSendMock).toHaveBeenNthCalledWith(4, 'ui:zoomBrowserPage', zoomTo('out'))
expect(rendererSendMock).toHaveBeenNthCalledWith(5, 'ui:zoomBrowserPage', zoomTo('reset'))
expect(rendererSendMock).toHaveBeenNthCalledWith(6, 'ui:zoomBrowserPage', zoomTo('in'))
})
it('forwards browser history shortcuts from focused guest pages', () => {
setupGuestShortcutForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer()
})
const backInput =
process.platform === 'darwin'
? { code: 'BracketLeft', key: '[', meta: true, control: false, alt: false }
: { code: 'ArrowLeft', key: 'ArrowLeft', meta: false, control: false, alt: true }
const forwardInput =
process.platform === 'darwin'
? { code: 'BracketRight', key: ']', meta: true, control: false, alt: false }
: { code: 'ArrowRight', key: 'ArrowRight', meta: false, control: false, alt: true }
const backPreventDefault = triggerBeforeInput(backInput)
const forwardPreventDefault = triggerBeforeInput(forwardInput)
expect(backPreventDefault).toHaveBeenCalledTimes(1)
expect(forwardPreventDefault).toHaveBeenCalledTimes(1)
expect(rendererSendMock).toHaveBeenNthCalledWith(
1,
'ui:browserHistoryNavigate',
historyTo('back')
)
expect(rendererSendMock).toHaveBeenNthCalledWith(
2,
'ui:browserHistoryNavigate',
historyTo('forward')
)
})
it('forwards browser Find with its registered page and workspace owner', () => {
setupGuestShortcutForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer(),
resolveWorkspaceId: () => 'workspace-1'
})
const preventDefault = triggerBeforeInput({ code: 'KeyF', key: 'f' })
expect(preventDefault).toHaveBeenCalledOnce()
expect(rendererSendMock).toHaveBeenCalledWith('ui:findInBrowserPage', {
browserPageId: browserTabId,
browserWorkspaceId: 'workspace-1'
})
})
it('forwards browser Find by page alone when no workspace owner is registered', () => {
setupGuestShortcutForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer()
})
const preventDefault = triggerBeforeInput({ code: 'KeyF', key: 'f' })
expect(preventDefault).toHaveBeenCalledOnce()
expect(rendererSendMock).toHaveBeenCalledWith('ui:findInBrowserPage', {
browserPageId: browserTabId,
browserWorkspaceId: undefined
})
})
it('forwards quick-command menu shortcuts from focused guest pages', () => {
setupGuestShortcutForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer(),
getKeybindings: () => ({
'tab.openQuickCommandsMenu': ['Mod+Shift+Q']
})
})
const isMac = process.platform === 'darwin'
const preventDefault = triggerBeforeInput({
code: 'KeyQ',
key: 'q',
meta: isMac,
control: !isMac,
shift: true
})
expect(preventDefault).toHaveBeenCalledTimes(1)
expect(rendererSendMock).toHaveBeenCalledWith('ui:toggleQuickCommandsMenu')
})
it('forwards workspace delete shortcuts from focused guest pages', () => {
setupGuestShortcutForwarding({
browserTabId,
guest: makeGuest(),
resolveRenderer: () => makeRenderer()
})
const isMac = process.platform === 'darwin'
const preventDefault = triggerBeforeInput({
code: 'Backspace',
key: 'Backspace',
meta: isMac,
control: !isMac,
shift: true
})
expect(preventDefault).toHaveBeenCalledOnce()
expect(rendererSendMock).toHaveBeenCalledWith('ui:deleteCurrentWorkspace')
})
// Why: Orca claims these on every repeat but acts once, so a held chord's repeats never reach the page.
it.each([
['workspace delete', { code: 'Backspace', key: 'Backspace' }],
@@ -1,4 +1,4 @@
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
import { mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
@@ -28,17 +28,6 @@ describe('browser host client identity', () => {
)
})
it('persists the identity where the next launch reads it', () => {
const directory = profileDirectory()
const identity = readOrCreateBrowserHostClientId(directory)
expect(JSON.parse(readFileSync(browserHostClientIdentityPath(directory), 'utf-8'))).toEqual({
version: 1,
browserHostClientId: identity
})
})
it.each([
['unparseable content', 'not json at all'],
['a future store version', JSON.stringify({ version: 2, browserHostClientId: 'kept' })],
@@ -1,47 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
nextBrowserHostReconnectDelay,
resolveBrowserHostReconnectDelay
} from './browser-host-lease-reconnect-delay'
describe('browser host lease reconnect delay', () => {
it('uses deterministic client-specific jitter within exponential and grace bounds', () => {
const delays = Array.from({ length: 8 }, (_, attempt) =>
nextBrowserHostReconnectDelay({
baseDelayMs: 100,
attempt,
remainingMs: 15_000,
browserHostClientId: 'host-a'
})
)
expect(delays).toEqual(
delays.map((_, attempt) =>
nextBrowserHostReconnectDelay({
baseDelayMs: 100,
attempt,
remainingMs: 15_000,
browserHostClientId: 'host-a'
})
)
)
expect(delays[0]).toBeGreaterThanOrEqual(50)
expect(delays[0]).toBeLessThanOrEqual(100)
expect(Math.max(...delays)).toBeLessThanOrEqual(2_000)
expect(
nextBrowserHostReconnectDelay({
baseDelayMs: 100,
attempt: 8,
remainingMs: 37,
browserHostClientId: 'host-b'
})
).toBe(37)
})
it('rejects invalid timer delays', () => {
expect(resolveBrowserHostReconnectDelay(undefined)).toBe(100)
expect(() => resolveBrowserHostReconnectDelay(0)).toThrow(
'Browser host reconnect delay is invalid'
)
})
})
@@ -1,91 +0,0 @@
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
BROWSER_IDENTITY_MODE_FILE,
BROWSER_IDENTITY_MODE_VERSION,
readBrowserIdentityModeRecord
} from './browser-identity-mode-record'
function makeUserData(): string {
return mkdtempSync(join(tmpdir(), 'orca-browser-identity-'))
}
function writeRecord(userDataPath: string, value: unknown): void {
writeFileSync(join(userDataPath, BROWSER_IDENTITY_MODE_FILE), JSON.stringify(value), 'utf8')
}
describe('readBrowserIdentityModeRecord', () => {
it('distinguishes missing data as implicit clean', () => {
expect(readBrowserIdentityModeRecord(makeUserData())).toEqual({
state: 'missing',
appliedMode: 'clean',
configuredMode: 'clean',
explicitSelection: false,
migrationNoticePending: false
})
})
it('returns a valid configured identity', () => {
const userDataPath = makeUserData()
writeRecord(userDataPath, {
version: BROWSER_IDENTITY_MODE_VERSION,
mode: 'native',
explicitSelection: true,
migrationNoticePending: true
})
expect(readBrowserIdentityModeRecord(userDataPath)).toEqual({
state: 'valid',
appliedMode: 'native',
configuredMode: 'native',
explicitSelection: true,
migrationNoticePending: true
})
})
it('falls back to clean without inventing a configured mode for corrupt data', () => {
const userDataPath = makeUserData()
writeFileSync(join(userDataPath, BROWSER_IDENTITY_MODE_FILE), '{not json', 'utf8')
expect(readBrowserIdentityModeRecord(userDataPath)).toEqual({
state: 'corrupt',
appliedMode: 'clean',
configuredMode: null,
explicitSelection: null,
migrationNoticePending: null
})
})
it('distinguishes a future record from corrupt data', () => {
const userDataPath = makeUserData()
writeRecord(userDataPath, {
version: BROWSER_IDENTITY_MODE_VERSION + 1,
mode: 'native',
explicitSelection: true,
migrationNoticePending: false
})
expect(readBrowserIdentityModeRecord(userDataPath)).toEqual({
state: 'future',
appliedMode: 'clean',
configuredMode: null,
explicitSelection: null,
migrationNoticePending: null
})
})
it('distinguishes an unreadable record from missing data', () => {
const userDataPath = makeUserData()
mkdirSync(join(userDataPath, BROWSER_IDENTITY_MODE_FILE))
expect(readBrowserIdentityModeRecord(userDataPath)).toEqual({
state: 'unreadable',
appliedMode: 'clean',
configuredMode: null,
explicitSelection: null,
migrationNoticePending: null
})
})
})
@@ -178,41 +178,6 @@ describe('production link bridge, store and mounted guest', () => {
expect(receipts.some((receipt) => receipt.path === '/removed-page')).toBe(false)
})
// These assertions capture unsafe baseline behavior pending claim handoff repair.
it.each(['remove-tab', 'profile-switch', 'delete-profile'])(
'records unsafe stale-source baseline through the real mount: %s',
async (transition) => {
rendererErrors.length = 0
await page.reload()
await ready()
const path = `/baseline-${transition}`
await open(false, path, true)
await page.evaluate(`window.linkProbe.mutate(${JSON.stringify(transition)})`)
await page.evaluate('window.probeApi.fixture.release()')
await page.evaluate('window.linkProbe.mountTabs()')
await expect
.poll(() => receipts.some((receipt) => receipt.path === path), { timeout: 10_000 })
.toBe(true)
const state = await page.evaluate<LinkSnapshot>('window.linkProbe.snapshot()')
const replacement = state.tabs.find((tab) => tab.id !== state.sourceWorkspaceId)
expect(replacement).toBeDefined()
const expectedProfile = transition === 'delete-profile' ? 'isolated' : 'other'
expect(receipts.filter((receipt) => receipt.path === path)).toEqual([
{ path, cookie: `synthetic_profile=${expectedProfile}` }
])
await expect
.poll(() => page.evaluate('window.probeApi.fixture.guests()'), { timeout: 10_000 })
.toEqual([
expect.objectContaining({
url: `${origin}${path}`,
isolated: expectedProfile === 'isolated',
other: expectedProfile === 'other'
})
])
},
25_000
)
it.todo(
'rejects removed source tabs, profile switches and deleted profiles without a tab, mount, default fallback or destination request'
)
@@ -97,28 +97,6 @@ describe('browserManager.setAnnotationViewportBridge', () => {
vi.restoreAllMocks()
})
it('installs the bridge in an isolated world', async () => {
const { guest } = makeGuest(4646)
registerPage('tab-annotations', guest)
const ok = await browserManager.setAnnotationViewportBridge(
'tab-annotations',
BRIDGE_OPTIONS,
resolveFromRegistry('tab-annotations')
)
expect(ok).toBe(true)
expect(guest.executeJavaScriptInIsolatedWorld).toHaveBeenCalledWith(
expect.any(Number),
[
expect.objectContaining({
code: expect.stringContaining('__orcaBrowserAnnotationViewportBridge')
})
],
false
)
})
// Navigation can replace the registered guest while its old WebContents remains alive.
it('injects into the guest the page has when a queued op finally runs, not the one it was asked with', async () => {
const { guest: firstGuest } = makeGuest(5101)
@@ -390,44 +390,6 @@ describe('browserManager', () => {
})
// Direct navigation permits setUserAgent before request dispatch.
it('still uses the WebContents UA write for navigations that are not redirects', () => {
const guest = {
id: 420,
isDestroyed: vi.fn(() => false),
isCurrentlyAudible: vi.fn(() => false),
send: vi.fn(),
getType: vi.fn(() => 'webview'),
setBackgroundThrottling: guestSetBackgroundThrottlingMock,
setWindowOpenHandler: guestSetWindowOpenHandlerMock,
on: guestOnMock,
off: guestOffMock,
openDevTools: guestOpenDevToolsMock,
getURL: vi.fn(() => 'https://example.com/'),
getUserAgent: vi.fn(() => guestBaseUserAgent),
setUserAgent: vi.fn(),
debugger: { isAttached: vi.fn(() => true), sendCommand: vi.fn(async () => undefined) },
session: { getUserAgent: vi.fn(() => guestBaseUserAgent) }
}
webContentsFromIdMock.mockReturnValue(guest)
browserManager.attachGuestPolicies(guest as never)
browserManager.registerGuest({
browserPageId: 'browser-direct-ua',
webContentsId: guest.id,
rendererWebContentsId
})
const didStartNavigation = guestOnMock.mock.calls.find(
([event]) => event === 'did-start-navigation'
)?.[1] as (event: unknown, url: string, isInPlace: boolean, isMainFrame: boolean) => void
didStartNavigation(null, 'https://accounts.google.com/v3/signin/identifier', false, true)
expect(guest.setUserAgent).toHaveBeenLastCalledWith(googleAuthUserAgent())
expect(guest.debugger.sendCommand).not.toHaveBeenCalledWith(
'Emulation.setUserAgentOverride',
expect.anything()
)
})
// Keep the process UA override until direct navigation can replace the stale Firefox base UA.
it('never clears the override onto a stale Firefox WebContents UA and heals it on the next navigation', async () => {
@@ -223,34 +223,6 @@ describe('client-hosted downloads', () => {
}
})
it('reports the whole lifecycle of a plain client-hosted download to its page renderer', async () => {
const { route, completed } = stubRoute()
registerBrowserClientDownloadRouter('env-a', { route: () => ({ kind: 'remote', route }) })
const item = createDownloadItem()
browserManager.handleGuestWillDownload({ guestWebContentsId: GUEST_WEB_CONTENTS_ID, item })
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:download-requested',
expect.objectContaining({ browserPageId: BROWSER_PAGE_ID, status: 'downloading' })
)
getDownloadItemEventHandler(item, 'once', 'done')?.({} as Electron.Event, 'completed')
await completed
expect(rendererSendMock).toHaveBeenCalledWith(
'browser:download-finished',
expect.objectContaining({
browserPageId: BROWSER_PAGE_ID,
status: 'completed',
remoteDestination: {
workspaceRelativePath: '.orca/browser-downloads/report.csv',
hostLabel: 'build-box'
}
})
)
})
it('cancels a client-hosted download that no composition owns', () => {
registerBrowserClientDownloadRouter('env-a', { route: () => ({ kind: 'unowned' }) })
const item = createDownloadItem()
@@ -72,14 +72,6 @@ describe('browser guest destruction ownership', () => {
expectRetainedCount(0)
})
it('leaves no dead-guest callbacks for the window-close unregisterAll path', () => {
for (let index = 0; index < 1000; index++) {
register(`window-${index}`).destroy()
}
manager.unregisterAll()
expectRetainedCount(0)
})
it('keeps explicit unregister before destruction idempotent', () => {
const guest = register('explicit')
manager.unregisterGuest('explicit')
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
import {
projectHostSetupProjectionFromRepos,
getProjectHostSetupsForProject,
isGitHubBackedRepo,
getProjectIdForProviderIdentity,
isProjectRemoteIdentityPending
} from './project-host-setup-projection'
@@ -449,31 +448,6 @@ describe('project host setup projection', () => {
).toHaveLength(3)
})
it('keeps same-named cross-host records separate when there is no shared repo identity', () => {
const projection = projectHostSetupProjectionFromRepos([
repo({
id: 'local-sample-app',
path: '/Users/alice/work/sample-app',
displayName: 'sample-app'
}),
repo({
id: 'ssh-sample-app',
path: '/srv/unrelated/sample-app',
displayName: 'sample-app',
connectionId: 'staging server'
}),
repo({
id: 'runtime-sample-app',
path: '/workspace/sample-app',
displayName: 'sample-app',
executionHostId: 'runtime:preview'
})
])
// Display names cannot prove identity; merge only with normalized remote identity or explicit user linking.
expect(projection.projects).toHaveLength(3)
})
it('does not collapse case-distinct remote paths for self-hosted git remotes', () => {
const projection = projectHostSetupProjectionFromRepos([
repo({
@@ -537,47 +511,6 @@ describe('project host setup projection', () => {
})
})
describe('isGitHubBackedRepo', () => {
it('is true when an explicit upstream owner/repo is present', () => {
const target = repo({
id: 'r',
path: '/r',
displayName: 'r',
upstream: { owner: 'stablyai', repo: 'orca' }
})
expect(isGitHubBackedRepo(target)).toBe(true)
})
it('is true when a GitHub-sourced avatar icon encodes the slug', () => {
const target = repo({
id: 'r',
path: '/r',
displayName: 'r',
repoIcon: {
type: 'image',
src: 'https://github.com/stablyai.png?size=64',
source: 'github',
label: 'stablyai/orca'
}
})
expect(isGitHubBackedRepo(target)).toBe(true)
})
it('is false for a non-GitHub icon and no upstream (GitLab/folder)', () => {
const target = repo({
id: 'r',
path: '/r',
displayName: 'r',
repoIcon: { type: 'lucide', name: 'gitlab' }
})
expect(isGitHubBackedRepo(target)).toBe(false)
})
it('is false for a plain local repo with no provider signal', () => {
expect(isGitHubBackedRepo(repo({ id: 'r', path: '/r', displayName: 'r' }))).toBe(false)
})
})
describe('getProjectIdForProviderIdentity', () => {
it('uses the same normalized identity key as project projection', () => {
expect(
@@ -1,20 +1,10 @@
import { expect, it } from 'vitest'
import { DEFAULT_TERMINAL_THEME_DARK } from './terminal-theme-selection'
import { TERMINAL_THEME_CATALOG } from './terminal-themes'
import {
ORCA_DEFAULT_COLOR_QUERY_REPLY_COLORS,
normalizeColorQueryReplyColors,
resolvePtyOwnerColorQueryColors
} from './pty-owner-color-query-colors'
it("falls back to the colours of Orca's default dark terminal theme", () => {
const theme = TERMINAL_THEME_CATALOG[DEFAULT_TERMINAL_THEME_DARK]
expect(ORCA_DEFAULT_COLOR_QUERY_REPLY_COLORS).toEqual({
foreground: theme?.foreground,
background: theme?.background
})
})
it('prefers host colours, then spawn colours, then the default, skipping unusable pairs', () => {
const host = { foreground: '#000000', background: '#111111' }
const spawn = { foreground: '#222222', background: '#333333' }
@@ -37,16 +37,6 @@ describe('PTY owner colour replies for the terminal life', () => {
expect(visible()).toBe('\x1b]11;#123456\x07\x1b]111\x07')
})
it('answers with the viewer colours the host holds now, not the colours sent at spawn', () => {
// The host's viewer may have changed theme since this pane was created.
const spawnedLight = { foreground: '#2e3434', background: '#ffffff' }
const { ingress, writes } = createHarness({ colors: spawnedLight, deadlineMs: 5_000 })
ingress.accept(QUERY)
expect(writes).toEqual([HOST_BACKGROUND_REPLY])
})
it('answers a query torn right after its ESC once the Kitty window has closed', () => {
const { ingress, writes, visible } = createHarness({
colors: HOST,
@@ -46,14 +46,6 @@ describe('live query replies', () => {
expect(needsCookedEchoSafeQueryReply(mode2031SequenceFor('light'))).toBe(true)
})
it('writes an echo-risk reply in the calling turn', () => {
const { ingress, writes } = harness()
expect(ingress.answerLiveQueryReply(OSC_COLOR_REPLY)).toBe(true)
// No timer advance: the whole point is that there is nothing to wait for.
expect(writes).toEqual([OSC_COLOR_REPLY])
ingress.drainAndClose()
})
it('answers repeated identical replies without collapsing them', () => {
const { ingress, writes } = harness()
expect(ingress.answerLiveQueryReply(COLOR_SCHEME_REPLY)).toBe(true)
-38
View File
@@ -79,16 +79,6 @@ describe('PtyStartupIngress', () => {
}
})
it('suppresses the first echo immediately and keeps a later exact collision', () => {
const { ingress, emissions } = createHarness({ projection: true })
ingress.accept('\x1b]10;?\x07')
const projected = ']10;rgb:2e2e/3434/3434\\'
ingress.accept(projected)
ingress.accept(projected)
ingress.drainAndClose()
expect(visible(emissions)).toBe(projected)
})
it('matches each echo across every split without skipping an earlier FIFO candidate', () => {
const foregroundEcho = ']10;rgb:2e2e/3434/3434\\'
const backgroundEcho = ']11;rgb:ffff/ffff/ffff\\'
@@ -751,32 +741,4 @@ describe('PtyStartupIngress', () => {
expect(ingress.acceptedRawSequence).toBe(closedAt)
expect(visible(emissions)).toBe(']10;rgb:2e2e/')
})
it('suppresses the readline echo, which no reading of the ECHO bit could predict', () => {
const { ingress, writes, emissions } = createHarness()
ingress.accept('\x1b]10;?\x07')
expect(writes).toEqual([FOREGROUND_REPLY])
// Live bash PTYs echo via readline even with raw mode/ECHO-clear; kernel ECHO cannot predict software echoes.
ingress.accept(POSIX_COOKED_ECHOES[1]?.(FOREGROUND_REPLY) ?? '')
ingress.drainAndClose()
expect(visible(emissions)).toBe('')
})
it('suppresses the kernel caret echo', () => {
const { ingress, writes, emissions } = createHarness()
ingress.accept('\x1b]10;?\x07')
expect(writes).toEqual([FOREGROUND_REPLY])
// Always arm the default ECHOCTL projection; no probe verdict can safely retire it.
ingress.accept(POSIX_COOKED_ECHOES[0]?.(FOREGROUND_REPLY) ?? '')
ingress.drainAndClose()
expect(visible(emissions)).toBe('')
})
it('writes the reply in the accepting turn, with no probe and no wait', () => {
const { ingress, writes } = createHarness()
ingress.accept('\u001b]10;?\u0007')
// No deferral: only output projections remove echo, while delayed writes reorder replies (#15559).
expect(writes).toEqual(['\u001b]10;rgb:2e2e/3434/3434\u001b\\'])
ingress.drainAndClose()
})
})
@@ -40,26 +40,6 @@ describe('replyEchoProjections on a POSIX pty', () => {
expect(locateEcho(replyEchoProjections(reply, 'posix-pty'), reply).kind).toBe('complete')
})
it('caret-encodes every control, not just ESC', () => {
const [kernel] = replyEchoProjections(OSC11_BEL, 'posix-pty')
expect(kernel?.needle).toBe('^[]11;rgb:2e2e/3434/3434^G')
expect(kernel?.needle).not.toContain('\x07')
})
// ECHOCTL leaves TAB/LF/CR literal and maps DEL→^? (vis(3)); pin future grammar behavior even though current replies lack them.
it.each([
{ name: 'TAB stays literal', input: '\t', encoded: '\t' },
{ name: 'LF stays literal', input: '\n', encoded: '\n' },
{ name: 'CR stays literal', input: '\r', encoded: '\r' },
{ name: 'NUL carets to ^@', input: '\x00', encoded: '^@' },
{ name: 'BEL carets to ^G', input: '\x07', encoded: '^G' },
{ name: 'ESC carets to ^[', input: '\x1b', encoded: '^[' },
{ name: 'DEL carets to ^?', input: '\x7f', encoded: '^?' }
])('$name under ECHOCTL', ({ input, encoded }) => {
const [kernel] = replyEchoProjections(`\x1b[?9${input}n`, 'posix-pty')
expect(kernel?.needle).toBe(`^[[?9${encoded}n`)
})
// Guard the private-DSR grammar locally: widening a caller must not arm DA1’s shared-prefix BEL needle.
it.each(['\x1b[?1;2c', '\x1b[?0u', '\x1b[?2026;2$y', '\x1b[?12;5R'])(
'projects no readline needle for %j, which is not a private DSR',
@@ -82,13 +62,6 @@ describe('replyEchoProjections on a POSIX pty', () => {
}
)
it('projects readline for a private DSR, which carries no OSC introducer', () => {
const needles = replyEchoProjections(DSR_997, 'posix-pty').map(
(projection) => projection.needle
)
expect(needles).toContain('\x07997;1n')
})
// Never hold ESC-led partials: a bare ESC could belong to a torn query and would escape unanswered when the hold expires.
it('holds a partial only for needles that do not start with ESC', () => {
for (const { reply } of LIVE_ECHOES) {
@@ -22,11 +22,6 @@ describe('resolvePublishedPaneAgentIdentity', () => {
})
})
it('keeps the launch record when an unambiguous title names a different agent', () => {
// Unlike agreeing/absent title cases, this detects the title-first mutant: explicit Claude branding cannot override a Codex launch.
expect(resolve({ launchAgent: 'codex', title: '✳ Claude Code' })).toBe('codex')
})
it('prefers the live foreground process to the launch record', () => {
expect(resolve({ launchAgent: 'claude', foregroundAgent: 'codex' })).toBe('codex')
})
@@ -33,14 +33,6 @@ describe('buildPullRequestFieldsPrompt', () => {
expect(prompt).toContain('Use conventional PR titles.')
})
it('requires ELI5 problem and solution sections before implementation details', () => {
const prompt = buildPullRequestFieldsPrompt(context, '')
expect(prompt).toContain('start with `## Problem`, then `## Solution`')
expect(prompt).toContain('simple ELI5 language before details')
expect(prompt).toContain('Reuse equivalent existing sections instead of duplicating them')
})
it('includes GitHub issue details and complete or partial reference guidance', () => {
const prompt = buildPullRequestFieldsPrompt(
{
@@ -110,19 +102,6 @@ describe('buildPullRequestFieldsPrompt', () => {
expect(prompt).toContain('`Fixes AB#99` only for a complete fix')
expect(prompt).toContain('use `AB#99`')
})
it('tells the agent to preserve existing review templates', () => {
const prompt = buildPullRequestFieldsPrompt(
{
...context,
currentBody: '## Summary\n\n## Testing\n\n- [ ] Required checks'
},
''
)
expect(prompt).toContain('Retain every heading, required section, and checklist')
expect(prompt).toContain('Leave genuinely unknown template items as TODO or unchecked')
})
})
describe('parseGeneratedPullRequestFields', () => {
@@ -140,28 +119,6 @@ describe('parseGeneratedPullRequestFields', () => {
})
})
it('parses CRLF fenced JSON output without full-string fence matching', () => {
const matchSpy = vi.spyOn(String.prototype, 'match')
const replaceSpy = vi.spyOn(String.prototype, 'replace')
const fields = parseGeneratedPullRequestFields(
'```JSON\r\n{"base":"main","title":"fix: add details.","body":"Summary","draft":true}\r\n```',
context
)
expect(fields.title).toBe('fix: add details')
const usedFenceMatch = matchSpy.mock.calls.some(
([pattern]) =>
pattern instanceof RegExp &&
pattern.source.startsWith('^```') &&
pattern.source.includes('[\\s\\S]')
)
const usedCrlfReplace = replaceSpy.mock.calls.some(
([pattern]) => pattern instanceof RegExp && pattern.source === '\\r\\n' && pattern.global
)
expect(usedFenceMatch).toBe(false)
expect(usedCrlfReplace).toBe(false)
})
it('falls back for missing optional values', () => {
const fields = parseGeneratedPullRequestFields('{"title":""}', context)
-48
View File
@@ -4,7 +4,6 @@ import {
buildGitLsFilesArgsForQuickOpen,
buildHiddenDirExcludeGlobs,
buildRgArgsForQuickOpen,
HIDDEN_DIR_BLOCKLIST,
normalizeQuickOpenRgLine,
shouldExcludeQuickOpenRelPath,
shouldIncludeQuickOpenPath
@@ -38,14 +37,6 @@ describe('shouldIncludeQuickOpenPath', () => {
expect(shouldIncludeQuickOpenPath('nested/.local/share/app/state.db')).toBe(false)
expect(shouldIncludeQuickOpenPath('.local/bin/tool')).toBe(true)
})
it('does NOT blocklist user-authored dirs like .config, .ssh, .github', () => {
expect(HIDDEN_DIR_BLOCKLIST.has('.config')).toBe(false)
expect(HIDDEN_DIR_BLOCKLIST.has('.ssh')).toBe(false)
expect(HIDDEN_DIR_BLOCKLIST.has('.github')).toBe(false)
expect(HIDDEN_DIR_BLOCKLIST.has('.devcontainer')).toBe(false)
expect(HIDDEN_DIR_BLOCKLIST.has('.local')).toBe(false)
})
})
describe('buildExcludePathPrefixes', () => {
@@ -91,40 +82,9 @@ describe('buildExcludePathPrefixes', () => {
it('strips trailing slashes', () => {
expect(buildExcludePathPrefixes('/r', ['/r/a/', '/r/b///'])).toEqual(['a', 'b'])
})
it('keeps valid child prefixes whose segment starts with dotdot characters', () => {
expect(buildExcludePathPrefixes('/home/u/repo', ['/home/u/repo/..fixtures'])).toEqual([
'..fixtures'
])
})
})
describe('shouldExcludeQuickOpenRelPath', () => {
it('matches the original filter across boundary and Unicode path combinations', () => {
const paths = [
'',
'/',
'a',
'a/',
'a//',
'ab',
'a/b',
'a\\b',
'A/b',
'界/😀',
'界/😀x',
'a[1]/x',
'a./x'
]
for (const prefix of paths) {
for (const relPath of paths) {
const expected =
relPath === prefix || (relPath.length > prefix.length && relPath.startsWith(`${prefix}/`))
expect(shouldExcludeQuickOpenRelPath(relPath, [prefix])).toBe(expected)
}
}
})
it('preserves normalized Windows and UNC exclusion boundaries', () => {
for (const [root, excluded] of [
['C:\\Repo', 'C:\\Repo\\trees\\one'],
@@ -327,14 +287,6 @@ describe('buildGitLsFilesArgsForQuickOpen', () => {
expect(ignoredPass).not.toContain(':(glob)**/.env*')
})
it('collapses untracked directories in both passes without generated pathspec churn', () => {
const { primary, ignoredPass } = buildGitLsFilesArgsForQuickOpen()
expect(primary).toContain('--directory')
expect(ignoredPass).toContain('--directory')
expect(ignoredPass).toContain('--no-empty-directory')
expect([...primary, ...ignoredPass]).not.toContain(':(exclude,glob)**/node_modules/**')
})
it('exclude prefixes prepend positive "." pathspec', () => {
const { primary, ignoredPass } = buildGitLsFilesArgsForQuickOpen(['packages/app'])
const dashDashIdx = primary.indexOf('--')
@@ -1,16 +1,10 @@
import { describe, expect, it } from 'vitest'
import {
limitQuickOpenFilesBySerializedBytes,
limitQuickOpenSearchReplyBySerializedBytes,
serializedQuickOpenPathBytes
limitQuickOpenSearchReplyBySerializedBytes
} from './quick-open-transport-budget'
describe('Quick Open transport byte budget', () => {
it('matches JSON.stringify byte accounting, including escaped paths', () => {
const path = 'src/line\nquote".ts'
expect(serializedQuickOpenPathBytes(path)).toBe(Buffer.byteLength(JSON.stringify(path), 'utf8'))
})
it('returns the largest prefix that fits as a JSON array', () => {
const files = ['src/a.ts', 'data/ignored\nfile.bin', 'src/b.ts']
const budget = Buffer.byteLength(JSON.stringify(files.slice(0, 2)), 'utf8')
+1 -244
View File
@@ -1,25 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { decodeBase64Prefix, exceedsRasterImagePreviewLimits } from './raster-image-base64-preview'
import type * as RasterImageDimensionsModule from './raster-image-dimensions'
import { readRasterImageDimensions } from './raster-image-dimensions'
import {
isKnownRasterImageMimeType,
isRasterImagePreviewDimensions,
RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES
} from './raster-image-preview-limits'
/** Frozen full-payload decode/dimension baseline for comparison with the prefix probe. */
function unprobedExceeds(content: string, mimeType: string | undefined): boolean {
if (!isKnownRasterImageMimeType(mimeType)) {
return false
}
const prefix = referenceDecodeBase64Prefix(content, RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES)
if (!prefix) {
return false
}
const dimensions = readRasterImageDimensions(prefix)
return dimensions !== null && !isRasterImagePreviewDimensions(dimensions)
}
import { RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES } from './raster-image-preview-limits'
// Observe decoded bytes handed to dimension reading; output equivalence alone cannot detect wasted full-payload decoding.
const { dimensionReadLengths } = vi.hoisted(() => ({ dimensionReadLengths: [] as number[] }))
@@ -35,123 +17,6 @@ vi.mock('./raster-image-dimensions', async (importOriginal) => {
}
})
// ── Reference decoder: the pre-change implementation, verbatim ──────────────────────────────────
const BASE64_PADDING = -2
const INVALID_BASE64 = -1
function base64Value(code: number): number {
if (code >= 65 && code <= 90) {
return code - 65
}
if (code >= 97 && code <= 122) {
return code - 71
}
if (code >= 48 && code <= 57) {
return code + 4
}
if (code === 43) {
return 62
}
if (code === 47) {
return 63
}
if (code === 61) {
return BASE64_PADDING
}
return INVALID_BASE64
}
function isWhitespace(code: number): boolean {
return code === 9 || code === 10 || code === 12 || code === 13 || code === 32
}
function referenceWriteQuartet(
output: Uint8Array,
offset: number,
quartet: readonly number[]
): { bytesWritten: number; padded: boolean } | null {
const [a, b, c, d] = quartet
if (a === undefined || b === undefined || a < 0 || b < 0) {
return null
}
if (c === BASE64_PADDING) {
if (d !== BASE64_PADDING) {
return null
}
if (offset < output.length) {
output[offset] = (a << 2) | (b >> 4)
}
return { bytesWritten: Math.min(1, output.length - offset), padded: true }
}
if (c === undefined || c < 0) {
return null
}
if (offset < output.length) {
output[offset] = (a << 2) | (b >> 4)
}
if (offset + 1 < output.length) {
output[offset + 1] = ((b & 15) << 4) | (c >> 2)
}
if (d === BASE64_PADDING) {
return { bytesWritten: Math.min(2, output.length - offset), padded: true }
}
if (d === undefined || d < 0) {
return null
}
if (offset + 2 < output.length) {
output[offset + 2] = ((c & 3) << 6) | d
}
return { bytesWritten: Math.min(3, output.length - offset), padded: false }
}
function referenceDecodeBase64Prefix(content: string, maxBytes: number): Uint8Array | null {
const capacity = Math.min(maxBytes, Math.ceil(content.length / 4) * 3)
const output = new Uint8Array(capacity)
const quartet: number[] = []
let outputLength = 0
let padded = false
for (let index = 0; index < content.length && outputLength < capacity; index += 1) {
const code = content.charCodeAt(index)
if (isWhitespace(code)) {
continue
}
if (padded) {
return null
}
const value = base64Value(code)
if (value === INVALID_BASE64) {
return null
}
quartet.push(value)
if (quartet.length !== 4) {
continue
}
const decoded = referenceWriteQuartet(output, outputLength, quartet)
if (!decoded) {
return null
}
outputLength += decoded.bytesWritten
padded = decoded.padded
quartet.length = 0
}
if (!padded && outputLength < capacity && quartet.length > 0) {
if (quartet.length === 1 || quartet.includes(BASE64_PADDING)) {
return null
}
while (quartet.length < 4) {
quartet.push(BASE64_PADDING)
}
const decoded = referenceWriteQuartet(output, outputLength, quartet)
if (!decoded) {
return null
}
outputLength += decoded.bytesWritten
}
return output.subarray(0, outputLength)
}
function pngBytes(totalBytes: number, width: number, height: number): Buffer {
const bytes = Buffer.alloc(Math.max(totalBytes, 24))
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]).copy(bytes)
@@ -191,89 +56,7 @@ function jpegBytes(
return Buffer.concat(parts)
}
function gifBytes(width: number, height: number): Buffer {
const gif = Buffer.alloc(64)
gif.write('GIF89a', 0, 'ascii')
gif.writeUInt16LE(width, 6)
gif.writeUInt16LE(height, 8)
return gif
}
function webpBytes(width: number, height: number): Buffer {
const webp = Buffer.alloc(64)
webp.write('RIFF', 0, 'ascii')
webp.writeUInt32LE(50, 4)
webp.write('WEBP', 8, 'ascii')
webp.write('VP8X', 12, 'ascii')
webp.writeUInt32LE(10, 16)
webp.writeUIntLE(width - 1, 24, 3)
webp.writeUIntLE(height - 1, 27, 3)
return webp
}
const DECODE_FIXTURES: { label: string; content: string }[] = [
{ label: 'png', content: pngBytes(24, 512, 512).toString('base64') },
{ label: 'png padded once', content: pngBytes(26, 512, 512).toString('base64') },
{ label: 'png padded twice', content: pngBytes(25, 512, 512).toString('base64') },
{ label: 'png 70 KiB', content: pngBytes(70_000, 512, 512).toString('base64') },
{ label: 'jpeg', content: jpegBytes(0, 640, 480).toString('base64') },
{ label: 'jpeg 70 KiB exif', content: jpegBytes(70_000, 4000, 3000).toString('base64') },
{ label: 'gif', content: gifBytes(320, 240).toString('base64') },
{ label: 'webp', content: webpBytes(800, 600).toString('base64') },
{ label: 'empty', content: '' },
{ label: 'one character', content: 'A' },
{ label: 'two characters', content: 'AB' },
{ label: 'three characters', content: 'ABC' },
{ label: 'invalid character', content: 'AB*D' },
{ label: 'invalid tail', content: `${pngBytes(24, 4, 4).toString('base64')}!!!` },
{ label: 'padding mid-payload', content: 'AAAA=AAA' },
{ label: 'lone padding in tail', content: 'AAAAAB=' },
{ label: 'single padding', content: 'AAAAAA==' },
{ label: 'double padding', content: 'AAAAAAA=' },
{ label: 'stray padding after padding', content: 'AAAA====' },
{
label: 'line-wrapped png',
content: pngBytes(70_000, 512, 512)
.toString('base64')
.replace(/(.{76})/g, '$1\r\n')
},
{ label: 'leading and trailing whitespace', content: `\n\t ${'AAAA'} \r\n` },
{ label: 'truncated png header', content: pngBytes(24, 512, 512).toString('base64').slice(0, 18) }
]
const DECODE_CAPS = [
0,
1,
2,
3,
4,
23,
24,
25,
63,
64,
65,
1024,
RASTER_IMAGE_PREVIEW_HEADER_MAX_BYTES
]
describe('decodeBase64Prefix', () => {
it('decodes byte-for-byte identically to the reference implementation', () => {
for (const { label, content } of DECODE_FIXTURES) {
for (const maxBytes of DECODE_CAPS) {
const expected = referenceDecodeBase64Prefix(content, maxBytes)
const actual = decodeBase64Prefix(content, maxBytes)
const detail = `${label} @ maxBytes=${maxBytes}`
if (expected === null) {
expect(actual, detail).toBeNull()
continue
}
expect(actual, detail).not.toBeNull()
expect(Array.from(actual!), detail).toEqual(Array.from(expected))
}
}
})
it('stops at the byte cap instead of decoding the whole payload', () => {
const content = pngBytes(70_000, 512, 512).toString('base64')
expect(decodeBase64Prefix(content, 32)?.byteLength).toBe(32)
@@ -311,30 +94,4 @@ describe('exceedsRasterImagePreviewLimits', () => {
const content = `${pngBytes(70_000, 32_769, 1).toString('base64')}!!!`
expect(exceedsRasterImagePreviewLimits(content, 'image/png')).toBe(false)
})
it('agrees with the unprobed implementation on every fixture and mime type', () => {
const mimeTypes = [
'image/png',
'image/jpeg',
'image/gif',
'image/webp',
'image/svg+xml',
undefined
]
const fixtures = [
...DECODE_FIXTURES,
{ label: 'over-limit png', content: pngBytes(24, 32_769, 1).toString('base64') },
{ label: 'over-limit pixels png', content: pngBytes(24, 8192, 8192).toString('base64') },
{ label: 'over-limit gif', content: gifBytes(65_535, 65_535).toString('base64') },
{ label: 'over-limit jpeg', content: jpegBytes(70_000, 40_000, 40_000).toString('base64') },
{ label: 'over-limit webp', content: webpBytes(40_000, 40_000).toString('base64') }
]
for (const { label, content } of fixtures) {
for (const mimeType of mimeTypes) {
expect(exceedsRasterImagePreviewLimits(content, mimeType), `${label} / ${mimeType}`).toBe(
unprobedExceeds(content, mimeType)
)
}
}
})
})
@@ -15,16 +15,6 @@ describe('react update depth attribution', () => {
).toBe(UNRELIABLE_BOUNDARY_ATTRIBUTION)
})
it('matches the legacy error-decoder invariant form', () => {
expect(
getReactErrorBoundaryAttribution(
new Error(
'Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message.'
)
)
).toBe(UNRELIABLE_BOUNDARY_ATTRIBUTION)
})
it('matches the development message', () => {
expect(
getReactErrorBoundaryAttribution(new Error('Maximum update depth exceeded. This can happen…'))
-31
View File
@@ -2,15 +2,12 @@ import { describe, expect, it } from 'vitest'
import {
findInstallerAssetName,
getReleaseNotesUrlForVersion,
getReleaseRepoForChannel,
getVersionChannel,
hasDedicatedReleaseRepo,
hasInstallableArtifactForPlatform,
isAdhocVersion,
isChannelSupportedOnPlatform,
isDailyVersion,
isHourlyVersion,
isReleaseChannel,
parseAdhocVersionStamp,
parseDailyVersionStamp,
parseDevBuildStamp,
@@ -33,24 +30,6 @@ describe('release channel', () => {
expect(getVersionChannel('not-a-version')).toBeNull()
})
// The main release Atom feed holds only 10 entries; daily/hourly tags would evict stable/RC updates.
it('keeps dev builds out of the main release repo, and apart from each other', () => {
expect(getReleaseRepoForChannel('hourly')).toBe('stablyai/orca-hourly')
expect(getReleaseRepoForChannel('daily')).toBe('stablyai/orca-daily')
// Keep unlanded adhoc branch builds away from users following main’s hourly channel.
expect(getReleaseRepoForChannel('adhoc')).toBe('stablyai/orca-adhoc')
expect(getReleaseRepoForChannel('stable')).toBe('stablyai/orca')
expect(getReleaseRepoForChannel('rc')).toBe('stablyai/orca')
})
it('marks exactly the dev channels as having their own repo', () => {
expect(hasDedicatedReleaseRepo('hourly')).toBe(true)
expect(hasDedicatedReleaseRepo('daily')).toBe(true)
expect(hasDedicatedReleaseRepo('adhoc')).toBe(true)
expect(hasDedicatedReleaseRepo('stable')).toBe(false)
expect(hasDedicatedReleaseRepo('rc')).toBe(false)
})
// Dev tags exist only in their dedicated repos; main-repo links 404.
it('builds release-notes links against the repo that published the version', () => {
expect(getReleaseNotesUrlForVersion('1.4.160-hourly.202607281400')).toBe(
@@ -221,16 +200,6 @@ describe('release channel', () => {
}
})
it('accepts only known channels', () => {
expect(isReleaseChannel('hourly')).toBe(true)
expect(isReleaseChannel('daily')).toBe(true)
expect(isReleaseChannel('adhoc')).toBe(true)
expect(isReleaseChannel('stable')).toBe(true)
expect(isReleaseChannel('nightly')).toBe(false)
expect(isReleaseChannel(null)).toBe(false)
expect(isReleaseChannel(undefined)).toBe(false)
})
// Timestamp tails distinguish consecutive hourlies; keep picker ordering chronological.
it('sorts consecutive hourly builds newest first', () => {
const build = (version: string): ReleaseBuild => ({
+1 -104
View File
@@ -18,12 +18,7 @@ import {
import { sendRemoteRuntimeRequest, subscribeRemoteRuntimeRequest } from './remote-runtime-client'
import { remoteRuntimeClientCapabilities } from './remote-runtime-client-capabilities'
import { MAX_TIMER_DELAY_MS } from './timer-delay'
import {
BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY,
BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY,
BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY
} from './protocol-version'
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from './electron-remote-runtime-client-capabilities'
import { BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY } from './protocol-version'
const servers: WebSocketServer[] = []
@@ -189,39 +184,6 @@ describe('subscribeRemoteRuntimeRequest', () => {
await vi.waitFor(() => expect(releaseSocket).toHaveBeenCalledOnce())
})
it('detaches subscription socket listeners after close', async () => {
const offSpy = vi.spyOn(WebSocketClient.prototype, 'off')
try {
const server = await createSubscriptionServer()
const onResponse = vi.fn()
const onError = vi.fn()
const onClose = vi.fn()
const subscription = await subscribeRemoteRuntimeRequest(
server.pairing,
'terminal.subscribe',
{ terminal: 't1' },
1000,
{
onResponse,
onError,
onClose
}
)
await vi.waitFor(() => expect(onResponse).toHaveBeenCalled())
subscription.close()
await vi.waitFor(() => expect(onClose).toHaveBeenCalledOnce())
const removedEvents = offSpy.mock.calls.map(([event]) => event)
expect(removedEvents).toEqual(expect.arrayContaining(['open', 'error', 'close', 'message']))
expect(subscription.sendBinary(new Uint8Array([9]))).toBe(false)
expect(onError).not.toHaveBeenCalled()
} finally {
offSpy.mockRestore()
}
})
it('closes a half-open subscription socket via client liveness so callers can resubscribe', async () => {
// Half-open dedicated streams get no close/pongs/data; client liveness must trigger onError/onClose so renderer resubscription runs (#7718).
const server = await createSubscriptionServer({ disableAutoPong: true })
@@ -288,52 +250,6 @@ describe('subscribeRemoteRuntimeRequest', () => {
})
describe('sendRemoteRuntimeRequest', () => {
it('keeps generic native authentication free of Electron placement support', async () => {
let receivedAuth: Record<string, unknown> | null = null
const server = await createOneShotServer({
onAuth: (auth) => {
receivedAuth = auth
}
})
await sendRemoteRuntimeRequest(
server.pairing,
'session.tabs.list',
{ worktree: 'id:worktree-a' },
1000
)
expect(receivedAuth).toMatchObject({
clientCapabilities: remoteRuntimeClientCapabilities()
})
})
it('advertises browser placement support only when the Electron caller opts in', async () => {
let receivedAuth: Record<string, unknown> | null = null
const server = await createOneShotServer({
onAuth: (auth) => {
receivedAuth = auth
}
})
await sendRemoteRuntimeRequest(
server.pairing,
'session.tabs.list',
{ worktree: 'id:worktree-a' },
1000,
undefined,
undefined,
ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES
)
expect(receivedAuth).toMatchObject({
clientCapabilities: expect.arrayContaining([
BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY,
BROWSER_CLIENT_PAGE_METADATA_RUNTIME_CAPABILITY
])
})
})
it.each([-1, 1.5, MAX_TIMER_DELAY_MS + 1, Number.MAX_SAFE_INTEGER + 1])(
'rejects invalid timer delay %s before reading pairing data',
async (timeoutMs) => {
@@ -565,25 +481,6 @@ describe('sendRemoteRuntimeRequest', () => {
expect(receivedRequest).not.toHaveProperty('orchestrationContractVersion')
expect(receivedRequest).not.toHaveProperty('orchestrationRequestId')
})
it('detaches one-shot socket listeners after a successful response', async () => {
const offSpy = vi.spyOn(WebSocketClient.prototype, 'off')
try {
const server = await createOneShotServer()
await sendRemoteRuntimeRequest<{ satisfied: boolean }>(
server.pairing,
'terminal.wait',
{ terminal: 't1', for: 'tui-idle', timeoutMs: 550 },
300
)
const removedEvents = offSpy.mock.calls.map(([event]) => event)
expect(removedEvents).toEqual(expect.arrayContaining(['open', 'error', 'close', 'message']))
} finally {
offSpy.mockRestore()
}
})
})
async function createSubscriptionServer(
@@ -3,7 +3,6 @@ import {
probeAllRemoteRuntimeSocketsNow,
startRemoteRuntimeSocketLiveness
} from './remote-runtime-socket-liveness'
import { SharedControlReconnectScheduler } from './remote-runtime/control/remote-runtime-shared-control-reconnect'
// After sleep, probe every socket immediately; 8s misses only re-ping and the normal 25s keepalive decides death so relayed paths have time to recover (#9092).
describe('remote runtime resume probe', () => {
@@ -154,45 +153,3 @@ describe('remote runtime resume probe', () => {
expect(onDead).toHaveBeenCalledTimes(1)
})
})
describe('SharedControlReconnectScheduler.retryNow after an outage', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it('restarts the backoff ladder so a failure right after wake retries quickly', () => {
const scheduler = new SharedControlReconnectScheduler()
const open = vi.fn()
for (let attempt = 0; attempt < 6; attempt += 1) {
scheduler.scheduleWithIdleBackoff(false, open)
vi.runOnlyPendingTimers()
}
expect(scheduler.attemptCount).toBeGreaterThanOrEqual(6)
scheduler.scheduleWithIdleBackoff(false, open)
expect(scheduler.retryNow()).toBe(true)
expect(scheduler.attemptCount).toBe(0)
})
it('restarts the ladder even when the wake lands while a reconnect is already opening', () => {
const scheduler = new SharedControlReconnectScheduler()
const open = vi.fn()
for (let attempt = 0; attempt < 6; attempt += 1) {
scheduler.scheduleWithIdleBackoff(false, open)
vi.runOnlyPendingTimers()
}
// The last open() is in flight, so no timer is pending for retryNow to advance.
expect(scheduler.isScheduled).toBe(false)
expect(scheduler.retryNow()).toBe(false)
expect(scheduler.attemptCount).toBe(0)
})
it('does nothing when no reconnect is pending', () => {
const scheduler = new SharedControlReconnectScheduler()
expect(scheduler.retryNow()).toBe(false)
expect(scheduler.attemptCount).toBe(0)
})
})
@@ -5,7 +5,6 @@ import { Buffer } from 'node:buffer'
import { describe, expect, it } from 'vitest'
import { RemoteRuntimeClientError } from './remote-runtime-client-error'
import {
RECOVERABLE_CODES,
RECOVERABLE_MESSAGE_FRAGMENTS,
isRecoverableRemoteRuntimeConnectionError,
isRuntimeRpcQueueOverloadError,
@@ -411,12 +410,6 @@ function classifyByMessageOnly(pair: TransportErrorPair): boolean {
}
describe('transport error code/message classification agreement', () => {
it('enumerates every reachable coded producer', () => {
// Use a floor so corpus additions pass; lower deliberately only for genuinely removed producers (#12667 originally enumerated 34).
expect(CODED_TRANSPORT_ERRORS.length).toBeGreaterThanOrEqual(59)
expect(CODED_TRANSPORT_ERRORS.every((pair) => typeof pair.code === 'string')).toBe(true)
})
it('never classifies a coded error as fatal while its own message reads as recoverable', () => {
const violations = CODED_TRANSPORT_ERRORS.filter(
(pair) => !isRecoverableRemoteRuntimeConnectionError(pair) && classifyByMessageOnly(pair)
@@ -450,33 +443,6 @@ describe('transport error code/message classification agreement', () => {
expect(misclassified).toEqual([])
})
it('backs every recoverable message fragment with a producer in the corpus', () => {
const allMessages = [...CODED_TRANSPORT_ERRORS, ...CODELESS_TRANSPORT_ERRORS].map((pair) =>
pair.message.toLowerCase()
)
const unbacked = RECOVERABLE_MESSAGE_FRAGMENTS.filter(
(fragment) => !allMessages.some((message) => message.includes(fragment))
).map(
(fragment) =>
`recoverable fragment "${fragment}" matches no producer in this corpus — add the producer that emits it, or drop the fragment.`
)
expect(unbacked).toEqual([])
})
it('backs every recoverable code with a producer in the corpus', () => {
// 'reconnecting' is vocabulary borrowed from the SSH/runtime status states; no error
// producer raises it. Kept recoverable defensively.
const codesWithoutProducer = new Set(['reconnecting'])
const corpusCodes = new Set(CODED_TRANSPORT_ERRORS.map((pair) => pair.code))
const unbacked = [...RECOVERABLE_CODES]
.filter((code) => !corpusCodes.has(code) && !codesWithoutProducer.has(code))
.map(
(code) =>
`recoverable code "${code}" matches no producer in this corpus — add the producer that raises it so its message is checked, or declare it producer-less here.`
)
expect(unbacked).toEqual([])
})
it('classifies remote_runtime_busy recoverable with or without its code (STA-3479)', () => {
for (const pair of BUSY_TRANSPORT_ERRORS) {
expect(isRecoverableRemoteRuntimeConnectionError(pair)).toBe(true)
@@ -11,7 +11,6 @@ import {
import { getRemoteRuntimeRequestAdmissionEvidence } from '../requests/remote-runtime-prepared-request-admission'
import { remoteRuntimeClientCapabilities } from '../../remote-runtime-client-capabilities'
import { RemoteRuntimeSharedControlConnection } from './remote-runtime-shared-control-connection'
import * as sharedControlProtocol from './remote-runtime-shared-control-protocol'
import { isRuntimeSubscriptionReplayResponse } from '../../runtime-subscription-replay'
import {
closeSharedControlTestServers,
@@ -81,10 +80,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
connection.close()
})
it('does not expose a binary sender on the shared control protocol surface', () => {
expect('sendSharedControlEncryptedBinary' in sharedControlProtocol).toBe(false)
})
it('releases a pending request when the socket send throws', async () => {
const connection = new RemoteRuntimeSharedControlConnection(
pairedRuntimeConnectionFromOffer({
@@ -122,36 +117,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
connection.close()
})
it('replaces a stuck pre-ready socket when a one-shot probe proves reachability', () => {
const connection = new RemoteRuntimeSharedControlConnection(
pairedRuntimeConnectionFromOffer({
v: 2,
endpoint: 'ws://127.0.0.1:1',
deviceToken: 'token',
publicKeyB64: Buffer.from(new Uint8Array(32).fill(1)).toString('base64')
})
)
const close = vi.fn()
const cleanup = vi.fn()
const open = vi.fn()
const unsafe = connection as unknown as {
state: string
ws: { readyState: number; close: () => void } | null
socketCleanup: (() => void) | null
open: () => void
}
unsafe.state = 'awaiting_ready'
unsafe.ws = { readyState: 0, close }
unsafe.socketCleanup = cleanup
unsafe.open = open
connection.reconnectNow()
expect(cleanup).toHaveBeenCalledOnce()
expect(close).toHaveBeenCalledOnce()
expect(open).toHaveBeenCalledOnce()
})
it('keeps a waiting request alive when a reachability probe replaces its pre-ready socket', async () => {
const server = await createServer({ suppressReadyFrameCount: 1 })
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
@@ -257,29 +222,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
connection.close()
})
it('keeps many logical subscriptions on one authenticated WebSocket', async () => {
const server = await createServer()
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
const subscriptions = await Promise.all(
Array.from({ length: 35 }, (_value, index) =>
connection.subscribe('runtime.clientEvents.subscribe', { index }, 1000, {
onResponse: vi.fn(),
onError: vi.fn()
})
)
)
await vi.waitFor(() => expect(server.requests).toHaveLength(35))
expect(server.connectionCount()).toBe(1)
expect(
server.requests.every((request) => request.method === 'runtime.clientEvents.subscribe')
).toBe(true)
subscriptions.forEach((subscription) => subscription.close())
connection.close()
})
it('reconnects and replays passive subscriptions without closing them', async () => {
const server = await createServer({ closeAfterFirstStreamingResponse: true })
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
@@ -310,74 +252,6 @@ describe('RemoteRuntimeSharedControlConnection', () => {
connection.close()
})
it('keeps passive subscriptions alive after reaching the capped reconnect delay', async () => {
const server = await createServer()
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
const onClose = vi.fn()
const unsafe = connection as unknown as {
reconnect: {
attempt: number
scheduleWithDefaultBackoff: (intentionallyClosed: boolean, open: () => void) => void
}
subscriptions: Map<string, unknown>
}
unsafe.reconnect.attempt = 7
unsafe.subscriptions.set('sub-1', {
requestId: 'sub-1',
method: 'runtime.clientEvents.subscribe',
params: null,
callbacks: { onResponse: vi.fn(), onError: vi.fn(), onClose },
sent: false,
closed: false,
closeAfterReady: false,
remoteSubscriptionId: null
})
unsafe.reconnect.scheduleWithDefaultBackoff(false, () => {})
expect(onClose).not.toHaveBeenCalled()
expect(connection.getDiagnostics()).toMatchObject({
state: 'reconnecting',
reconnectAttempt: 8,
subscriptionCount: 1
})
connection.close()
})
it('resets reconnect attempts after a stable authenticated ready period', async () => {
const server = await createServer()
const connection = new RemoteRuntimeSharedControlConnection(server.pairing, {
reconnectStableResetMs: 50
})
await expect(connection.request('worktree.ps', undefined, 1000)).resolves.toMatchObject({
ok: true
})
;(connection as unknown as { reconnect: { attempt: number } }).reconnect.attempt = 3
await vi.waitFor(() =>
expect(connection.getDiagnostics()).toMatchObject({ reconnectAttempt: 0 })
)
connection.close()
})
it('removes ready waiters when a one-shot request times out during handshake', async () => {
const server = await createServer({ suppressReadyFrame: true })
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
const unsafe = connection as unknown as {
readyWaiters: unknown[]
pendingRequests: Map<string, unknown>
}
await expect(connection.request('worktree.ps', undefined, 25)).rejects.toThrow('Timed out')
await vi.waitFor(() => expect(unsafe.readyWaiters).toHaveLength(0))
expect(unsafe.pendingRequests.size).toBe(0)
connection.close()
})
it('cleans up an id-scoped subscription closed before its ready response', async () => {
const server = await createServer({ delaySubscriptionReady: true })
const connection = new RemoteRuntimeSharedControlConnection(server.pairing)
@@ -74,15 +74,6 @@ describe('shared control keepalive timeout refresh semantics', () => {
await expect(promise).resolves.toMatchObject({ ok: true })
})
it('fires the deadline for a short RPC when no keepalives arrive', async () => {
const { pendingRequests, promise } = startRequest()
await vi.advanceTimersByTimeAsync(1001)
await expect(promise).rejects.toThrow()
expect(pendingRequests.size).toBe(0)
})
it('aborts one pending request without disturbing its peer', async () => {
const pendingRequests = new Map<string, SharedControlPendingRequest<unknown>>()
const controller = new AbortController()
@@ -92,31 +92,6 @@ describe('shared-control standing intent', () => {
scheduler.clear()
})
it.each([
{ manual: false, paused: false, subscriptions: 0, scheduled: true },
{ manual: false, paused: false, subscriptions: 1, scheduled: true },
{ manual: false, paused: true, subscriptions: 0, scheduled: false },
{ manual: false, paused: true, subscriptions: 1, scheduled: true },
{ manual: true, paused: false, subscriptions: 0, scheduled: false },
{ manual: true, paused: false, subscriptions: 1, scheduled: false },
{ manual: true, paused: true, subscriptions: 0, scheduled: false },
{ manual: true, paused: true, subscriptions: 1, scheduled: false }
])(
'applies the intent/capability truth table: $manual/$paused/$subscriptions',
({ manual, paused, subscriptions, scheduled }) => {
vi.useFakeTimers()
const connection = testConnection({ manual, paused })
if (subscriptions > 0) {
addLogicalSubscription(connection)
}
closeCurrentGeneration(connection)
expect(connection.getDiagnostics().state === 'reconnecting').toBe(scheduled)
connection.close()
}
)
it('advances an armed idle retry without recreating cleared work', () => {
vi.useFakeTimers()
const connection = testConnection()
@@ -1,7 +1,5 @@
import { describe, expect, it } from 'vitest'
import {
RECOVERABLE_CODES,
RECOVERABLE_MESSAGE_FRAGMENTS,
isRecoverableRemoteRuntimeConnectionError,
isRuntimeRpcQueueOverloadError,
toRemoteRuntimeClientErrorLike
@@ -64,33 +62,6 @@ describe('remote runtime client error classification', () => {
expect(isRecoverableRemoteRuntimeConnectionError(error)).toBe(true)
})
it('pins the recoverable codes and message fragments', () => {
expect([...RECOVERABLE_CODES].toSorted()).toEqual([
'reconnecting',
'remote_runtime_busy',
'remote_runtime_unavailable',
'runtime_rpc_queue_overloaded',
'runtime_timeout',
'runtime_unavailable',
'timeout'
])
expect(RECOVERABLE_MESSAGE_FRAGMENTS).toEqual([
'could not connect to the remote orca runtime',
'remote orca runtime closed the connection',
'remote orca runtime connection closed',
'remote orca runtime is not connected',
'remote runtime call queue is full',
'remote runtime connection closed',
'remote runtime connection wait limit reached',
'remote runtime request limit reached',
'remote runtime subscription limit reached',
'remote runtime subscription memory limit reached',
'remote runtime subscription closed before it started',
'remote terminal stream is not connected',
'timed out waiting for the remote orca runtime'
])
})
it.each([
'runtime_manually_disconnected',
'ssh_user_disconnected',
@@ -99,17 +70,4 @@ describe('remote runtime client error classification', () => {
])('does not treat client code %s as recoverable', (code) => {
expect(isRecoverableRemoteRuntimeConnectionError({ code, message: 'refused' })).toBe(false)
})
it.each(RECOVERABLE_MESSAGE_FRAGMENTS)(
'recovers a code-stripped message containing %s',
(fragment) => {
expect(
isRecoverableRemoteRuntimeConnectionError(
toRemoteRuntimeClientErrorLike(
new Error(`Error invoking remote method 'x': Error: ${fragment}.`)
)
)
).toBe(true)
}
)
})
@@ -136,27 +136,6 @@ describe('createUpdaterQuitAbortRelay', () => {
expect(aborted).toHaveBeenCalledTimes(1)
})
it('resets a prepared restart on a linux package-install recovery status', () => {
const eventTarget = new EventTarget()
const aborted = vi.fn()
eventTarget.addEventListener('update-restart-aborted', aborted)
const relay = createUpdaterQuitAbortRelay(eventTarget, 'update-restart-aborted')
relay.markPrepared()
relay.handleStatus({
state: 'error',
message: 'No authentication agent found.',
recovery: {
kind: 'linux-package-install',
packageType: 'deb',
reason: 'authentication-agent-unavailable',
version: '1.0.61'
}
} satisfies UpdateStatus)
expect(aborted).toHaveBeenCalledTimes(1)
})
it('ignores updater errors when no update restart was prepared', () => {
const eventTarget = new EventTarget()
const aborted = vi.fn()
-21
View File
@@ -2,18 +2,6 @@ import { describe, expect, it } from 'vitest'
import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from './repo-display-labels'
describe('getRepoDisplayLabelsByPath', () => {
it('keeps non-colliding repository names basename-only', () => {
const labels = getRepoDisplayLabelsByPath([
{ path: '/workspace/platform/web', displayName: 'web' },
{ path: '/workspace/platform/worker', displayName: 'worker' }
])
expect(labels.get(getRepoDisplayLabelKey({ path: '/workspace/platform/web' }))).toBe('web')
expect(labels.get(getRepoDisplayLabelKey({ path: '/workspace/platform/worker' }))).toBe(
'worker'
)
})
it('adds the minimal real parent suffix only for colliding basenames', () => {
const labels = getRepoDisplayLabelsByPath([
{ path: '/workspace/platform/web', displayName: 'web' },
@@ -80,15 +68,6 @@ describe('getRepoDisplayLabelsByPath', () => {
)
})
it('keeps identical project paths short across local and paired execution hosts', () => {
const local = { path: '/home/user/projects/app', displayName: 'app' }
const paired = { ...local, executionHostId: 'runtime:home' as const }
const labels = getRepoDisplayLabelsByPath([local, paired])
expect(labels.get(getRepoDisplayLabelKey(local))).toBe('app')
expect(labels.get(getRepoDisplayLabelKey(paired))).toBe('app')
expect(labels.size).toBe(2)
})
it('disambiguates collisions only within their owning host, even when paths overlap', () => {
const local = { path: '/workspace/payments/api', displayName: 'api' }
const sibling = { path: '/workspace/billing/api', displayName: 'api' }
+2 -17
View File
@@ -1,10 +1,6 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import { describe, expect, it } from 'vitest'
import { toRuntimeExecutionHostId, toSshExecutionHostId } from './execution-host'
import {
getRepoHostIdentityForParts,
getRepoRegistrationHostIds,
type HostScopedRepoKey
} from './repo-host-identity'
import { getRepoHostIdentityForParts, getRepoRegistrationHostIds } from './repo-host-identity'
// Why: these keys index persisted and cross-module repo state, so the bytes are frozen (D25).
describe('repo host identity golden bytes', () => {
@@ -21,17 +17,6 @@ describe('repo host identity golden bytes', () => {
] as const)('composes %s on %s', (repoId, hostId, expected) => {
expect(getRepoHostIdentityForParts(repoId, hostId)).toBe(expected)
})
it('brands the key so a plain string cannot pose as one', () => {
const key = getRepoHostIdentityForParts('repo-1', 'local')
expectTypeOf(key).toEqualTypeOf<HostScopedRepoKey>()
expectTypeOf<HostScopedRepoKey>().toExtend<string>()
expectTypeOf<string>().not.toExtend<HostScopedRepoKey>()
const raw: string = key
// @ts-expect-error a raw string is not a host-scoped key
const forged: HostScopedRepoKey = 'local\0repo-1'
expect(raw).toBe(forged)
})
})
describe('getRepoRegistrationHostIds', () => {
-14
View File
@@ -554,20 +554,6 @@ describe('RepoRefMaintenance single-flight and backoff', () => {
expect(spans).toHaveLength(1)
})
it('records a failure when the pack left the backlog in place', async () => {
const refs = await refsDirectoryWith(THRESHOLD + 2)
const { maintenance, spans } = createHarness({
packRefs: async () => {
throw new Error('permission denied')
}
})
maintenance.arm(target('local::/denied/.git', refs, () => Promise.reject(new Error('denied'))))
await elapseQuietPeriod(maintenance)
expect(attributesOf(spans[0])['repo.maintenance_outcome']).toBe('failed')
})
it('records a failure instead of throwing, and backs off', async () => {
const refs = await refsDirectoryWith(THRESHOLD + 2)
const { maintenance, spans, packRefs } = createHarness({
@@ -22,18 +22,6 @@ describe('getRepoRoutingConnectionId', () => {
expect(getRepoRoutingConnectionId(repo)).toBe(expected)
})
it('never answers local for a row whose raw connectionId is set', () => {
for (const executionHostId of [
undefined,
'local',
'runtime:env',
'ssh:gpu',
'ssh:other'
] as const) {
expect(getRepoRoutingConnectionId({ executionHostId, connectionId: 'gpu' })).not.toBeNull()
}
})
it('lets executionHostId win on a contradictory SSH row, as getRepoExecutionHostId does', () => {
expect(getRepoRoutingConnectionId({ executionHostId: 'ssh:b', connectionId: 'a' })).toBe('b')
})
@@ -9,12 +9,10 @@ import {
} from './agent-detection'
import { isDecorativeAgentTitleFrameChange } from './agent-decorative-title-signature'
import { stripLeadingAgentTitleDecoration } from './agent-title-decoration'
import { resolveExplicitTerminalTitleAgentType } from './terminal-title-agent-type'
// Titles captured from real `claude` binaries running the same one-line prompt.
// 2.1.228 swapped the busy spinner from braille to quarter circles, which read as
// "no agent" and made the tracker report a confirmed exit mid-turn (#13889).
const BUSY_2_1_227 = ['⠂ Claude Code', '⠐ Claude Code', '⠂ Say hi in one word'] as const
const BUSY_2_1_228 = ['◐ Claude Code', '◑ Claude Code', '◑ Say hi in one word'] as const
const CAPTURED_2_1_228_TURN = [
@@ -58,30 +56,12 @@ describe('Claude Code quarter-circle busy titles (#13889)', () => {
}
})
it('matches 2.1.227 status and identity frame for frame', () => {
BUSY_2_1_228.forEach((title, index) => {
const braille = BUSY_2_1_227[index]
expect(detectAgentStatusFromTitle(title)).toBe(detectAgentStatusFromTitle(braille))
expect(getAgentLabel(title)).toBe(getAgentLabel(braille))
expect(resolveExplicitTerminalTitleAgentType(title)).toBe(
resolveExplicitTerminalTitleAgentType(braille)
)
})
})
it('never confirms an agent exit across a real 2.1.228 turn', () => {
const events = trackTurn(CAPTURED_2_1_228_TURN)
expect(events).not.toContain('exited')
expect(events).toEqual(['working', 'idle'])
})
it('tracks the 2.1.228 turn exactly like the 2.1.227 turn', () => {
const brailleTurn = CAPTURED_2_1_228_TURN.map((title) =>
title.replace('◐', '⠂').replace('◑', '⠐')
)
expect(trackTurn(CAPTURED_2_1_228_TURN)).toEqual(trackTurn(brailleTurn))
})
it('strips the spinner from stale exit titles and displayed labels', () => {
expect(clearWorkingIndicators('◐ Say hi in one word')).toBe('Say hi in one word')
expect(stripLeadingAgentTitleDecoration('◐ Claude Code')).toBe('Claude Code')
@@ -51,22 +51,12 @@ describe('sharesWorktreeLineageBoundary', () => {
...overrides
})
it('accepts the same host', () => {
expect(sharesWorktreeLineageBoundary(boundary(), boundary())).toBe(true)
})
it.each([
['child host', boundary({ hostId: undefined }), boundary()],
['parent host', boundary(), boundary({ hostId: undefined })]
])('treats an undefined %s as compatible', (_label, child, parent) => {
expect(sharesWorktreeLineageBoundary(child, parent)).toBe(true)
})
it('rejects a different known host', () => {
expect(sharesWorktreeLineageBoundary(boundary(), boundary({ hostId: 'ssh:remote' }))).toBe(
false
)
})
})
describe('projectResolvedWorktreeLineage', () => {
@@ -1,46 +0,0 @@
// Opt-in Settings → Browser preview; enable with ORCA_SETTINGS_PREVIEW=1.
import { expect, test } from './helpers/orca-app'
test.skip(
process.env.ORCA_SETTINGS_PREVIEW !== '1',
'Preview only; run with ORCA_SETTINGS_PREVIEW=1'
)
const HOLD_MINUTES = 20
test('shows the remote browsing settings section and holds for review', async ({
electronApp,
orcaPage
}) => {
test.setTimeout((HOLD_MINUTES + 10) * 60_000)
await electronApp.evaluate(({ BrowserWindow }) => {
const window = BrowserWindow.getAllWindows()[0]
window?.setSize(1440, 900)
window?.center()
window?.show()
window?.focus()
})
// Seed one opted-out SSH host so the "Route again" list renders too.
await orcaPage.evaluate(() => {
const state = window.__store?.getState()
state?.updateSettings({ browserSshWorkspaceRoutingDisabledTargetIds: ['preview-target'] })
window.__store?.setState({
sshTargetLabels: new Map([['preview-target', 'openclaw']])
} as never)
state?.openSettingsTarget({
pane: 'browser',
repoId: null,
sectionId: 'browser-ssh-workspace-routing'
})
state?.openSettingsPage()
})
await expect(orcaPage.getByText('Remote browsing', { exact: true })).toBeVisible({
timeout: 30_000
})
console.log(`\n=== SETTINGS PREVIEW READY — window stays up ${HOLD_MINUTES} minutes ===\n`)
await orcaPage.waitForTimeout(HOLD_MINUTES * 60_000)
})
-50
View File
@@ -410,56 +410,6 @@ test.describe('Browser Tab', () => {
}
})
test('Cmd/Ctrl+0 resets a zoomed browser page to 100%', async ({ orcaPage }) => {
const formServer = await startBrowserFormServer()
try {
const worktreeId = (await getActiveWorktreeId(orcaPage))!
const browserTab = await createBrowserTab(
orcaPage,
worktreeId,
formServer.url('Zoom reset'),
'Zoom Reset'
)
expect(browserTab?.id).toBeTruthy()
await expect
.poll(async () => readBrowserInputValue(orcaPage, browserTab!.id), { timeout: 5_000 })
.not.toBeNull()
const zoomModifier: 'meta' | 'control' = process.platform === 'darwin' ? 'meta' : 'control'
await orcaPage.evaluate(
async ({ browserTabId, browserPageId, modifier }) => {
const slot = document.querySelector(`[data-browser-overlay-tab-id="${browserTabId}"]`)
const webview = slot?.querySelector('webview') as Electron.WebviewTag | null
if (!webview) {
throw new Error(`Missing webview for browser tab ${browserTabId}`)
}
window.dispatchEvent(
new CustomEvent('orca:browser-page-zoom', {
detail: { browserPageId, direction: 'in' }
})
)
await webview.sendInputEvent({ type: 'keyDown', keyCode: '0', modifiers: [modifier] })
await webview.sendInputEvent({ type: 'keyUp', keyCode: '0', modifiers: [modifier] })
},
{
browserTabId: browserTab!.id,
browserPageId: browserTab!.pageId ?? browserTab!.id,
modifier: zoomModifier
}
)
await expect
.poll(() =>
orcaPage.evaluate((browserTabId) => {
const slot = document.querySelector(`[data-browser-overlay-tab-id="${browserTabId}"]`)
return (slot?.querySelector('webview') as Electron.WebviewTag | null)?.getZoomLevel()
}, browserTab!.id)
)
.toBe(0)
} finally {
await formServer.close()
}
})
test('reloading one browser tab does not adopt another tab zoom', async ({ orcaPage }) => {
const [formServerA, formServerB] = await Promise.all([
startBrowserFormServer(),
@@ -1,136 +0,0 @@
// Opt-in client-hosted browser tooltip preview; enable with ORCA_TOOLTIP_PREVIEW=1.
import { createServer, type Server } from 'node:http'
import type { AddressInfo } from 'node:net'
import type { Page } from '@stablyai/playwright-test'
import { expect, test } from './helpers/orca-app'
import { launchHeadlessPairedRuntimeHost } from './helpers/headless-paired-runtime-host'
import { launchPairedElectronClient } from './helpers/paired-electron-client'
test.skip(process.env.ORCA_TOOLTIP_PREVIEW !== '1', 'Preview only; run with ORCA_TOOLTIP_PREVIEW=1')
const HOLD_MINUTES = 20
async function startFixtureServer(): Promise<{ close(): Promise<void>; url: string }> {
const server = createServer((_request, response) => {
response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
response.end(
'<!doctype html><html><head><title>tooltip-preview</title></head><body style="font-family:sans-serif;padding:2rem"><h1>Client-hosted page</h1><p>The intro tooltip should point at the controls above.</p></body></html>'
)
})
await new Promise<void>((resolve, reject) => {
server.once('error', reject)
server.listen(0, '127.0.0.1', () => {
server.off('error', reject)
resolve()
})
})
return {
close: () => closeServer(server),
url: `http://127.0.0.1:${(server.address() as AddressInfo).port}/preview`
}
}
async function closeServer(server: Server): Promise<void> {
await new Promise<void>((resolve, reject) => {
server.closeAllConnections()
server.close((error) => (error ? reject(error) : resolve()))
})
}
async function findPairedWorktreeId(page: Page, repoPath: string): Promise<string> {
const read = () =>
page.evaluate(
(path) =>
window.__store
?.getState()
.allWorktrees()
.find((worktree) => worktree.path === path)?.id ?? null,
repoPath
)
await expect
.poll(read, { timeout: 60_000, message: 'paired client never received the host worktree' })
.not.toBeNull()
const worktreeId = await read()
if (!worktreeId) {
throw new Error('Paired worktree disappeared after discovery')
}
return worktreeId
}
test('shows the client-hosted browser intro tooltip and holds for review', async ({
testRepoPath
}, testInfo) => {
test.setTimeout((HOLD_MINUTES + 10) * 60_000)
const fixtureServer = await startFixtureServer()
const host = await launchHeadlessPairedRuntimeHost()
let dispose = async (): Promise<void> => {
await host.dispose()
await fixtureServer.close()
}
try {
await host.client.call('repo.add', { path: testRepoPath, kind: 'git' })
await host.client.call('terminal.create', {
worktree: `path:${testRepoPath}`,
title: 'Preview Terminal'
})
const client = await launchPairedElectronClient(host.offer, testInfo, 'tooltip preview')
const disposeWithoutClient = dispose
dispose = async () => {
await client.dispose()
await disposeWithoutClient()
}
// The e2e profile launches hidden; surface the window for the human.
await client.app.evaluate(({ BrowserWindow }) => {
const window = BrowserWindow.getAllWindows()[0]
window?.setSize(1440, 900)
window?.center()
window?.show()
window?.focus()
})
const worktreeId = await findPairedWorktreeId(client.page, testRepoPath)
await client.page.evaluate(
({ environmentId, worktreeId }) => {
window.__store?.getState().setActiveWorktree(worktreeId, `runtime:${environmentId}`)
},
{ environmentId: client.environmentId, worktreeId }
)
await expect
.poll(
(): Promise<number> =>
client.page.evaluate(
(id) => window.__store?.getState().groupsByWorktree[id]?.[0]?.tabOrder.length ?? 0,
worktreeId
),
{ timeout: 120_000, message: 'paired client never adopted the host tab group' }
)
.toBeGreaterThanOrEqual(1)
await client.page.evaluate(
async ({ url, worktreeId }) => {
const state = window.__store?.getState()
if (!state) {
throw new Error('store unavailable')
}
const groupId = state.groupsByWorktree[worktreeId]?.[0]?.id
state.setBrowserDefaultUrl(url)
await state.openNewBrowserTabInActiveWorkspace(groupId)
},
{ url: fixtureServer.url, worktreeId }
)
await expect
.poll(
(): Promise<string | null> =>
client.page.evaluate(() => window.__store?.getState().activeContextualTourId ?? null),
{ timeout: 60_000, message: 'intro tooltip never became the active contextual tour' }
)
.toBe('client-hosted-browser')
console.log(`\n=== TOOLTIP PREVIEW READY — window stays up ${HOLD_MINUTES} minutes ===\n`)
await new Promise((resolve) => setTimeout(resolve, HOLD_MINUTES * 60_000))
} finally {
await dispose()
}
})