TypeScript/JavaScript client¶
The official TypeScript/JavaScript client lives in clients/js. Unlike the
read-only Go client, it speaks the WebSocket mutation protocol:
chat, claims, releases, board reads, presence, and receipts. It runs unchanged in
the browser and in Node 20+ (both expose a global WebSocket) and has no runtime
dependencies.
Install¶
npm install @anulum/synapse-channel
Connect and coordinate¶
import { SynapseClient, MessageType } from "@anulum/synapse-channel";
const client = new SynapseClient({
uri: "ws://127.0.0.1:8876",
name: "SYNAPSE-CHANNEL/web-agent",
token: process.env.SYNAPSE_TOKEN,
});
client.on(MessageType.Chat, (m) => console.log(`${m.sender}: ${m.payload}`));
await client.connect();
client.chat("hello", { target: "all" });
client.claim("synapse-channel:web", ["src/web/**"]);
client.requestBoard();
client.release("synapse-channel:web");
client.close();
claim(taskId, paths, pathIdentity?) also accepts the version-1
ClaimScopeIdentity exported by the package. This is an additive transport
type for bridges that already used the Python Git/filesystem resolver; the
dependency-free browser client deliberately does not inspect a local repository
or invent canonical values. When present, its canonical worktree_path is also
sent as the ordinary worktree field so the hub validates the full display
scope. Omit the third argument when no trusted local
resolver is available. The hub then retains legacy literal-path comparison.
connect() opens the socket, sends the registration heartbeat (with the token
when one is configured), and resolves once the hub returns its welcome; it
rejects if the hub closes the socket before welcoming the identity, if no
welcome arrives within readyTimeoutMs, or if close() is called first.
Each connect() is one socket generation. isReady is true only while that
socket is open and welcomed: a hub close, a transport error, a welcome timeout
or close() all leave the client not ready, stop the heartbeat and detach the
socket, so the same instance can connect() again and receives a fresh welcome.
Callbacks from a superseded socket are ignored. A connect() while a socket is
already open or pending rejects; close() it first.
Scope and boundaries¶
For release recovery, call prepareRelease(taskId, epoch?, idemKey?) with a unique
key before sending. Retain the prepared epoch and canonical semantic SHA-256
digest: sorted ASCII JSON keys, compact separators, non-ASCII characters escaped
as \uXXXX, excluding timestamp, client_timestamp, auth, and signature.
Send with release(taskId, prepared.epoch, idemKey). That call proves only a send.
Preparation and confirmation use the hub's Python whitespace rules for task
ids, including NEL while preserving BOM, and Unicode fingerprints use ASCII
JSON escapes. The prepared epoch is typed as an optional number for TS callers.
requestReleaseConfirmation(taskId, operationId, requestDigest, requestId) reads
the original result without another mutation. Match the hub sender, your target
and request id; accept only release_confirmation.status === "confirmed" with
the original task, owner, key, digest and valid receipt. Unknown and ordinary
legacy snapshots remain uncertain. Confirmation is historical and does not free
a newer claim. The wire contract and
manual CLI workflow define the same recovery semantics as Python.
The optional scoped attachment API uses wire version four.
Configure signRegistration with a bound identity signer and signAttachment
with a per-message signer, then call attachment(MessageType.AttachmentBegin,
fields) and subscribe to MessageType.AttachmentResult. The client refuses
these requests without a version-four welcome or an attachment signer. The
caller supplies the credential-backed signing callbacks; the dependency-free
client does not manage private keys.
The client implements the agent-side envelope and the connection lifecycle:
registration, keepalive heartbeats, typed send helpers, and inbound dispatch by
MessageType. It does not run the hub, does not enforce ACLs, and does not verify
per-message authentication — those are hub-side. On a secured or ACL-enforcing
hub, supply the connect token; namespace authorisation still depends on the hub
binding the sender, so use a token (and per-message auth) on an exposed hub.
It is a separate npm package and does not ship inside the Python synapse-channel
distribution.