pi-mono/packages/client
2026-08-15 01:08:12 +03:00
..
src feat: rename service IDs to server IDs 2026-08-14 23:25:56 +02:00
test feat(server): pass session metadata to harness hosts 2026-08-15 01:08:12 +03:00
CHANGELOG.md Add [Unreleased] section for next cycle 2026-08-14 12:00:04 +02:00
package.json feat: add fail-fast server generation replacement 2026-08-14 13:40:55 +03:00
README.md feat: rename service IDs to server IDs 2026-08-14 23:25:56 +02:00
tsconfig.build.json feat(client): add runtime-neutral session client 2026-07-31 12:19:19 +03:00
tsconfig.test.json feat(protocol): add service-addressed session RPC 2026-08-13 18:24:27 +03:00
vitest.config.ts feat(client): add runtime-neutral session client 2026-07-31 12:19:19 +03:00

@earendil-works/pi-client

Transport-neutral client for the initial Pi list and attach protocol slice.

import { PiClient, type ByteTransportFactory } from "@earendil-works/pi-client";

const transportFactory: ByteTransportFactory = async (handlers) => {
  // Connect using WebSocket, Unix socket, or another ordered byte transport.
  return {
    async send(chunk) {
      // Deliver bytes in invocation order and honor backpressure.
    },
    close() {},
  };
};

const client = await PiClient.connect({
  serverId: "01234567-89ab-4def-8123-456789abcdef",
  transportFactory,
});
const sessions = await client.listSessions();
const attachment = await client.attachSession(sessions[0].id);

The client verifies that the physical endpoint reports the expected logical serverId. Every list and attach request carries that ID again so the final server can reject misdelivery.

attachSession() currently returns only { sessionId }. Remote Session and Harness methods will be added directly from the new shared interfaces in a later slice. The client does not reconnect or replay requests automatically. After disconnection, call reconnect() and explicitly repeat safe control-plane actions.

Server lifecycle control is not part of PiClient. Launchers use the separate @earendil-works/pi-client/control entry point to request a drain and wait for that server generation to close.

import { requestServerDrain } from "@earendil-works/pi-client/control";

await requestServerDrain({ serverId, transportFactory });
// Drain was acknowledged. Verifying endpoint release and starting a successor are launcher policy.

requestServerDrain() applies a 15-second total timeout across connection, handshake, and drain acknowledgement. Pass timeoutMs to override it. A timeout is ambiguous and is never replayed automatically. The transport-neutral helper does not prove process termination or endpoint release; a Unix launcher separately waits for its stable socket path to disappear.

The control API is administrative, not an authorization boundary. Only expose it over a transport that authenticates launchers as administrators. The experimental local launcher relies on a mode-0600 Unix socket and treats every same-user peer as trusted.

Call transport handlers as follows:

  • handlers.onData(chunk) for inbound bytes;
  • handlers.onClose() for an orderly terminal close;
  • handlers.onError(error) for transport failures.

A transport factory creates a fresh authenticated connection for each attempt. Requests are correlated by ID, and server failures are exposed as PiServerError.

Unix-domain sockets

Node.js and Bun consumers can use the separate Unix transport:

import { PiClient } from "@earendil-works/pi-client";
import { createUnixTransportFactory } from "@earendil-works/pi-client/unix";

const client = new PiClient({
  serverId: "01234567-89ab-4def-8123-456789abcdef",
  transportFactory: createUnixTransportFactory({ path: "/tmp/pi.sock" }),
});
await client.connect();

Unix discovery scans an explicit physical-route directory, derives each expected server ID from its filename, and verifies it through the existing handshake:

import { discoverUnixServers } from "@earendil-works/pi-client/unix";

const routes = await discoverUnixServers({ directory: "/run/user/1000/pi" });
// [{ serverId: "...", path: "/run/user/1000/pi/<serverId>.sock" }]

Malformed entries, non-sockets, stale or unresponsive endpoints, and server-ID mismatches are ignored. Discovery is read-only and probes at most 16 sockets concurrently. Unexpected filesystem and socket errors reject discovery. The caller must choose a short, private directory because Unix socket path limits are substantially lower than normal filesystem path limits. Pass timeoutMs to override the default probe timeout.

PiClientOptions.maxFrameLength bounds protocol payloads. maxPendingBytes bounds queued Unix transport output. Configure matching limits on both peers.