* fix(gateway): stop a plugin dangerous flag from revoking desktop computer.act
`resolveNodeCommandAllowlistInternal` subtracted every plugin-declared
dangerous command from the composed allowlist, including core's own
`PLATFORM_DEFAULTS` entries and including the pairing allowlist, where core's
dangerous defaults are exempted. `computer.act` is both a desktop platform
default (grant = node-local enablement + pairing approval) and a command that
`registerComputerUseProvider` marks dangerous. Since `cua-computer` became
enabled-by-default on darwin, every macOS-hosted Gateway stripped `computer.act`
from both allowlists, so `normalizeDeclaredNodeCommands` dropped it from the
node's declaration with no pairing upgrade, no prompt, and no record. The
`computer` capability survived because caps were never allowlist-filtered, and
the `computerUse` descriptor was then dropped for the missing command.
Scope the plugin-dangerous subtraction to commands outside the platform-default
base: the flag still keeps a plugin's own surface behind an explicit allow and
still forces a registered invoke policy, but it no longer revokes a command core
declares itself.
Also make cap-without-command unrepresentable. `retainFulfilledNodeCapabilities`
drops a capability when policy withheld commands from its family and admitted
none, and the reconciler records the withheld commands so a refused declaration
is never silent.
* test(gateway): type the computer-use fixture against its contract
Adds an additive agent_provenance table (shared state DB, schema v8) owned
by src/state/agent-provenance.ts. createAgent() records operator/agent
provenance after commit, the system-agent create-agent operation passes its
own id as creator, and Claw installs record created-via claw at their roster
commit point. Agent deletion removes the agent's own row inside the deletion
journal transaction; children keep dangling creator ids as historical fact.
openclaw agents list gains --tree (provenance hierarchy) and JSON provenance
fields.
* fix(skills): expand explicit references on agent turns
Route generic Gateway, CLI, webhook, and local agent turns through the same explicit skill-reference renderer as channel auto-replies. Keep original transcript text, preserve unknown slash behavior, and fail visibly for allowlist-hidden skills.
Maintainer review: scoped Option 1 — generic agent turns expand both $skill-name and leading /skill-name args through shared skill rendering; they do not run the channel command dispatcher, and all other slash commands retain their existing behavior.
* fix(skills): bound explicit reference prompts
* fix(skills): prefer allowed reference collisions
* fix(skills): preserve command invocation boundaries
* fix(skills): reject hidden channel slash commands
* perf(skills): skip literal dollar discovery
`checks-windows-node-test` ran the whole 222-266s Windows body in a single
Blacksmith lane, so every run that scheduled it finished at 287-334s against a
~180s plateau. Measured across 45 canonical main runs it was the wall in all
seven runs that triggered it (324-519s vs a 210s median elsewhere).
Blacksmith's Windows class admits exactly 2 concurrent jobs (run 31865243804),
which is why the earlier 3-part split failed: its tail queued 211s behind a
finished part. Pin the split width to that measured capacity instead. The two
parts are balanced by measured per-project wall time -- part 1 keeps the old
list 1 plus list 2's non-infra projects (~108s), part 2 takes list 2's infra
project plus the old list 3 (~112s) -- so both land near 113s and the lane
stops setting the wall.
One canonical partition now serves every backend; the github-mode/dispatch
3-part variant and its separate file inventory are gone, along with the
single-lane `task: test` case and the per-row `matrix.runner` the collapsed
lane needed. The complete 65-file Windows inventory is preserved exactly, and
the guards pin the width on both sides (mutation-checked: restoring 3 parts
fails the matrix guard).
The Block Kit session card terminalized into a '🛠️ N tool calls · ⏱️ Ns'
footer, leaving a per-turn receipt in the Slack transcript on the default
progress path. PR #122976 deleted the same receipt from the native progress
surface but left this sibling rendering it.
Finished cards now keep only the durable diff stat and the session link;
tool-call and elapsed counters stay live working state. Also corrects the
documented channels.slack.streaming.mode default, stale since #122552.
* fix(browser): support Chrome Web Store native bootstrap
* chore: keep browser release note in PR body
* docs(browser): document Store identity trust boundary
* docs(browser): correct Store recovery guidance
* fix(state): store in-root agent database registry paths relative to the state dir (schema v9)
Copied state directories retained stale absolute agent database registry rows. The combined gateway store then merged old and new copies, causing every sessions.list request to fail with SessionCanonicalKeyMigrationRequiredError.
Schema v9 stores in-root registry paths relative to the state directory; migration rewrites eligible rows, deletes stale duplicates, and preserves external paths.
* fix(voice-call): describe state-relative registry migration
* fix(state): preserve registry locator traversal
* test(macos): move PortGuardian schema-version boundary to v9
The store opens any state database up to maximumSupportedSchemaVersion,
which the schema v9 bump moved from 8 to 9. Shift the supported/newer
partition in the boundary test accordingly.
* fix(state): re-anchor copied default-layout registry rows instead of deleting them
Resolve the ClawSweeper P1 by preserving genuine external default-layout registrations, re-anchoring copied rows when an in-root counterpart exists, and deleting only dual default-layout conflicts.
* refactor(line): replace nine-marker prompt DSL with typed rich messages
Delete the LINE plugin's double-bracket marker language (quick_replies,
location, confirm, buttons, media_player, event, agenda, device,
appletv_remote) and its parser. Portable interactions now flow through the
existing presentation-block seam (renderPresentation, matching Discord and
Feishu); LINE-specific cards ride closed channelData.line schemas mapped to
the existing Flex renderers. Prompt section shrinks to four capability
lines and explicitly de-fangs marker text. Removes the stale
assertion-safety baseline entry for the deleted parser.
Production LOC net -69, tests net -433. Suite: 510/510 green.
* fix(line): declare rich message schema dependency
* fix(line): satisfy rich message type checks
* docs(line): mark card fragments as partial
* fix(ui): keep Control UI device identity working on plain-HTTP origins
@noble/ed25519 defaults its SHA-512 provider to crypto.subtle, which
browsers gate to secure contexts, so device identity silently vanished
on http:// LAN dashboards and connects fell back to shared-credential
auth with no pairing. Wire a lazy pure-JS @noble/hashes fallback for
SHA-512 and the fingerprint SHA-256, and drop the isSecureContext gate
in the connect path. Secure contexts keep the platform digests and pay
no startup bytes: the fallback loads as its own lazy chunk, kept out of
the gateway-runtime startup chunk on purpose.
* test(ui): cover device identity minting and signing without crypto.subtle
New jsdom regression suite fails on pre-fix code (subtle-less crypto stub
with getRandomValues, which real insecure contexts keep). Rewrites the
gateway connect tests that previously asserted the device-less insecure
fallback: an insecure context now attaches a device identity.
* docs(web): plain-HTTP dashboards now pair with a device identity
The signing key never crosses the wire, so HTTP+pairing is strictly
stronger than the old HTTP token-only fallback; HTTPS (Tailscale Serve)
stays the recommendation for transport privacy.
* fix(ui): drop unnecessary boolean literal compare in secure-context timing meta
* test(ui): declare device.id on the connect-frame test shape
* test(ui): split the subtle-less scope-upgrade e2e into the two real invariants
Without crypto.subtle the browser can now sign, so the banner offers the
explicit admin upgrade; manual-only guidance is reserved for browsers that
cannot mint an identity at all (no WebCrypto RNG). Also corrects the
connect-path comment: blocked storage yields an ephemeral identity, only a
failed mint degrades device-less.
* fix(ui): address review findings on the HTTP device-identity path
- Storage-blocked pages keep one stable in-memory identity per page
lifetime instead of minting a fresh unpaired key on every reconnect,
and a write-rejecting store no longer fails the mint (regression tests
bite pre-fix).
- Connect timing now reports the real browser secure-context fact via a
shared browserSecureContext() helper instead of inferring it from
device-identity presence.
- Docs state the accepted trusted-proxy contract: browsers attach a
device identity on every origin, so first connects follow the standard
pairing flow (deviceAutoApprove or a one-time approval); device-less
admission remains only for browsers that cannot mint an identity.
* refactor(ui): trim the connect-path additions under the max-lines cap
* feat(voice-call): add sessionScope "main" for main-session call routing
Inbound and outbound calls can now share the configured agent's main
session instead of a dedicated voice session. The new scope resolves
through the existing explicit-key canonicalization path, honoring core
session.mainKey and global-scope aliasing. The inbound webhook path now
forwards coreSession like every other resolver call site.
* style: format events.test.ts
* test(voice-call): split events.test.ts under the max-lines cap
* fix(cli): fail uninstalled service mutations
Treat Gateway and Node start/restart as failures when no managed service is installed, while preserving absent-service stop as an idempotent success.
* docs(cli): clarify gateway restart recovery
`checks-ui-e2e-real-gateway` folded `hybrid` into the `github` breaker clause
instead of taking the attempt-gated route its sibling `checks-ui-e2e` uses, so
it was the only Chromium lane pinned to hosted capacity on attempt 1. Measured
across 46 canonical main runs it averaged 205s (max 225s) against a 150-190s
plateau, making it the run's slowest job whenever the compact Node matrix
behaved.
Route it like `checks-ui-e2e`: Blacksmith 16-vCPU on attempt 1, hosted on rerun,
manual dispatch, fork, and same-repo PR retry. Cache inputs stay on the shared
pnpm store for both backends, matching the sibling lane.
The guard now derives both Chromium lanes' `runs-on` from one pinned template so
a hosted-only divergence cannot return unnoticed; mutation-checked by reverting
the workflow clause (2 failures).
* refactor(gateway): delete the retired Control UI device-auth migration window
The one-time remediation window for the retired
gateway.controlUi.dangerouslyDisableDeviceAuth break-glass threaded a pending
state machine through the connect pipeline, startup bootstrap, lifecycle,
request context, device management, security audit, the hello frame, and a
Control UI banner flow — steady-state runtime carrying a retired-shape shim
the architecture reserves for doctor. The window is closed: the retired key
is now fully inert, doctor still detects and removes it, and a browser that
never completed the migration pairs through the normal device flow (one
approval, no data loss).
Deleted with it: the config-machine-state import of the legacy flag, the
hello deviceAuthMigration field (optional; clients validate envelopes only,
so older gateways emitting it stay compatible), the migration-bound device
management authz states, the requireNoPairingCapableOperator approval mode,
the effective-operator pairing event emitter whose only subscriber was the
migration completion, and the Control UI banner, loader, overlay wiring,
i18n strings, and e2e scenario support. Swift and Kotlin protocol models
regenerated.
Live-verified on an isolated gateway with the retired key present in config:
clean boot with no migration warning, silent CLI pairing and silent local
scope widening unaffected, and doctor reporting the key as retired-and-inert.
Net -1111 production LOC.
* fix(ci): finish migration-window cleanup surfaced by the gates
The doctor migration for the retired key kept its "Preserved for remediation"
change text and describe; both now state plain removal, with the retired
tests updated. peekStoredDeviceIdentityId lost its only production consumer
with the deleted banner loader and is removed with its test mock. Also fixes
the unrelated no-unnecessary-boolean-literal-compare lint break that #124636
landed on main in scripts/check-changed.mts (truthiness is equivalent for the
boolean-or-undefined TTY probe).
* fix(ui): drop the retired-key device-auth reader from the security summary
Review findings on the migration-window removal: the Control UI security
summary still derived its "Device auth" row from the retired
dangerouslyDisableDeviceAuth key, rendering device auth as disabled on
configurations that merely retain the inert key. Device auth is now
unconditionally enforced, so the row and its derivation are removed rather
than pinned to a constant. The build-admission test also tracks and removes
its temporary device-identity databases after each run.
* feat(protocol): add named session icon glyphs
* feat(ui): add session glyph and custom emoji picker
* docs: describe named and custom session icons
* fix(ui): give the custom emoji input an accessible name
ClawSweeper P2: the input had no label relationship; assistive tech announced an unnamed edit control.
* fix(protocol): guard the v-flag icon regex for browser module loads
ClawSweeper P1: the picker's static import evaluates this module in the browser; pre-Unicode-Sets engines threw at module scope and took down the session menu. Lazy capability-guarded construction; such engines fall back to the grapheme heuristic as client pre-validation while the Gateway keeps exact RGI validation.
Fork pull requests were pinned to GitHub-hosted runners while maintainer
PRs rode Blacksmith. That split arrived with ci.yml's first commit
(f4f990a) and was never a reasoned posture -- no rationale in docs,
commit bodies, or the ci-limits skill -- and this week's Blacksmith
tranches widened the gap: maintainer walls are ~3:36 while the last fork
PR I measured (#124633) took 13m0s. We already paid for it once in
#118530, which raised the cross-repo artifact budget to 35 minutes
because contributor PR #117992 timed out twice on hosted capacity.
Runner choice now follows author_association: OWNER, MEMBER, COLLABORATOR,
and CONTRIBUTOR get Blacksmith; FIRST_TIME_CONTRIBUTOR, FIRST_TIMER, NONE,
and MANNEQUIN stay on hosted runners, which are free for public repos, so
an unreviewed author cannot spend Blacksmith capacity. Earning the fast
path requires a landed commit, which requires a maintainer merge.
Note for anyone tempted to trim that list: maintainers report CONTRIBUTOR
here, not OWNER/MEMBER, because org membership is concealed -- steipete's
74 sampled PRs are all CONTRIBUTOR. Dropping CONTRIBUTOR would move
maintainer PRs to hosted.
Scope is deliberately runner-only: 27 runs-on clauses. The 34
dependency-cache/use-actions-cache conditions and 6 job ifs stay
fork-gated, because cache poisoning is a different risk from runner
choice -- a fork run still never writes an archive a trusted run restores.
Verified by evaluating all 25 configurable runs-on expressions: maintainer
PR 12/25 Blacksmith, returning-contributor fork 12/25 (identical), unknown
author 0/25, FIRST_TIME_CONTRIBUTOR 0/25, push to main 12/25 unchanged.
Guard tests gained trusted/untrusted fork cases; 119 pass.