Commit Graph

278 Commits

Author SHA1 Message Date
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
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
9eae43bd37 refactor(infra): move exec approvals into the shared SQLite state DB (#114063)
* refactor(infra): move exec approvals into the shared SQLite state DB

Delete the file-runtime exec-approvals store (exec-approvals.json + .lock
sidecar machinery) on both runtimes and make the reserved
exec_approvals_config singleton row canonical. Doctor owns the one-time
import with claim/verify/receipt discipline; runtime fails closed with a
doctor instruction while un-migrated legacy state exists. The wire CAS
contract, socket semantics, and gateway auth-token derivations are
unchanged. Kills the #113929 lock-contention bug class structurally and
nets around -2.9k lines.

* fix(infra): green CI gates and retire file-era exec approvals tests

Break the migration-type import cycle with a leaf contract, regenerate the
plugin-SDK API and native i18n baselines for the intentional surface change,
drop unused exports, and replace the macOS file-era approvals test suite with
SQLite-backed behavior coverage per the obsolete-internals test policy.

* chore: green max-lines ratchet, native i18n baseline, and unused-export scan
2026-07-26 06:39:23 -04:00
Peter Steinberger
27293aa273 feat(ui): gateway subtitle in the sidebar footer identity card (#114059)
* feat(ui): show gateway subtitle in sidebar footer

* chore: keep CHANGELOG release-only

* test(ui): fix indexed-access type in sidebar gateway case

* perf(ui): trim footer subtitle startup bytes

* perf(ui): trim gateway footer strings

* perf(ui): re-record startup baseline for gateway footer

* perf(ui): re-record startup baseline from Linux build
2026-07-26 05:23:42 -04:00
Peter Steinberger
5347285d6b improve(models): source pricing from hosted catalog (#114060)
* feat(model-catalog): serve hosted fallback pricing

* refactor(config): retire client pricing bootstrap settings

* refactor(gateway): delete client pricing refresh runtime

* docs(models): explain hosted catalog pricing

* fix(model-catalog): preserve pricing privacy and aliases

* fix(model-catalog): fingerprint pricing eligibility

* fix(model-catalog): harden pricing endpoint checks

* fix(model-catalog): materialize source-safe pricing aliases

* fix(model-catalog): keep unknown pricing fallbacks safe

* fix(model-catalog): reject zero-only hosted prices

* fix(model-catalog): fail closed without pricing policy metadata

* refactor(utils): extract usage pricing normalization

* fix(model-catalog): rebuild policy-owned pricing namespaces

* test(model-catalog): type publisher cost fixtures

* chore(config): regenerate schema baselines

* fix(utils): keep raw pricing tiers private
2026-07-26 03:48:25 -04:00
Peter Steinberger
299f5a4941 refactor(copilot): split attempt runtime (#114035)
* refactor(copilot): split attempt runtime

* refactor(copilot): narrow attempt helper exports

* refactor(copilot): expose user input bridge type

* refactor(copilot): consume user input bridge type
2026-07-26 01:58:31 -04:00
Peter Steinberger
5284408470 refactor(agents): split subagent-spawn and drop its max-lines baseline entry (#114030)
* refactor(agents): split subagent spawn helpers

* refactor(agents): keep spawn agent state caller-owned

* refactor(agents): keep relocated subagent-spawn symbols module-private
2026-07-26 01:12:07 -04:00
Peter Steinberger
99d8d90572 refactor(memory-core): split CLI runtime (#114013)
* refactor(memory-core): split CLI runtime

* refactor(memory-core): narrow CLI helper exports

* test(memory-core): follow secret resolver split
2026-07-25 21:37:59 -07:00
Peter Steinberger
c66f0d340b refactor(qa-lab): split suite execution (#113974)
* refactor(qa-lab): split suite execution

* refactor(qa-lab): tighten suite module contracts

* refactor(qa-lab): keep suite environment private
2026-07-25 20:43:51 -07:00
Peter Steinberger
8d75a6ded6 refactor(agents): split acp-spawn helpers and drop max-lines suppression (#113979)
* refactor(agents): split acp-spawn helpers and drop max-lines suppression

* refactor(agents): keep relocated acp-spawn helpers module-private
2026-07-25 20:11:54 -07:00
Peter Steinberger
71cb266c1f fix(ui): keep Coding sessions visible after reconnect (#113981)
* fix(ui): preserve scoped session hydration

* chore: leave changelog to release automation

* build(ui): refresh startup budget baseline
2026-07-25 19:56:50 -07:00
Peter Steinberger
3807591ff4 build(lint): adopt stylelint for Control UI css templates and stylesheets (#113971) 2026-07-25 19:13:46 -07:00
Peter Steinberger
f90cef67c8 feat(ui): drag custom sidebar groups between built-in session zones (#113948)
* feat(ui): drag custom sidebar groups between built-in session zones

* test(ui): remove stale split ratio fixture

* perf(ui): raise startup budget baseline for session-section ordering pref
2026-07-25 19:11:25 -07:00
Peter Steinberger
17ccfc4b98 refactor(talk): split realtime relay owners (#113967) 2026-07-25 19:04:15 -07:00
Peter Steinberger
1ae0dfed4d refactor(doctor): split health contributions (#113937)
* refactor(doctor): split health contributions

* refactor(doctor): keep auth flow reviewable

* refactor(doctor): keep config result internal

* refactor(doctor): preserve lazy module boundaries
2026-07-25 19:02:30 -07:00
Peter Steinberger
8b9314b7e6 refactor(protocol): split gateway protocol registries (#113941) 2026-07-25 18:03:42 -07:00
Peter Steinberger
cb099f5193 refactor(daemon): split Windows scheduled task service (#113934)
* refactor(daemon): split scheduled task service

* refactor(daemon): narrow scheduled task internals
2026-07-25 18:03:32 -07:00
Peter Steinberger
fb26903d6c feat: add flexible multi-sidebar chat layout (#113712)
* feat(ui): add persisted sidebar layout model

* feat(ui): render flexible chat sidebar columns

* test(ui): align board sidebar fixture type

* fix(ui): preserve sidebar panels across responsive layout

* fix(ui): satisfy sidebar CI ownership and performance

* test(ui): derive discussion helpers from panel config

* test(ui): mount session rail through its registry

* fix(ui): preserve sidebar state across projections

* style(ui): format sidebar state fixes

* fix(ui): satisfy sidebar lint constraints

* refactor(ui): break sidebar layout import cycle

* fix(ui): stabilize sidebar panel rendering

* fix(ui): keep the narrow sidebar grid off for an empty layout

The two-row narrow grid reserved a panel row even with no sidebar panel open, halving the primary surface height on every default mobile chat pane.

* fix(ui): lazy-load chat sidebar region

* style(ui): format rebased chat state page

* chore(ui): raise sidebar startup baseline

* fix(ui): preserve sidebar move and resize state

* chore(ui): align sidebar startup baseline

* chore(ui): refresh sidebar startup baseline

* test(ui): register discussion element in isolated test

* fix(ui): persist the dragged panel as the collapsed active panel

Drag moves activated the panel in its destination column but left the separate persisted collapsed-mode selection stale, so the narrow layout foregrounded the wrong panel after a move and the stale choice survived reload.

* fix(ui): preserve resolved canvas URL in detail panel
2026-07-25 17:45:29 -07:00
Peter Steinberger
52c11daed6 fix(control-ui): renew canvas capability so agent widgets keep rendering (#113702)
* fix(control-ui): renew canvas plugin surface capability so widgets keep loading

* fix(control-ui): scope canvas capability renewal to connections

* test(control-ui): cover rotated capability on a newly mounted widget

* fix(control-ui): bind canvas URLs to mounted frames

* fix(control-ui): keep reported widget heights across capability rotation

* fix(control-ui): keep canvas lease type private

* test(gateway): prove Canvas capability rotation over HTTP

* perf(control-ui): lazy-load canvas surface renewal

* chore(control-ui): raise startup budget baseline for canvas renewal lease

* fix(control-ui): satisfy promise-executor lint in gateway store test
2026-07-25 16:14:20 -07:00
Peter Steinberger
ddd3d4fdb3 refactor(agents): split before-tool-call policy pipeline (#113885)
* refactor(agents): split before-tool-call pipeline

* refactor(agents): isolate before-tool call types

* fix(agents): restore approval resolution type import
2026-07-25 15:59:30 -07:00
Peter Steinberger
bfc8c89c35 refactor(agents): split direct compaction pipeline (#113880) 2026-07-25 15:49:44 -07:00
Peter Steinberger
512b48bd26 refactor(auto-reply): split reply run orchestration (#113881)
* refactor(auto-reply): split reply run orchestration

* refactor(auto-reply): keep reply run types private
2026-07-25 15:00:45 -07:00
Peter Steinberger
5402ef192d refactor(matrix): split SDK client responsibilities (#113815)
* refactor(matrix): split SDK client responsibilities

* fix(matrix): drop stale split imports

* fix(matrix): narrow initialized decrypt bridge
2026-07-25 14:59:35 -07:00
Peter Steinberger
c0e4caae55 refactor(agents): split model fallback responsibilities (#113876)
* refactor(agents): split model fallback responsibilities

* fix(agents): remove unused fallback exports
2026-07-25 14:54:57 -07:00
Peter Steinberger
73bead1239 refactor(agents): split exec tool pipeline (#113807)
* refactor(agents): split exec tool pipeline

* chore(agents): keep exec prep type private
2026-07-25 13:58:01 -07:00
Peter Steinberger
731a5d1e45 refactor(cron): split service operations (#113830)
* refactor(cron): split service operations

* fix(ci): keep cron preparation type private
2026-07-25 13:41:29 -07:00
Peter Steinberger
71f7f9e4c2 refactor(gateway): split node server methods (#113832) 2026-07-25 13:03:42 -07:00
Peter Steinberger
e144bc16d8 refactor(gateway): split chat display projection (#113806)
* refactor(gateway): split chat display projection

* refactor(gateway): trim projection imports

* refactor(gateway): keep projection helper private
2026-07-25 12:20:58 -07:00
Peter Steinberger
502d1cf56b refactor(browser): split Playwright interaction tools (#113771)
* refactor(browser): split Playwright interactions

* test(browser): track split lint suppressions

* refactor(browser): inline interaction timeout resolver
2026-07-25 12:17:43 -07:00
Peter Steinberger
bde7033ac4 feat(models): dynamic model catalogs via hosted refresh overlay (#113660)
* feat(models): add remote catalog core

* feat(config): add model catalog refresh settings

* feat(models): wire remote catalog refresh

* ci(models): publish hosted catalog

* docs(models): document catalog refresh

* test(models): allow catalog count growth

* refactor(models): preserve safe catalog merge

* ci(models): publish catalog through GitHub

* docs(models): link public catalog history

* fix(model-catalog): enforce Kysely store access

* fix(model-catalog): trim unused catalog exports

* docs(models): refresh generated docs map
2026-07-25 12:15:05 -07:00
Peter Steinberger
0a2a629117 refactor(skills): split ClawHub lifecycle (#113789)
* refactor(skills): split ClawHub lifecycle

* refactor(skills): privatize ClawHub store internals
2026-07-25 11:33:47 -07:00
Peter Steinberger
eb4eaea39b refactor(agents): split embedded session lock lifecycle (#113779)
* refactor(agents): split session lock lifecycle

* refactor(agents): tighten session lock modules

* fix(agents): import session publication type
2026-07-25 11:28:47 -07:00
Peter Steinberger
5f98ffb445 refactor(agents): split model auth responsibilities (#113793)
* refactor(agents): split model auth responsibilities

* refactor(agents): keep env label helper private
2026-07-25 11:27:08 -07:00
Peter Steinberger
8b5337caf8 refactor(ui): split chat state container (#113780)
* refactor(ui): split chat state container

* refactor(ui): keep lightbox helper private
2026-07-25 11:23:44 -07:00
Peter Steinberger
822eaa53f7 refactor(memory): split LanceDB plugin entry (#113772)
* refactor(memory): split LanceDB plugin entry

* refactor(memory): keep hooks in plugin entry
2026-07-25 11:21:31 -07:00
Peter Steinberger
0bc11c4da0 refactor(agents): split embedded model resolution (#113785)
* refactor(agents): split embedded model resolution

* refactor(agents): keep model helpers private
2026-07-25 11:13:55 -07:00
Peter Steinberger
0ff2c82033 refactor(mattermost): split inbound monitor (#113775)
* refactor(mattermost): split inbound monitor

* test(channels): follow mattermost history split
2026-07-25 10:56:58 -07:00
Peter Steinberger
c65f509658 refactor(doctor): split configured plugin repair (#113777) 2026-07-25 10:56:40 -07:00
Peter Steinberger
7db5d5450e refactor(config): split validation responsibilities (#113713)
* refactor(config): split validation god file

* refactor(config): keep validation helper type private
2026-07-25 09:56:09 -07:00
Peter Steinberger
bd71c34015 refactor(gateway): split server implementation (#113725) 2026-07-25 09:12:12 -07:00
Peter Steinberger
1bfd207a54 refactor(doctor): split legacy model migrations (#113717) 2026-07-25 08:52:43 -07:00
Peter Steinberger
1a11f5394e refactor(ui): split workboard view (#113730) 2026-07-25 08:29:39 -07:00
Peter Steinberger
bab4b2973a refactor(ui): split config view panels (#113715) 2026-07-25 08:28:40 -07:00
Peter Steinberger
f808147f62 refactor(ui): split chat thread projection (#113714)
* refactor(ui): split chat thread projection

* refactor(ui): tighten chat thread module boundaries

* refactor(ui): type chat stream grouping locally
2026-07-25 08:28:02 -07:00
Peter Steinberger
589f28b57a refactor(plugins): split manifest normalization (#113720) 2026-07-25 08:27:03 -07:00
Peter Steinberger
22226bf2f6 refactor(agents): split CLI runner execution (#113716)
* refactor(agents): split CLI runner execution

* refactor(agents): keep CLI log helper private
2026-07-25 08:07:41 -07:00
Peter Steinberger
bd8277c248 refactor(agents): split subagent lifecycle (#113711) 2026-07-25 07:29:42 -07:00
Peter Steinberger
1dcb35b204 refactor(agents): split main session recovery (#113687) 2026-07-25 13:39:15 +00:00
Peter Steinberger
46a1338918 refactor(infra): split heartbeat runner (#113668)
* refactor(infra): split heartbeat runner

* refactor(infra): keep heartbeat helpers private
2026-07-25 06:37:26 -07:00
Peter Steinberger
4aa4ff7250 refactor(matrix): split CLI command groups (#113664)
* refactor(matrix): split CLI command groups

* refactor(matrix): keep CLI helpers private

* refactor(matrix): retain profile result type contract
2026-07-25 06:37:08 -07:00