* fix(skills): keep ClawHub publisher identity from search through install ClawHub search returns one entry per publisher, so several results can share a slug. Every client collapsed the selection to that bare slug before calling skills.detail and skills.install, and ClawHub answered 409 AMBIGUOUS_SKILL_SLUG with no in-product way forward. searchClawHubSkills now records the publisher-qualified reference once, on the result that carries it, and the Gateway protocol documents it. skills.detail parses the same reference grammar skills.install already accepted, so review and install cannot resolve to different publishers. Control UI carries that one reference through row actions, detail, busy state, and acknowledgement retries, and shows it so otherwise identical rows are distinguishable. Fixes #117633 * fix(apps): send the ClawHub publisher reference from native skill browsers macOS, iOS, and Android read the qualified reference from search results and use it for skills.detail, install, busy state, installed matching, and list identity, so two publishers sharing a slug stay distinct instead of collapsing into one ambiguous request. * fix(skills): refuse external-source skill detail instead of reading a same-slug skill ClawHub has no source-qualified read endpoint, so a skills-sh reference parsed down to its bare slug would have returned a registry skill's card while install resolved the external artifact. Review and install could name different skills. skills.detail now fails closed on any reference that carries a source, and the macOS and AgentPro rows show the publisher reference next to the summary instead of only when a summary is missing, so same-slug rows stay distinguishable. * chore(apps): refresh native i18n source baseline for the skill row references * refactor(skills): drop the unread search-result ownerHandle field installRef is the one reference clients send back, and no client reads the publisher handle separately, so the protocol and Control UI carry one field instead of two. * fix(skills): name the next step when external skill detail is refused Clients that gate install behind a successful review would otherwise see only a refusal, so the error names the direct install path and the CLI equivalent. * fix(macos): use a doc comment on the ClawHub row subtitle swift-format's docComments rule requires doc comments on declarations; the subtitle property carried a regular comment and failed macos-swift. * fix(skills): carry ClawHub trust state to clients that can install Forwarding installRef let clients install the exact publisher the operator picked, including external skills-sh sources. It did not forward the trust state that says ClawHub never scanned that source, so iOS AgentPro — the one surface that installs in a single tap with no review step — could install an unscanned artifact with nothing on screen saying so. The CLI already labels these (docs/clawhub/cli.md, docs/cli/skills.md); native clients could not, because trustState was never on the wire. trustState becomes an optional field on SkillsSearchResultSchema. It is purely additive: older clients ignore an unknown key and the field is absent for registry results, so downgraded readers are unaffected and no protocol version moves. Every client that renders a search row now shows "Not scanned by ClawHub", matching the CLI wording exactly: iOS AgentPro in the row above the install button, macOS and Android beside the review action, and Control UI on the row that explains why review is refused for these sources. Covered by a wire assertion that the state reaches clients for an external source and stays absent for registry rows, plus decode-and-label tests on the shared Swift kit and the Android parser, and a Control UI render assertion. * fix(ui): size the ClawHub detail dialog to a refusal message Refusing detail for an external source made an error-only dialog reachable. The shared preview panel reserves a tall reader height for skill documents, so a two-line refusal rendered in a mostly empty dialog and read as broken rather than deliberate. Found by inspecting the review captures. * revert(ui,apps): drop the ClawHub trust label layer Maintainer product decision: skills.sh runs its own scanners, so OpenClaw does not add a second alert layer in the apps. Removes the label from Control UI, iOS, macOS and Android, and drops the trustState wire field that nothing would render. The CLI keeps its existing label; changing that is a separate call. Publisher identity, the fail-closed detail refusal, and the message-only dialog are unchanged. Splits the oversized skills view test file to satisfy max-lines without a suppression. * test(ui): fix ClawHub skill fixture checks * chore(plugin-sdk): refresh API baseline --------- Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
@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-protocolexports runtime validators, selected schemas, error formatting, and their TypeScript types. This is the main TypeBox-backed entry.@openclaw/gateway-protocol/schemaexports the TypeBox schema graph, including theProtocolSchemasregistry used by generators.@openclaw/gateway-protocol/frame-guardsexports dependency-free structural guards for gateway event and response envelopes.@openclaw/gateway-protocol/client-infoexports client ID, mode, and capability registries plus normalization helpers.@openclaw/gateway-protocol/connect-error-detailsexports structured connect error readers and recovery metadata.@openclaw/gateway-protocol/gateway-error-detailsexports helpers for reading structured details from general gateway errors.@openclaw/gateway-protocol/startup-unavailableexports startup retry constants and helpers.@openclaw/gateway-protocol/versionexports 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:
keyis the established logical session selector used by mostsessions.*CRUD, send, subscription, patch, reset, delete, compaction, and usage methods. A key can be canonicalized or resolved within an agent's session store.sessionKeynames 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.sessionIdis the opaque stored transcript or runtime instance ID. Session results may return it beside a key. Talk, terminal, worker, and selected channel protocols also usesessionIdfor 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.