Skip to content

Fully support the OpenClaw gateway protocol across the current and previous lines - #77

Open
molefrog wants to merge 20 commits into
mainfrom
claude/openclaw-setup-requirements-f3hbj5
Open

Fully support the OpenClaw gateway protocol across the current and previous lines#77
molefrog wants to merge 20 commits into
mainfrom
claude/openclaw-setup-requirements-f3hbj5

Conversation

@molefrog

@molefrog molefrog commented Aug 4, 2026

Copy link
Copy Markdown
Owner

OpenClaw chats in moi now stream token previews live, flip tool cards pending → running → done with output mid-run, and honor the model/effort pickers via sessions.patch — verified against real gateways, not specs. Cron, channel, and subagent sessions stop rendering as anonymous empty chats: the selector badges them, isolated cron chats show their run history (cron.runs — the per-run transcripts are orphaned by the gateway's post-run session reset), and compaction/model switches surface as notice rows. The support target is wire protocol 4 (2026.7.x and the still-maintained 2026.6.x line, both live-verified); protocol-3 gateways (≤ 2026.5.x) are detected and surfaced as "gateway too old" instead of silent empty lists.

Screenshots (live runs, both gateway versions) are in the session; the key one shows a picker switch to Sonnet 4.6 with the reply confirming the model and a "Model changed" notice row.

Risky spots to review:

  • server/harness/openclaw/session.ts — the preview lifecycle (run-scoped meta.apiMessageId, trailing-delta clears on final/error/aborted) and the echo rendezvous by <runId>:user idempotency key, whose field placement differs between 2026.6.x and 2026.7.x (compat.ts).
  • server/harness/openclaw/compat.ts rule: old gateways validate params with additionalProperties: false — any new RPC param must be gated (archive translates the 6.x rejection into "needs OpenClaw 2026.7+").
  • client/features/chat/interleave-notices.ts — chronological notice placement uses <= so equal-timestamp notices render before the turn they announce.

Verified: bun test server/ client/ → 907 tests, 0 real failures (2 service-e2e cases require the dev server stopped, confirmed passing standalone); lint + typecheck:client clean; live end-to-end chats through 2026.7.1 and 2026.6.33 gateways from the real UI, plus a protocol mismatch classification probe against 2026.4.22. Protocol details and drift tables: server/harness/openclaw/NOTES.md.

claude added 4 commits August 4, 2026 10:33
Verified against openclaw@2026.7.1 in Claude Code on the web: the sandbox
Node (22.22.2) is below openclaw's SQLite-safety floor, so the doc covers
the nvm workaround, headless gateway bootstrap on the real profile, moi
skill install, and the one prerequisite the sandbox cannot self-serve — a
model provider credential.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
End-to-end confirmed with real keys: paste-api-key into the per-agent auth
store (shell env is off by default), set the default model, and restart the
detached openclaw-gateway process — killing the CLI wrapper alone leaves a
stale gateway serving old config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
…odel switch

Trunk of the protocol work, all shapes verified against live 2026.7.1 and
2026.6.33 gateways: chat delta/final frames drive StreamPreviews (cleared via
run-scoped meta.apiMessageId), session.tool frames flip tool cards
pending→running→success/error with inline output, sessions.patch applies
picker model/thinking before sends, the user echo matches on the
<runId>:user idempotency key (both field placements), and connect failures
classify protocol-3 gateways into a visible status instead of silent empty
lists. Session lists refresh on create/patch/subagent lifecycle reasons,
and compaction + mid-session model changes surface as notices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
sessions.steer interrupts the active run (interruptedActiveRun) and emits
chat state 'aborted' — clear the streaming preview on it like final/error,
verified live. README capability matrix and the OpenClaw prose now reflect
the protocol-4 feature set instead of the deliberate v2 cuts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e213d217b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread server/harness/openclaw/discovery.ts
Comment thread server/harness/openclaw/session.ts Outdated
Comment thread server/harness/openclaw/session.ts Outdated
claude added 3 commits August 4, 2026 18:17
…port

Server: session lists carry origin (external channels) + flavor
(cron/subagent); isolated cron chats render their run history via cron.runs
instead of an empty transcript (per-run transcripts are orphaned by the
gateway's post-run session reset); archiveSession patches archived:true with
actionable errors for the main chat and pre-2026.7 gateways; models.list
values are full provider/id refs so the picker, sessions.patch, and the
applied-model cache agree; cold transcripts interleave model-change notices.

Client: compact + model-change notices render as centered muted rows
interleaved chronologically; chat selector badges cron/subagent/channel
sessions; equal-timestamp notices sort before the turn they announce.

Review fixes on the trunk: reconcile clears stuck running tool cards,
notices fold into the REST view, session-list refreshes fire on send/steer
(2026.6.x emits no chat.title) with a trailing flush, re-emitted turns keep
their original preview slot, thinking-level patch failures degrade instead
of blocking sends, archive errors pass through the API.

Fixture tests from live-captured frames (2026.7.1 + 2026.6.33) cover the
compat helpers, preview mapping, tool states, and cron synthesis. NOTES.md
rewritten against the verified protocol-4 reality.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Spawned sessions carry "[Subagent Context] … [Subagent Task] …" as their
first user message; surface the task itself in the chat selector, previews,
and the transcript bubble, with a plain label fallback when a truncated
derived title cuts the task marker.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Live and cold paths now place the notice identically — before the turn it
announces — instead of drifting after it on the live path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
@molefrog molefrog changed the title OpenClaw: add live streaming, tool state, and model switching Fully support the OpenClaw gateway protocol across the current and previous lines Aug 4, 2026
claude added 4 commits August 4, 2026 23:24
Delivery failures record both a summary and an error; summary-only
rendering made a failed run indistinguishable from a clean one. Errored
runs now lead with the error and keep the reply below it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
The openclaw package is an optionalDependency — guard the dynamic SDK
import in both gateway paths so a missing install degrades to silent-null
discovery (one-shot) or a classified rejection (persistent) instead of a
module-not-found crash. Streaming previews now require stream === true,
matching the other harnesses' omitted-means-whole-block contract. Seeding a
live session reconstructs model-change notices from durable rows with the
same ids as the live emitter, so REST replays agree with the stream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Client-only dev route rendering the production chat components over inline
fixtures: every turn shape, tool-call state (pending/running/success/error/
approval), MCP and skill rows, subagent cards in all three statuses with a
nested transcript, live text and thinking previews, both notice kinds,
error and empty states, and chat selector rows with cron/subagent/channel
badges. Sections carry captions where production behavior lacks a dedicated
treatment yet (user-bubble markdown, TurnMeta, approval rows, pending vs
running) so restyling work is visible from one page. Component changes are
export-only extractions; fixtures are typed against lib/format.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
The catalog is now a single scripted debugging conversation flowing through
every state — merged tool runs, an exec timeout, the approval flow, a run
failure, a model change with its notice carrying full turn meta,
attachments, a channel-routed message, subagent cards, compaction — ending
in a "now" cluster with running work and a switchable live tail (none /
dots / thinking / text) plus an error-banner toggle. State names moved to
jump links; designer caveats moved to a collapsed notes block after the
conversation; the chat header renders the real session dropdown closed by
default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Comment on lines +109 to +113
const gateway = status.connected
? `gateway connected · protocol ${status.info?.protocol ?? '?'} · server ${status.info?.serverVersion ?? '?'}`
: status.failure
? `gateway ${status.failure.kind}: ${status.failure.message}`
: 'gateway not connected yet'

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where will this be displayed?

claude added 9 commits August 5, 2026 07:42
The narrative gains an investigation beat: TodoWrite, WebSearch, and
WebFetch rows, a cited reply with source-url and source-document links, an
Edit, and an image Read. The designer notes gain the findings that matter:
citations carry data but render nothing, Edit output is unhighlighted, the
Read image preview needs a live workspace, tool-return origins cannot occur
on the wire, and a "No rendering yet" list of the six skipped notice kinds
plus SessionSnapshot and ResultSummary with realistic payloads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
The logo playground (and its pixel painter) is gone; /dev now lists every
dev page with its path and a one-line description. New routes get listed
here alongside their DevRoutes entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
The /dev lazy import is gated on the NODE_ENV define, statically false in
scripts/build-client.ts builds — the bundler drops the dynamic import and
emits nothing from features/dev into dist (verified: a prod build contains
zero traces of the gallery, harness debug, or tool-call pages). In prod
/dev redirects home; the dev server keeps serving it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Three bugs surfaced testing against a live newer gateway (2026.7.2-beta.7,
state schema v6):

- moi appends the <moi-context> envelope to the message text (sessions.send
  has no system channel), so the gateway baked it into derivedTitle and
  displayName. toSessionInfo now strips it from every title source, not just
  the preview — verified the session title no longer shows the envelope.
- The tool-result wire shape drifted: 2026.7.2+ nests the result in a
  toolResult-typed block (rendered as a bare "[toolResult]") and its exec
  sandbox passes a `code` script instead of `command` (empty "Bash $").
  flattenToolResultContent now unwraps nested blocks; the tool brief reads
  `code` when `command` is absent; the label map learns bash/ls/find/grep.
- Hardened the optimistic user-echo rendezvous: match on the runId extracted
  from the durable idempotency key (covers the echo-before-send-response
  race) and compare whitespace-normalized, envelope-stripped text so drift
  between sent and stored text can't split the bubble.

Tests cover title stripping, the nested/flat/image result shapes, and echo
normalization. The duplicate did not reproduce on 2026.7.2-beta.7 end to
end, so the rendezvous fix is defensive against the likely race/text-drift
causes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
…down

Advertise caps: ['tool-events'] on both the persistent and one-shot gateway
clients — the gateway only registers a connection as a live tool-event
recipient when it does, so without it session.tool frames never arrived (the
SDK defaults caps to []). Extracted a pure gatewayClientBaseOptions builder
so the contract is unit-tested without a socket; pins protocol 4.

Reclaim idle live sessions: session subscriptions are now refcounted on the
gateway (subscribe on 0→1, unsubscribe on 1→0), and a record is torn down on
a 10-minute idle TTL, a soft 32-session cap, or server shutdown — never
while a run is in flight. Teardown is idempotent, releases the subscription,
and preserves sibling sessions' state; killAllOpenClawSessions is wired to
the harness shutdown hook (invoked via web.ts's SIGTERM path). Cold reopen
re-seeds from sessions.get, so eviction only costs a re-seed.

Also: coalesce the two run-end reconcile signals onto one in-flight fetch;
cache the last assistant model instead of rescanning per frame; default
streamEnabled to false (opt-in on send); fix stale comments (toolResult does
stream; OpenClaw supports model/effort/stream, ignores only fastMode).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Two streaming-path gaps surfaced testing on a newer OpenClaw (schema v6):

- Thinking never rendered. blockToPart and chatPreviewBlocks only matched
  `{ type: 'thinking', thinking }`; newer lines emit `reasoning` and/or carry
  the text in `text`, so the reasoning part was dropped and the Thought row
  vanished. Both paths now read thinking/reasoning/text across the type and
  field variants.

- Streaming dumped the reply at run end. moi previewed only from `chat`
  delta frames; a gateway that streams via `agent` stream=assistant frames
  produced no preview. Added an agent-stream preview source, arbitrated per
  run (first of chat/agent to emit a delta owns it) so a gateway emitting
  both never double-broadcasts.

Tests cover the reasoning field variants and the per-run source arbitration.
Could not verify end to end: the temporary API keys used for live testing
were revoked, so these are reasoned from the captured wire shapes and are
defensive/additive (never removing the existing chat-frame path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
On the codex-app-server backend (OpenAI models on newer OpenClaw) tool
activity streams as `agent`/tool frames during the run, but the durable
owner rows arrive only in the run-end batch — after the assistant text
has streamed. Tools therefore appeared all at once below the reply
instead of in order.

Render a synthetic live tool turn off the frame (keyed
`livetool:<toolCallId>`) so the card shows during the run; the durable
owner then merges onto it by re-iding to the same turn. Keep the
liveTools mapping after the merge so the run-end reconcile's re-emit
collapses onto the one card instead of dropping a duplicate.

Add live-tools.test.ts covering the frame→card path, result fold-in, the
durable-owner merge with a reconcile re-emit, and the two-tool run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Record why tool stdout on the codex backend (OpenAI models) only appears
when the run ends: the live agent/tool result frame carries just
{status, exitCode, durationMs}, and the stdout lands only in the run-end
durable toolResult batch. Verified from a wire capture and the
@openclaw/codex plugin source. Contrasts with session.tool (Anthropic),
which streams output live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Non-codex providers (ollama/deepseek, Anthropic) were getting duplicate
tool cards: a synthetic live card stuck spinning next to the real durable
card. The live-card synthesis was built for codex-app-server, which
batches durable rows at run end; on native-loop providers the durable
rows arrive during the run and the start-frame toolCallId need not match
the result/durable id, so the synthetic card never merged — and broadcast
turns can't be retracted.

Gate synthesis on a positive codex signal: set rec.codexBackend when a
codex* stream frame is seen (only the codex backend emits those, and they
precede its tool frames). liveTools is populated only then, which gates
both the synthesis and the durable-row merge. Native providers fall back
to the pre-existing durable-row rendering — one card, no orphan.

Add native-backend regression tests (no synthesis, mismatched start/durable
ids still yield one card) and document the gating in NOTES.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D34U9tcLdnKH3eWYY3LREj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants