Fully support the OpenClaw gateway protocol across the current and previous lines - #77
Open
molefrog wants to merge 20 commits into
Open
Fully support the OpenClaw gateway protocol across the current and previous lines#77molefrog wants to merge 20 commits into
molefrog wants to merge 20 commits into
Conversation
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
There was a problem hiding this comment.
💡 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".
…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
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
molefrog
commented
Aug 5, 2026
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' |
Owner
Author
There was a problem hiding this comment.
where will this be displayed?
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-scopedmeta.apiMessageId, trailing-delta clears onfinal/error/aborted) and the echo rendezvous by<runId>:useridempotency key, whose field placement differs between 2026.6.x and 2026.7.x (compat.ts).server/harness/openclaw/compat.tsrule: old gateways validate params withadditionalProperties: 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:clientclean; live end-to-end chats through 2026.7.1 and 2026.6.33 gateways from the real UI, plus aprotocol mismatchclassification probe against 2026.4.22. Protocol details and drift tables:server/harness/openclaw/NOTES.md.