diff --git a/src/daemon/__tests__/provider-device-admission.test.ts b/src/daemon/__tests__/provider-device-admission.test.ts new file mode 100644 index 0000000000..c90ea1b65a --- /dev/null +++ b/src/daemon/__tests__/provider-device-admission.test.ts @@ -0,0 +1,40 @@ +import { afterEach, beforeEach, expect, test } from 'vitest'; +import type { DeviceInfo } from '@agent-device/kernel/device'; +import { + installProviderDeviceAdmission, + isActiveProviderDevice, + providerDeviceAdmission, +} from '../provider-device-admission.ts'; + +const providerDevice = { id: 'provider-1', name: 'Cloud iPhone' } as unknown as DeviceInfo; +const localDevice = { id: 'local-1', name: 'iPhone 16' } as unknown as DeviceInfo; + +let previous = providerDeviceAdmission(); + +beforeEach(() => { + previous = providerDeviceAdmission(); +}); + +afterEach(() => { + installProviderDeviceAdmission(previous); +}); + +test('an un-composed process treats every device as local', () => { + installProviderDeviceAdmission({ isActive: () => false }); + expect(isActiveProviderDevice(providerDevice)).toBe(false); + expect(isActiveProviderDevice(localDevice)).toBe(false); +}); + +test('the installed admission is what the daemon decides on', () => { + installProviderDeviceAdmission({ isActive: (device) => device.id === 'provider-1' }); + expect(isActiveProviderDevice(providerDevice)).toBe(true); + expect(isActiveProviderDevice(localDevice)).toBe(false); +}); + +test('the fact is read per call, so a request-scoped scope stays live', () => { + let owned = false; + installProviderDeviceAdmission({ isActive: () => owned }); + expect(isActiveProviderDevice(providerDevice)).toBe(false); + owned = true; + expect(isActiveProviderDevice(providerDevice)).toBe(true); +}); diff --git a/src/daemon/__tests__/request-router-android-modal.test.ts b/src/daemon/__tests__/request-router-android-modal.test.ts index f49af77108..2ab2565c54 100644 --- a/src/daemon/__tests__/request-router-android-modal.test.ts +++ b/src/daemon/__tests__/request-router-android-modal.test.ts @@ -16,7 +16,15 @@ import { import type { SessionState } from '../session-state.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; -import { createProviderDeviceRuntimeRequestProviders } from '../../provider-device-runtime.ts'; +import { + createProviderDeviceRuntimeRequestProviders, + isActiveProviderDevice, +} from '../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../provider-device-admission.ts'; + +// Root composition installs the daemon's provider-device admission; this test composes the +// request providers the same way, so it installs the fact the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import type { ProviderDeviceRuntime } from '@agent-device/contracts/device'; import { makeTestScreenRecordingResource } from '../../__tests__/test-utils/screen-recording-live-handle.ts'; import { androidObservation } from '../../platform-runtime.ts'; diff --git a/src/daemon/__tests__/selector-capture-fixture.ts b/src/daemon/__tests__/selector-capture-fixture.ts index 225ff11938..0188688d9e 100644 --- a/src/daemon/__tests__/selector-capture-fixture.ts +++ b/src/daemon/__tests__/selector-capture-fixture.ts @@ -12,7 +12,7 @@ import { snapshotRuntimeOperationFacts, } from '@agent-device/contracts/snapshot-runtime'; import { deviceShape, type DeviceInfo } from '@agent-device/kernel/device'; -import { isActiveProviderDevice } from '../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../provider-device-admission.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; import { unavailableDeviceRuntimeGateway } from './test-device-runtime-gateway.ts'; diff --git a/src/daemon/__tests__/session-device-resolution.test.ts b/src/daemon/__tests__/session-device-resolution.test.ts index a92b41b116..45c5c37234 100644 --- a/src/daemon/__tests__/session-device-resolution.test.ts +++ b/src/daemon/__tests__/session-device-resolution.test.ts @@ -8,7 +8,7 @@ import { } from '../session-device-resolution.ts'; import { appleSessionObservation } from '../../platform-runtime-apple-resources.ts'; import { resolveTargetDevice } from '@agent-device/device-selection/dispatch-resolve'; -import { isActiveProviderDevice } from '../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../provider-device-admission.ts'; import { ensureDeviceReady } from '../device-ready.ts'; vi.mock('../../platform-runtime-apple-resources.ts', async (importOriginal) => ({ @@ -18,7 +18,7 @@ vi.mock('../../platform-runtime-apple-resources.ts', async (importOriginal) => ( vi.mock('@agent-device/device-selection/dispatch-resolve', () => ({ resolveTargetDevice: vi.fn(), })); -vi.mock('../../provider-device-runtime.ts', () => ({ +vi.mock('../provider-device-admission.ts', () => ({ isActiveProviderDevice: vi.fn(() => false), })); vi.mock('../device-ready.ts', () => ({ diff --git a/src/daemon/__tests__/snapshot-runtime-fixture.ts b/src/daemon/__tests__/snapshot-runtime-fixture.ts index aa8fefb7be..7a64abc51f 100644 --- a/src/daemon/__tests__/snapshot-runtime-fixture.ts +++ b/src/daemon/__tests__/snapshot-runtime-fixture.ts @@ -33,7 +33,7 @@ import { import { applePlugin } from '@agent-device/platform-apple'; import { type DispatchContext } from '../../core/dispatch-context.ts'; import { getRequestSignal } from '@agent-device/host-kit/request'; -import { isActiveProviderDevice } from '../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../provider-device-admission.ts'; import type { BindDeviceRuntime, InspectDeviceRuntimeFacts } from '../request-runtime-binding.ts'; import { unavailableDeviceRuntimeGateway } from './test-device-runtime-gateway.ts'; import { writeSolidPng } from './screenshot-runtime-fixture.ts'; diff --git a/src/daemon/android-foreground-surface.ts b/src/daemon/android-foreground-surface.ts index 1a84ab7eac..c1f6af601a 100644 --- a/src/daemon/android-foreground-surface.ts +++ b/src/daemon/android-foreground-surface.ts @@ -1,6 +1,6 @@ import type { AndroidObservationAdapter } from '@agent-device/contracts/android-observation'; import { AppError } from '@agent-device/kernel/errors'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; import type { SessionState } from './session-state.ts'; export type AndroidEscapeSurface = { diff --git a/src/daemon/android-system-dialog.ts b/src/daemon/android-system-dialog.ts index 3bd079bd36..8816bfe79a 100644 --- a/src/daemon/android-system-dialog.ts +++ b/src/daemon/android-system-dialog.ts @@ -10,7 +10,7 @@ import { centerOfRect, type SnapshotNode } from '@agent-device/kernel/snapshot'; import { isSnapshotNodeInteractionBlocked } from '@agent-device/capture-kit/snapshot-occlusion'; import { expireRefFrame } from './ref-frame.ts'; import type { SessionState } from './session-state.ts'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; const ANDROID_BLOCKING_MODAL_PATTERN = /\bis(?:n(?:'|'|')?t| not)\s+responding\b/i; const ANDROID_CLOSE_APP_PATTERN = /^close app$/i; diff --git a/src/daemon/device-ready.ts b/src/daemon/device-ready.ts index 15338570e9..0592781b6f 100644 --- a/src/daemon/device-ready.ts +++ b/src/daemon/device-ready.ts @@ -1,6 +1,6 @@ import type { DeviceInfo } from '@agent-device/kernel/device'; import { ensureLocalPlatformDeviceReady } from '../platform-runtime-device-ready.ts'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; import { createTtlMemo } from '@agent-device/kernel/ttl-memo'; // Exported so unit tests can assert TTL behavior without duplicating the value. diff --git a/src/daemon/direct-ios-selector.ts b/src/daemon/direct-ios-selector.ts index 66eb092f91..8eb0688635 100644 --- a/src/daemon/direct-ios-selector.ts +++ b/src/daemon/direct-ios-selector.ts @@ -1,6 +1,6 @@ import { isIosFamily } from '@agent-device/kernel/device'; import type { SnapshotNode } from '@agent-device/kernel/snapshot'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; import { isPostGestureStabilizationPending } from './deferred-interaction-outcome.ts'; import type { SessionState } from './session-state.ts'; import { readSimpleSelectorTarget } from '@agent-device/selectors'; diff --git a/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts b/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts index 57d14b1557..2adb0e5820 100644 --- a/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts +++ b/src/daemon/handlers/__tests__/session-doctor-warmup.test.ts @@ -1,6 +1,6 @@ import { beforeEach, expect, test, vi } from 'vitest'; import type { DeviceInfo } from '@agent-device/kernel/device'; -import { isActiveProviderDevice } from '../../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../../provider-device-admission.ts'; import { handleDoctorCommand } from '../session-doctor.ts'; import { createHostDiagnostics } from '../../../platform-runtime-host-diagnostics.ts'; import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; @@ -28,7 +28,7 @@ vi.mock('../session-doctor-app.ts', () => ({ vi.mock('../session-doctor-metro.ts', () => ({ probeMetro: vi.fn(async () => ({ id: 'metro', status: 'pass', summary: 'mocked' })), })); -vi.mock('../../../provider-device-runtime.ts', () => ({ +vi.mock('../../provider-device-admission.ts', () => ({ isActiveProviderDevice: vi.fn(() => false), })); diff --git a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts index 97c98f6ae8..580e3c1e02 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts @@ -2,7 +2,15 @@ import { test, expect, vi, beforeEach } from 'vitest'; import * as os from 'node:os'; import * as path from 'node:path'; import { LeaseRegistry } from '../../lease-registry.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import { createTestDeviceInventoryGateways } from '../../../__tests__/test-utils/device-inventory-gateways.ts'; import { makeSessionStore } from '../../../__tests__/test-utils/store-factory.ts'; import { makeSession } from '../../../__tests__/test-utils/session-factories.ts'; diff --git a/src/daemon/handlers/__tests__/snapshot-handler-capture-retry.test.ts b/src/daemon/handlers/__tests__/snapshot-handler-capture-retry.test.ts index d6097d1ff8..59350cb063 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler-capture-retry.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler-capture-retry.test.ts @@ -6,7 +6,15 @@ import { resetGetRuntimeFixture, } from '../../__tests__/interaction-get-runtime-fixture.ts'; import { captureSnapshot } from '../../snapshot-capture.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import { buildInteractionSurfaceSignature } from '../../interaction-outcome-policy.ts'; import { buildNodes } from '../../../__tests__/test-utils/snapshot-builders.ts'; import { resetSnapshotRuntimeFixture } from '../../__tests__/snapshot-runtime-fixture.ts'; diff --git a/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts b/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts index 42de3d411d..2712c6ab27 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler-freshness.test.ts @@ -4,7 +4,15 @@ import { resetGetRuntimeFixture } from '../../__tests__/interaction-get-runtime- import fs from 'node:fs'; import { captureSnapshot } from '../../snapshot-capture.ts'; import { SessionStore } from '../../session-store.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import { AppError } from '@agent-device/kernel/errors'; import { buildInteractionSurfaceSignature } from '../../interaction-outcome-policy.ts'; import { buildSnapshotPresentationKey } from '@agent-device/kernel/snapshot'; diff --git a/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts b/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts index bcdc9b6335..90a38568cf 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts @@ -1,7 +1,15 @@ import { test, expect, vi, afterEach, beforeEach } from 'vitest'; import { legacyDispatchCapture } from '../../__tests__/legacy-snapshot-capture-fixture.ts'; import { resetGetRuntimeFixture } from '../../__tests__/interaction-get-runtime-fixture.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import type { SessionState } from '../../session-state.ts'; import { buildSnapshotPresentationKey } from '@agent-device/kernel/snapshot'; import { diff --git a/src/daemon/handlers/__tests__/snapshot-handler.test.ts b/src/daemon/handlers/__tests__/snapshot-handler.test.ts index 627e2998aa..fada858f56 100644 --- a/src/daemon/handlers/__tests__/snapshot-handler.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-handler.test.ts @@ -2,7 +2,15 @@ import { test, expect, vi, afterEach, beforeEach } from 'vitest'; import { legacyDispatchCapture } from '../../__tests__/legacy-snapshot-capture-fixture.ts'; import { resetGetRuntimeFixture } from '../../__tests__/interaction-get-runtime-fixture.ts'; import { resetSnapshotRuntimeFixture } from '../../__tests__/snapshot-runtime-fixture.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import type { DaemonResponse } from '../../daemon-request.ts'; import type { SessionState } from '../../session-state.ts'; import { diff --git a/src/daemon/handlers/__tests__/snapshot-session-cleanup.test.ts b/src/daemon/handlers/__tests__/snapshot-session-cleanup.test.ts index 78867e619d..7b033fb6e8 100644 --- a/src/daemon/handlers/__tests__/snapshot-session-cleanup.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-session-cleanup.test.ts @@ -14,7 +14,15 @@ import { platformResourceCleanup } from '../../../platform-runtime-resource-clea import { closeIosApp } from '@agent-device/platform-apple/app-lifecycle'; import { stopIosRunnerSession } from '@agent-device/platform-apple/runner/operations'; import { IOS_SIMULATOR } from '../../../__tests__/test-utils/device-fixtures.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import type { ProviderDeviceRuntime } from '@agent-device/contracts/device'; const mockStopIosRunnerSession = vi.mocked(stopIosRunnerSession); diff --git a/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts b/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts index c3aca61036..22010e1d47 100644 --- a/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts +++ b/src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts @@ -1,7 +1,15 @@ import { test, expect, vi, afterEach, beforeEach } from 'vitest'; import { legacyDispatchCapture } from '../../__tests__/legacy-snapshot-capture-fixture.ts'; import { handleSnapshotCommands as handleProductionSnapshotCommands } from '../snapshot.ts'; -import { setActiveProviderDeviceRuntimes } from '../../../provider-device-runtime.ts'; +import { + isActiveProviderDevice, + setActiveProviderDeviceRuntimes, +} from '../../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../provider-device-admission.ts'; + +// The daemon reads provider ownership through its own typed admission seam; production +// installs it from root composition, and these tests compose it the same way. +installProviderDeviceAdmission({ isActive: isActiveProviderDevice }); import { platformResourceCleanup } from '../../../platform-runtime-resource-cleanup.ts'; import { fixtureSettingsMutations, diff --git a/src/daemon/handlers/session-doctor.ts b/src/daemon/handlers/session-doctor.ts index d4d7eeab1d..23371a69a8 100644 --- a/src/daemon/handlers/session-doctor.ts +++ b/src/daemon/handlers/session-doctor.ts @@ -3,7 +3,7 @@ import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { isIosFamily, publicPlatformString, type DeviceInfo } from '@agent-device/kernel/device'; import { AppError } from '@agent-device/kernel/errors'; import { emitRequestProgress } from '@agent-device/host-kit/request'; -import { isActiveProviderDevice } from '../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../provider-device-admission.ts'; import { listLocalDeviceInventory, shouldPropagateDeviceInventoryProbeError, diff --git a/src/daemon/interaction/internal/interaction-gesture.ts b/src/daemon/interaction/internal/interaction-gesture.ts index 086715c5e1..5729b4bc46 100644 --- a/src/daemon/interaction/internal/interaction-gesture.ts +++ b/src/daemon/interaction/internal/interaction-gesture.ts @@ -24,7 +24,7 @@ import { type Point, } from '@agent-device/kernel/snapshot'; import { resolveBoundGestureRuntime, type BoundGestureExecutor } from '../../gesture-runtime.ts'; -import { isActiveProviderDevice } from '../../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../../provider-device-admission.ts'; import { sleep } from '@agent-device/host-kit/retry'; import { ensureAndroidBlockingSystemDialogReady } from '../../android-system-dialog.ts'; import { readRefMutationFrame } from '../../ref-frame.ts'; diff --git a/src/daemon/interaction/internal/interaction-touch-reference-frame.ts b/src/daemon/interaction/internal/interaction-touch-reference-frame.ts index b395297ab9..d6072722e3 100644 --- a/src/daemon/interaction/internal/interaction-touch-reference-frame.ts +++ b/src/daemon/interaction/internal/interaction-touch-reference-frame.ts @@ -6,7 +6,7 @@ import type { SessionStore } from '../../session-store.ts'; import { getSnapshotReferenceFrame } from '../../touch-reference-frame.ts'; import type { SessionState } from '../../session-state.ts'; import type { BoundContextFromFlags, CaptureSnapshotForSession } from './types.ts'; -import { isActiveProviderDevice } from '../../../provider-device-runtime.ts'; +import { isActiveProviderDevice } from '../../provider-device-admission.ts'; async function resolveDirectTouchReferenceFrame(params: { session: SessionState; diff --git a/src/daemon/provider-device-admission.ts b/src/daemon/provider-device-admission.ts new file mode 100644 index 0000000000..8d69a4850f --- /dev/null +++ b/src/daemon/provider-device-admission.ts @@ -0,0 +1,38 @@ +import type { DeviceInfo } from '@agent-device/kernel/device'; + +/** + * Whether a device is currently owned by a provider runtime (a cloud or remote lease holder) + * rather than a local simulator or emulator. Callers branch on it for fast paths, foreground + * reads, and hint policy. + */ +export type ProviderDeviceAdmission = Readonly<{ + isActive(device: DeviceInfo): boolean; +}>; + +/** + * The no-provider state, which is what an un-composed process sees: every device is local. + * Root composition replaces it through `installProviderDeviceAdmission` before requests run, + * the same way it installs the other request-scoped runtime capabilities. + */ +const NO_PROVIDER_DEVICE_ADMISSION: ProviderDeviceAdmission = { + isActive: () => false, +}; + +let installedProviderDeviceAdmission: ProviderDeviceAdmission = NO_PROVIDER_DEVICE_ADMISSION; + +/** + * Root composition's installation point. The provider runtime scope is ambient per request, so + * this is the one place the daemon names where the fact comes from, and the daemon never reads + * provider runtime internals itself. + */ +export function installProviderDeviceAdmission(admission: ProviderDeviceAdmission): void { + installedProviderDeviceAdmission = admission; +} + +export function providerDeviceAdmission(): ProviderDeviceAdmission { + return installedProviderDeviceAdmission; +} + +export function isActiveProviderDevice(device: DeviceInfo): boolean { + return installedProviderDeviceAdmission.isActive(device); +} diff --git a/src/daemon/request-generic-dispatch.ts b/src/daemon/request-generic-dispatch.ts index b19ae38ab1..7f82f464d3 100644 --- a/src/daemon/request-generic-dispatch.ts +++ b/src/daemon/request-generic-dispatch.ts @@ -20,7 +20,7 @@ import { resolveRefFrameEffect, shouldGuardAndroidBlockingDialog, } from './daemon-command-registry.ts'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; import { buildActionEventResult } from '@agent-device/session-journal/session-event-action-presentation'; import type { AndroidObservationAdapter } from '@agent-device/contracts/android-observation'; diff --git a/src/daemon/server/daemon-runtime.ts b/src/daemon/server/daemon-runtime.ts index 7735bebc7c..405f08adc9 100644 --- a/src/daemon/server/daemon-runtime.ts +++ b/src/daemon/server/daemon-runtime.ts @@ -5,7 +5,11 @@ import { resolveSessionRequestLogPath } from '../session-artifact-paths.ts'; import { resolveDaemonPaths, resolveDaemonServerMode } from '../config.ts'; import { createDaemonHttpServer } from './http-server.ts'; import { trackDownloadableArtifact } from '../artifact-tracking.ts'; -import { createProviderDeviceRuntimeRequestProviders } from '../../provider-device-runtime.ts'; +import { + createProviderDeviceRuntimeRequestProviders, + isActiveProviderDevice, +} from '../../provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../provider-device-admission.ts'; import { androidObservation, createPlatformRuntimeGateway, @@ -277,6 +281,7 @@ export async function startDaemonRuntime( providerDeviceRuntimes, { providerRuntimeRequiredIds: DEFAULT_PROVIDER_RUNTIME_REQUIRED_IDS }, ); + installProviderDeviceAdmission({ isActive: (device) => isActiveProviderDevice(device) }); const requestPlatformProviders = createRequestPlatformProviders({ providers: { appleRunnerProvider: providerRuntimeProviders.appleRunnerProvider, diff --git a/src/daemon/session-device-resolution.ts b/src/daemon/session-device-resolution.ts index 80c1c02bae..6241b3143d 100644 --- a/src/daemon/session-device-resolution.ts +++ b/src/daemon/session-device-resolution.ts @@ -1,6 +1,6 @@ import { isIosFamily, type DeviceInfo } from '@agent-device/kernel/device'; import { AppError } from '@agent-device/kernel/errors'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; import { appleSessionObservation } from '../platform-runtime-apple-resources.ts'; import { resolveTargetDevice } from '@agent-device/device-selection/dispatch-resolve'; import type { DaemonRequest, DaemonResponse } from './daemon-request.ts'; diff --git a/src/daemon/snapshot-session.ts b/src/daemon/snapshot-session.ts index 8b89f573a2..884e94ac7a 100644 --- a/src/daemon/snapshot-session.ts +++ b/src/daemon/snapshot-session.ts @@ -2,7 +2,7 @@ import { resolveTargetDevice } from '@agent-device/device-selection/dispatch-res import type { PlatformResourceCleanup } from './platform-resource-cleanup.ts'; import type { DaemonRequest } from './daemon-request.ts'; import type { SessionScope, SessionState } from './session-state.ts'; -import { isActiveProviderDevice } from '../provider-device-runtime.ts'; +import { isActiveProviderDevice } from './provider-device-admission.ts'; import { SessionStore } from './session-store.ts'; export async function resolveSessionDevice( diff --git a/test/integration/provider-scenarios/harness.ts b/test/integration/provider-scenarios/harness.ts index 72ac5f1f5e..04d3cdce51 100644 --- a/test/integration/provider-scenarios/harness.ts +++ b/test/integration/provider-scenarios/harness.ts @@ -42,10 +42,16 @@ import { import { createHostDiagnostics } from '../../../src/platform-runtime-host-diagnostics.ts'; import type { PlatformRuntimeProviderRegistration } from '../../../src/platform-runtime-gateway.ts'; import { createProviderPlatformRuntimeRegistrations } from '../../../src/provider-device-runtimes.ts'; +import { isActiveProviderDevice } from '../../../src/provider-device-runtime.ts'; +import { installProviderDeviceAdmission } from '../../../src/daemon/provider-device-admission.ts'; import { unavailableDeviceRuntimeGateway } from '../../../src/daemon/__tests__/test-device-runtime-gateway.ts'; import { openWebSessionNames } from '../../../src/daemon/web-session-names.ts'; +// Match daemon composition (src/daemon/server/daemon-runtime.ts): the daemon decides on provider +// ownership through its own admission seam, which root composition installs. +installProviderDeviceAdmission({ isActive: (device) => isActiveProviderDevice(device) }); + const PROVIDER_SCENARIO_TOKEN = 'provider-scenario-token'; const PROVIDER_SCENARIO_TEMP_REMOVE_OPTIONS = { recursive: true,