/** * MeshPTYTransport — IPTYTransport over a truffle mesh peer. * * One `MeshPTYTransport` represents the link to a single remote peer. The * transport handles both sides of the viewer/owner protocol: * * Viewer (this device) → Owner (remote peer) * sendInput, sendResize, sendKill, sendFocus, subscribe, unsubscribe * * Owner (this device) → Viewer (remote peer) * sendOutput, sendResized, sendSessionEnded, sendFocusChanged * * RFC 022 (truffle ≥ 0.6): the transport holds an interned `Peer` handle. * Outbound traffic uses `peer.send(...)` (generation-checked routing). * Inbound messages are filtered by matching `msg.from` against the same * peer (WhoIs-verified Tailscale attribution, not a self-declared ULID). * * Ported from vibe-ctl and rewritten for `@vibecook/truffle`'s MeshNode API. */ import { EventEmitter } from 'events'; import type { MeshNode, Peer } from '@vibecook/truffle'; import type { IPTYTransport, TransportType, WSCreateSessionPayload } from '#types'; /** * Shared truffle namespace for all PTY mesh traffic. * * Every PTY message — viewer→owner and owner→viewer — is published on this * namespace. Truffle routes messages by (peer, namespace); the `payload.type` * field discriminates between message kinds within the namespace. */ export declare const PTY_NAMESPACE = "pty"; export interface MeshPTYTransportOptions { /** Truffle mesh node (from `createMeshNode`). */ node: MeshNode; /** * Interned Peer handle this transport targets (RFC 022). * Routing and message filtering use the handle — never a bare deviceId. */ peer: Peer; /** * Initial connection state. * Defaults to true — the bridge only constructs a transport once the peer * is WebSocket-connected. */ isConnected?: boolean; } /** * Mesh PTY transport for a single truffle peer. * * Implements the full `IPTYTransport` surface — both viewer-side and * owner-side operations — because a single peer connection is used in both * directions for relay mode. */ export declare class MeshPTYTransport extends EventEmitter implements IPTYTransport { private readonly _node; private readonly _peer; private _isReady; private _disposed; private _messageHandler; constructor(options: MeshPTYTransportOptions); /** * Process-local peer ref (`{tailscaleId}:{generation}`). Used as the * transport id so `PTYSessionManager` can look us up by peer. */ get transportId(): string; get transportType(): TransportType; /** Interned Peer handle this transport is bound to. */ get peer(): Peer; /** * Stable routing key for this transport (`peer.ref`). Prefer the `peer` * handle for networking; this string is for maps / IPC. */ get peerId(): string; /** Human-readable name for logs and the UI. */ get peerName(): string; /** Durable ULID once known; null until identity hello (RFC 022). */ get deviceId(): string | null; get isReady(): boolean; disconnect(reason?: string): void; dispose(): void; /** * Whether an inbound `msg.from` belongs to this transport's peer. * * With `createMeshNode`, `from` is usually an interned `Peer` (same * instance as `getPeers()`). Fallback string form is the WhoIs-verified * Tailscale id. */ private matchesFrom; /** * Dispatch a message already filtered to this peer. * * `msg.payload` is a JSON object truffle decoded on the receive side * (not a Buffer) — calling `JSON.parse` on it throws. We cast through * `PTYWireMessage` and pick out the `type` discriminator. */ private handleIncoming; /** * Serialize and publish a wire message to this transport's peer. * * Sends are fire-and-forget from the IPTYTransport contract's POV (the * signature is synchronous), but Peer.send returns a Promise. We dispatch * it and log any rejection — errors here are mesh-layer issues (peer gone, * socket dead) and the consumer recovers via the `'disconnected'` event. */ private sendWire; sendInput(sessionId: string, data: string | Buffer): void; sendResize(sessionId: string, cols: number, rows: number): void; sendKill(sessionId: string, signal?: string): void; sendFocus(sessionId: string, focused: boolean): void; /** * NOTE on `targetDeviceId`: the `IPTYTransport` signature allows routing * output to an arbitrary target, but `MeshPTYTransport` is already bound * to a single peer (`this._peer`). We honor the parameter for API * symmetry with the interface but log a warning if it disagrees — callers * should look up the right transport from `PTYMeshBridge` and send there. * * The historical name "targetDeviceId" now means any peer key we use * (typically `peer.ref`, sometimes a durable ULID). */ sendOutput(sessionId: string, data: Buffer, targetDeviceId: string): void; sendResized(sessionId: string, cols: number, rows: number, targetDeviceId: string): void; sendSessionEnded(sessionId: string, exitCode: number, targetDeviceId: string): void; sendFocusChanged(sessionId: string, focused: boolean, targetDeviceId: string): void; private assertTargetMatches; /** Subscribe to a remote session to start receiving its output. */ subscribe(sessionId: string): void; /** Unsubscribe from a remote session. */ unsubscribe(sessionId: string): void; /** * Request creation of a new session on the remote device. * * RemoteSessionService currently responds with CREATE_FAILED — remote * session spawning is out of scope for v0.1 (see plan D7). */ createRemoteSession(options: WSCreateSessionPayload): void; /** Mark the transport as connected/ready. */ handleConnected(): void; /** Mark the transport as disconnected. */ handleDisconnected(reason: string): void; } export declare function createMeshPTYTransport(options: MeshPTYTransportOptions): MeshPTYTransport; //# sourceMappingURL=mesh-pty-transport.d.ts.map