/** * RemoteSessionService — orchestrates cross-device session sharing. * * Composes the low-level pieces in this package into a single service: * * - `PTYMeshBridge` — creates one `MeshPTYTransport` per connected peer * - `PTYSyncStore` — publishes our local sessions + observes remote slices * - `RelaySessionManager` — per-subscriber owner-side forwarders * * And subscribes to the `'pty'` truffle namespace to handle owner-side * commands from remote viewers (SUBSCRIBE / UNSUBSCRIBE / INPUT / RESIZE / * KILL / FOCUS_CHANGED). Consumers drop in a `PTYSessionManager` that owns * local sessions and (optionally) an `IPeerNotifier` for surfacing focus * changes to a renderer. * * RFC 022 (truffle ≥ 0.6): * - Inbound `msg.from` is a Peer handle (or Tailscale id string fallback). * - Live PTY routing keys on `peer.ref` via the bridge. * - SyncedStore discovery keys on durable ULID; the bridge secondary index * maps deviceId → transport once identity is known. */ import { EventEmitter } from 'events'; import type { MeshNode } from '@vibecook/truffle'; import type { PTYSessionManager } from '#core'; import type { ITerminalStoreSync } from '#core'; import type { PTYMeshBridge } from './pty-mesh-bridge.js'; import type { PTYSyncStore } from './pty-sync-store.js'; import { type IRelaySessionManager } from './relay-session-manager.js'; /** * Side-channel for surfacing UI-relevant state changes to the consumer * application. Replaces vibe-ctl's direct `BrowserWindow.webContents.send()` * calls — Electron apps wire this to forward events to the renderer, * headless apps can omit it. */ export interface IPeerNotifier { /** A session's focus state changed (local input or remote viewer). */ sessionFocusChanged(sessionId: string, focused: boolean, source: 'local' | 'remote', /** Peer ref, deviceId, or other stable label for the remote device. */ deviceId?: string): void; /** * The set of sessions a peer is sharing changed. * `deviceId` is the durable ULID from SyncedStore when known. */ remoteSessionsChanged(deviceId: string, count: number): void; } export interface RemoteSessionServiceOptions { node: MeshNode; sessionManager: PTYSessionManager; bridge: PTYMeshBridge; syncStore: PTYSyncStore; /** Optional: terminal authority store used to gate resize requests. */ terminalStoreSync?: ITerminalStoreSync; /** Optional: side channel for UI notifications (Electron, etc). */ notifier?: IPeerNotifier; } export interface RemoteSessionServiceEvents { enabled: () => void; disabled: () => void; /** Fired with process-local peer ref when a transport is created. */ deviceConnected: (peerRef: string) => void; deviceDisconnected: (peerRef: string) => void; } export declare class RemoteSessionService extends EventEmitter { private readonly node; private readonly sessionManager; private readonly bridge; private readonly syncStore; private readonly terminalStoreSync?; private readonly notifier?; private relayManager; private enabled; private readonly sessionDiscoveredHandler; private readonly sessionLostHandler; private readonly bridgeTransportCreatedHandler; private readonly bridgeTransportRemovedHandler; constructor(options: RemoteSessionServiceOptions); isEnabled(): boolean; getRelayManager(): IRelaySessionManager; /** * Start processing cross-device sharing. * * Assumes `bridge.initialize()` has already been called by the caller. * If not, transports for currently connected peers won't exist yet — * subsequent connections will still work via the bridge's peer-change * handler. */ enable(): Promise; disable(): Promise; dispose(): Promise; /** * Publish the current set of shareable local sessions to the sync store. * * Only `local` and `ipc` sessions are published — `ws` sessions are * already-proxied remote sessions; re-publishing them would create a * re-sharing loop. Cooperative "last write wins" (no authority check). */ private publishLocalSessions; private handleIncomingPtyMessage; private fromKey; private resolveTransportFromMessage; private findTransportByTailscale; private handleSubscribe; private handleUnsubscribe; private handleInput; private handleResize; private handleKill; private handleFocusFromViewer; private replyCreateFailed; /** * Reconcile our proxy sessions against a peer's latest session list. * * Called in two places: * 1. `syncStore.onRemoteChange` fires when a peer updates/removes * their slice (keyed by durable ULID). * 2. `bridge.transportCreated` fires when a peer WS-connects — we * catch up on whatever they've already published (if identity known). * * @param deviceId durable ULID of the remote device * @param sessions new session list, or `null` for a peer_removed event */ private handleRemoteSessionsChanged; private cleanupPeerProxiesByDeviceId; } export declare function createRemoteSessionService(options: RemoteSessionServiceOptions): RemoteSessionService; //# sourceMappingURL=remote-session-service.d.ts.map