Commit Graph

8274 Commits

Author SHA1 Message Date
Peter Steinberger e907f412ca fix(ios): restore approval settings navigation and remove duplicate shell state (#130651)
* fix(ios): unify root navigation and restore approval settings routes

* fix(ios): align shell cleanup inventories and test entrypoints
2026-08-26 21:36:16 -07:00
Peter Steinberger 71d4a8c3e3 fix(parallels): main install and update smoke cannot complete (#130658)
* fix(parallels): restore candidate install and update smoke coverage

* fix(scripts): allow prepublish artifact imports from stdin

* refactor(parallels): remove obsolete server re-exports

* fix(parallels): require configured Windows gateway RPC readiness
2026-08-26 21:24:22 -07:00
Peter Steinberger 63f7df85bb feat(channels): post a grounded introduction when the bot joins a group room (#130103)
* feat(channels): introduce bots when they join group rooms

* feat(channels): add Discord and Telegram join introductions

* fix(channels): isolate untrusted evidence and select allowed join targets

* refactor(channels): scope joinIntro to implementing channels

* fix(channels): keep a delivered join introduction settled when its durable commit fails

* feat(channels): read more room history and document join introductions in detail

* chore(config): regenerate bundled channel metadata after rebase
2026-08-26 21:02:26 -07:00
Vincent Koc b7d9be0209 fix(ci): stabilize compact node shards (#129702) 2026-08-27 11:52:20 +08:00
Mislav Ivanda 3a5cb3847c feat(sandbox): add Daytona cloud sandbox backend plugin (#121554)
* feat: @openclaw/daytona-sandbox inital version

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* feat: plugin config params extended

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* feat: implement ClawSweeper review notes

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* fix(daytona): honor abort signals and clean up remote staging on failure

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* fix(daytona): register launcher as knip entry and refresh manifest schema

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* fix(daytona): cancellable session transport and auto-stopped sandbox restart

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* fix(daytona): deny egress by default and arm launcher cleanup before startup

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>

* fix(daytona): stop cancelled startup before submission

* test(daytona): satisfy deferred race lint

* test(release): update plugin publisher inventory

* fix(daytona): close provisioning and PTY cleanup gaps

* test(daytona): type PTY launcher mock

* fix(acpx): avoid promise-returning line handler

* fix(daytona): await PTY signal cleanup

* fix(daytona): declare ClawHub install route

* fix(daytona): forward PTY stdin EOF

* fix(daytona): serialize PTY input

* docs(daytona): document sandbox backend config

---------

Signed-off-by: Mislav Ivanda <mislavivanda454@gmail.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-26 20:52:12 -07:00
Peter Steinberger 0924fd9a0c fix(codex): reclaim binding capacity after session deletion (#128366)
* fix(codex): reclaim binding ownership when sessions are deleted

Prepare harness deletion outside the SQLite writer and compare-delete exact companion ownership at the session removal edge, with compensation before commit and native subscription release afterward. Preserve live siblings and upstream native history. Repair proven orphan bindings through the existing Doctor planner under maintenance ownership.

* fix(codex): reuse prepared auth for resumed thread ownership

* docs(plugins): clarify shared migration and auth contracts

* test(codex): use the canonical config type facade

* fix(codex): preserve native threads during manual resume

Record pending attachment intent and validate the immutable native tool catalog
before applying configuration to the selected thread. Require uninterrupted
physical-client ownership through native unsubscribe, resume, and binding commit.
Keep the thread intact on unsupported policy, competing work, or failed proof.

Reuse canonical retained-owner cleanup and preserve unsafe-client retirement.
Cover the real command producer, cold and loaded resumes, competing leases,
lazy-store commit fencing, and the Gateway competing-owner path.

* test: isolate Codex auth and session completion fixtures
2026-08-26 20:33:53 -07:00
JC 8a0cf73053 fix(memory): isolate sqlite-vec KNN from the event loop (#128078)
* fix(memory): isolate sqlite-vec KNN in subprocess

* fix(memory): satisfy KNN subprocess CI guards

* test(release): include memory KNN child artifact

* test(memory): update KNN subprocess fixtures

* fix(memory): bound KNN child row payloads

* fix(memory): preserve published readers during cancellable KNN

Keep shadow reindex database, vector readiness, FTS and metadata state in
an owned async context. Public operations retain the published connection,
and escaped shadow continuations cannot write to it after rebuild closes.

Terminate the one-shot query child through its owned handle and retain
admission until close. Remove process-tree/PID probing and test-only hooks.
Cover publication overlap, callback reads, cancellation and recovery.

Co-authored-by: OpenClaw Assistant <assistant@openclaw.local>

* refactor(memory): separate database context ownership

Keep the published reader and shadow writer context in its own owner module. Repair plugin test imports and typed lint findings without changing the verified KNN behavior.

---------

Co-authored-by: OpenClaw Assistant <assistant@openclaw.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-26 20:29:02 -07:00
Peter Steinberger 7e362eba35 fix(crabbox): keep bootstrap runtimes accessible under private umasks (#130627)
Install public Node and Corepack artifacts in one strict root child shell
with umask 022, leaving the caller's private umask unchanged.

The real sanitized AWS bootstrap failed with exit 127 under umask 0077:
root-owned 0700 installation directories hid the installed Node binary
from the non-root caller. The same-host corrected run completed the
pinned Node 24.19.0/pnpm 11.22.0 install and verified readable public
artifacts, caller umask 0077, temporary HOME 0700, and private files 0600.

Existing bootstrap tests: 2 passed. Shell checks and both required
independent reviews passed. The real privileged-install reproduction is
retained instead of adding a brittle extracted-source installer harness.
2026-08-26 20:20:12 -07:00
Peter Steinberger 372850d59f fix(tooling): resume PR checkouts after completed deletions (#130630) 2026-08-26 20:17:58 -07:00
Peter Steinberger b581ff0be7 feat(apps): resolve the per-profile accent live on iOS, macOS, and Android (#130598)
* feat(apps): resolve the per-profile accent live on iOS, macOS, and Android

Named follow-up from #130340: native apps now fetch the caller's own
profile accent (users.prefs.get, strict #rrggbb normalization) and prefer
it over the gateway accent, refetching on users.prefs.changed — the
gateway targets that event at the caller's own profile, so clients need no
identity logic. macOS stores it separately from the seam color so
settings-pane config refreshes cannot clobber it, and fetches bypass
ControlChannel.request to avoid degrading the channel on older gateways.
Profile-less and token connections are unchanged. Removes the ios/android
users.prefs.changed allowlist entries now that handlers exist.

* chore(macos): satisfy swiftformat explicit-self on profileAccentHex
2026-08-26 20:01:40 -07:00
Ayaan Zaidi 1cb914d6bf fix(telegram): make ask_user controls native and reliable (#130262)
Make ask_user use native Telegram controls when one single-select question can be represented directly, while keeping multi-select and batch questions on the text fallback. Add native Other-to-ForceReply input, preserve Gateway-owned option ordering across Telegram, Slack, and Discord, and prevent later progress from overwriting the delivered question.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-27 08:14:39 +05:30
Peter Steinberger 7f05472938 feat(buzz): retain bounded passive context for accepted turns (#130509)
* feat(buzz): retain bounded passive context for accepted turns

Add opt-in connection-owned room and thread context using the shared history window. Preserve sender admission, current roster filtering, UTF-8 bounds, and snapshot consumption without passive inference, session recording, or typing. Related: #129599.

* docs(config): align baseline with landed Feishu sticker sets
2026-08-26 19:36:05 -07:00
Peter Steinberger 7602af0667 fix: remove unavailable OpenGrep install alternatives (#130585) 2026-08-26 19:20:09 -07:00
Peter Steinberger 9ccbbf83f2 perf(control-ui): load built-in theme palettes on demand (#130473)
* perf(control-ui): load built-in theme palettes on demand

Every built-in theme's tokens shipped in the startup stylesheet, so the default
path downloaded six palettes it never painted and each new theme taxed everyone.
That is what pushed the startup CSS ceiling from 45 to 47 KiB when Tide, Beacon,
and Phosphor landed.

Moves the twelve non-default palettes into public/themes/<family>.css, one file
per family covering both modes. Claw stays inline because its tokens are the
:root defaults, so the default path loses nothing and gains the bytes back.

The first-paint story this needed: index.html's boot script now links the active
family's palette during head parsing, which makes it render-blocking exactly
like the app stylesheet, so a persisted theme paints its own colours on the
first frame instead of flashing the default. The href is built from the mount
prefix the gateway already stamps on <html>, so it follows a configured Control
UI base path without the script having to know one. theme.ts keeps the link
correct when the theme changes at runtime, reusing the helper the webfont
stylesheets already use.

The nested resolve-theme ternary became a family table in the same script, since
it now picks an asset as well as a data-theme value.

  startup CSS  45.8 -> 42.2 KiB gzip, below the 44.3 KiB it measured before the
               three themes landed; ceiling restored 47 -> 45 KiB
  base.css     64.0 -> 35.8 KiB raw

Adds a regression test that blocks every bundle script and asserts the palette
still applies, so moving this back into the app bundle fails instead of silently
reintroducing the flash. Verified it catches that: with the boot-script link
removed the assertion reports `expected null to be '/themes/tide.css'`.

* fix(control-ui): publish themes after their palettes load

* fix(control-ui): clean up palette completion listeners

* refactor(control-ui): consolidate theme name resolution
2026-08-26 18:56:14 -07:00
Peter Steinberger 2d386c2ea6 fix(packaging): serialize packs when the docs map is already generated (#130538) 2026-08-26 18:01:28 -07:00
Vincent Koc 8afa8f339e fix(release): restore direct package Telegram acceptance (#129912)
* fix(release): provision package acceptance plugin registry

* fix(release): accept registry tuple in package dispatch

* fix(release): default Docker advisory input

* fix(release): bind artifact registry to package source

* fix(release): preserve package advisory dispatch

* fix(ci): bound hosted release gate checks

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* test(ci): update release-gate manifest fixture

* fix(ci): avoid duplicate release gate lint

* test(release): restore Docker helper fixture path

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-27 08:32:19 +08:00
Ayaan Gazali 1f2e99832b fix(channels): bundled channels reject the documented responsePrefix override (#118148)
* fix(channels): bundled channels reject the documented responsePrefix override

* fix(feishu): pass channel and account to the reply prefix resolver

The ordinary reply dispatcher built the prefix context without channel or
accountId, so channels.feishu.responsePrefix and its account override fell
through to the global value. The comment dispatcher already passed them.

* fix(channels): accept twitch's root responsePrefix and type ClickClack's

Twitch validates as a two-branch union with both branches closed, so
responsePrefix declared only on the account shape left
channels.twitch.responsePrefix rejected whenever accounts is present. Also
declares responsePrefix on ClickClackAccountConfig so typed callers can express
what its schema already accepts.

* fix(clickclack): apply the configured responsePrefix in model mode

Model mode sends the completion straight to ClickClack instead of going through
the agent reply pipeline, so the documented responsePrefix parsed and then did
nothing. Resolve it through the public reply-pipeline seam so template
variables like {model} render from the completion's own provider and model.

* fix(clickclack): do not double the response prefix in model mode

systemPrompt is operator-owned, so a model can be instructed to emit the
configured prefix itself and the unconditional concatenation then sent it
twice. Match the shared reply normalizer's startsWith guard.

* test(clickclack): resolve inbound access through the real resolver

Main now requires access.channelIngress, which the hand-built fixture did not
provide, so the model-mode messages never dispatched. Drop the fixture and let
handleClickClackInbound resolve access from a DM-shaped message, matching the
sibling bot-loop tests.

* test(channels): reject a key missing from any composed branch

A union alternative is a configuration mode an operator can pick, so a key
present in only one mode is unusable in the other even though the union still
validates. Treat any rejecting alternative as a rejection, and walk allOf when
collecting account schemas.

* fix(channels): offer responsePrefix only where the reply path applies it

Applying the prefix to an outbound reply is per-channel wiring, not a shared
step. Buzz, Nostr, QA Channel, Raft, Reef and SMS never read it, so accepting
the key there validated a setting their delivery path ignores. Anchor the
contract to the channels that consume the prefix.

* fix(channels): complete shared response prefix delivery

Co-authored-by: ayaangazali <ayaangazali.work@gmail.com>

* docs(config): refresh response prefix baseline

* test(tlon): use canonical prefix fixture helpers

* test(channels): prove Twitch reply prefix delivery

Exercise schema admission and the real shared reply dispatcher at the Twitch native send boundary, replacing redundant schema-only coverage. Keep Tlon fixtures on canonical imports and recording options.

* docs(channels): clarify explicit message prefix behavior

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-26 17:08:28 -07:00
Dallin Romney cb1d892b3d fix(release): stage Codex for npm onboarding candidates (#130360)
* fix(release): stage Codex for npm onboarding candidates

* refactor(release): share candidate registry lifecycle
2026-08-26 16:58:45 -07:00
Peter Steinberger c9f791e075 fix(packaging): report final archive metadata (#130489) 2026-08-26 16:57:53 -07:00
Peter Steinberger a4c01c1a77 feat(ui): save appearance preferences per user profile (#130340)
* feat(ui): save appearance preferences per user profile

When a Control UI connection is bound to an authenticated user profile
(trusted-proxy, Tailscale, GitHub identity), theme, theme mode, and accent
become per-profile: stored in the existing user_preferences KV store via
users.prefs.set, overriding gateway-wide ui.prefs, with live cross-device
updates over a new additive users.prefs.changed event scoped to the same
merged profile. Restore default deletes only the profile key and falls back
to the gateway-wide value. talk.config projects the caller's profile accent
for native clients. Profile-less connections (token/password/none auth)
keep the existing gateway-config behavior byte-identically.

Release-note context: on multi-person gateways appearance choices are now
personal and follow you across devices; an admin restyling their own UI no
longer reskins the whole team.

* chore(protocol): allowlist users.prefs.changed for mobile clients

iOS and Android resolve the profile accent through talk.config on connect
and config refresh; live per-profile appearance push on natives is a named
follow-up.

* chore(protocol): regenerate Swift/Kotlin models for users.prefs.changed

* fix(ui): honor explicit defaults and identity switches for profile appearance

Addresses the three ClawSweeper review findings:
- resetValue for profile-bound appearance keys is the deletion fallback
  (gateway value), so an explicit product-default selection persists as a
  profile write instead of being misclassified as a reset.
- An identity switch between two known scopes forces a full appearance
  reconcile and clears appearance keys the returning identity never set,
  so a shared browser cannot keep the previous profile's look (boot keeps
  the last-seen shortcut).
- talk.config joins the profile-dependent dispatch gate so a pending
  GitHub identity sync completes before the profile accent is projected;
  token clients pass through untouched.

* chore(ui): satisfy assertion-safety ratchet and docs formatting

* refactor(ui): extract server-prefs storage primitives

server-prefs.ts crossed the 700-line cap after the review fixes; the
stateless localStorage persistence primitives move to
server-prefs-storage.ts (no behavior change). Shrink-prunes the
assertion-safety baseline for the moved casts.

* fix(ui): keep imported custom themes out of profile storage

Custom palettes are browser-local by contract, so a profile must never
carry theme=custom to a browser that cannot render it. The wire contract
drops custom from the storable theme values (readers self-heal any stored
value), a profile-bound custom selection stays retained browser-local
(including the offline-queued path), and the exhaustive theme test now
encodes the exception.
2026-08-26 16:48:26 -07:00
Josh Lehman 88aff1f21e fix(webchat): CLI skill and harness-injected turns render as operator-sent messages (#130427)
* fix(webchat): render CLI harness-injected turns as collapsed context, not operator bubbles

Claude Code writes harness-generated user-role turns (Skill instruction
bodies, continuation summaries) into its native transcript with
isMeta/isCompactSummary flags. The claude-cli history importer dropped
that provenance, so webchat rendered those turns as right-aligned
operator bubbles the operator never sent.

Record the provenance at the import boundary as structured
InputProvenance (internal_system / cli_harness_context) — the same
contract the embedded runner uses to keep internal system prompts out of
operator bubbles — and teach the system-notice path to render such turns
as a collapsed "Injected context" notice that does not start an operator
turn.

Regression coverage: importer unit test (provenance recorded only for
flagged rows), live chat.history gateway RPC test (provenance survives
merge + display projection), and a chat-thread UI test (injected turn
becomes a collapsed notice while a real operator message keeps its
bubble). Before/after webchat captures via
scripts/capture-injected-turn-notice-proof.mts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Publish openclaw/fix-cli-injected-turns-rendering-as-operator-messages

OpenClaw-Publication: 62f03fec-179b-4c5e-9423-4df80809de41

* chore: drop changelog entry; CHANGELOG.md is release-generated

Release-note context lives in the PR body per repo policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: register injected-turn proof capture as ui:proof script

Knip's full-tree unused-file scan requires capture scripts to be
package.json entry points, matching the sibling ui:proof:* captures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gateway): cover isVisibleInTranscriptOnly harness rows; absorb provenance helper

Claude Code groups isMeta, isCompactSummary, and isVisibleInTranscriptOnly
as its own "not a real operator turn" predicate (verified in the v2.1.246
bundle); import all three through one canonical predicate. Inline the
one-use provenance literal at the importer, returning input-provenance.ts
to its main state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 16:46:07 -07:00
Peter Steinberger 395e5db41b chore(deps): refresh dependencies after seven-day cooldown (#130296)
* chore(deps): refresh cooled npm and plugin dependencies

* chore(deps): refresh cooled build and workflow tooling

* chore(deps): retain formatter compatibility

* chore(deps): retain lint compatibility
2026-08-26 16:13:18 -07:00
Dallin Romney 116f364b50 fix(release): refresh IMAP plugin baselines (#130351)
* docs: refresh IMAP plugin inventory

* test(plugins): derive bundled startup expectations

* chore: refresh IMAP runtime sidecar baseline

* test(plugins): cover Linux-only startup defaults

* test(plugins): keep startup coverage within lint budget

---------

Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>
2026-08-26 15:09:58 -07:00
Peter Steinberger 820d167d21 fix: allow plugin state values up to 1 MiB (#130387)
* fix: allow plugin state values up to 1 MiB

* test: type-check plugin state write rejection cases

* fix(ci): keep tar archive paths local on Windows
2026-08-26 14:33:01 -07:00
Peter Steinberger d916808353 docs: compact generated maturity scorecard rows (#130331)
* docs: compact generated maturity scorecard rows

* docs: align compact maturity scorecard output
2026-08-26 14:32:19 -07:00
Peter Steinberger 066f5eb83d docs(plugins): register imap in generated plugin inventory (#130363)
The bundled imap plugin landed in #130230 without regenerating the plugin
inventory docs, so pnpm plugins:inventory:check failed on main and the plugin
was absent from the reference index. Regenerate, and register its operator
guide in PLUGIN_DOC_ALIASES so the generated reference page links to
/automation/imap (same mechanism codex and firecrawl use).
2026-08-26 13:33:30 -07:00
Peter Steinberger 1fc29beba2 refactor(state): fold singleton tables into config_machine_state at schema v12 (#129876)
* refactor(state): fold singleton tables into config_machine_state at schema v11

Eight singleton tables (skill_curator_state, update_check_state,
clawhub_promotions_feed_state, model_catalog_remote, voicewake_triggers,
voicewake_routing_config, voicewake_routing_routes,
onboarding_recommendations) were each one logical JSON value behind a
fixed key; their bespoke schemas, lazy ensures, and per-table accessors
collapse onto the shared config_machine_state KV under namespaced keys.
cron_store_epochs retires outright: it was born write-only in #114388
and no reader ever existed in any language. Durable values (update
check state, voicewake triggers and routing, per-workspace onboarding
answers) migrate insert-if-absent during the v10->v11 migration; cache
class contents rebuild on next use. Deferred with named reasons:
exec_approvals_config (macOS direct-SQL contract), installed_plugin_index
(same-tx lease fence), node_host_config and web_push_vapid_keys
(secret-table git-backup redaction).

# Conflicts:
#	src/skills/workshop/collection-review-state.ts
#	src/skills/workshop/collection-review.gateway-admission.test.ts

* test: register v11 guard carve-outs and suppression pin

The v11 migration module joins the raw-SQLite allowlist (migrations are
the named guardrail exception), the lint-suppression allowlist records
the second type-parameter suppression in config-machine-state, and the
identity module keeps only externally consumed exports.

* test: surface CLI stderr when migration-diagnostic assertion fails

* test: expect migration diagnostics on stderr for models plain commands

The #129037 pending-migration cases asserted that aliases/fallbacks
lists never open the state database, but config-health observation
(observeConfigSnapshot -> readConfigHealthStateFromStore) full-opens it
on any config read whose file exists — reproduced identically on clean
main with a main-built dist. The protected contract is exact stdout;
the diagnostic legitimately lands on stderr for every case.

* test: drop unused defaults import from CLI stdout e2e

* test: split session path derivation out of oversized session-files suite

#130016 pushed session-files.test.ts to 1008 lines, over the 1000-line
lint cap and red for every PR's check-lint. The sessionPathForFile
describe moves to a self-contained sibling following the existing
session-files.*.test.ts split pattern; no assertions change.

* refactor(state): fold four more singleton tables into schema v12

tui_last_sessions (cache-class, regenerates on next session switch),
sidebar_sections (persistent section order, migrated as one JSON array),
node_host_config, and web_push_vapid_keys join the v12 fold-in, taking
the retirement to thirteen tables at the same version. The two secret
singletons were blocked on table-granular git-backup redaction; backups
now exclude config_machine_state rows by secret key prefix (nodeHost.*,
webPush.vapidKeys) with a fail-closed row filter and regression proof,
so STATE_SECRET_TABLE_NAMES sheds both tables. The sidebar fold also
retires its lazy-ensure WeakSet and inline DDL; sidebar edits stay
inside the existing session-group write transaction via direct Kysely.

* fix(node-host): omit absent Cloudflare Access config like the column reader

The KV rewrite returned gateway.cloudflareAccess as an own undefined
property where the retired column reader omitted the key; toStrictEqual
consumers (state-migrations doctor-repair test) caught the shape drift.
Mirror the column reader's conditional spread at both construction
sites.

* fix(backup): disclose redacted machine-state prefixes after restore

The prefix-granular secret redaction recorded omitted key prefixes in
the backup manifest but the restore result exposed only excludedTables,
so a redacted restore looked complete while nodeHost.* and
webPush.vapidKeys configuration were intentionally absent. The restore
result and CLI output now disclose the omitted prefixes (JSON mode
carries them via the result shape), with restore-side regression
coverage.

* fix(tui): compare-and-delete retired session pointers

Doctor cleanup read matching pointer keys then deleted them
unconditionally, so a replacement pointer written between the scan and
the delete was erased. The delete now re-checks the stored value inside
the write transaction and only removes pointers that still name a
retired session; a live replacement survives (regression covered).
Also corrects the stale schema-version line in database-first.md.
2026-08-26 13:32:19 -07:00
Peter Steinberger fc2724d831 fix(release): normalize package tarball modes and prove non-root install (#130335)
npm/pnpm pack copy on-disk file modes into the tarball, and node-tar's
portable mode-fix only strips group/other write bits — it never adds
read bits. A restrictive-umask build host therefore ships owner-only
(0600/0700) tarball entries, which breaks the CLI for non-root users
after `sudo npm install -g` under mode-preserving consumers such as
system tar.

- Normalize every packed entry to 0644/0755 (a+rX, exec bits kept) as
  the last step of packOpenClawPackageForDocker.
- Add a tar -tvf mode gate to check-openclaw-package-tarball that
  rejects any non-world-readable entry.
- Run the docker-package-install npm lane as root and execute the
  installed CLI as a non-root user to prove the fix live.
- Fix the docker-package-install bun proof, broken on main since
  #129552 wired the bun smoke into the shared openclaw-e2e-instance
  library: replace the drift-prone per-file harness copy list with
  directory copies, and add a closure-walking guard test that fails
  on missing harness dependencies.
2026-08-26 13:24:13 -07:00
Heming Zeng 2555e9fa4e fix: stop retry storms after definitive channel rejections (#127353)
* fix(outbound): terminalize definitive channel rejections

* refactor(outbound): rethrow unclassified Slack rejections by identity

The Slack send boundary replaced every non-Error rejection with a synthetic
Error before classifying. That changed the propagated value at all four send
call sites, contradicting the stated contract that unlisted rejections keep
their previous path, and forced a compensating one-level `cause` walk in
`isSlackInvalidBlocksError` so the downstream `invalid_blocks` fallback could
still match. The guard's second operand was also dead: `isRecord` accepts Error
instances, so `!(err instanceof Error) || !isRecord(err)` never reached its
right side for a plain object.

Classify off the raw value and rethrow unclassified rejections by identity;
the `cause`-walking compensator and its test go away with it. Distill the
Telegram migration classifier's three-state result object into a
message-or-nothing, and document the description-first and 52-bit id contracts
inline.

Production surface for the PR drops from +71/-11 to +62/-8.

* test(agents): remove clock-tick race from workspace bootstrap ctime coverage

The in-place-edit case added in #127769 assumes restoring mtime leaves ctime as
the only changed stat field, and that it therefore differs. Linux and macOS
stamp ctime from a coarse per-tick clock, so an edit landing in the same tick as
the cached stat leaves ctimeMs equal: the ctime-only scenario never occurs, the
cache correctly serves its entry, and the assertion fails. Measured 199/200
identical ctimes in a tight loop; the test failed 1 in 8 local runs and broke
checks-node-compact-large-14 on CI.

Re-touch until the kernel advances ctimeMs, then assert mtimeMs and size are
unchanged so the scenario is provably ctime-only. Stripping ctimeMs from the
cache identity still fails the test.

* fix(slack): keep post-dispatch upload completion rejections ambiguous

PlatformMessageNotDispatchedError is a provider assertion that no
recipient-visible send began, and its contract says never use it after an
ambiguous send. files.completeUploadExternal runs after onPlatformSendDispatch
and is the one-time share operation, so a rejection there cannot prove the file
was never shared however definitive its code reads.

Drop the permanent-rejection classifier from that call and keep it on the
pre-dispatch calls only (chat.postMessage, files.getUploadURLExternal,
resolveChannelId). The upload test that pinned the old behavior asserted
onPlatformSendDispatch had already fired, which is exactly the condition that
forbids the claim; it now pins ambiguity instead.

Also widens the workspace bootstrap ctime wait to a 1s deadline and reshapes it
as a while loop, so a coarser filesystem tick cannot exhaust the bound.

* test(slack): prove permanent rejection recovery

* fix(test): stabilize Slack channel action routing

* fix(test): retain channel parity for precise targets

* refactor(outbound): drop unrelated test-routing changes

* fix(telegram): require Bot API error code for migration rejection

* test: repair Telegram tuples and preserve Slack test routing

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-26 13:16:48 -07:00
Peter Steinberger 1560142754 fix(e2e): stable candidate plugin repair fails after update (#130306)
* fix(e2e): resolve stable candidate plugins by latest

Amp-Thread-ID: https://ampcode.com/threads/T-01a039ee-f7c4-71d8-8b71-e028dbd60493

* fix(e2e): preserve stable fixture dist-tags

Amp-Thread-ID: https://ampcode.com/threads/T-01a039ee-f7c4-71d8-8b71-e028dbd60493

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 13:08:37 -07:00
Peter Steinberger e1a9f086e4 refactor(channels): compact generated channel catalog (#130284) 2026-08-26 12:19:31 -07:00
Peter Steinberger 99a02bf115 feat(approvals): typed approval scope summaries on channel cards (#130116)
* feat(approvals): typed approval scope summaries on channel cards

Approval owners can attach a closed ApprovalScope union (message-send,
payment, external-post) describing an action's blast radius. The gateway
sanitizes it once at the producer boundary, the core view model renders a
Scope metadata row so Slack/Discord/Google Chat cards show it unchanged,
shared text builders cover Telegram/WhatsApp/Signal/iMessage/Matrix, and
the durable presentation carries it additively for operator surfaces.
Scope is display-only, never authorization; missing scope keeps today's
cards.

* fix(approvals): emit native ApprovalScope union and clamp recipient previews

Name the three scope variants as registered protocol schemas so the Swift
generator emits the ApprovalScope discriminated union the presentation
structs reference, and commit the regenerated GatewayModels.swift. Clamp
recipient previews to the declared recipientCount at the sanitize boundary
so a count of 1 with 2 previews can no longer render inconsistently.
Addresses both ClawSweeper findings on #130116.

* refactor(approvals): extract text sanitizer to break the exec-approvals import cycle

check:architecture flagged approval-scope joining the exec-approvals SCC
through exec-approval-command-display. Move the self-contained display
sanitizer into a leaf module (exec-approval-text-sanitize) with no
exec-approvals imports and migrate all sanitize importers; command-display
keeps only the payload-typed command/preview resolver.

* chore(plugin-sdk): ratchet public surface budgets down after sanitizer extraction

The approval display sanitizers left the publicly reachable SDK graph when
they moved to the exec-approval-text-sanitize leaf: exports 4343 -> 4338,
callable exports 2582 -> 2578. Shrink-only budget pin.
2026-08-26 12:13:49 -07:00
Peter Steinberger 21df0f36a4 fix(e2e): mount prerelease registry verifier (#130288)
Amp-Thread-ID: https://ampcode.com/threads/T-01a039ee-f7c4-71d8-8b71-e028dbd60493

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 11:58:43 -07:00
Peter Steinberger 08a42a561c feat(control-ui): add the Tide, Beacon, and Phosphor themes (#130232)
* chore(control-ui): vendor JetBrains Mono and Atkinson Hyperlegible Next

Adds the woff2 faces for the Beacon and Phosphor themes plus the stylesheets
that declare them. Both families are SIL Open Font License 1.1 and ship
unmodified with their upstream license text, as the license requires.

Atkinson Hyperlegible Next was drawn by the Braille Institute for low vision:
its letterforms disambiguate the pairs that blur first at low acuity (I/l/1,
O/0, rn/m), which is why it belongs to the accessibility theme specifically.

base.css has always named JetBrains Mono first in --mono, but nothing shipped
it, so code blocks rendered in it only for people who happened to have it
installed locally. Phosphor makes that deterministic.

Latin and latin-ext subsets only, 195 KB. Served from the gateway rather than a
font CDN so font-src 'self' holds, no third-party request happens on load, and
an offline or LAN-only gateway still renders correctly. The url() references are
relative to their stylesheet so they follow a configured Control UI mount.

* feat(control-ui): add the Tide, Beacon, and Phosphor themes

Three built-in themes, each filling a gap the existing four share rather than
adding a fifth variation on them. Every shipped accent today is warm — coral,
crimson, chocolate, clay — and the only cool token anywhere is Claw's teal
secondary.

Tide is the cool one: steel cyan on deep slate, deep teal-blue on cool paper.
It deliberately ships no webfonts, so it costs nothing beyond its tokens.

Beacon is the accessibility one. It targets WCAG AAA (7:1) rather than the AA
4.5:1 floor the other themes hold, for low vision, direct sunlight, projectors,
and poor panels, and sets everything in Atkinson Hyperlegible Next. Reaching AAA
forced one deliberate break from the status-token convention: elsewhere each
--x-subtle is --x at 8%, but a label on a tint of its own hue caps achievable
contrast because the tint pulls the background toward the text. Beacon tints
neutrally and lets the label carry the contrast. Its focus ring is opaque and
3px for the same reason — a translucent ring is the first thing to vanish on a
bad panel. The contrast guardrail now enforces 7:1 for Beacon specifically, so a
later palette edit cannot quietly demote it to an ordinary dark theme.

Phosphor is the terminal one: phosphor green on green-cast black with the whole
surface, chat prose included, in JetBrains Mono. Monospace prose is a character
choice for an operator console, not a readability claim, which is why it is
opt-in.

Every palette was solved against the real guardrail before any CSS was written.
Worst text pairing per theme: Tide 6.12:1 dark / 4.63:1 light, Beacon 10.29:1 /
9.72:1, Phosphor 6.69:1 / 4.76:1. Worst status label on its own tint: Beacon
7.05:1 / 7.02:1, the others above 4.6:1.

Budgets: the startup CSS ceiling moves 45 -> 47 KiB and the JS ratchet baseline
moves 523 B. Every built-in theme's tokens ship in the startup sheet, so each
new theme costs ~0.5 KiB gzip whether or not anyone selects it. Moving per-theme
palettes to lazily linked stylesheets (as theme webfonts already are) would take
this back under the old ceiling, but it needs a first-paint story first: a late
palette flashes default colors where a late font only swaps.

* fix(control-ui): use Beacon's destructive foreground in chat confirmations

The chat confirmation button paints from --danger under --media-foreground, a
theme-invariant white, and each dark palette opts out of that pairing through a
selector list in chat/grouped.css. Beacon defines a deliberately light --danger
(#ffabab, chosen so status labels clear AAA) but was missing from that list, so
its confirm button rendered white on pale pink at 1.80:1 — on the theme whose
entire promise is contrast.

Adds Beacon to the list, which routes it to --destructive over
--destructive-foreground at 11.67:1.

Also adds a guardrail for the bug class, since the selector list has to be
extended by hand for every new dark theme and I have now missed it once. The
test reads membership back out of grouped.css rather than restating it, resolves
each theme's effective (background, color) pair, and holds it to that theme's
floor. It reproduces this defect at 1.80:1 against the pre-fix stylesheet.

Reported by ClawSweeper review on #130232.
2026-08-26 11:47:01 -07:00
Harjoth Khara 3be7baa1a3 fix(macos): timestamp hash-pinned Developer ID signatures (#118989)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-26 10:39:41 -07:00
Dallin Romney 8ce8085e84 fix(e2e): include Codex thread project id (#130086) 2026-08-26 09:59:36 -07:00
Josh Avant e6ed7e30cc feat(audit): record session action decisions (#129093)
* feat(audit): record session action decisions

* fix(protocol): preserve session sharing client compatibility
2026-08-26 08:21:37 -07:00
Peter Steinberger 60e3d5f194 fix: prevent Feishu and Mattermost suite collection stalls (#130142)
* test(extensions): narrow ingress state import graph

* test(extensions): migrate ingress-only state imports

* test(extensions): sync xai ingress boundary alias

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 08:00:58 -07:00
Peter Steinberger 776986f942 refactor: compact tool display metadata (#129523) 2026-08-26 05:59:41 -07:00
Vyctor H. Brzezowski 1c1673cc35 fix(ui): structure chat transcript attachments by kind (#127076)
* fix(ui): structure chat transcript attachments by kind

* Refine attachment previews

* Refine attachment cards and previews

* Refine audio attachment controls

* Refine attachment cards and previews

* Move audio timer before progress

* Add attachment link fixture

* Make attachment downloads secondary

* Add attachment file icon system

* Tune attachment icon sizing

* Compact attachment preview headers

* fix(ui): align large attachment card inset

* feat(ui): color attachment glyphs by family

* feat(ui): use icon-only attachment actions

* fix(ui): space compact attachment metadata

* fix(ui): finalize attachment card contracts

* Revert "fix(ui): finalize attachment card contracts"

This reverts commit 41b28970b5.

* fix(ui): reduce mobile waveform density

* fix(ui): restore native image rendering

* fix(ui): align and enlarge chat images

* fix(ui): restore open labels on file cards

* fix(ui): simplify labeled attachment actions

* fix(ui): size waveforms from container width

* fix(ui): update attachment open icon

* fix(ui): use continuous corners for chat images

* fix(ui): preserve sidebar video playback

* fix(ui): retain attachment kind in sidebar

* fix(ui): refresh open sidebar attachments

* fix(ui): bound delimited attachment previews

* fix(ui): harden attachment preview lifecycle

* fix(ui): reset lazy attachment previews

* style(ui): format attachment preview code

* fix(ui): align attachment sidebar content

* fix(ui): constrain CSV attachment previews

* fix(ui): keep text attachments compact

* fix(ui): block external document previews

* fix(ui): verify document iframe loads

* fix(ui): render honest audio waveforms

* test(ui): align attachment player expectations

* test(ui): exercise same-origin CSV previews

* test(ui): use browser-origin CSV fixtures

* style(ui): format attachment fixes

* fix(ui): normalize document preview URLs

* test(ui): clarify CSV fixture indices

* fix(ui): stabilize audio time label

* fix(ui): preserve external attachment downloads

* fix(ui): avoid eager video attachment loading

* fix(ui): parse TSV previews by attachment type

* fix(ui): retry managed attachment resolution

* style(ui): format attachment review fixes

* fix(ui): pass attachment type to table preview

* test(ui): normalize compact card label

* fix(ui): preserve sidebar attachment downloads

* test(ui): align attachment card expectations

* style(ui): order attachment imports

* fix(ui): isolate document attachment previews

* fix(ui): release Files attachment ownership

* fix(ui): resolve sidebar attachments from live context

* fix(ui): preserve attachment video aspect ratios

* style(ui): format attachment surfaces

* fix(ui): apply renewed video tickets on interaction

* fix(ui): restore visible attachment waveforms

* fix(ui): fade truncated CSV previews

* fix(ui): lazy load attachment previews in view

* fix(ui): preview image attachments inline

* test(ui): remove link attachment fixture

* fix(ui): block network in HTML attachment previews

* fix(ui): keep image hover actions stable

* feat(ui): preview SVG attachments safely

* perf(ui): split attachment preview styles

* fix(ui): harden attachment preview ownership

* refactor(ui): deslop attachment rendering

* refactor(ui): render transcript attachments as cards

* fix(ui): keep remote SVG attachments CSP-safe

* test(ui): keep chat pane suite within lint limit

* test(ui): cover Files shortcut cleanup

* fix(ui): normalize structured SVG metadata

* fix(ui): classify SVG attachments by source

* fix(ui): preserve attachment type precedence

* perf(ui): keep attachment CSS out of startup
2026-08-26 09:59:11 -03:00
Peter Steinberger 3b22a6cdd3 fix(gateway): keep loaded conversations responsive under heavy load (#130071)
* fix(gateway): keep loaded sessions responsive under heavy load

* test(gateway): narrow concurrency lane and stabilize inherited lint

* fix(ui): retain presented snapshots during background prefetch

* chore: respect release-owned root changelog policy
2026-08-26 05:42:01 -07:00
Peter Steinberger a842ad35f3 test(release): require live channel after restart (#127013)
* test(release): require live channel after restart

* test(release): migrate upgrade channel assertion

* test(release): assert channel runtime after restart

* test(release): await restarted channel runtime

* test(release): prove ClickClack reconnect generation

* test(release): preserve fixture runtime status

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 03:27:23 -07:00
xingzhou ee2f5f2084 fix(install): --json preserves valid NDJSON for dynamic values (#128682)
* fix(install): preserve valid NDJSON for dynamic values

* fix(install): satisfy installer shell checks
2026-08-26 03:19:43 -07:00
Peter Steinberger ea78c2488b feat(msteams): native Adaptive Card approve/deny for exec and plugin approvals (#129997)
* feat(msteams): deliver native Adaptive Card approvals

Exec and gateway plugin approvals now render as Adaptive Cards in Microsoft
Teams with token-bound approve/deny actions, mirroring the Google Chat card
pattern. Card submits are intercepted before message-text serialization,
authorized against channels.msteams.allowFrom/defaultTo AAD object IDs via
the existing approval auth, claimed once, resolved over the gateway, and the
card is updated in place to its terminal state. Native delivery gates on the
top-level approvals.exec/approvals.plugin forwarding config; the /approve
text fallback remains.

* chore(msteams): shrink assertion-safety baseline after send.ts cast removal

* chore(msteams): record approval-native adapter seam in chained-assertion ledger

* fix(msteams): surface a text approval fallback when card delivery fails

When the native route suppressed the local text prompt, a failed Adaptive
Card send only logged, leaving the pending approval invisible. On delivery
error, send a plain-text /approve prompt to the planned target so the
operator always has a visible approval path. Addresses the ClawSweeper P1
on #129997 channel-locally; #130040 tracks the shared-boundary fix.
2026-08-26 02:56:22 -07:00
Vincent Koc 02c5190014 fix(release): preserve beta channel in prerelease survivor (#129966)
* fix(release): preserve beta channel in prerelease survivor

* fix(release): register prerelease survivor scenario
2026-08-26 17:47:16 +08:00
Peter Steinberger 0f55efc443 fix(ci): restore Control UI startup CSS budget (#130046)
Amp-Thread-ID: https://ampcode.com/threads/T-01a03d42-8db7-733e-bbff-f3d2a08071d0

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 02:45:49 -07:00
Ayaan Zaidi a901d33184 fix(skills): share the foreground prompt prefix with the experience review (#130013)
The detached Skill Workshop experience review rebuilt its system prompt and tool catalog from a different context than the foreground turn, so every review missed the prompt cache. Native harnesses (embedded, Codex, Copilot) now hand the review the same foreground prompt context via buildEmbeddedForegroundPromptContext; the review reuses the foreground prefix and gates execution to skill_workshop while keeping the catalog identical. Reviews without a foreground prompt (CLI hook contexts) are skipped.
2026-08-26 09:39:54 +00:00
Peter Steinberger adcee720dd refactor(scripts): share web-fetch benchmark argument parsing (#129985) 2026-08-26 02:10:24 -07:00
Peter Steinberger ff73ba5f56 fix(ci): restore Control UI startup budget gate (#130015)
Amp-Thread-ID: https://ampcode.com/threads/T-01a037b5-3918-749f-90bd-5c9ac1dced16

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 01:48:46 -07:00
Peter Steinberger 1605dbd3ef chore(deps): refresh dependencies after seven-day cooldown (#129941)
* chore(deps): refresh dependencies after cooldown

* chore(deps): refresh sherpa-onnx runtime to v1.13.6

* test(ios): refresh Fastlane pin expectations
2026-08-26 01:37:37 -07:00