diff --git a/src/main/runtime/orca-runtime-tests/browser-capabilities-part-02.spec.ts b/src/main/runtime/orca-runtime-tests/browser-capabilities-part-02.spec.ts index fb74cfbb3fc..68adf9e8e35 100644 --- a/src/main/runtime/orca-runtime-tests/browser-capabilities-part-02.spec.ts +++ b/src/main/runtime/orca-runtime-tests/browser-capabilities-part-02.spec.ts @@ -1,16 +1,9 @@ import { describe, expect, it, vi } from 'vitest' -import { - OrcaRuntimeService, - RuntimeBrowserCommands, - setRuntimeBrowserCommandsFactory, - setRuntimeBrowserUnavailableCause -} from '../orca-runtime-test-mocks.spec' -import { runtimeAdvertisesBrowserClientHosting } from '../../../shared/browser-client-hosting-eligibility' +import { OrcaRuntimeService } from '../orca-runtime-test-mocks.spec' import { TEST_REPO_ID, TEST_WORKTREE_ID, TEST_WORKTREE_PATH, - createRuntime, store } from '../orca-runtime-test-fixtures.spec' @@ -42,22 +35,4 @@ describe('OrcaRuntimeService', () => { ).rejects.toThrow('selector_not_found') expect(internals.listResolvedWorktrees).not.toHaveBeenCalled() }) - - // #30731: engine-free orcad can host client-rendered pages while reporting its local engine failure. - it('advertises client hosting from an engine-free factory while reporting the engine cause', () => { - setRuntimeBrowserCommandsFactory((host) => new RuntimeBrowserCommands(host), { - headless: true, - clientHostingWithoutEngine: true, - isAvailable: () => false - }) - setRuntimeBrowserUnavailableCause({ reason: 'unconfigured' }) - const status = createRuntime().getStatus() - expect(runtimeAdvertisesBrowserClientHosting(status.capabilities)).toBe(true) - expect(status.capabilities).toContain('browser.tab-create-known-id.v1') - expect(status.capabilities).not.toContain('browser.headless.v1') - expect(status.degradations).toContainEqual( - expect.objectContaining({ code: 'browser_unavailable', reason: 'unconfigured' }) - ) - setRuntimeBrowserUnavailableCause(null) - }) }) diff --git a/src/main/runtime/orca-runtime-tests/browser-capabilities.spec.ts b/src/main/runtime/orca-runtime-tests/browser-capabilities.spec.ts index 1bbb89015c1..8d721254310 100644 --- a/src/main/runtime/orca-runtime-tests/browser-capabilities.spec.ts +++ b/src/main/runtime/orca-runtime-tests/browser-capabilities.spec.ts @@ -1,20 +1,12 @@ import { describe, expect, it, vi } from 'vitest' import { OrcaRuntimeService, - RuntimeBrowserCommands, deleteWorktreeHistoryDirMock, - electronMocks, getBrowserHostLeaseRegistry, - getRuntimeBrowserPageRegistry, - headlessBrowserTabsUnchanged, - setRuntimeBrowserCommandsFactory, - setRuntimeBrowserUnavailableCause, - setRuntimeTerminalUnavailableCause + getRuntimeBrowserPageRegistry } from '../orca-runtime-test-mocks.spec' -import type { RuntimeMobileSessionTabsResult } from '../orca-runtime-test-mocks.spec' import { TEST_REPO_ID, - TEST_WINDOW_ID, TEST_WORKTREE_ID, createRuntime, store @@ -23,276 +15,9 @@ import { attachClientBrowserHost, publishClientHostedPage } from '../orca-runtime-test-scenario-builders.spec' -import { mkdtempSync } from 'node:fs' -import { tmpdir } from 'node:os' -import { join } from 'node:path' -import { - initializeBrowserIdentityModeStore, - resetBrowserIdentityModeStoreForTests -} from '../../browser/browser-identity-mode-store' -import { runtimeAdvertisesBrowserClientHosting } from '../../../shared/browser-client-hosting-eligibility' import { hostWorktreeTarget } from '../../../shared/worktree/host-qualified-identity' describe('OrcaRuntimeService', () => { - it('advertises Google clear only for a live supporting executor, independently of client hosting', async () => { - for (const supported of [undefined, false, true]) { - let available = true - setRuntimeBrowserCommandsFactory((host) => new RuntimeBrowserCommands(host), { - headless: true, - clientHostingWithoutEngine: true, - isAvailable: () => available, - supportsGoogleCookieClear: supported === undefined ? undefined : () => supported - }) - const runtime = createRuntime() - const capabilities = runtime.getStatus().capabilities ?? [] - expect(capabilities.includes('browser.google-cookie-clear.v1')).toBe(supported === true) - available = false - expect(runtime.getStatus().capabilities).not.toContain('browser.google-cookie-clear.v1') - expect(runtimeAdvertisesBrowserClientHosting(runtime.getStatus().capabilities)).toBe(true) - } - const { installElectronBrowserCommands } = await import('../../host/electron-browser-commands') - installElectronBrowserCommands() - expect(createRuntime().getStatus().capabilities).toContain('browser.google-cookie-clear.v1') - setRuntimeBrowserCommandsFactory(null) - expect(createRuntime().getStatus().capabilities).not.toContain('browser.google-cookie-clear.v1') - }) - - it('advertises client hosting when its factory implements client page creation', () => { - const capabilities = createRuntime().getStatus().capabilities - expect(runtimeAdvertisesBrowserClientHosting(capabilities)).toBe(true) - expect(capabilities).toContain('browser.tab-create-known-id.v1') - }) - - it('does not offer client placement through a server-only external browser provider', () => { - setRuntimeBrowserCommandsFactory((host) => new RuntimeBrowserCommands(host), { - headless: true - }) - const capabilities = createRuntime().getStatus().capabilities - expect(capabilities).toContain('browser.headless.v1') - expect(capabilities).toContain('browser.tab-create-known-id.v1') - expect(capabilities).not.toContain('browser.clientHost.v1') - expect(runtimeAdvertisesBrowserClientHosting(capabilities)).toBe(false) - expect(capabilities).toContain('network.browserTunnel.v1') - }) - - it('withdraws client hosting when its factory health probe fails', () => { - let available = true - setRuntimeBrowserCommandsFactory((host) => new RuntimeBrowserCommands(host), { - clientHosting: true, - isAvailable: () => available - }) - const runtime = createRuntime() - expect(runtimeAdvertisesBrowserClientHosting(runtime.getStatus().capabilities)).toBe(true) - available = false - expect(runtimeAdvertisesBrowserClientHosting(runtime.getStatus().capabilities)).toBe(false) - }) - // Uninitialized stores cannot serve identity methods; never advertise them to mixed-version clients. - it('advertises the browser identity capability only where an identity store exists', () => { - resetBrowserIdentityModeStoreForTests() - expect(createRuntime().getStatus().capabilities).not.toContain('browser.identity.v1') - - initializeBrowserIdentityModeStore(mkdtempSync(join(tmpdir(), 'orca-identity-capability-'))) - try { - expect(createRuntime().getStatus().capabilities).toContain('browser.identity.v1') - } finally { - resetBrowserIdentityModeStoreForTests() - } - }) - - it('advertises headless browser capability when an offscreen backend backs a windowless host', () => { - const runtime = createRuntime() - runtime.setOffscreenBrowserBackend({ - createTab: vi.fn(), - closeTab: vi.fn(), - ownsTab: () => true - }) - - const capabilities = runtime.getStatus().capabilities - expect(capabilities).toContain('browser.screencast.v1') - expect(capabilities).toContain('browser.headless.v1') - expect(capabilities).toContain('browser.certificate-trust.v1') - }) - - it('advertises only while headless browser commands remain live', () => { - let available = true - setRuntimeBrowserCommandsFactory((host) => new RuntimeBrowserCommands(host), { - headless: true, - isAvailable: () => available - }) - const status = createRuntime().getStatus() - - expect(status.capabilities).toContain('browser.headless.v1') - expect(status.capabilities).not.toContain('browser.screencast.v1') - expect(status.capabilities).not.toContain('browser.clientHost.v1') - expect(status.capabilities).toContain('browser.tab-create-known-id.v1') - expect(runtimeAdvertisesBrowserClientHosting(status.capabilities)).toBe(false) - expect(status.capabilities).not.toContain('browser.certificate-trust.v1') - expect(status.degradations).toBeUndefined() - - available = false - const degraded = createRuntime().getStatus() - expect(degraded.capabilities).not.toContain('browser.headless.v1') - // Previously resolved provider death is health failure, not misconfiguration. - expect(degraded.degradations).toEqual([ - { - code: 'browser_unavailable', - capability: 'browser.headless.v1', - reason: 'provider_unhealthy', - message: 'The browser provider started but is no longer answering health checks.' - } - ]) - }) - it('surfaces live offscreen load failures in headless browser snapshots', () => { - const runtime = createRuntime() - runtime.setOffscreenBrowserBackend({ - createTab: vi.fn(), - closeTab: vi.fn(), - ownsTab: () => true - }) - runtime.setAgentBrowserBridge({ - tabList: vi.fn(() => ({ - tabs: [ - { - browserPageId: 'page-certificate-error', - index: 0, - url: 'https://localhost:3443/', - title: 'Local HTTPS', - active: true, - loadError: { - code: -202, - description: 'ERR_CERT_AUTHORITY_INVALID', - validatedUrl: 'https://localhost:3443/' - }, - certificateFailure: { - challengeId: 'challenge-1', - browserPageId: 'page-certificate-error', - errorCode: -202, - error: 'ERR_CERT_AUTHORITY_INVALID', - origin: 'https://localhost:3443', - displayHost: 'localhost:3443', - canProceed: true, - observedAt: 123 - } - } - ] - })) - } as never) - const browserTabs = runtime['buildHeadlessMobileSessionBrowserTabs'](TEST_WORKTREE_ID) - expect(browserTabs).toContainEqual( - expect.objectContaining({ - type: 'browser', - browserPageId: 'page-certificate-error', - loadError: { - code: -202, - description: 'ERR_CERT_AUTHORITY_INVALID', - validatedUrl: 'https://localhost:3443/' - }, - certificateFailure: { - challengeId: 'challenge-1', - browserPageId: 'page-certificate-error', - errorCode: -202, - error: 'ERR_CERT_AUTHORITY_INVALID', - origin: 'https://localhost:3443', - displayHost: 'localhost:3443', - canProceed: true, - observedAt: 123 - } - }) - ) - }) - - it('synthesizes runtime-owned client pages without a server browser backend', () => { - const runtime = createRuntime() - getRuntimeBrowserPageRegistry(runtime).publishClientPage({ - browserPageId: 'page-client', - workspaceId: TEST_WORKTREE_ID, - browserProfileId: 'profile-a', - executionHostKey: 'execution-a', - placement: { - kind: 'client', - browserHostClientId: 'host-a', - browserHostGeneration: 3, - pageHostGeneration: 9 - }, - url: 'https://remote.internal/', - title: 'Remote app', - loading: false, - canGoBack: true, - canGoForward: false, - active: true - }) - - expect(runtime['buildHeadlessMobileSessionBrowserTabs'](TEST_WORKTREE_ID)).toEqual([ - { - type: 'browser', - id: 'page-client', - title: 'Remote app', - browserWorkspaceId: 'page-client', - browserPageId: 'page-client', - browserProfileId: 'profile-a', - executionHostKey: 'execution-a', - placement: { - kind: 'client', - browserHostClientId: 'host-a', - browserHostGeneration: 3, - pageHostGeneration: 9 - }, - url: 'https://remote.internal/', - loading: false, - canGoBack: true, - canGoForward: false, - isActive: true - } - ]) - }) - - it('publishes a runtime-owned client page through the session-tab listener', () => { - const runtime = createRuntime() - runtime['mobileSessionTabsByWorktree'].set(TEST_WORKTREE_ID, { - worktree: TEST_WORKTREE_ID, - publicationEpoch: 'renderer:1', - snapshotVersion: 1, - activeGroupId: 'group-1', - activeTabId: null, - activeTabType: null, - tabGroups: [{ id: 'group-1', activeTabId: null, tabOrder: [] }], - tabs: [] - }) - const snapshots: RuntimeMobileSessionTabsResult[] = [] - const unsubscribe = runtime.onMobileSessionTabsChanged((snapshot) => snapshots.push(snapshot)) - getRuntimeBrowserPageRegistry(runtime).publishClientPage({ - browserPageId: 'page-client', - workspaceId: TEST_WORKTREE_ID, - browserProfileId: 'profile-a', - executionHostKey: 'execution-a', - placement: { - kind: 'client', - browserHostClientId: 'host-a', - browserHostGeneration: 3, - pageHostGeneration: 9 - }, - url: 'about:blank', - loading: true, - active: true - }) - - runtime.notifyMobileSessionTabsChanged(TEST_WORKTREE_ID) - - expect(snapshots.at(-1)?.tabs).toContainEqual( - expect.objectContaining({ - type: 'browser', - browserPageId: 'page-client', - loading: true, - placement: expect.objectContaining({ - kind: 'client', - browserHostClientId: 'host-a', - pageHostGeneration: 9 - }) - }) - ) - unsubscribe() - }) - it('preserves a live client page across headed renderer graph updates and prunes it after retirement', async () => { const runtime = createRuntime() const pages = getRuntimeBrowserPageRegistry(runtime) @@ -361,241 +86,6 @@ describe('OrcaRuntimeService', () => { expect((await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs).toEqual([]) }) - it('detects headless browser-tab changes by field, treating absent and null loadError alike', () => { - const base = { - type: 'browser' as const, - id: 'page-1', - title: 'Local', - browserWorkspaceId: 'page-1', - browserPageId: 'page-1', - url: 'https://localhost:3443/', - loading: false, - canGoBack: false, - canGoForward: false, - isActive: true - } - const err = { - code: -202, - description: 'ERR_CERT_AUTHORITY_INVALID', - validatedUrl: 'https://localhost:3443/' - } - const certificateFailure = { - challengeId: 'challenge-1', - browserPageId: 'page-1', - errorCode: -202, - error: 'ERR_CERT_AUTHORITY_INVALID', - origin: 'https://localhost:3443', - displayHost: 'localhost:3443', - canProceed: true, - observedAt: 123 - } - const unchanged = (a: unknown[], b: unknown[]): boolean => - headlessBrowserTabsUnchanged(a as never, b as never) - - // Missing/null loadError are equivalent despite differing JSON representations. - expect(unchanged([{ ...base }], [{ ...base, loadError: null }])).toBe(true) - expect(unchanged([{ ...base, loadError: err }], [{ ...base, loadError: { ...err } }])).toBe( - true - ) - expect(unchanged([{ ...base }], [{ ...base, loadError: err }])).toBe(false) - expect( - unchanged([{ ...base, loadError: err }], [{ ...base, loadError: { ...err, code: -200 } }]) - ).toBe(false) - expect( - unchanged( - [{ ...base, certificateFailure }], - [{ ...base, certificateFailure: { ...certificateFailure } }] - ) - ).toBe(true) - expect(unchanged([{ ...base }], [{ ...base, certificateFailure }])).toBe(false) - expect( - unchanged( - [{ ...base, certificateFailure }], - [{ ...base, certificateFailure: { ...certificateFailure, challengeId: 'challenge-2' } }] - ) - ).toBe(false) - expect(unchanged([{ ...base }], [{ ...base, title: 'Changed' }])).toBe(false) - expect(unchanged([{ ...base }], [{ ...base, isActive: false }])).toBe(false) - expect(unchanged([{ ...base }], [{ ...base }, { ...base, id: 'page-2' }])).toBe(false) - }) - - it('does not advertise headless browser capability when a renderer window exists', () => { - const runtime = createRuntime() - electronMocks.BrowserWindow.fromId.mockReturnValue({ isDestroyed: () => false } as never) - runtime.attachWindow(TEST_WINDOW_ID) - runtime.setOffscreenBrowserBackend({ - createTab: vi.fn(), - closeTab: vi.fn(), - ownsTab: () => true - }) - - expect(runtime.getStatus().capabilities).not.toContain('browser.headless.v1') - // Remote certificate proceed also applies to desktop webviews. - expect(runtime.getStatus().capabilities).toContain('browser.certificate-trust.v1') - }) - - it('declares browser unavailability when no browser provider resolves', async () => { - setRuntimeBrowserCommandsFactory(null) - const runtime = createRuntime() - const status = runtime.getStatus() - - expect(status.capabilities).not.toContain('browser.headless.v1') - expect(status.capabilities).not.toContain('browser.certificate-trust.v1') - expect(status.capabilities).not.toContain('browser.screencast.v1') - expect(status.capabilities).not.toContain('browser.clientHost.v1') - expect(status.capabilities).not.toContain('browser.tab-create-known-id.v1') - expect(runtimeAdvertisesBrowserClientHosting(status.capabilities)).toBe(false) - expect(status.degradations).toEqual([ - { - code: 'browser_unavailable', - capability: 'browser.headless.v1', - reason: 'unknown', - message: - 'Browser automation is unavailable on this host, and the cause could not be determined.' - } - ]) - await expect( - Promise.resolve().then(() => runtime.browserGoto({ url: 'https://example.com' })) - ).rejects.toMatchObject({ code: 'browser_unavailable' }) - await expect( - Promise.resolve().then(() => - runtime.browserScreencast( - { format: 'jpeg' }, - { sendBinary: () => true, emit: () => undefined } - ) - ) - ).rejects.toMatchObject({ code: 'browser_unavailable' }) - }) - - it('reports the driver as missing instead of telling a configured operator to configure it', () => { - setRuntimeBrowserCommandsFactory(null) - setRuntimeBrowserUnavailableCause({ reason: 'driver_missing' }) - - const [degradation] = createRuntime().getStatus().degradations ?? [] - - expect(degradation).toEqual({ - code: 'browser_unavailable', - capability: 'browser.headless.v1', - reason: 'driver_missing', - message: - 'ORCA_BROWSER_EXECUTABLE is set, but the bundled agent-browser driver is missing or not executable on this host, so Chromium cannot be driven.' - }) - expect(degradation?.message).not.toMatch(/set ORCA_BROWSER_EXECUTABLE/) - }) - - it('carries the underlying error to the client when a provider failed to start', () => { - setRuntimeBrowserCommandsFactory(null) - setRuntimeBrowserUnavailableCause({ - reason: 'electron_start_failed', - detail: 'sidecar exited with code 1' - }) - - const [degradation] = createRuntime().getStatus().degradations ?? [] - - expect(degradation).toEqual({ - code: 'browser_unavailable', - capability: 'browser.headless.v1', - reason: 'electron_start_failed', - detail: 'sidecar exited with code 1', - message: - 'The installed Electron browser provider failed to start. (sidecar exited with code 1)' - }) - }) - - it('blames the missing desktop window when a renderer-backed factory is installed', () => { - const [degradation] = createRuntime().getStatus().degradations ?? [] - - expect(degradation).toMatchObject({ - reason: 'desktop_window_unavailable', - message: 'Browser automation on this host needs a desktop window, and none is available.' - }) - }) - - it('keeps the degradation wire-safe for peers that predate structured causes', () => { - setRuntimeBrowserCommandsFactory(null) - setRuntimeBrowserUnavailableCause({ reason: 'executable_not_found', detail: '/nope/chromium' }) - - const [degradation] = createRuntime().getStatus().degradations ?? [] - - // Older clients require stable code/capability and a self-contained sentence without optional fields. - expect(degradation?.code).toBe('browser_unavailable') - expect(degradation?.capability).toBe('browser.headless.v1') - expect(degradation?.message).toBe( - 'ORCA_BROWSER_EXECUTABLE points at a path that does not exist. (/nope/chromium)' - ) - }) - - it('reports a host that cannot load node-pty, instead of that host never answering', () => { - // Loader crashes otherwise reach clients as unexplained dropped connections. - setRuntimeTerminalUnavailableCause({ - reason: 'libc_floor', - detail: 'the binary requires GLIBC_2.34' - }) - - const degradations = createRuntime().getStatus().degradations ?? [] - - expect(degradations).toContainEqual({ - code: 'terminal_unavailable', - capability: 'terminal.pty.v1', - reason: 'libc_floor', - detail: 'the binary requires GLIBC_2.34', - message: - "This host's node-pty binary was built against a newer C library than the host provides, so the dynamic loader refuses it. Rebuild node-pty on this host, or deploy a build whose prebuilt binary matches this platform's libc. (the binary requires GLIBC_2.34)" - }) - }) - - it('reports browser and terminal loss together, because they fail independently', () => { - setRuntimeBrowserCommandsFactory(null) - setRuntimeBrowserUnavailableCause({ reason: 'unconfigured' }) - setRuntimeTerminalUnavailableCause({ reason: 'dependency_missing' }) - - const codes = (createRuntime().getStatus().degradations ?? []).map((entry) => entry.code) - - expect(codes).toEqual(['browser_unavailable', 'terminal_unavailable']) - }) - - it('says nothing about terminals when no precondition proved them broken', () => { - // Unprobed hosts cannot claim proven health or failure; silence means no verdict. - const degradations = createRuntime().getStatus().degradations ?? [] - expect(degradations.map((entry) => entry.code)).not.toContain('terminal_unavailable') - }) - - it('closes a worktree’s offscreen browser pages when its metadata is removed (leak fix)', () => { - const runtime = createRuntime() - const closeTab = vi.fn().mockResolvedValue(undefined) - runtime.setOffscreenBrowserBackend({ createTab: vi.fn(), closeTab, ownsTab: () => true }) - runtime.setAgentBrowserBridge({ - tabList: vi.fn((worktreeId: string) => - worktreeId === TEST_WORKTREE_ID - ? { tabs: [{ browserPageId: 'page-a' }, { browserPageId: 'page-b' }] } - : { tabs: [] } - ) - } as never) - - runtime['removeWorktreeMetadataAndHistory'](store as never, TEST_WORKTREE_ID) - - expect(closeTab).toHaveBeenCalledWith('page-a') - expect(closeTab).toHaveBeenCalledWith('page-b') - expect(closeTab).toHaveBeenCalledTimes(2) - }) - - it('does not rescue a paired renderer PTY into a recreated worktree', () => { - const runtime = createRuntime() - const ptyId = 'paired-pty-deleted-worktree' - runtime.registerPty(ptyId, TEST_WORKTREE_ID, null, { - tabId: 'tab-deleted-worktree', - leafId: 'leaf-deleted-worktree' - }) - const internals = runtime as unknown as { - pairedRendererSessionOwnedPtyIds: Set - } - internals.pairedRendererSessionOwnedPtyIds.add(ptyId) - - runtime['removeWorktreeMetadataAndHistory'](store as never, TEST_WORKTREE_ID) - - expect(internals.pairedRendererSessionOwnedPtyIds.has(ptyId)).toBe(false) - }) - it('closes a worktree’s client-hosted browser pages when its metadata is removed (leak fix)', async () => { const runtime = createRuntime() const host = attachClientBrowserHost(runtime) @@ -631,17 +121,6 @@ describe('OrcaRuntimeService', () => { ) }) - it('does not republish a removed worktree’s client tabs to a same-id recreate', async () => { - const runtime = createRuntime() - const host = attachClientBrowserHost(runtime) - await publishClientHostedPage(runtime, host, 'page-removed', TEST_WORKTREE_ID) - - runtime['removeWorktreeMetadataAndHistory'](store as never, TEST_WORKTREE_ID) - - expect(runtime['buildHeadlessMobileSessionBrowserTabs'](TEST_WORKTREE_ID)).toEqual([]) - expect((await runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)).tabs).toEqual([]) - }) - it('drops a client page record even when its close command cannot be issued', async () => { const runtime = createRuntime() const host = attachClientBrowserHost(runtime)