Commit graph

100537 commits

Author SHA1 Message Date
Peter Steinberger
98104757ce
perf(crabbox): share the final source mirror inventory (#158828)
* perf(crabbox): share the final source mirror inventory

Collect mirror stamps during the fresh staging manifest walk, retaining the separate pre-Git integrity scan and both source freeze checks. Remove one complete payload traversal without changing persisted formats or cache custody.

* fix(crabbox): require recorded mirror allocations

Enforce the staging owner contract before opening the mirror database, including cold rebuilds. Dispose invalid allocations and report the invariant instead of accepting an empty final inventory.
2026-09-26 11:37:11 +00:00
Peter Steinberger
10619e2614
refactor: reuse fs-safe readers across file workflows (#158825)
Use released fs-safe 0.20.0 for Memory Wiki admission, Control UI asset opens, bounded session baseline reads, and update-recovery metadata decoding. Remove 105 net production TypeScript lines.

Preserve public warnings, link policies, manifest hashes, cached-path identity checks, and recovery ownership checks. Accept a checksum-verified retained copy after its source pathname is replaced. Stop rejecting display baselines solely for a modification-time change during reading while preserving their stored v1 hash format and 4 MiB/file, 16 MiB total limits. Baseline buffering is sequential and capped at 4 MiB/file instead of the former 64 KiB streaming window.

Four independent P0–P2 reviews and all selected checks passed. Focused proof covers 47 wiki/import-boundary cases, 61 retained-asset cases, 49 baseline/Gateway diff cases, and 24 recovery-status cases. The source-replacement and baseline regressions fail their original implementations. No dependency, schema, configuration or release change.
2026-09-26 04:36:46 -07:00
Peter Steinberger
a53efa5e72
test(scripts): isolate compiled lint fixtures from loader services
The fixture already compiles its wrapper closure and supplies native JavaScript setup tools. Keep loader import surfaces without starting unrelated compiler-service children; preserve real oxlint and all process cleanup assertions.
2026-09-26 04:32:36 -07:00
Peter Steinberger
2ff07dbab3
fix(update): check artifact ownership before stopping the Gateway on source updates (#158849)
* fix(update): reserve source artifacts before Gateway shutdown

Validate the installed checkout before activation and hold its artifact ownership through joined completion. Retained owners include exact recovery guidance and are never reclaimed based on PID death. Preserve published source adapters and fail closed when child cleanup remains uncertain.

Fixes #158281. Reported by @hannesrudolph.

* refactor(update): keep Git candidate rejection in admission

* refactor(update): carry native artifact admission into completion

* refactor(ui): inline sidebar navigation lookup input

* fix(update): keep artifact ownership declarations portable
2026-09-26 11:28:41 +00:00
Josh Avant
cd2724e432
feat(release): automate iOS and Android store releases (#158807)
Add manually triggered iOS Store Release and Android Store Release workflows with the same local release commands. Derive store versions and OpenAI release notes at build time, retain recovery artifacts, and avoid generated metadata commits.

Pin queued releases to their dispatch commit, qualify native iOS behavior before signing, and improve Android screenshots and interrupted artifact recovery.

Validation includes focused release/planner tests, workflow and type checks, native iOS qualification, Android screenshot capture, and historical release-note exercises. CI failure attribution and remaining hosted release verification are documented in the PR.

Closes #158806
2026-09-26 06:28:29 -05:00
Peter Steinberger
11a11ef164
feat(gateway): preserve callback ports on plugin routes (#158360)
* feat(gateway): add compatibility listeners to plugin routes

* fix(gateway): preserve legacy listener wire contracts

* fix(plugins): reject conflicting legacy listener profiles

* fix(gateway): drain retired webhook listeners before release

* fix(gateway): avoid shadowing the active legacy endpoint
2026-09-26 04:27:10 -07:00
Peter Steinberger
813169e282
fix: allow builds and lint in nested checkouts (#158674)
* fix: allow builds and lint in nested checkouts

Use the pinned native TypeScript 7.1 API to bound declaration resolution before ancestor installations can influence build and lint inputs. Keep portable input receipts, mutation fences, cache validation, and child cleanup, while preserving the SDK revision renderer dependency contract.

* fix: admit pinned prerelease native compilers

Accept the exact TypeScript prerelease version used by bounded declaration emission before trusted frozen-client parsing. Preserve lock, package, native executable, ownership, and integrity validation, with explicit range and tag rejection coverage.

* test: include compiler worker in artifact ownership fixture
2026-09-26 11:19:13 +00:00
Peter Steinberger
1011b10bb5
refactor(auto-reply): deslop auto-reply third pass (#158598) 2026-09-26 11:16:59 +00:00
Peter Steinberger
1d109710cd
refactor(gateway): simplify artifact history bookkeeping (#158834)
Preserve caller overloads and the delta result type while inferring the implementation return union. Reuse insertion-order map pruning for artifact image cursors without changing the 128-entry bound.
2026-09-26 04:13:12 -07:00
Peter Steinberger
0805a9d7f5
fix: preserve queued settings while a session is being created (#154807)
* fix(ui): preserve queued settings across session creation

Keep pending reasoning, speed and context choices attached to their field
owners while creation receipts adopt the physical session identity. Preserve
FIFO dispatch and each field's visible failure when later writes settle.

Validated with 125 focused and sibling cases, a failing preview regression
control, changed-source checks, the unchanged UI performance budget, and
a Chromium comparison that reproduces the missing error on the incoming
PR and retains it on this candidate. Independent review through P2 is clean.

Related: #154807, #130741, #152375

* test(ui): provide preview capability in chat fixtures

Keep existing composer and identity fixtures aligned with the session
capability contract. An undefined preview represents no pending settings;
existing overrides and rendering assertions stay unchanged.

Independent review through P2 and pinned formatter checks pass. This fixes
the missing-method failures observed on both Node and Bun; test replay is
pending on the updated candidate.

Related: #154807

* test(ui): return complete chat header patch receipts

* test(ui): preserve real session capabilities in permission controls
2026-09-26 04:10:49 -07:00
Peter Steinberger
3151eb1447
refactor(plugins): await batch inventory preparation (#158836)
Read the final uncached installed index through the metadata worker while retaining the original lifecycle lease. Seal collection before awaiting and recheck lease ownership and batch closure before publishing runtime targets. Keep repeated cleanup reads and native lease primitives unchanged.
2026-09-26 04:09:54 -07:00
Peter Steinberger
2bdf8709fc
test(core): remove low-value tests (batch d009) (#158725)
* test(codex): deslop s003 tests

* test(gateway): deslop s010 tests

* test(agents): deslop s013 tests

* test(commands): deslop s011 tests

* test(gateway): deslop s014 tests

* test(gateway): deslop s019 tests

* test(infra): deslop s016 tests

* test(agents): deslop s020 tests

* test(scripts): deslop s017 tests

* test(auto-reply): deslop s021 tests

* test: align d009 replay with current fixture contracts

* test(agents): remove orphaned BTW fixture export

* test: preserve independent regression coverage in d009

Restore eight independent authentication, retry, transcript, goal, reply ownership, and Xcode coverage contracts identified during review. Final correction review passed; final Testbox validation remains pending after lease and transport failures.
2026-09-26 11:08:36 +00:00
Peter Steinberger
eaf8d916f7
refactor(channels): deslop small channel and access plugins (#158441)
* refactor(channels): deslop small channel and access plugins

Consolidate repeated transport and webhook flows, reuse documented SDK owners, and remove redundant private forwarding layers while preserving authorization, credential, formatting, protocol, and state contracts. Correct Teams setup diagnostics to reflect its existing live-caption support.

* fix(nextcloud-talk): retain typed outbound context projection

* fix(zalo): retain inbound media kind normalization

* refactor(channels): resolve SDK import and setup lint findings

* refactor(imap): retain SDK authentication strength exhaustiveness
2026-09-26 11:07:19 +00:00
Peter Steinberger
443a0676b9
refactor(channels): extract ingress mutation kernels (#158628)
* refactor(channels): extract ingress mutation kernels

* docs(state): refresh ingress kernel inventory

* docs(state): refresh ingress worker inventory
2026-09-26 04:05:43 -07:00
Peter Steinberger
239bae9aa9
refactor(agents): simplify file edit no-op planning (#158831) 2026-09-26 03:59:45 -07:00
Peter Steinberger
2442c30269
perf(agents): move workspace attestation writes off the main thread (#158797)
* perf(agents): move workspace attestation writes off the main thread

* refactor(agents): keep attestation hash validation private
2026-09-26 03:53:24 -07:00
Peter Steinberger
2a53c32dc2
test(channels,infra,sdk): remove low-value tests (batch d016) (#158764)
* test(signal): deslop s080 tests

* test(node-host): deslop s085 tests

* test(imessage): deslop s084 tests

* test(scripts): deslop s075 tests

* test(flows): deslop s089 tests

* test(slack): deslop s087 tests

* test(mattermost): deslop s091 tests

* test(infra): deslop s086 tests

* test(plugin-sdk): deslop s090 tests

* test(voice-call): deslop s092 tests

* style(test): format batch d016 replay

* test(mattermost): preserve retained reaction regression

* test: retain independent contracts in batch d016

* test(plugin-sdk): avoid shadowed table labels

* test(node-host): remove unused runtime test type
2026-09-26 10:51:39 +00:00
Ayaan Zaidi
8fd72da7f2
feat(agents): let owners hand keys, config, and skill edits to their agent in chat (#158120)
## What Problem This Solves

Fixes: owners who ask their agent in chat to change a key, a config value, or one of their own skills get refused, sent to a dashboard, or told to file a Workshop proposal. Examples: "you can't post API keys here", switching the embeddings provider routed to the web-search wizard, only proposals for handwritten skills.

## User Impact

An owner can hand the agent an API key or token in chat, ask it to change config such as the embeddings provider, and have it edit skills they own. Session permission modes are unchanged: Full Access applies, restricted sessions ask.

- **Keys from chat.** Masked setup flows still keep keys out of model context and remain the default. If the user already pasted a key or token, the agent stores it in the shared secret store and points the config key at it with a `store` SecretRef instead of refusing. It never echoes the value back. The pasted message already reached the model provider and transcript; redaction covers later logs and output only, and the docs say so.
- **Existing store entries are never touched.** Each save inserts a new entry named after the config key plus a random suffix (`GATEWAY_REMOTE_TOKEN_9B139B5E231299BC`). Nothing is overwritten, revived, or deleted, and a new name can never match anything already pointing into the store, including a stale reference to a removed and purged entry. Replacing a key leaves its previous entry for `openclaw secrets store rm`. Rotating a key keeps its configured store provider alias, and the audit records the alias actually used.
- **Embeddings.** The agent now treats the memory embeddings provider, model, and key as `memory.search.*` config, not the web-search setup wizard.
- **Skills.** When the user asks, the agent edits skills they own directly: repository skill source, workspace `skills/`, project `.agents/skills/`, and configured extra skill directories. Bundled, ClawHub-installed, and plugin-provided skills are replaced by their owners' updates. For those, the agent says so and offers to capture the change as a Workshop skill.
- **Tone.** The "never request / paste credentials in chat" lines are gone from the `openclaw` tools and system-agent prompts. "Never echo secret values" stays, and so do factual pointers for flows that genuinely need a UI: channel sign-in, provider OAuth/accounts, and model onboarding.

No config option, schema, or protocol change. The Full Access permission-policy floor from the first revision moved to #158142 for its own security review.

## Why This Change Was Made

After #149870, approved config writes may target any path. What still blocked owners was model-facing text telling the agent to refuse credentials, plus the missing ability to store a chat-provided value anywhere but plaintext config.

`config_set_ref` gains an optional `secret` argument (read without trimming; only emptiness is checked). With it, the system agent:

1. registers the value for redaction when the proposal is built;
2. keeps the key's existing store provider alias when it has one;
3. sends one `secrets.writeForConfigRef` command to the SQLite state worker with the requester's live-authority guard. The host re-checks that guard at the worker's transaction and commit admission (`createSqliteWorkerWriteAdmission`), so a run stopped while the command is queued writes nothing. The transaction inserts a new row under a freshly minted `NAME_<16 random hex>`;
4. writes the ref through the existing config writer, which re-checks authority. If that write fails (before or after the writer commits), OpenClaw rereads the config and the error says the key was saved as `<NAME>` and whether the config key points at it. There is no automatic delete: another consumer may have linked the fresh entry, or the writer may have committed before failing;
5. the normal config reload picks up the new ref, since its id always changes.

Nothing new runs SQLite on the Gateway main thread.

<details>
<summary>Out of scope / follow-ups</summary>

- Found while proving this: in Full Access, after a delegated change applies, the next agent turn in the same chat fails with `SQLite database already belongs to another worker backend`. It reproduces on unmodified `origin/main` (`71bb516`) with a `logging.level` change followed by one more message. This PR does not fix it.
- Built-in provider sign-in and model onboarding stay handoffs; they own live verification of the active inference route.
- Other secret-store set/delete paths remain synchronous migration debt, as `worker-access.md` already records.

</details>

## Evidence

Real Telegram Test Server (Convex-leased userbot, fresh Gateway, QA mock provider, Full Access, tester is owner), first revision:

| | Screenshot |
|---|---|
| Token given in chat, applied with no approval prompt and no refusal (synthetic QA token) | ![User sends a remote Gateway token and asks to save it; the agent replies without refusing or asking for approval](https://github.com/user-attachments/assets/4f68f3db-225c-4047-984d-ed0cff79cd0c) |

### Final effects at this head

qa-channel scenario `system-agent-owner-trust` passes through a real Gateway and state worker. The Gateway is seeded with an unrelated `GATEWAY_REMOTE_TOKEN` entry, then:

1. A command-allowed **non-owner** (`bob`) sends the key. The `openclaw` tool is owner-only.
2. The **owner** (`alice`, Full Access) sends it.

Captured step details (redacted by the Gateway; the store ref id prints as `__OPENCLAW_REDACTED__`):

```json
{
  "nonOwnerEntryNames": ["GATEWAY_REMOTE_TOKEN"],
  "storedRef": { "source": "store", "provider": "default", "id": "__OPENCLAW_REDACTED__" },
  "storeEntryNames": ["GATEWAY_REMOTE_TOKEN", "GATEWAY_REMOTE_TOKEN_9B139B5E231299BC"]
}
```

- After the non-owner turn: only the seeded entry exists and `gateway.remote.token` is unset.
- After the owner turn: `gateway.remote.token` is a `store` SecretRef, the token is in its own minted entry (`GATEWAY_REMOTE_TOKEN_9B139B5E231299BC`), and the seeded entry's `updatedAt`/`updatedBy` are unchanged. No approval prompt was posted, and the token is absent from chat, config, and the store listing.

**Revoked request**, through the production worker (Node main thread, real broker, `writeSecretStoreEntryForConfigRef`). The requester's guard passes the caller's check, then reports the run stopped:

```text
seeded: [ 'GATEWAY_REMOTE_TOKEN (cli)' ]
revoked request rejected: requesting run is no longer active
after revoked request: [ 'GATEWAY_REMOTE_TOKEN (cli)' ]
owner request saved as: GATEWAY_REMOTE_TOKEN_F1657B971F691824
after owner request: [ 'GATEWAY_REMOTE_TOKEN (cli)', 'GATEWAY_REMOTE_TOKEN_F1657B971F691824 (openclaw)' ]
seeded value intact: true
```

Tests (each fails without the behavior it covers):
- production worker path (`secret-store-config-ref.worker.test.ts`, forked database-worker lane with the real broker): a chat secret gets its own minted entry beside a live `GATEWAY_REMOTE_TOKEN` without touching it; a requester revoked after the caller's check writes nothing;
- store kernel: a refusal at commit admission rolls the transaction back; each save mints a new `NAME_<hex>` and leaves the key's previous entry unchanged; a stale name whose entry was removed and purged still resolves to nothing after a chat save;
- operations: stored and referenced with no value in output or audit; authority gone before the store write writes nothing; a failed config write names the saved entry and leaves it in place; rotating a key keeps its configured store provider alias, and the audit records it;
- tool: proposes a store write without repeating the key, preserving leading and trailing whitespace.

Measured single-worker wall time per new or materially changed test file at this head (`node scripts/run-vitest.mjs run <file>`, local M-series; vitest Duration includes import and setup):

| File | Tests | Wall | Vitest duration |
|---|---:|---:|---:|
| `src/secrets/store/secret-store-config-ref.worker.test.ts` (new, database-worker lane) | 2 | 14 s | 2.05 s |
| `src/secrets/store/secret-store.test.ts` | 32 | 16 s | 13.37 s |
| `src/system-agent/operations.test.ts` | 43 | 18 s | 15.30 s |
| `src/agents/tools/system-agent-tool.test.ts` | 36 | 15 s | 12.89 s |

QA scenarios: `system-agent-owner-trust` (mock-openai) runs in about 27 s after build; `skill-owner-direct-edit-live` is live-frontier only and took about 3 min with `claude-cli/claude-sonnet-4-6`.

Wording pins for the removed lecture text were deleted. The focused store, worker, exclusivity, operations, tool, approval, and delegate suites pass. `node scripts/check-changed.mjs` passes every gate except core lint, which fails only on three files this PR does not touch (`server-chat-metadata-lifecycle.integration.test.ts`, `session-companion-ask.ts`, `app-sidebar-session-list-render.ts` over `max-lines` on the base); oxlint on the changed files is clean.

Security decision: a Full Access owner's pasted key goes to the Gateway-wide team store without a separate approval. Maintainer (@obviyus) accepted this in the PR conversation.

**Rotation with a second consumer**, through the production worker (Node main thread, real broker). A second consumer references the key's first entry before the next save lands; value fingerprints only:

```text
owner saves key #1 -> MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 (sha256:4a5c5a4aa8de)
second consumer now references MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 (e.g. linked while the next save is queued)
owner saves key #2 -> MODELS_PROVIDERS_OPENAI_API_KEY_4066F18ABAAE6903 (sha256:28bc4e3fe10d)
second consumer's entry MODELS_PROVIDERS_OPENAI_API_KEY_6D96F6E92B59E935 after rotation: sha256:4a5c5a4aa8de
unchanged: true
```

**Config write fails after the save, with a second consumer on the fresh entry**, through the production worker and the system-agent apply path (fingerprints only):

```text
owner result: Saved the secret as GATEWAY_REMOTE_TOKEN_6B68C031E571F81C, but could not point gateway.remote.token at it: config write failed after commit (rollbackStatus: not-restored). Retry, or remove the entry with `openclaw secrets store rm GATEWAY_REMOTE_TOKEN_6B68C031E571F81C`.
config gateway.remote.token: null
second consumer's entry GATEWAY_REMOTE_TOKEN_6B68C031E571F81C: sha256:09ae5b4fd36b
second consumer keeps the credential: true
```

**Stale reference to a removed and purged entry**, production worker for purge and save:

```text
purged rows: 1
stale ref GATEWAY_REMOTE_TOKEN after purge: SECRET_STORE_NOT_FOUND
chat save -> GATEWAY_REMOTE_TOKEN_8F698B5396990ADD resolves (value hidden)
stale ref GATEWAY_REMOTE_TOKEN after chat save: SECRET_STORE_NOT_FOUND
```

**Owned-skill edit with a live model.** New scenario `skill-owner-direct-edit-live` (live-frontier; run with `claude-cli/claude-sonnet-4-6`, subscription auth) passes at this head. It seeds workspace skill `qa-owner-greeting` replying `OWNER-GREETING-V1`, and the owner asks in plain words: "Please change my qa-owner-greeting skill so it replies OWNER-GREETING-V2 instead of OWNER-GREETING-V1." Captured result:

Skill file after the turn:

```markdown
---
name: qa-owner-greeting
description: Greets the owner with a fixed marker
---
When the user asks for the owner greeting, reply with exactly: OWNER-GREETING-V2
```

Agent reply: "Let me find the skill file. Done. The `qa-owner-greeting` skill now replies `OWNER-GREETING-V2` instead of `OWNER-GREETING-V1`."

The model edited the skill file in place and confirmed it; it did not refuse or file a Workshop proposal.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-26 16:20:43 +05:30
Peter Steinberger
35f17c7f5d
test(core,plugins,scripts): remove low-value tests (batch d012) (#158802)
* test(agents): deslop s047 tests

* test(codex): deslop s045 tests

* test(gateway): deslop s049 tests

* test(commands): deslop s048 tests

* test(scripts): deslop s012 tests

* test(scripts): deslop s046 tests

* test(workboard): deslop s053 tests

* test(plugins): deslop s051 tests

* test(slack): deslop s054 tests

* test: preserve current fixture contracts after replay

* test: retain distinct cleanup contracts and fixture scopes

* test(gateway): retain idle suspension coverage for local claims
2026-09-26 03:46:53 -07:00
Peter Steinberger
44c955105b
perf(subagents): avoid rebuilding list indexes for internal run updates (#158792)
* perf(subagents): patch list indexes on registry publication

Carry keyed registry publications into the resident session-list index. Skip unchanged compact facts, preserve full-read revision fences, and patch affected topology while retaining live-owner and source replacement semantics.

* fix(subagents): keep read index construction read-only

* fix(subagents): retain cloneable prepared run facts
2026-09-26 03:44:18 -07:00
Peter Steinberger
df4cee1617
test(runtime,plugins): remove low-value tests (batch d015) (#158721)
* test(state): deslop s073 tests

* test(system-agent): deslop s071 tests

* test(google): deslop s077 tests

* test(agents): deslop s074 tests

* test(gateway): deslop s076 tests

* test(agents): deslop s067 tests

* test(openai): deslop s079 tests

* test(agents): deslop s078 tests

* test(claws): deslop s081 tests

* test(tasks): deslop s083 tests

* test: format replayed batch d015 fixtures

* test(agents): keep discovery fixture credential types

* test(claws): use shared export options in selection coverage

* test: remove stale cleanup imports and suppression

* test: retain independent regression contracts in batch d015
2026-09-26 03:40:01 -07:00
Peter Steinberger
b9a4be580b
perf(plugins): avoid nested iterator result scope entries (#158799)
Recognize core-owned result readers through existing value views while retaining each iterator token check. Preserve callable projection, lazy plugin getters, thenable settlement, and caller-defined shadow identities.
2026-09-26 03:34:47 -07:00
Jason (Json)
3b829ce81b
fix(state): restore read worker test lint gate (#158820) 2026-09-26 04:33:47 -06:00
Peter Steinberger
468039ab18
perf(sessions): avoid inventory scans when creating conversations (#158809)
* perf(sessions): bound creation label lookups

Prepare only the requested explicit label claim alongside exact creation targets, using the existing label index and dirty-row validation. Remove whole-store creation metadata scans and mixed cache snapshots. Preserve listing, alias, snapshot, and creation concurrency semantics.

* refactor(sessions): trim creation title commentary

* refactor(sessions): remove redundant title comment

* refactor(sessions): use creation agent inputs directly

* fix(sessions): complete creation agent input cleanup

* refactor(sessions): keep listing iteration private

* style(sessions): avoid shadowed creation keys
2026-09-26 03:26:42 -07:00
Peter Steinberger
e7a71e9078
perf(agents): bound tool catalog fingerprint allocations (#158805) 2026-09-26 03:23:44 -07:00
Peter Steinberger
cc75881752
perf(gateway): avoid chat stalls during WAL maintenance (#158570)
* perf(sqlite): move periodic WAL maintenance off the Gateway thread

* test(sqlite): verify WAL identity refusal and release test observers

* test(sqlite): await asynchronous periodic containment

* style(sqlite): make WAL failure types and cancellation returns explicit

* style(sqlite): align the maintenance cancellation return

* fix(sqlite): break WAL worker context import cycle

Share context capture behind explicit lifecycle admission so WAL registration does not import its owning cache. Preserve synchronous schema and maintenance authority capture.

Validation: import-cycle guard, targeted lint, 41 lifecycle and schema-policy tests, and independent Codex review passed. Initial test compilation overlapped SDK declaration generation; the sequential run passed after generation settled.

* fix(sqlite): satisfy worker context and fixture contracts

Distinguish admission-injected context helpers from the canonical cache-backed facade. Remove the MCP reconstruction fixture session-key mock so shared storage cleanup can consume the real routing exports.

Validation: full architecture checks and independent Codex review passed. Original Gateway shard reproduced all 11 MCP failures; the repaired focused suite passed all 11 in 18.39 seconds.

* fix(maintainer): include WAL context capture in wrapper

Keep the materialized native PR wrapper complete after extracting the shared-state worker context capture module. The missing import prevented provisioner private-store injection.

Validation: reproduced the missing-module error before the fix; provisioning, extracted dependency closure and wrapper lease bootstrap passed afterward (3 tests, 92.08s wall). Independent Codex review passed.

* test(sqlite): observe periodic reclamation in its worker

Move settlement-order observations from the parent connection SQL spy to real worker transaction and commit admission. Retain authorization, rejected reclamation, page budget, original owner and reclamation-worker shutdown assertions; require zero parent vacuum calls.

Validation: reproduced both old fixture failures; all 13 reclamation and agent WAL admission tests passed (137.90s wall), targeted lint and independent Codex review passed.

* test(sqlite): preserve real coordinator error exports

Remove redundant pure-helper mocks in device and lease fixtures so shared storage cleanup can use real coordinator errors. Preserve worker metadata in the broker cleanup fixture while continuing to forbid native worker and channel construction.

Validation: reproduced all missing-export failures, then 42 tests across five fixtures passed in 20.92 seconds; targeted lint, formatting and independent Codex review passed. No assertions or native-access fences were relaxed.

* test(sqlite): expect periodic vacuum in the worker

Keep the publication-order fixture aligned with periodic maintenance execution placement. Assert no parent vacuum after settlement while retaining real freelist reduction, page budget and committed-row proof.

Validation: both original failures reproduced; all 20 worker-store tests passed in 50.76 seconds. Formatting, diff checks and independent Codex review passed.

* fix(sqlite): preserve published-handle sidecar containment

Keep the original Linux sidecar tripwire at timer entry before physical identity admission can refuse worker dispatch. The existing synchronous scan preserves fatal no-cleanup ordering; checkpoint, vacuum and file-size work remain in workers.

Extend the existing process fixture through registered agent and shared-state owners with replaced main files and sidecars. Both cases fail on the original production path; all 83 Linux WAL/admission/coordinator tests pass after the fix with source hashes verified. Typecheck, lint and independent Codex review passed.

* test(sqlite): retain native metadata in worker mocks

Preserve real worker-thread and OS metadata while retaining the existing fake workers, forbidden constructors and two-worker pool limit. This keeps shared storage teardown from failing on incomplete built-in mocks.

Validation: all 41 existing tests across five affected fixtures passed in 17.89 seconds after reproducing the missing-export failures; lint, formatting and independent Codex review passed. No assertions or native-access fences were relaxed.

* test(sqlite): forward captured timer arguments safely

Use native reflective invocation for the unchanged callback and argument list, avoiding the Node timer generic tuple mismatch without casts or relaxed checks.

Validation: reproduced TS2345 in the production core graph, then the graph, targeted lint, formatting and independent Codex review passed.

* fix(sqlite): keep writer admission free of database startup

Place agent WAL registration with the database publisher instead of the lightweight writer queue. Preserve the registration body and shared queue owner while removing eager lifecycle and compiled-worker imports from queue-only callers.

The unchanged report-owner shard previously timed out during cold worker preparation. After the move, the focused child emits no worker-build output and all 83 tests pass under the same deadline. WAL publication/admission (26 tests), extracted wrapper closure, core types, lint, import-cycle checks and independent review passed.

* fix(state): preserve sealed private binding publication

* test(state): consume published identity in pending close fixture
2026-09-26 03:16:38 -07:00
Vincent Koc
275d0d497b
fix(setup): honor baseline skip-bootstrap (#115945)
* fix(setup): preserve baseline skip-bootstrap intent

Persist the existing skip-bootstrap option before initial workspace creation and keep explicit local defaults out of shared include files. Preserve configured values, user-authored files and include ownership on later baseline runs.

Related: https://github.com/openclaw/openclaw/pull/115945

* fix(core): preserve setup and subagent model intent

* chore(setup): preserve original PR ancestry after scope split

Keep the reviewed current-main setup reconstruction as the complete candidate tree. Requester inheritance is already supplied by PR149036; the separate model-selection-source proposal remains tracked in issue158760. Retain the original PR commit as a parent instead of rewriting its history.

Related: https://github.com/openclaw/openclaw/pull/115945
Related: https://github.com/openclaw/openclaw/issues/158760
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-09-26 18:15:06 +08:00
Mert Başar
20965566ce
fix(acp): ACP runs are cancelled immediately after an in-process Gateway restart (#157658)
Closes #157442

## What Problem This Solves

Fixes: ACP runs are cancelled right after they start once the Gateway has restarted in-process, for example after a config change forces a restart.

## User Impact

User impact: after an in-process Gateway restart, ACP runs (`sessions_spawn` with `runtime: "acp"`, `/acp`) reach the agent again. They no longer end within milliseconds with "Subagent run killed", and you no longer need to restart the whole process. Nothing needs to change in configuration.

## Why This Change Was Made

Gateway shutdown drains the process-wide ACP session manager. The drain marks the manager as stopping for good and closes its runtime handles, but the manager stayed in the singleton, so the next Gateway boot in the same process reused it. Every accepted turn on that stopped manager was then aborted before any prompt reached the backend.

Shutdown now calls `disposeAcpSessionManager(reason)` on the singleton facade. It drains the current manager and then clears the singleton, so the next `getAcpSessionManager()` builds a fresh one. Following the issue and the ClawSweeper review, this gives the next boot a new instance rather than un-stopping the old one, whose runtime handles are already closed.

- The clear is compare-and-clear, so it never drops a manager that someone else already replaced.
- The clear runs in `finally`: a drain that throws has already set `stopping`, and keeping that instance would wedge the next boot again.
- Callers that still hold the old instance, such as late setup cleanups, keep using it.
- The clear lives in the singleton facade rather than in the core disposer, which avoids a `manager.core` → `manager` import cycle.

## Evidence

**Live, after-fix ACP client run across a config-triggered in-process restart** (built `dist`, `node openclaw.mjs gateway`, Windows, Node 24.19.0). Setup:

- `acp.backend: "acpx"`, with one allowed agent `echo`. It is a ~50-line ACP agent built on `@agentclientprotocol/sdk` that replies `ECHO: <prompt>` and appends each `session/new`, `session/load` and `prompt` it receives, with its pid, to a log.
- `gateway.reload.mode: "hybrid"`, `OPENCLAW_NO_RESPAWN=1`.
- Messages go in through the TUI's own `GatewayChatClient` (`chat.send`), and each call waits for that run's final `chat` event.

Sequence, identical for both builds:

1. `/acp spawn echo --bind here`
2. `ping before restart`
3. Edit `gateway.controlUi.basePath`, a restart-bound key.
4. The Gateway restarts in-process: `config change requires gateway restart (gateway.controlUi.basePath)` → `received SIGUSR2; restarting` → `restart mode: in-process restart (OPENCLAW_NO_RESPAWN)` → `http server listening`. The pid is the same before and after.
5. `ping after restart`

Before the fix (this PR's base `691d2e5cd4`, with only `manager.ts` and `server-close.ts` reverted):
```
[00:18:51] send: ping before restart
final: {"state":"final", ... "text":"ECHO: ping before restart" ...}
[00:19:13] edit config: gateway.controlUi.basePath /proof-a -> /proof-b (restart-bound key)
[00:19:28] gateway up again (boot 2)
[00:19:33] send: ping after restart
final: {"state":"aborted"}
```
Agent log: only `ping before restart` ever arrives. After the restart, no agent process is started and no prompt arrives.

With the fix (PR head `8d0f42ef173`):
```
[23:44:26] send: ping before restart
final: {"state":"final", ... "text":"ECHO: ping before restart" ...}
[23:44:47] edit config: gateway.controlUi.basePath /proof-a -> /proof-b (restart-bound key)
[23:45:02] gateway up again (boot 2)
[23:45:07] send: ping after restart
final: {"state":"final", ... "text":"ECHO: ping after restart" ...}
```
Agent log after the restart: a new agent process loads the same ACP session and then receives the prompt.
```
{"event":"agent-start","pid":21600}
{"event":"session-load","sessionId":"5f180a01-…"}
{"event":"prompt","sessionId":"5f180a01-…","text":"ping after restart"}
```
One disclosure: to boot a real Gateway on Windows, both runs carried the same local, uncommitted workaround for #157067. That workaround shallow-copies the `env` Proxy before `session-history` posts it to its worker. The workaround is identical in RED and GREEN and is not part of this PR.

**Test cost:**

| Test | CI (Linux, PR run `36084021270`) | Local (Windows) |
|---|---|---|
| `src/gateway/server-close.acp-restart.test.ts` | 8.8 s (8775 ms, shard `agentic-control-plane-runtime-server-hosted-2`) | 39.6 s |
| `src/acp/control-plane/manager.restart.test.ts` | 9 ms (shard `core-runtime-shared`) | 23 ms |

Why the Gateway test needs two boots: the bug only exists between a close and the next start in the same process. The first Gateway's close path drains the singleton, and the second Gateway's boot picks it up again. `manager.restart.test.ts` covers the facade by itself, meaning dispose → clear → new manager. The mocked `server-close.test.ts` only asserts that close calls `disposeAcpSessionManager("gateway-shutdown")`. Neither of them runs the real close handler against the real manager and then shows that the next `startGatewayServerCore` hands out a live manager whose turns reach the backend. That end-to-end handoff is the reported failure, and it needs exactly one boot on each side of the restart. Its 8.8 s in CI is mostly the two Gateway startups, and it runs in the existing `gateway-server` lane.


**Real Gateway, same process** (`src/gateway/server-close.acp-restart.test.ts`, the `gateway-server` lane): starts a real Gateway through `startGatewayServerCore` and registers a stub ACP backend. It then:

- runs an ACP prompt,
- closes the Gateway with `restartExpectedMs`,
- starts a new Gateway in the same module graph,
- re-registers the backend, as plugins do on boot,
- runs another prompt.

Only the ACP backend is stubbed: the session manager, session store and close path are the real ones.
- Before the fix (current `main`): the prompt before the restart reaches the backend. The prompt after it comes back only as `{ type: "done", status: "cancelled", stopReason: "cancel" }`, and the backend never sees it. That is the reported symptom.
  ```
  × submits ACP prompts after the Gateway closes and restarts in the same process
    → expected [ { type: 'done', …(2) } ] to not deep equally contain { type: 'done', …(2) }
  ```
- With the fix: both prompts reach the backend (`["before restart", "after restart"]`), the old manager ends with 0 active sessions, and the manager after the restart is a new instance.

**Unit** (`src/acp/control-plane/manager.restart.test.ts`): after `disposeAcpSessionManager("gateway-shutdown")`, `getAcpSessionManager()` returns a new manager, and its first turn is submitted to the runtime.

**Checks run locally (Windows):**
- `server-close.test.ts`: 82 passed. The one failure, `replaces the process supervisor after a concurrent adapter startup failure`, fails identically on unmodified `main` on this machine.
- `manager.preactive-cancellation.test.ts`: 5/5.
- `tsgo:core` and the core test-type shards pass.
- `oxfmt --check` and the line-cap ratchet against `upstream/main` are clean.
- `manager.owner`, `manager.runtime-handles` and `spawn.test.ts` stall or fail locally on unmodified `main` too, so CI is the signal for those.

The in-process restart path in the issue (`run-loop.ts` → close → start again in the same process) is the one the Gateway test drives. The only difference: the harness binds the restarted Gateway to a fresh test port, because its port-claim lock can't re-take the same port in-process. The ACP manager doesn't depend on the listener port.

CI note: after rebasing onto current `main`, the only red shard is `checks-node-changed-compact-large-26`. Its single failing test is `server.chat.gateway-server-chat-b.test.ts > chat.send forwards one-turn queue mode overrides internally`, which fails identically locally with this PR's two source files reverted to `main`.


## Maintainer verification

Verified on macOS with a synthetic ACP SDK peer and an isolated Gateway after merging main `8778b46ccf`. Through the TUI GatewayChatClient, a normal prompt completed. Changing `gateway.controlUi.basePath` triggered SIGUSR2 and an in-process restart with the same PID. With main’s two production files, the next prompt aborted before reaching the peer; with this fix, it completed and the peer retained both prompts in the same session. A full SIGTERM shutdown exited cleanly and the observed ACP child no longer existed. No paid provider was used.

Focused lifecycle tests: 90 passed across four files (58.35 s total; real Gateway restart test 14.23 s). Core production types and touched-file lint/format checks passed. Shutdown continues to drain runtime handles before retiring the singleton; it does not skip ACP cleanup.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-26 15:43:14 +05:30
Peter Steinberger
ac3db1a7a7
fix(ui): preserve composing drafts when a tab is hidden (#158801) 2026-09-26 03:11:33 -07:00
Peter Steinberger
54db1675d8
test(config): make snapshot allocation proof deterministic (#158798) 2026-09-26 03:09:24 -07:00
Peter Steinberger
e4b9f9a3a5
fix(ui): retain failed sidebar narration releases (#158794)
* fix(ui): retain failed sidebar narration releases

* test(ui): type narration release failure fixture

* test(ui): simplify deferred narration fixtures
2026-09-26 03:06:53 -07:00
Peter Steinberger
e9fef978be
fix(secrets): batch trusted Windows path permission checks (#158785)
Use released fs-safe ACL facts so native permission inspection honors the same directory grants as the structured fallback. Preserve executable-parent restrictions, TrustedInstaller owner exceptions, POSIX sticky handling, and before/after/final path identity checks. Report batch failures against the checked chain with their original cause.
2026-09-26 03:04:06 -07:00
Peter Steinberger
1dbbf6ce63
fix: keep channel policy reads off the Gateway event loop (#158783)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-09-26 03:01:32 -07:00
Peter Steinberger
20a865cd04
perf(gateway): wait for resume before retrying identity reads (#158779) 2026-09-26 03:01:29 -07:00
Peter Steinberger
facd3957cd
fix(test): prevent registry races in composed SDK fixture (#158790)
* fix(test): settle SDK fixture lifecycle writes before artifact reads

Seed a current transcript header and join the synthetic lifecycle persistence before artifact discovery. Assert the durable timeout outcome so background persistence failures cannot pass silently.

* fix(test): narrow observed lifecycle phases
2026-09-26 02:58:07 -07:00
Peter Steinberger
1d1fa3596d
perf(sessions): halve fresh-session worker write commands (#158771)
* perf(sessions): create fresh sessions with one worker write

Initialize canonical transcript headers inside the entry replacement transaction, retaining lifecycle publication order, archive receipts, alias rollback hooks, and recovery of older standalone headers. Reduce 100 fresh creations from 200 worker write commands to 100.

* fix(sessions): type the worker transcript assertion explicitly

* test(sessions): align custody proof with atomic creation
2026-09-26 02:57:49 -07:00
Patrick Erichsen
7f41da0c7e
feat: curate plugin categories and add Computer use discovery (#158686)
* feat: prioritize plugin categories with ClawHub curation

* feat: prepare CUA for trusted ClawHub discovery

* docs: regenerate CUA plugin distribution inventory

* docs: refresh generated CUA install reference

* fix: keep CUA publication staged with bundled host

* test: give discovery page fixtures visible categories

* test: run chat retention proof on Node

* test: align plugin browser fixtures with curated shelves

* test: retain icon coverage in curated plugin shelves
2026-09-26 02:55:54 -07:00
Peter Steinberger
90869ccd9c
refactor: share ACL inspection and file writes with fs-safe (#158772)
* refactor(secrets): delegate POSIX plan creation to fs-safe

Use released strict file durability while preserving exclusive creation, owner-only output, readable output directory modes and the collision diagnostic. Keep the Windows private creator unchanged. Existing Vault CLI cases pass before and after the cutover; changed checks and independent review pass.

* refactor(snapshot): prepare fs-safe ACL batch adoption

Delegate Windows owner and DACL transport to readOwnerAndDaclBatch while retaining snapshot trust and access policy. Keep the private Windows creation backend unchanged.

Release-blocked: this uses the accepted but unreleased fs-safe batch API. Dependency pins remain unchanged and this branch must not land before a supporting release is adopted.

* refactor: delegate atomic text writes to fs-safe
2026-09-26 09:52:39 +00:00
Peter Steinberger
8301f00613
fix(acp): keep session listings off the Gateway thread (#158782)
* fix(acp): move file-backed session listings off the Gateway thread

* refactor(acp): keep the native join helper private

* refactor(acp): keep session entry types with their store owner
2026-09-26 02:52:07 -07:00
Vincent Koc
eb22357959
docs(release): restore the npm publication bookmark (#141153)
* docs(release): restore the npm publication bookmark

Preserve the published publish-the-npm-packages fragment after the extended-stable heading rename. Keep the current release policy and canonical maintainer procedures unchanged.

* docs(reference): split the release runbook by reader job

docs/reference/RELEASING.md was 99,140 characters and mixed reference,
how-to, and explanation content for five release jobs on one page. It is
now a 4.6k index over nine pages under docs/reference/releasing/, one per
reader job, so an operator can complete one procedure on one page.

Children, in release order:

- releasing/versioning - version formats, git tags, npm dist-tags, cadence
- releasing/preflight - checks and generators to run before tagging
- releasing/regular-release - the twelve-step operator checklist
- releasing/test-boxes - Full Release Validation and the Vitest, Docker,
  QA Lab, and Package boxes
- releasing/publish-automation - OpenClaw Release Publish order, tooling
  tags, and Windows, Android, and ClawHub recovery
- releasing/beta-latest-sequence - the orchestrated stable sequence
- releasing/main-closeout - bringing main to the shipped state
- releasing/extended-stable - the monthly .33+ Gateway lane
- releasing/npm-workflow-inputs - operator-controlled workflow inputs

Anchor strategy. Per-anchor routes are impossible here: redirectSource()
in scripts/lib/docs-redirects.mjs rejects any source containing [?#]. So
every original anchor stays alive on the parent index, matching the
configuration-reference, Control UI, CI, protocol, and Slack splits: 18
authored <a id="..." /> stubs in a "Where each section moved" list, each
linking to /reference/releasing/<child>#<anchor>. The remaining two ids,
public-references and related, keep their sections on the index itself
and are deliberately not stubbed, so no duplicate authored/canonical ID
is raised.

Every id was computed with parseDocsDocument from
scripts/lib/docs-markdown.mjs, never a slug approximation. That matters
for "Regular beta/latest stable release sequence", which mints both the
percent-encoded canonical regular-beta%2Flatest-stable-release-sequence
and the compatibility alias regular-beta/latest-stable-release-sequence;
both are stubbed.

Anchor proof, run as a script rather than inferred from a passing audit
(a split rewrites the repo's own links, so docs-link-audit reads clean
even when every external deep link is broken): 20 pre-split ids
enumerated, 20 resolve against the parsed post-split index through
resolveDocsFragment, 18 stubs all point at an id that exists on the named
child, 0 collisions on the index or any child.

Losslessness, asserted mechanically by concatenating child bodies back
to the original section bodies: 0 divergences. Words 12,605 -> 13,045
(+440 from the index funnel, per-child frontmatter, and Related blocks).
Code fences 23 -> 23, unchanged. Links 23 -> 79 (+18 stubs, +9 index
bullets, +27 Related entries, +2 orphan repairs). Characters 98,834 ->
104,528.

Prose was not rewritten. The one declared exception is the cross-
reference repair the split requires: "see the dedicated workflow below"
in Version naming and "documented at the top of this page" in the
beta/latest sequence both pointed at content that now lives on another
page, so each became a real link to the extended-stable page.

Supporting changes:

- docs/docs.json gains a "Release runbook" nav group under "Release
  process", mirroring the "CI" group under "Testing and CI".
- .github/CODEOWNERS extends @openclaw/openclaw-release-managers to
  /docs/reference/releasing/, so the split does not silently drop
  release-manager ownership of the runbook.
- test/scripts/package-acceptance-workflow.test.ts and
  test/scripts/openclaw-npm-extended-stable-workflow.test.ts read
  RELEASING.md plus docs/reference/releasing/*.md as one set, following
  the pattern already used there for docs/ci.md plus docs/ci/*.md, so the
  assertions follow the content instead of a single file path.
- test/scripts/docs-sync-publish.test.ts pins the nine new routes.
- docs/.i18n/glossary.zh-CN.json gains one entry per new page title.
  The zh-CN targets are machine-written and unreviewed.

Closes audit findings: r3-0683, r3-0685

* Merge origin/main into docs-audit/split-releasing

Five commits changed docs/reference/RELEASING.md while this branch was
turning it into an index (#142195, #142291, #142260, #141786, #140672),
adding 98 lines. Resolved to the index, then re-extracted every child
section from main's current file: 15 sections refreshed across 9
children. Verified line by line that all 792 content lines present on
main survive the split.

Re-extraction reverted three of the split's own repairs, which is the
known cost of that approach:

- four cross-page links fell back to same-page fragments
  (#regular-release-publish-automation, #stable-main-closeout); all
  repointed at the children that own those headings
- versioning.md's "see the dedicated workflow below" lost its target
  again and is a link to /reference/releasing/extended-stable once more

The link reverts are caught by docs-link-audit and markdownlint MD051.
The prose revert is caught by nothing and was found by hand.

Glossary: union keyed on source, 701 entries, 0 duplicate sources.

Full CI docs gate after staging: markdownlint 0 issues,
docs-link-audit --anchors 0 broken links, check-docs-mdx passed,
format-docs clean.

Still requires @openclaw/openclaw-release-managers approval.

* docs(reference): stub the anchors main added, and relink extended-stable

Both found by ClawSweeper on the rebased head.

Main added three headings to RELEASING.md while this branch was open:
Previous updater compatibility, Design proposal: immutable runtime
generations, and Required checks. Re-extracting moved their content to
preflight.md but added no compatibility stubs, so links such as
/reference/RELEASING#previous-updater-compatibility lost their target.
Added four stubs — the punctuated heading emits both an encoded and a
cleaned id, and both are now covered.

Anchor preservation applies to content main adds mid-flight, not only to
what existed when the split was planned. Verified against main's current
file: 24 pre-split ids, 24 resolving on the index, 0 lost.

Also restored the second extended-stable cross-page link. The earlier
rebase reverted two of them; I fixed versioning.md but missed
beta-latest-sequence.md, which still said the path was "documented at
the top of this page" after that path moved to its own child. An
orphaned directional phrase passes every validator, which is why this
one survived a full gate run.

markdownlint 0 issues, docs-link-audit --anchors 0 broken links,
check-docs-mdx passed, format-docs clean.

* Merge origin/main into docs-audit/split-releasing

Glossary conflict only; union keyed on source, 709 entries, 0 duplicate
sources. No page conflicted.

Ran the new .audit/check-orphan-refs.py over this tree: 0 directional
references remaining. That check exists because this branch shipped two
orphaned 'below' references past a full gate run, and they were found by
a reviewer both times.

markdownlint 0 issues, docs-link-audit --anchors 0 broken links,
format-docs clean.

Still requires @openclaw/openclaw-release-managers approval.

* Merge origin/main into docs-audit/split-releasing

This PR has been waiting on release-manager review, and main moved under
it. Refreshed so it is mergeable the moment the owners approve.

#142538 added 41 lines on extended-stable validation dispatch to
docs/reference/RELEASING.md. Resolved to the index, then re-extracted
five child sections from main's current file so that content survives.
Verified line by line: 817 content lines on main, all present after the
split. The one apparent gap is the Tideclaw alpha line, which is present
with its link repointed at the npm-workflow-inputs child.

Re-extraction reverted the split's own repairs again, both kinds:
- four cross-page links fell back to same-page fragments and were
  repointed at the children owning those headings
- two orphaned directional references came back and were relinked,
  in versioning.md and beta-latest-sequence.md

The link reverts are caught by docs-link-audit; the prose reverts are
caught by nothing and were found with .audit/check-orphan-refs.py.

Anchor check against main's current file: 24 ids, 24 resolving, 0 lost.
markdownlint 0 issues, format-docs clean, check-docs-mdx passed.
docs-link-audit --anchors reports only the 3 pre-existing maturity
failures that also fail on a clean origin/main.

Still requires @openclaw/openclaw-release-managers approval.

* Merge origin/main into docs-audit/split-releasing

Three conflicts, resolved as follows.

docs/reference/RELEASING.md: kept the index (ours). main's only change to
this page since the merge base is ca3bc36e1b, which rewrote one paragraph
of the `update-first-hop-compat` lane inside "Release preflight". That
section now lives in docs/reference/releasing/preflight.md, so main's hunk
was applied there verbatim rather than dropped.

test/scripts/package-acceptance-workflow.test.ts: took main's version of
both hunks -- the recursive docs/ci walk and the new set-read over
docs/reference/full-release-validation/ -- and kept readReleasingDocs() as
the reader for the release policy page. readReleasingDocs() now walks
docs/reference/releasing recursively with toSorted(), matching the pattern
and the rationale main established for docs/ci.

docs/.i18n/glossary.zh-CN.json: order-preserving union. 1119 sources from
main + 10 from this branch = 1129, 0 duplicates, both sides' orders
preserved as subsequences. The 10 new entries were moved out of the
end-of-array collision zone to sit beside the parent "Release policy"
entry.

Losslessness re-proved against current origin/main:docs/reference/RELEASING.md
(body sha256 9159abcad8cc2afe9a823570ca852fe912f66ed27c6c85a40d6576c595c51230):
all 12 top-level sections are byte-identical once the 6 declared link
rewrites are reversed; concatenated sections hash
66734f4ef0c88c6f2a7153de8708a5a58c058ed1561843babcdd0dec77410104 on both
sides. Code fences 25 -> 25 with an identical (info string, body sha256)
multiset; 0 tables; words 14,179 -> 15,020.

* Merge remote-tracking branch 'origin/main' into docs-audit/split-releasing

* origin/main:
  fix(channels): preserve labels for unloaded plugins (#143416)
  refactor(tests): share image resource acquisition setup (#143443)
  refactor(hooks): consolidate source precedence policy (#143439)

* docs(release): retain the original split history

The canonical release-policy split landed in #156946. Preserve the original branch ancestry while retaining only its missing legacy publication anchor on current source.

* commit 'f478156122dcc285acb7413024c4ecfc3101e0c8':
  docs(reference): stub the anchors main added, and relink extended-stable
  docs(reference): split the release runbook by reader job

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-09-26 17:48:15 +08:00
Peter Steinberger
3ed87e53da
perf(state): reduce host CPU for agent worker writes (#158750)
* perf(state): reduce repeated agent worker admission work

* fix(state): preserve Windows worker environment lookup
2026-09-26 02:46:36 -07:00
Peter Steinberger
6f6bc4dc53
perf(update): overlap independent retained runtime writes (#158766) 2026-09-26 02:44:27 -07:00
Peter Steinberger
6be19d7668
refactor(gateway): schedule initial lifetime maintenance (#158727)
* refactor(gateway): schedule secret-store expiry maintenance

* refactor(gateway): schedule GitHub publication maintenance
2026-09-26 02:43:22 -07:00
Sarah Fortune
dd9877e244
fix(agentsapi): retry event submissions after server errors (#158775)
* fix(agentsapi): retry event submissions after server errors

* style(agentsapi): format event submission retries

* test(agentsapi): find the final submission without filtering

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2026-09-26 09:37:42 +00:00
Peter Steinberger
2919fa6bdc
refactor(gateway): schedule idle work and prewarm (#158666) 2026-09-26 02:36:31 -07:00
Peter Steinberger
6209f31ce9
fix(update): name managed-service preflight refusals and stop trusting inherited service markers (#158314)
* fix(update): preserve refusal codes and verify service ancestry

* fix(update): preserve native service membership after reparenting

* test(update): provide native membership facts for rollback fixture

* fix(codex): use SDK error helpers for filesystem checks
2026-09-26 09:34:38 +00:00
Vincent Koc
6c0b013c57
fix(qa): share exclusive smoke flow partitions (#120374)
* fix(qa): serialize exclusive channel smoke flows

Compute channel exclusivity before choosing the suite route, coalesce shared channel cases, and preserve isolated-worker, retry, and evidence ownership. Replace mutable profile membership with stable boundary fixtures.

* fix(qa): share exclusive smoke flow partitions

Punchcard-Session: golden-valley-workshop-br

* fix(qa): preserve smoke profile launch topology

Punchcard-Session: golden-valley-workshop-br

* chore(qa): preserve original partition repair ancestry

The current-main repair supersedes the original three-file branch delta. Keep both original commits ancestral without reviving the removed channel-driver API or catalogue-dependent fixture. The candidate source tree is unchanged.

* refactor(qa): isolate suite infrastructure retries

Move the shared retry owner and its cleanup-cause regression together, preserving CLI and parity callers. Reuse the SDK object reader and reduce the retired assertion allowance. Keep the exclusive-worker fixture behavior while removing its local binding shadow.

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-09-26 17:33:26 +08:00
Peter Steinberger
483158831b
fix(ui): unblock sidebar navigation line-cap checks (#158778)
Compute the route session key inside its existing session-route guard, removing the duplicate predicate and temporary local without changing navigation behavior. The reviewed saved-pane head was within the cap; mainline import refactoring added two lines when the patches composed. Related: #158639.
2026-09-26 02:32:32 -07:00
leilei3167
8778b46ccf
fix: outbound plain-text replies drop unspaced comparison prose (#152671)
Closes #152508

## What Problem This Solves

Fixes: plain-text replies silently lose comparison prose between an unspaced identifier comparison such as `attempts<max` and a later `>`.

## User Impact

User impact: the reported comparison guidance arrives intact, while known, custom-element and qualified HTML tags retain their existing stripping behavior. Numeric comparisons such as `attempts<3` already worked. No configuration or public API changes.

## Why This Change Was Made

The shared sanitizer retains main's tag matcher exactly. A narrow exclusion preserves an unspaced left operand followed by attribute-free prose containing a conjunction (`and`, `or`, `且`) or clause punctuation and a numeric right operand, such as `attempts<max and wait>5s`. The comparison name must be a simple identifier, optionally followed by one sentence-ending period; internal dots, colons, and hyphens retain main's stripping. A matching closing tag prevents the exclusion. **Ayaan's decision: standard HTML element names take precedence over the comparison exception**, case-insensitively, using one local name list with no new dependency. This keeps `foo<span and wait>5` stripped to `foo5`.

Known limitation: unspaced comparisons without a conjunction or clause punctuation are still treated as markup, same as main. Comparisons using standard element names also retain main's stripping: `x<b and y>2` becomes `x2`. Main's `range a<b-c>d` → `range ad` behavior remains unchanged.

## Evidence

### Delivered text

Isolated Gateway turns used a scripted local OpenAI-compatible HTTP provider and the actual IRC plugin connected to a local TCP server. Captured `PRIVMSG` bytes are the delivered channel text, not a mocked sanitizer callback. The baseline used the exact sanitizer from main `8af7f3e640`; each run had separate state and a fresh inbound turn. This capture predates the matcher redesign; the differential proof below verifies the same prose outcomes with the redesigned owner.

Provider response:

```html
retry if attempts<3 and wait>5s
retry if attempts<max and wait>5s
<b>bold</b> <script>alert(1)</script> <a href="https://example.com">link</a><br><img src=x onerror=alert(1)>done
```

Before, delivered:

```text
retry if attempts<3 and wait>5s retry if attempts5s *bold* alert(1) link done
```

After, delivered:

```text
retry if attempts<3 and wait>5s retry if attempts<max and wait>5s *bold* alert(1) link done
```

IRC normalizes line breaks to spaces. Script contents remain plain text as before; script tags are removed. This is real Gateway/provider/IRC protocol proof against local scripted peers, not an external IRC account or Telegram Test Server.

### Telegram compatibility

Telegram (test server, current head): comparison text and markup handling unchanged vs main, so Telegram's DM path doesn't lose this text on main; the fix doesn't regress it. The red/green is the IRC plain-text capture plus the differential.

At `d56972eda33ee08cfdd580737538ecb511019baa`, an isolated source Gateway with `richMessages: false`, a scripted model, and a Convex-leased Test Server bot delivered this exact text to the real-user TDLib recorder:

```text
retry if attempts<max and wait>5s
bold
```

The provider supplied the comparison line followed by `<b>bold</b>` on the next line. The current-head reply arrived at 26.654 s; three `POST /v1/responses` requests were recorded. A second run substituting the exact main sanitizer (`59100c2655`) in the same source Gateway also received the same text, at 17.188 s. Each run used its own isolated state and live credential lease. Both runs deleted their one QA-user message and one SUT reply using the user's captured receipts before releasing the lease; both exited successfully with credential scratch removed. No bot tokens or chat IDs are included here.

The Telegram proof carries over: the channel path and issue example's output are unchanged; later predicate repairs retain the same six explicit prose differences in the expanded differential.

### Differential and focused checks

- Generated differential execution against the exact main sanitizer: **2,675 unique inputs, zero unexpected differences, six explicit non-element prose inputs preserved**. The 2,520-case cross product covers every known/custom/qualified/void name shape against all 12 attribute forms, including `and`, `or`, `and wait`, and `or wait`, plus word/numeric/empty/emoji/CJK content, word/space/start adjacency, and paired/unpaired forms. Existing literals and numeric-adjacent qualified-tag regressions are included. Exact differences remain only the two original issue paragraphs (operands `max` and `budget.`), `attempts<max and wait>5s`, the CJK `max` case, the emoji `limit` case, and the issue's `budget.` cross-fence paragraph; each is restored verbatim. All other 2,669 outputs equal main, including quoted `>` handling.
- Negative controls demonstrated the original comparison loss and each previous review regression before repair.
- Sanitizer: 141 tests passed. Shared delivery: 251 tests passed. Earlier unchanged-path checks: iMessage behavior 289; Telegram adapter five.
- Full-sanitizer adversarial smoke: 50 KB quote-overlap, 10 KB whitespace, 90 KB repeated attributes, and 90 KB comparison prose each completed below 5 ms locally.
- The malformed input `x<max` + whitespace + `= and wait>5` exposed overlapping regex quantifiers during final validation. The 40k-space regression failed at 1,046 ms before repair; disjoint whitespace/prose branches reduce the 10k/20k/40k full-sanitizer probes to 1.93/1.84/3.08 ms, comparable to main's 5.16/2.09/2.77 ms. Bounded 10k/40k regressions now pass (500 ms allowance), and the differential JSON is byte-identical.
- Targeted formatting and patch whitespace checks passed.
- The corrected regex-capture access passed all 25 selected core tsgo test graphs and the plugin-SDK declaration graph used by the failing extension-boundary lane. The 1,834-input differential output remained identical.

### Measured test cost

Same local Node 24.21.0 checkout and single-file command, `node scripts/run-vitest.mjs run src/infra/outbound/sanitize-text.test.ts`: main source/tests ran 103 tests in **14.90 s command wall time** (**12.52 s Vitest duration**, including transform/import). These are single observations, not a statistically controlled performance claim.

The final 141-test sanitizer file ran in **16.82 s Vitest duration**, versus **12.52 s** for main's 103-test file: an observed **+4.30 s** difference, with 91% of the final run spent in transformation. The combined sanitizer, delivery, and targeted-format command took **28.72 s**. These runs include differing post-merge worker graphs, so this is observed wall cost, not a causal CPU-cost estimate.

Hosted CI: the 138 sanitizer cases passed in [run 36224078489, `checks-node-changed-compact-large-39`](https://github.com/openclaw/openclaw/actions/runs/36224078489/job/108355098309). Their reported execution durations sum to **0.037 s** in the Bun `core-unit-fast-2` lane, at millisecond reporting precision. That sum excludes import, transformation, and job setup and is not presented as whole-file wall time. This run preceded the type-only correction; its separate type failures were subsequently fixed and verified locally in the corresponding graphs.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-26 14:59:26 +05:30
Peter Steinberger
4543ce2515
fix(plugins): recover missing retained capture directories (#158769)
Recreate a lost payload directory under its existing native custody lease, without recursively recreating the coordinator parent. Preserve reference counting and advisory cleanup. Refs #158712; thanks @yihan331313.
2026-09-26 09:25:15 +00:00