| .. | ||
| src | ||
| test | ||
| CHANGELOG.md | ||
| package.json | ||
| README.md | ||
| tsconfig.build.json | ||
| tsconfig.test.json | ||
| vitest.config.ts | ||
@earendil-works/pi-server
Experimental local server for the new durable Session and Agent Harness interfaces.
The current slice supports Session discovery, creation, exclusive attachment, prompting, and optional main-lane observation. A host that implements HostedHarnessHandle.watch() supplies an authoritative snapshot plus buffered events. The server owns the attachment's single watch ID, starts delivery only after the client has received the snapshot, and removes the watch when the connection attachment is released.
listcalls the host's Session catalog without opening sessions.createasks the host to persist an optional Session ID and working directory without opening a Harness.attachfinds the requested metadata, passes it to the host, and retains the returned Harness handle in the server.promptexecutes one serializable Harness prompt through the requesting client's attached handle.watch,startWatch, andstopWatchprovide snapshot-first lane observation when supported by the host.
A Session permits one attached client connection at a time. Repeating attach from that connection is idempotent; another connection receives session_in_use. Prompting requires that exact connection to own the targeted attachment. Losing the connection rejects the local response but releases its attachment lease only after accepted prompts settle. The host decides when zero client demand and Harness activity permit worker retirement. Server shutdown closes every hosted Harness, releasing its Session writer ownership.
import { randomUUID } from "node:crypto";
import { MemorySessionRepo, type Session } from "@earendil-works/pi-agent-core";
import {
type HostedHarnessHandle,
type PiServerHost,
} from "@earendil-works/pi-server";
import { createUnixServer, getUnixSocketPath } from "@earendil-works/pi-server/unix";
async function startServer(
createHarnessForSession: (session: Session) => Promise<HostedHarnessHandle>,
) {
const sessions = new MemorySessionRepo();
const host: PiServerHost = {
sessions: {
list: () => sessions.list(),
async create({ id }) {
const session = await sessions.create({ id });
try {
return session.metadata;
} finally {
await session.close();
}
},
},
async createHarness(metadata) {
const session = await sessions.open(metadata);
try {
return await createHarnessForSession(session);
} catch (error) {
try {
await session.close();
} catch (cleanupError) {
throw new AggregateError(
[error, cleanupError],
"Harness creation and Session cleanup failed",
);
}
throw error;
}
},
};
const serverId = randomUUID();
const server = createUnixServer(host, {
serverId,
path: getUnixSocketPath(serverId, "/run/user/1000/pi"),
});
await server.start();
return server;
}
Applications supply a Session catalog and a Harness factory. The catalog lists and creates durable metadata; the host receives the repository's concrete metadata and owns opening the Session, creating the Harness, and cleaning up failed Harness creation. This permits the host to perform those operations in a worker process without passing an open JavaScript Session across processes.
serverId is a logical identity supplied by the launcher, not a socket address. The Unix preset requires an explicit physical path; getUnixSocketPath() derives one from a caller-selected directory. Choose a short, private runtime directory rather than deriving the route from an unbounded home-directory path. A long-lived launcher can reuse the same ID and path when replacing a server process.
PiServer composes authenticated transports through PiServerListener. The Unix submodule provides createUnixListener() and createUnixServer(). Low-level CBOR framing and validation come from @earendil-works/pi-protocol.
Server and worker lifecycle is managed outside the public Pi protocol. The replaceable application server converts connection attachments into private demand updates; the worker combines generation-tagged demand with authoritative Harness activity. The experimental coordinator only supplies stable routing and reports generic server-generation connection changes.