Commit Graph

70795 Commits

Author SHA1 Message Date
Peter Steinberger
b07985ef45 feat(ui): show waiting-for-approval run status in chat (#111445)
* feat(ui): show waiting approval run status

* fix(ui): hydrate waiting approval status

* fix(ui): correlate approval status by run

* fix(ui): harden approval status reconciliation
2026-07-19 19:44:25 -07:00
Yiğit ERDOĞAN
301657f2f3 fix(agents): keep exec ANSI sanitizer state across stream chunks (#111364)
The local exec runtime sanitized each stdout/stderr chunk with the
stateless `sanitizeBinaryOutput`, so an escape sequence straddling a read
boundary was escaped into visible text instead of being consumed. The
remote bash path already uses a per-stream parser via
`createStreamingBinaryOutputSanitizer` (#103706); this applies the same
treatment to the local path, with separate parsers for stdout and stderr
so neither stream can consume the other's pending sequence.
2026-07-19 19:34:07 -07:00
mushuiyu886
a20e08a56e fix(skills): keep ClawHub search results on one line (#111441) 2026-07-19 19:18:54 -07:00
mushuiyu886
c5bc6c3d2d fix(duckduckgo): preserve words around highlighted matches (#111460) 2026-07-19 19:09:09 -07:00
Wynne668
27f05c8993 fix(inworld): reject malformed base64 TTS audio (#111197) 2026-07-19 20:48:46 -04:00
Wynne668
0074270056 fix(xai): reject malformed streamed TTS base64 (#111201) 2026-07-19 20:40:53 -04:00
Wynne668
8a858c9c65 fix(openai): reject malformed Codex image base64 (#111235)
* fix(openai): reject malformed Codex image base64

* fix(openai): align Codex image base64 trimming

* fix(openai): match Codex base64 decoding
2026-07-19 20:40:18 -04:00
Peter Steinberger
3c0f55b58f fix(gateway): prevent cross-client system-agent session takeover (#111565)
* fix(gateway): bind system-agent sessions to callers

* test(gateway): type system-agent response helper
2026-07-19 16:05:18 -07:00
Peter Steinberger
c5f9addb52 fix(ui): keep custodian transcript aligned after inference errors (#111562)
* fix(ui): reconcile invalidated custodian sessions

* test(ui): preserve custodian page harness type
2026-07-19 16:00:08 -07:00
thomas.szbay
a0c3067b54 fix(qa-lab): bound evidence checkout ref git probe with timeout (#111448)
Co-authored-by: thomas.szbay <thomas.szbay@example.com>
2026-07-19 15:48:53 -07:00
Peter Steinberger
e0b1a39d2a improve(i18n): generate native locales after merge (#111557)
* ci(i18n): move native locale generation post-merge

* fix(i18n): allow generated Android companions
2026-07-19 15:42:41 -07:00
Peter Steinberger
3247a56d15 fix(ui): prevent cross-file mock leakage in Control UI tests (#111554)
* test(ui): stop shared mock registry leakage

* ci: add three-worker UI leakage canary
2026-07-19 15:38:50 -07:00
Peter Steinberger
7116ad6e28 fix(agents): preserve fresh workspace hatch (#111553) 2026-07-19 15:35:54 -07:00
Peter Steinberger
473962b7de fix(onboarding): scope recommendations by workspace (#111560) 2026-07-19 15:34:56 -07:00
Peter Steinberger
5fcf7e75de test: clear plugin runtimes between shared files (#111556) 2026-07-19 15:28:46 -07:00
Peter Steinberger
a1e5b6ef0e fix(doctor): honor active migration targets (#111555) 2026-07-19 15:27:03 -07:00
Peter Steinberger
6c25f5ae33 fix(ui): keep steered composer messages visible until the transcript owns them (#111540)
* fix(ui): keep steered composer messages visible until the transcript owns them

Typing while the agent was busy showed a brief steering state and then the
message vanished until the next full history reload. The steer path removed
the queued row at ack time and only restored the steered chip when the tab
had adopted the active run id, which never happens for runs started by
automations, other clients, or tabs opened mid-run.

- steer sends reuse the durable row's sendRunId as the wire idempotencyKey so
  delivered-turn and history-proof reconciliation can correlate steered rows
- the steered chip is restored after every accepted ack, keyed to the active
  run when the tab still tracks it, else to the steer's own gateway lifecycle
- terminal events materialize acknowledged steered chips into the transcript
  before clearing them, and stale history reloads keep idempotency-marked
  local turns until authoritative history catches up
- in-flight (unacknowledged) steers are never materialized, so a rejected
  send cannot leave a phantom user turn behind

Fix authored with Codex; reviewed and hardened via structured review.

* fix(ui): use bracket access for __openclaw test marker (no-underscore-dangle)

* fix(ui): resolve steered attachment payloads through the store before materializing

Queue rows carry attachment metadata only; composer upload bytes live in the
payload store. Resolving through getChatAttachmentDataUrl keeps attachment-only
steers visible when their chip retires (Codex connector review finding).

* fix(ui): only user-role entries satisfy queued-turn presence checks

An assistant entry can carry the same run's idempotency key; matching it let
the chip re-add and terminal materialization skip the user's copy
(ClawSweeper rank-up move).
2026-07-19 15:17:08 -07:00
Peter Steinberger
bcbbf28093 docs(plugin-sdk): migrate retained SDK contracts (#111535)
* refactor(plugin-sdk): finish retained public demotions

* test(plugin-sdk): tighten surface budgets

* fix(plugin-sdk): retain static tool metadata contract

* fix(plugin-sdk): keep retained exports pending

* fix(plugin-sdk): satisfy registry lint

* docs(plugin-sdk): document retained consumer seams
2026-07-19 15:00:45 -07:00
Peter Steinberger
c6f7ddff1a fix(ui): show attributed sender identity properly in chat (#111537)
* fix(ui): show attributed sender identity properly in chat

Three defects around multi-user sender attribution:

- Sender labels rendered as 'name (<profile uuid>)': resolveSenderLabel
  appended the id part even when it is an opaque UUID. Suppress UUID
  suffixes server-side and strip already-baked ones in the UI normalizer
  so historical transcripts read clean too.
- Attributed messages showed initials instead of the author's avatar:
  chat sender metadata carries only the profile id, so resolveAvatar now
  derives the canonical /api/users/<id>/avatar route (upload -> gateway
  Gravatar proxy -> initials) from UUID-shaped sender ids.
- Author identity was hover-only: identity-resolving gateways keep the
  always-visible avatar gutter even in 1:1-shaped sessions, and the
  gutter renders the message author's avatar (with identity-colored
  initials fallback) instead of the local viewer's.

* fix(ui): preserve legacy author identity and 404-safe sender avatars

Review findings: stripping the baked UUID suffix discarded the only
author key on legacy rows (the gutter then showed the local viewer's
face under another person's label) — the UUID now survives as
structured non-display sender identity. And the derived avatar route
can 404 (no upload, no Gravatar), so the gutter image swaps to
identity-colored initials on error instead of a broken image.

* fix(ui): attribute bare-UUID legacy sender labels to their profile

A nameless legacy sender labels as the bare profile UUID; recognize that
whole-label shape as the sender id (keeping it as last-resort display)
so the row resolves the author's avatar instead of misattributing to the
local viewer.
2026-07-19 14:59:33 -07:00
Marcus Castro
b887cd01d8 fix(whatsapp): use canonical media primitives (#107017)
* feat(media): forward outbound image optimization

* feat(media): support bounded Opus transcoding

* refactor(whatsapp): reuse canonical outbound media helpers

* refactor(whatsapp): reuse canonical base64 validation

* chore(plugin-sdk): refresh API baseline
2026-07-19 18:54:14 -03:00
Peter Steinberger
0fff74c800 feat(ios): replace the tab bar with a black overlay sidebar (web parity + dashboard) (#111339)
* feat(ios): unify navigation on a black overlay sidebar and drop the phone tab bar

* test(ios): rework navigation tests and snapshot UITests for the sidebar shell

* chore(i18n): sync derived native catalogs for sidebar strings

* feat(ios): reveal the sidebar beneath a push-away rounded content card

* feat(ios): web-parity sidebar — agent switcher, pinned pages, sessions vocabulary, uniform content card

* chore(i18n): translate sidebar and session strings across native catalogs

* fix(ios): sidebar footer shows connection dot only when degraded, like the web

* feat(ios): sidebar toggle top-left with edge-swipe open; card surface owns the drawer chrome

* fix(ios): single sidebar refresh owner, alias-aware Home badges, RM tap-close, user pin order, drop vestigial idiom shims

* feat(ios): align sidebar with the mobile prototype — agent roster, theme-following palette, web session subtitles, row badges

* chore(i18n): translate agent roster and Recent strings across native catalogs

* feat(ios): frosted-glass agent picker with uniform drawer insets

* fix(ios): address sidebar review findings — settings toggle edge, per-agent scoping, alias-aware selection, cron paging, refresh cadence

* fix(ios): clear landing CI blockers
2026-07-19 14:50:39 -07:00
Peter Steinberger
5420c5c409 feat(clickclack): session discussions — bound channels, side agent, lifecycle sync (#111503)
* feat(clickclack): add session discussions

* chore: remove release-owned changelog entry

* refactor(clickclack): split discussion service workflows

* fix(clickclack): satisfy plugin surface gates

* fix: narrow routing peer in inbound test and refresh docs map
2026-07-19 14:47:14 -07:00
Peter Steinberger
c45e3dd315 fix(gateway): avoid macOS startup freeze during worker reconciliation (#111533)
* fix(gateway): warm system CA before worker reconciliation

* fix(gateway): tolerate denied worker warmup
2026-07-19 14:39:25 -07:00
Peter Steinberger
6a408983d0 fix(onboard): make first-run hatch reliable (#111465)
* fix(onboard): harden first-run handoff

* style(ui): avoid onboarding comparator shadowing

* fix(agents): preserve mixed refresh failures
2026-07-19 14:38:58 -07:00
Peter Steinberger
77378cb0f3 fix(ui): keep stray file drops from leaving Control UI (#111531)
* fix(ui): guard unhandled file drops

* test(ui): exercise accepted file drop target

* test(ui): prove file drop guard in browser
2026-07-19 14:38:30 -07:00
Peter Steinberger
171a3852ba fix: install exact app recommendations and retry failures (#111518)
* fix(onboard): preserve recommendation install retries

* fix(onboard): preserve bootstrap recommendation retries

* fix(onboard): commit recommendation outcomes after config

* fix(onboard): guard recommendation state transitions

* fix(onboard): reconcile durable skill installs

* docs(clawhub): clarify search publisher contract
2026-07-19 14:10:50 -07:00
Peter Steinberger
13ed8b5aa1 perf(state): cap the per-agent SQLite handle cache with LRU eviction (#111411)
* perf(state): cap the per-agent SQLite handle cache with LRU eviction

Multi-tenant hosts open one WAL database per agent (~3 file descriptors
each); the process-local handle cache was unbounded, so large fleets
exhausted descriptors. Cache hits now refresh LRU recency, and cache-miss
opens evict the oldest non-transactional handle before constructing the
new one, capped at 64 open handles. Eviction closes the process-local
handle only; registry rows and durable data are untouched and evicted
databases reopen transparently on next access.

* fix(state): fleet-wide introspection reads stop opening writable agent databases

Live testing an 81-agent gateway surfaced three full-fleet sweeps that
opened every agent's SQLite database writable (schema ensure + registry
write transaction per open): the 60s health snapshot's per-agent session
summaries, usage-cost cache reads behind the gateway usage endpoints, and
the zalouser doctor detector that ran from every CLI startup. Each sweep
churned the new bounded handle cache and saturated the event loop.

Session listing gains a readonly non-registering variant (SDK exposes it
as an additive readOnly flag on listSessionEntries); health and usage
reads use it, with transient-lock tolerance owned by the health caller.
The zalouser detector gates on the channel/credentials actually existing,
collects once per pass, and detection reads go readonly while migration
apply stays writable. The usage refresh queue's flat 50ms busy retry
becomes exponential backoff capped at 5s, and refreshing summaries retain
their cache timestamp so the 30s TTL gates fleet rescans.

* perf(state): make per-agent handle eviction and reopen cheap

Eviction only works if reopen is cheap. Reopens after eviction previously
repeated full first-open work; ensureOpenClawAgentSchema takes BEGIN
IMMEDIATE and the registry upsert writes the shared state DB, so reopens
blocked in synchronous busy waits (observed 14-22s event-loop stalls with
reconcile workers holding write transactions). Owner/schema validation and
registration now run once per path per process with invalidation on
quarantine and disposal; the read-only integrity guard still runs on every
physical open. Eviction closes with a PASSIVE WAL checkpoint instead of
TRUNCATE, which waits on readers; orderly dispose keeps TRUNCATE so
sidecars are flushed before unlink.

* ci: regenerate plugin-SDK API baseline, drop prod test-seam exports, mock readonly accessor

The additive readOnly flag on the SDK listSessionEntries changed the
declaration surface, so the generated API baseline is regenerated via the
sanctioned script. Knip forbids production exports consumed only by tests:
the usage-cache testApi export is removed outright and the refresh-queue
seam moves behind an env-gated global exposed by a test-support module.
health.plugins.test's full session-accessor mock gains the newly imported
readonly listing.
2026-07-19 13:58:01 -07:00
Peter Steinberger
f6a3f22629 refactor(channels): consolidate message action read gate (#111529) 2026-07-19 13:57:39 -07:00
Peter Steinberger
1a574923cb fix(onboard): honor remote flags in interactive setup (#111517)
* fix(onboard): honor interactive remote flags

* fix(onboard): clear credentials for changed gateway

* fix(onboard): validate remote URL before probe

* test(onboard): mark synthetic remote tokens

* test(onboard): avoid literal credential fixtures

* refactor(onboard): name remote probe auth role

* refactor(onboard): keep remote probe seed canonical

* test(onboard): type remote URL validator mock
2026-07-19 13:25:46 -07:00
Sally O'Malley
cb0bbcb177 fix: update debugging skill file references (#111474) 2026-07-19 15:59:01 -04:00
Marvinthebored
22c336c0b4 fix(plugins): load active generation after upgrades (#111141)
* fix(plugins): load newest managed generation when a prior install lingers

An upgrade writes a plugin's new version into a distinct managed project
directory (an `__openclaw-generation__` dir) without removing the previous one.
The persisted install record keeps pointing at the older, still-present
directory, so `mergeRecoveredManagedNpmRecord` falls through to returning the
persisted record and the runtime imports the stale version even though the newer
generation is installed. `isUnavailableManagedNpmInstallRecord` only self-heals
when the persisted path is gone, so an upgrading install (both directories
present) is not covered — `plugins inspect <id> --runtime` reports the old
version and `install`/`update` appear to silently no-op.

- Recovery now selects the highest-version record per plugin id across the
  legacy flat dir and any generation dirs, instead of whichever project root
  sorts last (generation-dir suffixes are hashes, so on-disk order is unrelated
  to version).
- When the recovered managed install is a strictly newer version at a different
  path, repoint to it, reusing the metadata merge the unavailable-path branch
  uses. The repoint is gated to persisted records that live inside the managed
  npm root, so intentional custom/outside npm installs are left untouched.

This is a recovery-layer self-heal, so it also repairs installs that already
diverged from an earlier upgrade.

Refs #107228

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugins): honor active managed generations

Recover ambiguous managed installs by package recency only when no usable active ledger path exists, and let doctor retire non-authoritative generations safely.

Co-authored-by: Peter Lindsey <peter@lindsey.jp>

* test(plugins): use shared temp cleanup

* fix(plugins): compare active paths per platform

* fix(plugins): use managed install timestamps

* test(plugins): timestamp managed project roots

* test(plugins): exclude retired legacy recovery

* refactor(plugins): keep recovery candidate private

---------

Co-authored-by: Peter Lindsey <peter@lindsey.jp>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-19 12:50:35 -07:00
Peter Steinberger
50114c5163 fix(test): recognize CI env aliases (#111521) 2026-07-19 12:44:18 -07:00
Peter Steinberger
06480d767c feat(dashboard): stitch session dashboards end-to-end — live provider, show_widget pin, board commands (#111218)
* feat(dashboard): pin canvas widgets through board domain

* feat(control-ui): wire session dashboards end to end

* fix(control-ui): preserve direct reset confirmation

* fix(control-ui): localize dashboard default tab

* test(dashboard): satisfy changed typecheck gate

* fix(dashboard): satisfy changed lint gate

* fix(dashboard): harden pinned widget lifecycle

* fix(dashboard): use extracted mock normalizer

* test(dashboard): reconcile hardened board admission tests

* fix(dashboard): align grant and canvas protocol requests

* fix(dashboard): preserve refresh and pin invariants

* fix(boards): treat missing board tables as empty on read-only paths

* fix(boards): accept read-only handles in board table probe

* fix(control-ui): preserve virtual board widget types

* fix(dashboard): align view types with protocol

* fix(dashboard): keep resolved board view typed

* chore(canvas): drop test-only html read wrapper

* chore(release): defer dashboard note generation

* fix(protocol): align board widget put params type

* fix(control-ui): preserve queued reset approval

* fix(control-ui): gate dashboard pinning capability

* chore(test): isolate dashboard capability fixture

* fix(control-ui): refresh dashboard pin affordance
2026-07-19 12:36:54 -07:00
Peter Steinberger
431da14410 fix(plugins): load standalone files without manifests (#111515) 2026-07-19 12:21:11 -07:00
Peter Steinberger
9c12542946 fix(gateway): reload when included config changes (#111511)
* fix(gateway): watch included config files

* fix(config): keep rejected includes watchable

* fix(gateway): bound rejected include watches
2026-07-19 12:17:02 -07:00
Peter Steinberger
5ec3b2dab4 fix(install): isolate source postinstall state (#111514) 2026-07-19 12:14:21 -07:00
Peter Steinberger
1ddb6f1bbb fix(system-agent): preserve setup success on audit failure (#111504) 2026-07-19 12:06:59 -07:00
Peter Steinberger
31e52dc5c5 feat(gateway): allow explicit operator.admin in device auto-approval with critical audit finding (#111509) 2026-07-19 12:05:04 -07:00
Peter Steinberger
5a6a19c133 fix(qa): align runner inbound dispatch contract (#111507) 2026-07-19 11:51:24 -07:00
Peter Steinberger
163a6c6e16 fix(agents): declare visible-session constraints upfront in sessions_spawn (#111502) 2026-07-19 11:42:05 -07:00
Peter Steinberger
3e154b6761 fix(agents): let swarm collector fan-outs honor group caps (#111505)
* refactor(agents): single admission owner per child mode (#111401)

* refactor(agents): branch admission gather per mode — no cross-mode fallback pairing
2026-07-19 11:40:55 -07:00
Peter Steinberger
c69d6e148e feat(ui): who's-online roster menu on the footer facepile (#111501)
* feat(ui): who's-online roster menu on the footer facepile

Clicking the sidebar footer facepile now opens a scrollable roster of
everyone online — avatar, name, and email subtitle per person, with your
own entry pinned first and marked (you). Uses the stock menu-surface +
wa-dropdown idiom; long rosters scroll inside the popup via the dropdown's
menu part. Session-row facepiles stay non-interactive so row clicks keep
navigating.

* fix(ui): roster menu interaction hardening from review

- close explicitly on wa-select (preventDefault also cancels the
  dropdown's own select-and-hide)
- clear the open state when a presence update unmounts the footer
  facepile, so the menu cannot remount stale when presence returns
- restore focus to the visible facepile button on keyboard dismissal
  (the dropdown's own trigger is a hidden throwaway anchor)

* fix(ui): return focus to the facepile trigger on roster row selection
2026-07-19 11:34:17 -07:00
Peter Steinberger
40d31f3481 feat(control-ui): show relative commit age and hover-only copy button on About (#111495)
The About page commit row now embeds the commit's committer timestamp
(commitAt, advisory like branch/dirty) and renders it as an always-relative
age with the exact localized date-time on hover. The copy button becomes a
compact borderless icon revealed on hover/focus only where hover exists;
touch keeps it visible.
2026-07-19 11:13:25 -07:00
Peter Steinberger
6d4082ef97 fix(ui): prevent unsafe custodian nudge answers (#111430)
* fix(ui): harden custodian nudge actions

* refactor(ui): split custodian page helpers

* fix(ui): classify custodian nudge delivery

* fix(ui): ignore stale custodian question replies

* fix(ui): restore rejected custodian questions

* fix(ui): preserve custodian wizard uncertainty

* refactor(ui): keep nudge text helper private

* test(ui): model uncertain custodian delivery

* fix(ui): scope delayed custodian dismissals

* fix(ui): retain in-flight custodian nudges

* fix(ui): block nudges during hosted wizard input

* chore(protocol): regenerate gateway models
2026-07-19 11:10:41 -07:00
Peter Steinberger
b9cf1add0f fix(qa): retry transient history rebuilds (#111475)
* fix(qa): retry transient history rebuilds

* fix(qa): preserve history retry timeout cause

* test(qa): avoid retry timeout shadowing
2026-07-19 11:10:15 -07:00
Peter Steinberger
c7e7ac2728 refactor: remove expired plugin compatibility surfaces (#111451)
* docs(secrets): remove retired web credential paths

* refactor(web): remove retired provider compatibility paths

* refactor(providers): delete retired compatibility routes

* refactor(secrets): remove retired credential aliases

* refactor(plugin-sdk): delete retired compatibility surfaces

* docs(plugin-sdk): remove retired migration guidance

* chore(plugin-sdk): refresh rebased surface budgets

* chore(plugin-sdk): refresh API removal baseline

* refactor(compat): migrate retired internal callers

* chore(plugin-sdk): refresh current-main baselines

* test(config): migrate plugin-owned secret assertions

* test(gateway): narrow plugin secret refs

* fix(plugin-sdk): preserve private boundary type identity

* chore(compat): remove stale sweep references

* chore(lint): lower max-lines budget

* refactor(secrets): remove unused web helper

* build(plugin-sdk): drop removed compat entries

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): use Linux API baseline hash

* fix(plugin-sdk): preserve private bundled build entries

* fix(plugin-sdk): package private runtime facades

* fix(plugins): preserve external credential contracts
2026-07-19 11:04:48 -07:00
Peter Steinberger
e6d7aee41d fix(ui): stop chat normalizers throwing on null/undefined message entries (#111494)
normalizeMessage / isToolResultMessage / isStandaloneToolMessageForDisplay
cast their unknown input to a record and then read `typeof m.role`. That
still evaluates `m.role`, so an undefined entry (e.g. groupMessages calling
normalizeMessage(item.message) with an absent message on a pending or
malformed transcript row) threw "Cannot read properties of undefined
(reading 'role')" inside the gateway event handler — a caught but recurring
error storm on the live team instance. Coerce non-object input to {} at the
source so every downstream `typeof m.<field>` check works and role degrades
to "unknown". The existing safeNormalizeMessage try/catch wrapper existed
precisely for this hazard; the guard now lives in normalizeMessage itself.
2026-07-19 10:54:55 -07:00
WhatsSkiLL
50ce5dedea fix(android): improve Wear thread follow behavior (#111459)
* improve(android): move Wear agent, session, and model pickers to Home

* fix(android): improve Wear thread follow behavior

* fix(android): stabilize Wear thread follow lifecycle

* fix(android): keep streamed Wear tail visible

Co-authored-by: JARVIS-Glasses <whatsskilll@gmail.com>

---------

Co-authored-by: Colin <colin@solvely.net>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-19 10:54:10 -07:00
Shakker
8e0c50380c fix: preserve message-only terminal output (#111493) 2026-07-19 18:51:06 +01:00
Peter Steinberger
83303f1ba8 feat(channels): batch 1 producers drop media placeholder bodies (#111447)
* feat(channels): batch 1 producers drop media placeholder bodies

Media-placeholder program batch 1: Google Chat, Zalo, LINE, and
Mattermost stop minting <media:kind> placeholder bodies. Media-only
messages carry an empty caption plus one structured fact per native
attachment (type-only when a download fails or is rejected, so payload
positions and kind signals stay aligned). The shared
formatMediaPlaceholderText SDK formatter renders text-only carriers
(Mattermost pending-room lines) from structured facts; per-channel
placeholder builders and the expected-count side channel are deleted.

* fix(mattermost): satisfy type, deadcode, and SDK manifest gates
2026-07-19 10:32:09 -07:00