* feat(anthropic): import Claude Desktop custom groups
* fix(anthropic): resolve Claude Desktop groups from LevelDB entries
Group discovery regexed whole decompressed blocks, so matches were not
attributable to a Local Storage value and byte order decided the winner.
On a real 5.4MB store that mislabelled 59 of 159 sessions, surfacing a
mojibake label spliced out of Snappy copy-record bytes.
Parse SSTable entries properly instead: prefix-delta keys bounded by the
restart array, newest internal sequence per user key (honoring deletions),
and record scanning confined to a single value. Values are normalized so
Chromium's UTF-16 form scans like Latin-1, and unflushed WAL writes keep
precedence over SSTables.
Prod LOC grows ~77; it buys structural correctness in place of ordering
luck, and folds the old index-only walk into one shared entry decoder.
Verified against the live store: 159 assignments, 0 mislabelled versus an
independent entry-level ground truth, 0 control-character labels.
* fix(ui): sort custom session groups ahead of project groups
Custom groups were pushed into the section list as encountered, so their
position depended on roster order rather than the documented behavior;
the existing test only passed because its fixture happened to be ordered
that way. Collect custom and project groups separately and concatenate,
and assert the guarantee with a reversed-input case.
* fix(anthropic): widen LevelDB fixture key type for test typecheck
The prefix-delta helper assigned a Buffer into a Buffer-typed accumulator
whose generic argument differed, which tsgo rejects in the test lane.
* chore(anthropic): drop release-owned changelog edit from the PR
CHANGELOG.md is generated at release time, so a normal PR must not carry
an entry. The release-note context lives in the feature commit message and
the PR body instead.
* fix(ui): make stale-chunk Reload survive the gateway restart
The 'A new version is available' banner appears precisely because the
gateway was just updated — so when the user clicks Reload, that gateway
is usually still restarting. The handler probed once and, on failure,
silently fell back to revalidating the same replaced chunk, so the
button looked dead and a manual hard reload was the only way out.
Poll the document probe until the gateway answers (bounded, so an
unreachable gateway still degrades to the recoverable panel error
rather than a fatal navigation), and show a disabled 'Reloading…' state
while waiting.
* fix(ui): enforce the reload wait deadline locally
The bound was only checked after each probe settled, so it relied on the
probe timing itself out. The default probe does self-abort, but a
caller-supplied one need not, and a probe that never settles would leave
the Reload button disabled forever. Race each probe against the
remaining deadline instead.
* fix(ui): satisfy lint and deadcode gates for the reload retry
CI caught two things local oxlint does not: the wait default returned
the timer id from a Promise executor (eslint no-promise-executor-return),
and retryStaleChunkReload became an unused export once the lazy-route
button moved to the polling variant.
Wrap the executor body, and collapse the two near-identical retry paths
into one: the polling helper with timeoutMs: 0 is exactly the old
single-shot behavior, which the stylesheet-recovery banner keeps.
* fix(ui): keep the reload wait bounded across interval waits
The deadline was checked before the interval wait, so a wait that
carried past it left the next iteration taking the unbounded
single-shot branch — a probe that never settles would then strand the
disabled Reload button past its own bound. Only the first attempt may
probe from outside the window.
* build(protocol): make @openclaw/gateway-protocol publishable to npm
* chore(mxc): align package version with release train
* fix(release): support gateway-only core packages
* fix(clickclack): accept omitted nullable managed-channel fields and auto-open the discussion panel
* fix(ui): open the discussion when write access arrives after it resolved
* fix: support Back within channel setup
* docs: note channel setup Back navigation
* fix: keep navigation outcome type private
* style: format navigation outcome type
* chore: leave changelog to release prep
* refactor(ui): extract the steered-message lifecycle into a typed state machine
The steer flow was an implicit state machine spread across five chat modules,
with chip states encoded in loosely coupled optional fields and duplicated
guard predicates. This extracts it behind structural types:
- steered-chip.ts: leaf chip model — SteeredChip union (in-flight requires
sendState "steering"; acknowledged requires pendingRunId and forbids
sendState), constructors, and narrowing guards; impossible states are
unrepresentable and every call site uses the guards
- steer-lifecycle.ts: owns the steer send flow, single terminal retirement
pipeline (remember -> materialize acked chips -> clear), and history-proven
retirement; absorbs queued-user-turn.ts
- terminal handling in chat-gateway/chat-state and the stored-outbox history
verification in chat-send consolidate onto the shared helpers; chat-send.ts
shrinks by ~300 lines; net prod LOC is flat
- behavior fixes riding the pipeline: acknowledged chips retire once
authoritative history contains their user turn (no more brief chip/history
double display) and lingering chips keyed to a filtered run id now retire on
the next history load
- isTerminalFailureChatSendAck moves to chat-send-contract.ts so ack
narrowing stays type-safe at every formatter call site
* fix(ui): satisfy type, lint, and dead-export gates for the steer-lifecycle split
- restore the snapshotChatAttachments helper (oxc no-map-spread fires on the
inlined form)
- select the stored-outbox drain head with an explicit loop; the find-predicate
form made TS narrow away the failed state and flagged the guard (TS2367)
- drop the unused chip re-exports from steer-lifecycle and the consumer-less
isSteeredChip guard; chip types stay module-local, guard consumers import
the steered-chip leaf directly
* style(ui): oxfmt line joins in chat-send and chat-state