import z from "@deepseek-ai/schemastery"; import { ChildProcessWithoutNullStreams } from "node:child_process"; import { Readable } from "node:stream"; import { Context, Service } from "@deepseek-ai/cordis"; import { WebRoute } from "@deepseek-ai/dsh-host-webserver"; //#region src/storage.d.ts /** Persistent record containing only a digest of the long-lived device credential. */ interface StoredDevice { readonly id: string; readonly label: string; readonly tokenDigest: string; readonly createdAt: number; readonly expiresAt: number; readonly lastSeenAt: number; /** Legacy tombstone accepted on load; AccessController removes it during initialization. */ readonly revokedAt?: number; } /** Versioned device state. Raw device and Session credentials are never members. */ interface DeviceSnapshot { readonly version: 1; readonly devices: readonly StoredDevice[]; } /** Persistence seam for device-token digests and revocation metadata. */ interface DeviceStore { load(): Promise; save(snapshot: DeviceSnapshot): Promise; } /** Validate durable data before it can authorize a device. */ declare function parseDeviceSnapshot(value: unknown, maximumDevices?: number): DeviceSnapshot; /** Atomic JSON implementation with symlink refusal and owner-only file creation. */ declare class JsonDeviceStore implements DeviceStore { private readonly file; private readonly maximumDevices; constructor(file: string, maximumDevices?: number); load(): Promise; save(snapshot: DeviceSnapshot): Promise; } /** In-memory store useful for embedding and deterministic tests. */ declare class MemoryDeviceStore implements DeviceStore { private snapshot; constructor(initial?: DeviceSnapshot); load(): Promise; save(snapshot: DeviceSnapshot): Promise; /** Return a defensive copy for assertions or administrative export. */ inspect(): DeviceSnapshot; } //#endregion //#region src/access.d.ts /** Stable error categories converted to deliberately terse HTTP responses. */ declare class AccessError extends Error { readonly status: number; readonly code: string; constructor(status: number, code: string); } /** Resource and lifetime controls for device authentication. */ interface AccessControllerOptions { readonly pairingTtlMs: number; readonly deviceTtlMs: number; readonly sessionTtlMs: number; readonly maxDevices: number; readonly maxSessions: number; readonly rateLimitWindowMs: number; readonly maxPairingAttempts: number; readonly maxRateLimitKeys: number; readonly now?: () => number; } /** Values issued once after pairing; only digests survive the response. */ interface PairingResult { readonly deviceId: string; readonly deviceToken: string; readonly deviceExpiresAt: number; readonly sessionToken: string; readonly csrfToken: string; readonly sessionExpiresAt: number; } /** Values issued after renewal with the persistent HttpOnly device Cookie. */ interface RenewalResult { readonly deviceId: string; readonly sessionToken: string; readonly csrfToken: string; readonly sessionExpiresAt: number; } /** Authenticated Session identity retained only inside the gateway. */ interface SessionAuthorization { readonly sessionKey: string; readonly deviceId: string; readonly expiresAt: number; } /** Result of a persistent-device reachability check without opening a Session. */ interface DeviceProbeResult { readonly deviceId: string; readonly deviceExpiresAt: number; } /** Why a short-lived Session ended; only device revocation is sent to clients. */ type SessionEndReason = 'logout' | 'expired' | 'evicted' | 'revoked'; /** Safe device metadata returned by the loopback administration API. */ interface DeviceSummary { readonly id: string; readonly label: string; readonly createdAt: number; readonly expiresAt: number; readonly lastSeenAt: number; readonly revokedAt?: number; } /** Fixed-window limiter whose attacker-controlled key table is itself bounded. */ declare class BoundedRateLimiter { private readonly limit; private readonly windowMs; private readonly maximumKeys; private readonly buckets; constructor(limit: number, windowMs: number, maximumKeys: number); /** Consume one attempt; unknown keys fail closed when the bounded table is full. */ take(key: string, now: number): boolean; /** Current table size, exposed for bounded-state assertions. */ get size(): number; } /** Pairing, persistent-device, short-Session, revocation, and CSRF state machine. */ declare class AccessController { private readonly store; private readonly options; private readonly now; private readonly pairLimiter; private devices; private pairingWindow; private readonly sessions; private readonly sessionEndedListeners; private mutation; private initialized; private closing; private closeTask; constructor(store: DeviceStore, options: AccessControllerOptions); /** Load and validate digest-only durable state before accepting traffic. */ initialize(): Promise; private requireInitialized; private exclusive; private snapshot; private emitSessionEnded; private removeSession; private pruneSessions; private createSession; /** Open one short pairing window and return its one-time secret to a loopback caller only. */ openPairing(requestedTtlMs?: number): Promise<{ token: string; expiresAt: number; }>; /** Consume the pairing window exactly once and persist only the device-token digest. */ pair(sourceKey: string, token: string, label?: string): Promise; /** Exchange a valid persistent device credential for a new short Session. */ renew(deviceToken: string): Promise; /** Validate a persistent device credential without consuming a Session slot. */ probe(deviceToken: string): Promise; /** Resolve a short Session Cookie without revealing whether device or Session failed. */ authorizeSession(sessionToken: string): SessionAuthorization; /** Require the Session-bound anti-CSRF value for an authenticated mutation. */ assertCsrf(authorization: SessionAuthorization, csrfToken: string | undefined): void; /** End one short Session and notify the gateway to abort its attached work. */ logout(authorization: SessionAuthorization): void; /** Durably delete the device, then end every Session owned by that device. */ revokeDevice(deviceId: string): Promise; /** Remove every persistent credential and terminate every active Session. */ resetDevices(): Promise; /** Safe metadata for the loopback administration surface. */ listDevices(): readonly DeviceSummary[]; /** Pairing status without exposing the one-time secret. */ pairingStatus(): { open: boolean; expiresAt?: number; }; /** Subscribe gateway resources to Session logout, expiry, eviction, and device revocation. */ onSessionEnded(listener: (authorization: SessionAuthorization, reason: SessionEndReason) => void): () => void; /** Stop new operations, drain durable mutations, then clear volatile credentials. */ close(): Promise; private finishClose; /** Bounded volatile-state metrics for tests and local status. */ metrics(): { sessions: number; rateLimitKeys: number; }; } //#endregion //#region src/network.d.ts /** A parsed IP network used to authorize directly connected clients. */ interface ParsedCidr { readonly bits: 32 | 128; readonly network: bigint; readonly prefix: number; readonly source: string; } /** A normalized public authority. A missing port is filled from the bound listener. */ interface AuthoritySpec { readonly hostname: string; readonly port?: number; } /** Parse and canonicalize one IPv4 or IPv6 CIDR. */ declare function parseCidr(source: string): ParsedCidr; /** Whether a directly connected socket address belongs to at least one allowed CIDR. */ declare function addressAllowed(address: string | undefined, cidrs: readonly ParsedCidr[]): boolean; /** Whether an IP literal is loopback and therefore eligible for HTTP-only development. */ declare function isLoopbackAddress(address: string): boolean; /** * Whether a dotted-quad IPv4 literal is globally routable and therefore usable * as a public VPS / remote HTTPS endpoint. Rejects documentation addresses such * as 203.0.113.10 alongside private, shared, and reserved space. */ declare function isGloballyRoutableIpv4(address: string): boolean; /** Parse a bare host or host:port authority without accepting URL components. */ declare function parseAuthority(source: string): AuthoritySpec; /** Resolve an authority against the actual listener port. */ declare function resolveAuthority(spec: AuthoritySpec, listenerPort: number): string; /** Exact Host/Origin/CIDR policy for the directly exposed listener. */ declare class RequestTrustPolicy { readonly cidrs: readonly ParsedCidr[]; readonly authorities: ReadonlySet; readonly origins: ReadonlySet; private readonly scheme; constructor(specs: readonly AuthoritySpec[], listenerPort: number, cidrs: readonly ParsedCidr[], tls: boolean); /** Validate the exact Host header after WHATWG authority normalization. */ acceptsHost(header: string | undefined): boolean; /** Return the canonical accepted Host authority, otherwise undefined. */ canonicalHost(header: string | undefined): string | undefined; /** Validate an exact same-scheme browser Origin. */ acceptsOrigin(header: string | undefined): boolean; /** Return the canonical accepted Origin, otherwise undefined. */ canonicalOrigin(header: string | undefined): string | undefined; } //#endregion //#region src/config.d.ts /** TLS source accepted by the LAN listener. */ interface ProvidedTlsConfig { readonly mode: 'provided'; /** PEM server leaf followed by any intermediate certificate chain. */ readonly certFile: string; readonly keyFile: string; /** Optional PEM intermediates appended after the chain in `certFile`; roots are rejected. */ readonly caFile?: string; } /** HTTP is available only for an explicitly loopback-bound listener. */ interface DisabledTlsConfig { readonly mode: 'disabled'; } type TlsConfig = ProvidedTlsConfig | DisabledTlsConfig; /** Operator-facing plugin configuration. */ interface PluginConfig { /** Optional setup JSON written by the packaged CLI. */ setupFile?: string; /** Preferred HTTPS origin used to derive the public authority and listener port. */ publicOrigin?: string; listenHost?: string; listenPort?: number; upstreamOrigin?: string; publicAuthorities?: string[]; allowedCidrs?: string[]; stateFile: string; /** Internal persisted on/off preference managed by the DSH plugin card. */ controlFile: string; /** Optional user stylesheet served to the authenticated mobile UI. */ customCssFile?: string; /** Optional user script that mounts authenticated mobile-only Web features. */ customScriptFile?: string; /** Internal dedicated mobile layout browser bundle. */ mobileLayoutFile?: string; /** Standalone browser compatibility bundle used before DSH boot. */ mobileCompatibilityFile?: string; /** Stable public discovery identifier; it is not an authentication secret. */ instanceId?: string; /** Managed CA certificate offered to the Android installer after fingerprint binding. */ pairingCaFile?: string; /** First-run state used only while the control file does not exist. */ initiallyEnabled: boolean; tls?: { mode?: 'provided' | 'disabled'; certFile?: string; keyFile?: string; caFile?: string; }; pairingTtlMs?: number; deviceTtlMs?: number; sessionTtlMs?: number; maxDevices?: number; maxSessions?: number; maxConnections?: number; maxActiveRequests?: number; maxWebSockets?: number; maxBodyBytes?: number; upstreamTimeoutMs?: number; rateLimitWindowMs?: number; maxPairingAttempts?: number; maxRateLimitKeys?: number; } /** Resolved, validated security and resource limits. */ interface ResolvedGatewayConfig { readonly listenHost: string; readonly listenPort: number; readonly upstreamOrigin: URL; readonly authorities: readonly AuthoritySpec[]; readonly allowedCidrs: readonly ParsedCidr[]; readonly stateFile: string; /** Local extension root adjacent to the mobile-access state file. */ readonly extensionsDir: string; readonly customCssFile: string; readonly customScriptFile: string; readonly mobileLayoutFile: string; readonly mobileCompatibilityFile: string; readonly instanceId: string; readonly pairingCaFile?: string; readonly tls: TlsConfig; /** Whether the public hop is HTTPS, even when a trusted loopback proxy terminates TLS. */ readonly publicTls: boolean; /** LAN discovery is disabled for private proxy listeners such as Funnel ingress. */ readonly discovery: boolean; readonly pairingTtlMs: number; readonly deviceTtlMs: number; readonly sessionTtlMs: number; readonly maxDevices: number; readonly maxSessions: number; readonly maxConnections: number; readonly maxActiveRequests: number; readonly maxWebSockets: number; readonly maxBodyBytes: number; readonly upstreamTimeoutMs: number; readonly rateLimitWindowMs: number; readonly maxPairingAttempts: number; readonly maxRateLimitKeys: number; } /** Loader-facing defaults; {@link parseGatewayConfig} enforces cross-field security rules. */ declare const Config: z; /** Resolve the hidden runtime-control file independently from gateway configuration. */ declare function parseControlFile(value: unknown): string; /** Parse configuration and reject unsafe topology, credential, and resource combinations. */ declare function parseGatewayConfig(raw: unknown): ResolvedGatewayConfig; //#endregion //#region src/control.d.ts /** Versioned durable preference for the resident mobile-access runtime. */ interface MobileAccessControlState { readonly version: 1; readonly enabled: boolean; } /** Persistence seam for the runtime preference. */ interface MobileAccessControlStore { load(): Promise; save(state: MobileAccessControlState): Promise; } /** One started gateway runtime owned by the controller. */ interface MobileAccessRuntime { close(): Promise; } /** Validate control state loaded across the filesystem boundary. */ declare function parseMobileAccessControlState(value: unknown): MobileAccessControlState; /** Atomic JSON store whose absent-file state comes from the installation-time default. */ declare class JsonMobileAccessControlStore implements MobileAccessControlStore { private readonly file; private readonly initiallyEnabled; constructor(file: string, initiallyEnabled: boolean); load(): Promise; save(state: MobileAccessControlState): Promise; } /** Serialized persistent lifecycle for the gateway behind the always-loaded Cordis entry. */ declare class MobileAccessGatewayController { private readonly store; private readonly startRuntime; private runtime; private initialized; private closing; private queue; private closeTask; constructor(store: MobileAccessControlStore, startRuntime: () => Promise); /** Load the durable preference and start the first runtime when enabled. */ initialize(): Promise; /** Return the committed in-process runtime state. */ isRunning(): boolean; /** Start or stop the runtime and persist only a successfully committed transition. */ setRunning(running: boolean): Promise; /** Stop the runtime after earlier transitions without changing the restart preference. */ close(): Promise; private enable; private disable; private enqueue; } //#endregion //#region src/websocket-paths.d.ts /** Cap the admin-approved extra WebSocket upgrade paths (exact pathnames). */ declare const MAX_EXTRA_WEBSOCKET_PATHS = 16; declare const MAX_WEBSOCKET_PATH_LENGTH = 256; /** * Validate one exact pathname for proxying. Query strings are matched at * upgrade time, so only the pathname is stored. Rejects anything the * gateway cannot match exactly. */ declare function validateWebSocketPath(value: unknown): string; /** Validate a whole replacement list all-or-nothing; duplicates collapse. */ declare function normalizeWebSocketPaths(value: unknown): string[]; /** Cap remembered rejected upgrade paths offered for one-click approval. */ declare const MAX_BLOCKED_UPGRADE_PATHS = 32; interface BlockedUpgradePathEntry { readonly path: string; readonly attempts: number; readonly firstSeen: number; readonly lastSeen: number; } /** * In-memory log of rejected third-party upgrade paths, shared by every * gateway instance so the approval UI sees attempts on any listener. * Bounded and newest-first; a restart clears it (attempts reappear on * the next blocked handshake). */ declare class BlockedUpgradePathLog { private readonly entries; record(pathname: string): void; report(): BlockedUpgradePathEntry[]; } /** File-backed store shared by every gateway instance (LAN and remote). */ declare class WebSocketPathStore { private readonly file; private paths; private loaded; constructor(file: string); /** Snapshot for the upgrade check. */ has(pathname: string): boolean; list(): string[]; load(): Promise; /** Replace the whole list after validating; persists atomically. */ replace(paths: readonly string[]): Promise; } //#endregion //#region src/extensions.d.ts /** Maximum sizes enforced at the local-extension filesystem boundary. */ declare const EXTENSION_LIMITS: Readonly<{ manifest: number; script: number; css: number; asset: number; assetFiles: 256; assetBytes: number; assetDepth: 8; }>; /** A controlled business failure returned by an extension action or route. */ declare class MobileExtensionError extends Error { readonly code: string; readonly status: number; constructor(code: string, message: string, status?: number); } /** One host-side action exposed by an extension. */ type CallableActionInput = (value?: never, options?: never) => unknown; interface MobileHostAction { /** A callable Schemastery schema or an adapter exposing parse(). */ readonly input?: CallableActionInput | { parse(value: unknown): unknown; }; readonly run: (context: MobileActionContext, input: unknown) => unknown | Promise; } /** Context supplied to a host action. */ interface MobileActionContext { readonly signal: AbortSignal; readonly deviceId: string; } /** Safe request values supplied to a host route. */ interface MobileRouteRequest { readonly method: string; readonly pathname: string; readonly query: Readonly; readonly headers: Readonly>; readonly body: Uint8Array; readonly signal: AbortSignal; readonly deviceId: string; } /** Values an extension route may return; status is a final HTTP code from 200 through 599. */ interface MobileRouteResponse { readonly status?: number; readonly contentType?: string; readonly headers?: Readonly>; readonly body: string | Uint8Array | Readable; } /** One host-side route exposed by an extension. */ interface MobileHostRoute { readonly method: string; readonly path: string; readonly kind?: 'exact' | 'prefix'; readonly handle: (request: MobileRouteRequest) => MobileRouteResponse | Promise; } /** Metadata shared by local and npm-provided extensions. */ interface MobileExtensionManifest { readonly schemaVersion: 1; readonly id: string; readonly name: string; readonly version: string; readonly description?: string; } /** Definition registered by a normal Cordis plugin. */ interface MobileExtensionDefinition extends MobileExtensionManifest { readonly actions?: Readonly>; readonly routes?: readonly MobileHostRoute[]; } declare module '@deepseek-ai/cordis' { interface Context { mobileAccess: MobileAccessService; } } /** A local extension manifest read from extension.json. */ interface LocalExtensionManifest extends MobileExtensionManifest {} /** Public snapshot sent to the mobile browser. */ interface MobileExtensionClientEntry extends MobileExtensionManifest { readonly generation?: string; readonly scriptUrl?: string; readonly styleUrl?: string; readonly assetsUrl?: string; } interface LocalAssetSnapshot { readonly body: Buffer; readonly digest: string; readonly name: string; } /** Small status summary used by the desktop mobile-access card. */ interface MobileExtensionStatus { readonly loaded: number; readonly failed: number; } interface ActiveLocalExtension { readonly manifest: LocalExtensionManifest; readonly directory: string; readonly scriptBody?: Buffer; readonly styleBody?: Buffer; readonly assets: ReadonlyMap; readonly host: MobileExtensionDefinition; readonly controller: AbortController; readonly cleanups: readonly (() => void | Promise)[]; readonly digest: string; } /** Validate a stable extension id. */ declare function assertExtensionId(value: unknown): string; /** Validate a manifest from JSON or a plugin definition. */ declare function parseExtensionManifest(value: unknown): LocalExtensionManifest; /** Host registry and service consumed by both npm plugins and local extensions. */ declare class MobileAccessService extends Service { private readonly registered; private readonly local; private readonly retired; private readonly failures; private readonly contentListeners; private contentHash; private localRoot; private localContext; private localTimer; private localRefreshing; private localRefreshAbort; private localLifecycle; private localClosed; constructor(ctx: Context); /** Register a normal Cordis extension and return an idempotent disposer. */ registerExtension(definition: MobileExtensionDefinition): () => void; /** Aggregate digest covering every registered and active local extension. */ contentDigest(): string; /** Subscribe to committed extension generation changes. */ onContentChanged(listener: () => void): () => void; private updateContentHash; /** Return the current client-facing manifest, deterministically sorted by id. */ manifest(): readonly MobileExtensionClientEntry[]; /** Return loaded and failed local extension counts without exposing host errors. */ status(): MobileExtensionStatus; /** Locate one active extension. */ extension(id: string, generation?: string): MobileExtensionDefinition | ActiveLocalExtension | undefined; /** Return the active local generation signal for gateway cancellation wiring. */ signal(id: string, generation?: string): AbortSignal | undefined; /** Read a local client entry after validating that it remains inside its directory. */ readClientFile(id: string, kind: 'script' | 'style', signal?: AbortSignal, generation?: string): Promise<{ readonly body: Buffer; readonly digest: string; }>; /** Read a generation-pinned static asset from its validated snapshot. */ readAsset(id: string, assetPath: string, signal?: AbortSignal, generation?: string): Promise<{ readonly body: Buffer; readonly digest: string; readonly name: string; }>; /** Invoke one action after parsing its input and binding the request lifetime. */ invoke(id: string, actionName: string, input: unknown, context: MobileActionContext, generation?: string): Promise; /** Match one route and invoke it with a generation-bound abort signal. */ route(id: string, method: string, pathname: string, request: MobileRouteRequest, generation?: string): Promise; /** Start the local directory watcher; an absent directory is intentionally inert. */ startLocal(root: string, context: Context): Promise; /** Stop the watcher and abort every local host generation. */ stopLocal(): Promise; /** Refresh all local extensions atomically; failures keep the previous snapshot. */ refreshLocal(): Promise; private stageAndCommit; private retire; } /** Construct the service in a Cordis plugin without importing DSH internals. */ declare function createMobileAccessService(ctx: Context): MobileAccessService; //#endregion //#region src/gateway.d.ts /** Replace only DSH's layout client module while retaining its complete plugin graph. */ declare function rewriteMobileIndex(html: string): string; /** Authenticated TLS edge in front of the ordinary loopback-only DSH Web server. */ declare class MobileAccessGateway { readonly config: ResolvedGatewayConfig; private readonly extensions?; private readonly upstreamAuthenticatedUrl?; private readonly extraWebSocketPaths?; private readonly blockedUpgradeLog?; private readonly onDiscoveryDegraded?; readonly access: AccessController; private readonly listenerTlsEnabled; private readonly tlsEnabled; private policy; private server; private discoverySocket; private discoveryTimer; /** * Why broadcast discovery is unavailable, if it is. Windows keeps separate TCP and * UDP port-exclusion tables, so the port the OS handed the TCP listener can be * refused for UDP. That degradation is survivable but must stay observable: without * it, "the phone cannot find this computer" has no diagnosable cause on the host. */ private discoveryError; private mdnsError; /** Names of the bundled mobile assets that could not be read, if any. */ private mobileAssetError; private bonjour; private pairingCaCertificate; private listenerPort; private readonly connectedSockets; private readonly activeRequests; private readonly activeWebSockets; private readonly mobileBootBatches; /** Cached upstream bundle byte sizes (HEAD probes), keyed by resource URL. */ private readonly bootEntrySizeCache; private readonly extensionEventListeners; private extensionEventRevision; private readonly taskEventListeners; private readonly deviceEventListeners; private readonly pendingDeviceRevocations; private extensionChangeTimer; private extensionChangeTask; private legacyCustomDigest; private upstreamCookie; private upstreamCookieExpiresAt; private upstreamCookieTask; private upstreamAuthRequest; private nextOperationId; private closing; private started; private closeTask; private readonly removeSessionListener; private readonly removeExtensionContentListener; private readonly renewLimiter; private readonly probeLimiter; constructor(config: ResolvedGatewayConfig, store: DeviceStore, extensions?: MobileAccessService | undefined, upstreamAuthenticatedUrl?: string | undefined, extraWebSocketPaths?: { has(pathname: string): boolean; } | undefined, blockedUpgradeLog?: BlockedUpgradePathLog | undefined, onDiscoveryDegraded?: ((source: 'broadcast' | 'mdns', code: string) => void) | undefined); /** Initialize durable state, validate TLS, and bind the externally reachable listener. */ start(): Promise; private startDiscovery; private reportDiscoveryDegraded; private recordBroadcastFailure; private recordMdnsFailure; private discoveryAnnouncement; private closeFailedStart; private closeBonjour; /** Actual bound address, available after start and safe for loopback status output. */ address(): { host: string; port: number; origin: string; }; /** * Report which discovery channels this gateway actually owns. * * Broadcast discovery degrades on its own when its UDP port cannot be bound, so * callers need to tell "discovery is off" from "discovery was never attempted". * mDNS is published independently and is unaffected by that failure. */ discoveryStatus(): { readonly broadcast: boolean; readonly mdns: boolean; readonly errorCode?: string; readonly mdnsErrorCode?: string; readonly mobileAssetsErrorCode?: string; }; private requirePolicy; private authorize; private requireCsrf; private setSessionCookies; private handlePair; private handleRenew; private handleNativePair; private handleNativeRenew; private handleNativeProbe; private handleLogout; private handleExternalRequest; private handleExtensionRequest; private sendExtensionResponse; /** Exchange DSH's process-local launch token for an authority-bound cookie kept inside this gateway. */ private upstreamCookieHeader; private exchangeUpstreamCookie; private proxyMobileIndex; private rememberMobileBootBatch; /** * Determine which layout-batch entries can share a merged boot batch and which * must pass through. An entry at or above the per-entry cap, or one whose size * could not be probed, keeps its own upstream `/plugins` row: a merged batch * cannot carry it, and an unknown bundle must not risk the whole batch. */ private resolveMobileBootSizes; /** * Measure one upstream bundle by reading its body. The upstream serves * `/plugins` bundles as chunked streams without a Content-Length, so a probe * counts bytes; it aborts instantly past the per-entry cap. Measurements are * cached for a short window. */ private upstreamBundleSize; private serveMobileBootBatch; private startMobileBootBatchAssembly; private assembleMobileBootBatch; /** * Read one upstream bundle, retrying transient connection failures. * * Assembling a batch fans out over every client entry, and the upstream * resets a fraction of those connections before sending a byte * (`read ECONNRESET`, recv=0) — randomly, on any entry, at any concurrency. * A single such reset used to fail the whole batch with 502 * `upstream_unavailable`, surfacing in the browser as "bundle script * /mobile-access/mobile-boot/.js failed to load". Retrying recovers * every observed reset; a genuine upstream error still fails. */ private readUpstreamClientBundleWithRetry; private readUpstreamClientBundle; private allocateRequest; /** * Proxy one request upstream. Pass-through client bundles (`GET /plugins`) * receive bounded transient retries — the upstream resets a fraction of fresh * connections — matching the resilience the merged-batch assembly already has. * A request is only retried before any byte reached the client. */ private proxyHttp; private proxyHttpOnce; private abortSessionResources; private broadcastExtensionChange; /** Fan a completed task to every phone holding this gateway's event stream. */ broadcastTaskEvent(event: { readonly sessionId: string; readonly turn: number; }): void; /** Notify only the device whose persistent credential was revoked by the Host. */ private broadcastDeviceRevoked; private pollLegacyCustomChanges; private openExtensionEventStream; private readUpgradeResponse; /** Snapshot of rejected upgrade paths for the approval UI (newest first). */ blockedUpgradePathReport(): BlockedUpgradePathEntry[]; private handleUpgrade; /** Loopback-only DSH WebServer route for opening pairing and managing devices. */ localAdminRoute(prefix?: string): WebRoute; /** Close listeners and abort all accepted work before resolving teardown. */ close(): Promise; private performClose; /** Safe metadata helper for direct loopback integrations. */ devices(): readonly DeviceSummary[]; /** Status shown by the loopback mobile-access control card. */ extensionStatus(): { readonly loaded: number; readonly failed: number; }; } //#endregion //#region src/http-security.d.ts declare const DEVICE_COOKIE = "dsh_ma_device"; declare const SESSION_COOKIE = "dsh_ma_session"; declare const CSRF_COOKIE = "dsh_ma_csrf"; declare const CSRF_HEADER = "x-dsh-mobile-csrf"; declare const LOCAL_ADMIN_PREFIX = "/api/mobile-access"; declare const AUTH_PREFIX = "/mobile-access"; declare const WS_PATHS: Set; //#endregion //#region src/frp-component.d.ts interface FrpArtifact { readonly platform: NodeJS.Platform; readonly arch: string; readonly downloadUrl: string; readonly downloadBytes: number; readonly downloadSha256: string; readonly archiveName: string; readonly executableName: string; } /** Pinned official FRP release metadata for supported desktop targets. */ declare const FRP_COMPONENT_RELEASES: Readonly>; /** Public, credential-free description of the managed FRP client. */ interface FrpComponentStatus { readonly supported: boolean; readonly installed: boolean; readonly version: string; readonly downloadBytes: number; readonly installedBytes: number; readonly sourceUrl: string; readonly releasePage: string; readonly storagePath: string; readonly errorCode?: string; } interface FrpComponentManagerOptions { readonly stateDirectory: string; readonly platform?: NodeJS.Platform; readonly arch?: string; readonly fetchArtifact?: (artifact: FrpArtifact, signal: AbortSignal) => Promise; readonly extractArtifact?: (archive: string, destination: string, executableName: string) => Promise; readonly inspectExecutable?: (executable: string) => Promise; } /** Owns the optional official frpc binary inside the DSH Mobile state directory. */ declare class FrpComponentManager { readonly executable: string; readonly componentRoot: string; readonly componentStorage: string; readonly logRoot: string; private readonly stagingRoot; private readonly artifact; private readonly fetchArtifact; private readonly extractArtifact; private readonly inspectExecutable; private installed; private installedBytes; private errorCode; private queue; constructor(options: FrpComponentManagerOptions); /** Inspect the managed executable without relying on global FRP installations. */ initialize(): Promise; /** Return component metadata without exposing configuration or credentials. */ status(): FrpComponentStatus; /** Download, verify, and extract only frpc after explicit confirmation. */ install(): Promise; /** Remove all FRP executable, staging, and log files owned by DSH Mobile. */ purge(): Promise; private enqueue; } //#endregion //#region src/frp-template.d.ts /** Loopback-only HTTP vhost port used between Caddy and frps. */ declare const FRP_VHOST_HTTP_PORT = 7080; /** Caddy snippet owned entirely by DSH Mobile; the main Caddyfile only imports it. */ declare const FRP_CADDY_SNIPPET_PATH = "/etc/caddy/dsh-mobile-dsh.caddy"; /** First line of the owned snippet; also the legacy whole-file marker. */ declare const FRP_CADDY_SNIPPET_MARKER = "# Managed by DSH Mobile - snippet, safe to delete"; /** Exact line the main Caddyfile must contain (uncommented) for the site to load. */ declare const FRP_CADDY_IMPORT_LINE = "import /etc/caddy/dsh-mobile-dsh.caddy"; /** Build the Caddy site for one public host (without markers or import wiring). */ declare function createCaddySite(publicHost: string, certDir?: string): string; /** Build the only supported frps config and Caddy snippet from validated user inputs. */ declare function createRestrictedFrpServerTemplate(serverPort: number, token: string, publicOrigin: string): string; //#endregion //#region src/frp-config.d.ts /** Credentials and endpoints required by the restricted FRP provider. */ interface FrpSettings { readonly version: 1; readonly serverAddress: string; readonly serverPort: number; readonly token: string; readonly publicOrigin: string; } /** Safe FRP configuration fields returned to the desktop UI. */ interface FrpConfigurationStatus { readonly configured: boolean; readonly serverAddress?: string; readonly serverPort?: number; readonly publicOrigin?: string; readonly vhostHttpPort: number; readonly storagePath: string; readonly errorCode?: string; } /** Validate the FRP server hostname or IP address. */ declare function validateFrpServerAddress(value: unknown): string; /** Validate the FRP control port. */ declare function validateFrpServerPort(value: unknown): number; /** Validate a high-entropy FRP token before durable storage. */ declare function validateFrpToken(value: unknown): string; /** Validate the public HTTPS origin used by Caddy and Android pairing. */ declare function validateFrpPublicOrigin(value: unknown): string; /** Parse FRP settings at the loopback request and filesystem boundaries. */ declare function parseFrpSettings(value: unknown): FrpSettings; /** * Merge a partial VPS request body with the saved configuration so a blank * field keeps its saved value ("已保存时可留空"). Every merged field is still * validated; with nothing saved and nothing supplied the result reports a * missing configuration instead of silently deploying blanks. */ declare function mergeSavedFrpSettings(partial: Readonly>, saved: FrpSettings | undefined): FrpSettings; /** Merge a VPS target (address and control port) with the saved configuration. */ declare function mergeSavedFrpTarget(partial: Readonly>, saved: FrpSettings | undefined): { readonly serverAddress: string; readonly serverPort: number; }; /** Build the single-purpose frpc configuration for the current loopback gateway. */ declare function createFrpcToml(settings: FrpSettings, localPort: number): string; /** Build the matching restricted frps and Caddy templates for one VPS. */ declare function createFrpServerTemplate(settings: FrpSettings): string; /** Owns private FRP settings and generation-specific frpc configuration. */ declare class FrpConfigStore { readonly stateRoot: string; readonly settingsFile: string; readonly runtimeConfigFile: string; private settingsValue; private errorCode; constructor(stateDirectory: string); /** Load private settings while rejecting links, oversized files, and unknown fields. */ initialize(): Promise; /** Return configuration metadata without exposing the FRP token. */ status(): FrpConfigurationStatus; /** Return private settings only to the provider lifecycle. */ settings(): FrpSettings | undefined; /** Atomically replace private FRP settings. */ configure(value: unknown): Promise; /** Materialize the private generation-specific frpc configuration. */ writeRuntimeConfig(localPort: number): Promise; /** Remove only configuration files owned by the FRP provider. */ purge(): Promise; } //#endregion //#region src/remote.d.ts /** Remote transports supported by the desktop plugin and Android client. */ type RemoteProvider = 'tailscale' | 'cpolar' | 'cloudflared' | 'frp' | 'origin'; /** Common safe status returned by every remote provider controller. */ interface RemoteProviderStatus { readonly enabled: boolean; readonly state: string; readonly origin?: string; readonly backendOrigin?: string; readonly loginUrl?: string; readonly setupUrl?: string; readonly errorCode?: string; } /** Lifecycle shared by selectable remote providers. */ interface RemoteProviderController { initialize(): Promise; gateway(): MobileAccessGateway | undefined; status(): RemoteProviderStatus; setEnabled(enabled: boolean): Promise; reconnect(): Promise; reset(): Promise; close(): Promise; } /** Durable selection for the single active remote transport. */ interface RemoteProviderState { readonly version: 1; readonly provider: RemoteProvider; } declare const REMOTE_PROVIDERS: readonly RemoteProvider[]; /** Validate the provider selection loaded across the filesystem boundary. */ declare function parseRemoteProviderState(value: unknown): RemoteProviderState; /** Atomic selection store whose absent-file state uses the configured default. */ declare class JsonRemoteProviderStore { private readonly file; private readonly defaultProvider; constructor(file: string, defaultProvider: RemoteProvider); load(): Promise; save(state: RemoteProviderState): Promise; } /** Resolve the first-run provider without letting environment values bypass validation. */ declare function configuredRemoteProvider(environment: NodeJS.ProcessEnv): RemoteProvider; //#endregion //#region src/frp.d.ts /** Product-facing states for the restricted self-hosted FRP transport. */ type FrpState = 'off' | 'unavailable' | 'starting' | 'connecting' | 'ready' | 'error'; /** Safe FRP state returned only through the loopback DSH control route. */ interface FrpStatus { readonly enabled: boolean; readonly state: FrpState; readonly origin?: string; readonly errorCode?: string; } /** Inputs for one FRP client process and authenticated DSH gateway. */ interface FrpControllerOptions { readonly store: MobileAccessControlStore; readonly executable: string; readonly config: FrpConfigStore; readonly instanceId: string; readonly createGateway: (origin: string) => Promise; readonly onStatus?: (status: FrpStatus) => void; readonly verifyConfig?: (executable: string, configFile: string) => Promise; readonly launchClient?: (executable: string, configFile: string) => ChildProcessWithoutNullStreams; readonly probeVhostExposure?: (serverAddress: string, port: number) => Promise; readonly probeDiscovery?: (origin: string, expectedInstanceId: string, signal: AbortSignal) => Promise; readonly startTimeoutMs?: number; readonly retryIntervalMs?: number; } /** Owns frpc, its generation-specific configuration, and the remote gateway. */ declare class FrpController implements RemoteProviderController { private readonly options; private enabled; private initialized; private disposed; private child; private gatewayValue; private generation; private latest; private queue; private startupAbort; constructor(options: FrpControllerOptions); /** Restore the remembered FRP switch without changing LAN or other providers. */ initialize(): Promise; /** Return the active FRP-backed DSH gateway. */ gateway(): MobileAccessGateway | undefined; /** Return state safe for the desktop control UI. */ status(): FrpStatus; /** Enable or disable FRP without changing LAN or another provider. */ setEnabled(enabled: boolean): Promise; /** Restart FRP while retaining its private server settings and devices. */ reconnect(): Promise; /** Disable FRP without deleting its explicitly managed component or settings. */ reset(): Promise; /** Stop all FRP resources without changing the remembered switch. */ close(): Promise; private enqueue; private publish; private start; private waitForDiscovery; private failGeneration; private stop; private stopProcessAndGateway; } //#endregion //#region src/origin-proxy-config.d.ts declare const DEFAULT_ORIGIN_LISTEN_PORT = 3444; /** A private HTTP listener behind a user-managed HTTPS reverse proxy. */ interface OriginSettings { readonly version: 1; readonly publicOrigin: string; readonly listenHost: string; readonly listenPort: number; readonly allowedCidrs: readonly string[]; } /** Configuration metadata returned only to the local desktop control UI. */ interface OriginConfigurationStatus { readonly configured: boolean; readonly publicOrigin?: string; readonly listenHost?: string; readonly listenPort?: number; readonly allowedCidrs?: readonly string[]; readonly backendOrigin?: string; readonly storagePath: string; readonly errorCode?: string; } /** Require a public HTTPS origin; custom external ports are supported. */ declare function validateOriginPublicOrigin(value: unknown): string; /** Bind only one explicit loopback or RFC1918 IPv4 interface, never all interfaces. */ declare function validateOriginListenHost(value: unknown): string; declare function validateOriginListenPort(value: unknown): number; /** Authorize direct proxy socket peers, not untrusted forwarded client headers. */ declare function validateOriginAllowedCidrs(value: unknown, listenHost: string): readonly string[]; /** Validate both saved settings and local administrative requests. */ declare function parseOriginSettings(value: unknown): OriginSettings; /** Owns only origin settings; shared paired-device storage is never removed. */ declare class OriginConfigStore { readonly stateRoot: string; readonly settingsFile: string; private settingsValue; private errorCode; constructor(stateDirectory: string); initialize(): Promise; status(): OriginConfigurationStatus; settings(): OriginSettings | undefined; configure(value: unknown): Promise; purge(): Promise; } //#endregion //#region src/origin-proxy.d.ts type OriginState = 'off' | 'unavailable' | 'starting' | 'ready' | 'error'; /** Ready means the private listener is ready, not that public ingress was tested. */ interface OriginStatus { readonly enabled: boolean; readonly state: OriginState; readonly origin?: string; readonly backendOrigin?: string; readonly errorCode?: string; } interface OriginControllerOptions { readonly store: MobileAccessControlStore; readonly config: OriginConfigStore; readonly createGateway: (settings: OriginSettings) => Promise; readonly onStatus?: (status: OriginStatus) => void; } /** Owns an authenticated HTTP gateway, with no tunnel process or public network probes. */ declare class OriginController implements RemoteProviderController { private readonly options; private enabled; private initialized; private disposed; private gatewayValue; private latest; private queue; constructor(options: OriginControllerOptions); initialize(): Promise; gateway(): MobileAccessGateway | undefined; status(): OriginStatus; setEnabled(enabled: boolean): Promise; reconnect(): Promise; /** Reset the switch, retaining both settings and shared remote device pairings. */ reset(): Promise; /** Stop the listener without changing the durable switch. */ close(): Promise; private assertAvailable; private enqueue; private publish; private start; private stop; } //#endregion //#region src/cloudflared-component.d.ts /** * Pinned cloudflared components fetched only after an explicit user action. * * Unlike the cpolar archive each artifact IS the executable: there is nothing * to unpack, so the download digest and the installed digest are the same pair. * `--no-autoupdate` is passed at runtime as well, so the pinned bytes stay the * bytes that were verified. */ interface CloudflaredArtifact { readonly version: string; readonly platform: NodeJS.Platform; readonly arch: string; readonly downloadUrl: string; readonly downloadBytes: number; readonly downloadSha256: string; readonly executableName: string; } /** * Canonical release metadata. New code should select from * {@link CLOUDFLARED_COMPONENT_RELEASES} by platform and architecture; this * alias preserves the original Windows x64 entry for existing callers. */ declare const CLOUDFLARED_COMPONENT_RELEASE: CloudflaredArtifact; /** * Public, credential-free description of the managed cloudflared component. * * There is deliberately no `configured` flag: a quick tunnel needs no account, * token, or DNS record, so installation is the only precondition. */ interface CloudflaredComponentStatus { readonly supported: boolean; readonly installed: boolean; readonly version: string; readonly downloadBytes: number; readonly installedBytes: number; readonly sourceUrl: string; readonly downloadPage: string; readonly termsUrl: string; readonly storagePath: string; readonly errorCode?: string; } interface CloudflaredComponentManagerOptions { readonly stateDirectory: string; readonly platform?: NodeJS.Platform; readonly arch?: string; readonly fetchArtifact?: (url: string, signal: AbortSignal) => Promise; } /** Owns the optional cloudflared binary inside DSH Mobile state. */ declare class CloudflaredComponentManager { readonly executable: string; readonly componentRoot: string; readonly componentStorage: string; readonly stateRoot: string; readonly logRoot: string; private readonly stagingRoot; private readonly platform; private readonly arch; private readonly release; private readonly fetchArtifact; private installed; private errorCode; private queue; constructor(options: CloudflaredComponentManagerOptions); /** Inspect the managed binary without using any global cloudflared state. */ initialize(): Promise; /** Return a safe status that never includes machine-specific account data. */ status(): CloudflaredComponentStatus; /** Download, verify, and install the pinned cloudflared executable after explicit confirmation. */ install(): Promise; /** Remove every cloudflared file owned by DSH Mobile without touching global state. */ purge(): Promise; private enqueue; } //#endregion //#region src/cloudflared-tunnel.d.ts /** Which tunnel flavour the cloudflared provider runs. */ type CloudflaredTunnelMode = 'quick' | 'named'; /** * Cloudflared provider configuration. * * A quick tunnel owns nothing durable: cloudflared allocates a random hostname * and a random forward port every start. A named tunnel instead runs with an * account token, and its public hostname is routed by Cloudflare to the local * port recorded here, so that port must stay stable across restarts. */ type CloudflaredTunnelSettings = { readonly version: 1; readonly mode: 'quick'; } | { readonly version: 1; readonly mode: 'named'; readonly token: string; readonly hostname: string; readonly port: number; }; /** Configuration metadata safe to hand to the desktop panel; never the token. */ interface CloudflaredTunnelStatus { readonly mode: CloudflaredTunnelMode; readonly configured: boolean; readonly hostname?: string; readonly port?: number; readonly storagePath: string; readonly errorCode?: string; } /** * Validate the public hostname Cloudflare routes to this tunnel. * * The name must be a real DNS name, not an IP literal or a wildcard, and it * cannot sit under Cloudflare's own control-plane suffixes: `trycloudflare.com` * belongs to quick tunnels, and a `cfargotunnel.com` name is the routing target * rather than a routable public address. */ declare function validateCloudflaredTunnelHostname(value: unknown): string; /** Validate the stable loopback port Cloudflare's ingress forwards to. */ declare function validateCloudflaredTunnelPort(value: unknown): number; /** * Validate a connector token before durable storage. * * A token is a base64url-encoded JSON blob that carries the account, tunnel id * and tunnel secret; the provider passes it through the environment rather than * the command line, and it is never returned to any client. */ declare function validateCloudflaredTunnelToken(value: unknown): string; /** Parse tunnel settings at the request and filesystem boundaries. */ declare function parseCloudflaredTunnelSettings(value: unknown): CloudflaredTunnelSettings; /** * Merge a partial panel request with the saved named-tunnel settings so a blank * field keeps its stored value. The token in particular is write-only, so the * panel submits an empty string to mean "keep the existing connector token". */ declare function mergeSavedCloudflaredTunnelSettings(partial: Readonly>, saved: CloudflaredTunnelSettings | undefined): CloudflaredTunnelSettings; /** Owns the private cloudflared tunnel configuration for one DSH installation. */ declare class CloudflaredTunnelStore { readonly stateRoot: string; readonly settingsFile: string; private settingsValue; private errorCode; constructor(stateDirectory: string); /** Load private settings while rejecting links, oversized files and unknown fields. */ initialize(): Promise; /** Return configuration metadata without exposing the connector token. */ status(): CloudflaredTunnelStatus; /** Return private settings only to the provider lifecycle. */ settings(): CloudflaredTunnelSettings; /** Atomically replace the tunnel configuration. */ configure(value: unknown): Promise; /** Forget a named tunnel and its connector token. */ purge(): Promise; } //#endregion //#region src/cloudflared.d.ts /** Product-facing states for the optional cloudflared remote transport. */ type CloudflaredState = 'off' | 'unavailable' | 'starting' | 'connecting' | 'ready' | 'error'; /** Safe cloudflared state returned only through the loopback DSH control route. */ interface CloudflaredStatus { readonly enabled: boolean; readonly state: CloudflaredState; readonly origin?: string; readonly errorCode?: string; } /** Inputs for one cloudflared process and its authenticated DSH gateway. */ interface CloudflaredControllerOptions { readonly store: MobileAccessControlStore; readonly executable: string; /** Named-tunnel configuration; quick tunnels are used when it reports quick mode. */ readonly tunnel: { settings(): CloudflaredTunnelSettings; }; readonly createGateway: (origin: string, listenPort: number) => Promise; readonly onStatus?: (status: CloudflaredStatus) => void; /** Liveness bound for one startup-timeout round; defaults to the product timeout. */ readonly startupTimeoutMs?: number; /** * Total startup-timeout rounds before a live named connector is given up on; * defaults to NAMED_STARTUP_ROUNDS. Quick tunnels always use a single round. */ readonly maxStartupRounds?: number; readonly spawnProcess?: (executable: string, args: readonly string[], environment: NodeJS.ProcessEnv) => ChildProcessWithoutNullStreams; } /** * Extract a validated public HTTPS origin from one cloudflared output line. * * Quick tunnels print the public hostname inside a decorated banner, so the * candidate is taken from the line and then re-parsed as a URL: a lookalike such * as `https://x.trycloudflare.com.evil.test` fails the hostname check instead of * being truncated into an accepted origin. */ declare function parseCloudflaredOrigin(line: string): string | undefined; /** * Whether one cloudflared log line reports an established edge connection. * * A named tunnel prints no banner, so registration is the only signal that the * connector reached Cloudflare and the public hostname can serve traffic. Both * the current and the older wording are accepted; anything else is ignored so a * chatty log line cannot be mistaken for readiness. */ declare function isCloudflaredRegistration(line: string): boolean; /** * Owns an installed cloudflared client and a provider-specific DSH remote gateway. * * Quick tunnels allocate a random hostname and a random forward port on every * start. Named tunnels instead read an account token and a stable public * hostname from the tunnel store, forward to the configured loopback port, and * learn readiness from the connector's registration line rather than a banner. */ declare class CloudflaredController implements RemoteProviderController { private readonly options; private enabled; private initialized; private disposed; private child; private gatewayValue; private reservation; private generation; private buffer; private mode; private namedOrigin; private latest; private queue; private startupTimer; private startupRounds; constructor(options: CloudflaredControllerOptions); /** Restore the remembered cloudflared switch independently from LAN and other providers. */ initialize(): Promise; /** Return the active cloudflared-backed DSH gateway. */ gateway(): MobileAccessGateway | undefined; /** Return state safe for the desktop control UI. */ status(): CloudflaredStatus; /** Enable or disable cloudflared without changing LAN or other provider state. */ setEnabled(enabled: boolean): Promise; /** Restart cloudflared and allocate a fresh quick tunnel. */ reconnect(): Promise; /** Disable cloudflared without deleting the installed component. */ reset(): Promise; /** Stop owned resources without changing the remembered switch. */ close(): Promise; private enqueue; private publish; private start; private armStartupTimer; /** * Give a live named connector more time instead of killing it: it retries the * edge on its own and usually registers once the rebooted network is usable. * A dead connector, or an exhausted budget, fails the generation as before. */ private checkStartupTimeout; private consume; /** Mark a named tunnel ready once the connector has registered with the edge. */ private confirmNamedReady; private attachGateway; /** Replace the gateway authority when a quick tunnel restarts on a new hostname. */ private rotateGateway; private failGeneration; private stop; private stopProcessAndGateway; } //#endregion //#region src/task-events.d.ts /** * Host-side task-completion fan-out for phone notifications. * * The exact moment a run ends is known only on the Host (the phone page can * merely infer it from UI state), so this module watches the public * `session/event` bus for completed root turns and hands them to a hub that * fans out to every live mobile gateway. Phones render the text locally, so * no user content crosses this boundary — only opaque session and turn ids. */ /** A completed root turn worth announcing on paired phones. */ interface TaskCompletionEvent { readonly sessionId: string; readonly turn: number; } /** Minimal session shape read off the `session/event` bus. */ interface TaskEventSession { readonly id: unknown; readonly header?: { readonly parentSession?: unknown; } | null | undefined; } /** * Minimal turn event shape read off the `session/event` bus. Fields stay * unknown here and are validated at runtime below so this module never * depends on the harness session packages. */ interface TaskTurnEvent { readonly type: string; readonly data?: unknown; } /** Structural slice of the Host context this module needs (keeps tests cordis-free). */ interface TaskEventContext { on(event: 'session/event', handler: (session: TaskEventSession, event: TaskTurnEvent) => void): () => void; } interface TaskEventWatcherOptions { /** Trailing debounce per session that merges turn-boundary bursts. */ readonly debounceMs?: number; readonly onTaskCompleted: (event: TaskCompletionEvent) => void; readonly log?: (event: string, fields: Readonly>) => void; } declare const TASK_EVENT_DEBOUNCE_MS = 1000; /** * Subscribe to completed root turns. Subagent turns are skipped so one task * announces once, and rapid turn boundaries collapse into a single event. * Returns a disposer that also drops pending debounces. */ declare function watchTaskCompletions(ctx: TaskEventContext, options: TaskEventWatcherOptions): () => void; /** Receives fanned-out completion events (normally a mobile gateway). */ interface TaskEventSink { broadcastTaskEvent(event: TaskCompletionEvent): void; } /** One subscription feeding every live gateway; gateways register on start. */ declare class TaskEventHub { private readonly sinks; /** Register a sink; returns its disposer. */ add(sink: TaskEventSink): () => void; /** Fan out to a snapshot so a failing sink cannot break its siblings. */ broadcast(event: TaskCompletionEvent): void; /** Visible for tests. */ get size(): number; } //#endregion //#region src/plugin.d.ts declare module '@deepseek-ai/dsh-llm' { interface MessageSourceMap { 'plugin:dsh-mobile': { kind: 'plugin:dsh-mobile'; form: 'notice'; summary: string; }; } } /** Stable Cordis plugin name. */ declare const name = "dsh-mobile"; /** The stock WebServer serves the control card; Connection authenticates the loopback DSH origin. */ declare const inject: string[]; /** Reuse remote HTTPS policy while binding a separately validated private HTTP origin. */ declare function originGatewayConfig(template: ResolvedGatewayConfig, settings: OriginSettings, stateFile: string, instanceId: string, listenPort?: number): ResolvedGatewayConfig; /** Mount the resident control route and its optional authenticated LAN gateway. */ declare function apply(ctx: Context, config: PluginConfig): Promise; //#endregion export { AUTH_PREFIX, AccessController, type AccessControllerOptions, AccessError, type AuthoritySpec, type BlockedUpgradePathEntry, BlockedUpgradePathLog, BoundedRateLimiter, CLOUDFLARED_COMPONENT_RELEASE, CSRF_COOKIE, CSRF_HEADER, CloudflaredComponentManager, type CloudflaredComponentStatus, CloudflaredController, type CloudflaredControllerOptions, type CloudflaredState, type CloudflaredStatus, type CloudflaredTunnelMode, type CloudflaredTunnelSettings, type CloudflaredTunnelStatus, CloudflaredTunnelStore, Config, DEFAULT_ORIGIN_LISTEN_PORT, FRP_VHOST_HTTP_PORT as DEFAULT_VHOST_HTTP_PORT, FRP_VHOST_HTTP_PORT, DEVICE_COOKIE, type DeviceProbeResult, type DeviceSnapshot, type DeviceStore, type DeviceSummary, type DisabledTlsConfig, EXTENSION_LIMITS, FRP_CADDY_IMPORT_LINE, FRP_CADDY_SNIPPET_MARKER, FRP_CADDY_SNIPPET_PATH, FRP_COMPONENT_RELEASES, FrpComponentManager, type FrpComponentStatus, FrpConfigStore, type FrpConfigurationStatus, FrpController, type FrpControllerOptions, type FrpSettings, type FrpState, type FrpStatus, JsonDeviceStore, JsonMobileAccessControlStore, JsonRemoteProviderStore, LOCAL_ADMIN_PREFIX, type LocalExtensionManifest, MAX_BLOCKED_UPGRADE_PATHS, MAX_EXTRA_WEBSOCKET_PATHS, MAX_WEBSOCKET_PATH_LENGTH, MemoryDeviceStore, type MobileAccessControlState, type MobileAccessControlStore, MobileAccessGateway, MobileAccessGatewayController, type MobileAccessService as MobileAccessRegistry, MobileAccessService, type MobileAccessRuntime, type MobileActionContext, type MobileExtensionClientEntry, type MobileExtensionDefinition, MobileExtensionError, type MobileExtensionManifest, type MobileExtensionStatus, type MobileHostAction, type MobileHostRoute, type MobileRouteRequest, type MobileRouteResponse, OriginConfigStore, type OriginConfigurationStatus, OriginController, type OriginControllerOptions, type OriginSettings, type OriginState, type OriginStatus, type PairingResult, type ParsedCidr, type PluginConfig, type ProvidedTlsConfig, REMOTE_PROVIDERS, type RemoteProvider, type RemoteProviderController, type RemoteProviderState, type RemoteProviderStatus, type RenewalResult, RequestTrustPolicy, type ResolvedGatewayConfig, SESSION_COOKIE, type SessionAuthorization, type SessionEndReason, type StoredDevice, TASK_EVENT_DEBOUNCE_MS, type TaskCompletionEvent, type TaskEventContext, TaskEventHub, type TaskEventSession, type TaskEventSink, type TaskEventWatcherOptions, type TaskTurnEvent, type TlsConfig, WS_PATHS, WebSocketPathStore, addressAllowed, apply, assertExtensionId, configuredRemoteProvider, createCaddySite, createFrpServerTemplate, createFrpcToml, createMobileAccessService, createRestrictedFrpServerTemplate, inject, isCloudflaredRegistration, isGloballyRoutableIpv4, isLoopbackAddress, mergeSavedCloudflaredTunnelSettings, mergeSavedFrpSettings, mergeSavedFrpTarget, name, normalizeWebSocketPaths, originGatewayConfig, parseAuthority, parseCidr, parseCloudflaredOrigin, parseCloudflaredTunnelSettings, parseControlFile, parseDeviceSnapshot, parseExtensionManifest, parseFrpSettings, parseGatewayConfig, parseMobileAccessControlState, parseOriginSettings, parseRemoteProviderState, resolveAuthority, rewriteMobileIndex, validateCloudflaredTunnelHostname, validateCloudflaredTunnelPort, validateCloudflaredTunnelToken, validateFrpPublicOrigin, validateFrpServerAddress, validateFrpServerPort, validateFrpToken, validateOriginAllowedCidrs, validateOriginListenHost, validateOriginListenPort, validateOriginPublicOrigin, validateWebSocketPath, watchTaskCompletions }; //# sourceMappingURL=index.d.mts.map