| .. | ||
| web | ||
| binding-registry.ts | ||
| extension.ts | ||
| package-lock.json | ||
| package.json | ||
| protocol.ts | ||
| README.md | ||
| server.ts | ||
| tui.ts | ||
| ws-transport.ts | ||
Cap'n Web client binding spike
A separate version of the client-capability spike using Cap'n Web for symmetric, object-capability RPC.
It demonstrates a server extension invoking a typed client binding without knowing whether the originating client is a TUI or browser. The TUI renders notifications in yellow. The browser displays them centrally and dramatically spins them away.
Architecture
browser/TUI exports ClientBindingApi server exports ServerApi
┌──────────────────────────────┐ ┌───────────────────────────┐
│ event(event) │◀─────────│ prompt(text) │
│ notify({ message }) │─────────▶│ command(name, input) │
└──────────────────────────────┘ Cap'n Web└───────────────────────────┘
▲ │
│ ▼
└──── originating-client stub ─ notify extension
├─ notify command
├─ notify LLM tool
└─ exact-Paris hook
Every WebSocket connection is a symmetric Cap'n Web session:
- The server exports a connection-scoped
ServerApias its main RPC interface. - The client exports
ClientBindingApias its main RPC interface. - The client calls
connect()with binding IDs and versions. - The server retains that connection's remote client stub.
- A prompt arrives through the connection-scoped server object, so the originating client is known without trusting a caller-supplied client ID on every call.
- Commands, tools, and hooks call
context.clientBindings.notify(). Cap'n Web handles reverse invocation, request correlation, promise resolution, and remote errors.
Pi still owns semantic concerns that Cap'n Web does not provide: binding names and versions, originating-client selection, runtime validation, timeouts, and extension-to-tool exposure. TypeBox validates RPC values because Cap'n Web's TypeScript interfaces do not perform runtime validation.
Unlike the HTTP/SSE spike, prompts, events, commands, and client binding calls all use one WebSocket. Plain HTTP only serves the static browser files.
This remains a local spike. Authentication, persistent sessions, reconnect replay, controller leases, and production authorization are intentionally omitted. Cap'n Web is also currently described by its maintainers as experimental.
Install
The experimental dependencies are isolated from the coding-agent package:
npm install --prefix packages/coding-agent/examples/capability-capnweb-spike --ignore-scripts
Run
From the repository root:
OPENAI_API_KEY=... npm run server --prefix packages/coding-agent/examples/capability-capnweb-spike
The default model is openai/gpt-5.4-nano. Select another registered OpenAI or Anthropic model with:
PI_CAPNWEB_SPIKE_MODEL=anthropic/claude-haiku-4-5 ANTHROPIC_API_KEY=... \
npm run server --prefix packages/coding-agent/examples/capability-capnweb-spike
The server starts without provider credentials, which is enough to exercise direct binding calls. LLM prompts require the selected provider's environment credential.
Open http://127.0.0.1:8788 for the browser client.
In another terminal, run the TUI:
npm run tui --prefix packages/coding-agent/examples/capability-capnweb-spike
Try the direct binding path:
/notify TUI binding confirmed.
Or prompt the model:
Notify me that Cap'n Web works, then briefly explain what happened.
When multiple clients are connected, notifications target only the client that submitted the prompt.
For a deterministic extension-initiated call without an LLM tool call, ask:
What is the capital of France?
The system prompt makes the assistant answer exactly Paris. The extension observes that completed response and calls the originating client's notify() binding with ❤️.
Files
protocol.ts: shared TypeScript RPC interfaces, binding metadata, and TypeBox schemasbinding-registry.ts: connected-client routing, negotiation, validation, timeout, and stub invocationextension.ts: binding-facing command, LLM tool, and exact-Parisresponse hookserver.ts:AgentHarness, HTTP static server, and connection-scoped Cap'n Web APIsws-transport.ts: typed adapter between Node'swsserver andRpcSessiontui.ts: Cap'n Web TUI clientweb/: static Cap'n Web browser clientpackage.json: isolated, exact-version Cap'n Web and WebSocket dependencies