* 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.
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.
* refactor(qqbot): remove bundled extension source
Mechanical deletion half of the #107295 squashed rebase; the catalog
repoint and host integration land in the follow-up commit.
Co-authored-by: sliverp <870080352@qq.com>
* refactor(qqbot): install plugin from Tencent package
Squashed rebase of #107295 onto current main. Repoints the official
external channel catalog at @tencent-connect/openclaw-qqbot@2.0.1 and
adapts onboarding, doctor migrations, secrets, build guards, and tests.
Documents the known limitation that the external package does not
support structured SecretRef clientSecret values; operators move those
to QQBOT_CLIENT_SECRET or clientSecretFile before upgrading.
Co-authored-by: sliverp <870080352@qq.com>
* fix(doctor): reuse shared hasOwnKey record helper
The rebased QQBot migration carried its own hasOwnKey export, colliding
with the one main now ships in legacy-config-record-shared.ts.
Co-authored-by: sliverp <870080352@qq.com>
* fix(plugins): carry catalog integrity through the update bridge
The externalized-bundled-plugin bridge dropped the official catalog's
expectedIntegrity pin, so bundled-user updates installed the external
npm package without integrity verification. The bridge now carries the
pin for the catalog's exact npm spec and both bridge install calls pass
it through; update-channel spec overrides intentionally skip the pin
since it only covers the pinned version.
Co-authored-by: sliverp <870080352@qq.com>
* chore(plugin-sdk): refresh per-entrypoint API baselines
The QQBot compat export and bundled-type removal shift 26 entrypoint
closure hashes in the new split baseline layout.
Co-authored-by: sliverp <870080352@qq.com>
* refactor(qqbot): drop helper reintroduced during rebase
Main's coercion consolidation added this file after the deletion
commit's base; its only consumers were the removed qqbot sources.
Co-authored-by: sliverp <870080352@qq.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Per-requester OAuth for HTTP MCP servers: in shared channels each trusted sender connects their own account. New config: mcp.servers.<name>.oauth.identity ("shared" default, byte-identical behavior) and gateway.publicOrigin (HTTPS except loopback) for the new GET /oauth/mcp/callback served on the Gateway HTTP server. Requester tokens are isolated per (channel, account, sender) in mcp_oauth_stores rows (no schema bump); callbacks correlate through a durable state-keyed one-time index with a 10-minute TTL; per-requester servers are fail-closed out of static/scheduled runtimes; unauthenticated senders get a sign-in link with a portable URL button; MCP config mutations clear requester credentials at the canonical owner. mcp status --json keeps the legacy authStatus fields additively. Proven by a two-user self-hosted-Executor E2E through the real gateway callback. Part of #122034.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Stage transitional plugin install-record writes without plugin-schema validation, then require fresh doctor and strict validation for every aggregate plugin change.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Make updater doctor phases explicit so only post-plugin finalization marks post-core convergence, and strip ambient convergence state from fresh child environments.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* improve(ui): align transient menu chrome with design-system geometry
Use one shared transient-menu recipe across Control UI panels and items.
Match the carapace reference: 6px item radius plus 4px inset yields a 10px panel radius, with restrained motion for fixed menus.
* test(ui): align gateway picker menu expectations
* test(cli): keep benign sqlite diagnostics out of ACP exit stderr assertions
* fix(ui): restore 44px menu touch targets on coarse pointers
Follow-up to #122115: startMcpOAuthAuthorization suppressed stored tokens
unconditionally, forcing a browser authorization on every mcp login even with a
valid session. Suppression is again gated on a recorded authorization-required
challenge; start returns a closed authorized|redirect result and the CLI
early-returns the already-logged-in outcome.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Prep for per-requester MCP OAuth (#122034). McpOAuthIdentity owns the store-key
grammar (operator keys byte-identical, zero migration); the authorize -> capture ->
exchange dance becomes a resumable coordinator session with the CLI loopback as a
capture adapter. Production net -7 lines; #112032 issuer binding preserved.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(cli): validate webhooks gmail --tailscale mode at parse time
--tailscale <mode> was stored unchecked and cast to the tailscale union
type, so a typo like --tailscale offf (truthy and !== "off") was treated
as enabled and only failed deep inside tailscale setup with a raw CLI
usage error. Reject modes other than funnel|serve|off in
parseGmailCommonOptions, mirroring the strict numberOption pattern, and
export the setup/run parsers for direct testing.
* fix(cli): reject blank Gmail tailscale modes
* fix(cli): preserve tailscale mode type
---------
Co-authored-by: Dallin Romney <dallinromney@gmail.com>
* refactor(agents): move announce family into subagents/announce/
* refactor(agents): move completion and main-session recovery families
* refactor(agents): move registry family into subagents/registry
* refactor(agents): update registry state type imports
* style(agents): format moved subagent imports
* test(agents): preserve isolated registry test routing
* fix(scripts): recognize relocated subagent announce seams
Replace divergent per-skill curation with one daily isolated collection review that can consolidate, rewrite, create, and drop writable skills atomically.
Autonomous auto mode remains automatic. Adds bounded review, transactional recovery, durable cadence, shared locking, and reuse-first /learn behavior.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>