Commit Graph

897 Commits

Author SHA1 Message Date
Peter Steinberger ce0fafefce fix(tooling): avoid build and test startup stalls on shared tsx caches (#130924)
* fix(tooling): avoid shared tsx cache startup stalls

Use the shared preloader before tsx initializes so maintained build, check, and test commands retain memory transforms without scanning other checkouts' disk caches. Preserve changed-cwd forks and copied tooling closures.

* test: align command contracts with tooling bootstrap
2026-08-27 10:36:12 -07:00
Peter Steinberger ff3cf3de3b docs(start): correct headless credential handoff to the SQLite auth store (#131024) 2026-08-27 09:40:27 -07:00
Peter Steinberger 52750a2a65 fix(agents): restore direct-command proactive compaction (#130863)
Share usage-based maintenance with successful built-in command turns while
preserving diagnostic-only pressure estimates and native compaction ownership.
Carry the winning auth profile privately, fence session accounting, and retain
completed local replies when ordinary post-turn summarization fails.

Cover threshold, delivery, cancellation, successor, and routing boundaries;
clarify the documented scheduling behavior without adding configuration.

Closes #130716
2026-08-27 03:27:56 -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
Vincent Koc d62cb8aa07 fix(sqlite): safely discard retired commitments state (#130629)
* fix(sqlite): finalize commitments retirement

* fix(doctor): discard retired commitments json
2026-08-27 11:18:58 +08:00
Peter Steinberger 5153f25495 fix(memory): prevent forgotten session content from returning (#130451) 2026-08-26 16:26:00 -07:00
Peter Steinberger dcb20658fc fix(openshell): preserve file writes during concurrent mirror commands (#130447)
Lease complete mirror file operations through the workspace owner instead of locking only remote transport. Preserve successful writes across exec publication and share serialization across backend handles.

Proven with a real Docker red/green reproduction, 128-workflow mirror/remote stress matrix, exact inventories, failure recovery, 170 focused tests, and updated operator docs. Related: #127441; follow-up to #130031.
2026-08-26 15:59:31 -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 87b56458a1 feat(plugins): inbound IMAP email trigger (#130230)
* feat(plugin-sdk): add in-process hook agent dispatch seam

* feat(plugins): add imap inbound email trigger plugin

* fix(imap): coalesce sweep wakeups and drop test-only gate exports

* docs(imap): fix agents bindings command invocation

* fix(gateway): guard optional bound context in plugin hook dispatch

Post-rebase drift made resolveBoundGatewayContext possibly-undefined; the
plugin hook dispatch path invoked it unconditionally. Use the optional-call
guard consistent with sibling runtime accessors.

* fix(imap): bump mailparser to 3.9.16 off vulnerable deepmerge-ts

mailparser 3.9.15 pinned html-to-text@10.0.0 -> deepmerge-ts@7.1.6 (GHSA
HIGH, stack exhaustion, <8.0.0). 3.9.16 pins html-to-text@10.0.1 ->
deepmerge-ts@8.0.2, clearing the production audit gate.

* refactor(gateway): extract plugin hooks runtime to keep server-plugins under max-lines

The dispatchHookAgentTurn seam pushed server-plugins.ts to 702 effective lines
(cap 700). Extract createGatewayHooksRuntime into the sibling runtime-helpers
module, mirroring createGatewayNodesRuntime/createGatewaySubagentRuntime; no
suppression added.

* docs(secrets): register imap plugin password in secretref credential matrix

The bundled imap plugin declares a secretInput (accounts.*.password); regenerate
the user-supplied credentials matrix and surface list so the target-registry docs
sync test stays green.
2026-08-26 12:24:02 -07:00
Peter Steinberger 8e39f58365 docs(memory): add memory provenance and deletion concepts page (#130278)
* docs(memory): add memory provenance and deletion concepts page

New concepts/memory-provenance page tells the operator policy story end to end: recorded session lineage through consolidation, the admission policy and its pipeline-only boundary, memory forget guarantees (whole-entry purge, derived-artifact sweep, forgotten tombstones) and reported boundaries (transcripts, curated writes, paraphrased prose), plus the person/source purge workflow. Cross-linked from memory architecture, dreaming, builtin, overview, CLI, and config reference; registered in docs navigation.

* docs(i18n): add glossary terms for the memory provenance page

* docs(memory): distinguish curated entries from missing lineage in the provenance page
2026-08-26 11:36:39 -07:00
Peter Steinberger 9d9dc31275 feat(memory): session provenance, admission policy, and openclaw memory forget (#130151)
* feat(state): add memory entry origins and session tombstone tables with session-aware write provenance

Additive same-version per-agent tables (memory_entry_origins, memory_session_tombstones) declared canonically with lazy idempotent ensure; read-only agent DB access exposed through the SDK; memory-write observers and the session-memory hook record the authoring session; archived narrative transcripts classified by authoritative run identity.

* feat(memory): admission policy, entry provenance, and openclaw memory forget

memoryPolicy.excludeSessions keeps configured sources out of the dreaming pipeline with recorded exclusions; promotion writes are marker-addressable and carry per-session origins through consolidation; memory forget purges whole entries and every derived artifact (corpus lines, diary quotes, index/FTS/vec/cache, short-term state, backups), reports curated agent writes from transcripts, and tombstones purged sessions so ingestion, backfill, and transcript indexing never re-admit them.

* docs(memory): document admission policy, memory forget, and provenance boundaries

* fix(memory): open the vec probe through openNodeSqliteDatabase

Kysely guardrail forbids direct node:sqlite constructors in production; the in-memory extension probe now uses the canonical opener.

* fix(memory): resolve archived and explicit sessions in memory forget

Explicit --session selectors union live session windows, transcript archives, and exact unresolved ids so an operator-named session always purges and tombstones instead of silently no-oping after lifecycle archival; reports carry per-session resolution sources. ClawSweeper P1 on #130151.

* fix(memory): reconcile consolidation origins for every workspace agent

Shared workspaces record entry origins per source agent; consolidation now re-keys and prunes origins in each participating agent's database (membership from the authoritative dreaming workspace mapping), so a non-owner agent's later memory forget still finds the live consolidated entry. ClawSweeper re-review P1 on #130151.
2026-08-26 10:57:04 -07:00
RoboClaw 44d60a29e6 fix: preserve unread reminder for open sessions (#129386)
* fix: preserve manual unread markers in open sessions

* fix: distinguish explicit session reads

* fix(ui): gate unread contract on gateway capability

* perf(ui): keep server capabilities out of startup bundle

* test(gateway): keep agent fixtures roster-consistent

* fix(sessions): preserve legacy read compatibility

* test(gateway): type agent fixture configs

* fix(ui): remove unread gateway fallback

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

* test(infra): avoid fixed SSH tunnel port

* fix(ui): acknowledge unread after history commit

* docs: clarify unread upgrade boundary

* test(ui): drive mobile session menu by tap

* fix(ios): remove stale read reconciliation call

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-26 05:04:39 -07:00
Peter Steinberger 49d916e8e9 fix(memory): preserve session recall when upgrading from QMD (#130016)
* fix(memory): preserve QMD session search across builtin migration

* fix(memory): migrate agent defaults to canonical memory owner
2026-08-26 03:18:42 -07:00
Peter Steinberger 4bd8859126 fix(skills): record skill usage again and retire dead curator tables (#129769)
* fix(scripts): see SAFETY comments after template substitutions

The assertion ratchet scanned each file with a raw ts.createScanner, which
reads the `}` closing a template-literal substitution as a block close. The
scanner desynced there, so every `// SAFETY:` comment after a file's first
`${...}` was invisible and its annotated assertions were counted as bare.

Track substitution brace depth and rescan the closing brace the way the parser
does. The fix uncovers already-annotated assertions in eight files, so the
baseline shrinks accordingly.

* fix(skills): record skill usage again and retire dead curator tables

Skill lifecycle curation shipped in 2026.7.1 with two producers: a trusted
`skill.used` consumer writing `skill_usage`, and a daily sweep aging skills
into `skill_lifecycle`. The weekly collection review (#121653) replaced that
policy and deleted both producers, but left every reader in place. Since then
`skill_usage` has had no writer, so curator status reported `lastUsedAtMs:
null` and `useCount: 0` forever, and `skill_lifecycle` had no writer either, so
pin/unpin/restore either threw "not found" on fresh installs or, worse,
"succeeded" on upgraded ones while gating nothing at all.
`skill_workshop_proposal_origin_runs` was never read in any revision; proposal
provenance is authoritative in `record_json`.

Restore the usage producer at its owner and retire what has no owner:

- `skill.used` events populate `skill_usage` again, registered with the
  collection-review maintenance it belongs beside. Curator status derives
  curated skills from applied Workshop create proposals and reports real usage.
- Weekly review receives bounded `useCount` / `lastUsedDaysAgo` evidence, with
  prompt text stating usage supports keeping a procedure and never alone
  justifies a drop.
- State schema 10 drops `skill_lifecycle` and
  `skill_workshop_proposal_origin_runs`. Previously archived skills return to
  the active collection, where review judges them by content; the migration
  logs how many. Reconcile now clears usage only for skills it actually drops.
- `skills.curator.pin`/`unpin`/`restore` stay registered for existing clients
  but fail with an explicit retirement message instead of silently doing
  nothing.

Retirement code moves to openclaw-state-db-table-retirements.ts to keep the
schema-repair module under max-lines; that split is a pure relocation.

Production delta is +23 raw: about -46 for the change itself, +44 for the file
split, +25 for the ratchet fix in the previous commit.
2026-08-26 01:06:06 -07:00
Peter Steinberger 0229fe7a2a improve: verify packaged runtime on Bun 1.4 (#129552)
* test: exercise packaged OpenClaw under Bun

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-66db-71f0-91e7-1578b383afb2

* docs: align Bun runtime guidance

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b7-66db-71f0-91e7-1578b383afb2

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-25 23:41:05 -07:00
Peter Steinberger 3d0df74b0f fix(openshell): sandbox cleanup, workspace sync, and gateway setup failures (#129641)
* fix(openshell): harden sandbox lifecycle and workspace sync

* fix(openshell): preserve shipped workspace root layouts

* test(gateway): reset shared agent harness state between cases

* test(qa): measure channel health startup grace directly

* chore(release): leave changelog updates to release automation
2026-08-25 18:08:55 -07:00
Peter Steinberger 6a246f70d1 refactor(state): retire six dead shared-state tables at schema v10 (#129626)
* refactor(state): retire six dead shared-state tables at schema v10

agent_model_catalogs, android_notification_recent_packages,
command_log_entries, diagnostic_stability_bundles, media_blobs, and
model_capability_cache landed with the database-first squash but their
runtime writers never reached main; every stable since v2026.6.10 created
them empty (agent_model_catalogs held only rebuildable catalog cache rows
until #111173 removed its writer). State schema 10 drops all six tables
and seven indexes through both the runtime-open and doctor migration
paths, records the retirements, bumps the native reader ceiling, and
corrects stale database-first doc claims that still named these tables
as canonical stores.

* test: move cross-lane schema-version pins to v10

The v10 retirement missed current-version pins outside src/state: the
native guard vitest wrapper, placement-move and node-worker-launch
same-version assertions, and the audit outbound-progress tripwire. The
pinned pre-C04 audit reader is a v9-era build that now refuses v10
databases by the version contract, so the test projects the file back to
the exact v9 shape with the documented 10-to-9 downgrade fixture before
the reader proof; the shared fixture also seeds the v10 retirement
regression.

* test: keep only the used downgrade fixture export
2026-08-25 17:31:32 -07:00
Peter Steinberger 6a1dedb00c refactor(agents): remove redundant heartbeat system prompts (#129642)
* refactor(agents): remove redundant heartbeat system prompts

* test(agents): align heartbeat cleanup checks and prompt snapshots
2026-08-25 16:10:22 -07:00
Peter Steinberger 9b7c95a0f0 fix(anthropic): keep automated heartbeats on subscription usage (#129513)
* fix(anthropic): keep automated heartbeats on subscription usage

* test(agents): align retired heartbeat template expectations

* fix(anthropic): avoid heavyweight policy bootstrap imports

* chore(config): prune retired template assertion baseline

* test(vitest): cover Codex startup test family

* refactor(anthropic): remove obsolete thinking-profile re-export
2026-08-25 14:11:38 -07:00
Peter Steinberger 72b06b0077 fix(release): keep polling before decision artifacts exist (#129166)
Handle GitHub's current missing-artifact message without abandoning active full validation, while preserving early blocker decisions. Covers #129104.
2026-08-25 01:55:19 -07:00
Peter Steinberger a556d5379b fix(google): honor supported Gemini embedding dimensions (#129038) 2026-08-24 23:39:35 -07:00
Martin Cleary 7f24f54214 docs: require discussion for material SQLite changes (#128876) 2026-08-24 23:53:14 +01:00
Peter Steinberger e30279f89f fix(google): support stable Gemini Embedding 2 request contracts (#128716)
* fix(memory): support stable Gemini Embedding 2

* fix(memory): validate async Gemini batch dimensions

* docs(memory): explain stable Gemini index rebuild

* docs(memory): cover explicit-dimension rebuilds

* fix(google): honor stable Gemini embedding task contracts

Co-authored-by: Franck MEYER <meyerfranckpro@gmail.com>

---------

Co-authored-by: Codex OpenClaw Migration <noreply@local>
Co-authored-by: Franck MEYER <meyerfranckpro@gmail.com>
2026-08-24 05:38:54 -07:00
Peter Steinberger 554fb212c9 fix(nodes): report camera positions the hardware actually reached (#128595)
* fix(nodes): report camera positions the hardware actually reached

`camera.ptz.control` returned a position it never verified, and
`camera.snap`/`camera.clip` could capture from a camera the caller did
not ask for. Both told the agent an action succeeded when it had not.

PTZ read its post-write status from the same UVC connection that issued
the write. Gimbal cameras echo a pending setpoint back on the writing
connection, so the check confirmed its own write. Those cameras also
service camera-terminal controls only while a video stream is active, and
no capture session was held, so writes could be discarded entirely while
reads returned phantom values.

Hold a frame-discarding capture session across every PTZ operation, close
the writing controller, and verify through a fresh connection against each
axis's advertised resolution. An axis that misses now reports through the
existing CAMERA_PTZ_PARTIAL outcome with observed versus requested values
and what to check next.

Apple camera selection accepted an explicit deviceId and silently fell
back to the default camera when nothing matched. Linux already rejected
this, and CameraPTZService already rejected it in the same app. Centralize
exact selection in OpenClawKit so macOS and iOS both fail with a
device-not-found error; the facing/default fallback stays only for
requests that supply no deviceId.

camera.ptz.status now activates the camera and its privacy indicator for
the duration of the read. That is the cost of returning real positions.

* fix(nodes): tell callers how to recover from an unknown camera ID

Device IDs change when cameras are reconnected, so a bare
device-not-found error dead-ends the caller. Both Apple errors and the
docs now point at camera.list for current IDs.

Addresses the ClawSweeper P2 finding on #128595.
2026-08-24 01:56:12 -07:00
Peter Steinberger 53dcaaedec feat(gateway): generic operator roles for non-maintainer access (#128548)
* feat(gateway): generic operator roles for non-maintainer access

Adds gateway.roles: named role bundles over a closed capability set —
sessions.others (none/view/suggest/write), an agents allowlist, and an
operator-scope ceiling. Roles are person-level (additive user_profiles.role
column, SQLite stays at v9); users.setRole (admin-only) assigns them. With
no gateway.roles config, behavior is unchanged for solo deployments.

Enforcement is deny-by-default from a host-minted actor identity
(system vs operator+profileId on server-only client.internal, never
accepted from the wire) and covers every entry point: WS RPCs, OpenAI-compat
and Responses HTTP, tools invocation, cron, questions, usage, task
suggestions, session catalog/sharing/reads. The agents allowlist gates both
session creation and run-start on existing sessions. Subagent completion
announce and descendant wake mint explicit system authority so role
boundaries never silently drop parent notifications.

The enforcement surface is expressed through a narrow policy vocabulary
(operatorSessionCap, hasOperatorBoundary, authorizeSessionSharing) rather
than per-handler policy internals.

* fix(gateway): heal PR CI after rebase onto main

- Break import cycles: extract GatewayOperatorRoleActor leaf contract; merge
  session-group-mutation-targets into session-sharing-target-input.
- Split sessions-suggestions.test.ts (max-lines) into a visibility suite.
- Add users.setRole to the 2026.8 train registry test and regenerate the
  Kotlin protocol client.
- Startup UNAVAILABLE gating now precedes session authorization: session
  stores are not loaded during startup, so authorization reads would deny
  with a misleading non-retryable error.
- sessions.assignOwner keeps its documented visibility-authorized contract
  when no operator role caps the caller; view/suggest-capped roles still
  cannot reassign foreign session ownership.
- Test stubs updated for main's socket readyState guard (#128144) and the
  system-authority arg on channel-native resets.

* test(gateway): chat.send pending-profile dispatch carries its required session target

chat.send requires a non-empty sessionKey at the protocol level; the mutation
pipeline now rejects targetless frames before profile-dependent dispatch, so
the pending-profile test must send a realistic frame.
2026-08-24 01:20:03 -07:00
Vincent Koc 1696ff97ef fix(ci): settle Claude cache probe before generation baseline (#128507)
* test(gateway): settle Claude cache probe before baseline

* docs(test): describe Claude cache settlement probe
2026-08-23 23:18:58 -07:00
Peter Steinberger 8083d4dd3f fix(memory): enforce canonical SecretRef resolution (#127699)
* fix(memory): enforce canonical SecretRef resolution

Make Gateway runtime snapshots the exclusive owner of memory SecretRef materialization. Bind embedding credentials and headers to provider-owned destinations, and fence per-agent stale reuse by the provider destination/auth contract.

Release note: Memory search resolves secret references through configured provider policy and keeps embedding credentials scoped to their intended destination.

* fix(lmstudio): preserve resolved memory headers

memory remote headers are already materialized by the Gateway snapshot and now bypass SecretRef re-resolution; provider-owned headers retain canonical resolution; final loopback request proof covers literal preservation and precedence.

* fix(memory): bind stale credentials to auth owners

Resolve memory adapter credential owners from snapshot manifest metadata, conservatively fail cold when metadata is absent, and prove Gemini/Google destination changes plus zero-egress unresolved refs.

* fix(memory): scope compatible embedding credentials

Apply destination ownership to the core compatible adapter while preserving destination-owned credentials and intentionally unauthenticated endpoints. Distinguish loopback principals, consolidate duplicate security tests, and verify the final credential boundary through a live isolated Gateway request.

* test(memory): align destination auth precedence

* fix(memory): bind credentials to query identity

Include URL query parameters in embedding destination ownership so provider credentials and headers never cross tenant boundaries.

* fix(memory): preserve query-bound embedding destinations
2026-08-23 20:36:55 -07:00
Dallin Romney 53fbe2eb33 fix(release): preserve validation plan across reruns (#127343)
* fix(release): preserve validation plan across reruns

* test(release): align rerun plan assertions

* refactor(release): use canonical plan cache action

* style(test): format release plan cache assertion
2026-08-21 19:08:32 -07:00
Peter Steinberger 7909ea6983 ci: remove the Test Performance Agent
The workflow ran a Codex agent over the full test suite and pushed
`test: optimize slow tests` straight to `main` under `contents: write`, with no
pull request and no human review. Its gates were a path allowlist, a
no-add/delete/rename rule, a non-decreasing total test count, and
`pnpm check:changed` -- which covers changed lanes, not the full suite. Test
optimization is exactly the class of change where a plausible edit can weaken
coverage without moving the test count, so unattended landing is the wrong
trade. Autonomous commits to `main` are not something this repo wants.

It had also been inert since well before this. The daily-cadence gate excluded
prior runs with `select(.status != "cancelled")`, but a finished cancelled run
reports `status: "completed"` with `conclusion: "cancelled"` -- verified against
run 32506655531, which that filter counts as a prior run. Its `concurrency`
block sets `cancel-in-progress: false`, so main's push rate produced dozens of
cancelled runs per hour and every trigger skipped, reporting green after ~2
minutes of doing nothing. No `test: optimize slow tests` commit has ever landed
on `main`.

`pnpm test:perf:groups` and the rest of the performance tooling it drove stay;
they are useful by hand and documented in docs/reference/test.md.

Repository secret OPENCLAW_TEST_PERF_AGENT_OPENAI_API_KEY now has no consumer
and can be deleted.
2026-08-21 13:19:14 -07:00
Josh Avant 0b85966072 fix gateway conversation route ownership (#126424) 2026-08-21 12:20:21 -07:00
Vincent Koc a2f0b84e8a fix(release): report blockers before diagnostics finish (#127014)
* fix(release): separate decisions from diagnostic drain

* test(release): align decision drain fixtures

* fix(release): pin artifact downloads to valid v8 SHA

* fix(release): default empty evidence paths in plans

* fix(release): bind complete evidence reuse selection

* fix(release): harden retry artifact integrity

* fix(release): canonicalize reused validation evidence

* fix(release): bind manifest children to execution plan

* test(release): fix validation fixture types

* fix(release): retry transient decision artifact reads
2026-08-21 09:14:39 -07:00
Vincent Koc fa86caf94f fix(release): keep protected tooling trusted after main moves (#126881)
* fix(release): keep protected tooling trusted after main moves

* fix(release): cover protected tooling recovery paths

* fix(release): honor live tooling contracts

* fix(release): revalidate tooling at npm publish

* fix(release): bind npm publishers to live tooling

* fix(release): preserve trusted dispatch identity

* fix(release): revalidate parent authorization

* fix(release): bind ClawHub to release parent

* docs(release): define frozen tooling identity

* test(release): align ClawHub protected dispatch ref

* fix(release): trust protected plugin npm preflight tooling

* docs(release): scope protected writer guarantees

* fix(release): keep protected tooling foundation npm-only

* test(release): cover trusted npm preflight tooling
2026-08-21 07:24:31 +00:00
Vincent Koc 750f2f3762 fix(release): require concrete validation retry groups (#127012)
* fix(release): require concrete validation retry groups

* fix(release): reject mismatched retry filters

* fix(release): align retry controller vocabulary

* fix(release): preserve historical validation evidence

* fix(release): validate retry filters before scheduling

* test(release): follow shared filter validator

* docs(testing): clarify release QA retry groups
2026-08-20 23:35:16 -07:00
Bek e2a48d4b70 fix(cron): prune terminal task history after seven days (#126095) 2026-08-21 00:10:11 -04:00
Peter Steinberger 94f042ba86 fix(nostr): retain SecretRef-backed accounts (#126934) 2026-08-20 19:14:08 -07:00
Peter Steinberger bbbd70542b feat(sessions): recover offline device placements (#126284)
* feat(sessions): recover offline device placements

* chore(protocol): refresh session placement models

* perf(ui): lazy-load session placement recovery

* test(ui): remove dropdown timing assertion

* test(ui): await committed cloud recovery route

* fix(sessions): complete explicit abandonment locally

* fix(sessions): fence lists by runner availability

* fix(ui): preserve canonical session freshness

* fix(sessions): preserve recovery contracts after rebase

* test(ui): await durable cloud recovery entry

* test(gateway): complete current runner fixtures

* fix(gateway): publish runner availability edges

* fix(ui): preserve shared session freshness

* fix(ui): preserve canonical sidebar session state

* fix(sessions): preserve abandoned partials and run-owned replies

* fix(sessions): resume durable abandonment retries

* test(gateway): compose provisioning replay with runner availability

* fix(sessions): publish recovered move transitions
2026-08-20 09:59:59 -07:00
Vincent Koc c28c279afa fix(release): keep frozen validation independent of main (#126622)
* fix(release): freeze validation tooling identity

* fix(release): enforce frozen validation contract

* fix(release): validate candidate identity in parent

* fix(ci): close release isolation gate findings
2026-08-20 04:32:38 -07:00
Peter Steinberger 147edf47f3 fix(auth): keep a retired auth JSON from stranding a migrated store (#126562)
* fix(auth): keep a retired auth JSON from stranding a migrated store

Runtime failed closed with AUTH_PROFILE_MIGRATION_REQUIRED whenever a retired
credential file was present, even when the canonical SQLite store already held
the agent's profiles. One leftover auth.json therefore made a fully migrated
install unusable, and the gateway lifecycle preflight refused start/restart on
top of it, so every channel and provider stayed offline until Doctor ran.

A legacy file is now only fatal when the canonical store cannot serve
credentials. Doctor's importer never overwrites a usable stored credential, so
a file sitting beside a populated store is unarchived bytes, not pending
migration: runtime logs a one-time warning and keeps serving. An empty store
with a credential file still fails closed and never falls through to
environment auth. Startup degrades that owner to configured-unavailable
instead of refusing to boot, which lets the lifecycle preflight go away.

* refactor(secrets): retire the auth-profiles.json vocabulary

Auth profiles moved to SQLite, but operator-facing surfaces still named the
retired JSON file. The duplicate-agentDir error told operators to copy
auth-profiles.json to share credentials, which does nothing and lands the
second agent in a migration-required state; `openclaw migrate plan codex`
reported a target file that is never created; and the secrets picker labelled
candidates with a filename that no longer exists.

Renames the SecretTargetConfigFile discriminator to "auth-profile-store" and
corrects the operator-facing text, the migrate plan target, and the docs that
described the file as a live target. Genuine legacy-filename uses in doctor,
the security fixer, and migration fixtures are unchanged.

Also deletes resolveSecretPlanTargetByPath and ResolvedSecretPlanTarget from
the plugin SDK. They have no callers in core, plugins, or tests, and the
symbols are absent from the latest stable tag, so they carry no compatibility
obligation and are removed rather than deprecated. Their inline parameter type
was the only thing putting the retired filename on the public SDK surface.

* improve(wizard): warn about device-code phishing

The device-code prompt only warned against sharing the code, and only when an
expiry was known. Device-code phishing works the other way around: the attacker
starts the login and gets the victim to enter the attacker's code. Codes
delivered over a chat channel are the risky case and carry no expiry hint, so
the warning is now unconditional and covers received codes, matching the Codex
CLI prompt.

Also documents the Codex auth handoff: a subscription profile is installed as
in-memory external auth rather than persisted, and token refresh is inverted
so the refresh token stays in OpenClaw's store.

* fix(test): make transcript read-failure injection order-independent

server.sessions.compaction-read-errors.test.ts injected its failures with
mockRejectedValueOnce, which fails the NEXT call to loadTranscriptEvents
globally. Under --isolate=false a shard shares one worker, so any sibling
transcript read could consume the one-shot rejection before the compaction RPC
issued its own; compaction then ran against the real reader and returned ok,
failing three assertions. This shard was already red on main; a prior repair
fixed the mock's initialization order but left the call-order dependency.

Key the injection on the seeded sessionId instead, so unrelated readers cannot
consume it and the re-read case counts only its own session's reads.

Also updates two expectations invalidated by this branch: the duplicate-agentDir
remediation text, and the plugin SDK export ratchet, shrunk by the two retired
secret-plan exports.
2026-08-20 00:42:41 -07:00
Steven ff37627804 fix(discord): resolve realtime voice API key references (#125443)
* fix(discord): resolve realtime voice SecretRefs

* fix(discord): isolate realtime voice secret owners

* fix(discord): contain realtime secret owner gate

* fix(build): align plugin SDK boundary paths

* fix(discord): normalize realtime secret owner accounts

* fix(discord): gate canonical realtime secret owners

* style(discord): format realtime voice tests

* test(codex): assign run-attempt tools shard

* fix(discord): skip unavailable realtime providers

* fix(discord): preserve realtime provider availability errors

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-19 15:23:14 -07:00
Jason (Json) 45b8750d8b fix(compaction): default maintenance reasoning to low (#126421)
* fix(compaction): default maintenance reasoning to low

* test(compaction): cover explicit inherit fallback

* test(compaction): preserve inherit literal type
2026-08-19 16:18:06 -06:00
Peter Steinberger ae55a4090c refactor(canvas): make the panel a widget presenter (#126030)
* refactor(canvas): retire legacy host and commands

* refactor(apple): narrow shared Canvas contracts

* refactor(macos): keep Canvas as widget presenter

* refactor(ios): remove Canvas client

* refactor(android): remove Canvas client

* refactor(linux): remove Canvas client

* fix(ci): isolate native locale artifacts

* fix(linux): regenerate companion lockfile

* fix(canvas): refresh native tool display metadata

* test(canvas): align coverage with presenter surface

* test(canvas): remove obsolete asset root seam

* test(canvas): stabilize retirement CI coverage

* refactor(swift): remove orphaned resource wrapper

* test(ios): remove retired canvas layout assertion

* fix(macos): reserve retired canvas command namespace

* refactor(macos): isolate canvas command policy

* fix(canvas): select only eligible macOS panels

* fix(canvas): keep panel selection plugin-owned
2026-08-19 08:21:07 -07:00
Peter Steinberger 2a97397f47 docs: consolidate setup and plugin references (#126132)
* docs: consolidate setup and plugin references

* docs: preserve meeting plugin configuration
2026-08-18 20:00:05 -07:00
Peter Steinberger f2158a9c36 feat(sessions): keep durable work visible and auto-archive stale sessions (#124925)
* feat(agents): clarify visible session guidance

* feat(sessions): auto-archive inactive dashboard sessions

* fix(sessions): preserve active dashboard sessions during maintenance

* test(sessions): preserve admitted dashboard sessions during archive

* chore: refresh session generated artifacts

* test: align recovery id with transcript redaction

* fix(sessions): report only committed maintenance counts
2026-08-18 14:02:54 -07:00
Peter Steinberger b295ae1625 docs(onboard): match classic setup order (#125454)
* docs(onboard): match classic setup order

* docs(onboard): align first-run setup order
2026-08-17 17:19:31 -07:00
Peter Steinberger e3de722971 refactor(tooling): consolidate baseline ratchets (#125459) 2026-08-17 17:17:49 -07:00
Peter Steinberger 2d3aaaa0d5 feat(node-host): support Cloudflare Access service tokens (#125193)
* feat(node-host): support Cloudflare Access service tokens

Carry SecretRef-backed, origin-bound Cloudflare Access credentials across node onboarding, gateway and worker connections, and node-owned worker transfers. Refs #125112.

* chore: align changed gate after rebase

* test(node-host): keep access proof within lint budgets

* style(node-host): keep supervisor within line budget

* fix(node-host): omit absent access config

* fix(node-host): reject Access credentials on plaintext routes

* fix(node-host): reject plaintext before ref resolution

* test: cover Access log redaction over TLS
2026-08-17 04:12:27 -07:00
ClawSweeper 1d65e7b449 test: default trusted checks to local execution (#125120)
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-16 23:45:50 -07:00
Patrick Erichsen 9e4ba15f4c improve: detect Claude CLI prompt cache regressions (#124988)
* test(gateway): cover Claude CLI prompt cache reuse

* test(gateway): validate Claude CLI cache across processes

* test(gateway): perturb Claude native cache context

* test(gateway): calibrate Claude CLI cache threshold

* docs(testing): align Claude cache threshold

* test(gateway): preserve config literal types

* test(gateway): split node admission coverage
2026-08-16 21:48:23 -07:00
Peter Steinberger 4f715eb37b docs: align classic onboarding setup flow (#124712)
* docs(onboarding): align classic setup flow

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

* docs(onboard): clarify import reset incompatibility

Amp-Thread-ID: https://ampcode.com/threads/T-01a00ae0-190d-718b-8a76-b75f3e8d1fae

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-16 21:15:39 -07:00
Peter Steinberger bf9b25ab7e fix(gateway): prevent control-plane polling stalls (#124891)
* fix(gateway): avoid repeated control-plane scans

* fix(tooling): allow concurrent worktree validation

* fix(ci): refresh protocol and runner inputs

* perf(ui): defer hidden session refreshes

* fix(ui): resolve session refresh lint failure

* fix(update): preserve pre-cache update channel

* fix(update): normalize cached update channel

* fix(gateway): lifecycle-cache update install identity

* fix(ui): preserve manual history retry after layout scroll

* test(codex): repair side-question tool schema fixture
2026-08-16 20:58:27 -07:00