Keep raw recovery keys in explicitly opted-in JSON output. Add a safe text
hint in the CLI-owned result renderer, remove duplicated stored-key output,
and cover normal, verbose, missing-key and JSON command behavior.
Thanks @hartmark for the original fix and real-account reproduction.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agents): strip external-content nonce before loop-detection resultHash
External-content wrappers carry a fresh anti-forgery nonce per result
(createExternalContentMarkerId). The loop detector hashed the wrapper text
including the nonce, so identical failing wrapped tool calls never reached
the no-progress block threshold — the detector warned but never blocked,
leaving the agent to loop until it gave up (#130210).
Strip the wrapper nonce before hashing in extractTextContent, mirroring
the existing stripVolatileSendIds treatment of per-call send ids (#89090).
Only the hash is normalized; the delivered wrapper keeps its nonce, so
anti-forgery is unaffected.
Closes#130210
Co-Authored-By: Claude <noreply@anthropic.com>
* test(agents): validate protected error marker capture
Narrow the generated marker capture before recording it so the real network-error loop regression also passes its owning test-type graph.
---------
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(github-copilot): preserve catalog thinking efforts in requests
Unify discovered and bundled capability mapping with the provider thinking policy. Preserve supported xhigh/max Responses efforts and map minimal to the supported low minimum, while respecting explicit account opt-outs and transport limits.
Fixes#107792
Co-authored-by: Pluviobyte <Pluviobyte@users.noreply.github.com>
* fix(github-copilot): resolve nullable thinking policy transport
Accept the public policy API context and resolve missing transports before enforcing Claude and Gemini effort restrictions. Cover undefined and null API values without changing explicit Responses routes.
* refactor(github-copilot): normalize manifest models as one catalog
Use the canonical batch model provider builder after the single-row helper was removed on main. Preserve model transport and compatibility decoration without a legacy API shim.
* refactor(github-copilot): decorate owned catalog rows in place
Keep the normalized manifest batch as the sole owner of runtime rows and apply transport metadata directly, avoiding redundant row copies.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Pluviobyte <Pluviobyte@users.noreply.github.com>
* feat(channels): introduce bots when they join group rooms
* feat(channels): add Discord and Telegram join introductions
* fix(channels): isolate untrusted evidence and select allowed join targets
* refactor(channels): scope joinIntro to implementing channels
* fix(channels): keep a delivered join introduction settled when its durable commit fails
* feat(channels): read more room history and document join introductions in detail
* chore(config): regenerate bundled channel metadata after rebase
* [AI] fix(plugins): filter pnpm parent-child overrides at managed npm producer
readOpenClawManagedNpmRootOverrides returned host pnpm-workspace overrides
without filtering pnpm-only parent-child selectors (parent>child). Those
selectors are invalid npm override keys and trigger EINVALIDTAGNAME before
the retry-based compatibility path can recover, breaking managed npm plugin
installs and the pre-install peer-sync that runs before the retry loop.
Filter pnpm parent-child selectors once at the shared producer so every
managed npm install/uninstall consumer receives a npm-compatible manifest
up front. The existing retry path stays as a fallback for npm alias
compatibility, which is npm-version-dependent.
Fixes#124426
* fix(plugins): normalize overrides before managed peer planning
Plan peers against the incoming compatible override set so retired selectors cannot silently retain stale pins. Remove duplicate selector retry and synchronization paths while preserving npm alias compatibility.
Co-authored-by: WangYan <wang.yan29@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): reclaim binding ownership when sessions are deleted
Prepare harness deletion outside the SQLite writer and compare-delete exact companion ownership at the session removal edge, with compensation before commit and native subscription release afterward. Preserve live siblings and upstream native history. Repair proven orphan bindings through the existing Doctor planner under maintenance ownership.
* fix(codex): reuse prepared auth for resumed thread ownership
* docs(plugins): clarify shared migration and auth contracts
* test(codex): use the canonical config type facade
* fix(codex): preserve native threads during manual resume
Record pending attachment intent and validate the immutable native tool catalog
before applying configuration to the selected thread. Require uninterrupted
physical-client ownership through native unsubscribe, resume, and binding commit.
Keep the thread intact on unsupported policy, competing work, or failed proof.
Reuse canonical retained-owner cleanup and preserve unsafe-client retirement.
Cover the real command producer, cold and loaded resumes, competing leases,
lazy-store commit fencing, and the Gateway competing-owner path.
* test: isolate Codex auth and session completion fixtures
* fix(memory): isolate sqlite-vec KNN in subprocess
* fix(memory): satisfy KNN subprocess CI guards
* test(release): include memory KNN child artifact
* test(memory): update KNN subprocess fixtures
* fix(memory): bound KNN child row payloads
* fix(memory): preserve published readers during cancellable KNN
Keep shadow reindex database, vector readiness, FTS and metadata state in
an owned async context. Public operations retain the published connection,
and escaped shadow continuations cannot write to it after rebuild closes.
Terminate the one-shot query child through its owned handle and retain
admission until close. Remove process-tree/PID probing and test-only hooks.
Cover publication overlap, callback reads, cancellation and recovery.
Co-authored-by: OpenClaw Assistant <assistant@openclaw.local>
* refactor(memory): separate database context ownership
Keep the published reader and shadow writer context in its own owner module. Repair plugin test imports and typed lint findings without changing the verified KNN behavior.
---------
Co-authored-by: OpenClaw Assistant <assistant@openclaw.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Keep replacement preparation ahead of service mutation in configure and onboarding. Let platform installers own replacement, and report surviving Gateway reachability independently of failed setup.
Skip unused SYSTEM.md and APPEND_SYSTEM.md discovery in embedded turns,
compaction, and workers. Preserve bounded workspace context and interpret
Gateway-supplied instructions as literal text rather than local filenames.
Share the existing loader policy and retain standalone loader semantics.
Rewrite the prompt-read repair reported in #110776.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(plugins): fail closed when plugin runtime is unavailable
Core fabricated an empty object as `PluginRuntime` for the `cli-metadata`
and `setup-only` registration passes, so any plugin touching `api.runtime`
during `register()` died with an opaque
`TypeError: Cannot read properties of undefined (reading 'openSyncKeyedStore')`
that named neither the contract nor the mode.
Replace those fabrications with one shared fail-closed runtime that throws a
named, actionable error identifying the plugin and registration mode. Symbol
reads stay inert so inspection and reflection cannot trigger the guard.
Also resolve the lightweight `cli-metadata` entry beside the resolved plugin
entry, not only at the package root. Published plugin packages emit it at
`dist/cli-metadata.js`, so the probe always missed and the loader fell back to
executing the full heavy entry for CLI metadata collection.
Documents which registration modes expose a live `api.runtime`; the SDK
reference previously claimed it was injected into every plugin.
* fix(ui): restore sidebar lazy import boundaries
Remove the redundant viewer-facepile dynamic import: sidebar consumers
already register that element through the static graph.
Import pet data and sprite helpers from their owning leaf modules, delete
the registration module's re-export barrel, and make the element itself
the lobster-pet.runtime.ts lazy boundary. Keep shared sprite styles with
the look renderer so standalone pages do not need element registration.
Regenerate the measured boot inventory with the canonical browser probe.
Use Lit's typed lifecycle map to remove the obsolete assertion baseline
entry and keep the now-private facepile variant type local.
The full build has no INEFFECTIVE_DYNAMIC_IMPORT warnings. Relevant UI
coverage passes (503 tests plus 5 Chromium E2E tests); the unchanged plugin
runtime regression suites pass all 58 tests. The broad plugin suite still
reports schema-version, doctor-closure, hook-process, and catalog failures.
Make ask_user use native Telegram controls when one single-select question can be represented directly, while keeping multi-select and batch questions on the text fallback. Add native Other-to-ForceReply input, preserve Gateway-owned option ordering across Telegram, Slack, and Discord, and prevent later progress from overwriting the delivered question.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(buzz): retain bounded passive context for accepted turns
Add opt-in connection-owned room and thread context using the shared history window. Preserve sender admission, current roster filtering, UTF-8 bounds, and snapshot consumption without passive inference, session recording, or typing. Related: #129599.
* docs(config): align baseline with landed Feishu sticker sets
* fix(ui): highlight source code in chat and session diffs
* test(ui): isolate roster bootstrap from slug lookup
* fix(ui): preserve both languages in renamed diffs
* fix(agents): stabilize runtime context across tool rounds
Keep current-turn metadata after the active user and before tool calls so Responses continuation retains the complete prior request prefix. Preserve active hook context and intentionally resend when transient context expires on a later turn.
Reworks #130166. Thanks to Markus Hartung for investigating continuation failures.
Co-authored-by: Markus Hartung <mail@hartmark.se>
* fix(ai): localize continuation ordering to Responses
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(compaction): scope the transcript byte fuse to the latest compaction window
readVisibleTranscriptStats only closed its window at a reset boundary, so once a session compacted, maxActiveTranscriptBytes measured the whole active path and every following turn re-compacted. The preflight fuse now measures the window the model actually sees (since the latest compaction or reset, plus its kept tail); history readers keep their reset-only scope.
* test(compaction): cover retained transcript accounting
## Worked on by
- @VACInc
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
---------
Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Reuse the existing Peekaboo live permission service so Quick Chat, Settings, screenshot gating, and computer-control diagnostics retain confirmed grants instead of re-reading stale CoreGraphics denials. Passive checks remain consent-gated. Document same-build grant recovery.
Use the shared per-Gateway bot-pair budget with the latest received signed room roles. Preserve existing sender and mention admission and human traffic. Release note: bound repeated Buzz bot exchanges without adding a channel-specific policy or persistent state.
* feat(feishu): search configured received stickers by keyword
Bind curated sticker sets to the selected bot app ID and reuse the existing sticker action gate and native sender. Enforce matching scalar bounds in runtime and generated config schemas, with bounded search output and explicit truncation. Completes the configurable-set request in #114578.
* refactor(feishu): keep sticker schemas with config owner
* build(feishu): regenerate catalog metadata after channel schema update
* fix(workers): re-arm admission-deadline launches, terminalize dead-worker turns, defer debris cleanup
Three lifecycle repairs from the 50-worker campaign (#129979): the gateway
launch adapter re-arms a launch up to five times (exponential backoff with
jitter, caller-deadline capped, deterministic derived launch/turn ids so
adapter replays find the same journal rows) when the node journal proves the
child died on the admission deadline — credential/build rejections and
post-start failures stay terminal; stuck-turn recovery records a terminal
failure once a journal-terminal worker has no claim past a 30s cleanup grace
instead of skipping keep_lane forever; and startup recovery defers orphan
git cleanup for terminal placements into the first tracked background sweep
(50-placement fixture: 50 startup git scans to zero, ~556ms to ~5ms) while
pending-result recovery and ownership fences stay ahead of readiness.
* fix(workers): use the diagnosed admission-deadline message after #130435 rebase
WorkerConnection now formats the redacted last-failure diagnosis into the
deadline error itself, so the runtime's re-format (and its
lastConnectionFailure tracking) is redundant; errorText uses error.message.
* fix(workers): cap admission re-arms within the minted credential lifetime
Re-arms reused the credential minted before the first admission attempt,
so with five 120s windows plus backoff the final advertised retry could
start after the credential expiry and fail closed as credential-expired.
The launcher now threads credential.expiresAtMs through the tunnel launch
request, and the adapter re-arms only while the retried child still gets a
full admission window (WORKER_ADMISSION_DEADLINE_MS, now shared from the
connection contract) on the original credential; otherwise it returns the
terminal admission result. Expiry-boundary tests walk both sides.
* fix(channels): bundled channels reject the documented responsePrefix override
* fix(feishu): pass channel and account to the reply prefix resolver
The ordinary reply dispatcher built the prefix context without channel or
accountId, so channels.feishu.responsePrefix and its account override fell
through to the global value. The comment dispatcher already passed them.
* fix(channels): accept twitch's root responsePrefix and type ClickClack's
Twitch validates as a two-branch union with both branches closed, so
responsePrefix declared only on the account shape left
channels.twitch.responsePrefix rejected whenever accounts is present. Also
declares responsePrefix on ClickClackAccountConfig so typed callers can express
what its schema already accepts.
* fix(clickclack): apply the configured responsePrefix in model mode
Model mode sends the completion straight to ClickClack instead of going through
the agent reply pipeline, so the documented responsePrefix parsed and then did
nothing. Resolve it through the public reply-pipeline seam so template
variables like {model} render from the completion's own provider and model.
* fix(clickclack): do not double the response prefix in model mode
systemPrompt is operator-owned, so a model can be instructed to emit the
configured prefix itself and the unconditional concatenation then sent it
twice. Match the shared reply normalizer's startsWith guard.
* test(clickclack): resolve inbound access through the real resolver
Main now requires access.channelIngress, which the hand-built fixture did not
provide, so the model-mode messages never dispatched. Drop the fixture and let
handleClickClackInbound resolve access from a DM-shaped message, matching the
sibling bot-loop tests.
* test(channels): reject a key missing from any composed branch
A union alternative is a configuration mode an operator can pick, so a key
present in only one mode is unusable in the other even though the union still
validates. Treat any rejecting alternative as a rejection, and walk allOf when
collecting account schemas.
* fix(channels): offer responsePrefix only where the reply path applies it
Applying the prefix to an outbound reply is per-channel wiring, not a shared
step. Buzz, Nostr, QA Channel, Raft, Reef and SMS never read it, so accepting
the key there validated a setting their delivery path ignores. Anchor the
contract to the channels that consume the prefix.
* fix(channels): complete shared response prefix delivery
Co-authored-by: ayaangazali <ayaangazali.work@gmail.com>
* docs(config): refresh response prefix baseline
* test(tlon): use canonical prefix fixture helpers
* test(channels): prove Twitch reply prefix delivery
Exercise schema admission and the real shared reply dispatcher at the Twitch native send boundary, replacing redundant schema-only coverage. Keep Tlon fixtures on canonical imports and recording options.
* docs(channels): clarify explicit message prefix behavior
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ui): save appearance preferences per user profile
When a Control UI connection is bound to an authenticated user profile
(trusted-proxy, Tailscale, GitHub identity), theme, theme mode, and accent
become per-profile: stored in the existing user_preferences KV store via
users.prefs.set, overriding gateway-wide ui.prefs, with live cross-device
updates over a new additive users.prefs.changed event scoped to the same
merged profile. Restore default deletes only the profile key and falls back
to the gateway-wide value. talk.config projects the caller's profile accent
for native clients. Profile-less connections (token/password/none auth)
keep the existing gateway-config behavior byte-identically.
Release-note context: on multi-person gateways appearance choices are now
personal and follow you across devices; an admin restyling their own UI no
longer reskins the whole team.
* chore(protocol): allowlist users.prefs.changed for mobile clients
iOS and Android resolve the profile accent through talk.config on connect
and config refresh; live per-profile appearance push on natives is a named
follow-up.
* chore(protocol): regenerate Swift/Kotlin models for users.prefs.changed
* fix(ui): honor explicit defaults and identity switches for profile appearance
Addresses the three ClawSweeper review findings:
- resetValue for profile-bound appearance keys is the deletion fallback
(gateway value), so an explicit product-default selection persists as a
profile write instead of being misclassified as a reset.
- An identity switch between two known scopes forces a full appearance
reconcile and clears appearance keys the returning identity never set,
so a shared browser cannot keep the previous profile's look (boot keeps
the last-seen shortcut).
- talk.config joins the profile-dependent dispatch gate so a pending
GitHub identity sync completes before the profile accent is projected;
token clients pass through untouched.
* chore(ui): satisfy assertion-safety ratchet and docs formatting
* refactor(ui): extract server-prefs storage primitives
server-prefs.ts crossed the 700-line cap after the review fixes; the
stateless localStorage persistence primitives move to
server-prefs-storage.ts (no behavior change). Shrink-prunes the
assertion-safety baseline for the moved casts.
* fix(ui): keep imported custom themes out of profile storage
Custom palettes are browser-local by contract, so a profile must never
carry theme=custom to a browser that cannot render it. The wire contract
drops custom from the storable theme values (readers self-heal any stored
value), a profile-bound custom selection stays retained browser-local
(including the offline-queued path), and the exhaustive theme test now
encodes the exception.
At high completion concurrency, 12/50 turns failed with 'Cloud worker
finished, but its workspace result could not be reconciled' (#129979):
between a worker's manifest upload and its verification, a sibling
completion could trigger retention with a snapshot holding only the old
base manifest — the terminal worker no longer protected the workspace, so
cleanup deleted the just-uploaded manifest and verification died on ENOENT.
The node workspace runtime now pins the latest transferred manifest per
generation and includes it in every cleanup's reachable set (cleared with
its generation), and gateway retention snapshots treat unsettled results —
an active claim or a pending-result fence — as protection, which also
preserves it across restarts when node-local pins are gone. Capture errors
retain bounded redacted stderr so this failure class can never be
faceless again. No deadlines, protocol, or completion-semantics changes;
deterministic 1-turn and 50-turn reproductions fail pre-fix.
* fix(config): keep core channels keys in the generated config schema
* test(config): expect the core channels keys in schema surfaces
* fix(config): keep the core channels schema instead of listing its keys
ChannelsSchema is the only core owner at this point, since plugin channel
entries merge in later, so clearing the property map and re-adding a literal
allowlist duplicated that ownership and would silently drop any future core
field. Stop clearing it, and assert parity against the core schema rather
than a key list.
* test(config): derive the channels parity keys from the production conversion
The parity assertion converted OpenClawSchema without the io:input mode the
base schema builder uses, so an input-only core channel field could be absent
from the expectation and still get dropped. Also formats the file, which is
what turned check-docs and check-lint red.
* test(config): repair the parity test after the schema helper rename
* fix(config): preserve core channel schema lookup contracts
Remove redundant schema normalization and cover core descendant lookups alongside bundled and external channel metadata.
Co-authored-by: Ayaan Gazali <ayaangazali.work@gmail.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Two follow-ups from #130264: agent exec temporary state now preserves the
original shared auth root, so temp-state runs read-through portable static
profiles (local precedence and cooldowns preserved, no inherited-secret
copies); and agent deletion safety consults resolveSharedAuthStoreOwnership,
so the implicit legacy main owner is protected only while the legacy store
owns credentials — post-relocation main is deletable and shared credentials
provably survive. Explicitly bound inheritance owners stay protected.
Lease complete mirror file operations through the workspace owner instead of locking only remote transport. Preserve successful writes across exec publication and share serialization across backend handles.
Proven with a real Docker red/green reproduction, 128-workflow mirror/remote stress matrix, exact inventories, failure recovery, 170 focused tests, and updated operator docs. Related: #127441; follow-up to #130031.
* fix(ios): distinguish incomplete gateway setup from credential save failures
Record received and persisted device-auth roles separately, and centralize setup handoff failure handling without changing credential grants or persistence policy. Related: #108888. Thanks @rhubain for reporting the misleading recovery guidance.
* fix(ios): refresh handoff diagnostic localization inventory
Keep explicit CLI choice precedence while preserving existing pnpm, Bun, and Yarn preferences when onboarding is rerun. Retain npm defaults for fresh config and the narrower explicit CLI choices. Fixes#130433.
WorkerAdmissionDeadlineExceededError now carries the terminal diagnosis —
the last connection failure kind and detail, attempt count, and the
redacted endpoint — instead of a bare deadline sentence, and that text
flows through IPC, container stderr, the node launch journal, and the
gateway turn error. Credential material (raw, URL-encoded, JSON-escaped)
is scrubbed before truncation. A LAN partition was misdiagnosed as a
gateway admission wedge for a day because the old text was identical for
never-connected and never-answered (#129979). Container daemon
revalidation gets a 30s budget: a slammed-but-healthy OrbStack daemon
exceeded the old 5s under a 50-container burst; fail-closed semantics
unchanged.