npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

connector-protocol

v0.4.0

Published

WebSocket protocol schemas + shared DTOs for the Synx connector daemon (synx-connector). Zod schemas and inferred types for the daemon↔API control channel — zero private workspace dependencies.

Readme

connector-protocol

WebSocket protocol schemas and shared DTOs for the Synx connector daemon (synx-connector) — the local agent that bridges a developer's machine to the Synx API so Claude-agent sessions can run against local folders.

This package is the single source of truth for the daemon ↔ API control-channel message shapes. It is published so the public synx-connector CLI can depend on it; it has zero dependencies on private @synx/* workspace packages and its only runtime dependency is zod.

Where the private Synx API / web apps need these DTOs, @synx/types re-exports from this package. The dependency direction is always public ← private — never the reverse.

Install

npm install connector-protocol zod

zod (^4) is a peer dependency, not a bundled one — install it alongside. This package constructs and returns z.ZodError values across its API boundary (parseConnectorMessage returns { success: false, error: ZodError }), so it must share the same zod instance as its consumer; a peer dependency guarantees a single deduplicated copy and keeps instanceof ZodError checks working. (synx-connector already depends on zod directly, satisfying the peer.)

Usage

Parse an inbound WS frame

parseConnectorMessage accepts a raw JSON string or an already-decoded value and returns a discriminated result — it never throws for control flow.

import { parseConnectorMessage, serializeConnectorMessage } from "connector-protocol";

const result = parseConnectorMessage(rawFrame); // rawFrame: string | unknown
if (!result.success) {
  // result.error is a ZodError
  return;
}
switch (result.message.type) {
  case "session.start":
    // result.message is fully typed as SessionStartMessage here
    break;
}

// A bare heartbeat — minimum a daemon must send on each cadence tick.
const frame = serializeConnectorMessage({ type: "heartbeat", ts: Date.now() });

// A heartbeat carrying the optional liveness signal: the ids of sessions with
// an SDK turn currently in flight. The server bumps each listed session's
// `updated_at` so the UI can tell "agent busy, just quiet" from "daemon gone".
// `busySessionIds` is OPTIONAL and additive — omit it and you get the same
// behavior as before it existed.
const liveness = serializeConnectorMessage({
  type: "heartbeat",
  ts: Date.now(),
  busySessionIds: ["1f0c…", "2a7b…"],
});

Additive-field convention (for external daemon authors). New OPTIONAL fields like busySessionIds are added without bumping the message type. This is safe in both directions: an older daemon that never sends the field behaves exactly as before, and an older server strips unknown additive fields during parsing (the message schemas are z.object, which drops unrecognized keys by default) — so a newer daemon still talks to an older server. Send new optional fields freely; never rely on the server echoing or persisting a field your server version predates.

Heartbeat cadence (single source of truth)

The cadence and its derived timeout / liveness windows live in one place so the daemon's send interval, the server's reap timeout, and the web liveness UI can never drift apart:

import {
  CONNECTOR_HEARTBEAT, // { INTERVAL_MS, TIMEOUT_MULTIPLIER }
  HEARTBEAT_INTERVAL_MS, // send a heartbeat this often (ms)
  HEARTBEAT_TIMEOUT_MS, // no heartbeat for this long ⇒ peer presumed gone
} from "connector-protocol";

HEARTBEAT_TIMEOUT_MS is derived as INTERVAL_MS × TIMEOUT_MULTIPLIER (3× the cadence), tolerating one missed beat plus clock skew before a peer is judged gone. A custom daemon should send a heartbeat every HEARTBEAT_INTERVAL_MS.

Session events (forward/backward compatible)

The session.event envelope keeps its eventType as an open string and its payload as unknown, so a daemon built against an older version of this package never hard-fails on an event type a newer server introduces. Decode the payload with parseSessionEvent, which returns a known / unknown result:

import { parseSessionEvent } from "connector-protocol";

const parsed = parseSessionEvent(msg.eventType, msg.payload);
if (parsed.kind === "known") {
  // parsed.type narrows parsed.payload to the matching typed shape
} else {
  // unknown (or malformed-known) event — store/forward parsed.payload verbatim
}

This is the design choice for compatibility: the envelope is permissive, the typed validation is a second, opt-in step. The SDK is known to emit undocumented message types (e.g. rate_limit_event); they flow through as unknown events rather than breaking the channel.

Tool-result truncation (byte-accurate)

import { truncateToolResult, MAX_TOOL_RESULT_BYTES } from "connector-protocol";

const { content, truncated, originalBytes } = truncateToolResult(bigOutput);

Truncation is measured in UTF-8 bytes, not characters, and never splits a multi-byte sequence.

Close codes

import { CONNECTOR_CLOSE_CODES } from "connector-protocol";
// 4401 AUTH · 4403 REVOKED · 4408 PROTOCOL_VIOLATION

Stability

Pre-1.0 (0.x): the protocol may change between minor versions until the connector v1 surface settles.

License

MIT