Commit Graph

72283 Commits

Author SHA1 Message Date
Peter Steinberger c682bbcf90 fix(trajectory): bound runtime event retention (#114250) 2026-07-26 23:46:08 -04:00
Eden 0c35fd578b fix(line): keep replies deliverable when action data or button URLs exceed LINE's size caps (#113081)
* fix(line): cap flex postback data and action URIs at LINE's size limits

* fix(line): preserve encoded action boundaries

* fix(line): surface unavailable oversized actions

* fix(line): centralize oversized callback fallback

* test(line): pin UTF-16 action limits

* fix(line): fit fallback labels in image carousels

* fix(line): normalize raw actions at builder boundaries

* fix(line): normalize remaining flex actions

* fix(line): enforce action limits at send boundary

* fix(line): preserve message action identity

* fix(line): surface unavailable video links

* fix(line): render non-button action warnings

* fix(line): count action limits by code point

* fix(line): normalize imagemap actions

* fix(line): normalize imagemap video links

* fix(line): bound imagemap video labels

* fix(line): satisfy code-point lint guard

* fix(line): finalize imagemap action limits

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

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 23:46:02 -04:00
Peter Steinberger a05cfd4756 feat(runtime): run OpenClaw under Bun runtimes that provide node:sqlite (#114256)
* feat(runtime): allow Bun runtimes that provide node:sqlite

* fix(process): drop execa buffer encoding under Bun spawn (Bun rejects non-spawn options)

* chore(process): cite oven-sh/bun#36049 in bun spawn workaround

* docs(install): bun with node:sqlite can run openclaw; bun install workspace caveat

* fix(process): clear execa buffer encoding under Bun without mutating read-only options
2026-07-26 23:44:09 -04:00
Peter Steinberger 992a86a28f fix(linux): make the companion's Rust test suite runnable and run it (#114260)
Three defects that compounded into a test suite nobody could run and nobody
was running.

The suite has been red on main since 2026-07-20. `connect_frame_matches_gateway_schema`
asserts a TLS-pinned connection advertises no capabilities, but #111933 wrote
that assertion while caps held only inline-widgets, and #111920 made
`agent-kind` unconditional the same day. No textual conflict, so both landed
and the assertion has been wrong ever since. Pinning only withdraws inline
widgets, so assert exactly that.

`cargo test` could not run on macOS at all: tauri-plugin-notifications links a
Swift static library, nothing adds an rpath for the Swift runtime, and every
test binary aborted at load with `Library not loaded:
@rpath/libswift_Concurrency.dylib`. Emit the rpath from build.rs.

Neither surfaced because linux-app.yml never ran `cargo test` - it only checked
formatting and built bundles. Run the suite on Linux, and upgrade the macOS job
from `check` to `test` so link-time breakage like the rpath is caught at all;
`check` never links, so it cannot see this class of failure.
2026-07-26 23:43:32 -04:00
Sally O'Malley dc797dd455 fix(plugins): report empty npm install failures (#114215)
* fix(plugins): report empty npm install failures

Signed-off-by: sallyom <somalley@redhat.com>

* fix(plugins): report silent peer sync failures

---------

Signed-off-by: sallyom <somalley@redhat.com>
2026-07-26 23:43:03 -04:00
Peter Steinberger 04bb2b7ee9 fix(openai): make onboarding models account-aware (#114258) 2026-07-26 23:33:57 -04:00
Shakker 24786f7219 test: cover agent scope first-switch sync (#114259)
Adds focused Control UI regression coverage for first-switch agent scope label synchronization.

Closes #114142.
Prepared head SHA: 415d6e9336
Reviewed-by: @shakkernerd
2026-07-27 04:33:51 +01:00
Peter Steinberger e717d24c2e refactor(meetings): close manual action state (#114247) 2026-07-26 23:25:19 -04:00
Peter Steinberger af9d583e1d docs(changelog): note Codex controls, WAL verification, model-policy latency, and Claude stall-recovery fixes 2026-07-26 20:15:27 -07:00
Vito Cappello 5ae8a4f4f9 fix: preserve Claude cache during stalled CLI recovery (#113866)
* Preserve Claude cache during stalled CLI recovery

* fix(agents): harden Claude stall recovery

* fix(agents): reject partial Claude recovery output

* fix(agents): rewind Claude recovery to a safe checkpoint

* fix(agents): preserve checkpointed fork retries

* fix(agents): cold-reseed downgraded Claude forks

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 23:11:39 -04:00
Peter Steinberger e71e4ac83e docs: explain why steering waits for the current tool-call batch (#114249)
* docs: explain why steering waits for the current tool-call batch

* docs: regenerate docs map for steering batch section
2026-07-26 23:10:24 -04:00
Peter Steinberger fa2b84697b feat(dev): add cron fixtures to the Control UI mock server (#114248) 2026-07-26 23:08:22 -04:00
Peter Steinberger c59d38138e fix(scripts): make the mock Control UI exec approval opt-in via --fixture=approval (#114246) 2026-07-26 23:06:03 -04:00
Peter Steinberger 37182b9050 refactor(channels): declare thread addressing as a channel trait (#114245)
* refactor(channels): declare thread addressing as a channel trait

* fix(tasks): require declared thread capability for direct parent-review delivery

* docs(tasks): record the no-target-parsing tradeoff at the delivery gate
2026-07-26 23:05:13 -04:00
Peter Steinberger ac5386aa5a fix(models): make refreshed catalogs usable from the CLI (#114244) 2026-07-26 23:02:24 -04:00
Dallin Romney c1c231ced9 fix(qa): reject runtime tool intent without result evidence (#113502)
* fix(qa): require runtime tool result evidence

* fix(qa): preserve meaningful tool coverage gates

* refactor(qa): colocate runtime parity projection

* fix(qa): keep passing tool totals evidence-backed
2026-07-27 11:00:59 +08:00
Peter Steinberger 1759f4d5e6 refactor(ai): unify OpenAI completions compatibility (#114236)
* refactor(ai): unify OpenAI completions compatibility

* refactor(ai): make cache compat resolution explicit

* fix(ai): keep ModelStudio token field canonical
2026-07-26 22:59:15 -04:00
Peter Steinberger e94796e447 fix(linux): restore the macOS build of the Tauri companion (#114243)
* fix(linux): restore the macOS build of the Tauri companion

Tauri gates `WebviewWindowBuilder::transparent` behind its `macos-private-api`
feature on macOS, so the companion stopped compiling for macOS when Quick Chat
landed in #109947: `no method named 'transparent' found for struct
'WebviewWindowBuilder'`. Enable that feature together with the matching
`app.macOSPrivateApi` config flag, which Tauri requires to agree with it or
tauri-build fails the allowlist check.

Quick Chat is built for a transparent window - quickchat.css makes html and
body transparent behind a 16px-radius translucent card with a drop shadow - so
compiling macOS by dropping `.transparent(true)` would ship an opaque rectangle
instead of the floating composer.

Also add a per-PR macOS `cargo check`. The only job that compiled the macOS
target sits behind a manual dispatch input in linux-app-release.yml, which is
why a broken macOS build survived for nine days.

* ci(linux): build the macOS companion on macos-15

tauri-plugin-notifications' Swift sources use typed throws
(`throws(FFIResult)`), which requires Swift 6. The macos-14 runner image still
ships Swift 5.x and cannot parse them, so the plugin's build script panics with
"Swift build failed for target: arm64-apple-macosx13.0".

This hit the new per-PR check immediately, and it means the release workflow's
`build_macos` job could not have produced a macOS bundle either - it compiles
the same crate on the same image, but only runs behind a manual dispatch input
so nothing surfaced it. Move both to macos-15.
2026-07-26 22:57:36 -04:00
Peter Steinberger 570eab59e7 perf(sessions): stop per-row full-store scans in ACP meta reads (#114237)
* perf(sessions): stop per-row full-store scans in ACP meta reads

Every per-session ACP meta read listed and JSON-parsed the entire session
store just to find one row, making gateway sessions.list O(rows^2). A CPU
profile on team.openclaw.ai showed 12.7s of a 78.5s window inside
parseSqliteSessionEntryJson via buildGatewaySessionRow -> readAcpSessionMeta.

Resolve the store key with exact read-only single-row probes (exact key,
then lowercased key) and keep the full scan only as the fallback for legacy
case-variant keys. Adds the missing read-only exact-probe accessor next to
loadExactSessionEntry.

* refactor(acp): split session store binding out of session-meta

max-lines pushed session-meta.ts past 700; the store-binding helpers
(resolveStoreEntryForSessionKey, resolveSessionStorePathForAcp,
readSessionEntryFromStore) are one cohesive unit and move to
session-meta-store.ts. Re-export keeps the public entrypoint stable.

* perf(sessions): case-variant fallback scans keys, not parsed entries

Review caught that a per-row miss still parsed the full store. The fallback
only needs persisted keys to find a legacy case-variant, so list keys without
touching entry_json and probe the single winner.
2026-07-26 22:25:26 -04:00
ClawSweeper f4d454c0c6 fix(ui): new-session composer grows before scrolling (#113842)
* fix(ui): grow new session prompt before scrolling

* fix(ui): preserve composer fallback for non-pixel caps

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 22:23:54 -04:00
ClawSweeper ca11ee6db1 fix(ui): new worktree sessions stay in Coding during creation (#113834)
* fix(ui): preserve new coding session selection

* test(ui): cover prepared sidebar sessions

* fix(ui): retire prepared work sessions on delete

* test(ui): refresh prepared sidebar mock for the configured main key

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 22:18:07 -04:00
Peter Steinberger 7846a32757 test(ai): freeze Responses stream processor parity (#114230)
* test(ai): freeze Responses stream processor parity

* test(ai): align parity adapter output type
2026-07-26 22:13:18 -04:00
Peter Steinberger a3283af7c7 feat(ui): complete the Labs roster with the remaining experimental gates (#114233)
* feat(ui): complete the Labs roster with the remaining experimental gates

Labs shipped with two entries while three more experimental surfaces stayed
reachable only by hand-editing config. Adds Tool Search, lean local-model tools,
and message audit metadata, each with the subtext and documentation link the
page already promises.

Two of them do not fit the boolean gate the registry assumed, so the registry
now states each row's on/off values instead of hardcoding true/false:

- Tool Search is a mode, and resolveToolSearchConfig defaults an unset mode to
  "code" even in object form. A bare enable would therefore select the surface
  with the weakest recall rather than the bounded directory this row advertises,
  so enabling writes `mode: "directory"` alongside the gate in one patch.
- Message audit is `off | direct | all`. Labs offers the conservative `direct`,
  so turning it on cannot start recording group or unknown conversations the
  operator never opted into, and `all` deliberately does not read as on — that
  is a broader choice made elsewhere and this row must not quietly narrow it.

The audit row carries a restart hint because startGatewayEventSubscriptions
resolves the mode once and bakes it into the recorder, which outlives the reload
plan's `logging: none` rule. The other four are read per agent run.

* fix(ui): read a broader audit mode as enabled instead of narrowing it

`all` records more than the `direct` this row offers, but it is still on. Strict
equality against onValue rendered it as off, which made the switch look available
and would have silently rewritten a deliberately broader operator setting down to
`direct` on the next click.

Separate "what enabling writes" from "what counts as enabled": onValue stays the
value Labs sets, activeValues lists every value that reads as on. Turning the row
off from `all` now writes `off`, which is the only narrowing the operator asked
for.

The test that covered this asserted the opposite of what its own comment
described; corrected, plus coverage for the off-from-all write.

* fix(ui): mirror the runtime rule for Tool Search enablement

ToolSearchConfig is a union, and resolveToolSearchConfig treats an object that
configures anything besides `enabled` as already on:
`readBoolean(raw.enabled, configured)`. Reading only the `enabled` leaf showed
`{ mode: "tools" }` as off, so the row offered a switch that would have replaced
that operator's mode with `directory` — the same narrowing the audit row was
just fixed for.

Give the registry a `readEnabled` override for gates whose enablement is not a
single leaf, and point Tool Search at a copy of the runtime rule with the
resolver named so the two stay comparable. The other four rows keep the leaf
read and declare `readEnabled: null` explicitly.

* chore(ui): keep LabFeatureValue local to the registry

Only the registry names the type, and the hard-zero Knip production scan rejects
an export with no production consumer outside its own module.
2026-07-26 22:11:14 -04:00
Peter Steinberger 83307f6bc7 refactor(irc): delegate outbound sends to message adapter (#114235) 2026-07-26 22:10:51 -04:00
Peter Steinberger 7594e09100 refactor(meetings): centralize talk-back readiness (#114229) 2026-07-26 22:03:12 -04:00
Peter Steinberger c5d6dcfc84 fix(scripts): run changed checks locally when Blacksmith never ran them (#114225)
* fix(scripts): run changed checks locally when Blacksmith never ran them

AGENTS.md already says trusted-source work falls back to local execution when
the remote backend is unavailable, but the tooling did not implement it: a lease,
broker, DNS, or network failure surfaced as a plain exit 1, so the lanes were
reported red without ever having been evaluated. That is worse than slow — a run
that never happened looked the same as a run that failed.

Tee the wrapper output and use its run summary as the discriminator. The summary
only appears once the command reached the box, so a failure carrying
`command-exit` is a real verdict and propagates unchanged; anything else never
produced one and re-runs locally, with a loud note so the proof summary records
which machine produced it.

Deliberately a positive test for `command-exit` rather than a blocklist of
infrastructure errors. Guessing wrong toward "infrastructure" only re-runs the
checks locally; guessing wrong toward "real failure" would block on an outage.
It must never widen to "fall back on any non-zero exit" — prompt snapshots are
Linux-only truth and would pass locally on macOS, turning a red gate green.

The sparse-checkout path keeps no fallback: it exists precisely because the
checkout cannot resolve the diff refs, so there is nothing local to run.

* fix(scripts): require positive pre-dispatch evidence before falling back

A missing run summary does not prove the remote never started: a wrapper that
crashes or loses its output transport after dispatch looks identical. Reading
that absence as "never ran" would rerun locally and could turn an unknown or
failing Linux-only lane green, which is the exact masking this guard exists to
prevent.

Require a positive pre-dispatch signature instead, and keep the command-exit
veto. Also reapply backpressure on the tee: inherited stdio got it from the OS,
piping does not, so a verbose delegated run could buffer its whole output here.
2026-07-26 22:02:36 -04:00
Peter Steinberger e9ef30e739 refactor(config): split write preparation primitives (#114228) 2026-07-26 22:00:29 -04:00
Peter Steinberger 8e78ee45ce refactor(agents): clarify provider request param classifier (#114231) 2026-07-26 21:59:58 -04:00
Peter Steinberger 5d74ba56f8 refactor(volcengine): reuse model compat patcher (#114232) 2026-07-26 21:58:27 -04:00
Peter Steinberger 0776872712 refactor(reply): share turn accounting and recovery (#114220)
* refactor(reply): share turn accounting and recovery

* fix(reply): keep recovery result internal

* docs(reply): clarify recovery diagnostic invariant
2026-07-26 21:57:41 -04:00
Peter Steinberger a348a63cda refactor(ai): preserve abort reasons so restart recovery reads a code (#114219)
* refactor(ai): preserve abort reasons so restart recovery reads a code

* fix(ai): rethrow only coded abort reasons from transports

* test(agents): prove restart abort code reaches the recovery verdict
2026-07-26 21:54:44 -04:00
Peter Steinberger e6a6d478f2 fix(linux): draw the macOS tray icon from a template silhouette (#114223)
* fix(linux): draw the macOS tray icon from a template silhouette

AppKit renders menu bar template images from the alpha channel alone, so the
opaque rounded-tile 32x32.png painted a solid white square instead of the
mascot. Add a dedicated tray-template.svg and its 36px render, a silhouette
with the eyes knocked back out, and select it only on macOS. Its geometry
mirrors the native macOS app's CritterIconRenderer at rest so both clients
wear the same face; other platforms keep the full-color 32x32.png.

* chore(linux): drop the root changelog entry from the tray icon fix

CHANGELOG.md is release-owned in this repo (AGENTS.md) and `scripts/pr
prepare-run` rejects normal PRs that touch it; release generation derives
entries from merged PRs. The release-note context lives in the PR body and
the preceding commit message instead.
2026-07-26 21:53:18 -04:00
Jason (Json) f42cb65393 fix(auth): complete profile migration when main already owns an OAuth credential (#114226)
A non-main agent store deliberately does not persist an OAuth credential the
main store already owns at the same or newer expiry. The SQLite migration
verified its write by reloading and comparing every imported profile, so that
intentional dedup read as data loss: verification threw, the migration aborted,
and the legacy auth-profiles.json stayed on disk.

The runtime then refuses to start while any legacy credential file exists, and
the error it prints tells the operator to run openclaw doctor --fix, which is
what had just failed. That is a boot loop with circular remediation.

Treat a profile the local store intentionally deduped to main as verified
rather than missing.
2026-07-26 19:47:08 -06:00
Peter Steinberger 5804d023ec refactor(qqbot): inject live config accessor (#114227) 2026-07-26 21:45:21 -04:00
Peter Steinberger 32719ea59a fix(ui): show real cron failure detail in the sidebar attention tooltip (#114224)
* fix(ui): show cron failure detail in sidebar attention tooltip

* refactor(ui): reuse clampText for cron tooltip error cap
2026-07-26 21:44:18 -04:00
Vito Cappello 47d37ddd54 fix: reduce reply delay when model policy is configured (#114117)
* fix: reuse model selection metadata snapshot

* test: cover concurrent model policy snapshot reuse

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 21:39:19 -04:00
JesusSerrano-Seimako 0ac926b8fb fix(telegram): scope dispatch dedupe by bot identity (#113667)
* fix(telegram): scope dispatch dedupe by bot identity

* refactor(telegram): require bot identity for dispatch dedupe

* docs(telegram): explain dedupe key cutover

---------

Co-authored-by: JesusSerrano-Seimako <269908146+JesusSerrano-Seimako@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-07-27 07:04:48 +05:30
Peter Steinberger 4f34c60d45 build(deps): drop dead glob/markdown-it ownership-manifest entries (#114218)
* build(deps): drop stale root partial-json and dead ownership-manifest entries

partial-json is owned by packages/ai (which declares its own copy); glob and
markdown-it no longer exist as root dependencies. Audit follow-up to #114006.

* build(deps): keep root partial-json (openclaw/ai bundling contract); drop only dead manifest entries
2026-07-26 21:33:27 -04:00
Peter Steinberger 3ba0d95e73 refactor(state): isolate verifier test cleanup (#114216)
* test(state): isolate verifier temp cleanup

* test(state): guarantee verifier test cleanup
2026-07-26 21:30:53 -04:00
Peter Steinberger f88d618589 refactor(providers): share admin usage aggregation (#114221) 2026-07-26 21:28:53 -04:00
Jesse Merhi d7627d6f4c refactor(prompt): use plain inbound context labels and drop system-tag sanitizer (#112000)
* refactor(prompt): plain inbound context labels with a provenance marker

Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.

Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.

Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.

Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.

* fix(ci): resolve gate failures for plain inbound context labels

- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
  so the Kysely connection-boundary guardrail holds; unexport the now-internal
  transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
  plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
  the 700-line oxlint cap; suppressions are disallowed, so follow the existing
  sibling record-module pattern. Public exports and PluginCompatCode literals
  unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
  only). The bracket de-fang and System: rewrite it expected were removed with
  sanitizeInboundSystemTags; forged system lines are neutralized at the
  system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.

* fix(prompt): harden inbound context label migration and drop in-band sanitizer

Review follow-ups on the plain-label + provenance-marker change:

- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
  look-alike `System:`/`[System]` markers corrupted legitimate user text and is
  not a real injection boundary; role separation plus external-content wrapping
  is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
  now requires a complete known legacy sentinel line, a legacy label followed by
  a fenced JSON body, or the complete legacy external-content header. The prior
  predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
  deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
  alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
  header's line break, migrated assistant rows skip newline normalization, and
  without it the marked-header replace wins and the body strips to empty. Added
  a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
  prose-block strip path.

Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH
2026-07-27 11:27:53 +10:00
Vito Cappello 5e841bc2e1 fix(agents): preserve native runtime controls on Codex routes (#107588)
* fix(agents): preserve Codex runtime controls

* fix(agents): cover shipped fast cutoff aliases

* fix(agents): validate native runtime control values

* refactor(agents): simplify fast mode key matching

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 21:25:44 -04:00
Peter Steinberger b6dcb8c323 test(auth): avoid broad profile discovery in migration isolation (#114217) 2026-07-26 21:12:33 -04:00
Peter Steinberger 472f8e7c72 fix(ui): keep the live token counter current in memoized chat rows (#114213) 2026-07-26 21:09:18 -04:00
Peter Steinberger c91a37aeed feat(ui): add a Memory settings page (#114037)
* feat(ui): add a Memory settings page with Dreaming as a tab

Memory config was scattered across five surfaces: the memory.* schema section
lived on AI & Agents with 43 of 51 keys behind the Advanced tier, the memory
slot owner was only visible on Plugins, dreaming's knobs were JSON-only, its
status UI sat under Agents, and Memory Import was a separate route.

/settings/memory now owns that surface, following the MCP page shape (curated
rows above an embedded schema editor):

- Overview: the exclusive memory slot rendered as a segmented control over
  installed memory-kind plugins, memory.backend promoted out of Advanced with
  the qmd sub-config revealed only when qmd is selected, additive add-on rows,
  and a Memory Import link.
- Search: the memory.search surface via the embedded editor.
- Dreaming: the global frequency/model/timezone/storage/phase knobs, which
  previously required hand-editing openclaw.json, plus an agent picker feeding
  the existing dream scene/diary/advanced panel for the agent-scoped reads.

Engine selection calls plugins.setEnabled so the gateway's exclusive slot
policy stays the single owner instead of being duplicated in the UI.

* fix(ui): redirect stale ai-agents memory deep links to the memory page

* fix(ui): report memory runtime defaults on the Memory page

The Dreaming tab rendered its own defaults instead of the ones
resolveMemoryDreamingConfig applies, so a config carrying only
dreaming.enabled showed all three phases off while they were running, and
an unset storage mode read as inline instead of separate. Toggle specs now
carry the runtime fallback and the storage default is stated once, both
pointing at src/memory-host-sdk/dreaming.ts.

Three more surfaces asserted things the runtime does not do:

- plugins.slots.memory "none" is the explicit-off sentinel, not an engine
  id, so the segmented control selected nothing. The slot now resolves to a
  closed auto/off/pinned selection with its own hint.
- memory.backend is resolved by the memory runtime the slot owner
  registers, which only memory-core ships, so the row is hidden for any
  other engine instead of saving a value nothing reads.
- The Dreaming tab wrote config.dreaming for whichever plugin owns the
  slot even when that plugin's schema cannot hold it. It now reuses the
  enablement flow's schema check (resolveDreamingConfigPathSupport, shared
  with updateDreamingEnabled) and renders an unsupported state instead.

Also key the plugin-catalog sync on the connected phase: the connecting ->
connected transition keeps the same client object, so a page mounted
during the handshake never loaded the catalog and never showed the engine
picker.

The tab keeps the autosave status line and restart banner the embedded
editor renders on the other tabs; these knobs autosave, but nothing
reported it. The pure view moved to memory-dreaming.ts with the element in
memory-dreaming-page.ts, matching memory.ts/memory-page.ts.

* fix(ui): resolve the memory slot through the canonical policy

The Memory page re-derived plugins.slots.memory instead of using the rule the
runtime applies, which broke both directions of the engine control:

- An unset slot was reported as "the first enabled memory-kind plugin in the
  catalog". The runtime resolves it to the slot's default owner
  (DEFAULT_SLOT_BY_KEY.memory), so the page could show one engine as active
  while another was loaded, reveal or hide the backend row for the wrong
  plugin, and target the wrong plugin when switching memory off.
- Off called plugins.setEnabled(false), which writes enablement only. The slot
  stayed pinned, so the choice did not survive a refresh and re-enabling that
  plugin from the Plugins page silently switched memory back on.

resolveSlotSelection now lives next to defaultSlotIdForKey in
src/plugins/slots.ts and owns the rule once; config normalization consumes it
and the page imports it instead of restating it. Off writes the explicit "none"
sentinel through the config form, so it round-trips; picking an engine still
goes through plugins.setEnabled, which is where the exclusive slot policy
lives. The dreaming controller's own copy of the rule is gone too.

Four smaller fixes on the same surface:

- A failed engine change is reported next to the control instead of being
  swallowed, so the selector no longer just snaps back.
- Dreaming's numeric inputs carry the memory-core manifest's integer/min/max
  bounds and refuse out-of-range edits at the field, rather than patching a
  value autosave then fails to write.
- Settings search destinations carry the Memory tab that renders the matched
  child, so a memory.search hit no longer lands on Overview, whose narrowed
  editor omits it.
- The Dreaming tab caches only a definitive schema-capability answer. An
  offline or failed lookup now reports "unknown" and is retried on reconnect
  instead of permanently suppressing the recheck.

* fix(ui): model unknown memory state instead of collapsing it

The Memory page reported unknowns as decided values. An empty catalog meant
loading, disconnected, or a failed plugins.list, yet add-on rows rendered
"Disabled"; catalog completions were keyed on client identity, which survives a
phase flip, so a stale load could repopulate a disconnected page or overwrite a
newer read; and `?tab=` was adopted once per distinct value, so a repeat
navigation to a tab the user had left was ignored.

Replace the ad-hoc nullable fields with closed shapes. MemoryCatalog is a
loading/unavailable/ready union, so absence of an entry only decides anything
inside `ready`, and MemoryAddonRow carries a four-state enablement the view
renders without ever inventing an "off". CatalogConnection is one object per
(client, connected) transition and doubles as the request generation an
in-flight load carries, so obsolete completions are dropped by identity. The tab
is no longer page state at all: the URL owns it, tab clicks navigate, and every
arrival is honored.

Settings search now resolves the engine/backend through the same
resolveMemoryBackend the page uses and matches only the `memory.*` children the
page can surface, so a `memory.qmd` hit under the built-in backend no longer
routes to an Overview whose editor omits it.

* fix(ui): surface a disabled memory owner and anchor curated backend search

The slot and plugin enablement are independent config surfaces, so
`plugins.slots.memory` can name a plugin the catalog reports as disabled.
The engine control showed that plugin as selected, and because re-picking an
already-selected radio fires no change event, there was no way back on. Add an
explicit enable row for that state and let the same-id write through when the
owner is not running; picking Off stays a no-op.

`memory.backend` is curated out of the schema editor, so the generic
`#config-section-memory` anchor scrolled past it. Fold the memory tab and hash
choice into one `memoryDestination` owner that routes a curated-only match to
the new anchor above the editor.

* fix(ui): scope the dreaming capability probe to its connection

The probe was deduplicated by plugin id alone, which cannot tell a current
answer from a stale one. A disconnect and reconnect on the same slot owner left
the token armed, so the reconnect read as "already in flight" and swallowed the
retry that an `unknown` answer requires — leaving an unsupported engine's knobs
editable until some unrelated config notification arrived. An A -> B -> A switch
had the mirror problem: the old A response was accepted for the new A probe.

Make the in-flight probe an object whose identity is the generation, drop it
whenever the owner or the connection changes, and accept only the completion
that still owns the slot. Same shape as the catalog guard on the Memory page.

* fix(ui): satisfy the lint and dead-export gates on the memory page

Exhaustive switches need a terminal `default:` to satisfy
typescript/consistent-return, matching the existing view-status.ts shape.

Seven symbols were exported with no production consumer outside their own
module, which the hard-zero Knip production scan rejects. Tests alone do not
make internals contracts, so drop the exports and reach the behavior through
each module's public surface instead: the view props type comes from
`Parameters<typeof renderMemory>`, the tab panel is found by its ARIA role, and
the dreaming number/storage helpers are proven through `renderDreamingSettings`.

Folding those helper unit tests into the render path also corrected one of them:
a `type="number"` input coerces unparseable text to empty, so the "reject
garbage" case was unreachable through the real control. Replaced with the
inclusive-bound and clear-the-field cases, which are reachable.

* refactor(ui): keep the memory schema facts out of the startup bundle

Settings pages are already lazy — the config route is `import("./config-page.ts")`
— but settings search runs from app-host at startup, and it needed the same
answers about which `memory.*` children are reachable and where a match lives.
Importing those from the view module dragged lit, hub-tabs, and settings-ui into
the startup chunk with it, blowing the Control UI startup budget.

Move the rendering-free facts (slot/backend resolution, tab and curated key
lists, schema narrowing, the anchor id) into memory-schema.ts, which imports
only record-coerce and the shared slot policy. The view keeps the templates and
now consumes the same module, so there is still one owner per fact.

* chore(ui): record the memory settings surface in the startup budget baseline

Routing settings search through memory-schema.ts instead of the view module
recovered 10,872 B of the startup chunk (334,992 -> 324,120 B), which is back
under the 324,608 B ceiling. The remaining 2,795 B over the old baseline is the
honest cost of the new surface: its i18n strings, plus the slot/backend facts
the startup search index has to read.

Measured by hosted CI (run 30189972795); this worktree cannot build locally
because pnpm wants to purge a node_modules shared with other running agents.
2026-07-26 21:05:03 -04:00
Peter Steinberger abd9eededf test(ui): assert the Home run ring in chat run lifecycle e2e (#114212) 2026-07-26 21:03:21 -04:00
Vito Cappello c15ab158b0 fix(state): preserve live SQLite WAL files during verification (#114016)
* fix(state): isolate database verifier snapshots

* test(state): assert verifier retains POSIX lock

* fix(state): scope verifier child IPC

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-26 20:57:17 -04:00
Jason (Json) 0f6a693acd fix(models): shape unreleased Claude ids with the newest known contract (#114205)
Request shaping is selected by version predicates in llm-core, so a Claude id
from a generation newer than anything the plugin encodes fell through to pre-4.6
shaping: manual budget_tokens thinking plus caller sampling parameters, both of
which current models reject.

Resolve such an id through the plugin's existing forward-compat path and stamp
params.canonicalModelId, the same seam Bedrock and Mantle use to map a
provider-native id onto a canonical Claude contract. Shared contracts are
untouched, so Claude served through third-party providers keeps its own
shaping.
2026-07-26 18:46:34 -06:00
Peter Steinberger a53795ec35 fix(worktrees): reclaim git worktree locks left by dead processes (#114130)
* fix(worktrees): reclaim git worktree locks left by dead processes

A gateway that dies without releasing its worktrees leaves
"openclaw pid=<dead>" locks behind. lockState() already classifies those as
{ kind: "dead" }, and release()/remove() reclaim them, but
lockWorktreeForProcess() threw instead. Every later acquire then failed with
"fatal: ... is already locked", and retainGitLock() swallowed the error and ran
the agent with no lock at all -- so the stale lock permanently disabled the
protection it was meant to provide.

Reclaim a dead-owner lock on acquire, the way the sibling call sites do.

* docs(worktrees): enumerate every reclaim path in the scope note
2026-07-26 20:45:45 -04:00
keshavbotagent 46f4018ff9 fix(codex): recover in-place session resets (#114056)
* fix(codex): recover in-place session resets

* fix(codex): preserve retired generation fences

---------

Co-authored-by: Josh Lehman <josh@martian.engineering>
2026-07-26 16:49:57 -07:00