Files
openclaw/packages/gateway-protocol
Vyctor H. Brzezowski f61ec66249 Preserve ClawHub external source identity and expose only supported actions (#124250)
* fix(skills): keep ClawHub search results on the source the operator picked

ClawHub search returns each result's origin under `install.reference`, but the
response model expected a flat `installRef`. That field is never present, so
every row fell through to a synthesized `@owner/slug` reference. External
skills.sh results were rewritten onto a ClawHub-native identity, dropping both
the commit-pinned source and the "not scanned by ClawHub" trust record.

Map the search wire shape explicitly and make the search contract
action-specific: `installRef` always names the result's own source, `detailRef`
appears only while ClawHub can serve a detail card for that identity, and
`trustState` travels with unscanned sources. Clients render install directly
when detail is absent instead of offering a review the Gateway must refuse.

Covers the Control UI, macOS, iOS Settings, iOS AgentPro, and Android, which
previously routed every row through review and could not install an external
skill at all.

* fix(skills): make install-only sources explicit and keep legacy review intact

Address review findings on the search identity contract:

- Replace the detail-reference capability with an explicit `installOnly` flag.
  A Gateway released before this field omits it, and reading omission as
  install-only made ordinary registry results skip the reviewed-version flow on
  every client. Absence now means the existing review-then-install path.
- Parse closed source variants in the producer. A row whose source is unknown,
  whose external reference is missing, or whose registry publisher is absent is
  dropped instead of falling through to `@owner/slug`, which was the original
  source swap in a different disguise.
- Carry the exact install reference alongside the canonical slug. The Gateway
  already records `requestedReference`; the clients dropped it and matched
  installs by slug, so a completed external install read back as unknown.
- Gate the direct-install action on admin rights. The row previously stayed
  enabled for read-only operators and reached a guard that silently returned.
- Route the unscanned-source warning through the native and Control UI string
  catalogs instead of a hardcoded literal.

* chore(i18n): leave generated native locale artifacts to the refresh workflow

Preflight isolates generated locale output from source changes: only the native
sources and apps/.i18n/native-source.json belong in a feature commit.

* fix(skills): satisfy Android ktlint wrapping and Swift test link construction

Extract the ClawHub result action guard into a named value so the multiline
condition follows ktlint wrapping, and pass the new requestedReference field in
the OpenClawKit installed-link fixtures.

* fix(skills): preserve external install identity across clients

* test(skills): add exact refs to recommendation fixtures

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-15 21:38:46 -07:00
..

@openclaw/gateway-protocol

Typed schemas, inferred TypeScript types, and runtime validators for the OpenClaw Gateway WebSocket protocol.

The current wire protocol is version 4. General clients must use v4; authenticated node clients and lightweight probes may use the N-1 window during rolling upgrades. See the Gateway protocol specification for transport, authentication, roles, scopes, and complete frame examples.

Versioning

Package versions follow the OpenClaw calendar release train: YYYY.M.PATCH, with the same prerelease suffix when applicable. A package version therefore identifies the OpenClaw source release that produced the schemas; it is not the wire protocol number.

The wire protocol integer is versioned separately. Its current value is exported as PROTOCOL_VERSION from @openclaw/gateway-protocol/version. Gateway protocol changes are additive first. An incompatible wire change requires an explicit protocol-version decision and coordinated client follow-through. See CHANGELOG.md for the wire and schema history.

Install

npm install @openclaw/gateway-protocol

Entry points

  • @openclaw/gateway-protocol exports runtime validators, selected schemas, error formatting, and their TypeScript types. This is the main TypeBox-backed entry.
  • @openclaw/gateway-protocol/schema exports the TypeBox schema graph, including the ProtocolSchemas registry used by generators.
  • @openclaw/gateway-protocol/frame-guards exports dependency-free structural guards for gateway event and response envelopes.
  • @openclaw/gateway-protocol/client-info exports client ID, mode, and capability registries plus normalization helpers.
  • @openclaw/gateway-protocol/connect-error-details exports structured connect error readers and recovery metadata.
  • @openclaw/gateway-protocol/gateway-error-details exports helpers for reading structured details from general gateway errors.
  • @openclaw/gateway-protocol/startup-unavailable exports startup retry constants and helpers.
  • @openclaw/gateway-protocol/version exports the current and minimum accepted protocol versions.

The frame-guards, client-info, connect-error-details, gateway-error-details, startup-unavailable, and version entry points are TypeBox-free. Prefer them when a browser bundle only needs envelope dispatch, handshake constants, or reconnect policy. This also avoids runtime compilation in CSP-sensitive consumers. The root and schema entry points provide the full validation surface and depend on TypeBox.

Validate an inbound frame

The compiled validators are callable type guards. Their errors property contains the most recent validation errors.

import { formatValidationErrors, validateRequestFrame } from "@openclaw/gateway-protocol";

const frame: unknown = JSON.parse(inboundText);

if (!validateRequestFrame(frame)) {
  throw new Error(formatValidationErrors(validateRequestFrame.errors));
}

console.log(frame.id, frame.method);

validateRequestFrame validates the request envelope. Dispatch code must also use the validator for the selected method's params; the root entry point exports those validators as validate*Params functions.

Guard an event without TypeBox

Use the lightweight guards when code only needs safe frame discrimination. They check dispatch-critical envelope fields and intentionally allow additive payload fields.

import { isGatewayEventFrame } from "@openclaw/gateway-protocol/frame-guards";

const frame: unknown = JSON.parse(inboundText);

if (isGatewayEventFrame(frame)) {
  console.log(frame.event, frame.seq);
}

Build handshake version and capability fields

Protocol levels and client capabilities live in TypeBox-free entry points.

import { GATEWAY_CLIENT_CAPS } from "@openclaw/gateway-protocol/client-info";
import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";

const handshake = {
  minProtocol: MIN_CLIENT_PROTOCOL_VERSION,
  maxProtocol: PROTOCOL_VERSION,
  caps: [GATEWAY_CLIENT_CAPS.TOOL_EVENTS],
};

Nodes and probes use MIN_NODE_PROTOCOL_VERSION and MIN_PROBE_PROTOCOL_VERSION, respectively. A capability advertises client support; it does not grant authorization.

Contract notes

Session identifiers

Several identifier names coexist because they identify different things:

  • key is the established logical session selector used by most sessions.* CRUD, send, subscription, patch, reset, delete, compaction, and usage methods. A key can be canonicalized or resolved within an agent's session store.
  • sessionKey names the same logical routing identity where the contract needs to make that meaning explicit. chat.*, session file and diff APIs, transcript branch/rewind/fork APIs, agent events, and channel delivery payloads use this spelling.
  • sessionId is the opaque stored transcript or runtime instance ID. Session results may return it beside a key. Talk, terminal, worker, and selected channel protocols also use sessionId for their own concrete session instances; do not substitute a logical session key there.

Follow each method schema rather than converting fields based on their spelling. sessions.resolve is the explicit bridge when a caller has a key, raw session ID, label, Control UI short ID, or parent/agent scope.

Intentionally open fields

The schema graph is strict by default, but roughly 60 fields intentionally use Type.Unknown() passthroughs. The main clusters are transport-owned channel payloads, logs-chat message and attachment passthrough, worker and node tool arguments/results, and the dynamic config.schema response. Frame params, payload, and error details are also open at the envelope layer because the selected method, event, or error code owns their concrete shape.

Do not treat these fields as validated domain objects. Narrow them at their owner boundary before reading nested values.

Machine-readable schema

protocol.schema.json ships in the npm tarball as the generated machine-readable contract. It contains the frame union, named schema definitions, and core method metadata. It is generated during prepack and is not committed to the repository.

Method discovery

The hello-ok.features.methods list is conservative discovery, not a complete enumeration of every callable method. It reflects the methods the connected Gateway intentionally advertises. Core-internal, role-specific, plugin-provided, or otherwise non-advertised methods can have valid schemas without appearing in that list. Clients should use discovery to enable optional UI, not to reject an otherwise documented method contract.