Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/daemon/__tests__/provider-device-admission.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
10 changes: 9 additions & 1 deletion src/daemon/__tests__/request-router-android-modal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/__tests__/selector-capture-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
4 changes: 2 additions & 2 deletions src/daemon/__tests__/session-device-resolution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand All @@ -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', () => ({
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/__tests__/snapshot-runtime-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/android-foreground-surface.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/android-system-dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/device-ready.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/direct-ios-selector.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
4 changes: 2 additions & 2 deletions src/daemon/handlers/__tests__/session-doctor-warmup.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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),
}));

Expand Down
10 changes: 9 additions & 1 deletion src/daemon/handlers/__tests__/session-relaunch-close.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 9 additions & 1 deletion src/daemon/handlers/__tests__/snapshot-handler-wait.test.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion src/daemon/handlers/__tests__/snapshot-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 9 additions & 1 deletion src/daemon/handlers/__tests__/snapshot-session-cleanup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 9 additions & 1 deletion src/daemon/handlers/__tests__/snapshot-settings-handler.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/handlers/session-doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/interaction/internal/interaction-gesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
38 changes: 38 additions & 0 deletions src/daemon/provider-device-admission.ts
Original file line number Diff line number Diff line change
@@ -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);
}
2 changes: 1 addition & 1 deletion src/daemon/request-generic-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
7 changes: 6 additions & 1 deletion src/daemon/server/daemon-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/session-device-resolution.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/daemon/snapshot-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading
Loading