103 lines · 3.8 KB · TypeScript
RawDownload
/** * A device host is a machine with simulators or emulators on it. The service * layer only ever talks to this interface, so a future SSH or cloud host slots * in beside `LocalDeviceHost` without touching discovery, the proxy, or the * MCP tools. * * Every ready host presents a loopback origin where expo-device-hub answers. * Hosts add an agent-device daemon endpoint only after agent access is granted. * For the local host both run on this machine; a remote host would forward * them here. */import type {  DeviceHostId,  DeviceHostSummary,  DevicePlatform,  DevicePlatformAvailability,} from "@t3tools/contracts";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema"; export class DeviceHostError extends Schema.TaggedError<DeviceHostError>()("DeviceHostError", {  hostId: Schema.String,  step: Schema.String,  cause: Schema.Defect(),}) {  override get message(): string {    return `Device host ${this.hostId} failed while ${this.step}.`;  }} export class DeviceHostTimeoutError extends Schema.TaggedError<DeviceHostTimeoutError>()(  "DeviceHostTimeoutError",  { hostId: Schema.String, timeoutMs: Schema.Number },) {  override get message(): string {    return `Device host ${this.hostId} did not start agent tools within ${this.timeoutMs} ms.`;  }} export interface DeviceHubEndpoint {  /** Loopback origin of expo-device-hub, e.g. `http://127.0.0.1:3400`. */  readonly origin: string;} export interface AgentDeviceEndpoint {  readonly baseUrl: string;  readonly token: string;  /** Host-local path of the agent-device entry script. The provider uses a separate local CLI install. */  readonly entryPath: string;} export interface DeviceHostReady {  readonly nodePath: string;  readonly hub: DeviceHubEndpoint;  /**   * Runs a host command (`xcrun`, `adb`, or a helper bundled with the hub)   * where the devices live. On the local host this is a plain spawn; a   * remote host would run it over its transport.   */  readonly run: (    command: string,    args: ReadonlyArray<string>,    options?: { readonly timeoutMs?: number; readonly stdin?: string },  ) => Effect.Effect<{ readonly stdout: string; readonly stderr: string; readonly code: number }>;  /** Absolute paths of helper binaries vendored with the hub, when present. */  readonly helpers: {    readonly serveSimAxSettings: string | null;    readonly serveSimCli: string | null;  };} export interface DeviceHostAgentReady extends DeviceHostReady {  readonly agentDevice: AgentDeviceEndpoint;} export class DeviceHost extends Context.Service<  DeviceHost,  {    readonly id: DeviceHostId;    readonly summary: Effect.Effect<DeviceHostSummary>;    readonly platformAvailability: (      platform: DevicePlatform,    ) => Effect.Effect<DevicePlatformAvailability>;    /**     * Installs tools on first use and starts the helper processes. Idempotent:     * concurrent callers share one start, and a ready host returns immediately.     */    readonly ensureReady: (      onPhase: (phase: "installing" | "starting") => Effect.Effect<void>,    ) => Effect.Effect<DeviceHostReady, DeviceHostError>;    /** Installs and starts agent-device after the user grants agent access. */    readonly ensureAgentReady: (      onPhase: (phase: "installing" | "starting") => Effect.Effect<void>,    ) => Effect.Effect<DeviceHostAgentReady, DeviceHostError | DeviceHostTimeoutError>;    /** Current endpoints when already running, without starting anything. */    readonly current: Effect.Effect<DeviceHostReady | null>;    /** Stops only agent-device. Manual viewing through the hub stays available. */    readonly stopAgent: Effect.Effect<void>;    /** Stops helpers. Devices themselves keep running; the user owns those. */    readonly stop: Effect.Effect<void>;  }>()("t3/device/DeviceHost") {}