Commit Graph

766 Commits

Author SHA1 Message Date
Peter Steinberger e0e4335a62 fix(agents): track persisted tool call identities in session guard 2026-08-27 14:53:19 -07:00
Vincent Koc 30aa2794d9 fix(update): preserve plugin convergence through package restart (#131062)
* fix(update): preserve plugin convergence during package upgrades

* fix(e2e): isolate upgrade restart auth setup

* fix(e2e): isolate candidate restart config

* fix(doctor): skip repairs for disabled plugins

* fix(e2e): use canonical identity for restart install

* fix(update): release plugin lease before doctor

* fix(e2e): seed upgrade companion installs

* fix(e2e): validate companion install versions

* fix(ci): register upgrade config parking script

* test(plugins): type npm install config fixture
2026-08-28 03:26:43 +08:00
Peter Steinberger 8231076d69 refactor(gateway): consolidate node request validation (#130909) 2026-08-27 11:51:44 -07:00
Peter Steinberger 706a06eab6 refactor(channels): consolidate account logout cleanup (#130976)
* refactor(channels): consolidate account logout cleanup

* fix(channels): initialize local logout plugins
2026-08-27 09:18:11 -07:00
Peter Steinberger b4262689db fix(daemon): preserve structured command termination outcomes (#130634)
* fix(daemon): preserve structured command termination outcomes

Interpret service state only after a normal command exit. Carry timeout, signal and spawn failures through the shared command owner, and reuse that boundary in doctor before removing legacy service files.

* test(daemon): cover interrupted service decisions

* fix(daemon): preserve uncertain service probe outcomes
2026-08-27 09:01:58 -07:00
Peter Steinberger 3aaf13ca84 feat(ui): show activity cards for online people (#130664)
* feat(ui): show activity cards for online people

Record live person timing independently of heartbeat freshness and expose keyboard- and touch-accessible cards with visible session links. Preserve continuous online intervals across overlapping tabs without adding persistent activity history.

Closes #130649

* build(protocol): refresh generated presence timing fields

* fix(gateway): scope presence snapshots to read-access operators

Apply one recipient projection to hello, system-presence, and events. Keep person timing and time-zone details behind read access, and filter watched references with the canonical session-list policy without leaking hidden counts. Preserve idle-person metadata for readers and prevent non-reader activity-driven frames.

* fix(plugins): preserve Gateway capability load context

Carry the owning registry's metadata generation, workspace, install records, and built-artifact preference through capability discovery. Preserve standalone source loading and existing speech eligibility while avoiding synchronous source transforms on cold Talk catalog requests.

Fixes #130777

* test: align integrated presence and capability coverage

Keep the recipient matrix in its timing-aware owner, close the suite-owned Gateway for shutdown proof, and spy on the canonical install-record reader. Remove the retired private carrier's assertion allowance.

* fix(gateway): broadcast presence when clients connect

Publish completed connection rows through the canonical scoped broadcaster so established readers see first connections and reconnects without waiting for profile edits or activity. Preserve shared online intervals and reset them only after the final socket closes.

* fix(ci): align presence fixtures and sidebar type ownership

Reuse the canonical sidebar host type to remove the type-only lazy-runtime cycle. Assert omitted scopes through hello auth and denied presence reads, and supply presence version callbacks in the shared WebSocket context fixture. Retain startup admission and drain assertions without changing deadlines.

* fix(ui): retain latest scroll commands until their destination

Do not treat the virtualizer's idle debounce or a decreasing offset as reader takeover. Settle at the actual DOM end within one pixel, retaining the separate eight-pixel UI-follow policy and explicit input cancellation. Cover retargeted idle delivery and near-end precision with the real dependency, synchronize pointer baseline capture, and type the existing composer snapshot as its textarea contract.

Fixes #130892
2026-08-27 08:07:22 -07:00
Peter Steinberger 14ff866288 fix(doctor): repair agent workspace and heartbeat values on the canonical roster shape (#130959)
Restore existing null-workspace and invalid-heartbeat repairs for canonical agent entries. Share roster traversal while preserving the reachable legacy-list path when include ownership blocks migration, and verify snapshot-to-repair persistence and convergence.
2026-08-27 07:30:01 -07:00
Peter Steinberger a540a2e29b refactor(agents): simplify serialized suspension writes (#130905) 2026-08-27 06:39:16 -07:00
Peter Steinberger f04a622c1f fix(browser): resolve unnamed role refs and initialize AX markers (#130881)
* fix(browser): resolve unnamed role refs and initialize AX markers

Keep explicit raw ARIA empty names distinct from omitted serializer names and share one dependency-faithful role lookup. Bind the DOM document in the marker-owning CDP session without adding a round trip.

Related: #130879. The separate snapshot membership and ordering failures remain open.

* chore(browser): shrink ref assertion safety baseline

Record the five type assertions removed by the ref-name repair. Tighten the canonical per-file allowance from seven to two; runtime and browser-test bytes are unchanged.
2026-08-27 05:22:38 -07:00
Vyctor H. Brzezowski f15b825f64 feat(ui): make mobile navigation a draggable drawer (#130319)
* feat(ui): make mobile navigation a draggable drawer

* fix(ui): align drawer gesture with mobile layout

* refactor(ui): isolate drawer startup owners

* perf(ui): keep drawer opening in lazy chunk

* fix(ui): harden drawer modal lifecycle

* fix(ui): complete drawer modal lifecycle

* fix(ui): preserve drawer modal ownership

* fix(ui): keep drawer modal controls reachable

* fix(ui): own drawer shortcuts in capture phase

* fix(ui): defer drawer escape to nested controls

* fix(ui): suppress shell shortcuts under modals

* refactor(ui): isolate drawer key handling

* fix(ui): preserve drawer modal ownership

* fix(ui): defer picker escape to drawer

* fix(ui): scope inbox modal semantics

* fix(ui): dismiss inbox across breakpoints

* fix(ui): preserve overlay escape ownership

* fix(ui): mark drawer swipe runtime boundary

* fix(ui): separate shell key phases

* chore(ui): shrink assertion baseline
2026-08-27 10:05:58 +00:00
Vyctor H. Brzezowski 8d47620d50 feat(ui): refine the mobile chat topbar (#130318)
* feat(ui): refine the mobile chat topbar

* fix(ui): preserve responsive topbar contracts

* perf(ui): lazy-load mobile chat header styles

* style(ui): normalize chrome breakpoint check

* fix(ui): scope mobile chrome to chat routes

* fix(ui): reset browser chrome without shell

* fix(ui): align compact chat media queries

* fix(ui): remove compact transcript fade

* fix(ui): respect compact landscape insets

* style(ui): format compact safe-area insets

* fix(ui): inset compact landscape composer

* fix(ui): preserve compact chat clearances

* fix(ui): preserve composer bottom ownership

* fix(ui): preserve compact composer clearance

* fix(ui): preserve new session bottom gap

* fix(ui): inset new session above safe area
2026-08-27 10:05:58 +00:00
Peter Steinberger 0dbd5c81d5 feat(plugins): one consent screen for plugin capabilities, bound to the reviewed artifact (#130168)
* feat(plugins): surface plugin capability consent in Control UI and CLI

Adds plugins.inspect (declared manifest surface, operator grants, install
provenance/integrity, ClawHub trust), a Control UI consent dialog on install
and external-plugin enable, a server-side acceptance gate persisted on the
install record, artifact-anchored widen diffing, and --accept-capabilities
for non-interactive CLI use.

NOT READY TO LAND: autoreview found critical gaps (see PR notes) — the
declared surface omits 20 of 21 contract families, native plugins always
report zero hooks, several install/enable paths bypass the gate, and the
acknowledgment is not bound to the reviewed surface.

* refactor(plugins): bind capability consent to the reviewed surface

Collapses the consent error payload to the fields the client cannot fetch
(reviewToken, widened, acceptedAt) and pulls identity/declared/grants/source/
trust from plugins.inspect, shrinking the registry-free protocol reader from
395 to 91 lines and removing its divergence from the closed schema.

Acknowledgment now carries the SHA-256 reviewToken of the surface the operator
saw; the server recomputes the final staged artifact's surface and rejects any
mismatch before persisting acceptance. That closes review-then-swap, laundering
of forged acceptance through an unchanged update, and cross-artifact replay.

All 22 manifest contract families are now declared, hashed and diffed, so a
privileged family such as gatewayMethodDispatch can no longer be added without
re-consent. Consent reads the manifest runtime discovery will execute, ambiguous
install ownership fails closed, integrity resolution has one owner and no longer
labels npm SHA-1 shasums as SHA-256, and code plugins disclose that hooks
register at runtime instead of rendering an empty "no hooks" row.

* fix(gateway): register plugins.inspect in method inventories and regenerate protocol

Adds plugins.inspect to the advertised-method inventories (widening the
fixed-size slice windows so older indices stay stable), regenerates the Kotlin
protocol bindings, drops an unused exported type, and replaces two nested
conditional spreads with a plain conditional.

* refactor(plugins): split oversized consent modules and clear lint findings

Extracts the MCP controller out of the plugins page, unchanged-install
reconciliation out of update-installed, and the install lifecycle suite out of
the management-service tests, bringing all three back under the max-lines limit
without suppressions. Also renames a shadowed binding, drops an unnecessary
generic, removes a spread-to-modify in a map, and types catch callbacks as
unknown.

* chore(protocol): regenerate Kotlin bindings after rebase

* feat(plugins): let chat /plugins install review and accept capabilities

The consent gate applies to chat installs too, but the command had no way to
give consent, so external installs dead-ended on a CLI-only flag. Chat now
replies with the plugin's declared capability surface and the exact command to
rerun, and accepts a trailing --accept-capabilities mirroring the existing
--force acknowledgement. ClawHub trust acknowledgement stays CLI-only.

Staged-artifact verification is unchanged: the reviewToken is still checked
against the final artifact before acceptance is recorded.

* refactor(plugins): single-source the declared-surface groups and manifest precedence

The ordered capability group list was defined independently in the consent
engine, the protocol error reader, the CLI formatter and the Control UI, so a
new contract family had to be added in four places with nothing enforcing it.
All four now derive from one canonical list in the protocol schema with a
compile-time exhaustiveness guard.

Native-versus-bundle manifest precedence is centralized in one helper that both
discovery and staged consent call, so the two cannot drift again — that
divergence was a real bug where consent read one manifest and the runtime
executed another.

Also documents that carrying acceptance forward requires pinned artifact
integrity, so integrity-less sources such as local paths ask on every install.

* fix(plugins): enforce reviewed consent across activation flows

Route setup, repair, linked installs, updates, and chat activation through artifact-bound capability consent. Reuse canonical package discovery and recheck staged activation before config publication. Invalidate stale Control UI review requests on reconnect.

Verified focused owner and sibling tests, runtime rebuild, and real isolated CLI/Gateway install, inspect, enable, widening, and stale-token rejection flows.

* test(plugins): cover beta installs through capability consent

* test(plugins): align consent fixtures with staged artifacts

* fix(ui): review staged plugin capabilities once

* test(ui): inline the remaining plugin consent confirmation

* test(plugins): verify consent with deferred install transactions

* refactor(setup): share inference execution plan construction

* test(ui): settle applied config before deferring refresh

* fix(plugins): protect consent provenance and reuse acceptance
2026-08-27 02:58:07 -07:00
Peter Steinberger c52cf19bc6 refactor: consolidate diagnostic stability field projection (#130845) 2026-08-27 02:15:02 -07:00
Peter Steinberger 84484c1ae3 fix(ui): keep chat messages separated after scroll interruptions (#130758)
* fix(ui): keep chat messages separated after scroll interruptions

* refactor(ui): place transcript types with their owners

* fix(ui): publish settled transcript scroll position
2026-08-27 01:44:42 -07:00
felirami 0c313d7128 fix(plugins): allow install when outgoing channel schema disagrees (#122984)
* fix(plugins): unblock upgrades rejected by outgoing channel schemas

Recover only requested-plugin channel schema diagnostics, including channel keys different from the plugin ID. Keep incoming-schema validation and unrelated-error rejection intact. Consolidate duplicate config fixtures and remove an unnecessary error assertion; total LOC is net negative.

Co-authored-by: felirami <6752178+felirami@users.noreply.github.com>

* fix(plugins): retain source rollback until install records commit

Use the existing deferred install transaction across managed sources. Record persistence completion at its owner and remove index/path-inferred compensation, keeping committed payloads intact on late refresh failures.

Co-authored-by: felirami <6752178+felirami@users.noreply.github.com>

* fix(plugins): preserve rollback causes and satisfy lint

Keep both failures in AggregateError and its rollback cause. Document the pinned linter false positive for third-argument options and restore required test braces.

* test(plugins): register intentional AggregateError lint exception

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: felirami <6752178+felirami@users.noreply.github.com>
2026-08-27 01:03:55 -07:00
Peter Steinberger f8b04160ac fix(models): keep OpenCode fallback aligned with catalog lifecycle (#130785) 2026-08-27 00:56:13 -07:00
wangmiao0668000666 bf6e96d46e fix(channels): preserve terminal ingress outcomes on stop (#111347)
Keep completed and deferred delivery outcomes authoritative when shutdown abort races the callback return. This prevents durable ingress from replaying an already completed side effect after restart.

Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-27 13:16:40 +05:30
Peter Steinberger 625f3cf7ea fix(agents): preserve Codex for reasoning capability metadata (#130752)
* fix(agents): preserve Codex for reasoning capability metadata

Keep native-compatible reasoning metadata out of authored transport overrides.
Share effective harness availability across execution, session metadata, and
thinking controls while preserving exact-request fallback and locked ownership.

Fixes #130665.

* fix(agents): keep cold harness registry reads side-effect free
2026-08-27 00:41:18 -07:00
Peter Steinberger e4d602c6f3 fix(worker): keep background servers alive after replies (#130733)
* fix(worker): keep background servers alive between turns

Retain the supervised worker environment while background execs are live,
while recreating turn admission, tools, connections, and authorization for
each turn. Keep physical worker ownership separate from bounded turn
receipts so cancellation, restart recovery, and environment retirement
cannot discard a still-running worker.

Preserve process polling across turns, release turn callbacks, join exec
finalization before environment cleanup, and require exact worker stop
confirmation unless a dedicated provider proves the machine is gone.

Fixes #130450.

* fix(worker): fence revoked placements and verify retained lifetimes

Record provider-loss teardown intent before remote stop, prevent pending cleanup
from being recovered as an active placement, and preserve exact physical ownership
until stop is confirmed. Keep draining state when the provider becomes unavailable.

Remove the ownership/lifecycle import cycle and align workspace-retention proof
with physical teardown. Exercise missing-capability rejection and reconnect on
real Gateway/node wire, preserving scenario and cleanup failures in the harness.

* test(worker): verify durable startup revocation cleanup
2026-08-27 00:35:19 -07:00
Peter Steinberger 1ea2640f54 refactor(state): consolidate wide rows, plugin index, workspace attestations, and shared auth singletons at schema v13 (#130466)
* refactor(state): make cron and subagent rows JSON-canonical

* refactor(state): make gateway origin device tokens canonical at v13

The lazy ensure predates the table joining the canonical schema; at the
v13 bump the schema owns creation, so the feature-local DDL, WeakSet
dedupe, and lazy-list entry retire. The legacy-file guard the ensure
carried stays at each call site.

* test: drop obsolete lazy-ensure coverage for origin device tokens

The table is canonical at v13; same-version lazy creation no longer
exists to protect. Origin CRUD, isolation, and rotation coverage remains
in the surviving cases.

* refactor(state): fold installed_plugin_index into config_machine_state

The singleton index row becomes one JSON value under
plugins.installedIndex with its rollback-fencing revision inside the
value; reads, CAS restore, and the lease-held write transactions use
direct Kysely on config_machine_state so the state_leases assertion
stays in-transaction. The v13 migration imports the row and drops the
table; the additive workspace_dir entry folds with it. Doctor guidance,
docker staging, and the e2e probes name the machine-state row.

* refactor(state): merge workspace_attestations into workspace_setup_state

One row per workspace now carries both setup milestones and the
attestation clock: nullable setup columns represent attestation-only
workspaces (replaceWorkspaceAttestation can precede any setup write) and
setupExists derives from a non-null version. The bootstrap-hash FK
repoints to the merged table; migration receipts keep the historical
workspace_attestations discriminator string. The v13 migration grows and
rebuilds the table, merges attestation rows (orphans without a path
alias drop — their hashes re-derive at the next bootstrap attestation),
and the consolidation kind is renamed state-consolidation-v13 to cover
the batch.

* test(state): cover the workspace merge and consolidation fallout

The v12-to-v13 regression seeds merged, attestation-only, and orphan
attestation workspaces; the 13-to-12 downgrade fixture recreates
workspace_attestations and installed_plugin_index from the folded data;
the fold-in migration gates the additive workspace_dir column for
pre-additive rows; the workspace merge now triggers on the setup table's
own shape so stable-era databases without an attestations table still
reshape; the consolidation applied-message covers the batch.

* refactor(state): fold shared auth profile singletons into config_machine_state

The shared-state auth_profile_stores/auth_profile_state rows (fixed key
'shared') become authProfiles.store/authProfiles.state machine-state
values; the agent-DB tables of the same names are untouched. Git-backup
redaction moves from table-drop to the authProfiles. secret prefix with
seeded-secret absence proof; migration receipts keep the historical
table-name discriminators; the shared-auth relocation and receipt
verification project the KV cells back to the receipt-era row shapes so
persisted digests stay byte-compatible. mcp_oauth_stores stays a table —
its multi-key fold is a named follow-up.

* test(state): finish shared-auth fold coverage and annotate boundary casts

Auth seeders and assertions across the e2e/scripts/secrets suites target
the authProfiles machine-state cells; the v12-to-v13 regression proves
payload-byte fidelity, non-shared-row drop, and insert-if-absent
precedence; the downgrade fixture recreates and repopulates both v12
tables. Boundary type assertions in the plugin-index store carry SAFETY
invariants per the ratchet.

* chore: shrink assertion-safety baseline for plugin-index store

* refactor(doctor): delete the dead onboarding-recommendations migration

Its input — the unscoped 'primary' onboarding row — existed only between
9a93a52a8a and 473962b7de, a two-day beta window; no shipped stable
can produce it and the runtime table folded away at v12. The audit
backup list keeps recognizing system-agent.jsonl artifacts because beta
installs that ran that import may still carry its backups.

* docs: sync the 13-to-12 downgrade example with the executable fixture

* style: format the synced downgrade example

* style: drop unused import and duplicate union constituent

* fix(state): keep orphan attestations across the v13 workspace merge

The merged workspace_setup_state required a workspace path, but legacy
orphan hashed-key attestations never recorded one. workspace_path is now
nullable (setup rows still enforce it via CHECK), the v13 migration and
the doctor file import keep orphans with a NULL path that heals on the
next live access, and the 13-to-12 downgrade keeps attestation-owned
hashes. Doctor test seeds move to the folded KV row.

* perf(state): retire unused cron indexes

* fix(state): preserve v13 migration recovery

* fix(state): preserve v12 lazy-table upgrade

* docs(state): document v13 auth relocation

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-08-27 15:26:14 +08:00
Peter Steinberger 7930940c23 refactor: simplify channel config adapters (#130705)
* refactor: simplify channel config adapters

* refactor(ui): share session-default reading
2026-08-26 22:40:28 -07:00
Peter Steinberger a678ed6565 refactor(video): deduplicate generation contracts (#130635)
* refactor(video): reuse SDK generation types

* refactor(video): keep shared contracts in core type leaf
2026-08-26 22:05:32 -07:00
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
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
Yiğit ERDOĞAN 73b98c4b50 fix(agents): node plugin tool calls report a generic client timeout (#118720)
* fix(agents): node plugin tool calls report a generic client timeout

Plain node-hosted plugin tool calls send no invocation budget, so the Gateway
arms no deadline over pairing, wake, and policy work and the node registry falls
back to its own 30s pending timer that only starts at dispatch. The agent-side
wait defaults to the same 30s but starts earlier, so the caller always abandons
the request first and the Gateway answer that carries nodeCommandDispatched
retry-safety provenance can never arrive.

Send the same 30s budget explicitly and give the caller the +5s grace the node
MCP path already uses, so the Gateway answer wins the race.

* test(gateway): prove the node invoke deadline answer reaches the agent

Adds an end-to-end proof that runs a real Gateway, pairs a real node that
accepts the forwarded command and never answers, and drives the shipped agent
tool through the real gateway client. It asserts the agent receives the
Gateway's structured timeout with nodeCommandDispatched provenance rather than a
client-side gateway timeout.

* fix(gateway): keep node invoke dispatch inside the caller budget

The invocation budget only started once dispatch was reached, so pairing
revalidation before it spent nothing. With the Gateway deadline now armed for
plain node plugin tools, that let a send land after the deadline had already
answered the caller with nodeCommandDispatched:false, advertising a command the
node had in fact received as retry-safe.

Anchor the budget at entry and re-derive what is left after pairing
revalidation, refusing to dispatch once it is exhausted.

* test(gateway): restore the Gateway URL after the invoke deadline proof

The shared Gateway fixture does not snapshot OPENCLAW_GATEWAY_URL, so the
proof left later tests in the same worker pointing at its stopped ephemeral
server. Capture and restore the key, including its previously-unset case.

* fix(gateway): read the node invoke budget after request serialization

Pairing revalidation is not the only step that can spend the caller budget
before the pending timer is armed: tool parameters are unbounded, so
serializing them and resolving the pending system.run event can outlive the
remaining budget on their own.

Reading the budget once all of that work is behind the call site keeps the node
timeout and the pending timer starting from what is actually left, so an
exhausted budget refuses the dispatch instead of arming a timer that answers
after the caller already reported a timeout.

* test(gateway): pin node invoke dispatch provenance against a budget-consuming send

The review asked for the final event envelope to be serialized before the
deadline admission check, on the grounds that a slow serialization could let
the Gateway report nodeCommandDispatched: false for a command the node did
receive. Envelope build, send and onDispatchReady are one synchronous block,
so nothing can settle the pending request between them. Pin that invariant
with a send that spends the whole remaining budget.

* test(gateway): type the node socket send mock with its call signature

The budget-consuming send regression wraps the recorded socket send, and
ReturnType<typeof vi.fn> resolves to a mock without a call signature, so
tsgo rejected the wrapper with TS2348 while vitest ran it fine.

* test(gateway): follow the node pairing helpers into device-pairing-node

main folded src/infra/node-pairing.ts into src/infra/device-pairing-node.ts,
so the deadline proof stopped resolving its pairing import after the merge.

* fix(gateway): scope the invoke deadline to caller-supplied budgets

Callers that omit timeoutMs, such as fs.listDir and the exec-approval
paths, have always used the registry's 30-second fallback as a
post-dispatch pending timer. Anchoring that fallback before pairing
revalidation turned it into a dispatch admission check, so a slow
revalidation could answer TIMEOUT without ever dispatching. Only a
positive caller-supplied budget anchors the deadline now, which keeps
the plain plugin-tool path covered while those callers keep their
established semantics.

* fix(test): import approveDevicePairing from its new module

main split the device pairing lifecycle modules in #124734 and moved
approveDevicePairing to src/infra/device-pairing-approval.ts. Nothing on
either side touched this import line, so the merge stays conflict free
and only the typecheck reports the missing export.

* refactor(gateway): move node invoke deadline budget to a sibling module

node-registry-private.ts sits at 693 counted lines on main against a 700-line
max-lines ratchet, and the inline budget bookkeeping pushed it to 703, which is
what turned check-lint-core-2 red. The budget arithmetic moves to
node-registry.invoke-budget.js unchanged; the call site keeps the same two reads.

* refactor(gateway): move system.run invoke helpers to their sibling module

The max-lines ratchet caps src/gateway/node-registry-private.ts at 700
effective lines and main now sits at 699, so the dispatch budget guard
this branch adds takes the file to 705 and check-lint-core fails.

resolvePendingSystemRunEvent and normalizeSystemRunInvokeParams are
module-private and both already delegate to normalizeSystemRunTimeoutMs
in node-registry.system-run.ts, so that module is where they belong.
Pure move: no behavior change and no new export surface.

* fix(gateway): keep the moved system.run helpers within the ratchet baselines

The previous commit moved resolvePendingSystemRunEvent and
normalizeSystemRunInvokeParams into node-registry.system-run.ts. It changed no
behavior, but it did move three per-file measurements.

normalizeSystemRunTimeoutMs was exported because node-registry-private.ts
consumed it; the two callers that moved were its only external consumers, so
after the move the export had none and Knip reported it as an unused production
export. It is module-private now.

The moved code also carried two `as Record<string, unknown>` assertions that
were grandfathered under the old file. The new file's baseline is 0, so both
now carry the // SAFETY: invariant the ratchet asks for, and the source file's
baseline entry is shrunk from 3 to 1 as the ratchet's own message requested.

* test(gateway): fix typed node invoke serialization fixture

Close over the typed execution payload instead of reading contextual this
inside toJSON, whose surrounding invoke params contract is unknown.
Keep the serialization delay and independent execution-budget assertions.

The 141 registry tests and focused canonical-inherited test type graph pass.

Co-authored-by: Yigtwxx <yigiterdogan023@gmail.com>

* docs(changelog): avoid node deadline entry merge conflict

Move only this PR's credited release-note entry beside the existing Gateway
client watchdog entry, away from the concurrently edited section boundary.
The entry and tested runtime behavior are unchanged.

Co-authored-by: Yigtwxx <yigiterdogan023@gmail.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-26 20:47:01 -07:00
Mike 8c1a55d446 fix(openai-responses): reconcile terminal tool calls (#108461)
* fix(ai): reconcile terminal Responses tool calls

Recover validated terminal tool calls through one completion owner when item-done events are missing. Reuse positional completion state, preserve exact arguments, and reject ambiguous or conflicting batches before tool execution. Also remove the shared empty identity alias for anonymous calls.

Fixes #108460. Builds on #122560 and preserves the original contribution from #108461.

Co-authored-by: snotty <snotty@users.noreply.github.com>

* refactor(ai): type terminal tool scratch state directly

* fix(ai): retain anonymous tool completion ownership

* chore(ai): prune resolved assertion baseline

* test(ai): deduplicate generated tool identity coverage

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: snotty <snotty@users.noreply.github.com>
2026-08-26 20:39:37 -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
Vito Cappello fc7031baae fix(code-mode): avoid repair mode for invalid nested calls (#130478)
* fix(code-mode): restore nested input validation

* fix(code-mode): trust exact read-only dispatches

* test(code-mode): prove nested recovery boundaries

* test(code-mode): split headless validation coverage

* fix(code-mode): preserve guest network errors during validation

* fix(code-mode): preserve wrapped network errors

* fix(code-mode): keep side-effecting plugins restricted

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-26 23:22:01 -04:00
Vincent Koc a87fb08e70 refactor(lobster): unify managed flow parameter parsing (#129540) 2026-08-27 10:58:06 +08:00
Peter Steinberger 850c85887a refactor(memory): remove unused private REM facade (#130594) 2026-08-26 19:49:27 -07:00
Peter Steinberger f05a7dde19 refactor(models): prepare model-list context once (#130600)
* refactor(models): prepare model-list context once

* test(models): consolidate model-list row contexts
2026-08-26 19:48:22 -07:00
Peter Steinberger bbd66c475e fix(plugins): fail closed when plugin runtime is unavailable during registration (#130597)
* fix(plugins): fail closed when plugin runtime is unavailable

Core fabricated an empty object as `PluginRuntime` for the `cli-metadata`
and `setup-only` registration passes, so any plugin touching `api.runtime`
during `register()` died with an opaque
`TypeError: Cannot read properties of undefined (reading 'openSyncKeyedStore')`
that named neither the contract nor the mode.

Replace those fabrications with one shared fail-closed runtime that throws a
named, actionable error identifying the plugin and registration mode. Symbol
reads stay inert so inspection and reflection cannot trigger the guard.

Also resolve the lightweight `cli-metadata` entry beside the resolved plugin
entry, not only at the package root. Published plugin packages emit it at
`dist/cli-metadata.js`, so the probe always missed and the loader fell back to
executing the full heavy entry for CLI metadata collection.

Documents which registration modes expose a live `api.runtime`; the SDK
reference previously claimed it was injected into every plugin.

* fix(ui): restore sidebar lazy import boundaries

Remove the redundant viewer-facepile dynamic import: sidebar consumers
already register that element through the static graph.

Import pet data and sprite helpers from their owning leaf modules, delete
the registration module's re-export barrel, and make the element itself
the lobster-pet.runtime.ts lazy boundary. Keep shared sprite styles with
the look renderer so standalone pages do not need element registration.

Regenerate the measured boot inventory with the canonical browser probe.
Use Lit's typed lifecycle map to remove the obsolete assertion baseline
entry and keep the now-private facepile variant type local.

The full build has no INEFFECTIVE_DYNAMIC_IMPORT warnings. Relevant UI
coverage passes (503 tests plus 5 Chromium E2E tests); the unchanged plugin
runtime regression suites pass all 58 tests. The broad plugin suite still
reports schema-version, doctor-closure, hook-process, and catalog failures.
2026-08-26 19:45:39 -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
xiaobao-k8s c756161862 fix(agent-core): preserve supported reasoning off (#120020)
Preserve supported explicit and default Agent reasoning off through the
managed transport handoff. Retain unsupported-off transport defaults,
model-owned enabled fallbacks, and native Anthropic exceptions. Replace the
single-use Set and casting guard with direct switch narrowing.

Proved with real Agent-to-embedded-to-managed HTTP/SSE regressions and
provider sibling coverage, plus actual Qwen inference through Ollama's
OpenAI-compatible reasoning-effort dialect. Related: #119959; the full
reported vLLM configuration remains unverified and that issue remains open.

This metadata-only replacement retains the exact reviewed and tested tree.
It removes an obsolete closing reference from the earlier commit messages
without changing any source, test, or baseline content.
2026-08-26 19:21:22 -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 67d22a58ac refactor(embeddings): unify provider contract (#130506)
* refactor(embeddings): unify provider contract

* test(plugins): declare embedding type bridge

* test(memory): migrate embedding fixtures
2026-08-26 18:20:48 -07:00
Peter Steinberger 162e8d1394 refactor(plugins): derive private config types from schemas (#130414)
* refactor(plugins): derive private config types from schemas

* test(ci): make directory binding proof deterministic

Replace race-prone child-cwd polling with a synchronous fixture retarget after the real chdir, preserving the full worker and security assertions. Shrink the assertion-safety baseline for the three casts removed by the schema cleanup.
2026-08-26 17:21:24 -07:00
Josh Avant 29625bc310 feat(audit): record generic tool action decisions (#130358)
* feat(audit): record generic tool action decisions

* fix(audit): bind OpenClaw action descriptors

* fix(audit): keep decision routing private

* fix(audit): avoid duplicate void-hook decisions

* fix(audit): scope plugin hook ownership

* docs(agents): preserve decision audit FIFO ownership
2026-08-26 17:13:52 -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
Peter Steinberger 5153f25495 fix(memory): prevent forgotten session content from returning (#130451) 2026-08-26 16:26:00 -07:00
Peter Steinberger 95161c2335 fix(workers): protect in-flight workspace manifests from concurrent retention (#130449)
At high completion concurrency, 12/50 turns failed with 'Cloud worker
finished, but its workspace result could not be reconciled' (#129979):
between a worker's manifest upload and its verification, a sibling
completion could trigger retention with a snapshot holding only the old
base manifest — the terminal worker no longer protected the workspace, so
cleanup deleted the just-uploaded manifest and verification died on ENOENT.
The node workspace runtime now pins the latest transferred manifest per
generation and includes it in every cleanup's reachable set (cleared with
its generation), and gateway retention snapshots treat unsettled results —
an active claim or a pending-result fence — as protection, which also
preserves it across restarts when node-local pins are gone. Capture errors
retain bounded redacted stderr so this failure class can never be
faceless again. No deadlines, protocol, or completion-semantics changes;
deterministic 1-turn and 50-turn reproductions fail pre-fix.
2026-08-26 16:22:52 -07:00
Peter Steinberger a0791fd289 refactor(models): consolidate config normalization (#130439) 2026-08-26 15:14:01 -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 1f60d9eca2 feat(feishu): let agents resend received stickers (#130374)
* feat(feishu): resend received stickers without losing topic routing

Add opt-in agent sticker sending using received file keys, preserve inbound keys, and stop unsupported sticker resource downloads. Reuse keyed-media delivery and canonical reply selection; inherit source topics only within the same destination and account. Related: #114578.

* fix(feishu): preserve topic mode for prepared implicit replies

* docs(feishu): preserve account action gates when enabling stickers
2026-08-26 15:01:37 -07:00
Peter Steinberger 9afd058d23 refactor(models): prepare selection policy once (#130413)
* refactor(models): prepare selection policy once

* style(models): fix selection exports and return consistency
2026-08-26 15:00:00 -07:00
Peter Steinberger 86233c34a9 fix(browser): response body reads outlive their timeout (#130386)
* fix(browser): bound response body reads through completion

* test(browser): use public response test types
2026-08-26 14:18:49 -07:00
Peter Steinberger 1fd3c071d8 refactor(workboard): centralize dispatch owner selection (#130333)
Use one slot-owner policy for ready candidates and occupied workers while preserving heartbeat-reclaim grace. Remove the unused private KV store factory; doctor remains the legacy migration owner.
2026-08-26 13:34:34 -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 281464f824 fix(usage): prevent model identity collisions (#130307) 2026-08-26 13:13:07 -07:00
Peter Steinberger c3ea9775ca feat(portals): expose portals to sessions on node-backed cloud workers (#130105)
* feat(portals): expose portals to sessions on node-backed cloud workers

Worker-turn sessions placed on cloud workers (bundled Crabbox) can now open
portals. The portal tool joins the closed worker session-tool catalog and
executes on the Gateway, which mints a single-use ticket per proxied
connection; the enrolled node redeems it over a TLS-pinned WebSocket via the
new private worker.portal.stream.v1 command and splices to the worker's
loopback port. The desktop stream broker is generalized with discriminated
desktop/portal tickets, and shared node stream transport is extracted from
the desktop stream command. Portal availability is negotiated via a new
gateway hello capability plus the node's reconnect-scoped
workerHost.portalStream declaration, so old node bundles simply do not
receive the tool. Environment teardown and owner-epoch replacement close a
worker's portals; node disconnects destroy live streams while the listener
keeps serving the waiting page.

Live-proven on a Crabbox AWS worker: agent opened a portal over the worker
protocol, python3 -m http.server on the box served through the Gateway
portal URL with token auth intact.

* fix(portals): break portal proxy/service type cycle; regenerate Swift protocol models

* fix(portals): drop dead PortalTarget re-export; fix test import

* fix(portals): only tear down turn-created portals on post-open authority loss

ClawSweeper finding: a revoked turn's duplicate open could close the live
portal a still-authorized predecessor established, because service.open
returns the existing same-environment portal and the executor's error path
closed whatever id it received. portal.open now reports created-vs-reused;
the executor tears down only portals it created and closes its redundant
carrier handle whenever an existing portal is reused.

* feat(portals): fence portal reuse behind the caller's live-authority assert

Rank-up move from review: portal.open revalidates the caller inside the
serialized operation before a reuse can mutate a live portal's metadata,
mirroring close(assertCurrent). Regression test proves a lapsed caller
cannot rewrite a reused portal's title.

* test(portals): expect assertCurrent in worker portal open call

* refactor(gateway): extract worker request dispatch into its own module

worker-connection.ts crossed the 700-line ratchet after rebasing onto
current main; dispatchWorkerRequest and its rejection helper move to
worker-connection-dispatch.ts unchanged (434 + 329 lines).
2026-08-26 12:51:48 -07:00