Commit Graph

11577 Commits

Author SHA1 Message Date
Peter Steinberger c6d499afaf docs: cover sqlite3_rsync pull replication in the backups guide (#129820)
* docs: cover sqlite3_rsync pull replication in the backups guide

Add sqlite3_rsync as a periodic incremental pull path alongside Litestream:
where to get it (sqlite-tools bundles; package managers often omit it),
live-origin safety via read transaction on WAL databases, and the caveat
that OpenClaw's incremental auto-vacuum maintenance relocates pages and
inflates page-level deltas right after vacuum or large-deletion windows.
Verified live against a real state database: quiescent re-sync exchanged
~4 KB for a 4.7 MB database, appends transferred at data size, and a
post-vacuum sync resent most of the file.

* docs: gate replication guidance on the actual origin journal mode

OpenClaw only uses WAL on local filesystems; network-backed storage (NFS,
SMB, cross-VM) deliberately falls back to rollback journaling before WAL
setup (src/infra/sqlite-wal.ts). Qualify the WAL claims in the
sqlite3_rsync and Litestream sections and add a journal-mode preflight.
Addresses the ClawSweeper P2 finding.
2026-08-25 20:36:48 -07:00
Peter Steinberger 20b453f155 fix(agents): reject forged plugin owner authority (#104872)
* fix(security): bind channel owner authority to trusted runtime

* fix(plugins): preserve untrusted channel guest ingress

* fix(plugins): snapshot channel ingress authority once
2026-08-25 20:36:05 -07:00
Peter Steinberger 9ab6df9f19 fix(openshell): remote workspace is never seeded when the gateway restarts between sandbox create and first exec (#129809)
The remote-mode seed obligation lived only in process memory
(remoteSeedPending), so a gateway restart between sandbox create and the
first exec adopted the sandbox via 'sandbox get' and silently skipped the
one-time seed, leaving an empty remote-canonical workspace with no visible
failure.

Adopted remote sandboxes now probe the managed roots once per process at
the ensure boundary and arm the seed only when every root is missing or
empty, so recovery can never wipe operator content in an already-seeded
workspace (the seed guard shipped in v2026.7.1, so legacy seeded sandboxes
without any marker exist). The probe also heals a create that timed out
client-side after the sandbox actually came up.

Regression test simulates create -> restart -> first exec and fails
pre-fix; a sibling test locks the never-reseed-non-empty-roots invariant.
2026-08-25 20:32:40 -07:00
Peter Steinberger 54b0f9ff85 fix(release): prevent empty VCR mirror inputs (#129467)
* fix(release): preserve VCR mirror source digests

Transport only attestation-verified digests across secret-scanned job outputs, reconstruct immutable GHCR refs inside the VCR mirror, and add an approved mirror-only recovery path.\n\nCloses #129466

* fix(release): verify VCR recovery sources

Revalidate attestations and release-version labels before any VCR registry write so manual recovery preserves the immutable source boundary.

* test(release): keep VCR regression scoped

Leave global workflow-to-test routing cleanup for a follow-up; this PR directly changes and runs both VCR regression suites without forcing metadata-complete CI.

* fix(ci): preserve caches after warmer failures

Finish every selected cache-warm group, save content-keyed transform and compile caches, then fail visibly after the save steps. Ordinary CI remains fail-fast.
2026-08-25 20:15:42 -07:00
Peter Steinberger 422d6818e4 refactor(workers): unify cloud bootstrap lifecycle (#129719)
* refactor(workers): unify cloud bootstrap lifecycle

* fix(workers): preserve cloud setup and repair qa fixtures
2026-08-25 20:11:33 -07:00
sashankh 93573e8fdf fix(models): --agent is silently ignored by models aliases and models scan (#126864)
* fix(models): reject --agent on aliases and scan instead of ignoring it

models aliases list/add/remove and models scan never read the parent
--agent option, so an operator scoping one of them to an agent got no
feedback that the flag did nothing, and a typo'd or nonexistent agent id
was never validated. They only read or write agents.defaults.*, so there
is no agent-scoped path for the flag to feed.

Reuse the existing set/set-image guard rather than inventing a new
mechanism: rename it to rejectAgentScopedModelCommand, give each
global-only command an accurate scope phrase, and call it before the
command runs. fallbacks/image-fallbacks are deliberately untouched;
they are tracked separately in #106346.

Fixes #126597

* fix(models): drop release-owned changelog entry

* fix(models): simplify global-only scope message

Review flagged the production LOC delta. Collapse the runtime scope map
into a type-only union and one accurate message covering both the read
(aliases list) and write commands; net production delta is now +20.

* fix(models): state the guard scope accurately for scan

Review finding: the shared message claimed every guarded command 'only
reads or writes global model config', but models scan --no-probe prints
the catalog and returns before any config update (scan.ts:283).

Say what is true of all six instead: they are global and never
agent-scoped. Docs line updated to match.

---------

Co-authored-by: sashankh <saisashankhd@microsoft.com>
2026-08-25 19:24:25 -07:00
Josh Avant 1c37c8cdc7 fix(exec): scope reusable approvals to their working directory (#129636)
* fix(exec): bind durable approvals to working directory

* chore(apps): refresh native string inventory

* test(node-host): preserve prepared working directory

* fix(exec): use shared path safety facade

* fix(exec): revalidate approved directory identity
2026-08-25 18:24:14 -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 91cc37b788 fix(gateway): stop fabricating scope-upgrade requests on trusted-proxy reconnects (#129563)
* fix(gateway): stop fabricating scope-upgrade requests on trusted-proxy reconnects

Same-key trusted-proxy reconnects whose auto-approvable scopes are already
covered by the paired grant now narrow the session in place instead of
running the pairing request/approve machinery: no pending-store writes and
no false 'device access upgrade requested' / 'auto-approved' security-audit
warns on every Control UI page load. The upgrade audit warn moved into
requirePairing so it fires only when a pairing request is materialized.
Durable widening (broadened deviceAutoApprove.scopes) and key-mismatch
repairs keep the existing lanes.

* fix(gateway): revalidate the live paired grant in the reconnect fast path

Address ClawSweeper P1: the narrowing decision now rereads the
authoritative paired row as the last await before returning, so a
concurrent revoke, key replacement, or grant reduction fails closed into
the pairing lane instead of riding the pre-plan snapshot. Extract the
pairing approval-plan resolver into connect-pairing-approval-plan.ts to
keep the authorization owner under the 700-line lint boundary.
2026-08-25 17:59:51 -07:00
Erick Kinnee cbdfedee84 fix(plugins): scope runtime catalog discovery by provider (#129624)
* fix: scope provider catalog discovery by selected identities

* fix(plugins): enforce agent-scoped live catalog discovery

Co-authored-by: Erick Kinnee <ekinnee@gmail.com>

* chore: keep provider release notes in pull request context

---------

Co-authored-by: Erick Kinnee <ekinnee@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 17:37:31 -07:00
Peter Steinberger 6f61184d1e fix(telegram): support polls lasting up to seven days (#129461) 2026-08-25 17:36:11 -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 66a939526e fix(skills): bound installer downloads (#129630)
* fix(skills): bound installer downloads

Direct skill dependency response bodies are capped at 256 MiB while streaming, preventing headerless or broken endpoints from consuming unbounded staging disk while preserving supported bundled artifacts.

Refs #81817

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>

* fix(skills): reject oversized declared downloads

Cancel trustworthy identity Content-Length values above the shared 256 MiB
ceiling before opening the staging writer. Keep encoded or malformed
declarations on the authoritative decoded-stream byte cap.

Refs #81817

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
2026-08-25 17:17:35 -07:00
Peter Steinberger df962dd559 fix(cli): emit JSON failures when existing config is invalid (#129578)
* fix(cli): preserve invalid-config failures in JSON output

* test(cli): assert structured invalid-config process failures
2026-08-25 17:17:26 -07:00
Peter Steinberger 85d5df1d87 fix(onboarding): OpenAI setup installs mismatched Codex plugin (#129195)
* fix(onboarding): align Codex plugin with core release

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b4-f621-7144-bfdf-a68df795dd57

* test(onboarding): reject unpinned Codex candidates

Amp-Thread-ID: https://ampcode.com/threads/T-01a037b4-f621-7144-bfdf-a68df795dd57

* fix(onboarding): match stable correction release plugin cohorts

Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>

* test: isolate plugin metadata and register startup retry coverage

* test: adopt upstream startup retry ownership pattern

* test: keep plugin metadata readers process-stable

* test: isolate terminal session recovery cases

* test: drop speculative gateway isolation change

* test(gateway): mock terminal transcript lifecycle decision

* fix(plugins): align correction cohorts across stable tracks

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>
Co-authored-by: Dallin Romney <dallinromney@gmail.com>
2026-08-25 17:03:39 -07:00
Peter Steinberger b5864ba1db fix(skills): fail closed for unavailable remote Gateway (#117567)
* fix(cli): respect explicit Gateway ownership

* fix(gateway): fence offline CLI fallbacks

* fix(gateway): restrict local recovery closes

* fix(cli): preserve offline Gateway ownership and log recovery
2026-08-25 16:58:39 -07:00
Peter Steinberger 9b2831afd5 feat(buzz): authorize different senders in each room (#129655)
* feat(buzz): support room-scoped sender authorization

* fix(buzz): synchronize generated official channel catalog
2026-08-25 16:52:42 -07:00
Peter Steinberger 9e14c2c5b7 docs(plugins): use public sdk types in backend example (#129646) 2026-08-25 16:40:51 -07:00
Peter Steinberger 948ae80ae6 fix(onboard): preserve JSON errors when no terminal is available (#129437) 2026-08-25 16:31:09 -07:00
Peter Steinberger 473b4f19e3 feat(approvals): scoped standing grants make recurring cron automations approvable once (#129526)
* feat(approvals): mint scoped standing grants for cron allow-always

When an operator resolves allow-always for an approval raised by a cron
job's isolated run, the Gateway now mints a scoped standing grant in the
same SQLite transaction that resolves the approval, instead of writing an
unbounded command digest into the JSON allowlist. Subsequent occurrences
of that job execute the exact approved operation (command text, cwd, env
hash) without prompting while the grant revalidates against authoritative
rows: 30-day expiry, revocation, the cron job still existing with the same
config revision, and the minting approval row still holding allow-always
all fail closed back to the normal prompt. Non-cron allow-always behavior
is unchanged.

- New first-use lazy STRICT table operator_approval_standing_grants in the
  shared state DB (declared canonically, no schema-version bump; older and
  downgraded readers stay valid without it).
- The cron run owner records run -> {agent, job, config revision} in a
  process-local registry at run start; exec.approval.request stamps the
  cron source and exact operation binding onto the approval at creation,
  so nothing is ever inferred from session keys or run ids.
- The gateway exec host consults grants only when policy would prompt;
  ask=always, security=deny, mutable file operands, heredoc, strict
  inline-eval, and audit-suppression approvals keep prompting. Grant use
  updates last_used_at_ms/use_count and emits the exec approval security
  event with the grant and minting approval as lineage.
- Abort-wins guard: a run with an abort tombstone never mints.

* feat(approvals): deliver cron exec approvals to approval clients and wait inline

The standing-grant mint path was unreachable end-to-end: #128031 made
cron approval requests register with delivery fully suppressed, so the
shared owner expired them as no-approval-route within milliseconds, and
even a delivered card would have died seconds later when the isolated
run finalized on the approval-pending handoff and authority-close
cancelled the parked approval.

Cron approval requests now carry deliverToApprovalClientsOnly: the
shared delivery owner broadcasts them to connected websocket approval
clients (Control UI, TUI) but skips internal chat approval runtimes,
forwarder/iOS delivery, and turn-source routes, so the per-occurrence
chat spam #128031 removed stays removed. With no approval client
connected, the request still expires no-route into the existing
headless denial. The gateway exec host additionally waits inline for
cron-triggered approvals (the same treatment native chat channels got
in #93918), keeping the isolated run and its delegated authority alive
for the full approval window; cron jobs are single-flight, so at most
one card per job is pending at a time and allow-always ends the
recurrence by minting the standing grant.

Live-proven on a hermetic gateway: card delivered with ~30-minute
window, run waited 72s for the operator click, allow-always executed
the occurrence and minted the grant with no JSON allowlist digest,
the next occurrence ran promptless (use_count 1, no new approval row),
and editing the job failed closed back to a fresh prompt.

* chore(protocol): regenerate Swift models for deliverToApprovalClientsOnly

* fix(approvals): consume standing grants at the spawn boundary; keep node cron headless

Review findings from ClawSweeper on #129526:

- Grant authority is now recorded only at the final effect. The consult
  path validates without recording a use and returns a
  revalidateBeforeExecution closure (the mutable-file-binding seam) that
  consumes the grant immediately before runExecProcess; any invalidation
  during awaited pre-spawn work (job edit/delete, revocation, parent
  approval reversal) denies with next-step text instead of executing on
  stale authority. Regression proves consult leaves use_count at 0 and a
  reversed minting approval denies at the boundary.
- Cron approval-client delivery is scoped to host=gateway. Node-host cron
  cannot mint or consume grants yet, so it keeps the fully suppressed
  headless policy from #128031 instead of raising cards whose allow-always
  could not stick; node-host grant support stays a named follow-up.

* test(agents): complete plugin-metadata-snapshot mock factories

Five explicit vi.mock factories for current-plugin-metadata-snapshot.js
exported only getCurrentPluginMetadataSnapshot. Under isolate:false shard
composition the incomplete mock can bleed into siblings that import the
real module — model-resolution-consistency.test.ts failed on CI with
'No withPluginMetadataSnapshotScope export is defined on the mock'.
Spread importOriginal so every binding prod touches stays exported, per
the repo mock-factory rule; only the snapshot getter stays overridden.

* chore: drop accidentally committed pinned swiftlint binary; ignore .build/

scripts/install-swift-tools.sh installs pinned Swift tools into
.build/swift-tools per the lint-swift.sh remediation hint; the 36MB
binary must never ride a commit. Remove it and ignore the directory.

* test(agents): shield model-resolution-consistency from leaked snapshot mocks

The agents-embedded shard still failed after completing five factories:
~20 more test files across the repo mock current-plugin-metadata-snapshot
with incomplete explicit factories, and under isolate:false composition
any of them can strip withPluginMetadataSnapshotScope from this file's
imports. Give the victim a file-local identity mock (importOriginal
spread) that always wins, and revert the static-catalog factory edit that
tipped that grandfathered file over the max-lines cap — the repo-wide
factory completion belongs to a dedicated sweep.
2026-08-25 16:17:58 -07:00
Josh Avant ede9775941 fix(gateway): isolate portal cookies by lifetime (#129503) 2026-08-25 16:11:30 -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 3626a8e18e fix(agents): enforce session permissions for shared terminal input (#129604)
* fix(agents): enforce session permissions for shared terminal input

* chore: keep terminal release notes in pull request

* fix(agents): fence full terminal input to active run authority
2026-08-25 15:59:28 -07:00
Peter Steinberger df9b7a5fbe fix(ui): show accessible last activity on session progress cards (#129520)
* fix(ui): show accessible last activity on session progress cards

* fix(ui): validate progress activity timestamps at ingress
2026-08-25 15:47:56 -07:00
Leon-SK668 0db1d89d05 fix(backup): preserve full backups with managed runtime symlinks (#124821)
* fix(backup): skip managed runtime symlinks

Keep full backup creation fail-closed for arbitrary symlinks while omitting and reporting regenerable browser, sandbox, plugin-skill, and agent-runtime paths. Verify relative links remain within declared backup assets across create and restore.

Closes #124769

Co-authored-by: Leon-SK668 <0668001470@xydigit.com>

* test(codex): route startup retry coverage

* test(codex): align startup inventory with main

* test(agents): complete plugin metadata snapshot mock

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 15:16:57 -07:00
Vincent Koc c3eca4f508 fix(sqlite): index agent session lookups (#129569) 2026-08-26 05:59:25 +08:00
Peter Steinberger 34067fcc2e fix(worktrees): preserve nested repositories during cleanup (#129454)
* fix(worktrees): preserve nested repositories during cleanup

Automatic idle, limit, and run-end cleanup now treats nested repositories and linked worktrees as separate lifecycle boundaries. This prevents repeated cleanup warnings and avoids deleting unregistered nested user state.

Closes #129414

* test: isolate media auth fixture from plugin loading

Keep the OAuth fixture outside the refresh window and stub only the unrelated retired-profile plugin lookup so the media auth contract completes deterministically in CI.
2026-08-25 14:16:44 -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
Josh Avant f9dbda38dc fix(mcp): revoke resource access with app authority (#129550) 2026-08-25 14:04:14 -07:00
Peter Steinberger a6ebad9964 fix(worker): honor full access on paired node sessions (#129537)
* fix(worker): honor full access on paired node sessions

* test(worker): preserve narrowed operator in live proof

* test(vitest): assign Codex startup retry to its owner shard
2026-08-25 14:01:39 -07:00
Sasan e9620fba9d fix(gateway): usage.status no longer waits on provider HTTP (#121799)
* fix(gateway): refresh provider usage asynchronously

* fix(ui): report a stalled provider-usage refresh on Model Providers

The page observed the incomplete-usage marker but discarded the exhausted
outcome, so once the retry budget was spent it rendered ordinary provider
cards with no usage and no explanation — indistinguishable from providers
that report no usage at all. Keep the outcome and render the warning the
Usage page already owns, reusing usage.providerUsage.stalled rather than
minting a Model Providers key so no locale baseline churns.

A user-initiated refresh now restarts the retry budget. The notice tells the
operator to refresh, so the button has to hand back attempts to spend; only
the forced path resets it, or the budget could never exhaust.

Also fixes tsgo:core:test on the current head: createStore's inferred literal
had no usageStats, so the run-bookkeeping case could not stamp it, and
view.test.ts needed the new prop.

Closes the ClawSweeper P2 at model-providers-page.ts:169-175.

* fix(ui): keep the stalled usage notice when usage.status starts rejecting

loadModelProvidersData turned a rejected usage.status into providerUsage:
null, which the page read as a completed load. observe(false) then reset the
retry budget and cleared the stalled callout, so a permanently broken usage
endpoint rendered as ordinary cards with no usage and no explanation — the
same silent failure the callout was added to prevent. The reset also fired
mid-cycle: one incomplete response followed by one rejection restarted the
budget, so the notice could be deferred indefinitely.

Record the failure at its producer instead of inferring it downstream. A null
providerUsage also means "not loaded yet", and no caller can tell the two
apart, so load.ts now reports providerUsageFailed explicitly and the page
treats a failed read as unresolved rather than resolved-empty.

Found by a Codex review of 417d43b65d.

* revert(gateway): drop the opportunistic model-catalog fast path

It broke two chat.history tests on main — both assert the cold catalog loader
runs exactly once, and reading the prepared snapshot first means it never does.
checks-node-compact-small-10 was red for that reason.

The change was a separate-surface latency fix that this PR picked up in passing,
and the body already offered to split it. Dropping it is the honest resolution:
rewriting main's assertions to accommodate a drive-by optimization would trade
one concern's proof for another's convenience. optional-model-catalog.ts,
server-model-catalog-auth.ts and their test return to the merge-base.

This PR is now only the usage.status non-blocking contract and its clients.

* fix(usage): preserve incomplete retry state

* perf(ui): keep usage capability startup-neutral

* fix(ui): restore provider usage retry convergence

* fix(usage): restore retry and cache invariants

* fix(usage): stabilize provider convergence

* test(ui): exercise provider recovery path

* test(ui): remove stale usage route fixture field

* fix(macos): show provider usage errors

* fix(macos): bound usage retries per menu open

* fix(macos): end usage retries on menu close

---------

Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>
2026-08-25 13:20:10 -07:00
Vincent Koc 333ef0f2ce docs(sqlite): align database-first guidance (#129571) 2026-08-26 03:43:01 +08:00
Dallin Romney 1d526c5c0e refactor(qa): adopt Crabline server artifacts (#118008)
* refactor(qa): adopt Crabline server artifacts

* fix(deps): restore Crabline release-age guard
2026-08-25 10:38:28 -07:00
Ayaan Zaidi b0c27e2d8f fix(skills): fork the foreground session for lean experience review (#129282)
## What Problem This Solves

Skill Workshop experience review ran as an isolated agent with a re-rendered 60k-character transcript, its own bootstrap, and a trimmed tool surface. Every review was a cold request: no prompt-cache reuse, a large re-serialized trajectory, and a reasoning/tool profile that differed from the foreground turn. Autonomous updates could target any workspace skill, size limits allowed 40 KB skills to grow unchecked (one deployed workspace hit a 21 KB `SKILL.md`), the collection reviewer had to re-list every skill in its plan and read them under a fixed 24k-character budget (a 16-skill workspace failed every pass with "Read every current skill before reconciling"), collection review only recorded successes so a failing pass retried on every Gateway restart, and `openclaw skills curator status` showed nothing about what the last reviews did.

## Why This Change Was Made

- **Warm fork.** Experience review now continues the finished turn from the same in-memory session prefix (same session identity, bootstrap, skills prompt, tool schemas, `promptCacheKey`), appends one short review message, and runs with `sessionPersistence: "detached"` so nothing it writes reaches the foreground transcript or session record. Only `skill_workshop` executes; every other tool keeps its schema for cache parity and fails with a next-step message. The gate wraps the core tool list before Code Mode / Tool Search compaction, so catalog-hidden tools stay gated while `tool_call`/`exec` remain callable. `src/agents/embedded-agent-runner/run/attempt.skills-policy.test.ts` asserts identical system-prompt and tool digests between a foreground turn and its review, and that `tool_call` reaches `skill_workshop` but not `read` during review. Code Mode swarm globals (`phase`, `log`, `agents.run`) are the `sessions_spawn` capability and gate on the same allowlist, so a review cannot emit foreground lifecycle events or launch collectors. The review shares the foreground session, so it no longer retires that session's MCP runtime on run end; it reuses the warm runtime like any turn. Accepted tradeoff: the review inherits foreground tool construction (it creates the session MCP runtime only when the session has none, and spawns per-attempt LSP servers only when configured), because splitting tool construction from the foreground turn would break the cache-parity invariant this PR exists for.
- **One apply owner, rechecked at the write boundary.** `src/skills/workshop/autonomous-apply.ts` decides apply-vs-pending for both the post-review loop and foreground repair: creates and updates to Workshop-authored skills apply; updates to user-authored skills stay pending with a recorded reason. `applySkillProposalTransition` rechecks path-based ownership under the commit lock for non-operator actors (agent, or no actor), so a claim released after the pre-check cannot let an agent write a user-authored skill; gateway and CLI operators still approve any pending proposal. The user-authored pending write runs under the same commit lock and rereads the row, so an operator apply that lands first is kept. The old `auto-apply.ts` path and name-based `listWorkshopAuthoredSkillNames` are deleted.
- **Sparse collection plan and model-relative reads.** The collection reviewer returns only `write` and `drop` entries; unlisted skills stay untouched, so a 16-skill workspace no longer has to restate 16 keeps. The skill read budget is derived from the model's context window (35%) instead of a fixed 24k characters, with the 10,000-character skill cap still enforced per skill. No windowed read parameters: a skill is read whole or not at all.
- **Weekly cadence.** Collection review runs once every 7 days instead of daily (`REVIEW_INTERVAL_MS`), matching how slowly a skill library actually changes; the attempt is recorded before the model starts, so a failed pass does not retry on every restart.
- **Lean cap.** `AUTONOMOUS_SKILL_MAX_CHARS = 10,000` in `src/skills/workshop/collection-contracts.ts` is enforced by the tool and by collection reconcile; an oversized skill may only shrink. Tool description, experience prompt, and collection prompt were rewritten around procedures-not-records, one trigger per branch, and "NOTHING_TO_LEARN is the correct answer for most turns".
- **Detached runs end quietly.** A detached run writes no transcript or session record and runs under the foreground session key, so `attempt-finalize` now skips agent_end side effects for it: plugins do not observe the review as a foreground turn, and a deep review cannot schedule a successor review of itself.
- **Recorded outcomes.** Experience review records `applied | proposed | nothing | failed` with usage; one attempt per turn, drop on error. `openclaw skills curator status` prints the last collection and experience review outcome.

## User Impact

- Default `auto` mode: experience review reuses the foreground prompt cache and skips message/other tool execution, so review cost is one bounded continuation instead of a cold re-render.
- Autonomous edits touch only Workshop-authored skills; handwritten skills surface as pending proposals for operator approval.
- Autonomous `SKILL.md` results stay ≤ 10,000 characters.
- Collection review runs weekly and succeeds on larger skill libraries. A workspace with no recorded attempt reviews at the first daily check after Gateway start; an upgraded install keeps its recorded last attempt and reviews once it is older than 7 days. The 10,000-character cap applies to the next autonomous write; existing oversized skills are only ever shrunk.
- `openclaw skills curator status` shows the last collection and experience review outcome, time, and tokens.
- Docs: https://docs.openclaw.ai/tools/self-learning, https://docs.openclaw.ai/tools/skill-workshop

Related: #123866, #128871 both patch skills above the reviewer read budget; this PR caps autonomous skill size and restricts oversized skills to shrink-only rewrites.

## Evidence

- Live gateway (Linux, Telegram, `xai` provider) at `133ffe3`: manual experience review on a real foreground turn → `nothing` (usage: 0 uncached input, 13,902 cached, 36 output — the forked review hit the foreground prompt cache); manual collection review over the workspace → `succeeded`; the three oversized skills were rewritten under the cap (21,002 → 9,449; 11,882 → 7,735; 10,300 → 7,413 bytes, originals in `skill-workshop/collection-backups/`), the rest untouched, and the pre-PR "Read every current skill before reconciling" failure is gone. The next foreground turn's automatic experience review recorded `nothing` and `openclaw skills curator status` shows both outcomes. The apply recheck, MCP-runtime change (`26e821f`), and detached agent_end skip (`7e4a506`) landed after that run and are covered by the regression tests below.
- `pnpm test src/skills/workshop src/agents/tools/skill-workshop-tool src/gateway/server-methods/skills` plus `src/agents/embedded-agent-runner/run/attempt.skills-policy.test.ts`, `src/agents/embedded-agent-runner/run/attempt.tool-search-catalog-abort.test.ts`, `src/cli/skills-cli.curator.test.ts`, `src/agents/harness/tool-surface-bridge.test.ts` — green. New regression tests (`service.test.ts` agent-vs-operator apply on a user-authored skill and operator apply kept over a stale pending snapshot; `experience-review.apply.test.ts` no `cleanupBundleMcpOnRunEnd`; `attempt-phase-lifecycle.test.ts` no agent_end for a detached run; `code-mode-swarm.test.ts` swarm globals refused under the review allowlist) fail on the pre-fix code.
- Dependent sweep: 46 test files importing the touched modules — green.
- `oxfmt`, `scripts/run-oxlint.mjs` on changed files, `git diff --check` — clean.
- `pnpm tsgo && pnpm check:test-types` on Blacksmith Testbox — clean.
- Local ClawSweeper review (`gpt-5.6-terra`, high): `133ffe3` raised two findings (ownership recheck removed from the apply boundary; review retiring the shared session's MCP runtime), fixed in `26e821f`; `26e821f` raised one (detached review re-entering agent-end scheduling), fixed in `7e4a506`; `7e4a506` raised three: pending write racing an operator apply and Code Mode swarm globals bypassing the execution gate, both fixed in `e4b4322`; "prove detached review does not start configured MCP or LSP runtimes" is skipped as the cache-parity tradeoff stated above (LSP tool schemas come from the LSP runtime; the MCP runtime is session-owned and already warm). Maintainer decision on `e4b4322`: accepted — a detached review reuses the session MCP runtime and re-spawns configured per-attempt LSP servers exactly like a foreground turn; it still cannot execute them.
- Unrelated CI on `26e821f`/`7e4a506`: `check-lint-core-2` (`max-lines` in `src/gateway/server-methods/models-list-result.ts`, unused param in `models.test.ts`, both from #129332) and `checks-node-compact-small-8` (`doctor-auth.profile-health.test.ts`, Claude CLI auth from #129052) fail identically on `main` run 32857954734; `checks-node-compact-small-31` pins plugin SDK export counts (`4340` vs `4342`) that #129052 moved — this PR touches no `src/plugin-sdk` file. On `e4b4322` the failing set is the same twelve `checks-node-compact` shards that fail on `main` run 32857954734 (`large-5/12/13/14/15/18/22`, `small-8/14/20/21/26`) plus `small-31`; `check-lint-core-2` passes here. None are fixed here; #129357 carries the `main` fix.
- LOC (raw numstat): production +712 / −664 (net +48; the four review-fix commits after the live run add +76 / −26); tests + docs +1269 / −1238 (net +31).
2026-08-25 20:27:25 +05:30
Ayaan Zaidi 750a64e7cd fix(anthropic): keep Claude CLI authentication native (#129052)
Stop OpenClaw from copying or refreshing Claude CLI OAuth tokens.
Claude CLI now owns native login and refresh state; Doctor removes retired copies while preserving CLI routing.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-08-25 17:18:06 +05:30
Peter Steinberger 22fa9f5786 fix(linux): first-run desktop onboarding automatically connects working AI (#129211)
* fix(linux): make desktop AI onboarding work automatically

* fix(linux): harden provider ownership and onboarding lifecycle

* test(ui): document partial onboarding context fixture

* fix(linux): preserve operator-owned onboarding API keys

* fix(linux): retire previously managed provider credentials

* fix(linux): retain rotated onboarding provider credentials
2026-08-25 04:47:35 -07:00
ruel225 34a1cfc69c fix(cli-runner): drop stock watchdog defaults that disable resume promotion (#125045) (#125085)
* fix(cli-runner): drop stock watchdog defaults from CLI backend descriptors (#125045)

The claude-cli and gemini-cli backend descriptors spread
CLI_FRESH/RESUME_WATCHDOG_DEFAULTS into config.reliability.watchdog.
Those copies are byte-identical to the fallback pickWatchdogProfile
already uses, so shipping them only makes `configured` always truthy,
which permanently disables the promotion gate (!configured) and pins
resumed cron/explicit-timeout turns to the 180s resume no-output ceiling
instead of the 600s fresh ceiling.

Removing the blocks lets `configured` be undefined, restoring the
promotion gate. No user-config path is affected: reliability.watchdog
is not exposed in the config schema, the descriptor is the sole source,
and pickWatchdogProfile is the only runtime reader. The codex backend
never shipped the block, so this aligns the three backends.

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

* test(cli-runner): add descriptor-through-resolver watchdog proof (#125045)

Drive the real shipped Anthropic/Gemini CLI backend descriptor config through
the production resolveCliNoOutputTimeoutMs resolver to prove the resumed-cron
promotion is live post-fix. Pre-fix the descriptor shipped byte-identical
resume defaults, so configured was truthy and the !configured promotion gate
was dead, pinning resumed cron turns to the 180s resume ceiling (180000).
Post-fix the block is gone and the resolver returns the promoted fresh budget
(480000 = 600000 * 0.8).

resolveCliNoOutputTimeoutMs is re-exported through the test-only
openclaw/plugin-sdk/test-fixtures subpath (excluded from published dist), so
extension tests can drive the real descriptor through the real resolver without
crossing the extension/core boundary.

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

* test(anthropic): split watchdog promotion proof into its own file

Move the descriptor-shape and descriptor→resolver real-behavior proof
tests out of cli-shared.test.ts into cli-watchdog-promotion.test.ts.
cli-shared.test.ts exceeded the extensions max-lines budget (1000
non-blank/non-comment lines) once the proof tests landed; the focused
file keeps the same test names and coverage while staying under the
budget. No production change.

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

* fix(cli-runner): restore inherited resume watchdog promotion

Validate bundled plugin descriptors through their public setup entries and keep custom watchdog contracts intact.

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

* chore(cli-runner): align conflicting provider owner with main

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

* fix(cli-runner): complete inherited resume watchdog promotion

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

* test(cli): keep watchdog regressions in their owning plugins

Keep Anthropic and Google descriptor assertions inside their plugin-owned registration suites, preserve generic resumed/custom watchdog coverage in its core owner, and remove the cross-plugin core test rejected by architecture CI.

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

---------

Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 04:14:45 -07:00
Peter Steinberger 33d8f24c99 refactor(agents): move live model policy to test owner (#129184)
* refactor(agents): move live model policy to test owner

* test(agents): retain dynamic live model policy map types
2026-08-25 03:41:36 -07:00
Peter Steinberger 044c0feb7c fix(crabbox): preserve Machine0 provisioning budget (#128185)
* fix(crabbox): bound Machine0 lifecycle backoff recovery

* fix(crabbox): reserve full Machine0 cleanup budget

* fix(crabbox): allow paced Machine0 inspection

* fix(crabbox): reserve Machine0 readiness retry

* fix(crabbox): pace Machine0 readiness checks

* fix(crabbox): use readiness-aware Machine0 status

* feat(crabbox): forward explicit setup environment

* fix(crabbox): preserve bounded command failure context

* fix(crabbox): enforce exact setup environment
2026-08-25 03:41:06 -07:00
Peter Steinberger 452e734022 fix(cli): unsuccessful message sends report success (#129202)
* fix(cli): report unsuccessful message sends accurately

* fix(cli): preserve narrowed send result for failure JSON

* chore(pr): leave release-owned changelog unchanged
2026-08-25 03:27:12 -07:00
Peter Steinberger b9d522738d fix(tasks): blocked completion outcomes cannot be filtered (#129196)
* fix(tasks): make blocked completion outcomes filterable

* refactor(tasks): remove unused raw status export

* test(tasks): update invalid status filter expectations
2026-08-25 03:02:11 -07:00
Peter Steinberger fda20a6ed2 fix(systemd): honor effective drop-in configuration (#129170)
* fix(systemd): honor effective drop-in configuration

Read one coherent manager-loaded service snapshot while retaining the managed base definition for repairs. Track drop-in ownership, surface pending daemon reloads, and keep update and doctor rewrites from adopting operator-owned values.

Closes #128929

* fix(systemd): expose reload status in service snapshot

* test(systemd): cover Linux doctor ownership
2026-08-25 02:30:34 -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 24898ba894 fix(workers): repair cloud session lifecycle and recovery (#121564)
* fix(workers): repair cloud session lifecycle and recovery

* test(workers): preserve dual-mode provider dispatch coverage

* refactor(workers): centralize inherited profile authorization
2026-08-25 01:48:57 -07:00
Peter Steinberger 6885eb69b0 fix(telemetry): suppress reporting from automated environments (#129155)
CI jobs are not installs; unchecked they outnumber operators by orders of
magnitude and make version and platform counts meaningless. A configured
telemetry endpoint still reports, so update-path E2E lanes keep working.
2026-08-25 01:37:48 -07:00
Peter Steinberger 5b5fdacfda feat(ui): dock the session progress card beside a wide composer (#129141)
* feat(ui): dock the session progress card beside a wide composer

The progress card had two placements: the companion rail when that side
panel is open, otherwise a collapsed one-line bar stacked inside the
composer box. On a wide chat the composer stays centered at the
transcript width, so the space either side of it sits empty while the
card is squeezed into the composer.

Add a third placement. When the measured free gutter beside the composer
is at least 280px, the card docks into it with its full checklist
expanded; below that it falls back to the existing composer bar, and an
open companion rail still wins. Exactly one placement renders at a time,
now expressed as a closed {card, placement} prop so the composer bar and
the dock cannot both draw the same card.

The gutter is measured from the DOM by a small ResizeObserver controller
rather than derived from the pane width: the transcript width is a
browser-local setting in arbitrary CSS units, and an open side panel
shrinks the conversation column without changing the pane. The dock is
positioned absolutely in that gutter, so the transcript and composer
never shift when a card appears or is dismissed, and its inline-start
edge repeats the composer's own half-width formula through a shared
--chat-composer-side-inset token so the two stay in agreement.

* fix(ui): keep the new-session composer sized outside the chat surface

The composer shell tokens are declared on .chat, but the new-session page
reuses .agent-chat__composer-shell outside it. There the var() had no
value, so the whole width declaration was invalid and dropped, and the
composer stretched full-width instead of holding its 48rem centered box.

Give both tokens their literal fallback at the use site, matching the
neighbouring --chat-thread-max-width. The custodian surface was already
immune because it overrides width outright.

Caught by ui/src/e2e/new-session-page.places.e2e.test.ts, which is the
regression test for this: it failed on the previous head and passes now.
2026-08-25 01:34:09 -07:00
Rohit kumar kashyap a2c5198dfc fix(memory-wiki): honor configured state directory (#122591)
Fixes #122567

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 01:10:40 -07:00
wangmiao0668000666 ebf07e56e8 fix(memory): apply temporal decay to dated files in memory subdirectories (#121103)
* fix(memory): apply temporal decay to dated files in memory subdirectories

DATED_MEMORY_PATH_RE only matched memory/YYYY-MM-DD.md at the root, so
dated files in subdirectories (e.g. dreaming phase reports at
memory/dreaming/light/2026-05-14.md) were classified as evergreen and
kept full ranking weight forever even with temporalDecay enabled.

Accept a date-only Markdown basename at any depth below memory/, matching
the nested-path shape short-term promotion already recognizes. MEMORY.md,
USER.md, and undated files remain evergreen; non-memory sources keep the
mtime fallback.

Fixes #121046

* refactor(memory): trim issue-specific commentary from dated-path matcher

* fix(memory): decay nested and slugged dated memory files

Co-authored-by: wangmiao0668000666 <wang.miao86@xydigit.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 00:36:10 -07:00
RoboClaw 0415c403a7 improve: use agent identity as the sole assistant identity (#129067)
* refactor(config): remove duplicate assistant identity

* test(config): isolate assistant migration coverage

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-25 00:21:13 -07:00
RoboClaw 49c7fc5bae fix(ui): stop claiming queued sessions need a slot (#129081)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 00:19:58 -07:00