| Version | 0.1.0 |
| Purity | pure (deps: @clawboo/events, @clawboo/executor, @clawboo/gateway-client, @clawboo/logger, @clawboo/protocol; reuses the SAME pure frame parsers the SPA pipeline uses, no Node/WS imports of its own) |
| Purpose | The reference RuntimeAdapter: wraps the OpenClaw Gateway client so start delivers a message (chat.send), events normalizes the Gateway frame stream into the RuntimeEvent union, and setModel/writeContext/abort map onto the Gateway’s typed helpers. |
| Workspace deps | @clawboo/events, @clawboo/executor, @clawboo/gateway-client, @clawboo/logger, @clawboo/protocol |
| External deps | none (runtime) · tsup, typescript, vitest, @clawboo/tsconfig (dev) |
RuntimeAdapter seam, and the template the other four runtimes follow. It changes nothing about how OpenClaw runs; it only re-expresses a live Gateway session as the normalized lifecycle stream. The adapter depends on the structural OpenClawGatewayClient shape (not the concrete GatewayClient class), so a test double or the server’s operator-client slice can be passed in.
Public API
All exports come fromsrc/index.ts. The package declares a single . export in package.json (no subpath barrels).
Classes
| Export | Signature | Contract |
|---|---|---|
OpenClawAdapter | class OpenClawAdapter implements RuntimeAdapter | The adapter. Constructed with (client: OpenClawGatewayClient). id = 'openclaw', participantKind = 'agent'. |
OpenClawAdapter members:
capabilities(): Capabilities: returns{ streaming: true, mcp: false, worktrees: false, resume: true, toolApproval: true, models: [], runtimeClass: 'connected-substrate', nativeSkills: 'preserve', nativeMemory: 'preserve', nativeChannels: 'gateway', nativeScheduler: true }.nativeHomeis deliberately omitted; the Gateway owns its own state dir. Theconnected-substrateclass is what makes the one-shot executor runner refuse OpenClaw by construction: its runs ride the LIVE Gateway session over this adapter’s long-lived client.health(): Promise<HealthResult>: racesclient.agents.list()against a 2 s timeout; any throw →{ ok: false, message }, else{ ok: true }.start(_task: TaskHandle, opts: StartOpts): Promise<RunHandle>: concatenatesopts.context(if any) aboveopts.message, then calls the raw RPCchat.sendwith{ sessionKey, message, deliver: false, idempotencyKey: crypto.randomUUID() }. TherunIdis not returned here; it late-binds on the first streaming frame insideevents(), so the handle is{ adapterId: 'openclaw', sessionKey, runId: null }. A{ok:true}ack that carriesaccepted: falseor a no-run status (no-active-run/no_active_run/dropped/rejected/no_session) throws “acknowledged but did not start a run” so the caller can release/retry instead of blocking on frames that never come.events(run: RunHandle): AsyncIterable<RuntimeEvent>: subscribes eagerly (on call, not on first pull) toclient.onEvent, filters frames torun.sessionKey, late/re-bindsrun.runIdfrom each frame, accumulates non-reasoningtext-deltatext, and yields the mapped stream via a boundedcreateAsyncQueue(max: 1000). It is a continuous session observer; it yields across multiple runs on the same long-lived session. Consumer termination (return()) unsubscribes.abort(run: RunHandle): Promise<void>: two-tier teardown viaPromise.allSettled: the surgical per-runchat.abort(sessionKey, runId)only when arunIdis bound, PLUS the heaviersessions.abort(sessionKey, runId?)backstop ALWAYS (covers the runId-not-yet-bound race and queued/pending work).setModel(run: RunHandle, model: string): Promise<void>:sessions.patch(sessionKey, { model }).writeContext(run: RunHandle, key: string, value: string): Promise<void>:agents.files.set(agentId, key, value), recoveringagentIdfrom theagent:<agentId>:<session>sessionKey shape.
Functions
| Export | Signature | Contract |
|---|---|---|
isTerminalFrame | (frame: EventFrame) => boolean | True when a chat frame represents a run terminal; its parsed payload state is final, aborted, or error. Non-chat frames are never terminal. |
mapFrameToRuntimeEvents | (frame: EventFrame, ctx: MapContext, nextSeq: () => number, now?: () => number, accumulatedText?: string) => RuntimeEvent[] | Pure frame→RuntimeEvent mapper. Stateless except for the optional accumulatedText (used to give an aborted run a non-empty summary, since the Gateway drops streamed text on abort). chat frames: a delta state emits assistant + reasoning text-delta; tool calls/results ride either delta or final; final→done reason:'success', aborted→done reason:'aborted', error→a fatal error + done reason:'error'. agent frames: lifecycle start/end→status (running/turn-complete), error→fatal error; data text→text-delta (channel reasoning when isReasoningStream(stream), else assistant). Unknown events yield []. Reuses the SAME pure parsers as the SPA (parseChatPayload, parseAgentPayload, isReasoningStream, parseMessage, extractText, extractThinking). |
Types & interfaces
| Export | Kind | Contract |
|---|---|---|
OpenClawGatewayClient | interface | The structural slice of the Gateway client the adapter uses (the concrete GatewayClient is assignable; tests pass an in-memory double). Members: onEvent(handler) => () => void · call<T>(method, params?): Promise<T> (raw RPC, used for chat.send) · agents.list() + agents.files.set(agentId, name, content) · sessions.patch(key, { model? }) + sessions.abort(key, runId?) · chat.abort(sessionKey, runId). |
MapContext | interface | { runId: string | null; sessionId: string | null }, the ids the mapper stamps onto every emitted RuntimeEvent base (runId is late-bound and may be null before binding). |
The
Capabilities, HealthResult, RunHandle, RuntimeAdapter, RuntimeEvent, RuntimeEventBase, StartOpts, and TaskHandle types referenced above are owned by @clawboo/executor; EventFrame, AgentsListResult, and SessionPatchResult come from @clawboo/gateway-client. None are re-exported here.Used by
apps/web/src/features/group-chat/useBoardOrchestration.ts, instantiatesnew OpenClawAdapter(client)as theRuntimeAdapterfor the in-browser board-orchestration observer.apps/web/server/lib/teamChat/runTeamExchange.ts, wraps the operator client inOpenClawAdapterto drive a peer-chat turn.apps/web/server/lib/routines/openclawDispatch.ts, importsOpenClawAdapter+OpenClawGatewayClientfor the connected-substrate routine dispatcher (uses the adapter’sstart/events/abortslice;makeAdapteris a test seam).apps/web/server/lib/routines/wakeBridge.ts, probes a scheduled OpenClaw fire vianew OpenClawAdapter(operatorClient).apps/web/server/lib/agentSource/openClawAgentSource.ts, exposes its operator client typed asOpenClawGatewayClient.
Source
packages/adapters/openclaw/src/index.ts (barrel; re-exports ./adapter, ./types, ./mapFrame).
See also
- @clawboo/executor, the
RuntimeAdaptertrait +RuntimeEventunion this adapter implements. - @clawboo/gateway-client, the
GatewayClientwhose structural slice this adapter wraps. - RuntimeAdapter trait, the cross-runtime contract.
- OpenClaw runtime, using the runtime end-to-end.
- Sibling adapters: @clawboo/adapter-claude-code, @clawboo/adapter-codex, @clawboo/adapter-hermes, @clawboo/adapter-native.