Commit Graph

72318 Commits

Author SHA1 Message Date
Peter Steinberger
4bc1fd314b refactor(config): consolidate write preparation regression tests (#114332) 2026-07-27 01:31:18 -04:00
Peter Steinberger
dc8fdb1756 refactor(providers): derive simple providers from manifests (#114331) 2026-07-27 01:29:53 -04:00
Peter Steinberger
ccd6845e4d perf(plugins): memoize channel catalog discovery (#114324) 2026-07-27 01:29:21 -04:00
Peter Steinberger
885121b1f3 perf(gateway): cache session PR git facts (#114311) 2026-07-27 01:27:10 -04:00
Peter Steinberger
71a472bf54 refactor: consolidate channel and auto-reply tests (#114330) 2026-07-27 01:26:47 -04:00
Peter Steinberger
b65dae1511 fix(linux): surface manual update results (#114312) 2026-07-27 01:26:28 -04:00
Peter Steinberger
1c42998f0d refactor(retry): consolidate abort-safe ClawHub and memory retries (#114300)
* refactor(retry): reuse canonical abort-safe retry policies

* fix(retry): keep retry-after parsing source-safe
2026-07-27 01:25:40 -04:00
Peter Steinberger
cba38b74a3 improve(slack): consolidate transport regression coverage (#114329) 2026-07-27 01:24:49 -04:00
Peter Steinberger
df5d85a5b2 refactor(agents): centralize terminal outcome handling (#114310) 2026-07-27 01:21:07 -04:00
Peter Steinberger
2a8c0ca513 refactor(agents): consolidate subagent lifecycle regression tests (#114326) 2026-07-27 01:19:32 -04:00
Peter Steinberger
94923a1688 docs(agents): add ops telegraph notes for CI dispatch, release logs, and shared checkouts (#114307) 2026-07-27 01:17:38 -04:00
Peter Steinberger
beef065fe4 refactor(agents): fold single-consumer subagent-spawn helpers back into callers (#114301) 2026-07-27 01:17:31 -04:00
Peter Steinberger
e0eed257c8 fix(ui): persist Control UI settings across reconnects via gateway-owned LWW prefs writes (#114286)
* fix(ui): sync Control UI prefs via hash-free LWW config.patch

* test(ui): prove prefs reconnect replay end to end

* fix(gateway): surface hash-free prefs commit races instead of replaying stale intent

* fix(ui): never re-reconcile a retained config snapshot over acked prefs

* fix(ui): merge persisted pending prefs across tabs instead of clobbering

* test(ui): pass current runtime config through sidebar prefs reconcile fixture
2026-07-27 01:17:03 -04:00
Peter Steinberger
d413fbd8ec fix: Tool Search finds tools by how agents actually phrase requests (#114285)
* feat(agents): rank Tool Search with BM25 over names, descriptions, and parameters

Ranking was case-insensitive substring matching with hand-tuned weights, which
failed in ways that made tools unreachable rather than merely mis-ordered:

- "scheduling" found nothing against a tool described "Schedule a recurring
  task" — no stemming.
- "read" ranked spreadsheet_open, because "sp-read-sheet" contains it.
- A non-English query tokenized to zero terms, and the scorer returned 1 for
  every entry, so the model received an arbitrary alphabetical slice of the
  catalog presented as a ranked result.

Replace it with Okapi BM25 over a tokenizer that splits on Unicode word
boundaries, drops stopwords, and collapses light English inflection. Index
parameter names and descriptions too, which Codex (bm25 crate) and the Claude
API tool-search tools both do; a query like "repository" now reaches a tool
whose description never says it. Underscore-joined names index as both the whole
name and its parts.

A small query-expansion table bridges intent to description vocabulary ("look up
the price" -> search/web), which pure lexical overlap cannot do. It holds only
generic capability words, never plugin or vendor names.

Empty queries now score nothing instead of everything, and both `tool_search`
and the code-mode bridge tell the model to query in English, so the degenerate
case is steered away from rather than silently mishandled.

Untrusted schemas are still never traversed: parameters are indexed only for
`openclaw` entries, matching the boundary compactToolSearchCatalogEntry already
enforces by reporting MCP and client inputs as "unknown".

* fix(agents): undouble inflected consonants and state the real English contract

Autoreview caught two defects in the new stemmer and its documentation.

"running" stripped to "runn", which can never meet "run", so a tool named
task_runner described "Running tasks" became unreachable for the query "run" —
a regression the substring scorer did not have. English doubles the final
consonant before -ing/-ed/-er, so undo that, while keeping doubles that belong
to the root (call, process, off, buzz).

The English-only claim was also stronger than the code: the tokenizer keeps
Unicode letters, so a non-English query yields terms and can match. Rejecting
them would make a catalog that legitimately names a tool in another script
permanently unreachable, so the behavior stays and the wording now matches it —
catalogs are written in English, so other languages usually match nothing, and
the model is asked to query in English for that reason rather than because the
input is filtered. The previous test only proved an unrelated document scored
zero, so it is replaced by one that asserts each half directly.

* fix(agents): split camelCase, discount expansions, and stop trigger collisions

Three defects in the new tokenizer, all found by autoreview and all reproduced
before fixing.

`splitWords` lowercased before looking for boundaries, so `readFile` produced
only `readfil` and the natural query `read file` could not meet it. MCP catalogs
commonly use camelCase, and the old substring scorer matched those. Split case
transitions before lowercasing.

Expansion terms carried the same BM25 weight as words the caller typed, so
`weather` — which expands to search/web — could rank a general web tool above
the exact weather tool by matching two terms instead of one. Expansions are a
guess about how the catalog words a capability, so they now score at 0.35, and a
term the caller actually wrote keeps full weight even when an expansion repeats
it.

Trigger matching ran the document stemmer, which collapsed unrelated vocabulary:
`news` became `new`, so "open a new issue" silently acquired a web-search intent.
Triggers now normalize by singularization only, which leaves `news` intact, and
the table lists `reminder` explicitly rather than relying on the stemmer to
reach it.

* fix(agents): normalize -ies plurals and tier literal matches above expansions

`repositories` stemmed to `repositori` while `repository` stayed put, so the two
never met; `-ies` now normalizes back to `-y` in both the document stemmer and
the expansion triggers, which also lets `directories` and `memories` reach their
intended groups instead of stalling at `directorie`.

The 0.35 expansion discount is not sufficient on its own to keep a literal match
ranked first: BM25 sums per term, so a common literal like `weather` carries
little IDF while a short document collecting two rare expansions can outscore
it, and a small result limit then drops every tool matching the typed word.
Literal overlap is now reported per hit and ranked as a tier ahead of score, so
the discount orders within a tier rather than trying to carry the invariant.

Verified by running the ranker over the failing inputs rather than reasoning
about them; the first attempt at the trigger fix silently did not apply because
the formatter had reflowed the function, which the matrix run caught.

* fix(agents): keep non-plural -s words out of the plural stem rule

"news" stemmed to "new", which literal-matched every "Create a new ..." tool;
because literal overlap now outranks expansion-only hits, a search for news
returned creation tools instead of the web tool the query meant.

The collision is not unique to news — "status", "canvas", and "alias" are all
ordinary tool vocabulary here and all lose their meaning under the same rule, so
the exemption covers that class rather than the single reported word.

* fix(agents): restore the exact-name tier and keep `get` searchable

Two signals the old substring scorer had and BM25 alone does not.

It gave an exact name/id match +20, which flattening everything into one
document removed: querying a known tool name could rank a shorter entry that
merely mentions the word above the tool itself, and the result limit would then
drop the tool asked for. Exact name/id is now a sort tier ahead of literal
overlap.

`get` was in the stopword list, but it names real operations in a tool catalog.
Discarding it made `search("get")` empty and reduced "get issue" to "issue",
where a shorter delete_issue or update_issue entry can win on length
normalization. Capability verbs stay indexed.

* fix(agents): keep acronyms whole, both -ies readings, and stopword-named tools

* refactor(agents): keep the ranking types and entry-text helper module-local

Knip's hard-zero unused-export gate flagged WeightedTerm, RankedDocument,
LexicalIndex, and toolSearchEntryText: nothing outside their own modules
imported them. Narrowing the surface is what the repo asks for anyway.

The untrusted-schema test now drives ToolSearchRuntime.search instead of
calling toolSearchEntryText directly, which proves the boundary through the
real entry point. Verified it still bites: removing the source gate makes it
fail with "client properties must remain deferred".
2026-07-27 01:10:53 -04:00
Peter Steinberger
1168213a26 refactor(plugins): remove filesystem polling from metadata snapshots (#114289)
* refactor(plugins): reuse process-stable metadata snapshots

* fix(plugins): remove obsolete snapshot fingerprint
2026-07-27 01:07:38 -04:00
Peter Steinberger
de92c557f1 fix(linux): serialize tray gateway operations (#114291) 2026-07-27 01:06:03 -04:00
Momo
16d2b7e467 fix(memory-lancedb): make table initialization atomic (#105896)
* fix(memory-lancedb): make table init atomic

# Conflicts:
#	extensions/memory-lancedb/index.ts

* fix(memory-lancedb): stabilize concurrent table init
2026-07-27 12:57:24 +08:00
Peter Steinberger
7cce91cda9 refactor(channels): unify account and setup adapters (#114290) 2026-07-27 00:55:46 -04:00
Ian Moog
40c17e76da fix: prevent gateway crash on unreachable pinned addresses (#113905)
* fix(net): defer pinned DNS lookup callbacks

* fix(net): defer package lookup callbacks

Co-authored-by: Ian Moog <ianmoog42@gmail.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-27 00:55:35 -04:00
Peter Steinberger
60aac74672 fix(openai): harden live and packaged onboarding (#114288) 2026-07-27 00:50:49 -04:00
dandriscoll
71eb9a3ec9 fix: Telegram forum topics answered twice (duplicate conversation per topic) (#113063)
* fix: Telegram forum topics answered twice (duplicate conversation per topic)

* fix(telegram): repair General topic conversation identity

Co-authored-by: Dan Driscoll <thedandriscoll.org@gmail.com>

* fix: keep doctor repair detail internal

---------

Co-authored-by: Dan Driscoll <thedandriscoll.org@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-27 00:42:00 -04:00
Peter Steinberger
20a73ed166 fix(models): make dynamic catalog selection follow one canonical plan (#114284) 2026-07-27 00:40:54 -04:00
Peter Steinberger
6e4f2e1ec4 fix(gateway): settle account stops before failing (#114280)
Co-authored-by: Qiong <yang.ji2@xydigit.com>
2026-07-27 00:39:55 -04:00
Peter Steinberger
8b66fc103d feat(ui): durable session board face and dashboards index (#114262)
* feat(ui): durable session board face and dashboards index

Board face lived only in client-side boardSessionViews, capped at 50 entries,
so the preference never followed the user to another device, evicted as
sessions accumulated, and could not be seen as a set.

Persist it as SessionEntry.boardFace, which rides the existing entry_json blob
and so needs no SQLite schema change or version bump. Expose it on the session
list row and add it to the sessions.patch write-scope allowlist alongside label,
pinned, and archived: setting your own view preference is user-level chat
organization, not policy. Unknown patch fields still fail closed to
operator.admin.

Generic navigation now reads the stored face, so the sidebar and session list
open a thread on the face you left it on. boardSessionViews keeps only
activeTabId and reopenDockByTab, which are genuinely per-device.

Add /dashboards listing threads whose preferred face is dashboard. Filtering
runs server-side in filterSessionEntries before pagination, because the client
holds only a capped page and a client-side filter would silently omit
dashboards.

* test(protocol): assert the pre-rename face param is rejected

The gateway-protocol validator test still passed the pre-rename 'face' key,
which the closed schema rejects. Use boardFace, and pin the old name as a
negative case so it cannot silently return.

* chore(protocol): regenerate Swift bindings and docs map for boardFace

Adding boardFace to the sessions schema changes two committed generated
artifacts: the Swift gateway models (pnpm protocol:gen:swift) and the docs map
(pnpm docs:map:gen), which now lists the dashboards index section.
2026-07-27 00:35:34 -04:00
Peter Steinberger
a2eceb9b2f fix(reply): honor channel-declared explicit-reply-tag opt-out (#114268)
* fix(reply): honor channel-declared explicit-reply-tag opt-out

* test(reply): export getLoadedChannelPlugin from followup-delivery plugins mock

* docs(reply): state the named-channel default truthfully and pin it
2026-07-27 00:30:29 -04:00
Peter Steinberger
fdfe5125a3 fix(ai): unify Responses stream processing (#114263)
* refactor(ai): unify Responses stream processing

* fix(ai): preserve unindexed output boundaries

* refactor(ai): remove obsolete Responses helpers

* refactor(ai): trim canonical stream surface
2026-07-27 00:28:07 -04:00
Dallin Romney
b699a93c87 fix(qa): stop live config waits from timing out (#114112)
* fix(ci): start QA script scenarios in parallel

* fix(qa): stop false live config waits
2026-07-27 12:18:20 +08:00
joshavant
44378dd24a fix(channels): harden record session override 2026-07-26 23:15:06 -05:00
joshavant
1b16d12b58 fix(discord): clear cancelled interaction defers 2026-07-26 23:15:06 -05:00
joshavant
d8de71258a refactor(channels): route bundled command replies 2026-07-26 23:15:06 -05:00
Peter Steinberger
66714b8622 refactor(config): record exact include ownership (#114251) 2026-07-27 00:12:44 -04:00
Peter Steinberger
730b341f3f fix(linux): keep Quick Chat on screen across DPI and widget resizes (#114271)
Two geometry defects found while stress-testing the companion.

Positioning mixed coordinate spaces. `work_area()` is physical pixels of the
monitor Quick Chat is moving to, but `inner_size()` is physical pixels at the
scale of the monitor it is on now. A 640pt window on a 2x display reports
1280px, so invoking it on a 1x 1920px display centred using 1280 and landed it
320px left of centre. Re-express the window size in the target monitor's scale
first; equal scales give a ratio of 1, so single-monitor setups are untouched
to the pixel.

Widget growth resized without re-anchoring. `quickchat_set_expanded` resizes
and then repositions, but the widget path only resized, keeping the old top
edge. Growing from 360pt to 440pt in a 440pt work area left the bottom 80pt
off-screen and unreachable. `resize_window_if_needed` now reports whether it
actually changed the height so the caller re-anchors only on a real resize,
rather than moving the window when nothing changed.
2026-07-27 00:05:52 -04:00
Pavan Kumar Gondhi
95f56b84cb fix: reject escaped newline shell words (#114134) 2026-07-27 09:31:55 +05:30
Peter Steinberger
bb55d44840 fix(gateway): stop fabricating assistant agent id "main" before roster resolution (#114257)
Three fabrication points told clients the assistant agent was "main" before
any roster/config resolution: DEFAULT_ASSISTANT_IDENTITY in
src/gateway/assistant-identity.ts, the Control UI store's initial snapshot
(ui/src/app/gateway-store.ts:65), and its hello fallback (:336). On installs
whose implicit main agent is retired, every page reload fired
sessions.catalog.list for the nonexistent agent and flashed
'unknown agent id "main"' in the sidebar.

The no-roster state now carries no agent id: DEFAULT_ASSISTANT_IDENTITY drops
agentId, resolveAssistantIdentity returns ResolvedAssistantIdentity (agentId
required) for config-backed paths, the bootstrap omits the optional
assistantAgentId field without config, the UI store starts null, and agent
selection adopts the roster default once agents.list arrives.
2026-07-26 23:48:13 -04:00
Peter Steinberger
b6e09a0f8f fix(models): show refreshed OpenAI models in provider-filtered lists (#114265) 2026-07-26 23:47:01 -04:00
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