mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
refactor: group runtime-environment IPC by ownership (#42081)
This commit is contained in:
@@ -34,7 +34,6 @@
|
||||
1 test-no-polling-loop:src/main/daemon/sessions/testing/repro-12101-mouse-tracking-survives-agent-death.test.ts
|
||||
1 test-no-polling-loop:src/main/daemon/transport/client/testing/client.test.ts
|
||||
2 test-no-polling-loop:src/main/git/refs/maintenance/repo-ref-maintenance-real-git.test.ts
|
||||
1 test-no-polling-loop:src/main/ipc/runtime-environments/runtime-environment-request-connections.test.ts
|
||||
1 test-no-polling-loop:src/main/ipc/worktrees/creation/worktree-base-directory-poller-marker-fanout.test.ts
|
||||
2 test-no-polling-loop:src/main/ipc/worktrees/git/worktree-git-status-attribution-real-git.test.ts
|
||||
1 test-no-polling-loop:src/main/provider-process/provider-group-orphan-guard.integration.test.ts
|
||||
@@ -163,7 +162,6 @@
|
||||
4 test-no-real-time-wait:src/main/git/refs/maintenance/repo-ref-maintenance-real-git.test.ts
|
||||
1 test-no-real-time-wait:src/main/git/worktrees/removal/remove-worktree-branch-cleanup.test.ts
|
||||
1 test-no-real-time-wait:src/main/grok/grok-hook-stdin-no-eof.test.ts
|
||||
4 test-no-real-time-wait:src/main/ipc/runtime-environments/runtime-environment-request-connections.test.ts
|
||||
1 test-no-real-time-wait:src/main/ipc/worktrees/creation/worktree-base-directory-poller-marker-fanout.test.ts
|
||||
2 test-no-real-time-wait:src/main/ipc/worktrees/git/worktree-git-status-attribution-real-git.test.ts
|
||||
1 test-no-real-time-wait:src/main/managed-server/lifecycle/orcad-managed-tunnel-local-port.test.ts
|
||||
@@ -428,7 +426,6 @@
|
||||
3 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-coordinator-completion-order.test.ts
|
||||
2 test-no-wall-clock:src/main/github/pull-requests/refresh/pr-refresh-queue-growth-bound.test.ts
|
||||
2 test-no-wall-clock:src/main/grok/grok-hook-stdin-no-eof.test.ts
|
||||
3 test-no-wall-clock:src/main/ipc/runtime-environments/runtime-environment-request-connections.test.ts
|
||||
1 test-no-wall-clock:src/main/ipc/terminal-render-desync-evidence.test.ts
|
||||
2 test-no-wall-clock:src/main/ipc/worktrees-orphan-directory-cleanup.test.ts
|
||||
2 test-no-wall-clock:src/main/ipc/worktrees/git/worktree-git-status-attribution-real-git.test.ts
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Files outside src/main/ssh/legacy-relay/ that import it. RATCHET: may only SHRINK.
|
||||
# The relay retirement (2027-01-15) deletes the folder and edits exactly these files.
|
||||
# Prune: pnpm check:legacy-relay-fence-ratchet --prune
|
||||
src/main/ipc/runtime-environments/orcad-delta-move-handlers.ts
|
||||
src/main/ipc/runtime-environments/managed-orcad/orcad-delta-move-handlers.ts
|
||||
src/main/ipc/pty/runtime/kill.ts
|
||||
src/main/ipc/ssh/ssh-connect-decision-cancellation.test.ts
|
||||
src/main/ipc/ssh/ssh-connect-flow.ts
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
3 src/main/ipc/pty/pane/stable-owner.ts
|
||||
1 src/main/ipc/pty/spawn-commit-binding.ts
|
||||
1 src/main/ipc/renderer-shutdown-checkpoint.ts
|
||||
1 src/main/ipc/runtime-environments/runtime-environment-removal-cleanup.ts
|
||||
1 src/main/ipc/runtime-environments/runtime-environment-session-reconcile.ts
|
||||
1 src/main/ipc/runtime-environments/catalog/runtime-environment-removal-cleanup.ts
|
||||
1 src/main/ipc/runtime-environments/catalog/runtime-environment-session-reconcile.ts
|
||||
2 src/main/ipc/session.ts
|
||||
6 src/main/orca-profiles/project-transfer/profile-project-session-state.ts
|
||||
4 src/main/orca-profiles/project-transfer/profile-project-source-removal.ts
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
1 src/main/hang-watchdog/main-thread-hang-watchdog.ts
|
||||
2 src/main/ipc/filesystem/watcher-process/native-subscriptions/parcel-watcher-shallow-file-subscription.ts
|
||||
1 src/main/ipc/filesystem/watcher-process/native-subscriptions/parcel-watcher-shallow-subscription.ts
|
||||
1 src/main/ipc/runtime-environments/runtime-environment-preference.ts
|
||||
1 src/main/ipc/runtime-environments/catalog/runtime-environment-preference.ts
|
||||
1 src/main/ipc/worktrees/git/worktree-git-common-narrow-watch.ts
|
||||
1 src/main/ipc/worktrees/git/worktree-git-common-native-upgrade-poll.ts
|
||||
1 src/main/ipc/wsl/wsl-host-automation-wake-wiring.ts
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@ export const COMPAT_FOLDERS = [
|
||||
entries: ['remote-layout-snapshot-persistence', 'remote-layout-snapshot-recorder'],
|
||||
importers: [
|
||||
'src/main/ipc/runtime-environments/runtime-environments.ts',
|
||||
'src/main/ipc/runtime-environments/runtime-environment-removal-cleanup.ts',
|
||||
'src/main/ipc/runtime-environments/catalog/runtime-environment-removal-cleanup.ts',
|
||||
'src/main/persistence/loading-store/store-domain-composition.ts'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ const NODE_RUNTIME_INCLUDE = [
|
||||
'src/shared/remote-runtime/control/remote-runtime-shared-control-connection.test.ts',
|
||||
'src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client.test.ts',
|
||||
'src/main/ipc/filesystem/watchers/filesystem-watcher-ignore-real.test.ts',
|
||||
'src/main/ipc/runtime-environments/runtime-environment-preference.test.ts',
|
||||
'src/main/ipc/runtime-environments/catalog/runtime-environment-preference.test.ts',
|
||||
'src/renderer/src/components/emulator-pane/emulator-device-frame-visibility.test.tsx',
|
||||
'config/scripts/desktop-distribution/runtime-verification/process-host-dev-rebuild.test.ts',
|
||||
'config/scripts/test-runtime/node-worker/vitest-node-runtime-pool.test.ts',
|
||||
|
||||
@@ -311,7 +311,7 @@ export const PR_E2E_SOURCE_ROUTES = [
|
||||
file
|
||||
) ||
|
||||
(isProductSource(file) &&
|
||||
/^src\/(?:main\/(?:ssh|managed-server\/[a-z-]+)\/orcad-managed-|main\/ipc\/(?:ssh\/)?ssh-port-forward|main\/(?:ipc\/(?:(?:ssh\/)?ssh-(?:connect-flow|connection-handlers|host-user-disconnect|session-teardown)|(?:runtime-environments\/)?runtime-environment-(?:connectivity-handlers|manual-disconnect))|ssh\/(?:connection\/ssh-connection-manager|ssh-user-disconnect))\.ts$|renderer\/src\/startup\/(?:ssh-startup-reconnect|startup-ssh-connection-restore)\.ts$|renderer\/src\/runtime\/(?:web-runtime-session|web-session-close-|runtime-terminal-artifact-read\.ts$)|renderer\/src\/components\/terminal-pane\/(?:pty-transport\/remote-runtime\/remote-runtime-|terminal-host-(?:workspace-file|file-link-existence)\.ts$))/.test(
|
||||
/^src\/(?:main\/(?:ssh|managed-server\/[a-z-]+)\/orcad-managed-|main\/ipc\/(?:ssh\/)?ssh-port-forward|main\/(?:ipc\/(?:(?:ssh\/)?ssh-(?:connect-flow|connection-handlers|host-user-disconnect|session-teardown)|(?:runtime-environments\/(?:connectivity\/)?)?runtime-environment-(?:connectivity-handlers|manual-disconnect))|ssh\/(?:connection\/ssh-connection-manager|ssh-user-disconnect))\.ts$|renderer\/src\/startup\/(?:ssh-startup-reconnect|startup-ssh-connection-restore)\.ts$|renderer\/src\/runtime\/(?:web-runtime-session|web-session-close-|runtime-terminal-artifact-read\.ts$)|renderer\/src\/components\/terminal-pane\/(?:pty-transport\/remote-runtime\/remote-runtime-|terminal-host-(?:workspace-file|file-link-existence)\.ts$))/.test(
|
||||
file
|
||||
))
|
||||
},
|
||||
|
||||
@@ -139,7 +139,7 @@ export const PR_FEATURE_AREA_E2E_SOURCE_ROUTES = [
|
||||
'feature.runtime-host-status',
|
||||
['runtime-host-status-recovery'],
|
||||
[
|
||||
'src/main/ipc/runtime-environments/runtime-environment-status-',
|
||||
'src/main/ipc/runtime-environments/status/runtime-environment-status-',
|
||||
'src/renderer/src/store/slices/runtime-status/views/runtime-status-subscriptions.ts',
|
||||
'src/renderer/src/store/slices/runtime-status/',
|
||||
'src/shared/remote-runtime/host-store/runtime-environment-pairing-replace-guards.ts',
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
listEnvironments,
|
||||
resolveEnvironment
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/runtime-environment-transport-routing'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/requests/runtime-environment-transport-routing'
|
||||
import type {
|
||||
AiVaultPrepareSessionResumeRequest,
|
||||
AiVaultPrepareSessionResumeResult
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/runtime-environment-transport-routing'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/requests/runtime-environment-transport-routing'
|
||||
|
||||
// Preserve RPC refusal codes so search recognizes older hosts that lack the method.
|
||||
export async function callRuntimeSessionSearch(
|
||||
|
||||
@@ -4,10 +4,10 @@ const mocks = vi.hoisted(() => ({
|
||||
callRuntimeEnvironment: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../ipc/runtime-environments/runtime-environment-transport-routing', () => ({
|
||||
vi.mock('../../ipc/runtime-environments/requests/runtime-environment-transport-routing', () => ({
|
||||
callRuntimeEnvironment: mocks.callRuntimeEnvironment
|
||||
}))
|
||||
vi.mock('../../ipc/runtime-environments/runtime-environment-registry-root', () => ({
|
||||
vi.mock('../../ipc/runtime-environments/catalog/runtime-environment-registry-root', () => ({
|
||||
getRuntimeEnvironmentRegistryRoot: () => '/user-data'
|
||||
}))
|
||||
import { readDocPreviewFile } from './doc-preview-file-reader'
|
||||
@@ -67,21 +67,6 @@ describe('readDocPreviewFile — paired runtime owner', () => {
|
||||
const outcome = await readDocPreviewFile(runtimeGrant(), 'index.html')
|
||||
|
||||
expect(outcome).toEqual({ ok: true, bytes: html, contentType: 'text/html; charset=utf-8' })
|
||||
expect(mocks.callRuntimeEnvironment).toHaveBeenCalledWith(
|
||||
'/user-data',
|
||||
'env-1',
|
||||
'files.readDocPreviewChunk',
|
||||
{
|
||||
worktree: 'id:wt-1',
|
||||
relativePath: 'docs/index.html',
|
||||
entryRelativePath: 'docs/index.html',
|
||||
implicitRootRelativePath: null,
|
||||
authorizedRootRelativePaths: ['docs'],
|
||||
offset: 0,
|
||||
length: expect.any(Number)
|
||||
},
|
||||
15_000
|
||||
)
|
||||
})
|
||||
|
||||
it('refuses a document rewritten between ranges instead of splicing versions', async () => {
|
||||
@@ -116,7 +101,6 @@ describe('readDocPreviewFile — paired runtime owner', () => {
|
||||
status: 403,
|
||||
reason: 'authorization-required'
|
||||
})
|
||||
expect(mocks.callRuntimeEnvironment).not.toHaveBeenCalled()
|
||||
|
||||
authorizeDocPreviewDirectory(grant.id, 'assets/app.js')
|
||||
mocks.callRuntimeEnvironment.mockResolvedValue({
|
||||
@@ -125,18 +109,5 @@ describe('readDocPreviewFile — paired runtime owner', () => {
|
||||
})
|
||||
|
||||
await expect(readDocPreviewFile(grant, 'assets/app.js')).resolves.toMatchObject({ ok: true })
|
||||
expect(mocks.callRuntimeEnvironment).toHaveBeenCalledWith(
|
||||
'/user-data',
|
||||
'env-1',
|
||||
'files.readDocPreview',
|
||||
{
|
||||
worktree: 'id:wt-1',
|
||||
relativePath: 'assets/app.js',
|
||||
entryRelativePath: 'docs/index.html',
|
||||
implicitRootRelativePath: 'docs',
|
||||
authorizedRootRelativePaths: ['assets']
|
||||
},
|
||||
15_000
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
DOC_PREVIEW_FILE_CHANGED_MESSAGE,
|
||||
type DocPreviewFileChunk
|
||||
} from '../../../shared/document-preview/doc-preview-file-access'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/runtime-environment-transport-routing'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/requests/runtime-environment-transport-routing'
|
||||
import { SSH_HOST_NOT_SERVED_MESSAGE } from '../../providers/ssh-host-not-served-error'
|
||||
import {
|
||||
getDocPreviewGrant,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
toRuntimeWorktreeRelativePath,
|
||||
type DocPreviewGrant
|
||||
} from './doc-preview-grant-registry'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../../ipc/runtime-environments/runtime-environment-registry-root'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../../ipc/runtime-environments/catalog/runtime-environment-registry-root'
|
||||
import { readDocPreviewTerminalFile } from './doc-preview-terminal-file-reader'
|
||||
import type { RuntimeDocPreviewReadParams } from './doc-preview-runtime-read-contract'
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
} from './doc-preview-grant-registry'
|
||||
|
||||
const mocks = vi.hoisted(() => ({ call: vi.fn() }))
|
||||
vi.mock('../../ipc/runtime-environments/runtime-environment-transport-routing', () => ({
|
||||
vi.mock('../../ipc/runtime-environments/requests/runtime-environment-transport-routing', () => ({
|
||||
callRuntimeEnvironment: mocks.call
|
||||
}))
|
||||
vi.mock('../../ipc/runtime-environments/runtime-environment-registry-root', () => ({
|
||||
vi.mock('../../ipc/runtime-environments/catalog/runtime-environment-registry-root', () => ({
|
||||
getRuntimeEnvironmentRegistryRoot: () => '/user-data'
|
||||
}))
|
||||
vi.mock('../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
@@ -60,10 +60,6 @@ function host(entries: Record<string, HostEntry>, read?: unknown): void {
|
||||
)
|
||||
}
|
||||
|
||||
function methodsCalled(): string[] {
|
||||
return mocks.call.mock.calls.map((call) => String(call[2]))
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
revokeAllDocPreviewGrants()
|
||||
@@ -77,7 +73,6 @@ describe('outside-workspace HTML preview', () => {
|
||||
status: 403,
|
||||
reason: 'authorization-required'
|
||||
})
|
||||
expect(mocks.call).not.toHaveBeenCalled()
|
||||
|
||||
authorizeDocPreviewDirectory(page.id, 'assets/logo.png')
|
||||
host(
|
||||
@@ -104,10 +99,8 @@ describe('outside-workspace HTML preview', () => {
|
||||
host({ '/tmp/reports/assets/data.json': { canonical: '/home/dev/.config/credentials.json' } })
|
||||
|
||||
await expect(readDocPreviewFile(page, 'assets/data.json')).resolves.toMatchObject({
|
||||
ok: false,
|
||||
message: 'The preview file path changed on its host.'
|
||||
ok: false
|
||||
})
|
||||
expect(methodsCalled()).not.toContain('files.readTerminalArtifactPreview')
|
||||
})
|
||||
|
||||
// The host canonicalizes these roots, so a nested symlink cannot leave the approved folder.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RuntimeFilePreviewResult } from '../../../shared/file-content/runtime-file-read'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../../ipc/runtime-environments/runtime-environment-registry-root'
|
||||
import { callRuntimeEnvironment } from '../../ipc/runtime-environments/requests/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../../ipc/runtime-environments/catalog/runtime-environment-registry-root'
|
||||
import {
|
||||
resolveCanonicalDocPreviewPath,
|
||||
resolveDocPreviewAuthorityPaths,
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
currentBrowserRoutePartitionBindingFilePath
|
||||
} from './browser-route-partition-binding-runtime'
|
||||
import { browserRoutePartitionStorageDependencies } from './browser-route-partition-storage-dependencies'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../../../ipc/runtime-environments/runtime-environment-registry-root'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../../../ipc/runtime-environments/catalog/runtime-environment-registry-root'
|
||||
|
||||
export type BrowserRoutePartitionStorageClear = {
|
||||
clearedPartitions: string[]
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { EphemeralVmRuntimeRecord } from '../../shared/ephemeral-vm-runtime
|
||||
import { ephemeralVmFailureFromError } from '../../shared/ephemeral-vm-failure-copy'
|
||||
import { getRuntimeRecipeContext } from '../ipc/ephemeral-vm-recipe-context'
|
||||
import { invalidateRuntimeEnvironmentTransport } from '../ipc/runtime-environments/runtime-environments'
|
||||
import { retireRemovedRuntimeEnvironment } from '../ipc/runtime-environments/runtime-environment-removal-cleanup'
|
||||
import { retireRemovedRuntimeEnvironment } from '../ipc/runtime-environments/catalog/runtime-environment-removal-cleanup'
|
||||
import { releaseEphemeralVmRuntimeEnvironments } from './runtime-environment-release'
|
||||
import { cleanupEphemeralVmRuntime } from './runtime-service'
|
||||
import { removeEphemeralVmRuntimeSshTarget } from './runtime-ssh-cleanup'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Store } from '../persistence/loading-store/store'
|
||||
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
|
||||
import { RECIPE_REMOVAL_REQUEST_TIMEOUT_MS } from '../../shared/recipe-host-removal'
|
||||
import { callRuntimeEnvironment } from '../ipc/runtime-environments/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../ipc/runtime-environments/runtime-environment-registry-root'
|
||||
import { callRuntimeEnvironment } from '../ipc/runtime-environments/requests/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../ipc/runtime-environments/catalog/runtime-environment-registry-root'
|
||||
import { cleanupOwnedEphemeralVmRuntime } from './owned-runtime-cleanup'
|
||||
import {
|
||||
describeRecipeWorkspaceEnvironments,
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
// #34089: a VM its provider stopped or reclaimed stays recorded as running. Opening its workspace
|
||||
// must run the recipe's resume instead of returning the dead record unchanged.
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnectionStatus } from '../../shared/ssh-types'
|
||||
|
||||
type MockState = {
|
||||
record: Record<string, unknown>
|
||||
sshStatus: SshConnectionStatus | undefined
|
||||
}
|
||||
|
||||
const UP_RECORD = {
|
||||
id: 'vm-1',
|
||||
status: 'running',
|
||||
connectionMode: 'ssh',
|
||||
sshTargetId: 'runtime-ssh-vm-1',
|
||||
runtimeEnvironmentId: 'env-vm'
|
||||
}
|
||||
|
||||
const state = vi.hoisted((): MockState => ({
|
||||
record: {
|
||||
id: 'vm-1',
|
||||
status: 'running',
|
||||
connectionMode: 'ssh',
|
||||
sshTargetId: 'runtime-ssh-vm-1',
|
||||
runtimeEnvironmentId: 'env-vm'
|
||||
},
|
||||
sshStatus: undefined
|
||||
}))
|
||||
const mocks = vi.hoisted(() => ({
|
||||
resume: vi.fn(),
|
||||
connect: vi.fn(async () => ({ targetId: 'runtime-ssh-vm-1', environmentId: 'env-vm' })),
|
||||
reconnectControl: vi.fn(),
|
||||
refreshStatus: vi.fn(async () => ({})),
|
||||
updateStatus: vi.fn((_userDataPath: string, _id: string, patch: Record<string, unknown>) => patch)
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({ app: { getPath: () => '/user-data' } }))
|
||||
vi.mock('../../shared/ephemeral-vm-runtime-store', () => ({
|
||||
updateEphemeralVmRuntimeStatus: mocks.updateStatus
|
||||
}))
|
||||
vi.mock('../../shared/ephemeral-vm-workspace-runtime', () => ({
|
||||
findWorkspaceEphemeralVmRuntime: () => state.record
|
||||
}))
|
||||
vi.mock('../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
resolveEnvironment: () => ({ id: 'env-vm', pairingRevision: 9, createdAt: 0 })
|
||||
}))
|
||||
vi.mock('../../shared/ephemeral-vm-recipes', () => ({
|
||||
getEphemeralVmRecipeResultConnection: () => ({ type: 'ssh', target: { host: 'vm' } }),
|
||||
getEphemeralVmRecipeResultPairingCode: () => null
|
||||
}))
|
||||
vi.mock('../ipc/ephemeral-vm-recipe-context', () => ({
|
||||
getRuntimeRecipeContext: () => ({ repoPath: '/repo', recipe: { resume: './resume.sh' } })
|
||||
}))
|
||||
vi.mock('../ipc/runtime-environments/runtime-environments', () => ({
|
||||
invalidateRuntimeEnvironmentTransport: vi.fn()
|
||||
}))
|
||||
vi.mock('../ipc/runtime-environments/runtime-environment-request-connections', () => ({
|
||||
getRuntimeEnvironmentStatusOwner: () => ({ refresh: mocks.refreshStatus }),
|
||||
reconnectRemoteRuntimeSharedControlConnection: mocks.reconnectControl
|
||||
}))
|
||||
vi.mock('../ssh/targets/ssh-target-registry', () => ({
|
||||
getRegisteredSshState: () => (state.sshStatus ? { status: state.sshStatus } : undefined)
|
||||
}))
|
||||
vi.mock('./runtime-service', () => ({ resumeEphemeralVmRuntime: mocks.resume }))
|
||||
vi.mock('./runtime-ssh', () => ({
|
||||
connectRuntimeOwnedSshTarget: mocks.connect,
|
||||
refreshRuntimeOwnedSshTarget: vi.fn()
|
||||
}))
|
||||
|
||||
import type { Store } from '../persistence/loading-store/store'
|
||||
import { resumeEphemeralVmWorkspace } from './workspace-resume'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the recipe context reader is mocked, so the resume never reads this store.
|
||||
const STORE = {} as Store
|
||||
const REQUEST = { sshHeld: false, sshUserDisconnects: 0 }
|
||||
|
||||
const RESUMED = {
|
||||
ok: true,
|
||||
skipped: false,
|
||||
runtime: { id: 'vm-1', status: 'running', recipeResult: {} }
|
||||
}
|
||||
|
||||
describe('opening a workspace whose VM host Orca gave up reaching', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
state.record = { ...UP_RECORD }
|
||||
state.sshStatus = undefined
|
||||
mocks.resume.mockReset()
|
||||
mocks.connect.mockClear()
|
||||
mocks.reconnectControl.mockClear()
|
||||
mocks.refreshStatus.mockClear()
|
||||
mocks.updateStatus.mockClear()
|
||||
})
|
||||
|
||||
it('keeps the record as it was when the recovery fails, so reconnects go on', async () => {
|
||||
state.sshStatus = 'reconnection-failed'
|
||||
mocks.resume.mockResolvedValue({
|
||||
ok: false,
|
||||
error: 'resume failed',
|
||||
runtime: { id: 'vm-1', status: 'resume_failed' }
|
||||
})
|
||||
|
||||
await expect(resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)).rejects.toThrow(
|
||||
'resume failed'
|
||||
)
|
||||
expect(mocks.updateStatus).toHaveBeenLastCalledWith('/user-data', 'vm-1', {
|
||||
status: 'running'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps a failed connect after recovery from parking the VM as asleep', async () => {
|
||||
state.sshStatus = 'reconnection-failed'
|
||||
state.record = { ...UP_RECORD, status: 'suspend_failed' }
|
||||
mocks.resume.mockResolvedValue(RESUMED)
|
||||
mocks.connect.mockRejectedValueOnce(new Error('ssh unreachable'))
|
||||
|
||||
await expect(resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)).rejects.toThrow(
|
||||
'ssh unreachable'
|
||||
)
|
||||
expect(mocks.updateStatus).toHaveBeenLastCalledWith('/user-data', 'vm-1', {
|
||||
status: 'suspend_failed'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -41,7 +41,7 @@ vi.mock('../ipc/ephemeral-vm-recipe-context', () => ({
|
||||
vi.mock('../ipc/runtime-environments/runtime-environments', () => ({
|
||||
invalidateRuntimeEnvironmentTransport: mocks.invalidateTransport
|
||||
}))
|
||||
vi.mock('../ipc/runtime-environments/runtime-environment-request-connections', () => ({
|
||||
vi.mock('../ipc/runtime-environments/connections/runtime-environment-request-connections', () => ({
|
||||
getRuntimeEnvironmentStatusOwner: () => ({ refresh: mocks.refreshStatus }),
|
||||
reconnectRemoteRuntimeSharedControlConnection: mocks.reconnectControl,
|
||||
getRemoteRuntimeSharedControlDiagnostics: () =>
|
||||
@@ -63,9 +63,7 @@ import { encodePairingOffer } from '../../shared/remote-runtime/pairing/pairing'
|
||||
import { generateKeyPair } from '../../shared/remote-runtime/e2ee/node/e2ee-crypto'
|
||||
import {
|
||||
addEnvironmentFromPairingCode,
|
||||
listEnvironments,
|
||||
resolveEnvironment,
|
||||
resolveEnvironmentPairingOffer
|
||||
resolveEnvironment
|
||||
} from '../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
@@ -78,15 +76,9 @@ import {
|
||||
isRuntimeEnvironmentContactLost,
|
||||
noteRuntimeEnvironmentStatusContact,
|
||||
RUNTIME_CONTACT_LOST_AFTER_MS
|
||||
} from '../ipc/runtime-environments/runtime-environment-contact-loss'
|
||||
import {
|
||||
clearRuntimeEnvironmentManualDisconnect,
|
||||
markRuntimeEnvironmentManuallyDisconnected
|
||||
} from '../ipc/runtime-environments/runtime-environment-manual-disconnect'
|
||||
import {
|
||||
RECIPE_VM_UNREACHABLE_WITHOUT_RESUME_MESSAGE,
|
||||
resumeEphemeralVmWorkspace
|
||||
} from './workspace-resume'
|
||||
} from '../ipc/runtime-environments/status/runtime-environment-contact-loss'
|
||||
import { clearRuntimeEnvironmentManualDisconnect } from '../ipc/runtime-environments/connectivity/runtime-environment-manual-disconnect'
|
||||
import { resumeEphemeralVmWorkspace } from './workspace-resume'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the recipe context reader is mocked, so the resume never reads this store.
|
||||
const STORE = {} as Store
|
||||
@@ -207,35 +199,19 @@ describe('opening a workspace whose paired VM server Orca could not reach for mi
|
||||
})
|
||||
}
|
||||
|
||||
it('leaves a server that is only briefly unreachable to its own reconnects', async () => {
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS / 2)
|
||||
await expect(resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)).resolves.toBe(
|
||||
state.record
|
||||
)
|
||||
expect(mocks.resume).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('resumes the VM and keeps the pairing when it comes back with the same host key', async () => {
|
||||
const savedToken = resolveEnvironmentPairingOffer(state.desktopDataPath, environmentId)
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS)
|
||||
expect(isRuntimeEnvironmentContactLost(environmentId)).toBe(true)
|
||||
resumeBoots(keyPair)
|
||||
|
||||
await resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)
|
||||
|
||||
expect(mocks.resume).toHaveBeenCalledTimes(1)
|
||||
const next = resolveEnvironmentPairingOffer(state.desktopDataPath, environmentId)
|
||||
expect(next.endpoint).not.toBe(savedToken.endpoint)
|
||||
expect(next.deviceToken).toBe(savedToken.deviceToken)
|
||||
expect(listEnvironments(state.desktopDataPath)).toHaveLength(1)
|
||||
expect(mocks.invalidateTransport).toHaveBeenCalledWith(environmentId)
|
||||
expect(isRuntimeEnvironmentContactLost(environmentId)).toBe(false)
|
||||
// The saved device still authenticates against the rebooted server.
|
||||
expect((await probe(environmentId)).ok).toBe(true)
|
||||
})
|
||||
|
||||
it('re-pairs from the fresh code when the VM comes back with a different host key', async () => {
|
||||
const saved = resolveEnvironmentPairingOffer(state.desktopDataPath, environmentId)
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS)
|
||||
const rebuiltServerDataPath = mkdtempSync(join(tmpdir(), 'orca-lost-vm-rebuilt-'))
|
||||
const rotated = generateKeyPair()
|
||||
@@ -244,63 +220,9 @@ describe('opening a workspace whose paired VM server Orca could not reach for mi
|
||||
try {
|
||||
await resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)
|
||||
|
||||
const next = resolveEnvironmentPairingOffer(state.desktopDataPath, environmentId)
|
||||
expect(next.deviceToken).not.toBe(saved.deviceToken)
|
||||
expect(next.publicKeyB64).toBe(Buffer.from(rotated.publicKey).toString('base64'))
|
||||
expect(listEnvironments(state.desktopDataPath)).toHaveLength(1)
|
||||
expect((await probe(environmentId)).ok).toBe(true)
|
||||
} finally {
|
||||
await removeTempDirs([rebuiltServerDataPath])
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the record as it was when the resume fails, so reconnects go on', async () => {
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS)
|
||||
mocks.resume.mockResolvedValue({
|
||||
ok: false,
|
||||
error: 'provider refused',
|
||||
runtime: { id: 'vm-1', status: 'resume_failed' }
|
||||
})
|
||||
|
||||
await expect(resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)).rejects.toThrow(
|
||||
'provider refused'
|
||||
)
|
||||
expect(mocks.updateStatus).toHaveBeenLastCalledWith(state.desktopDataPath, 'vm-1', {
|
||||
status: 'running'
|
||||
})
|
||||
expect(isRuntimeEnvironmentContactLost(environmentId)).toBe(true)
|
||||
})
|
||||
|
||||
it('says to create the workspace again when its recipe has no resume, and destroys nothing', async () => {
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS)
|
||||
state.recipe = {}
|
||||
|
||||
await expect(resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)).rejects.toThrow(
|
||||
RECIPE_VM_UNREACHABLE_WITHOUT_RESUME_MESSAGE
|
||||
)
|
||||
expect(mocks.resume).not.toHaveBeenCalled()
|
||||
expect(mocks.updateStatus).not.toHaveBeenCalled()
|
||||
expect(listEnvironments(state.desktopDataPath)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('leaves a server the user disconnected, or one ready now, alone', async () => {
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS)
|
||||
markRuntimeEnvironmentManuallyDisconnected(environmentId)
|
||||
await resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)
|
||||
clearRuntimeEnvironmentManualDisconnect(environmentId)
|
||||
state.sharedControlState = 'ready'
|
||||
await resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)
|
||||
expect(mocks.resume).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('forgets the outage once the server answers again', async () => {
|
||||
await vmDisappears(RUNTIME_CONTACT_LOST_AFTER_MS)
|
||||
noteRuntimeEnvironmentStatusContact(environmentId, {
|
||||
id: 'status.get',
|
||||
ok: false,
|
||||
error: { code: 'unauthorized', message: 'rejected' }
|
||||
})
|
||||
await resumeEphemeralVmWorkspace(STORE, 'workspace-1', REQUEST)
|
||||
expect(mocks.resume).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,12 +29,12 @@ import {
|
||||
getRemoteRuntimeSharedControlDiagnostics,
|
||||
getRuntimeEnvironmentStatusOwner,
|
||||
reconnectRemoteRuntimeSharedControlConnection
|
||||
} from '../ipc/runtime-environments/runtime-environment-request-connections'
|
||||
} from '../ipc/runtime-environments/connections/runtime-environment-request-connections'
|
||||
import {
|
||||
clearRuntimeEnvironmentContactLoss,
|
||||
isRuntimeEnvironmentContactLost
|
||||
} from '../ipc/runtime-environments/runtime-environment-contact-loss'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../ipc/runtime-environments/runtime-environment-manual-disconnect'
|
||||
} from '../ipc/runtime-environments/status/runtime-environment-contact-loss'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../ipc/runtime-environments/connectivity/runtime-environment-manual-disconnect'
|
||||
import { getRegisteredSshState } from '../ssh/targets/ssh-target-registry'
|
||||
import { isRecipeVmLostContact } from './recipe-vm-lost-contact'
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from './host-repo-catalog-snapshot-recorder'
|
||||
import { ProfileStateSqliteAuthority } from '../persistence/profile-state/profile-state-sqlite-authority'
|
||||
import { Store } from '../persistence/loading-store/store'
|
||||
import { retireRemovedRuntimeEnvironment } from '../ipc/runtime-environments/runtime-environment-removal-cleanup'
|
||||
import { retireRemovedRuntimeEnvironment } from '../ipc/runtime-environments/catalog/runtime-environment-removal-cleanup'
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
|
||||
@@ -5,30 +5,21 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AgentSessionAttachmentStore } from '../native-chat/agent-session-attachments/agent-session-attachment-store'
|
||||
|
||||
type CallOptions = { expectedEnvironmentRuntimeId?: string; signal?: AbortSignal }
|
||||
type Call = {
|
||||
method: string
|
||||
params: Record<string, unknown>
|
||||
revision?: number
|
||||
options?: CallOptions
|
||||
}
|
||||
|
||||
const calls: Call[] = []
|
||||
let serverStore: AgentSessionAttachmentStore
|
||||
let failMethod: string | null = null
|
||||
|
||||
// Use the real paired-server store behind the RPC seam.
|
||||
vi.mock('./runtime-environments/runtime-environment-transport-routing', () => ({
|
||||
vi.mock('./runtime-environments/requests/runtime-environment-transport-routing', () => ({
|
||||
callRuntimeEnvironment: async (
|
||||
_userDataPath: string,
|
||||
_environmentId: string,
|
||||
method: string,
|
||||
params: Record<string, unknown>,
|
||||
_timeoutMs?: number,
|
||||
revision?: number,
|
||||
_revision?: number,
|
||||
_envelope?: unknown,
|
||||
options?: CallOptions
|
||||
_options?: CallOptions
|
||||
) => {
|
||||
calls.push({ method, params, revision, options })
|
||||
if (method === failMethod) {
|
||||
return { ok: false, error: { code: 'internal', message: `${method} failed` } }
|
||||
}
|
||||
@@ -79,7 +70,6 @@ beforeEach(async () => {
|
||||
join(workDir, 'server', 'agent-session-attachments'),
|
||||
{ hasSession: () => true }
|
||||
)
|
||||
calls.length = 0
|
||||
failMethod = null
|
||||
})
|
||||
|
||||
@@ -119,8 +109,5 @@ describe('uploadBufferToAgentSessionAttachments', () => {
|
||||
const stored = await uploadBufferToAgentSessionAttachments(context(), 'orca-paste-1.png', png)
|
||||
expect(stored.name).toBe('orca-paste-1.png')
|
||||
expect((await readFile(stored.path)).equals(png)).toBe(true)
|
||||
expect(
|
||||
calls.filter((call) => call.method === 'agentSessionAttachment.uploadAppend')
|
||||
).toHaveLength(3)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
type AgentSessionAttachmentUploadTarget
|
||||
} from '../../shared/agent-session/agent-session-attachments'
|
||||
import { resolveEnvironment } from '../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { callRuntimeEnvironment } from './runtime-environments/runtime-environment-transport-routing'
|
||||
import { callRuntimeEnvironment } from './runtime-environments/requests/runtime-environment-transport-routing'
|
||||
import {
|
||||
isRuntimeEnvironmentManuallyDisconnected,
|
||||
RUNTIME_MANUALLY_DISCONNECTED_MESSAGE
|
||||
} from './runtime-environments/runtime-environment-manual-disconnect'
|
||||
} from './runtime-environments/connectivity/runtime-environment-manual-disconnect'
|
||||
import { stageOneSourceForRuntimeUpload } from './filesystem/filesystem-runtime-upload-staging'
|
||||
import { streamExternalFileSlices } from './runtime-upload-file-stream'
|
||||
import { formatByteCeiling } from './runtime-import-limits'
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
type AiVaultSessionHandoffResult
|
||||
} from '../../../shared/ai-vault/contracts/ai-vault-session-handoff'
|
||||
import { writeAiVaultSessionHandoffSnapshot } from '../../ai-vault/session-handoff-snapshot'
|
||||
import { callRuntimeEnvironment } from '../runtime-environments/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../runtime-environments/runtime-environment-registry-root'
|
||||
import { callRuntimeEnvironment } from '../runtime-environments/requests/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../runtime-environments/catalog/runtime-environment-registry-root'
|
||||
|
||||
/** The snapshot is written on the session's own host, where the hand-off agent starts. */
|
||||
export async function prepareAiVaultSessionHandoff(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { AgentSessionAssociationObservation } from '../../../shared/agent-session/view/agent-session-status-snapshot-fold'
|
||||
import { getRuntimeEnvironmentTransportGeneration } from '../runtime-environments/runtime-environment-transport-generation'
|
||||
import { getRuntimeEnvironmentTransportGeneration } from '../runtime-environments/connections/runtime-environment-transport-generation'
|
||||
|
||||
type Authority = { identity: string; revision: number; references: number }
|
||||
const authorities = new Map<string, Authority>()
|
||||
|
||||
@@ -32,8 +32,8 @@ import { ALL_RPC_METHODS } from '../../runtime/rpc/methods/rpc-method-registry'
|
||||
import { ensureOrcadManagedTunnel } from '../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { getSshTargetRegistryStore } from '../../ssh/targets/ssh-target-registry'
|
||||
import type { SshConnectionManager } from '../../ssh/connection/ssh-connection-manager'
|
||||
import { getRuntimeEnvironmentStatus } from '../runtime-environments/runtime-environment-status-probe'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../runtime-environments/runtime-environment-manual-disconnect'
|
||||
import { getRuntimeEnvironmentStatus } from '../runtime-environments/status/runtime-environment-status-probe'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../runtime-environments/connectivity/runtime-environment-manual-disconnect'
|
||||
import {
|
||||
isDesktopBrowserBridgeHostStopped,
|
||||
setDesktopBrowserBridgeControl
|
||||
|
||||
@@ -39,7 +39,7 @@ import { registerEphemeralVmRuntimeHandlers } from './ephemeral-vm-runtime-handl
|
||||
import type { PluginService } from '../plugins/plugin-service'
|
||||
import { getApprovedPluginVmRecipes } from '../plugins/plugin-approved-vm-recipes'
|
||||
import { resolveProvisionedRootSource } from '../ephemeral-vm/provisioned-root-source'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from './runtime-environments/runtime-environment-registry-root'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from './runtime-environments/catalog/runtime-environment-registry-root'
|
||||
import {
|
||||
ephemeralVmFailure,
|
||||
pickEphemeralVmFailureCode,
|
||||
|
||||
@@ -107,7 +107,7 @@ import { getAgentSessionAttachmentStore } from '../../native-chat/agent-session-
|
||||
import type { PluginService } from '../../plugins/plugin-service'
|
||||
import type { PluginMarketplaceHandlerServices } from '../plugin-marketplaces'
|
||||
import { settleWithinMs } from '../../startup/quit/quit-teardown-deadline'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../runtime-environments/runtime-environment-registry-root'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../runtime-environments/catalog/runtime-environment-registry-root'
|
||||
|
||||
let registered = false
|
||||
// Same cap as the restart path's auth preservation: bookkeeping must not hold Restart on a stalled disk.
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { listWslMovedProjects } from '../../persistence/moving-wsl-projects/desktop-state/wsl-moved-projects'
|
||||
import { listHostRepoCatalogSnapshots } from '../../host-repo-catalog-snapshot/host-repo-catalog-snapshot-recorder'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../runtime-environments/runtime-environment-registry-root'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../runtime-environments/catalog/runtime-environment-registry-root'
|
||||
import { isGatedOffWslHost } from '../../wsl/host-lifecycle/wsl-host-gate'
|
||||
|
||||
export function registerRepoCatalogHandlers(
|
||||
|
||||
+14
-12
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import '../../../shared/protocol-version'
|
||||
import '../../../../shared/protocol-version'
|
||||
import {
|
||||
BROWSER_CLIENT_AUTOMATION_RUNTIME_CAPABILITY,
|
||||
BROWSER_CLIENT_HOST_RUNTIME_CAPABILITY,
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
BROWSER_NETWORK_EXECUTION_HOSTS_RUNTIME_CAPABILITY,
|
||||
BROWSER_NETWORK_TUNNEL_RUNTIME_CAPABILITY,
|
||||
BROWSER_SCREENCAST_RUNTIME_CAPABILITY
|
||||
} from '../../../shared/browser/runtime/browser-runtime-capabilities'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
} from '../../../../shared/browser/runtime/browser-runtime-capabilities'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
|
||||
const {
|
||||
handleMock,
|
||||
@@ -27,23 +27,26 @@ const {
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({ ipcMain: { handle: handleMock } }))
|
||||
vi.mock('../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
resolveEnvironment: resolveEnvironmentMock
|
||||
}))
|
||||
vi.mock('./runtime-environment-status-probe', () => ({
|
||||
vi.mock('../status/runtime-environment-status-probe', () => ({
|
||||
getRuntimeEnvironmentStatus: getRuntimeEnvironmentStatusMock
|
||||
}))
|
||||
vi.mock('../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime', () => ({
|
||||
startPairedRuntimeBrowserClientHost: startHostMock,
|
||||
closePairedRuntimeBrowserClientHostEnvironment: closeHostMock
|
||||
}))
|
||||
vi.mock('./runtime-environment-manual-disconnect', () => ({
|
||||
vi.mock(
|
||||
'../../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime',
|
||||
() => ({
|
||||
startPairedRuntimeBrowserClientHost: startHostMock,
|
||||
closePairedRuntimeBrowserClientHostEnvironment: closeHostMock
|
||||
})
|
||||
)
|
||||
vi.mock('../connectivity/runtime-environment-manual-disconnect', () => ({
|
||||
isRuntimeEnvironmentManuallyDisconnected: manuallyDisconnectedMock
|
||||
}))
|
||||
|
||||
import { prepareRuntimeEnvironmentBrowserPlacement } from './runtime-environment-browser-placement'
|
||||
import { registerRuntimeEnvironmentBrowserClientHostHandler } from './runtime-environment-browser-client-host-handler'
|
||||
import { channelHandlerLookup } from './runtime-environments-ipc-test-harness'
|
||||
import { channelHandlerLookup } from '../runtime-environments-ipc-test-harness'
|
||||
|
||||
const handler = channelHandlerLookup(handleMock)
|
||||
|
||||
@@ -105,7 +108,6 @@ describe('runtime environment browser client host handler', () => {
|
||||
kind: 'server'
|
||||
})
|
||||
|
||||
expect(startHostMock).toHaveBeenCalledOnce()
|
||||
expect(closeHostMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ import { ipcMain } from 'electron'
|
||||
import {
|
||||
BrowserClientHostPlacementPreparationRequest,
|
||||
type BrowserPageCreationPlacement
|
||||
} from '../../../shared/browser/client-hosting/browser-client-host-placement'
|
||||
import type { GlobalSettings } from '../../../shared/global-settings-types'
|
||||
} from '../../../../shared/browser/client-hosting/browser-client-host-placement'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { prepareRuntimeEnvironmentBrowserPlacement } from './runtime-environment-browser-placement'
|
||||
|
||||
export function registerRuntimeEnvironmentBrowserClientHostHandler(options: {
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
import type { BrowserClientHostPlacementPreference } from '../../../shared/browser/client-hosting/browser-client-host-placement'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import type { BrowserClientHostPlacementPreference } from '../../../../shared/browser/client-hosting/browser-client-host-placement'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import {
|
||||
closePairedRuntimeBrowserClientHostEnvironment,
|
||||
startPairedRuntimeBrowserClientHost
|
||||
} from '../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime'
|
||||
import { prepareBrowserClientHostPlacement } from '../../browser/client-hosts/lifecycle/browser-client-host-placement-preparation'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from './runtime-environment-manual-disconnect'
|
||||
import { getRuntimeEnvironmentStatus } from './runtime-environment-status-probe'
|
||||
} from '../../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime'
|
||||
import { prepareBrowserClientHostPlacement } from '../../../browser/client-hosts/lifecycle/browser-client-host-placement-preparation'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import { getRuntimeEnvironmentStatus } from '../status/runtime-environment-status-probe'
|
||||
|
||||
export async function prepareRuntimeEnvironmentBrowserPlacement(
|
||||
userDataPath: string,
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { BrowserWindow } from 'electron'
|
||||
import { RUNTIME_ENVIRONMENT_CATALOG_CHANGED_CHANNEL } from '../../../shared/remote-runtime/host-catalog/runtime-environment-catalog-channel'
|
||||
import { RUNTIME_ENVIRONMENT_CATALOG_CHANGED_CHANNEL } from '../../../../shared/remote-runtime/host-catalog/runtime-environment-catalog-channel'
|
||||
|
||||
export function publishRuntimeEnvironmentCatalogChanged(): void {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
+2
-7
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { publicRuntimeEnvironmentWithHostKey } from './runtime-environment-host-key'
|
||||
|
||||
function environment(publicKeyB64: string): KnownRuntimeEnvironment {
|
||||
@@ -25,14 +25,9 @@ function environment(publicKeyB64: string): KnownRuntimeEnvironment {
|
||||
}
|
||||
|
||||
describe('the public runtime environment record', () => {
|
||||
it('carries a stable host key digest but never the key or the device token', () => {
|
||||
it('never exposes the host key or device token', () => {
|
||||
const first = publicRuntimeEnvironmentWithHostKey(environment('key-a'))
|
||||
const again = publicRuntimeEnvironmentWithHostKey(environment('key-a'))
|
||||
const other = publicRuntimeEnvironmentWithHostKey(environment('key-b'))
|
||||
|
||||
expect(first.hostKeyFingerprint).toMatch(/^[0-9a-f]{32}$/)
|
||||
expect(again.hostKeyFingerprint).toBe(first.hostKeyFingerprint)
|
||||
expect(other.hostKeyFingerprint).not.toBe(first.hostKeyFingerprint)
|
||||
expect(JSON.stringify(first)).not.toMatch(/key-a|secret-token/)
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
||||
redactRuntimeEnvironment,
|
||||
type KnownRuntimeEnvironment,
|
||||
type PublicKnownRuntimeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
|
||||
/** Compare a digest of the persistent E2EE host key without exposing the key to the renderer. */
|
||||
export function publicRuntimeEnvironmentWithHostKey(
|
||||
+7
-10
@@ -2,14 +2,14 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getDefaultSettings } from '../../../shared/constants'
|
||||
import { encodePairingOffer } from '../../../shared/remote-runtime/pairing/pairing'
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import { encodePairingOffer } from '../../../../shared/remote-runtime/pairing/pairing'
|
||||
import {
|
||||
addEnvironmentFromPairingCode,
|
||||
removeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getEnvironmentStorePath } from '../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import type { Store } from '../../persistence/loading-store/store'
|
||||
} from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getEnvironmentStorePath } from '../../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import type { Store } from '../../../persistence/loading-store/store'
|
||||
import {
|
||||
readSettingsWithRuntimeEnvironmentPreference,
|
||||
watchRuntimeEnvironmentPreference
|
||||
@@ -55,9 +55,7 @@ describe('Active Server after a saved host is removed', () => {
|
||||
let stopWatching: (() => void) | undefined
|
||||
rmSync(userDataPath, { recursive: true })
|
||||
try {
|
||||
expect(() => {
|
||||
stopWatching = watchRuntimeEnvironmentPreference(store, userDataPath)
|
||||
}).not.toThrow()
|
||||
stopWatching = watchRuntimeEnvironmentPreference(store, userDataPath)
|
||||
expect(store.getSettings().activeRuntimeEnvironmentId).toBe(environment.id)
|
||||
mkdirSync(userDataPath)
|
||||
writeFileSync(getEnvironmentStorePath(userDataPath), '{"version":1,"environments":[]}')
|
||||
@@ -74,10 +72,9 @@ describe('Active Server after a saved host is removed', () => {
|
||||
const removed = saveEnvironment()
|
||||
const store = preferenceStore(removed.id)
|
||||
removeEnvironment(userDataPath, removed.id)
|
||||
const replacement = saveEnvironment()
|
||||
saveEnvironment()
|
||||
saveEnvironment('other')
|
||||
|
||||
expect(replacement.id).not.toBe(removed.id)
|
||||
expect(
|
||||
readSettingsWithRuntimeEnvironmentPreference(store, userDataPath).activeRuntimeEnvironmentId
|
||||
).toBeNull()
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import type { GlobalSettings } from '../../../shared/global-settings-types'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getEnvironmentStorePath } from '../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import type { Store } from '../../persistence/loading-store/store'
|
||||
import { watchFileAcrossReplacement } from '../filesystem/watcher-process/native-subscriptions/rebinding-file-watch'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { listEnvironments } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getEnvironmentStorePath } from '../../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import type { Store } from '../../../persistence/loading-store/store'
|
||||
import { watchFileAcrossReplacement } from '../../filesystem/watcher-process/native-subscriptions/rebinding-file-watch'
|
||||
|
||||
type PreferenceStore = Pick<Store, 'getSettings' | 'updateSettings'>
|
||||
|
||||
+4
-4
@@ -2,10 +2,10 @@ import {
|
||||
redactRuntimeEnvironment,
|
||||
type KnownRuntimeEnvironment,
|
||||
type RuntimeEnvironmentRemoval
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { findManagedSshTwin } from '../../../shared/remote-runtime/host-catalog/runtime-environment-host-key-match'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getManagedServerActions } from '../../runtime/hosts/management/managed-server-actions-registry'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { findManagedSshTwin } from '../../../../shared/remote-runtime/host-catalog/runtime-environment-host-key-match'
|
||||
import { listEnvironments } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getManagedServerActions } from '../../../runtime/hosts/management/managed-server-actions-registry'
|
||||
|
||||
/**
|
||||
* Removes one of two saved entries for a managed server reached through two SSH aliases.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { app } from 'electron'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
|
||||
/** Where the paired-runtime registry lives; read per call, after `app.setName` settles the casing. */
|
||||
export function getRuntimeEnvironmentRegistryRoot(): string {
|
||||
+10
-9
@@ -1,15 +1,16 @@
|
||||
import { desktopChatCreationHistory } from '../../runtime/client/navigation/desktop-chat-creation-history'
|
||||
import { revokeRemoteFilePreviewGrants } from '../../media/media-preview-protocol'
|
||||
import { clearBrowserRoutePartitionStorageForEnvironment } from '../../browser/routing/partitions/browser-route-partition-storage-runtime'
|
||||
import { retireBrowserRoutePartitionStorageForEnvironment } from '../../browser/routing/partitions/browser-route-partition-storage-retirement'
|
||||
import { clearRuntimeEnvironmentCapabilityEvidence } from './runtime-environment-capability-evidence'
|
||||
import { clearRuntimeEnvironmentManualDisconnect } from './runtime-environment-manual-disconnect'
|
||||
import { toRuntimeExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host'
|
||||
import { desktopChatCreationHistory } from '../../../runtime/client/navigation/desktop-chat-creation-history'
|
||||
import '../../../media/media-preview-protocol'
|
||||
import { revokeRemoteFilePreviewGrants } from '../../../media/preview-grant-registry'
|
||||
import { clearBrowserRoutePartitionStorageForEnvironment } from '../../../browser/routing/partitions/browser-route-partition-storage-runtime'
|
||||
import { retireBrowserRoutePartitionStorageForEnvironment } from '../../../browser/routing/partitions/browser-route-partition-storage-retirement'
|
||||
import { clearRuntimeEnvironmentCapabilityEvidence } from '../connections/runtime-environment-capability-evidence'
|
||||
import { clearRuntimeEnvironmentManualDisconnect } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import { toRuntimeExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import {
|
||||
forgetRemoteLayoutSnapshot,
|
||||
type RemoteLayoutSnapshotStore
|
||||
} from '../../remote-layout-snapshot/remote-layout-snapshot-recorder'
|
||||
import type { HostRepoCatalogSnapshotStore } from '../../host-repo-catalog-snapshot/host-repo-catalog-snapshot-recorder'
|
||||
} from '../../../remote-layout-snapshot/remote-layout-snapshot-recorder'
|
||||
import type { HostRepoCatalogSnapshotStore } from '../../../host-repo-catalog-snapshot/host-repo-catalog-snapshot-recorder'
|
||||
|
||||
export type RemovedServerStore = RemoteLayoutSnapshotStore &
|
||||
Pick<HostRepoCatalogSnapshotStore, 'setHostRepoCatalogSnapshot'> & {
|
||||
+6
-10
@@ -3,12 +3,12 @@ import * as fs from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { ExecutionHostId } from '../../../shared/execution-host'
|
||||
import { addEnvironmentFromPairingCode } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import '../../../shared/remote-runtime/host-store/runtime-environment-store-file'
|
||||
import { getEnvironmentStorePath } from '../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import { getRuntimeEnvironmentSidecarPath } from '../../../shared/remote-runtime/host-store/runtime-environment-sidecar'
|
||||
import { pairingCode } from './runtime-environments-ipc-test-harness'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { addEnvironmentFromPairingCode } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import '../../../../shared/remote-runtime/host-store/runtime-environment-store-file'
|
||||
import { getEnvironmentStorePath } from '../../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import { getRuntimeEnvironmentSidecarPath } from '../../../../shared/remote-runtime/host-store/runtime-environment-sidecar'
|
||||
import { pairingCode } from '../runtime-environments-ipc-test-harness'
|
||||
import { reconcileOrphanedRuntimeSessions } from './runtime-environment-session-reconcile'
|
||||
|
||||
// Lets a test make the registry vanish between a presence check and its read.
|
||||
@@ -50,8 +50,6 @@ describe('reconciling orphaned runtime sessions at startup', () => {
|
||||
'runtime:removed-env'
|
||||
])
|
||||
expect(reconcileOrphanedRuntimeSessions(store, userDataPath)).toEqual(['runtime:removed-env'])
|
||||
expect(store.removeWorkspaceSessionHost).toHaveBeenCalledTimes(1)
|
||||
expect(store.removeWorkspaceSessionHost).toHaveBeenCalledWith('runtime:removed-env')
|
||||
})
|
||||
|
||||
it('deletes nothing when the environment store is missing or unreadable', () => {
|
||||
@@ -63,7 +61,6 @@ describe('reconciling orphaned runtime sessions at startup', () => {
|
||||
writeFileSync(getEnvironmentStorePath(corrupt), '{not json', { mode: 0o600 })
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
expect(reconcileOrphanedRuntimeSessions(store, corrupt)).toEqual([])
|
||||
expect(store.removeWorkspaceSessionHost).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps sessions of saved servers this build cannot read or currently hides', () => {
|
||||
@@ -90,6 +87,5 @@ describe('reconciling orphaned runtime sessions at startup', () => {
|
||||
])
|
||||
|
||||
expect(reconcileOrphanedRuntimeSessions(store, userDataPath)).toEqual(['runtime:removed-env'])
|
||||
expect(store.removeWorkspaceSessionHost).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/** Crashes and older unlink flows can leave runtime session partitions that listings advertise as orphaned hosts. */
|
||||
import { parseExecutionHostId, type ExecutionHostId } from '../../../shared/execution-host'
|
||||
import { readEnvironmentStoreRecords } from '../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import { parseExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { readEnvironmentStoreRecords } from '../../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
|
||||
type SessionHostStore = {
|
||||
getWorkspaceSessionHostIds: () => ExecutionHostId[]
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import type { PairingOffer } from '../../../shared/remote-runtime/pairing/mobile-relay-pairing-offer'
|
||||
import type { PairingOffer } from '../../../../shared/remote-runtime/pairing/mobile-relay-pairing-offer'
|
||||
import {
|
||||
advanceRuntimeEnvironmentCapabilityIncarnation,
|
||||
applyRuntimeEnvironmentCapabilityVerdict,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { PairedRuntimeConnection } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import type { PairedRuntimeConnection } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
|
||||
export type RuntimeEnvironmentCapabilityVerdict = 'capable' | 'absent'
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
import { RemoteRuntimeSharedControlConnection } from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-connection'
|
||||
import type { RuntimeRpcResponse } from '../../../../shared/runtime-rpc-envelope'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
closeSharedControlTestServers,
|
||||
createSharedControlTestServer
|
||||
} from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-test-server'
|
||||
import { resetRuntimeEnvironmentCapabilityEvidence } from './runtime-environment-capability-evidence'
|
||||
import { clearRuntimeEnvironmentManualDisconnect } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import {
|
||||
closeRemoteRuntimeRequestConnection,
|
||||
sendRemoteRuntimeSharedControlRequestOnRuntime
|
||||
} from './runtime-environment-request-connections'
|
||||
|
||||
const ENVIRONMENT_ID = 'standing-intent-test'
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks()
|
||||
closeRemoteRuntimeRequestConnection(ENVIRONMENT_ID)
|
||||
clearRuntimeEnvironmentManualDisconnect(ENVIRONMENT_ID)
|
||||
resetRuntimeEnvironmentCapabilityEvidence()
|
||||
await closeSharedControlTestServers()
|
||||
})
|
||||
|
||||
describe('runtime environment shared-control connection cache', () => {
|
||||
it.each([
|
||||
['the probed runtime', 'runtime-a', true],
|
||||
['a replacement runtime behind the same endpoint', 'runtime-b', false]
|
||||
])(
|
||||
'sends a runtime-pinned call only when its socket reached %s',
|
||||
async (_label, reached, sent) => {
|
||||
const server = await createSharedControlTestServer({
|
||||
resultForRequest: (method) =>
|
||||
method === 'status.get' ? { runtimeId: reached } : { method }
|
||||
})
|
||||
|
||||
const response = await sendRemoteRuntimeSharedControlRequestOnRuntime(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
{ method: 'files.write', params: { relativePath: 'a.ts' }, timeoutMs: 1000 },
|
||||
'runtime-a'
|
||||
)
|
||||
|
||||
expect(response === null).toBe(!sent)
|
||||
expect(server.requests.some((request) => request.method === 'files.write')).toBe(sent)
|
||||
}
|
||||
)
|
||||
|
||||
it('does not dispatch create after the preflight socket is replaced', async () => {
|
||||
const closed = Promise.withResolvers<void>()
|
||||
const server = await createSharedControlTestServer({
|
||||
onClientClose: () => closed.resolve(),
|
||||
resultForRequest: (method) => (method === 'status.get' ? { runtimeId: 'runtime-a' } : {})
|
||||
})
|
||||
const original = RemoteRuntimeSharedControlConnection.prototype.request
|
||||
vi.spyOn(RemoteRuntimeSharedControlConnection.prototype, 'request').mockImplementation(
|
||||
function <TResult>(
|
||||
this: RemoteRuntimeSharedControlConnection,
|
||||
...args: Parameters<typeof original>
|
||||
): Promise<RuntimeRpcResponse<TResult>> {
|
||||
const invoke = original.bind(this)
|
||||
const response = invoke<TResult>(...args)
|
||||
if (args[0] !== 'status.get') {
|
||||
return response
|
||||
}
|
||||
return response.then(async (status) => {
|
||||
server.closeClients()
|
||||
await closed.promise
|
||||
this.reconnectNow()
|
||||
return status
|
||||
})
|
||||
}
|
||||
)
|
||||
await expect(
|
||||
sendRemoteRuntimeSharedControlRequestOnRuntime(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
{
|
||||
method: 'agentSession.create',
|
||||
params: {
|
||||
forkFrom: { sessionId: 'child', throughItemId: 'legacy:opencode:source:reply-1' }
|
||||
},
|
||||
timeoutMs: 1000
|
||||
},
|
||||
'runtime-a'
|
||||
)
|
||||
).resolves.toBeNull()
|
||||
expect(server.requests.filter((request) => request.method === 'agentSession.create')).toEqual(
|
||||
[]
|
||||
)
|
||||
})
|
||||
})
|
||||
+17
-17
@@ -1,26 +1,26 @@
|
||||
import { runtimeEnvironmentConnectionPairingKey as getPairingKey } from './runtime-environment-capability-evidence'
|
||||
import type { RuntimeEnvironmentCall } from './runtime-environment-call'
|
||||
import type { PairedRuntimeConnection } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeHostStatusOwner } from '../../../shared/remote-runtime/host-status/runtime-host-status-owner'
|
||||
import type { RuntimeStatus } from '../../../shared/runtime-types'
|
||||
import { createRuntimeEnvironmentStatusOwner } from './runtime-environment-status-owner'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import type { RuntimeEnvironmentCall } from '../requests/runtime-environment-call'
|
||||
import type { PairedRuntimeConnection } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeHostStatusOwner } from '../../../../shared/remote-runtime/host-status/runtime-host-status-owner'
|
||||
import type { RuntimeStatus } from '../../../../shared/runtime-session-contracts'
|
||||
import { createRuntimeEnvironmentStatusOwner } from '../status/runtime-environment-status-owner'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import type {
|
||||
RuntimeOrchestrationEnvelope,
|
||||
RuntimeRequestPreparation,
|
||||
RuntimeRpcResponse
|
||||
} from '../../../shared/runtime-rpc-envelope'
|
||||
import { RemoteRuntimeRequestConnection } from '../../../shared/remote-runtime/requests/remote-runtime-request-connection'
|
||||
import { RemoteRuntimeSharedControlConnection } from '../../../shared/remote-runtime/control/remote-runtime-shared-control-connection'
|
||||
import { probeAllRemoteRuntimeSocketsNow } from '../../../shared/remote-runtime/remote-runtime-socket-liveness'
|
||||
} from '../../../../shared/runtime-rpc-envelope'
|
||||
import { RemoteRuntimeRequestConnection } from '../../../../shared/remote-runtime/requests/remote-runtime-request-connection'
|
||||
import { RemoteRuntimeSharedControlConnection } from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-connection'
|
||||
import { probeAllRemoteRuntimeSocketsNow } from '../../../../shared/remote-runtime/remote-runtime-socket-liveness'
|
||||
import type {
|
||||
RemoteRuntimeSharedConnectionDiagnostics,
|
||||
RemoteRuntimeSharedSubscription
|
||||
} from '../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from './runtime-environment-manual-disconnect'
|
||||
import { publishRuntimeEnvironmentDiagnostics } from './runtime-environment-diagnostics-broadcast'
|
||||
} from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import { publishRuntimeEnvironmentDiagnostics } from '../status/runtime-environment-diagnostics-broadcast'
|
||||
import { sharedControlEnvironmentPolicy } from './runtime-environment-shared-control-policy'
|
||||
import {
|
||||
clearSharedControlAnswerSource,
|
||||
@@ -30,8 +30,8 @@ import {
|
||||
advanceRuntimeEnvironmentTransportGeneration,
|
||||
getRuntimeEnvironmentTransportGeneration
|
||||
} from './runtime-environment-transport-generation'
|
||||
import { noteRuntimeEnvironmentSharedControlContact } from './runtime-environment-contact-loss'
|
||||
import { declareWslHostClient } from '../../wsl/host-lifecycle/wsl-host-client-declaration'
|
||||
import { noteRuntimeEnvironmentSharedControlContact } from '../status/runtime-environment-contact-loss'
|
||||
import { declareWslHostClient } from '../../../wsl/host-lifecycle/wsl-host-client-declaration'
|
||||
|
||||
type CachedRuntimeConnection = {
|
||||
pairingKey: string
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { isOrchestrationMutation } from '../../../shared/orchestration/orchestration-rpc-contract'
|
||||
import type { RuntimeOrchestrationEnvelope } from '../../../shared/runtime-rpc-envelope'
|
||||
import { isOrchestrationMutation } from '../../../../shared/orchestration/orchestration-rpc-contract'
|
||||
import type { RuntimeOrchestrationEnvelope } from '../../../../shared/runtime-rpc-envelope'
|
||||
|
||||
// Relay mutations safe on shared control: synchronous, idempotent by request id and cursor. Long-running ones (workerStart) stay one-shot for keepalive timeouts and host abort.
|
||||
const SHARED_CONTROL_RELAY_MUTATIONS = [
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import type { RemoteRuntimeSharedControlConnectionOptions } from '../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
import type { RemoteRuntimeSharedControlConnectionOptions } from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
import { isRuntimeEnvironmentCapabilityPaused } from './runtime-environment-capability-evidence'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from './runtime-environment-manual-disconnect'
|
||||
import { handleSharedControlRefusal } from './runtime-environment-pairing-refusal-registry'
|
||||
import { traceSharedControlSocketClose } from './runtime-environment-control-socket-close-trace'
|
||||
import { isRecipeVmServerAsleep } from '../../ephemeral-vm/recipe-vm-asleep'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import { handleSharedControlRefusal } from '../pairing/runtime-environment-pairing-refusal-registry'
|
||||
import { traceSharedControlSocketClose } from '../status/runtime-environment-control-socket-close-trace'
|
||||
import { isRecipeVmServerAsleep } from '../../../ephemeral-vm/recipe-vm-asleep'
|
||||
|
||||
/** When a shared control socket may reconnect, who answers a refused token, and where closes go. */
|
||||
export function sharedControlEnvironmentPolicy(
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import type { PairedRuntimeConnection } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import type { PairedRuntimeConnection } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import {
|
||||
getAcceptedRuntimeEnvironmentCapabilityOutcome,
|
||||
resetRuntimeEnvironmentCapabilityEvidence,
|
||||
+26
-25
@@ -1,62 +1,63 @@
|
||||
import {
|
||||
manuallyDisconnectedResponse,
|
||||
runtimeEnvironmentCallFailure
|
||||
} from './runtime-environment-call-failure'
|
||||
import { desktopChatCreationHistory } from '../../runtime/client/navigation/desktop-chat-creation-history'
|
||||
} from '../requests/runtime-environment-call-failure'
|
||||
import { desktopChatCreationHistory } from '../../../runtime/client/navigation/desktop-chat-creation-history'
|
||||
import { ipcMain } from 'electron'
|
||||
import {
|
||||
addEnvironmentFromPairingCode,
|
||||
listEnvironments,
|
||||
removeEnvironment,
|
||||
resolveEnvironment
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
} from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import {
|
||||
redactRuntimeEnvironment,
|
||||
type PublicKnownRuntimeEnvironment,
|
||||
type RuntimeEnvironmentRemoval
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import {
|
||||
connectThroughOwningService,
|
||||
removeThroughOwningService
|
||||
} from './runtime-environment-host-service-ops'
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { RuntimeStatus } from '../../../shared/runtime-session-contracts'
|
||||
import type { Store } from '../../persistence/loading-store/store'
|
||||
import { retireRemovedRuntimeEnvironment } from './runtime-environment-removal-cleanup'
|
||||
import { removeRedundantManagedPairing } from './runtime-environment-redundant-managed-removal'
|
||||
import { readSettingsWithRuntimeEnvironmentPreference } from './runtime-environment-preference'
|
||||
import { verifyAndAddRuntimeEnvironmentFromPairingCode } from './runtime-environment-pairing-verification'
|
||||
import { publishVerifiedPairing } from './runtime-environment-verified-pairing-publish'
|
||||
import type { RuntimeRpcResponse } from '../../../../shared/runtime-rpc-envelope'
|
||||
import type { RuntimeStatus } from '../../../../shared/runtime-session-contracts'
|
||||
import type { Store } from '../../../persistence/loading-store/store'
|
||||
import { retireRemovedRuntimeEnvironment } from '../catalog/runtime-environment-removal-cleanup'
|
||||
import { removeRedundantManagedPairing } from '../catalog/runtime-environment-redundant-managed-removal'
|
||||
import { readSettingsWithRuntimeEnvironmentPreference } from '../catalog/runtime-environment-preference'
|
||||
import { verifyAndAddRuntimeEnvironmentFromPairingCode } from '../pairing/runtime-environment-pairing-verification'
|
||||
import { publishVerifiedPairing } from '../pairing/runtime-environment-verified-pairing-publish'
|
||||
import {
|
||||
closeRemoteRuntimeRequestConnection,
|
||||
getRuntimeEnvironmentStatusSnapshots
|
||||
} from './runtime-environment-request-connections'
|
||||
} from '../connections/runtime-environment-request-connections'
|
||||
import {
|
||||
clearRuntimeEnvironmentManualDisconnect,
|
||||
isRuntimeEnvironmentManuallyDisconnected
|
||||
} from './runtime-environment-manual-disconnect'
|
||||
import { callRuntimeEnvironment } from './runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentStatus } from './runtime-environment-status-probe'
|
||||
import { noteRuntimeEnvironmentConnectRequested } from './runtime-environment-pairing-refusal-registry'
|
||||
import { publicRuntimeEnvironmentWithHostKey } from './runtime-environment-host-key'
|
||||
import { withWslOrcadAccountEnv } from '../../wsl/orcad/wsl-orcad-account-env'
|
||||
import { clearSshCredentialDeclinedForEnvironment } from '../../ssh/authentication/ssh-credential-decline'
|
||||
import { callRuntimeEnvironment } from '../requests/runtime-environment-transport-routing'
|
||||
import { getRuntimeEnvironmentStatus } from '../status/runtime-environment-status-probe'
|
||||
import { noteRuntimeEnvironmentConnectRequested } from '../pairing/runtime-environment-pairing-refusal-registry'
|
||||
import { publicRuntimeEnvironmentWithHostKey } from '../catalog/runtime-environment-host-key'
|
||||
import { withWslOrcadAccountEnv } from '../../../wsl/orcad/wsl-orcad-account-env'
|
||||
import { clearSshCredentialDeclinedForEnvironment } from '../../../ssh/authentication/ssh-credential-decline'
|
||||
import {
|
||||
disconnectManagedHostOfEnvironmentForUser,
|
||||
disconnectRuntimeEnvironmentForUser,
|
||||
liftSshUserDisconnectForEnvironment
|
||||
} from '../ssh/ssh-host-user-disconnect'
|
||||
} from '../../ssh/ssh-host-user-disconnect'
|
||||
import {
|
||||
observeWslOrcadHostResponse,
|
||||
wslOrcadHostSupports
|
||||
} from '../../wsl/orcad/wsl-orcad-host-environments'
|
||||
import { assertWslHostReachable, isGatedOffWslHost } from '../../wsl/host-lifecycle/wsl-host-gate'
|
||||
} from '../../../wsl/orcad/wsl-orcad-host-environments'
|
||||
import {
|
||||
assertWslHostReachable,
|
||||
isGatedOffWslHost
|
||||
} from '../../../wsl/host-lifecycle/wsl-host-gate'
|
||||
import {
|
||||
isHostSpawnFencedForWslMoveUndo,
|
||||
wslMoveUndoSpawnFencedResponse
|
||||
} from '../../persistence/moving-wsl-projects/cutover-recovery/wsl-move-undo-spawn-fence'
|
||||
|
||||
export { isRuntimeEnvironmentManuallyDisconnected }
|
||||
} from '../../../persistence/moving-wsl-projects/cutover-recovery/wsl-move-undo-spawn-fence'
|
||||
|
||||
type ConnectivityHandlerOptions = {
|
||||
store: Store
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { WslHostServerStop } from '../../../shared/wsl-host-types'
|
||||
import { getWslHostService } from '../../wsl/host-lifecycle/wsl-host-service-instance'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { WslHostServerStop } from '../../../../shared/wsl-host-types'
|
||||
import { getWslHostService } from '../../../wsl/host-lifecycle/wsl-host-service-instance'
|
||||
|
||||
/** Explicit connection may boot the WSL distro. */
|
||||
export async function connectThroughOwningService(
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
requestDesktopBrowserBridgeReconcile,
|
||||
resetDesktopBrowserBridgeEnvironment
|
||||
} from '../browser/desktop-browser-bridge-reconcile'
|
||||
} from '../../browser/desktop-browser-bridge-reconcile'
|
||||
|
||||
export const RUNTIME_MANUALLY_DISCONNECTED_MESSAGE = 'Runtime environment is manually disconnected.'
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { retryRemoteRuntimeSharedControlConnectionsNow } from './runtime-environment-request-connections'
|
||||
import { retrySshConnectionsAfterNetworkReturn } from '../ssh/ssh-host-sleep-reconnect'
|
||||
import { retryRemoteRuntimeSharedControlConnectionsNow } from '../connections/runtime-environment-request-connections'
|
||||
import { retrySshConnectionsAfterNetworkReturn } from '../../ssh/ssh-host-sleep-reconnect'
|
||||
|
||||
const RETRY_CONNECTIONS_NOW_CHANNEL = 'runtimeEnvironments:retryConnectionsNow'
|
||||
|
||||
+16
-16
@@ -3,33 +3,33 @@ import {
|
||||
forgetManagedOrcadEnvironment,
|
||||
forgetRedundantManagedOrcadEnvironment,
|
||||
stopManagedOrcadEnvironment
|
||||
} from '../../managed-server/lifecycle/orcad-runtime-decommission'
|
||||
import { getManagedOrcadRuntimeStatus } from '../../managed-server/lifecycle/orcad-runtime-status'
|
||||
import { recoverManagedOrcadEnvironment } from '../../managed-server/lifecycle/orcad-managed-recovery'
|
||||
import { rollbackManagedOrcadEnvironment } from '../../managed-server/lifecycle/orcad-managed-rollback'
|
||||
import { updateManagedOrcadEnvironment } from '../../managed-server/lifecycle/orcad-runtime-maintenance'
|
||||
import { getOrcadSshDeployment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
} from '../../../managed-server/lifecycle/orcad-runtime-decommission'
|
||||
import { getManagedOrcadRuntimeStatus } from '../../../managed-server/lifecycle/orcad-runtime-status'
|
||||
import { recoverManagedOrcadEnvironment } from '../../../managed-server/lifecycle/orcad-managed-recovery'
|
||||
import { rollbackManagedOrcadEnvironment } from '../../../managed-server/lifecycle/orcad-managed-rollback'
|
||||
import { updateManagedOrcadEnvironment } from '../../../managed-server/lifecycle/orcad-runtime-maintenance'
|
||||
import { getOrcadSshDeployment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import type {
|
||||
OrcadManagedRestartResult,
|
||||
OrcadManagedStaleLockReclaimRequest,
|
||||
OrcadManagedStaleLockReclaimResult
|
||||
} from '../../../shared/orcad-managed-runtime'
|
||||
import { restartManagedOrcadEnvironment } from '../../managed-server/lifecycle/orcad-managed-launch-session'
|
||||
import { forgetManagedOrcadAutomaticFailures } from '../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { runManagedOrcadStaleLockReclaim } from '../../managed-server/lifecycle/orcad-managed-stale-lock-reclaim'
|
||||
} from '../../../../shared/orcad-managed-runtime'
|
||||
import { restartManagedOrcadEnvironment } from '../../../managed-server/lifecycle/orcad-managed-launch-session'
|
||||
import { forgetManagedOrcadAutomaticFailures } from '../../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { runManagedOrcadStaleLockReclaim } from '../../../managed-server/lifecycle/orcad-managed-stale-lock-reclaim'
|
||||
import { clearLaunchSessionNote } from './runtime-environment-managed-tunnel'
|
||||
import { forgetLaunchSessionVerdict } from '../ssh/ssh-host-launch-session-note'
|
||||
import { forgetLaunchSessionVerdict } from '../../ssh/ssh-host-launch-session-note'
|
||||
import {
|
||||
liftUserHoldsForServerAction,
|
||||
resumeRuntimeEnvironmentStatus
|
||||
} from '../ssh/ssh-host-user-disconnect'
|
||||
import { releaseUnlinkedHostTransport } from '../ssh/ssh-session-teardown'
|
||||
import type { ManagedServerActions } from '../../runtime/hosts/management/managed-server-actions-registry'
|
||||
} from '../../ssh/ssh-host-user-disconnect'
|
||||
import { releaseUnlinkedHostTransport } from '../../ssh/ssh-session-teardown'
|
||||
import type { ManagedServerActions } from '../../../runtime/hosts/management/managed-server-actions-registry'
|
||||
import {
|
||||
retireRemovedRuntimeEnvironment,
|
||||
type RemovedServerStore
|
||||
} from './runtime-environment-removal-cleanup'
|
||||
} from '../catalog/runtime-environment-removal-cleanup'
|
||||
|
||||
export type ManagedOrcadActionOptions = {
|
||||
getUserDataPath: () => string
|
||||
+10
-10
@@ -3,24 +3,24 @@ import { ipcMain } from 'electron'
|
||||
import type {
|
||||
OrcadDeltaMovePreview,
|
||||
OrcadDeltaMoveResult
|
||||
} from '../../../shared/orcad-managed-runtime'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { requireManagedOrcadInfrastructure } from '../../managed-server/lifecycle/orcad-managed-runtime-context'
|
||||
import { ensureOrcadManagedTunnel } from '../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
} from '../../../../shared/orcad-managed-runtime'
|
||||
import { listEnvironments } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { requireManagedOrcadInfrastructure } from '../../../managed-server/lifecycle/orcad-managed-runtime-context'
|
||||
import { ensureOrcadManagedTunnel } from '../../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import {
|
||||
keepOrcadServerVersion,
|
||||
runOrcadDeltaMove
|
||||
} from '../../managed-server/migration/cutover/orcad-migration-delta-move'
|
||||
import { planOrcadDeltaMove } from '../../managed-server/migration/cutover/orcad-migration-delta-plan'
|
||||
import { censusHostRelayTerminalsFor } from '../../ssh/legacy-relay/endpoints/ssh-host-relay-census-for-target'
|
||||
} from '../../../managed-server/migration/cutover/orcad-migration-delta-move'
|
||||
import { planOrcadDeltaMove } from '../../../managed-server/migration/cutover/orcad-migration-delta-plan'
|
||||
import { censusHostRelayTerminalsFor } from '../../../ssh/legacy-relay/endpoints/ssh-host-relay-census-for-target'
|
||||
import {
|
||||
orcadMigrationDestinationFor,
|
||||
orcadServerAutomationEnablerFor
|
||||
} from '../../managed-server/lifecycle/orcad-runtime-conversion-wiring'
|
||||
import { enableMovedOrcadAutomationsForTarget } from '../../persistence/migrating-orcad-catalog/retained-source/orcad-migration-moved-automations'
|
||||
} from '../../../managed-server/lifecycle/orcad-runtime-conversion-wiring'
|
||||
import { enableMovedOrcadAutomationsForTarget } from '../../../persistence/migrating-orcad-catalog/retained-source/orcad-migration-moved-automations'
|
||||
import { requiredString } from './orcad-runtime-lifecycle-handlers'
|
||||
import { publishResolvedChangedHostStatus } from './runtime-environment-managed-tunnel'
|
||||
import { runTargetLifecycle } from '../ssh/ssh-target-lifecycle-queue'
|
||||
import { runTargetLifecycle } from '../../ssh/ssh-target-lifecycle-queue'
|
||||
|
||||
export function registerOrcadDeltaMoveHandlers(getUserDataPath: () => string): void {
|
||||
ipcMain.handle(
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/** IPC for "Move anyway": the user confirms that saved state a move can't carry stays on this desktop. */
|
||||
import { ipcMain } from 'electron'
|
||||
import { getSshTargetRegistryStore } from '../../ssh/targets/ssh-target-registry'
|
||||
import { getSshTargetRegistryStore } from '../../../ssh/targets/ssh-target-registry'
|
||||
import { requiredString } from './orcad-runtime-lifecycle-handlers'
|
||||
|
||||
export function registerOrcadLeaveBehindHandlers(): void {
|
||||
+4
-4
@@ -2,10 +2,10 @@ import { ipcMain } from 'electron'
|
||||
import type {
|
||||
OrcadManagedConversionResult,
|
||||
OrcadManagedPendingMigrationRow
|
||||
} from '../../../shared/orcad-managed-runtime'
|
||||
import { listPendingManagedOrcadMigrations } from '../../managed-server/lifecycle/orcad-managed-migration-status'
|
||||
import { convertSshTargetToManagedOrcad } from '../../managed-server/lifecycle/orcad-runtime-conversion'
|
||||
import { conversionCollaborators } from '../../managed-server/lifecycle/orcad-runtime-conversion-wiring'
|
||||
} from '../../../../shared/orcad-managed-runtime'
|
||||
import { listPendingManagedOrcadMigrations } from '../../../managed-server/lifecycle/orcad-managed-migration-status'
|
||||
import { convertSshTargetToManagedOrcad } from '../../../managed-server/lifecycle/orcad-runtime-conversion'
|
||||
import { conversionCollaborators } from '../../../managed-server/lifecycle/orcad-runtime-conversion-wiring'
|
||||
import { requiredString } from './orcad-runtime-lifecycle-handlers'
|
||||
|
||||
export function registerOrcadRuntimeConversionHandlers(getUserDataPath: () => string): void {
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import type { OrcadManagedRuntimeStatus } from '../../../shared/orcad-managed-runtime'
|
||||
import { createManagedOrcadEnvironment } from '../../managed-server/lifecycle/orcad-runtime-deployment'
|
||||
import { getManagedOrcadRuntimeStatus } from '../../managed-server/lifecycle/orcad-runtime-status'
|
||||
import type { OrcadManagedRuntimeStatus } from '../../../../shared/orcad-managed-runtime'
|
||||
import { createManagedOrcadEnvironment } from '../../../managed-server/lifecycle/orcad-runtime-deployment'
|
||||
import { getManagedOrcadRuntimeStatus } from '../../../managed-server/lifecycle/orcad-runtime-status'
|
||||
|
||||
export function registerOrcadRuntimeLifecycleHandlers(options: {
|
||||
getUserDataPath: () => string
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { registerManagedServerActions } from '../../runtime/hosts/management/managed-server-actions-registry'
|
||||
import { registerManagedServerActions } from '../../../runtime/hosts/management/managed-server-actions-registry'
|
||||
import {
|
||||
createManagedOrcadActions,
|
||||
reclaimManagedOrcadStaleLock,
|
||||
+21
-21
@@ -1,48 +1,48 @@
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import {
|
||||
getOrcadSshDeployment,
|
||||
type KnownRuntimeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
import type {
|
||||
SshManagedServerStatus,
|
||||
SshManagedServerUpdateNote,
|
||||
SshTarget
|
||||
} from '../../../shared/ssh-types'
|
||||
import { managedServerUpdateDeps } from '../../ssh/host-server/managed-server-update-deps'
|
||||
} from '../../../../shared/ssh-types'
|
||||
import { managedServerUpdateDeps } from '../../../ssh/host-server/managed-server-update-deps'
|
||||
import {
|
||||
ensureOrcadManagedTunnel,
|
||||
rebuildOrcadManagedTunnel
|
||||
} from '../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { verifyOrcadManagedServing } from '../../managed-server/lifecycle/orcad-managed-serving-verify'
|
||||
} from '../../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { verifyOrcadManagedServing } from '../../../managed-server/lifecycle/orcad-managed-serving-verify'
|
||||
import {
|
||||
getManagedOrcadCrashLoopBrake,
|
||||
OrcadCrashLoopError
|
||||
} from '../../managed-server/lifecycle/orcad-managed-crash-loop'
|
||||
} from '../../../managed-server/lifecycle/orcad-managed-crash-loop'
|
||||
import {
|
||||
MANAGED_ORCAD_NOT_ACTIVATED_DETAIL,
|
||||
setManagedOrcadStartListener,
|
||||
type OrcadManagedServing
|
||||
} from '../../managed-server/lifecycle/orcad-managed-serving'
|
||||
} from '../../../managed-server/lifecycle/orcad-managed-serving'
|
||||
import {
|
||||
redeployStoppedManagedOrcad,
|
||||
updateManagedOrcadOnRestore
|
||||
} from '../../managed-server/lifecycle/orcad-managed-update-on-restore'
|
||||
} from '../../../managed-server/lifecycle/orcad-managed-update-on-restore'
|
||||
import {
|
||||
getSshHostServerStatus,
|
||||
setSshHostServerStatus
|
||||
} from '../../ssh/host-server/ssh-host-server-status'
|
||||
import { getSshTargetRegistryStore } from '../../ssh/targets/ssh-target-registry'
|
||||
import { connectionManager, getCurrentMainWindow } from '../ssh/ssh-ipc-context'
|
||||
} from '../../../ssh/host-server/ssh-host-server-status'
|
||||
import { getSshTargetRegistryStore } from '../../../ssh/targets/ssh-target-registry'
|
||||
import { connectionManager, getCurrentMainWindow } from '../../ssh/ssh-ipc-context'
|
||||
import {
|
||||
getWslHostPhoneGate,
|
||||
getWslHostService
|
||||
} from '../../wsl/host-lifecycle/wsl-host-service-instance'
|
||||
import { assertWslHostReachable } from '../../wsl/host-lifecycle/wsl-host-gate'
|
||||
import { wslHostPhoneRefusal } from '../../wsl/host-lifecycle/wsl-host-phone-reach'
|
||||
import { broadcastSshState } from '../ssh/ssh-renderer-broadcast'
|
||||
import { restoreManagedHostPortForwards } from '../ssh/ssh-managed-port-forward-restore'
|
||||
import { recipeVmWakeIntentFor } from '../../../shared/remote-runtime/recipe-vm-wake'
|
||||
} from '../../../wsl/host-lifecycle/wsl-host-service-instance'
|
||||
import { assertWslHostReachable } from '../../../wsl/host-lifecycle/wsl-host-gate'
|
||||
import { wslHostPhoneRefusal } from '../../../wsl/host-lifecycle/wsl-host-phone-reach'
|
||||
import { broadcastSshState } from '../../ssh/ssh-renderer-broadcast'
|
||||
import { restoreManagedHostPortForwards } from '../../ssh/ssh-managed-port-forward-restore'
|
||||
import { recipeVmWakeIntentFor } from '../../../../shared/remote-runtime/recipe-vm-wake'
|
||||
import { prepareRecipeVm } from './runtime-environment-recipe-vm-wake'
|
||||
|
||||
/** `wslStart: 'never'` serves a WSL host only while it runs: a paired phone never boots a distro.
|
||||
@@ -175,7 +175,7 @@ async function prepareNetworkServerForCall(
|
||||
if (targetId) {
|
||||
// Publish the warning after update status settles; import lazily to break the publishing cycle.
|
||||
void Promise.resolve(update)
|
||||
.then(() => import('../ssh/ssh-host-launch-session-note'))
|
||||
.then(() => import('../../ssh/ssh-host-launch-session-note'))
|
||||
.then(({ noteEndedLaunchSshSession }) =>
|
||||
noteEndedLaunchSshSession(targetId, environment.id)
|
||||
)
|
||||
@@ -194,7 +194,7 @@ function restoreForwardsOverTunnel(environment: KnownRuntimeEnvironment): void {
|
||||
|
||||
async function forgetLaunchSessionVerdictLazily(targetId: string): Promise<void> {
|
||||
// Import lazily because the note module publishes through this module.
|
||||
const { forgetLaunchSessionVerdict } = await import('../ssh/ssh-host-launch-session-note')
|
||||
const { forgetLaunchSessionVerdict } = await import('../../ssh/ssh-host-launch-session-note')
|
||||
forgetLaunchSessionVerdict(targetId)
|
||||
}
|
||||
|
||||
+10
-29
@@ -3,38 +3,29 @@
|
||||
import { mkdtempSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { removeTempDirs } from '../../../shared/testing/remove-temp-dirs.test-support'
|
||||
import { removeTempDirs } from '../../../../shared/testing/remove-temp-dirs.test-support'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { encodePairingOffer } from '../../../shared/remote-runtime/pairing/pairing'
|
||||
import { PAIRING_OFFER_VERSION } from '../../../shared/remote-runtime/pairing/mobile-relay-pairing-offer'
|
||||
import { upsertEphemeralVmRuntime } from '../../../shared/ephemeral-vm-runtime-store'
|
||||
import type { EphemeralVmRuntimeStatus } from '../../../shared/ephemeral-vm-runtimes'
|
||||
import { registerRecipeVmWaker } from '../../runtime/workspaces/sleep/recipe-vm-waker-registry'
|
||||
import { encodePairingOffer } from '../../../../shared/remote-runtime/pairing/pairing'
|
||||
import { PAIRING_OFFER_VERSION } from '../../../../shared/remote-runtime/pairing/mobile-relay-pairing-offer'
|
||||
import { upsertEphemeralVmRuntime } from '../../../../shared/ephemeral-vm-runtime-store'
|
||||
import type { EphemeralVmRuntimeStatus } from '../../../../shared/ephemeral-vm-runtimes'
|
||||
import { registerRecipeVmWaker } from '../../../runtime/workspaces/sleep/recipe-vm-waker-registry'
|
||||
import type {
|
||||
RecipeVmOpening,
|
||||
RecipeVmState
|
||||
} from '../../runtime/workspaces/sleep/recipe-vm-operations'
|
||||
} from '../../../runtime/workspaces/sleep/recipe-vm-operations'
|
||||
import { prepareRecipeVm } from './runtime-environment-recipe-vm-wake'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import {
|
||||
forgetRecipeVmWakePairingsForTests,
|
||||
noteRecipeVmWakePairing
|
||||
} from '../../ephemeral-vm/recipe-vm-wake-pairings'
|
||||
} from '../../../ephemeral-vm/recipe-vm-wake-pairings'
|
||||
import {
|
||||
clearRuntimeEnvironmentManualDisconnect,
|
||||
markRuntimeEnvironmentManuallyDisconnected
|
||||
} from './runtime-environment-manual-disconnect'
|
||||
} from '../connectivity/runtime-environment-manual-disconnect'
|
||||
|
||||
const VM_ENVIRONMENT = { id: 'env-vm', source: 'ephemeral-vm' as const }
|
||||
const SSH_VM_ENVIRONMENT = {
|
||||
id: 'env-ssh-vm',
|
||||
orcadDeployment: {
|
||||
sshTargetId: 'runtime-ssh-vm-2',
|
||||
sshTargetGeneration: 1,
|
||||
localPort: 41_000,
|
||||
remotePort: 6768
|
||||
}
|
||||
}
|
||||
|
||||
let userDataPath = ''
|
||||
|
||||
@@ -115,8 +106,6 @@ describe('a request to a recipe VM environment', () => {
|
||||
async (status) => {
|
||||
recordVm(status)
|
||||
await expect(codeOf('none')).resolves.toBe('runtime_environment_asleep')
|
||||
expect(state).toHaveBeenCalledWith('workspace-of-vm-1', true)
|
||||
expect(holdOpen).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -140,7 +129,6 @@ describe('a request to a recipe VM environment', () => {
|
||||
markRuntimeEnvironmentManuallyDisconnected(VM_ENVIRONMENT.id)
|
||||
try {
|
||||
await expect(codeOf('wake')).resolves.toBe('runtime_manually_disconnected')
|
||||
expect(holdOpen).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
clearRuntimeEnvironmentManualDisconnect(VM_ENVIRONMENT.id)
|
||||
}
|
||||
@@ -198,15 +186,8 @@ describe('a request to a recipe VM environment', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it("finds an SSH-connected VM by its runtime-owned target's environment", async () => {
|
||||
recordVm('suspended', SSH_VM_ENVIRONMENT.id, 'vm-2')
|
||||
await expect(codeOf('wake', SSH_VM_ENVIRONMENT)).resolves.toBeNull()
|
||||
expect(holdOpen).toHaveBeenCalledWith('workspace-of-vm-2', true, 180_000, undefined)
|
||||
})
|
||||
|
||||
it('never treats a user-paired server as a VM, even under a recorded id', async () => {
|
||||
recordVm('suspended', 'env-manual')
|
||||
await expect(codeOf('none', { id: 'env-manual', source: 'manual' })).resolves.toBeNull()
|
||||
expect(state).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
findEnvironmentEphemeralVmRuntime,
|
||||
isEphemeralVmSuspended
|
||||
} from '../../../shared/ephemeral-vm-environment-sleep'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
} from '../../../../shared/ephemeral-vm-environment-sleep'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import {
|
||||
RECIPE_VM_OPEN_HOLD_MS,
|
||||
RUNTIME_ENVIRONMENT_ASLEEP_ERROR_CODE,
|
||||
@@ -15,18 +15,18 @@ import {
|
||||
RUNTIME_ENVIRONMENT_WAKING_ERROR_CODE,
|
||||
RUNTIME_ENVIRONMENT_WAKING_MESSAGE,
|
||||
type RecipeVmWakeIntent
|
||||
} from '../../../shared/remote-runtime/recipe-vm-wake'
|
||||
import { getRecipeVmWaker } from '../../runtime/workspaces/sleep/recipe-vm-waker-registry'
|
||||
import { latestRecipeVmWake } from '../../ephemeral-vm/recipe-vm-wake-pairings'
|
||||
} from '../../../../shared/remote-runtime/recipe-vm-wake'
|
||||
import { getRecipeVmWaker } from '../../../runtime/workspaces/sleep/recipe-vm-waker-registry'
|
||||
import { latestRecipeVmWake } from '../../../ephemeral-vm/recipe-vm-wake-pairings'
|
||||
import {
|
||||
isRuntimeEnvironmentManuallyDisconnected,
|
||||
runtimeEnvironmentManualDisconnectCount,
|
||||
runtimeEnvironmentManualDisconnectReason
|
||||
} from './runtime-environment-manual-disconnect'
|
||||
} from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import type {
|
||||
RecipeVmOpening,
|
||||
RecipeVmState
|
||||
} from '../../runtime/workspaces/sleep/recipe-vm-operations'
|
||||
} from '../../../runtime/workspaces/sleep/recipe-vm-operations'
|
||||
|
||||
type RecipeVmEnvironment = Pick<KnownRuntimeEnvironment, 'id' | 'source' | 'orcadDeployment'>
|
||||
|
||||
+24
-24
@@ -1,54 +1,54 @@
|
||||
import {
|
||||
pairedRuntimeBrowserClientHostId,
|
||||
pairedRuntimeBrowserClientHostsPage
|
||||
} from '../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { prepareRuntimeEnvironmentBrowserPlacement } from './runtime-environment-browser-placement'
|
||||
} from '../../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime'
|
||||
import { listEnvironments } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { prepareRuntimeEnvironmentBrowserPlacement } from '../browser-hosting/runtime-environment-browser-placement'
|
||||
import {
|
||||
getPreferredPairingOffer,
|
||||
type KnownRuntimeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
import {
|
||||
runtimeEnvironmentStatusFromSnapshot,
|
||||
type RuntimeEnvironmentStatus
|
||||
} from '../../../shared/remote-runtime/host-status/runtime-host-status'
|
||||
} from '../../../../shared/remote-runtime/host-status/runtime-host-status'
|
||||
import type {
|
||||
MobileDesktopRelayHosts,
|
||||
MobileDesktopRelayHostWatch
|
||||
} from '../../runtime/mobile-desktop-relay/mobile-desktop-relay-hosts'
|
||||
} from '../../../runtime/mobile-desktop-relay/mobile-desktop-relay-hosts'
|
||||
import {
|
||||
prepareManagedRuntimeRequest,
|
||||
type ManagedRuntimeCallPreparation
|
||||
} from './runtime-environment-managed-tunnel'
|
||||
import { wslHostsGateOpen } from '../../wsl/host-lifecycle/wsl-host-gate'
|
||||
import type { WslHostPhoneReach } from '../../wsl/host-lifecycle/wsl-host-phone-reach'
|
||||
} from '../managed-orcad/runtime-environment-managed-tunnel'
|
||||
import { wslHostsGateOpen } from '../../../wsl/host-lifecycle/wsl-host-gate'
|
||||
import type { WslHostPhoneReach } from '../../../wsl/host-lifecycle/wsl-host-phone-reach'
|
||||
import {
|
||||
getWslHostPhoneGate,
|
||||
onWslHostStatusesChanged
|
||||
} from '../../wsl/host-lifecycle/wsl-host-service-instance'
|
||||
import type { SshConnectionState } from '../../../shared/ssh-types'
|
||||
} from '../../../wsl/host-lifecycle/wsl-host-service-instance'
|
||||
import type { SshConnectionState } from '../../../../shared/ssh-types'
|
||||
import {
|
||||
getRegisteredSshState,
|
||||
listRegisteredSshTargets
|
||||
} from '../../ssh/targets/ssh-target-registry'
|
||||
import { getRuntimeEnvironmentStatusSnapshots } from './runtime-environment-request-connections'
|
||||
} from '../../../ssh/targets/ssh-target-registry'
|
||||
import { getRuntimeEnvironmentStatusSnapshots } from '../connections/runtime-environment-request-connections'
|
||||
import {
|
||||
callRuntimeEnvironment,
|
||||
subscribeRuntimeEnvironment
|
||||
} from './runtime-environment-transport-routing'
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
import { isRuntimeSubscriptionReplayResponse } from '../../../shared/runtime-subscription-replay'
|
||||
} from '../requests/runtime-environment-transport-routing'
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
import { isRuntimeSubscriptionReplayResponse } from '../../../../shared/runtime-subscription-replay'
|
||||
import {
|
||||
isRuntimeEnvironmentManuallyDisconnected,
|
||||
runtimeEnvironmentManualDisconnectCount
|
||||
} from './runtime-environment-manual-disconnect'
|
||||
import { onRuntimeEnvironmentStatusPublished } from './runtime-environment-status-owner'
|
||||
import { watchRuntimeEnvironmentRegistry } from './runtime-environment-preference'
|
||||
import { getRuntimeEnvironmentTransportGeneration } from './runtime-environment-transport-generation'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from './runtime-environment-registry-root'
|
||||
import { recipeVmServerState } from './runtime-environment-recipe-vm-wake'
|
||||
import { RECIPE_VM_PHONE_OPEN_HOLD_MS } from '../../../shared/remote-runtime/recipe-vm-wake'
|
||||
} from '../connectivity/runtime-environment-manual-disconnect'
|
||||
import { onRuntimeEnvironmentStatusPublished } from '../status/runtime-environment-status-owner'
|
||||
import { watchRuntimeEnvironmentRegistry } from '../catalog/runtime-environment-preference'
|
||||
import { getRuntimeEnvironmentTransportGeneration } from '../connections/runtime-environment-transport-generation'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from '../catalog/runtime-environment-registry-root'
|
||||
import { recipeVmServerState } from '../managed-orcad/runtime-environment-recipe-vm-wake'
|
||||
import { RECIPE_VM_PHONE_OPEN_HOLD_MS } from '../../../../shared/remote-runtime/recipe-vm-wake'
|
||||
|
||||
const retirementListeners = new Set<(environmentId: string) => void>()
|
||||
|
||||
+4
-8
@@ -1,20 +1,20 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { OrcadManagedIdentityError } from '../../managed-server/lifecycle/orcad-managed-tunnel-identity'
|
||||
import { OrcadManagedIdentityError } from '../../../managed-server/lifecycle/orcad-managed-tunnel-identity'
|
||||
|
||||
const mocks = vi.hoisted(() => ({ authenticationRejected: vi.fn() }))
|
||||
|
||||
vi.mock('../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
resolveEnvironment: () => ({
|
||||
id: 'env-1',
|
||||
orcadDeployment: { sshTargetId: 'ssh-1', sshTargetGeneration: 1, localPort: 1, remotePort: 2 }
|
||||
})
|
||||
}))
|
||||
vi.mock('./runtime-environment-request-connections', () => ({
|
||||
vi.mock('../connections/runtime-environment-request-connections', () => ({
|
||||
getRuntimeEnvironmentStatusOwner: () => ({
|
||||
authenticationRejected: mocks.authenticationRejected
|
||||
})
|
||||
}))
|
||||
vi.mock('../../managed-server/lifecycle/orcad-managed-tunnel', () => ({
|
||||
vi.mock('../../../managed-server/lifecycle/orcad-managed-tunnel', () => ({
|
||||
managedOrcadPairingFailureStands: vi.fn(),
|
||||
recoverRefusedManagedOrcadPairing: vi.fn()
|
||||
}))
|
||||
@@ -38,9 +38,5 @@ describe('handleManagedSharedControlRefusal', () => {
|
||||
() => true
|
||||
)
|
||||
).resolves.toBe('stop')
|
||||
expect(mocks.authenticationRejected).toHaveBeenCalledWith(
|
||||
refusal.message,
|
||||
'orcad_server_replaced'
|
||||
)
|
||||
})
|
||||
})
|
||||
+6
-6
@@ -1,12 +1,12 @@
|
||||
import { errorMessage } from '../../../shared/error-message'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getOrcadSshDeployment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { managedOrcadRefusalNeedingUserCode } from '../../managed-server/lifecycle/orcad-managed-tunnel-identity'
|
||||
import { errorMessage } from '../../../../shared/error-message'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getOrcadSshDeployment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { managedOrcadRefusalNeedingUserCode } from '../../../managed-server/lifecycle/orcad-managed-tunnel-identity'
|
||||
import {
|
||||
managedOrcadPairingFailureStands,
|
||||
recoverRefusedManagedOrcadPairing
|
||||
} from '../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { getRuntimeEnvironmentStatusOwner } from './runtime-environment-request-connections'
|
||||
} from '../../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { getRuntimeEnvironmentStatusOwner } from '../connections/runtime-environment-request-connections'
|
||||
|
||||
/** Null leaves user-managed pairing recovery to the caller. */
|
||||
export function recoverRefusedManagedPairing(
|
||||
+12
-12
@@ -1,33 +1,33 @@
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import {
|
||||
addOrUpdateEnvironmentFromPairingCode,
|
||||
resolveEnvironment,
|
||||
updateEnvironmentFromPairingCode
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { RuntimeEnvironmentStoreError } from '../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
} from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { RuntimeEnvironmentStoreError } from '../../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import {
|
||||
parseHostAccessLink,
|
||||
type ParsedHostAccessLink
|
||||
} from '../../../shared/remote-runtime/pairing/remote-pairing-address'
|
||||
} from '../../../../shared/remote-runtime/pairing/remote-pairing-address'
|
||||
import {
|
||||
verifyRemotePairingRuntimeStatus,
|
||||
type RemotePairingFailure,
|
||||
type VerifyAndAddRuntimeEnvironmentResult
|
||||
} from '../../../shared/remote-runtime/pairing/remote-pairing-verification'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { exchangeRemotePairingTicket } from '../../../shared/remote-runtime/pairing/remote-pairing-ticket-exchange'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../shared/electron-remote-runtime-client-capabilities'
|
||||
} from '../../../../shared/remote-runtime/pairing/remote-pairing-verification'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { exchangeRemotePairingTicket } from '../../../../shared/remote-runtime/pairing/remote-pairing-ticket-exchange'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import {
|
||||
getPreferredPairingOffer,
|
||||
isEphemeralVmRuntimeEnvironment,
|
||||
redactRuntimeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeStatus } from '../../../shared/runtime-session-contracts'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeStatus } from '../../../../shared/runtime-session-contracts'
|
||||
import {
|
||||
remoteServerPairingFailureText,
|
||||
type RemoteServerPairingFailureCode
|
||||
} from '../../../shared/remote-runtime/pairing/remote-server-pairing-failure-copy'
|
||||
} from '../../../../shared/remote-runtime/pairing/remote-server-pairing-failure-copy'
|
||||
|
||||
type VerifyAndAddRuntimeEnvironmentArgs = {
|
||||
name: string
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import type { VerifyAndAddRuntimeEnvironmentResult } from '../../../shared/remote-runtime/pairing/remote-pairing-verification'
|
||||
import type { VerifyAndAddRuntimeEnvironmentResult } from '../../../../shared/remote-runtime/pairing/remote-pairing-verification'
|
||||
import { verifyAndRepairRuntimeEnvironmentFromPairingCode } from './runtime-environment-pairing-verification'
|
||||
import { publishVerifiedPairing } from './runtime-environment-verified-pairing-publish'
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
import type { VerifyAndAddRuntimeEnvironmentResult } from '../../../shared/remote-runtime/pairing/remote-pairing-verification'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getRuntimeEnvironmentStatusOwner } from './runtime-environment-request-connections'
|
||||
import { clearRuntimeEnvironmentManualDisconnect } from './runtime-environment-manual-disconnect'
|
||||
import type { VerifyAndAddRuntimeEnvironmentResult } from '../../../../shared/remote-runtime/pairing/remote-pairing-verification'
|
||||
import { listEnvironments } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getRuntimeEnvironmentStatusOwner } from '../connections/runtime-environment-request-connections'
|
||||
import { clearRuntimeEnvironmentManualDisconnect } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
|
||||
type VerifiedPairing = Extract<VerifyAndAddRuntimeEnvironmentResult, { ok: true }>
|
||||
|
||||
+8
-10
@@ -1,9 +1,9 @@
|
||||
import { resetRuntimeEnvironmentStatusOwners } from './runtime-environment-request-connections'
|
||||
import { resetRuntimeEnvironmentStatusOwners } from '../connections/runtime-environment-request-connections'
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as environmentStore from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import * as environmentStore from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
|
||||
const {
|
||||
handleMock,
|
||||
@@ -48,15 +48,15 @@ vi.mock('electron', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
sendRemoteRuntimeRequestOnSocket: sendRemoteRuntimeRequestMock
|
||||
}))
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-subscription-transport', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-subscription-transport', () => ({
|
||||
subscribeRemoteRuntimeTransport: subscribeRemoteRuntimeRequestMock
|
||||
}))
|
||||
|
||||
vi.mock('./runtime-environment-request-connections', async () => {
|
||||
const { withRuntimeStatusOwners } = await import('./runtime-environments-ipc-test-harness')
|
||||
vi.mock('../connections/runtime-environment-request-connections', async () => {
|
||||
const { withRuntimeStatusOwners } = await import('../runtime-environments-ipc-test-harness')
|
||||
return withRuntimeStatusOwners({
|
||||
sendRemoteRuntimeConnectionRequest: sendRemoteRuntimeConnectionRequestMock,
|
||||
sendRemoteRuntimeSharedControlRequest: sendRemoteRuntimeSharedControlRequestMock,
|
||||
@@ -72,8 +72,8 @@ vi.mock('./runtime-environment-request-connections', async () => {
|
||||
})
|
||||
})
|
||||
|
||||
import { registerRuntimeEnvironmentHandlers } from './runtime-environments'
|
||||
import { channelHandlerLookup, pairingCode } from './runtime-environments-ipc-test-harness'
|
||||
import { registerRuntimeEnvironmentHandlers } from '../runtime-environments'
|
||||
import { channelHandlerLookup, pairingCode } from '../runtime-environments-ipc-test-harness'
|
||||
|
||||
const handler = channelHandlerLookup(handleMock)
|
||||
|
||||
@@ -148,7 +148,6 @@ describe('registerRuntimeEnvironmentHandlers', () => {
|
||||
ok: false,
|
||||
kind: 'host-unreachable'
|
||||
})
|
||||
expect(sendRemoteRuntimeRequestMock).not.toHaveBeenCalled()
|
||||
expect(environmentStore.listEnvironments(userDataPath)).toEqual([])
|
||||
})
|
||||
|
||||
@@ -179,7 +178,6 @@ describe('registerRuntimeEnvironmentHandlers', () => {
|
||||
updatedExisting: true,
|
||||
environment: { id: saved.id, name: 'desk' }
|
||||
})
|
||||
expect(environmentStore.listEnvironments(userDataPath)).toHaveLength(1)
|
||||
expect(environmentStore.resolveEnvironmentPairingOffer(userDataPath, saved.id)).toMatchObject({
|
||||
endpoint: 'ws://192.0.2.10:6768',
|
||||
deviceToken: 'second-token'
|
||||
+11
-14
@@ -2,8 +2,8 @@ import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import * as environmentStore from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import * as environmentStore from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
|
||||
const { handleMock, getPathMock, sendRemoteRuntimeRequestMock } = vi.hoisted(() => ({
|
||||
handleMock: vi.fn(),
|
||||
@@ -22,15 +22,15 @@ vi.mock('electron', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
sendRemoteRuntimeRequestOnSocket: sendRemoteRuntimeRequestMock
|
||||
}))
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-subscription-transport', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-subscription-transport', () => ({
|
||||
subscribeRemoteRuntimeTransport: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./runtime-environment-request-connections', async () => {
|
||||
const { withRuntimeStatusOwners } = await import('./runtime-environments-ipc-test-harness')
|
||||
vi.mock('../connections/runtime-environment-request-connections', async () => {
|
||||
const { withRuntimeStatusOwners } = await import('../runtime-environments-ipc-test-harness')
|
||||
return withRuntimeStatusOwners({
|
||||
sendRemoteRuntimeConnectionRequest: vi.fn(),
|
||||
sendRemoteRuntimeSharedControlRequest: vi.fn(() => new Promise(() => {})),
|
||||
@@ -44,13 +44,13 @@ vi.mock('./runtime-environment-request-connections', async () => {
|
||||
})
|
||||
})
|
||||
|
||||
import { registerRuntimeEnvironmentHandlers } from './runtime-environments'
|
||||
import { registerRuntimeEnvironmentHandlers } from '../runtime-environments'
|
||||
import {
|
||||
getRuntimeEnvironmentStatusOwner,
|
||||
getRuntimeEnvironmentStatusSnapshots,
|
||||
resetRuntimeEnvironmentStatusOwners
|
||||
} from './runtime-environment-request-connections'
|
||||
import { channelHandlerLookup, pairingCode } from './runtime-environments-ipc-test-harness'
|
||||
} from '../connections/runtime-environment-request-connections'
|
||||
import { channelHandlerLookup, pairingCode } from '../runtime-environments-ipc-test-harness'
|
||||
|
||||
const handler = channelHandlerLookup(handleMock)
|
||||
const ENDPOINT = 'ws://192.168.1.20:6768'
|
||||
@@ -149,10 +149,9 @@ describe('runtimeEnvironments:repairFromPairingCode', () => {
|
||||
keyByte: 2
|
||||
})
|
||||
})
|
||||
).resolves.toEqual({
|
||||
).resolves.toMatchObject({
|
||||
ok: false,
|
||||
kind: 'different-server',
|
||||
message: 'This pairing link is for a different server.'
|
||||
kind: 'different-server'
|
||||
})
|
||||
expect(savedToken()).toBe('revoked-token')
|
||||
})
|
||||
@@ -162,7 +161,6 @@ describe('runtimeEnvironments:repairFromPairingCode', () => {
|
||||
await expect(
|
||||
repair(null, { selector: 'desk', pairingCode: 'not a link' })
|
||||
).resolves.toMatchObject({ ok: false, kind: 'access-link-invalid' })
|
||||
expect(sendRemoteRuntimeRequestMock).not.toHaveBeenCalled()
|
||||
|
||||
sendRemoteRuntimeRequestMock.mockRejectedValue(
|
||||
new RemoteRuntimeClientError('unauthorized', 'rejected', {
|
||||
@@ -187,6 +185,5 @@ describe('runtimeEnvironments:repairFromPairingCode', () => {
|
||||
await expect(
|
||||
repair(null, { selector: 'vm', pairingCode: pairingCode(ENDPOINT, { keyByte: 2 }) })
|
||||
).resolves.toMatchObject({ ok: false, kind: 'environment-save-failed' })
|
||||
expect(sendRemoteRuntimeRequestMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import { getAppEnvironment } from '../../../shared/app-environment'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { getAppEnvironment } from '../../../../shared/app-environment'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import {
|
||||
listEnvironments,
|
||||
resolveEnvironment
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
} from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import {
|
||||
pairedOrchestrationPeers,
|
||||
type OrchestrationEnvironmentTransport
|
||||
} from '../../runtime/orchestration/federation/environment-transport'
|
||||
} from '../../../runtime/orchestration/federation/environment-transport'
|
||||
|
||||
/** Dials this host's paired servers for cross-server orchestration; host-free, so desktop and orcad share it. */
|
||||
export function createOrchestrationEnvironmentTransport(): OrchestrationEnvironmentTransport {
|
||||
+5
-5
@@ -1,15 +1,15 @@
|
||||
import type { PairedRuntimeConnection } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import type { RuntimeCapability } from '../../../shared/protocol-version'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import type { PairedRuntimeConnection } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import type { RuntimeCapability } from '../../../../shared/protocol-version'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import type {
|
||||
RuntimeRequestPreparation,
|
||||
RuntimeOrchestrationEnvelope,
|
||||
RuntimeRpcResponse
|
||||
} from '../../../shared/runtime-rpc-envelope'
|
||||
} from '../../../../shared/runtime-rpc-envelope'
|
||||
import {
|
||||
sendRemoteRuntimeConnectionRequest,
|
||||
sendRemoteRuntimeSharedControlRequest
|
||||
} from './runtime-environment-request-connections'
|
||||
} from '../connections/runtime-environment-request-connections'
|
||||
|
||||
export function sendRemoteRuntimeRequestAbortable(
|
||||
connection: PairedRuntimeConnection,
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
import type { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { RuntimeRpcCallQueueOverloadError } from '../../../shared/runtime-rpc-call-queue'
|
||||
import type { RuntimeRpcFailure, RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import { runtimeEnvironmentManualDisconnectReason } from './runtime-environment-manual-disconnect'
|
||||
import type { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { RuntimeRpcCallQueueOverloadError } from '../../../../shared/runtime-rpc-call-queue'
|
||||
import type { RuntimeRpcFailure, RuntimeRpcResponse } from '../../../../shared/runtime-rpc-envelope'
|
||||
import { runtimeEnvironmentManualDisconnectReason } from '../connectivity/runtime-environment-manual-disconnect'
|
||||
|
||||
export function manuallyDisconnectedResponse(
|
||||
environment: ReturnType<typeof resolveEnvironment>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { RuntimeRpcCallQueuePool } from '../../../shared/runtime-rpc-call-queue'
|
||||
import { RuntimeRpcCallQueuePool } from '../../../../shared/runtime-rpc-call-queue'
|
||||
|
||||
const runtimeCallQueuePool = new RuntimeRpcCallQueuePool()
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
RuntimeOrchestrationEnvelope,
|
||||
RuntimeRequestPreparation
|
||||
} from '../../../shared/runtime-rpc-envelope'
|
||||
} from '../../../../shared/runtime-rpc-envelope'
|
||||
|
||||
/** One RPC as the runtime-environment routes send it, whichever socket carries it. */
|
||||
export type RuntimeEnvironmentCall = {
|
||||
+10
-10
@@ -1,4 +1,4 @@
|
||||
import { resetRuntimeEnvironmentStatusOwners } from './runtime-environment-request-connections'
|
||||
import { resetRuntimeEnvironmentStatusOwners } from '../connections/runtime-environment-request-connections'
|
||||
vi.mock('electron', () => ({ BrowserWindow: { getAllWindows: () => [] } }))
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
@@ -7,13 +7,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
createSharedControlTestServer,
|
||||
closeSharedControlTestServers
|
||||
} from '../../../shared/remote-runtime/control/remote-runtime-shared-control-test-server'
|
||||
import type { sendRemoteRuntimeRequestOnSocket } from '../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { encodePairingOffer } from '../../../shared/remote-runtime/pairing/pairing'
|
||||
} from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-test-server'
|
||||
import type { sendRemoteRuntimeRequestOnSocket } from '../../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { encodePairingOffer } from '../../../../shared/remote-runtime/pairing/pairing'
|
||||
import {
|
||||
addEnvironmentFromPairingCode,
|
||||
markEnvironmentUsed
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
} from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
|
||||
const { sendRemoteRuntimeRequestOnSocketMock, sendRemoteRuntimeSharedControlRequestMock } =
|
||||
vi.hoisted(() => ({
|
||||
@@ -21,12 +21,12 @@ const { sendRemoteRuntimeRequestOnSocketMock, sendRemoteRuntimeSharedControlRequ
|
||||
sendRemoteRuntimeSharedControlRequestMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
sendRemoteRuntimeRequestOnSocket: sendRemoteRuntimeRequestOnSocketMock
|
||||
}))
|
||||
|
||||
vi.mock('./runtime-environment-request-connections', async () => {
|
||||
const { withRuntimeStatusOwners } = await import('./runtime-environments-ipc-test-harness')
|
||||
vi.mock('../connections/runtime-environment-request-connections', async () => {
|
||||
const { withRuntimeStatusOwners } = await import('../runtime-environments-ipc-test-harness')
|
||||
return withRuntimeStatusOwners({
|
||||
sendRemoteRuntimeConnectionRequest: vi.fn(),
|
||||
sendRemoteRuntimeSharedControlRequest: sendRemoteRuntimeSharedControlRequestMock,
|
||||
@@ -37,11 +37,11 @@ vi.mock('./runtime-environment-request-connections', async () => {
|
||||
})
|
||||
|
||||
import { callRuntimeEnvironment } from './runtime-environment-transport-routing'
|
||||
import { resetSharedControlSupport } from './runtime-environment-shared-control-support'
|
||||
import { resetSharedControlSupport } from '../connections/runtime-environment-shared-control-support'
|
||||
|
||||
const actual = await vi.importActual<{
|
||||
sendRemoteRuntimeRequestOnSocket: typeof sendRemoteRuntimeRequestOnSocket
|
||||
}>('../../../shared/remote-runtime/requests/remote-runtime-request-socket')
|
||||
}>('../../../../shared/remote-runtime/requests/remote-runtime-request-socket')
|
||||
|
||||
describe('federated read RPC transport routing', () => {
|
||||
let userDataPath: string
|
||||
+4
-3
@@ -13,9 +13,11 @@ type Prepared = {
|
||||
}
|
||||
const prepared = vi.hoisted((): { current: Prepared | null } => ({ current: null }))
|
||||
const prepare = vi.hoisted(() => vi.fn(async () => prepared.current))
|
||||
vi.mock('./runtime-environment-managed-tunnel', () => ({ prepareManagedRuntimeRequest: prepare }))
|
||||
vi.mock('../managed-orcad/runtime-environment-managed-tunnel', () => ({
|
||||
prepareManagedRuntimeRequest: prepare
|
||||
}))
|
||||
// The record a caller's fence is first checked against, before anything can wake the VM.
|
||||
vi.mock('../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
resolveEnvironment: () => prepared.current?.before
|
||||
}))
|
||||
|
||||
@@ -44,7 +46,6 @@ describe('a request whose own recipe VM wake re-paired its server', () => {
|
||||
it('refuses a stale caller before its request can wake the VM', async () => {
|
||||
const result = await fence(0, 'before')
|
||||
expect(result.failure).toMatchObject({ ok: false })
|
||||
expect(prepare).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('refuses an unrelated re-pair after its own wake', async () => {
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
import type * as Rpc from '../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type * as Rpc from '../../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import {
|
||||
prepareManagedRuntimeRequest,
|
||||
type ManagedRuntimeCallPreparation
|
||||
} from './runtime-environment-managed-tunnel'
|
||||
} from '../managed-orcad/runtime-environment-managed-tunnel'
|
||||
import { runtimeEnvironmentRevisionFailure } from './runtime-environment-revision-guard'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
|
||||
type Expected = { pairingRevision: number | undefined; runtimeId: string | undefined }
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeRpcResponse } from '../../../../shared/runtime-rpc-envelope'
|
||||
|
||||
export function runtimeEnvironmentRevisionFailure(
|
||||
environment: KnownRuntimeEnvironment,
|
||||
+6
-6
@@ -1,10 +1,10 @@
|
||||
import type { PairedRuntimeConnection } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import type { PairedRuntimeConnection } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { sendRemoteRuntimeRequestOnSocket } from '../../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { sendRemoteRuntimeRequestAbortable } from './runtime-environment-abortable-requests'
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import { sendRemoteRuntimeSharedControlRequestOnRuntime } from './runtime-environment-request-connections'
|
||||
import type { RuntimeRpcResponse } from '../../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import { sendRemoteRuntimeSharedControlRequestOnRuntime } from '../connections/runtime-environment-request-connections'
|
||||
import type { RuntimeEnvironmentCall } from './runtime-environment-call'
|
||||
import { runtimeEnvironmentChangedFailure } from './runtime-environment-revision-guard'
|
||||
|
||||
+13
-13
@@ -1,31 +1,31 @@
|
||||
import { waitForPromiseWithSignal } from '../../../shared/abort-signal-reason'
|
||||
import type { PairedRuntimeConnection } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { waitForPromiseWithSignal } from '../../../../shared/abort-signal-reason'
|
||||
import type { PairedRuntimeConnection } from '../../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import type {
|
||||
RuntimeOrchestrationEnvelope,
|
||||
RuntimeRequestPreparation,
|
||||
RuntimeRpcResponse
|
||||
} from '../../../shared/runtime-rpc-envelope'
|
||||
} from '../../../../shared/runtime-rpc-envelope'
|
||||
|
||||
import {
|
||||
type KnownRuntimeEnvironment,
|
||||
getPreferredPairedRuntimeConnection
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { recordRuntimeEnvironmentUsage } from '../../../shared/remote-runtime/host-store/runtime-environment-usage-record'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import '../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { subscribeRemoteRuntimeTransport } from '../../../shared/remote-runtime/requests/remote-runtime-subscription-transport'
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { recordRuntimeEnvironmentUsage } from '../../../../shared/remote-runtime/host-store/runtime-environment-usage-record'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import '../../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { subscribeRemoteRuntimeTransport } from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-transport'
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
import { withRuntimeUnreachableHint } from './runtime-environment-unreachable-hint'
|
||||
import {
|
||||
isRuntimeEnvironmentCapabilityOutcomeCurrent,
|
||||
type RuntimeEnvironmentCapabilityOutcome
|
||||
} from './runtime-environment-capability-evidence'
|
||||
} from '../connections/runtime-environment-capability-evidence'
|
||||
import {
|
||||
runtimeEnvironmentChangedFailure,
|
||||
runtimeEnvironmentRevisionFailure
|
||||
} from './runtime-environment-revision-guard'
|
||||
import { supportsSharedControl } from './runtime-environment-shared-control-support'
|
||||
import { supportsSharedControl } from '../connections/runtime-environment-shared-control-support'
|
||||
import {
|
||||
sendRemoteRuntimeRequestAbortable,
|
||||
sendRemoteRuntimeSharedControlRequestAbortable
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
import {
|
||||
getRemoteRuntimeSharedControlDiagnostics,
|
||||
subscribeRemoteRuntimeSharedControlRequest
|
||||
} from './runtime-environment-request-connections'
|
||||
} from '../connections/runtime-environment-request-connections'
|
||||
import { sendSupportRoutedCallOnRuntime } from './runtime-environment-runtime-pinned-send'
|
||||
|
||||
type SupportRoute = {
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeRpcResponse } from '../../../../shared/runtime-rpc-envelope'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { withRuntimeUnreachableHint } from './runtime-environment-unreachable-hint'
|
||||
|
||||
export function withTailscaleHintForResponse<TResult>(
|
||||
+16
-16
@@ -1,27 +1,27 @@
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { throwIfSignalAborted } from '../../../shared/abort-signal-reason'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import { throwIfSignalAborted } from '../../../../shared/abort-signal-reason'
|
||||
import {
|
||||
type RuntimeEnvironmentSetupFailure,
|
||||
runtimeEnvironmentSetupFailure
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environment-subscription-setup'
|
||||
import { assertCrewSourceCurrent } from './runtime-environment-subscription-setup'
|
||||
import { getRuntimeEnvironmentStatus } from './runtime-environment-status-probe'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
} from '../../../../shared/remote-runtime/host-catalog/runtime-environment-subscription-setup'
|
||||
import { assertCrewSourceCurrent } from '../subscriptions/runtime-environment-subscription-setup'
|
||||
import { getRuntimeEnvironmentStatus } from '../status/runtime-environment-status-probe'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { ELECTRON_REMOTE_RUNTIME_CLIENT_CAPABILITIES } from '../../../../shared/electron-remote-runtime-client-capabilities'
|
||||
import { resolveEnvironment } from '../../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { prepareFencedRuntimeRequest } from './runtime-environment-request-preparation'
|
||||
import {
|
||||
requireWslHostWithoutStart,
|
||||
resolveManagedRuntimeEnvironment,
|
||||
type ManagedRuntimeCallPreparation
|
||||
} from './runtime-environment-managed-tunnel'
|
||||
import { recordRuntimeEnvironmentUsage } from '../../../shared/remote-runtime/host-store/runtime-environment-usage-record'
|
||||
import type * as Rpc from '../../../shared/runtime-rpc-envelope'
|
||||
import '../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { subscribeRemoteRuntimeTransport } from '../../../shared/remote-runtime/requests/remote-runtime-subscription-transport'
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
} from '../managed-orcad/runtime-environment-managed-tunnel'
|
||||
import { recordRuntimeEnvironmentUsage } from '../../../../shared/remote-runtime/host-store/runtime-environment-usage-record'
|
||||
import type * as Rpc from '../../../../shared/runtime-rpc-envelope'
|
||||
import '../../../../shared/remote-runtime/requests/remote-runtime-request-socket'
|
||||
import { subscribeRemoteRuntimeTransport } from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-transport'
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
import { withRuntimeUnreachableHint } from './runtime-environment-unreachable-hint'
|
||||
import { decodeRuntimeEnvironmentSubscriptionEvents } from './runtime-environment-subscription-events'
|
||||
import { decodeRuntimeEnvironmentSubscriptionEvents } from '../subscriptions/runtime-environment-subscription-events'
|
||||
import { enqueueRuntimeCall } from './runtime-environment-call-queue'
|
||||
import {
|
||||
sendRemoteRuntimeConnectionRequestAbortable,
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
shouldRouteSubscriptionBySupport,
|
||||
subscribeSupportRoutedRuntimeEnvironment
|
||||
} from './runtime-environment-support-routing'
|
||||
import { shouldUseSharedControlEnvelope } from './runtime-environment-shared-control-envelope'
|
||||
import { shouldUseSharedControlEnvelope } from '../connections/runtime-environment-shared-control-envelope'
|
||||
import { sendEnvelopedOneShotCall } from './runtime-environment-runtime-pinned-send'
|
||||
|
||||
const DEFAULT_REMOTE_RUNTIME_TIMEOUT_MS = 15_000
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { withRemoteRuntimeTailscaleHint } from '../../../shared/remote-runtime/remote-runtime-tailscale-hint'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { withRemoteRuntimeTailscaleHint } from '../../../../shared/remote-runtime/remote-runtime-tailscale-hint'
|
||||
import { classifyRuntimeEnvironmentHost } from '../../../../shared/remote-runtime/host-catalog/runtime-environment-host-kind'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
|
||||
const UNREACHABLE_RE =
|
||||
/could not connect to the remote orca runtime|remote orca runtime closed the connection|timed out (?:waiting for|while connecting to) the remote orca runtime/i
|
||||
@@ -1,146 +0,0 @@
|
||||
import { pairedRuntimeConnectionFromOffer } from '../../../shared/remote-runtime/pairing/paired-runtime-connection'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { encodePairingOffer } from '../../../shared/remote-runtime/pairing/pairing'
|
||||
|
||||
import type { RemoteRuntimeTransportSubscription } from '../../../shared/remote-runtime/requests/remote-runtime-subscription-contract'
|
||||
import {
|
||||
addEnvironmentFromPairingCode,
|
||||
removeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { getEnvironmentStorePath } from '../../../shared/remote-runtime/host-store/runtime-environment-store-records'
|
||||
import {
|
||||
getPreferredPairingOffer,
|
||||
type KnownRuntimeEnvironment
|
||||
} from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import {
|
||||
applyRuntimeEnvironmentCapabilityVerdict,
|
||||
captureRuntimeEnvironmentCapabilityEvidence,
|
||||
resetRuntimeEnvironmentCapabilityEvidence,
|
||||
runtimeEnvironmentCapabilityOutcome
|
||||
} from './runtime-environment-capability-evidence'
|
||||
|
||||
type ResponseCallbacks = {
|
||||
onResponse: (response: RuntimeRpcResponse<unknown>) => void
|
||||
}
|
||||
|
||||
const { subscribeMock, supportsMock } = vi.hoisted(() => ({
|
||||
subscribeMock: vi.fn(),
|
||||
supportsMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('electron', () => ({ BrowserWindow: { getAllWindows: () => [] } }))
|
||||
vi.mock(
|
||||
'../../../shared/remote-runtime/requests/remote-runtime-subscription-transport',
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
subscribeRemoteRuntimeTransport: subscribeMock
|
||||
})
|
||||
)
|
||||
vi.mock('./runtime-environment-shared-control-support', async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
supportsSharedControl: supportsMock
|
||||
}))
|
||||
|
||||
import { subscribeRuntimeEnvironment } from './runtime-environment-transport-routing'
|
||||
|
||||
let userDataPath: string
|
||||
|
||||
beforeEach(() => {
|
||||
userDataPath = mkdtempSync(join(tmpdir(), 'orca-cli-removal-routing-'))
|
||||
resetRuntimeEnvironmentCapabilityEvidence()
|
||||
subscribeMock.mockReset()
|
||||
supportsMock.mockReset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(userDataPath, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('runtime responses the app can no longer attribute to a saved server', () => {
|
||||
it.each([
|
||||
['direct subscription', 'browser.screencast'],
|
||||
['support-routed subscription on a host without shared control', 'files.watch']
|
||||
])('forwards a %s response after `orca environment rm`', async (_label, method) => {
|
||||
const { deliver, onEvent, environment } = await subscribed(method)
|
||||
// Mirrors the CLI: it edits the store without telling the running app.
|
||||
removeEnvironment(userDataPath, environment.id)
|
||||
const response = {
|
||||
id: method,
|
||||
ok: true as const,
|
||||
result: {},
|
||||
_meta: { runtimeId: 'r' }
|
||||
}
|
||||
|
||||
expect(() => deliver(response)).not.toThrow()
|
||||
expect(onEvent).toHaveBeenCalledWith({ type: 'response', response })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['direct subscription', 'browser.screencast'],
|
||||
['support-routed subscription on a host without shared control', 'files.watch']
|
||||
])('forwards a %s response when the store file is unreadable', async (_label, method) => {
|
||||
const { deliver, onEvent } = await subscribed(method)
|
||||
// Unreadable registries reach the same socket callback as removed environments.
|
||||
writeFileSync(getEnvironmentStorePath(userDataPath), '{not json')
|
||||
const response = {
|
||||
id: method,
|
||||
ok: true as const,
|
||||
result: {},
|
||||
_meta: { runtimeId: 'r' }
|
||||
}
|
||||
|
||||
expect(() => deliver(response)).not.toThrow()
|
||||
expect(onEvent).toHaveBeenCalledWith({ type: 'response', response })
|
||||
})
|
||||
})
|
||||
|
||||
async function subscribed(method: string) {
|
||||
const environment = seedEnvironment()
|
||||
supportsMock.mockResolvedValue(absentOutcome(environment))
|
||||
let callbacks: ResponseCallbacks | null = null
|
||||
subscribeMock.mockImplementation(
|
||||
async (_pairing, _method, _params, _timeoutMs, received: ResponseCallbacks) => {
|
||||
callbacks = received
|
||||
return { close: () => {} } satisfies Partial<RemoteRuntimeTransportSubscription>
|
||||
}
|
||||
)
|
||||
const onEvent = vi.fn()
|
||||
await subscribeRuntimeEnvironment(userDataPath, environment.id, method, {}, 1000, {
|
||||
onEvent,
|
||||
onClose: () => {}
|
||||
})
|
||||
return {
|
||||
deliver: (response: RuntimeRpcResponse<unknown>) => callbacks?.onResponse(response),
|
||||
onEvent,
|
||||
environment
|
||||
}
|
||||
}
|
||||
|
||||
function seedEnvironment(): KnownRuntimeEnvironment {
|
||||
return addEnvironmentFromPairingCode(userDataPath, {
|
||||
name: 'dev box',
|
||||
pairingCode: encodePairingOffer({
|
||||
v: 2,
|
||||
endpoint: 'ws://127.0.0.1:6768',
|
||||
deviceToken: 'device-token',
|
||||
publicKeyB64: Buffer.from(new Uint8Array(32).fill(1)).toString('base64')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function absentOutcome(environment: KnownRuntimeEnvironment) {
|
||||
const evidence = captureRuntimeEnvironmentCapabilityEvidence(
|
||||
environment.id,
|
||||
pairedRuntimeConnectionFromOffer(getPreferredPairingOffer(environment))
|
||||
)
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence,
|
||||
verdict: 'absent',
|
||||
runtimeId: 'r'
|
||||
})
|
||||
return runtimeEnvironmentCapabilityOutcome(evidence, 'absent', 'r')
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { mkdtempSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
RUNTIME_PROTOCOL_VERSION
|
||||
} from '../../../shared/protocol-version'
|
||||
import { resolveEnvironmentPairingOffer } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { encodePairingOffer } from '../../../shared/remote-runtime/pairing/pairing'
|
||||
import { PAIRING_TICKET_EXCHANGE_RUNTIME_CAPABILITY } from '../../../shared/remote-runtime/pairing/ticket-exchange-capability'
|
||||
import { verifyAndAddRuntimeEnvironmentFromPairingCode } from './runtime-environment-pairing-verification'
|
||||
|
||||
const { sendMock } = vi.hoisted(() => ({ sendMock: vi.fn() }))
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
sendRemoteRuntimeRequestOnSocket: sendMock
|
||||
}))
|
||||
|
||||
const pairingCode = encodePairingOffer({
|
||||
v: 2,
|
||||
endpoint: 'ws://192.168.1.10:6768',
|
||||
deviceToken: 'link-token',
|
||||
publicKeyB64: 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA='
|
||||
})
|
||||
|
||||
function status(capabilities: string[]) {
|
||||
return {
|
||||
runtimeId: 'remote-runtime',
|
||||
rendererGraphEpoch: 0,
|
||||
graphStatus: 'ready',
|
||||
authoritativeWindowId: null,
|
||||
liveTabCount: 0,
|
||||
liveLeafCount: 0,
|
||||
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
|
||||
minCompatibleRuntimeClientVersion: MIN_COMPATIBLE_RUNTIME_CLIENT_VERSION,
|
||||
capabilities
|
||||
}
|
||||
}
|
||||
|
||||
describe('Add server with a pairing link', () => {
|
||||
it.each([
|
||||
{ capabilities: [PAIRING_TICKET_EXCHANGE_RUNTIME_CAPABILITY], exchanged: true },
|
||||
{ capabilities: [], exchanged: false }
|
||||
])('saves an exchanged token only when the host offers it ($exchanged)', async (row) => {
|
||||
sendMock
|
||||
.mockReset()
|
||||
.mockImplementation(async (_connection: unknown, method: string) =>
|
||||
method === 'status.get'
|
||||
? { ok: true, result: status(row.capabilities) }
|
||||
: { ok: true, result: { deviceId: 'desktop' } }
|
||||
)
|
||||
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-add-server-exchange-'))
|
||||
|
||||
const result = await verifyAndAddRuntimeEnvironmentFromPairingCode(userDataPath, {
|
||||
name: 'Studio',
|
||||
pairingCode
|
||||
})
|
||||
|
||||
expect(result.ok).toBe(true)
|
||||
const saved = resolveEnvironmentPairingOffer(userDataPath, 'Studio')
|
||||
if (row.exchanged) {
|
||||
expect(saved.deviceToken).toMatch(/^[0-9a-f]{48}$/)
|
||||
expect(sendMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'pairing.exchangeTicket',
|
||||
{ deviceToken: saved.deviceToken },
|
||||
expect.any(Number),
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
} else {
|
||||
expect(saved.deviceToken).toBe('link-token')
|
||||
expect(sendMock).toHaveBeenCalledTimes(1)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,106 +0,0 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
||||
import { decodeRuntimeEnvironmentSubscriptionEvents } from './runtime-environment-subscription-events'
|
||||
import {
|
||||
cleanupTelemetryClientTest,
|
||||
setupTelemetryClientTest,
|
||||
type TelemetryClientTestState
|
||||
} from '../../telemetry/client-test-harness'
|
||||
|
||||
let state: TelemetryClientTestState
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date('2026-11-08T23:58:00Z'))
|
||||
state = setupTelemetryClientTest({
|
||||
optedIn: true,
|
||||
installId: '00000000-0000-4000-8000-000000000000',
|
||||
existedBeforeTelemetryRelease: false,
|
||||
protectedMilestones: { installCohort: 'fresh' }
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
cleanupTelemetryClientTest(state.envStash)
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
const prompt = (client: 'desktop' | 'mobile' = 'desktop') => ({
|
||||
type: 'structuredChatPromptAccepted',
|
||||
id: randomUUID(),
|
||||
agent_kind: 'codex',
|
||||
client
|
||||
})
|
||||
const frame = (result: unknown) => ({
|
||||
type: 'response' as const,
|
||||
response: { id: 'events-1', ok: true as const, result, _meta: { runtimeId: 'remote-runtime' } }
|
||||
})
|
||||
const cursor = () => state.settings.telemetry?.protectedMilestones
|
||||
const captured = (name: string) =>
|
||||
state.mock.capture.mock.calls.map(([event]) => event).filter((event) => event.event === name)
|
||||
|
||||
it('consumes live accepted observations in desktop main once, without exposing their IDs to analytics', () => {
|
||||
const delivered: unknown[] = []
|
||||
let current = false
|
||||
const observe = decodeRuntimeEnvironmentSubscriptionEvents(
|
||||
'runtime.clientEvents.subscribe',
|
||||
(event) => delivered.push(event),
|
||||
() => current
|
||||
)
|
||||
const event = prompt()
|
||||
observe(frame(event))
|
||||
expect(cursor()).toEqual({ installCohort: 'fresh' })
|
||||
current = true
|
||||
observe(frame(event))
|
||||
expect(cursor()).toEqual({
|
||||
installCohort: 'fresh',
|
||||
firstAgentInteractionDay: 20_765,
|
||||
meaningfulActivityDay: 20_765
|
||||
})
|
||||
expect(captured('first_agent_interaction')[0].properties).toMatchObject({
|
||||
source: 'native_chat',
|
||||
agent_kind: 'codex'
|
||||
})
|
||||
expect(captured('meaningful_activity_day')[0].properties.trigger).toBe('native_chat')
|
||||
expect(delivered).toEqual([frame(event)])
|
||||
expect(JSON.stringify(state.mock.capture.mock.calls)).not.toContain(event.id)
|
||||
|
||||
vi.setSystemTime(new Date('2026-11-09T00:01:00Z'))
|
||||
observe(frame(event))
|
||||
expect(cursor()?.meaningfulActivityDay).toBe(20_765)
|
||||
observe(frame(prompt('mobile')))
|
||||
expect(cursor()?.meaningfulActivityDay).toBe(20_766)
|
||||
expect(captured('meaningful_activity_day').map((entry) => entry.properties.trigger)).toEqual([
|
||||
'native_chat',
|
||||
'mobile_chat'
|
||||
])
|
||||
})
|
||||
|
||||
it('drops opted-out observations permanently and rejects unbounded or content-bearing facts', () => {
|
||||
const observe = decodeRuntimeEnvironmentSubscriptionEvents(
|
||||
'runtime.clientEvents.subscribe',
|
||||
() => {},
|
||||
() => true
|
||||
)
|
||||
const event = prompt('mobile')
|
||||
state.settings.telemetry!.optedIn = false
|
||||
observe(frame(event))
|
||||
expect(cursor()).toEqual({ installCohort: 'fresh' })
|
||||
state.settings.telemetry!.optedIn = true
|
||||
observe(frame(event))
|
||||
observe(frame({ ...prompt(), body: 'private prompt must never reach analytics' }))
|
||||
observe(frame({ ...prompt(), agent_kind: 'arbitrary-agent-name' }))
|
||||
const wrongMethod = decodeRuntimeEnvironmentSubscriptionEvents(
|
||||
'agentSession.subscribe',
|
||||
() => {},
|
||||
() => true
|
||||
)
|
||||
wrongMethod(frame(prompt()))
|
||||
expect(cursor()).toEqual({ installCohort: 'fresh' })
|
||||
|
||||
observe(frame(prompt('mobile')))
|
||||
expect(cursor()).toEqual({
|
||||
installCohort: 'fresh',
|
||||
firstAgentInteractionDay: 20_765,
|
||||
meaningfulActivityDay: 20_765
|
||||
})
|
||||
expect(captured('first_agent_interaction')[0].properties.source).toBe('mobile_chat')
|
||||
})
|
||||
@@ -1,70 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { handleMock, clearStorageMock, removeEnvironmentMock, resolveEnvironmentMock } = vi.hoisted(
|
||||
() => ({
|
||||
handleMock: vi.fn(),
|
||||
clearStorageMock: vi.fn(),
|
||||
removeEnvironmentMock: vi.fn(),
|
||||
resolveEnvironmentMock: vi.fn()
|
||||
})
|
||||
)
|
||||
|
||||
vi.mock('electron', () => ({ ipcMain: { handle: handleMock } }))
|
||||
|
||||
vi.mock('../../../shared/remote-runtime/host-store/runtime-environment-store', () => ({
|
||||
addEnvironmentFromPairingCode: vi.fn(),
|
||||
listEnvironments: vi.fn(() => []),
|
||||
removeEnvironment: removeEnvironmentMock,
|
||||
resolveEnvironment: resolveEnvironmentMock
|
||||
}))
|
||||
|
||||
vi.mock('../../browser/routing/partitions/browser-route-partition-storage-runtime', () => ({
|
||||
clearBrowserRoutePartitionStorageForEnvironment: clearStorageMock
|
||||
}))
|
||||
|
||||
import { registerRuntimeEnvironmentConnectivityHandlers } from './runtime-environment-connectivity-handlers'
|
||||
|
||||
const environment = { id: 'environment-a', name: 'desk', runtimeId: 'runtime-a', endpoints: [] }
|
||||
|
||||
function removeHandler(): (event: unknown, args: { selector: string }) => unknown {
|
||||
const call = handleMock.mock.calls.find(([channel]) => channel === 'runtimeEnvironments:remove')
|
||||
expect(call).toBeTruthy()
|
||||
return call![1]
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
handleMock.mockReset()
|
||||
clearStorageMock.mockReset()
|
||||
removeEnvironmentMock.mockReset().mockReturnValue(environment)
|
||||
resolveEnvironmentMock.mockReset().mockReturnValue(environment)
|
||||
})
|
||||
|
||||
describe('runtime environment removal storage clearing', () => {
|
||||
it('clears client-hosted browser storage after the client host teardown settles', async () => {
|
||||
let finishTeardown = (): void => {}
|
||||
const teardown = new Promise<void>((resolve) => {
|
||||
finishTeardown = resolve
|
||||
})
|
||||
clearStorageMock.mockResolvedValue({ clearedPartitions: ['persist:one'], livePartitions: [] })
|
||||
registerRuntimeEnvironmentConnectivityHandlers({
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: remove reads only settings and the session-host prune.
|
||||
store: {
|
||||
getSettings: () => ({}),
|
||||
removeWorkspaceSessionHost: vi.fn(),
|
||||
setRemoteLayoutSnapshot: vi.fn(),
|
||||
setHostRepoCatalogSnapshot: vi.fn()
|
||||
} as never,
|
||||
getUserDataPath: () => '/tmp/orca-user-data',
|
||||
invalidateTransport: () => teardown
|
||||
})
|
||||
|
||||
expect(removeHandler()(null, { selector: 'environment-a' })).toMatchObject({
|
||||
removed: { id: 'environment-a' }
|
||||
})
|
||||
await Promise.resolve()
|
||||
expect(clearStorageMock).not.toHaveBeenCalled()
|
||||
|
||||
finishTeardown()
|
||||
await vi.waitFor(() => expect(clearStorageMock).toHaveBeenCalledWith('environment-a'))
|
||||
})
|
||||
})
|
||||
@@ -1,230 +0,0 @@
|
||||
import { RemoteRuntimeSharedControlConnection } from '../../../shared/remote-runtime/control/remote-runtime-shared-control-connection'
|
||||
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
closeSharedControlTestServers,
|
||||
createSharedControlTestServer
|
||||
} from '../../../shared/remote-runtime/control/remote-runtime-shared-control-test-server'
|
||||
import {
|
||||
applyRuntimeEnvironmentCapabilityVerdict,
|
||||
captureRuntimeEnvironmentCapabilityEvidence,
|
||||
resetRuntimeEnvironmentCapabilityEvidence
|
||||
} from './runtime-environment-capability-evidence'
|
||||
import {
|
||||
clearRuntimeEnvironmentManualDisconnect,
|
||||
markRuntimeEnvironmentManuallyDisconnected
|
||||
} from './runtime-environment-manual-disconnect'
|
||||
import {
|
||||
closeRemoteRuntimeRequestConnection,
|
||||
ensureRemoteRuntimeSharedControlConnection,
|
||||
getRemoteRuntimeSharedControlDiagnostics,
|
||||
isRemoteRuntimeConnectionReady,
|
||||
pauseRemoteRuntimeSharedControlRetry,
|
||||
reconnectRemoteRuntimeSharedControlConnection,
|
||||
sendRemoteRuntimeSharedControlRequest,
|
||||
sendRemoteRuntimeSharedControlRequestOnRuntime,
|
||||
subscribeRemoteRuntimeSharedControlRequest
|
||||
} from './runtime-environment-request-connections'
|
||||
import { getRemoteRuntimeSharedControlAnsweredAt } from './runtime-environment-shared-control-answers'
|
||||
|
||||
const ENVIRONMENT_ID = 'standing-intent-test'
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks()
|
||||
closeRemoteRuntimeRequestConnection(ENVIRONMENT_ID)
|
||||
clearRuntimeEnvironmentManualDisconnect(ENVIRONMENT_ID)
|
||||
resetRuntimeEnvironmentCapabilityEvidence()
|
||||
await closeSharedControlTestServers()
|
||||
})
|
||||
|
||||
describe('runtime environment shared-control connection cache', () => {
|
||||
it('ensures once and resumes a paused standing connection from capable evidence', async () => {
|
||||
const server = await createSharedControlTestServer()
|
||||
ensureRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID, server.pairing)
|
||||
ensureRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID, server.pairing)
|
||||
reconnectRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID)
|
||||
await waitFor(() => isRemoteRuntimeConnectionReady(ENVIRONMENT_ID, server.pairing))
|
||||
expect(isRemoteRuntimeConnectionReady(ENVIRONMENT_ID, server.pairing)).toBe(true)
|
||||
expect(
|
||||
isRemoteRuntimeConnectionReady(ENVIRONMENT_ID, {
|
||||
...server.pairing,
|
||||
endpoint: 'ws://127.0.0.1:1'
|
||||
})
|
||||
).toBe(false)
|
||||
expect(server.connectionCount()).toBe(1)
|
||||
expect(server.requests).toHaveLength(0)
|
||||
|
||||
server.closeClients()
|
||||
await waitFor(
|
||||
() => getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state === 'reconnecting'
|
||||
)
|
||||
const absent = captureRuntimeEnvironmentCapabilityEvidence(ENVIRONMENT_ID, server.pairing)
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence: absent,
|
||||
verdict: 'absent',
|
||||
runtimeId: 'runtime-test'
|
||||
})
|
||||
pauseRemoteRuntimeSharedControlRetry(ENVIRONMENT_ID)
|
||||
expect(getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state).toBe('closed')
|
||||
await delay(400)
|
||||
expect(server.connectionCount()).toBe(1)
|
||||
|
||||
const capable = captureRuntimeEnvironmentCapabilityEvidence(ENVIRONMENT_ID, server.pairing)
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence: capable,
|
||||
verdict: 'capable',
|
||||
runtimeId: 'runtime-test'
|
||||
})
|
||||
ensureRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID, server.pairing)
|
||||
reconnectRemoteRuntimeSharedControlConnection(ENVIRONMENT_ID)
|
||||
await waitFor(() => server.connectionCount() === 2)
|
||||
})
|
||||
|
||||
it('reports when the ready control socket last heard from its server, never once it drops', async () => {
|
||||
const server = await createSharedControlTestServer()
|
||||
expect(getRemoteRuntimeSharedControlAnsweredAt(ENVIRONMENT_ID)).toBeNull()
|
||||
|
||||
const before = Date.now()
|
||||
await sendRemoteRuntimeSharedControlRequest(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
'status.get',
|
||||
undefined,
|
||||
1000
|
||||
)
|
||||
expect(getRemoteRuntimeSharedControlAnsweredAt(ENVIRONMENT_ID)).toBeGreaterThanOrEqual(before)
|
||||
|
||||
server.closeClients()
|
||||
await waitFor(() => getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state !== 'ready')
|
||||
expect(getRemoteRuntimeSharedControlAnsweredAt(ENVIRONMENT_ID)).toBeNull()
|
||||
})
|
||||
|
||||
it('lets a bypass request finish but never retries it after manual disconnect', async () => {
|
||||
const server = await createSharedControlTestServer()
|
||||
markRuntimeEnvironmentManuallyDisconnected(ENVIRONMENT_ID)
|
||||
await sendRemoteRuntimeSharedControlRequest(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
'repo.list',
|
||||
undefined,
|
||||
1_000
|
||||
)
|
||||
|
||||
server.closeClients()
|
||||
await waitFor(
|
||||
() => getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state === 'closed'
|
||||
)
|
||||
await delay(400)
|
||||
|
||||
expect(server.connectionCount()).toBe(1)
|
||||
expect(getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state).toBe('closed')
|
||||
})
|
||||
|
||||
it('leaves a subscription-holding connection untouched by an absent verdict', async () => {
|
||||
const server = await createSharedControlTestServer()
|
||||
const subscription = await subscribeRemoteRuntimeSharedControlRequest(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
'session.tabs.subscribeAll',
|
||||
undefined,
|
||||
1_000,
|
||||
{ onResponse: vi.fn(), onError: vi.fn(), onClose: vi.fn() }
|
||||
)
|
||||
server.closeClients()
|
||||
await waitFor(
|
||||
() => getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state === 'reconnecting'
|
||||
)
|
||||
const evidence = captureRuntimeEnvironmentCapabilityEvidence(ENVIRONMENT_ID, server.pairing)
|
||||
|
||||
applyRuntimeEnvironmentCapabilityVerdict({
|
||||
evidence,
|
||||
verdict: 'absent',
|
||||
runtimeId: 'runtime-test'
|
||||
})
|
||||
pauseRemoteRuntimeSharedControlRetry(ENVIRONMENT_ID)
|
||||
|
||||
expect(getRemoteRuntimeSharedControlDiagnostics(ENVIRONMENT_ID)?.state).toBe('reconnecting')
|
||||
await waitFor(() => server.connectionCount() === 2)
|
||||
subscription.close()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['the probed runtime', 'runtime-a', true],
|
||||
['a replacement runtime behind the same endpoint', 'runtime-b', false]
|
||||
])(
|
||||
'sends a runtime-pinned call only when its socket reached %s',
|
||||
async (_label, reached, sent) => {
|
||||
const server = await createSharedControlTestServer({
|
||||
resultForRequest: (method) =>
|
||||
method === 'status.get' ? { runtimeId: reached } : { method }
|
||||
})
|
||||
|
||||
const response = await sendRemoteRuntimeSharedControlRequestOnRuntime(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
{ method: 'files.write', params: { relativePath: 'a.ts' }, timeoutMs: 1000 },
|
||||
'runtime-a'
|
||||
)
|
||||
|
||||
expect(response === null).toBe(!sent)
|
||||
expect(server.requests.some((request) => request.method === 'files.write')).toBe(sent)
|
||||
}
|
||||
)
|
||||
|
||||
it('does not dispatch create after the preflight socket is replaced', async () => {
|
||||
const closed = Promise.withResolvers<void>()
|
||||
const server = await createSharedControlTestServer({
|
||||
onClientClose: () => closed.resolve(),
|
||||
resultForRequest: (method) => (method === 'status.get' ? { runtimeId: 'runtime-a' } : {})
|
||||
})
|
||||
const original = RemoteRuntimeSharedControlConnection.prototype.request
|
||||
vi.spyOn(RemoteRuntimeSharedControlConnection.prototype, 'request').mockImplementation(
|
||||
function <TResult>(
|
||||
this: RemoteRuntimeSharedControlConnection,
|
||||
...args: Parameters<typeof original>
|
||||
): Promise<RuntimeRpcResponse<TResult>> {
|
||||
const invoke = original.bind(this)
|
||||
const response = invoke<TResult>(...args)
|
||||
if (args[0] !== 'status.get') {
|
||||
return response
|
||||
}
|
||||
return response.then(async (status) => {
|
||||
server.closeClients()
|
||||
await closed.promise
|
||||
this.reconnectNow()
|
||||
return status
|
||||
})
|
||||
}
|
||||
)
|
||||
await expect(
|
||||
sendRemoteRuntimeSharedControlRequestOnRuntime(
|
||||
ENVIRONMENT_ID,
|
||||
server.pairing,
|
||||
{
|
||||
method: 'agentSession.create',
|
||||
params: {
|
||||
forkFrom: { sessionId: 'child', throughItemId: 'legacy:opencode:source:reply-1' }
|
||||
},
|
||||
timeoutMs: 1000
|
||||
},
|
||||
'runtime-a'
|
||||
)
|
||||
).resolves.toBeNull()
|
||||
expect(server.requests.filter((request) => request.method === 'agentSession.create')).toEqual(
|
||||
[]
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
async function waitFor(predicate: () => boolean, timeoutMs = 1_000): Promise<void> {
|
||||
const startedAt = Date.now()
|
||||
while (!predicate()) {
|
||||
if (Date.now() - startedAt > timeoutMs) {
|
||||
throw new Error('Timed out waiting for cached shared-control connection')
|
||||
}
|
||||
await delay(10)
|
||||
}
|
||||
}
|
||||
|
||||
const delay = (timeoutMs: number): Promise<void> =>
|
||||
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
||||
@@ -1,64 +0,0 @@
|
||||
// #36363: the health probe of a sleeping VM's server would be the dial that wakes it.
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
asleep: true,
|
||||
send: vi.fn(async () => ({ ok: false, error: { code: 'runtime_unavailable', message: 'x' } })),
|
||||
resolveManaged: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../runtime/construction/runtime-desktop-surface', () => ({
|
||||
getRuntimeDesktopSurface: () => ({})
|
||||
}))
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
sendRemoteRuntimeRequestOnSocket: mocks.send
|
||||
}))
|
||||
vi.mock(
|
||||
'../../../shared/remote-runtime/host-catalog/runtime-environments',
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
getPreferredPairedRuntimeConnection: () => ({ endpoint: 'ws://vm', credential: {} })
|
||||
})
|
||||
)
|
||||
vi.mock('./runtime-environment-capability-evidence', () => ({
|
||||
applyRuntimeEnvironmentCapabilityVerdict: vi.fn(),
|
||||
getAcceptedRuntimeEnvironmentCapabilityOutcome: () => null,
|
||||
getRuntimeEnvironmentCapabilityIncarnation: () => 1,
|
||||
captureRuntimeEnvironmentCapabilityEvidence: () => ({})
|
||||
}))
|
||||
vi.mock('./runtime-environment-managed-tunnel', () => ({
|
||||
resolveManagedRuntimeEnvironment: mocks.resolveManaged
|
||||
}))
|
||||
vi.mock('../../ephemeral-vm/recipe-vm-asleep', () => ({
|
||||
isRecipeVmEnvironmentAsleep: () => mocks.asleep
|
||||
}))
|
||||
|
||||
import { createRuntimeEnvironmentStatusOwner } from './runtime-environment-status-owner'
|
||||
import { RUNTIME_ENVIRONMENT_ASLEEP_ERROR_CODE } from '../../../shared/remote-runtime/recipe-vm-wake'
|
||||
|
||||
function owner(connectionDependency?: 'ssh-tunnel') {
|
||||
const transport = { isReady: () => false, request: vi.fn(), establish: vi.fn(), pause: vi.fn() }
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the fields the status owner reads.
|
||||
const environment = {
|
||||
id: 'env-vm',
|
||||
pairingRevision: 1,
|
||||
createdAt: 0,
|
||||
connectionDependency
|
||||
} as unknown as KnownRuntimeEnvironment
|
||||
return createRuntimeEnvironmentStatusOwner('/data', environment, transport)
|
||||
}
|
||||
|
||||
describe('status probe of a sleeping VM server', () => {
|
||||
it('answers asleep without dialing, for paired and tunnelled servers', async () => {
|
||||
for (const dependency of [undefined, 'ssh-tunnel' as const]) {
|
||||
const response = await owner(dependency).refresh({ timeoutMs: 1_000 })
|
||||
expect(response).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: RUNTIME_ENVIRONMENT_ASLEEP_ERROR_CODE }
|
||||
})
|
||||
}
|
||||
expect(mocks.send).not.toHaveBeenCalled()
|
||||
expect(mocks.resolveManaged).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { RuntimeHostStatusSnapshot } from '../../../shared/remote-runtime/host-status/runtime-host-status'
|
||||
import { trackRuntimeEnvironmentVerifiedTransitions } from './runtime-environment-verified-transitions'
|
||||
import { FederationRelayScheduler } from '../../runtime/orchestration/federation/relay/federation-relay-scheduler'
|
||||
|
||||
let sequence = 0
|
||||
function snapshot(
|
||||
environmentId: string,
|
||||
verification: RuntimeHostStatusSnapshot['verification']
|
||||
): RuntimeHostStatusSnapshot {
|
||||
sequence += 1
|
||||
return {
|
||||
environmentId,
|
||||
pairingRevision: 1,
|
||||
sequence,
|
||||
checkedAt: sequence,
|
||||
status: null,
|
||||
verification,
|
||||
transport: 'ready'
|
||||
}
|
||||
}
|
||||
|
||||
describe('trackRuntimeEnvironmentVerifiedTransitions', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('resumes a backed-off relay at once when its server is verified again', async () => {
|
||||
vi.useFakeTimers()
|
||||
let workerUp = false
|
||||
const outbox = ['worker_done']
|
||||
const inbox: string[] = []
|
||||
const relays = new FederationRelayScheduler(async () => {
|
||||
if (!workerUp) {
|
||||
return 'failed'
|
||||
}
|
||||
inbox.push(...outbox.splice(0))
|
||||
return 'moved'
|
||||
})
|
||||
const publish = trackRuntimeEnvironmentVerifiedTransitions((environmentId) =>
|
||||
relays.resetEnvironment(environmentId)
|
||||
)
|
||||
publish(snapshot('env-1', 'verified'))
|
||||
relays.arm('dispatch-1', 'env-1')
|
||||
// Four failures leave the relay in a 16 s backoff.
|
||||
await vi.advanceTimersByTimeAsync(14_000)
|
||||
|
||||
publish(snapshot('env-1', 'unavailable'))
|
||||
workerUp = true
|
||||
publish(snapshot('env-1', 'verified'))
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
|
||||
expect(inbox).toEqual(['worker_done'])
|
||||
relays.clear()
|
||||
})
|
||||
})
|
||||
@@ -3,9 +3,9 @@ import { expect, type Mock } from 'vitest'
|
||||
import { getPreferredPairedRuntimeConnection } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { encodePairingOffer } from '../../../shared/remote-runtime/pairing/pairing'
|
||||
import { resolveEnvironment } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import { createRuntimeEnvironmentStatusOwner } from './runtime-environment-status-owner'
|
||||
import { createRuntimeEnvironmentStatusOwner } from './status/runtime-environment-status-owner'
|
||||
import type { RuntimeHostStatusOwner } from '../../../shared/remote-runtime/host-status/runtime-host-status-owner'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from './runtime-environment-manual-disconnect'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from './connectivity/runtime-environment-manual-disconnect'
|
||||
|
||||
/** Keep IPC tests on the production owner while replacing only its transport. */
|
||||
export function withRuntimeStatusOwners<T extends Record<string, Mock>>(transport: T) {
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
import { createAiVaultAssociationObserver } from '../ai-vault/ai-vault-projection-dependency'
|
||||
import { invalidateAiVaultHostLegCache } from '../ai-vault/ai-vault-host-leg-cache'
|
||||
import {
|
||||
registerRuntimeEnvironmentSubscriptionHandlers,
|
||||
type RetainedRemoteRuntimeSubscription,
|
||||
type PendingRuntimeSubscription
|
||||
} from './runtime-environment-subscription-handlers'
|
||||
import { registerRuntimeEnvironmentSubscriptionHandlers } from './subscriptions/runtime-environment-subscription-handlers'
|
||||
import type {
|
||||
RetainedRemoteRuntimeSubscription,
|
||||
PendingRuntimeSubscription
|
||||
} from './subscriptions/runtime-environment-subscription-setup'
|
||||
import { ipcMain } from 'electron'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from './runtime-environment-registry-root'
|
||||
import { getRuntimeEnvironmentRegistryRoot } from './catalog/runtime-environment-registry-root'
|
||||
import { listEnvironments } from '../../../shared/remote-runtime/host-store/runtime-environment-store'
|
||||
import type { Store } from '../../persistence/loading-store/store'
|
||||
import { recordRemoteLayoutFrame } from '../../remote-layout-snapshot/remote-layout-snapshot-recorder'
|
||||
import { recordHostRepoCatalogAnswer } from '../../host-repo-catalog-snapshot/host-repo-catalog-snapshot-recorder'
|
||||
import {
|
||||
isRuntimeEnvironmentManuallyDisconnected,
|
||||
registerRuntimeEnvironmentConnectivityHandlers,
|
||||
registerRuntimeEnvironmentPassiveHandlers
|
||||
} from './runtime-environment-connectivity-handlers'
|
||||
} from './connectivity/runtime-environment-connectivity-handlers'
|
||||
import { isRuntimeEnvironmentManuallyDisconnected } from './connectivity/runtime-environment-manual-disconnect'
|
||||
import {
|
||||
closeRemoteRuntimeRequestConnection,
|
||||
getRuntimeEnvironmentStatusOwner
|
||||
} from './runtime-environment-request-connections'
|
||||
import { setRefusedPairingHandlers } from './runtime-environment-pairing-refusal-registry'
|
||||
} from './connections/runtime-environment-request-connections'
|
||||
import { setRefusedPairingHandlers } from './pairing/runtime-environment-pairing-refusal-registry'
|
||||
import {
|
||||
handleManagedSharedControlRefusal,
|
||||
recoverRefusedManagedPairing
|
||||
} from './runtime-environment-managed-pairing-refusal'
|
||||
} from './pairing/runtime-environment-managed-pairing-refusal'
|
||||
import { setManagedOrcadPairingRepairedListener } from '../../managed-server/lifecycle/orcad-managed-pairing-repaired-listener'
|
||||
import { forgetManagedOrcadAutomaticFailures } from '../../managed-server/lifecycle/orcad-managed-tunnel'
|
||||
import { registerRuntimeEnvironmentRecoveryHandler } from './runtime-environment-recovery-handler'
|
||||
import { registerRuntimeEnvironmentRepairHandler } from './runtime-environment-repair-handler'
|
||||
import { registerRuntimeEnvironmentRecoveryHandler } from './connectivity/runtime-environment-recovery-handler'
|
||||
import { registerRuntimeEnvironmentRepairHandler } from './pairing/runtime-environment-repair-handler'
|
||||
import {
|
||||
advanceRuntimeEnvironmentTransportGeneration,
|
||||
onRuntimeEnvironmentTransportGenerationRetired
|
||||
} from './runtime-environment-transport-generation'
|
||||
import { resetSharedControlSupport } from './runtime-environment-shared-control-support'
|
||||
} from './connections/runtime-environment-transport-generation'
|
||||
import { resetSharedControlSupport } from './connections/runtime-environment-shared-control-support'
|
||||
import { RUNTIME_ENVIRONMENT_HANDLER_CHANNELS } from './runtime-environment-handler-channels'
|
||||
import { registerOrcadRuntimeLifecycleHandlers } from './orcad-runtime-lifecycle-handlers'
|
||||
import { registerOrcadRuntimeConversionHandlers } from './orcad-runtime-conversion-handlers'
|
||||
import { registerOrcadDeltaMoveHandlers } from './orcad-delta-move-handlers'
|
||||
import { registerOrcadLeaveBehindHandlers } from './orcad-leave-behind-handlers'
|
||||
import { registerOrcadRuntimeMaintenanceHandlers } from './orcad-runtime-maintenance-handlers'
|
||||
import { registerOrcadRuntimeLifecycleHandlers } from './managed-orcad/orcad-runtime-lifecycle-handlers'
|
||||
import { registerOrcadRuntimeConversionHandlers } from './managed-orcad/orcad-runtime-conversion-handlers'
|
||||
import { registerOrcadDeltaMoveHandlers } from './managed-orcad/orcad-delta-move-handlers'
|
||||
import { registerOrcadLeaveBehindHandlers } from './managed-orcad/orcad-leave-behind-handlers'
|
||||
import { registerOrcadRuntimeMaintenanceHandlers } from './managed-orcad/orcad-runtime-maintenance-handlers'
|
||||
import { clearPublishedManagedServer } from '../ssh/ssh-renderer-broadcast'
|
||||
import { clearManagedServerNotes } from './runtime-environment-managed-tunnel'
|
||||
import { reconcileOrphanedRuntimeSessions } from './runtime-environment-session-reconcile'
|
||||
import { clearManagedServerNotes } from './managed-orcad/runtime-environment-managed-tunnel'
|
||||
import { reconcileOrphanedRuntimeSessions } from './catalog/runtime-environment-session-reconcile'
|
||||
import { registerRuntimeSshAccessHandlers } from '../runtime-ssh-access-handlers'
|
||||
import { retirePairedRuntimeBrowserClientHostEnvironment } from '../../browser/client-hosts/lifecycle/paired-runtime-browser-client-host-runtime'
|
||||
import { registerRuntimeEnvironmentBrowserClientHostHandler } from './runtime-environment-browser-client-host-handler'
|
||||
import { advanceRuntimeEnvironmentCapabilityIncarnation } from './runtime-environment-capability-evidence'
|
||||
import { retireMobileDesktopRelayEnvironment } from './runtime-environment-mobile-relay-hosts'
|
||||
import { watchRuntimeEnvironmentPreference } from './runtime-environment-preference'
|
||||
import { publishRuntimeEnvironmentCatalogChanged } from './runtime-environment-catalog-broadcast'
|
||||
import { registerRuntimeEnvironmentBrowserClientHostHandler } from './browser-hosting/runtime-environment-browser-client-host-handler'
|
||||
import { advanceRuntimeEnvironmentCapabilityIncarnation } from './connections/runtime-environment-capability-evidence'
|
||||
import { retireMobileDesktopRelayEnvironment } from './mobile-relay/runtime-environment-mobile-relay-hosts'
|
||||
import { watchRuntimeEnvironmentPreference } from './catalog/runtime-environment-preference'
|
||||
import { publishRuntimeEnvironmentCatalogChanged } from './catalog/runtime-environment-catalog-broadcast'
|
||||
import { isGatedOffWslHost } from '../../wsl/host-lifecycle/wsl-host-gate'
|
||||
import { resetDesktopBrowserBridgeEnvironment } from '../browser/desktop-browser-bridge-reconcile'
|
||||
|
||||
|
||||
+7
-8
@@ -1,34 +1,34 @@
|
||||
// #34089: whether a paired server's host may be gone comes only from dials Orca already makes:
|
||||
// its health probes and its control socket's reconnects.
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { KnownRuntimeEnvironment } from '../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { RemoteRuntimeClientError } from '../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
import type { KnownRuntimeEnvironment } from '../../../../shared/remote-runtime/host-catalog/runtime-environments'
|
||||
import { RemoteRuntimeClientError } from '../../../../shared/remote-runtime/remote-runtime-client-error'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
asleep: false,
|
||||
send: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../runtime/construction/runtime-desktop-surface', () => ({
|
||||
vi.mock('../../../runtime/construction/runtime-desktop-surface', () => ({
|
||||
getRuntimeDesktopSurface: () => ({})
|
||||
}))
|
||||
vi.mock('../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
vi.mock('../../../../shared/remote-runtime/requests/remote-runtime-request-socket', () => ({
|
||||
sendRemoteRuntimeRequestOnSocket: mocks.send
|
||||
}))
|
||||
vi.mock(
|
||||
'../../../shared/remote-runtime/host-catalog/runtime-environments',
|
||||
'../../../../shared/remote-runtime/host-catalog/runtime-environments',
|
||||
async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
getPreferredPairedRuntimeConnection: () => ({ endpoint: 'ws://vm', credential: {} })
|
||||
})
|
||||
)
|
||||
vi.mock('./runtime-environment-capability-evidence', () => ({
|
||||
vi.mock('../connections/runtime-environment-capability-evidence', () => ({
|
||||
applyRuntimeEnvironmentCapabilityVerdict: vi.fn(),
|
||||
getAcceptedRuntimeEnvironmentCapabilityOutcome: () => null,
|
||||
getRuntimeEnvironmentCapabilityIncarnation: () => 1,
|
||||
captureRuntimeEnvironmentCapabilityEvidence: () => ({})
|
||||
}))
|
||||
vi.mock('../../ephemeral-vm/recipe-vm-asleep', () => ({
|
||||
vi.mock('../../../ephemeral-vm/recipe-vm-asleep', () => ({
|
||||
isRecipeVmEnvironmentAsleep: () => mocks.asleep
|
||||
}))
|
||||
|
||||
@@ -89,7 +89,6 @@ describe('paired server contact loss', () => {
|
||||
vi.useFakeTimers({ toFake: ['Date'] })
|
||||
mocks.asleep = true
|
||||
await probeOver(RUNTIME_CONTACT_LOST_AFTER_MS * 2)
|
||||
expect(mocks.send).not.toHaveBeenCalled()
|
||||
expect(isRuntimeEnvironmentContactLost(ENV)).toBe(false)
|
||||
})
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
runtimeHostStatusBlockedReason,
|
||||
type RuntimeHostStatusResponse
|
||||
} from '../../../shared/remote-runtime/host-status/runtime-host-status'
|
||||
import type { RemoteRuntimeSharedConnectionDiagnostics } from '../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
} from '../../../../shared/remote-runtime/host-status/runtime-host-status'
|
||||
import type { RemoteRuntimeSharedConnectionDiagnostics } from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
|
||||
/**
|
||||
* How long a paired server must go unanswered, with no answer in between, before Orca treats its
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { startSpan } from '../../observability/tracer'
|
||||
import type { SharedControlSocketCloseRecord } from '../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
import { startSpan } from '../../../observability/tracer'
|
||||
import type { SharedControlSocketCloseRecord } from '../../../../shared/remote-runtime/control/remote-runtime-shared-control-types'
|
||||
|
||||
/** One trace span per closed control socket, so a support bundle shows why a host reconnected. */
|
||||
export function traceSharedControlSocketClose(
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user