* feat(gateway): make suspend/resume operator-usable end to end
A prepared Gateway now accepts authenticated WebSocket connects while
keeping every method except gateway.suspend.* fenced, so a fresh CLI or
controller process can resume a suspension instead of dead-ending on a
rejected upgrade until the two-minute lease expires. Restart drain,
worker ingress, and desktop-observe streams stay fully closed.
The gateway client surfaces non-101 upgrade responses (bounded body
read) as typed retryable errors instead of an opaque 1006 close, and
new openclaw gateway suspend / resume commands drive the whole
handshake, including bounded --wait polling with blocker output.
Live-verified on an isolated dev gateway: prepare, SIGSTOP/SIGCONT
freeze, resume, over-TTL expiry self-heal, conflict and mismatch paths.
* refactor(gateway-client): move wire-client contract types to protocol-client-contract
The connectError addition pushed protocol-client.ts over the 700-line
max-lines gate; split the adapter-facing contract types into their own
module instead of suppressing.
* refactor(gateway-client): keep contract-internal option types unexported
Knip deadcode gates reject exported types with no importer; the connect
and close decision shapes are only referenced inside the contract module.
* chore(plugin-sdk): refresh gateway-runtime API baseline after rebase
* fix(gateway-client): preserve hello type after rebase
* test(gateway): support websocket upgrade rejection events
* test(gateway): expect connection errors in close info
* fix(gateway): keep prepared-suspension connects control-only
Address ClawSweeper review: node and worker connects stay refused while
suspension is prepared (only operator control connects pass), and the
CLI never issues another suspend prepare after its --wait deadline.
* fix(agents): unify tiered model resolution for chat and compaction
Manual /compact failed with Unknown_model when the agent model was
configured as an undated ref (e.g. anthropic/claude-haiku-4-5): the chat
run resolved models through a two-tier path (discovery-free lookup, then
prepared stores with bundled static-catalog fallback) while both
compaction entry points made a single bare resolveModelAsync call and
dead-ended before the static catalog could resolve the undated id.
resolveTieredModel is now the canonical resolution owner used by the
chat run, direct compaction, and queued compaction; the duplicated
chat-only tier logic and both bare compaction lookups are removed.
Regression test proves chat and manual compaction resolve the same
undated configured model through the shared owner.
* fix(agents): satisfy lint and test-types on tiered resolution call sites
Drop the redundant ?? {} spread fallbacks (spreading undefined is a
no-op) and give the test registry mock its real (provider, modelId)
signature for check:test-types.
Supervisor-capture tests asserted real telegram/slack channel ids, so
their pass/fail depended on which channel plugins the selected vitest
lane loads (stubs when focused, real normalizers under unit-fast);
switch to a neutral synthetic channel. Two embedded-runner tests
carried a shared literal /tmp/openclaw.sqlite storePath — one actually
opened it, colliding with stale artifacts across parallel runs; both
now use realpath'd auto-cleaned per-test temp dirs.
Evidence in PR #122164 body; pre-fix aggregates failed 12/1202 (cli)
and 4/4750 (embedded), post-fix green in default and shuffled order.
Require an account-bound confirmation before deleting WhatsApp credentials, and revalidate Gateway ownership plus account state after the modal resolves.
Clear stale uncertainty debt when the same final reaches an authoritative terminal outcome, preventing false notices on the next inbound turn.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(delivery): preserve suppressed send outcomes
Preserve authoritative durable-send suppression outcomes so task, subagent, and exec followups no longer report false delivery or duplicate ambiguous adapter dispatches.
* fix(delivery): keep suppression reason internal
Derive the outbound projection from the existing durable batch result union so the repair does not change the public plugin SDK contract.
* fix(delivery): avoid retrying ambiguous task updates
Advance the state-change event cursor after an adapter returns no identity so a possibly visible progress notification is not sent twice; intentional suppression remains retryable.
The externalization left the docs inventory seed and the channel page
advertising the retired @openclaw/qqbot npm package; the catalog already
resolves qqbot to @tencent-connect/openclaw-qqbot. Regenerates the
plugin reference and inventory pages from the corrected seed.
* refactor: use canonical re-export names
* fix(test): classify suite support as test source
* fix(agents): retarget gateway stub session-entry import
* test(gateway): retarget session-utils mock keys after alias removal
Use the selected harness for isolated title generation, including native Codex auth.
Retry failed dashboard titles from the first user message without overriding manual names.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Detected Codex/Claude Code CLI candidates now say whether they ride a
flat-rate subscription login (ChatGPT, Claude) or a metered API key, so
picking one is an informed billing decision for an always-on assistant.
Codex classification parses `codex login status` output per
codex-rs/cli/src/login.rs; Claude uses prompt-free credential-file and
env/apiKeyHelper signals only (never the keychain). Unknown states keep
the plain 'logged in'. Display-only: ranking and defaults unchanged.
* fix(ui): resolve project icons from deterministic paths
Prepare project icon bytes during chat startup and serve only the process-stable snapshot. Keep all filesystem work asynchronous and lift the breadcrumb trail onto the topbar's optical axis.
* fix(ui): keep project icon paths web-focused
Drop the IDE-specific icon convention so every candidate remains an explicit web project path.
* fix(ui): preserve project icon compatibility
Restore existing web icon paths and refresh bounded session snapshots when they are served. Keep the IDE-specific path excluded per the final product decision.
CI run 31515400879 (checks-node-compact-small-1) failed the run-main
import-failure test as an opaque "expected null to be 1": the deadlock
guard had fired with the child still alive, but the guard rejection
carried the captured child output only as hidden error properties and
the tests asserted on failure.code blindly, discarding the startup
trace lines that pinpoint where the child stopped.
runCliProcess now takes expectedExitCode and throws self-describing
errors for all failure identities (guard fired, signal death, wrong
exit code) with bounded tails of the child stderr/stdout embedded in
the message. The CliProcessFailure try/catch pattern is deleted
file-wide; formatter regression tests protect the diagnostic contract.
Exact-reconciliation sends skipped pre-send promotion, so dispatch cleared the producer claim without publishing the platform send attempt identity. The post-send ack then failed its ownership guard with "Delivery platform claim was lost", settling provably delivered sends as unknown; since #121833 that ambiguity surfaces as a spurious "couldn't confirm my previous reply" recovery notice. Dispatch now atomically promotes the producer claim into the active attempt and publishes the attempt identity, so successful sends settle and lease renewals keep working. Adds a boundary regression pinning unknown_after_send evidence across a later unclaimed batch dispatch.
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
sessions.create reset-in-place persists the retired worktree's removedAt
before the RPC resolves, but a successor worktree create that rolled the
same random crustacean slug (1/420) hit nameIsUnavailable's owner-match
bypass and silently restored the retired registry record, clearing
removedAt and reviving the old branch (flaked
server.sessions.create.test.ts reset-in-place).
Generated names (title slugs and random slugs) now treat the caller's
removed records as collisions and take a numeric suffix; explicit
worktree names keep their documented reuse/restore semantics.