Keep WhatsApp QR login state synced across gateway, macOS, and UI wait flows.
- Preserve the latest QR data URL/version while login polling rotates codes.
- Keep the wait-result protocol bounded to current QR metadata.
- Stabilize QR rendering and media fixture coverage after rebasing on main.
Validation:
- pnpm test extensions/whatsapp/src/login-qr.test.ts extensions/whatsapp/src/media.test.ts extensions/whatsapp/src/agent-tools-login.test.ts src/gateway/protocol/channels.schema.test.ts src/gateway/server-methods/web.start.test.ts ui/src/ui/controllers/channels.test.ts
- pnpm test:extension whatsapp
- cd apps/macos && swift test --filter ChannelsSettingsSmokeTests
- GitHub PR checks: 62 success, 5 skipped
* WhatsApp: harden auth persistence and backup recovery
* WhatsApp: model unstable auth state across runtime and setup
* WhatsApp: recover login and monitor startup from unstable auth
* Channels: surface auth stabilizing in status and health
* Gateway protocol: add channels.start surface
* Gateway: reconcile local channel runtime after CLI login
* Channels UI: reflect recovered login start state
* Changelog: note WhatsApp auth stabilization
* Gateway: fix lint in call test
* fix: allow unknown properties in WakeParams schema (#68347)
WakeParamsSchema used additionalProperties: false, rejecting unknown
properties like 'paperclip' from external tools. Changed to
additionalProperties: true for forward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: trim wake params schema comments
* fix: allow unknown properties in WakeParams schema (#68355) (thanks @kagura-agent)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(agents): filter bundled tools through final policy
* changelog: filter bundled tools through final policy (#68195)
* forward agentId into compaction tool-policy filter
Pass effectiveSkillAgentId to applyFinalEffectiveToolPolicy in the
compaction path so per-agent tool policies apply to bundled tools
during compaction the same way they do during normal runs.
* scope final tool-policy filter to bundled tools only
Running the full tool-policy pipeline on the merged core + bundled tool list
re-filters core tools whose plugin WeakMap metadata no longer survives the
normalize/hook wrappers applied by createOpenClawCodingTools(). Narrow the
helper to only the newly-appended bundled MCP/LSP tools so plugin-provided
core tools keep matching group:plugins and plugin-id allowlist entries.
* harden authorization signals on final tool policy
- message.action gateway handler now server-derives senderIsOwner from the
authenticated gateway client scopes (ADMIN_SCOPE on client.connect.scopes)
and ignores any senderIsOwner value on the wire, so a non-admin scoped
caller cannot spoof owner status to unlock owner-only channel actions or
owner-only tool policy. Schema keeps the field optional for wire compat
but documents that it is ignored.
- applyFinalEffectiveToolPolicy now cross-checks caller-provided groupId
against the session-derived group context resolved from sessionKey (and
spawnedBy). When they disagree, the caller groupId plus its adjacent
groupChannel/groupSpace are dropped and a warn is emitted, so a caller
that fabricates a different group id cannot reach a more permissive
group-scoped tool policy during the final bundled-tool filter. Added a
JSDoc trust invariant on the helper input describing the required
server-verified identity contract.
* align compact agentId resolution with core tools
Drop the explicit agentId on applyFinalEffectiveToolPolicy during
compaction. The core tool set produced just above via
createOpenClawCodingTools(...) also omits agentId, so resolveEffectiveToolPolicy
falls back to resolveAgentIdFromSessionKey(sessionKey) in both places.
Passing effectiveSkillAgentId only to the final filter made the two
policy lookups diverge on legacy/non-agent session keys where the
sessionKey path resolves to main but effectiveSkillAgentId follows the
configured default-agent path, which could deny or allow bundled tools
under a different per-agent policy than the already-created core tools.
* tighten trusted propagation for owner and group signals
- message.action gateway handler: full-operator callers (shared-secret
bearer or operator.admin scope) now propagate the request-provided
senderIsOwner through to channel action handlers instead of having it
hard-coded off. Previously the hardened path force-derived ownership
from ADMIN_SCOPE alone, which broke owner-gated actions when the
trusted runtime forwards them via the least-privilege gateway path
(callGatewayLeastPrivilege requests only the method scope, so even
legitimate owner senders were downgraded to senderIsOwner=false).
Narrowly-scoped callers (e.g. operator.write-only) still have the wire
value forced to false so a non-admin caller cannot assert ownership.
- applyFinalEffectiveToolPolicy: fail-closed when the session key and
spawnedBy encode no group context. Previously the helper only dropped
a caller-provided groupId that conflicted with a non-empty set of
session-derived group ids, which left an accept-caller fallback open
when the session had no group context at all (direct/cron/subagent
session keys). An attacker who could run without a group-bound session
could then supply an arbitrary groupId and reach a more permissive
group-scoped tool policy. Now: no session-derived group context plus
any caller-provided groupId drops the caller value and warns.
* suppress unavailable-core-tool warnings in bundled-only pass
applyToolPolicyPipeline infers its coreToolNames reference set from the
tools array it is filtering. The bundled-only second pass only sees the
MCP/LSP subset, so normal core allowlist entries (for example
tools.allow: ['read', 'exec']) would look "unknown" during this pass
and emit misleading warnings even when the config is valid for the full
effective tool set — polluting logs and potentially evicting real
diagnostics from the shared warning cache. Set
suppressUnavailableCoreToolWarning on every step of this pass so known
core-tool allowlist entries stay silent; genuinely unknown entries
still surface through the otherEntries warning path.
* feat(gateway): add skills.search and skills.detail RPC methods
Expose ClawHub search and detail capabilities through the Gateway protocol,
enabling desktop/web clients to browse and inspect skills from the registry.
New RPCs:
- skills.search: search ClawHub skills by query with optional limit
- skills.detail: fetch full detail for a single skill by slug
Both methods delegate to existing agent-layer functions
(searchSkillsFromClawHub, fetchSkillDetailFromClawHub) which wrap
the ClawHub HTTP client. No new external dependencies.
Signed-off-by: samzong <samzong.lu@gmail.com>
* feat(skills): add ClawHub skill search and detail in Control UI
Add skills.search and skills.detail Gateway RPC methods with typed
protocol schemas, AJV validators, and handler implementations. Wire
the new RPCs into the Control UI Skills panel with a debounced search
input, results list, detail dialog, and one-click install from ClawHub.
Gateway:
- SkillsSearchParams/ResultSchema and SkillsDetailParams/ResultSchema
- Handler calls searchClawHubSkills and fetchClawHubSkillDetail directly
- Remove zero-logic fetchSkillDetailFromClawHub wrapper
- 9 handler tests including boundary validation
Control UI:
- searchClawHub, loadClawHubDetail, installFromClawHub controllers
- 300ms debounced search input to avoid 429 rate limits
- Dedicated install busy state (clawhubInstallSlug) with success/error feedback
- Install buttons disabled during install with progress text
- Detail dialog with owner, version, changelog, platform metadata
Part of #43301
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(skills): guard search and detail responses against stale writes
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(skills): reset loading flags on query clear and detail close
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(gateway): register skills.search/detail in read scope and method list
Add skills.search and skills.detail to the operator READ scope group
and the server methods list. Without this, unclassified methods default
to operator.admin, blocking read-only operator sessions.
Also guard the detail loading reset in the finally block by the active
slug to prevent a transient flash when rapidly switching skills.
Signed-off-by: samzong <samzong.lu@gmail.com>
* fix(skills): guard search loading reset by active query
Signed-off-by: samzong <samzong.lu@gmail.com>
* test: cover ClawHub skills UI flow
* fix: clear stale ClawHub search results
---------
Signed-off-by: samzong <samzong.lu@gmail.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
* feat(gateway): make chat history max chars configurable
* fix(gateway): address review feedback
* docs(changelog): note configurable chat history limits