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

@graphmind-ai/schema

v0.5.1

Published

Versioned wire contract (Zod schemas + TypeScript types) for the GraphMind live agent debugger protocol.

Downloads

2,952

Readme

@graphmind-ai/schema

The versioned wire contract of the GraphMind live agent debugger: Zod schemas + inferred TypeScript types for every message that crosses the WebSocket between an instrumented app and a viewer, plus a version-negotiating parser and a JSON Schema export for adapter authors in other languages.

Publishing note: the @graphmind npm scope is currently taken by an unrelated project, so this package is "private": true and workspace-internal until the published name is decided. The wire contract itself is name-independent.

The envelope

Every frame is one JSON text message:

{
  "gm": 1,          // protocol MAJOR version — see "Versioning"
  "seq": 42,        // per-sender monotonically increasing counter
  "ts": 1756200000000, // sender wall clock, epoch ms
  "runId": "run_ab12cd34ef56", // owning run, or "*" if not run-bound
  "type": "node.started",
  "payload": { /* type-specific */ }
}
  • seq lets receivers deduplicate replays: after a reconnect the app re-sends its ring buffer, and envelopes keep their original seq.
  • runId is "*" (WILDCARD_RUN_ID) for handshake and breakpoint/mode messages, which are not bound to a run.

Events (app → viewer)

| type | payload | |---|---| | run.started | app, sdk {name, version}, meta? (record) | | run.finished | status: ok\|error\|aborted, error? {name, message, stack?} | | graph.hint | nodes: [{nodeId, kind, name, parentId?}] — optional static structure | | node.started | nodeId, parentId?, kind: agent\|llm\|tool\|custom, name, instanceId, input | | node.token | nodeId, deltas: [{t: text\|reasoning\|tool-args, v}] | | node.finished | nodeId, output, usage? {inputTokens, outputTokens}, durationMs, status | | node.error | nodeId, error {name, message, stack?} | | exec.paused | pauseId, nodeId, point: before\|after\|error | | exec.resumed | pauseId, action: continue\|retry\|inject\|abort |

exec.resumed is emitted by the app for every release of a held gate — including releases the app performed itself (fail-open auto-continue on disconnect, pause timeout) — so a viewer can always reconstruct pause history.

Controls (viewer → app)

| type | payload | |---|---| | exec.resume | pauseId, action: continue\|retry\|inject\|abort, output? (inject only) | | breakpoint.set | matcher {kind?, name?, point?} | | breakpoint.clear | matcher {kind?, name?, point?} — removes by exact matcher equality | | mode.set | mode: run\|step |

Breakpoint matcher semantics: every present field must match; absent fields match anything; point defaults to before. {} therefore means "pause before every node". Matchers are deduplicated and cleared by exact field equality.

Handshake

The app is the WebSocket client (it dials the viewer, default ws://127.0.0.1:4747/ingest).

  1. app → viewer: hello — versions {protocol, client}, capabilities: ["pause", "step", "inject", "retry", "abort", ...], app?, sdk?
  2. viewer → app: hello.ack — versions {protocol, viewer}, capabilities, breakpoints (current matchers) and mode (current mode)

Until hello.ack arrives the app must consider itself detached (gates pass through). The ack carries the viewer's full desired debug state so an app that reconnects — or attaches mid-run — is re-armed in a single message. Capability strings are open-ended: unknown ones must be ignored, not rejected.

Versioning & forward compatibility

gm is the protocol major. The rules, implemented by parseEnvelope:

  • Reject any envelope whose gm differs from yours → version-mismatch.
  • Tolerate unknown message types → unknown-type (opaque but valid envelope; don't error, don't crash).
  • Tolerate unknown payload fields and unknown envelope fields — all object schemas are loose and preserve extra keys.
  • Anything structurally broken → invalid (with reason + issues).
import { parseEnvelopeJson } from '@graphmind-ai/schema';

const result = parseEnvelopeJson(frame); // never throws
switch (result.kind) {
  case 'ok':               /* result.envelope is the discriminated union */ break;
  case 'unknown-type':     /* forward compat: ignore or display raw */ break;
  case 'version-mismatch': /* stop talking; report result.received */ break;
  case 'invalid':          /* log result.reason, drop the frame */ break;
}

Sending side:

import { createEnvelope, serializeEnvelope } from '@graphmind-ai/schema';

ws.send(serializeEnvelope(createEnvelope({
  type: 'node.token',
  payload: { nodeId: 'n1', deltas: [{ t: 'text', v: 'Hel' }] },
  seq: seq++,
  runId,
})));

JSON Schema artifact

schema.json (package root, regenerated by pnpm build) is a JSON Schema (draft 2020-12) rendering of the whole contract, for adapters not written in TypeScript. exportJsonSchema() / exportJsonSchemaString() produce it programmatically. A golden-file test pins its exact content: any diff to test/fixtures/schema.golden.json is a contract change and should be reviewed as one.

Divergences from the validated spike (examples/spike)

The spike (24/24 assertions against [email protected]) proved the gating mechanism; this package generalizes its ad-hoc protocol. Differences, with reasons:

  1. Gate points are node-relative (before | after | error), not SDK-relative (before-step | before-tool | on-error). The spike's points only made sense for the ai package's step/tool loop; production nodes are agent | llm | tool | custom, so "before an llm node" == the spike's "before-step" and "before a tool node" == "before-tool".
  2. Structured breakpoint matchers replace the spike's string keys ("before-tool:searchFlights"). Matchers add kind and default point: 'before', and clear by exact equality instead of set-string identity.
  3. hello/hello.ack replace the spike's implicit handshake (the spike armed breakpoints via a bare bp.set on connect and treated the first bp.set as "handshake done"). The ack now carries versions, capabilities, breakpoints and mode, so reconnects re-arm atomically — a direct lesson from the spike's connect() waiting on bp.set.
  4. exec.resume is flat (action + optional output) rather than the spike's nested { type, output } action objects — simpler to validate and to extend with per-action fields later.
  5. run.finished gained optional error so a viewer can show why a run failed without correlating a separate node.error.
  6. runId is mandatory on every envelope (the spike had none). Control messages not bound to a run use "*".
  7. New event types (run.*, graph.hint, node.token, node.finished usage/duration) — the spike only had a free-form exec.node event; a real viewer needs typed lifecycle events and token streams (the spike's stream-tee finding f.1/f.2 is what node.token carries).
  8. exec.resumed is a first-class event (the spike only traced resolution internally) so pause history survives on the wire.

Scripts

  • pnpm typecheck — tsc over src + tests
  • pnpm test — vitest (round-trip property tests, forward-compat, version negotiation, golden file)
  • pnpm build — emit dist/ (ESM + .d.ts) and regenerate schema.json