Commit graph

42 commits

Author SHA1 Message Date
Ayaan Zaidi
7753ffb475
fix(ui): publish initial model catalog on connect (#145927)
## What Problem This Solves

The first web model picker open could wait seven seconds for a catalog reply. A late initial snapshot could also restore an old saved account after the user changed it.

## Why This Change Was Made

Publish the existing catalog during connection setup through the authenticated request dispatcher. Initial and ordinary replies use the same cache publication checks, so delayed work cannot replace a newer session selection.

## User Impact

Published choices appear on first open and reconnect while discovery continues. Warm reopen stays fast, without an extra catalog request from opening the picker.

## Evidence

With catalog replies held for seven seconds, the original picker had no usable choices until the reply. The corrected New Session and active-chat pickers showed rows within 100 ms; warm reopen took 8–12 ms. Neither open added a request.

Real browser/Gateway tests cover reconnect and a saved-account change before snapshot completion. Sixteen valid configuration fixtures start; an invalid legacy fixture rejects as expected. Previous-version source builds connect in both directions; installed-release upgrades were not tested. Full consumer analysis: `consumers.md`.

## Compatibility

The new connect field is sent only after server advertisement. Clients without opt-in receive no snapshot. No protocol-version bump, configuration key, database change, or migration.

## Consumers

- New Session picker: uses published rows before its ordinary catalog reply.
- Active-chat picker: receives the saved session's model/account projection and retains newer selections.
- Mounted browser shell: preserves initial publication across reconnect.

## Invalidation

Connection, identity, configuration, session metadata, session mutations, and explicit refresh retire stale reads. Expiry and eviction retain publication ordering; delayed results cannot refill invalidated state.

## Contention

Snapshot publication adds no lock, transaction, or await. The existing dispatcher owns catalog reads; the common cache owner admits results synchronously.

## Tests

Browser picker tests exercise fresh, saved-session, refresh, and reconnect flows. Gateway tests enter connect, `models.list`, and `sessions.patch`. Handshake, cache, metadata, and sibling checks pass. The shared test partition guard passes with the server grouping from #146212. Full automated validation is still required.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-12 23:39:39 +05:30
Peter Steinberger
b5134de70a
feat: manage and reload plugins without Gateway restarts (#145484)
* feat: manage and reload plugins without Gateway restarts

* fix(plugins): preserve Bun loading and align lifecycle fixtures
2026-09-11 23:12:58 -07:00
Peter Steinberger
2bf30bac7b
feat(update): retain run history and share outcome reports (#138690)
* feat(update): persist update runs and share outcome reports

Carry one durable run ID through gateway admission, CLI orchestration,
managed handoff, campaigns, restart sentinels, and boot verification.
Record refusals and native recovery outcomes so every attempt has an
operator-visible result, with bounded redacted metadata in the shared DB.

Add update.runs.get/list and update.run.changed without removing existing
status fields. Use one report renderer for chat, CLI, status, and restart
notices; retain the stable pre-ledger sentinel upgrade adapter.

Keep the existing activation order and detached helper process model.

(cherry picked from commit 13ca668a9d4f788586f03189978fb8fa9b8c651a)

* docs(update): document durable run history and reports

Describe run IDs, history RPCs, report-backed status and restart notices,
and the same-version lazy update_runs table in the shared state DB.

(cherry picked from commit 7e798570f7ddd7f5fcdb5b5684d49da523217c43)

* fix(update): preserve diagnostic paths and terminal run history

Port precise redaction and immutable JSON bounding, share the run vocabulary,
and regenerate native protocol models. Preserve phase identities and timestamps
when diagnostic details exceed the byte budget.

Record Windows cancellation at the recovery owner before releasing its process
exit gate, including restoration failure. Remove the redundant outer inference.
Cover actual history handlers and retain newer main service-refresh behavior.

Independent Codex P0/P1 review found no actionable findings. Focused ledger,
report, watcher, protocol and history handler proof passed; cancellation tests
failed before the fix and passed afterward. Full validation follows separately.

* refactor(update): keep Windows autostart recovery in one owner

* test(update): align fixtures with recovery ownership

* fix(update): preserve state before run admission

Admit CLI ledger writes only after validation and ownership checks. Preserve existing history on rejected invocations while verifying valid dry-run records through the built CLI. Align the protocol owner inventory and DDL exemption, and refresh the Workboard asset pointers generated by the additive protocol schemas.

* fix(gateway): register update.runs methods on the current protocol train

* test(gateway): list update.runs methods on the 2026.8 train

* chore(workboard): refresh built Control UI asset pointer for update run schemas

* fix(gateway): register update.runs methods on the 2026.9 train

* test(gateway): build update run broadcast fixtures with the client registry
2026-09-04 20:17:32 -07:00
Peter Steinberger
6a97159ece
feat: add experimental plugin UI customization (#134943)
* feat: let plugins customize the Control UI

* fix: harden feature plugin lifecycle and artifact activation

* fix(plugins): complete native UI integration

* fix(plugins): preserve hook ownership and composer styling

* chore(workboard): refresh generated browser assets

* test(android): hold reconciliation replies until delivery checks

* refactor(plugins): simplify feature UI ownership

Share bundle validation and scoped host-handle cleanup. Consolidate
Workboard component and draft-save lifecycles, and remove unreachable
loading/enablement paths and retired select styles.

Keep both compiler regression matrices through shared fixtures, remove
duplicate tests, and regenerate the reduced locale catalog and browser
asset references.

* test(ui): return session snapshots from worker stop fixtures

* fix(ui): hydrate session rosters from selected agents

Use the application selection owner for bootstrap and reconnect, retain its filtered query for later refreshes, and remove duplicate sidebar refreshes. Cover delayed bootstrap, saved selection, and offline selection changes.

Refresh generated protocol and browser assets after the rebase, and share hydration fixtures and roster reconciliation helpers.

* perf(ui): defer plugin initialization and customization

Load plugin assets with the existing lazy SDK host and load customization controls on demand. Keep activation cleanup with the runtime and preserve dialog reload state across close and reopen.

Remove retired Workboard selectors, move glyph styling into the plugin, and regenerate its browser revision. Preserve the existing startup payload limits.

* refactor(ui): separate native asset loading from host services

* test(plugins): align UI integration with current main

Refresh canonical Workboard assets and the Control UI boot inventory after rebasing. Match the current bootstrap signature, delegated permission policy, and widget Delete label in existing regressions.

* chore(workboard): refresh browser revision after rebase

* chore(ui): refresh generated assets after main sync

* fix(plugins): preserve native UI lifetimes after main sync

Keep saved plugin panels closable while their registration is unavailable and prevent actions withdrawn during resolution from starting. Defer native view mounting code through plugin activation while preserving synchronous built-in rendering.

Integrate the shared Dashboard side-panel lifecycle, remove the session helper type cycle, reuse core Gateway classification, and consolidate menu coverage. Refresh generated assets after the main rebase.

Validated with focused Gateway/UI tests, failing/passing lifetime regressions, 14 browser scenarios, typechecks, lint, cycle and assertion guards, and the enforced Control UI performance check.

* chore(ui): refresh boot manifest after main rebase

* chore(ui): refresh feature integration after main update

Preserve current composer admission and sidebar ownership while adopting the canonical formatter output and browser assets. Complete the existing panel fixture with the new desktop-focus contract.

* test(ui): verify native plugin asset admission

* test(ui): await service worker activation in phone proof

* refactor(plugins): remove redundant UI plumbing

* feat(plugins): gate custom UI behind an experimental lab

* fix(plugins): align Labs helper types with callers

* style(ui): format retained plugin panel definitions

* test(ui): preserve minimized dashboard in native plugin flow

* fix: preserve plugin UI edits and refresh ordering

Synchronize reapplied template fields, fence shared widget reads across moves, and retain newer mutation errors through queued refreshes. Verify immutable browser assets when Windows reports a directory collision. Keep the checkout helper terminal exit outside exception handlers to avoid Python 3.9 context-cycle hangs.

* test: align plugin UI proof with current panel layout

* test: repair plugin UI validation and generated checkout helper

* test: bind session search fixtures to their selection owner

* fix: preserve session search ownership and execution denial proof

* test: use a real page element for plugin sidebar fixtures

* fix: respect native plugin UI deployment boundaries

* test: share UI fixture isolation across runners

* refactor: share the native plugin asset root

* test: check failure trailers on their owning stream
2026-09-04 09:50:51 -07:00
Peter Steinberger
516fbb224b
feat(control-ui): add human mentions and temporary inbox (#135853)
* feat(control-ui): add human mentions and temporary inbox

Bind explicit composer selections to authorized human profiles and deliver
recipient-owned Inbox entries only after a fresh canonical user-message commit.
Carry selections through first sends, queues, recovery, and native transcript
mirrors without treating replay or rewritten content as a new mention.

Reuse existing profile/session visibility and Web Push owners. Keep the Inbox,
dismissals, and delivery dedupe bounded and process-local; no SQLite schema or
migration change. Add an optional default-off browser notification category.

Document the API, temporary retention, retry semantics, and notification limits.
Refs #135645.

* refactor(gateway): keep active-leaf response helper private

The responder is now called only inside its admission owner. Remove the obsolete export while preserving its response shape and control flow.

This clears the production and full-tree unused-export guard.

* refactor(mentions): consolidate visibility and commit ownership

Reuse profile session filters instead of fabricating recipient clients.
Derive request types from the wire schema, reuse sanitized input, carry one
Codex persistence receipt, and let Inbox rows own their navigation target.

Preserve current permissions, replay fencing, and explicit dismissal.
No schema, migration, configuration, or protocol changes.

* test: fix cache restore and sidebar navigation fixtures

Seed only historical declaration chunks outside the final cache inventory,
so the portable restore still proves every owned output byte. Reuse the
test-only repair from upstream 5e6117d17d.

Wait for committed routes before interacting with the Inbox after navigation,
while preserving every click and scope/dismissal check. Capture diagnostics
before closing the browser on failure.

Co-authored-by: ruel225 <liu.rui25@xydigit.com>

* fix(ci): align checkout history and fixture readiness

Reuse the authenticated security-history checkout repair from #136232.
Wait for rendered Android model labels and align the plaintext bootstrap
fixture with its existing manual TLS preference.

Retain bounded Windows termination diagnostics and both primary and cleanup
failures without changing process-tree acceptance or shutdown deadlines.

* fix: publish real profile changes and stabilize CI fixtures

Emit profile invalidations only after an effective state write, through the
existing synchronous transaction and post-commit owner.

Keep shared Gateway test config at its owner and carry explicit model config
across preparation. Reuse the bounded subprocess helper for complete CLI JSON.
Preserve the alias regression cases in their existing owning test suite.

This repair adds no schema, Gateway configuration, protocol, or dependency.

* perf(control-ui): keep inbox state behind its lazy owner

Create mention state with the application-owned Inbox controller, retain it
across presenter remounts, and dispose it with that owner. Keep dismissal
identity policy in the dismissal store so startup does not load the full
Inbox projection. Preserve authorization, persistence, and size limits.

Co-authored-by: ruel225 <liu.rui25@xydigit.com>

* perf(control-ui): load compact menu styles with their renderer

Keep shared sidebar and chat-header compact menu styles with the module
that renders them instead of the startup shell. Preserve all declarations
and the existing startup size limit.

Co-authored-by: ruel225 <liu.rui25@xydigit.com>

* fix(ci): account for the human mentions schema owner

Keep the registry inventory check aligned with the additional reviewed
human-mentions module. No runtime or generated protocol change.

* refactor(control-ui): import mention type from its protocol owner

Use the same public type directly instead of the chat re-export. This
also resolves the adjacent-import conflict with current main without
changing emitted JavaScript or pulling unrelated changes into the PR.

* refactor(control-ui): keep dismissal types with their owner

Remove the type cycle between Inbox entries and dismissal storage by
moving the two dismissal types to the existing dismissal owner and
updating internal imports. Preserve runtime imports and behavior.

* refactor(gateway): apply request handler overrides once

---------

Co-authored-by: ruel225 <liu.rui25@xydigit.com>
2026-09-02 23:36:37 -07:00
Peter Steinberger
fcc0d52b88
feat(nodes): report live node host load, memory, and disk stats (#136859)
* feat(nodes): report live host resource stats

Paired CLI and macOS node hosts publish CPU count, load averages, memory,
and home-volume disk capacity on connection and every minute. Store each
snapshot with a Gateway receipt timestamp on the exact live node session,
expose it through node.list and node.describe, and broadcast updates to
readable operators. Stats disappear on disconnect without persistence or
prompt-context updates.

Route native worker node.event requests through the existing acknowledged
Gateway request bridge so object payloads and results survive unchanged.
Refresh generated protocol models and document the wire contract.

* refactor(node-host): publish host stats through the existing node-event frame

* fix(node-host): clear the host stats timer only when it was started

The unconditional clearInterval on connection retire counted as a lifetime
handle clear in the foreground runner test.
2026-09-02 22:15:55 -07:00
Peter Steinberger
e0ab990762
fix(control-ui): show Gateway suspension in account footers (#136220)
* fix(control-ui): show Gateway suspension in account footers

Publish the authoritative suspension admission phase in hello snapshots and immediate events, and share its footer projection across the main and Settings sidebars. Show Suspending… while preparing or draining and Suspended while prepared; clear only when admission reopens, preserving offline and restart precedence.

Cover expiry, scheduler recovery, host thaw, reconnect snapshots, and lifecycle cleanup. No new configuration, persistence schema, dependency, or protocol version. Verified with focused regressions, changed gates, builds, and an isolated real-Gateway browser flow.

* fix(protocol): declare native suspension event non-consumption

Record the approved iOS and Android disposition for the Control UI account-footer event. Native clients do not render that footer; no runtime handler or behavior change is needed. The protocol coverage checker and all 12 focused owner tests pass.

* test(ui): hold dropdown animations at interruption boundaries

Control the real CSS animation at active progress instead of racing delayed
animationstart delivery. Await next-frame interruptions before restoring the
original play state and rate.

Preserve dismissal, focus, cancellation, ownership, pending, and zero-duration
assertions. The same renderer-stall reproduction and all 49 dropdown tests pass.
No production, dependency, CSS-duration, or timeout changes.
2026-09-02 13:49:27 -07:00
Jason (Json)
8f8e774149
fix(ios): disclose model targets and enforce availability (#135044)
* fix(ios): disclose model selection target

* fix(ios): respect model availability in chat

* test(ios): prove model picker target variants

* chore(ios): refresh native localization inventory

* fix(ios): refresh model availability after session mutations

* fix(ios): refresh model availability during resync
2026-09-01 13:40:37 -06:00
Peter Steinberger
fe403619f9
fix(ui): chat stays blocked after model credentials recover (#132179)
* fix(ui): refresh chat readiness after credential recovery

Keep retained Chat and New Session on one accepted metadata publication
flow, preserve session profile locks, and notify clients when authoritative
metadata settles. Fence stale responses and publish persisted inline model
selections; refresh Android metadata through the same event.

Release note: Control UI chat recovers model readiness after credential or
selection changes without reloading, clearing drafts, or hiding prior errors.

* test(gateway): adopt prepared metadata projection fixture

* fix(ui): preserve metadata scope across chat interactions

Read slash commands from the active session publication and rebind retained global chats when agent selection changes. Preserve warm picker state until the authoritative session projection settles. Integrate runtime fixtures and move cross-layer persisted-selection coverage into the Gateway lane without changing check limits.

* test(ui): return valid empty lifecycle RPC results

* test(ui): keep metadata regression within suite limits

* test(gateway): supply prepared model metadata in auth recovery regression

* test(gateway): preserve session metadata read access

Extend the documented view/suggest read-policy matrix to chat history, startup, and metadata. Participation continues to gate mutations; existing none, incognito, and pending-profile guards remain unchanged.

* fix(android): retain session scope in chat metadata

Preserve profile-specific availability across metadata refreshes and same-agent session transitions. Carry the required prepared catalog owner into the Gateway lifecycle regression fixture.

* fix(ui): preserve draft controls when reconnecting

* fix(android): negotiate session metadata refresh scope

Advertise session-scoped chat metadata through hello capabilities while
preserving the shipped agent-only request and cache contract for older peers.

Refresh matching session mutations and missed metadata publications through
one synchronous request fence, retaining accepted catalogs and chat drafts.

* fix(ui): split streamed pull-request refresh from chat events

Keep the merged event owner below the unchanged 700-line lint bound.
Move existing link parsing and bounded stream refresh without changing
behavior, reply visibility, terminal recovery, or metadata invalidation.

The merged guard fails at 710 lines before and passes after. Existing
117 UI tests, forced UI types, lint, build, and isolated P0 review pass.
2026-08-29 05:10:23 -07:00
Peter Steinberger
b581ff0be7
feat(apps): resolve the per-profile accent live on iOS, macOS, and Android (#130598)
* feat(apps): resolve the per-profile accent live on iOS, macOS, and Android

Named follow-up from #130340: native apps now fetch the caller's own
profile accent (users.prefs.get, strict #rrggbb normalization) and prefer
it over the gateway accent, refetching on users.prefs.changed — the
gateway targets that event at the caller's own profile, so clients need no
identity logic. macOS stores it separately from the seam color so
settings-pane config refreshes cannot clobber it, and fetches bypass
ControlChannel.request to avoid degrading the channel on older gateways.
Profile-less and token connections are unchanged. Removes the ios/android
users.prefs.changed allowlist entries now that handlers exist.

* chore(macos): satisfy swiftformat explicit-self on profileAccentHex
2026-08-26 20:01:40 -07:00
Peter Steinberger
a4c01c1a77
feat(ui): save appearance preferences per user profile (#130340)
* 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.
2026-08-26 16:48:26 -07:00
Josh Avant
e6ed7e30cc
feat(audit): record session action decisions (#129093)
* feat(audit): record session action decisions

* fix(protocol): preserve session sharing client compatibility
2026-08-26 08:21:37 -07:00
Peter Steinberger
a3ca8466ee
fix(ui): refresh device labels after rename (#126432)
* fix(ui): refresh device labels after rename

* fix(ci): classify device label refresh event for mobile

* chore(protocol): regenerate Android gateway events
2026-08-19 14:18:50 -07:00
Peter Steinberger
af599f2cf7
feat(android): render durable progress card in chat (#125444)
* feat(android): render durable progress card in chat

Remove Android consumption of the legacy stream:"plan" agent events and in-flight plan snapshots.\n\nHandle progressCard.changed through the durable progressCard.get store and remove the Android protocol coverage allowlist entry.

* fix(android): refetch progress card on unattributable poke

The changed event carries the server-derived observer scope key, which the
client only learns from a get response carrying a card. Before that, a
canonical-keyed poke (e.g. global session scope) failed both match checks and
was silently dropped until reconnect. Unknown attribution now triggers an
authoritative refetch instead.

* chore(i18n): refresh native source baseline for progress-card strings

* fix(android): render legacy plan events when the gateway lacks the progress-card store

Released gateways through v2026.7.x emit stream:"plan" events and do not advertise progressCard.get, so retain a negotiated Android fallback.

Remove this branch with the gateway legacy dual-emit after the minimum supported gateway ships the progress-card store.
2026-08-17 18:42:19 -07:00
Peter Steinberger
60920998c0
feat(apps): migrate iOS/macOS plan surface to the durable progress card (#125442)
* feat(apps): migrate iOS/macOS plan surface to the durable progress card

Replace the legacy stream:"plan" agent-event pipeline (runId-scoped state,
run-gated pill) with the sessionKey-scoped progress-card store: the shared
chat surface now renders progressCard.get snapshots, refetches on
progressCard.changed pokes with revision dedupe, clears on null-revision
pokes, and persists the card after the run completes. The card renders
markdown through the shared markdown view plus typed steps. Legacy Apple-side
plan handling (agent-event case, run-snapshot plan reconciliation,
OpenClawChatPlanStep parsing) is deleted; gateway emission stays for Android.
Removes the ios progressCard.changed coverage allowlist entry so the check
enforces the handler.

* chore(i18n): refresh native inventory for the progress-card rename

* fix(apps): keep the last progress card when a refresh fails

A transient progressCard.get failure no longer clears an already-rendered
durable card; only a successful null fetch or a null-revision poke clears it.
2026-08-17 18:07:17 -07:00
Peter Steinberger
7170a6231a
feat(agents): unify agent status into a durable progress_card (#125125)
* feat(agents): unify agent status into a durable progress_card

Replace the write-only update_plan to-do tool and the fragmented plan
rendering with one durable status artifact per session: progress_card
({plan?, markdown?}, replace-on-write, 8 KiB markdown / 50-step caps).
Cards persist in a lazy-additive session_progress_cards table in the
per-agent DB (no schema-version bump), broadcast progressCard.changed,
and render from the store with exactly one live placement per view
(session rail when visible, else the composer-adjacent bar); transcripts
collapse to one-line receipts, and the sidebar hovercard shows other
sessions' cards inline (markdown + <progress>, DOMPurify allowlist, no
iframes). The three stream-derived plan renderers and their dedup
heuristics are deleted.

Codex runs disable the native plan tool per thread
(tools.update_plan.enabled=false) and receive progress_card via the
dynamic-tool bridge; compaction restore now reinjects the card (steps +
bounded markdown). Card writes still emit the legacy plan stream event so
native apps and channels keep working until their per-platform
migrations. Policy names map update_plan -> progress_card; the shipped
tools.updatePlan=false kill switch is honored.

Net -277 production LOC; -480 test LOC.

* test(agents): regenerate Codex prompt snapshots for update_plan thread-config disable

* chore(protocol): allowlist progressCard.changed for native apps pending card migration

* fix(ci): repair progress card integration checks

* fix(codex): canonicalize native progress cards

* test(gateway): reconcile progress card method order

* test(codex): stabilize native approval fixture
2026-08-17 09:44:04 -07:00
Vyctor H. Brzezowski
3f006ba0fc
fix(ui): complete mobile pairing after code redemption (#120933)
* fix(ui): complete pairing setup lifecycle

Redeemed and expired mobile setup codes stayed on screen as usable QR
codes, so a successful pairing had no visible outcome and expired bearer
material still looked live.

The Gateway now mints an opaque, non-authorizing setupId beside each
setup credential, returns its authoritative expiresAtMs, records the
terminal outcome of the exact redemption, and broadcasts
device.pair.setup.completed. Because that broadcast is dropped for
buffered operator sockets, the completion is persisted first and can be
reconciled through the new device.pair.setupStatus method: the Control UI
asks for the recorded outcome before it may present a credential as
expired, so a pairing that succeeds is never shown as a failure.

The Control UI models one closed lifecycle (selection, loading, waiting,
success, expired, error) correlated only by the active setupId, and
Pairing help now carries the external-link affordance.

* fix(ui): preserve unknown pairing outcomes

* test(ui): target pairing terminal headings

* test(ui): align pairing completion fixture

* fix(gateway-protocol): decode setup-code results from older gateways

Older protocol-v4 gateways omit the new setupId and expiresAtMs
lifecycle fields, so requiring them in the generated native model broke
decoding an existing device.pair.setupCode response. Keep both optional
at the wire boundary, require lifecycle metadata before the Control UI
enters its waiting state so a missing outcome stays visible, and cover
the legacy payload with Swift and schema regressions.

* fix(ui): surface rejected pairing dialog loads

The lazy pairing chunk could reject while its overlay was already open,
leaving the shell rendering nothing at all. Record the rejection on the
shell and render a recoverable modal with a reason and a retry so the
open action always ends in a visible outcome.

* fix(state): preserve pairing setup schema compatibility

* test(gateway): cover pairing setup release train

* fix(gateway): commit pairing setup completion atomically

* refactor(state): distinguish setup transaction helpers

* refactor(state): remove obsolete bootstrap restore path

* fix(gateway): preserve setup handoff type safety

* fix(gateway): keep pairing completion terminal after consume

* fix(gateway): validate Watch binding during setup commit

* fix(gateway): revalidate setup credential expiry at commit

* chore: refresh Plugin SDK API contracts after rebase

* fix(pairing): prune expired setup completions

* chore: retrigger CI

* fix(protocol): deduplicate setup expiry field

* fix(protocol): refresh pairing setup clients

* fix(gateway): make pairing setup completion durable

* fix(ui): retire expired pairing credentials immediately

* fix(ui): keep pairing dialog visible while loading

* fix(macos): align setup result initializer order

* fix(gateway): restore generic bootstrap retries

* chore(ui): record pairing startup budget

* chore(ui): refresh pairing startup budget

* style(gateway): format maintenance imports

* test(gateway): cover session-sharing mock

* fix(state): defer setup correlation schema

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-14 11:10:30 -07:00
Peter Steinberger
6c9916a48a
feat(runners): publish atomic device runner inventory (#123094)
* feat(runners): publish atomic node inventory

# Conflicts:
#	docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json
#	src/gateway/server-methods/environments.ts
#	src/gateway/worker-environments/device-provider.test.ts

* fix(runners): refresh topology on inventory removal

* fix(runners): resolve session host type overlap

* fix(protocol): document mobile runner inventory event

* fix(runners): project authoritative session host state

* docs(nodes): clarify prepared session host status
2026-08-13 04:54:26 -07:00
Peter Steinberger
cc2fc55f9b
feat: portals — expose agent-run dev servers to the operator (#122536)
* feat(protocol): add portal methods and event

Bump the reviewed protocol owner-module count from 55 to 56.

* feat(gateway): add portal service and reverse proxy

* feat(agents): add portal tool

* fix(gateway): refine portal URL and proxy auth

* refactor(gateway): keep portal helper types private

* fix(gateway): declare portal transport service

* test(gateway): satisfy portal proxy lint

* test(gateway): narrow websocket payload types

* refactor(protocol): compact portal schema exports

* fix(gateway): export portal protocol types

* feat(ui): add portals page

* docs(gateway): add portals guide

* fix(gateway): dial portal targets via localhost dual-stack

Vite and other Node >=17 dev servers bind ::1 only for localhost, so a
fixed 127.0.0.1 dial 502s on the default path. Use hostname localhost
with family autoselection and rewrite Host to match.

* fix(gateway): type portal dual-stack connection

* fix: satisfy portal integration gates

* fix(gateway): isolate portal cookie jars per target

Cookies are hostname-scoped, not port-scoped, so the per-port origin
split alone let Gateway plugin-auth cookies reach agent-run targets.
Forward only cookies carrying this portal's own name prefix (stripped),
rewrite target Set-Cookie names to the prefixed form incl. the WS 101
handshake, and drop Domain attributes.

* fix(ui): detect unreachable portals behind proxied gateways

Probe the portal origin from the browser (no-cors, 4s timeout) and show
a recovery notice with the gateway-host URL instead of a dead iframe
when only the gateway port is exposed (Serve/Funnel/reverse proxy).
Docs: cookie isolation + reachability; zh-CN glossary entry.

* test(ui): satisfy portal reachability lint

* test(gateway): provide control UI request hosts

* chore(protocol): regenerate after rebase

* fix(gateway): namespace portal auth cookies by listener

* fix(gateway): scope portal token URLs to write-capable clients

The portal bearer token rides in the summary url/tokenQuery; portal.list
is operator.read and portal.changed fans out to read subscribers, so a
read-only client could harvest an openable URL. Make those fields
optional, redact them from read-scope list responses, and drop them from
every portal.changed broadcast; write/admin clients still receive them
and the UI refetches the list on change.

* docs(web): list the portals route

* fix(gateway): type portal open credentials

* docs(gateway): clarify portals PORT/PUBLIC_URL are agent-set

Opening a portal creates only the proxy listener; the agent sets PORT
and PUBLIC_URL in its own exec command, matching the portal tool
contract. Removes the implication of an automatic env handoff.

* chore(protocol): regenerate portal models

* style(gateway): format portal method-order assertions

Rebase union-merge left the portal.list assertion wrapped; oxfmt fits it
on one line.

* chore(plugin-sdk): refresh API baseline after rebase

* chore(plugin-sdk): refresh API baseline after rebase

* chore(protocol): refresh portal event order after rebase

* chore(plugin-sdk): refresh API baseline after rebase

* fix(gateway): pin portal referrer policy to no-referrer

The portal URL carries its bearer token in the query, and upstream
response headers are copied verbatim, so a target answering with
Referrer-Policy: unsafe-url could leak that URL to every third-party
origin it references. Force no-referrer after the copy and drop any
inbound Referer that still carries the token before forwarding.
2026-08-13 00:46:11 -07:00
Peter Steinberger
4b46a14a54
feat(apple): show live subagent activity in chat (#121815)
* feat(apple): show live subagent activity in chat

* chore(apple): refresh native localization inventory
2026-08-10 21:50:13 -07:00
Peter Steinberger
8c3c8a2ab0
feat(android): show live subagent activity in chat (#121813)
* feat(android): show live subagent activity

* chore(android): mark task event handled

* fix(android): localize activity diff chips

* fix(android): reconcile dropped task activity
2026-08-10 21:08:44 -07:00
Peter Steinberger
662abec754
feat(gateway): push session PR indicators to subscribed clients (#115643)
* feat(gateway): push session PR indicators to subscribed clients

* test(gateway): fix PR indicator drift

* chore(protocol): allowlist PR-indicator event for native apps (Control UI surface)

* test: align PR indicator lifecycle coverage

* fix(ui): scope session PR store listeners to active watchers
2026-07-29 06:30:01 -04:00
Bryan Tegomoh, MD, MPH
15866acef5
Refresh macOS skills after node reconnect (#107660)
* Refresh macOS skills after node reconnect

* fix(macos): queue skills refresh during reconnect

* chore(i18n): refresh native skills inventory

* fix(macos): refresh skills from gateway invalidations

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* fix(android): update gateway event catalog

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* refactor(skills): keep remote eligibility helper bounded

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* ci(test): pin model shard worker budget

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* ci(test): isolate network-sensitive agent suites

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* test: align isolated prefix routing expectation

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* test(macos): isolate gateway process test port

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* fix(macos): recover skills after failed refresh

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

* fix(ci): avoid duplicate agent test routing

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-28 09:10:45 -04:00
Peter Steinberger
90aee82793
feat(sessions): suggestion queue + typing indicator (#113173)
* feat(protocol): add session collaboration contracts

* feat(gateway): add session suggestion queue and typing

* feat(ui): add session suggestion controls

* fix(collaboration): restrict suggestion resolution

* fix(collaboration): dedupe suggestion dispatch modes

* fix(collaboration): preserve resolver identity

* fix(collaboration): reconcile suggestion state

* fix(collaboration): filter identityless suggestion events

* fix(ui): expose full suggestion text

* fix(collaboration): durably claim suggestion dispatch

* fix(collaboration): harden suggestion events and typing

* fix(collaboration): reconcile suggestion races

* fix(ui): reconcile suggestion capabilities

* fix(collaboration): close suggestion privacy races

* test(ui): satisfy suggestion lifecycle lint

* fix(collaboration): fence resolved suggestion events

* test(collaboration): type deferred audit result

* fix(collaboration): fence delayed typing events

* fix(ui): coalesce suggestion refreshes

* fix(ui): preserve resolved self suggestions

* fix(collaboration): enforce draft suggestion visibility

* fix(collaboration): fence post-dispatch finalization

* fix(ui): retain suggestions across visibility changes

* fix(collaboration): fence suggestion context and archives

* fix(collaboration): fence suggestion resolve lifecycle

* fix(collaboration): map suggestion replacement races

* refactor(gateway): extract session typing state

* fix(collaboration): integrate suggestion storage with session nodes

* refactor(gateway): extract session sharing snapshot cache

* fix(collaboration): satisfy protocol and deadcode gates

* fix(ci): register iOS release script entrypoints

* fix(collaboration): fence typing by session instance

* fix(collaboration): enforce incognito suggestion privacy

* docs(ui): clarify solo suggestion dormancy

* test(gateway): preserve incognito literal type

* test(gateway): split session typing coverage

* test(gateway): register collaboration method expectations
2026-07-23 18:59:58 -07:00
Peter Steinberger
6251343384
feat(sessions): visibility states, membership, and server-enforced participation (#112787)
* feat(sessions): enforce visibility and membership

* feat(ui): add session sharing controls

* docs: add session sharing implementation report

* refactor(sessions): use canonical creator identity

* fix(sessions): adopt creator ownership contract

* docs: refresh session sharing rebase report

* docs: record final creator integration proof

* docs: record final main rebase

* chore: drop worktree report artifact

* fix(sessions): keep drafts owner-only

* fix(ui): preserve redacted session restrictions

* fix(sessions): preserve scoped sharing authorization

* fix(sessions): re-verify session instance inside sharing mutation queue

* test(sessions): cover stale sharing mutation

* fix(sessions): bind membership to session instance, gate absence blocking on sharing

* fix(sessions): preserve entry normalization on rebase

* fix(sessions): atomic visibility instance guard, reset visibility on recreate

* docs(ui): name the absence-heuristic tradeoff and link follow-up

* feat(protocol): expose session sharing row state

* docs: note generated creator identity type

* fix(sessions): bind member writes and visibility rollback to session instance

* fix(ui): discard stale-connection sharing loads; drop worktree scratch files

* fix(ui): block composer only on observed sharing state, never on list absence

* fix(gateway): authorize agent runs against the resolved session (close keyless bypass)

* chore(protocol): allowlist Control UI-only session.sharing event for mobile

* test(config): record session.sharing keys in common-tier snapshot

* refactor(sessions): unexport internal sharing helpers

* test(gateway): update sessions changed routing assertions

* fix(sessions): align sharing identity with created actor

* fix(sessions): align membership identities and storage keys

* fix(gateway): re-filter drafts against fresh sharing state in sessions.list

* fix(gateway): drafts stay owner+admin only in the sessions.list fresh filter

* fix(ui): re-export sharing protocol types for the Control UI

* fix(ui): keep SessionSharingRole internal to satisfy deadcode gate

* fix(gateway): read runtime config lazily in session-mutation authorization

authorizeSessionMutation ran on every gateway request but eagerly called
context.getRuntimeConfig() — a non-trivial config resolve — for methods that
are never session mutations. Read config only once a real session-mutation
target is resolved. Also register the four session sharing methods in the
2026.7 release-train inventory test.

* fix(gateway): share one config snapshot across session-mutation authorization

Group rename/delete discovery and the authorization loop were each resolving
runtime config separately after the lazy-read change. Memoize the resolve so
non-session requests still pay nothing, while any session mutation resolves
config at most once and both discovery and authorization use that single
snapshot (no double reload, no mid-request config-change split).

* fix(gateway): resolve session-sharing CI gates

- isGatewayAdmin: null-safe connect access so internal/plugin-runtime runs
  (which reach authorization with a connect-less client) do not crash.
- emitSessionsChanged: scope only to a concrete session key; a [undefined]
  sessionKeys scope filters nothing correctly and would strip draft gating.
- session stores: mark the sync TOCTOU re-read and the sqlite_master
  existence probe as narrowly-justified raw SQLite primitives.
- tests: provide getRuntimeConfig to the session-action contract context,
  drop a shadowed 'call' binding, use structuredClone, and assert the
  agent-scoped sessions.changed broadcast shape.

* docs(gateway): note best-effort participation gate + refresh native i18n baseline

Session ownership/visibility are usability features, not a security boundary
(docs/concepts/multi-user.md, SECURITY.md); document that the pre-dispatch
authorization is intentionally not commit-bound to the resolved instance.
Also refresh apps/.i18n/native-source.json after the session-sharing protocol
codegen shifted line numbers of existing native strings (position-only).

* test(gateway): reset session sharing snapshots

* style(gateway): format sharing reset import
2026-07-23 08:53:57 -04:00
Peter Steinberger
0b080b9c2e
feat(apps): native session-list observer digests on iOS, Android, and macOS (#112597)
* feat(apps): native session-list observer digests on iOS, Android, and macOS

* fix(apps): satisfy swift, periphery, and lint gates for native digests
2026-07-22 01:53:29 -07:00
Peter Steinberger
89fe452991
feat(gateway): session observer digests over the utility model (#112216)
* feat(gateway): session observer digests over the utility model

* fix(gateway): split session-observer modules and satisfy ci gates

* fix(gateway): observer reads session entries without materializing agent state
2026-07-21 04:04:13 -07:00
Peter Steinberger
d7de67ae02
feat: ask_user follow-ups — harness convergence, channel finalization + reactions, native cards, docked web panel (#110372)
* feat(ask-user): follow-up harness slice

* feat(ask-user): follow-up channels slice

* feat(ask-user): follow-up native slice

* feat(ui): dock question panel above composer with stepper and compact stream summaries

* docs: refresh follow-up integration maps

* test(ui): align terminal summary proof

* fix(infra): echo declared option answers in terminal status when free-text is allowed

* fix(infra): keep reaction answering when display labels are formatter-adjusted

* fix(agents): settle plain-text claims only after question registration commits

* fix(agents,apps): commit-ordered claim persistence, claim-aware prompt delivery, non-blocking question refresh

* fix(harness,infra): reaction-appropriate question copy, caller presentations honored

* fix(native,infra,agents): local-expiry eviction, value-addressed reactions, reserve-before-request

* fix(infra,android): dual-mode question resolver for compact callbacks; reset terminal retention on replayed pending

* fix(harness,discord): claim-aware prompt delivery in run helper; escape finalization labels

* fix(macos): merge transient-content visibility with question cards after main sync

* fix: repair ask user follow-up CI

* test: update limited bootstrap scope expectation

* fix: retain shared question card API

* chore: refresh native i18n inventory
2026-07-18 03:32:39 -04:00
Peter Steinberger
da44d52ac6
feat: ask_user — structured questions from the agent with web card, channel buttons, and text answers (#109922)
* feat(gateway): add transient question runtime (question.* methods + broadcasts)

* feat(agents): add blocking ask_user question tool with chat prompt delivery and text-reply claim

* feat(ui): interactive in-thread question cards for ask_user

* feat(channels): native tap-to-answer buttons for ask_user on Telegram, Discord, and Slack

* feat(ui): unify codex and gateway question cards with interactive gateway answering

* refactor(agents): collapse ask_user pending state to one registry; docs for ask_user

* fix(agents): include ask_user in normal gateway runs; add question-flow control-ui e2e

* test(ui): avoid credential-shaped fixture in question card test

* refactor(ui): reorder stream-group context keys

* fix(gateway,ui): validate question answers at resolve; reject secret/duplicate-label questions; UI retry and reconnect hardening

* fix(gateway,agents): canonicalize accepted option answers; bound ask_user option labels to 64 chars

* chore(ci): prune unused question exports, allowlist mobile question events, fix discord lint

* chore(ci): regenerate protocol/i18n/docs/tool-display artifacts for question surface

* fix(protocol): flatten QuestionRecord for native codegen; drop TS-only alias from schema registry

* chore(android): regenerate ask-user localization resources

* docs: regenerate docs map after rebase

* fix(ci): avoid stale read-only dependency disks

* test: remove stale reef lint suppression ratchet

* fix(ci): keep source locale drift advisory in release gates

* fix(ci): scope locale advisory handling to parity check
2026-07-17 22:24:17 +01:00
Peter Steinberger
cf029ea29e feat(agents): screen tool drives Control UI layout 2026-07-16 05:43:02 -07:00
Peter Steinberger
b1f4aac349 feat(agents): OpenClaw system-agent delegation
New openclaw delegation tool relays to openclaw.chat in-process; persistent
writes surface through the existing durable operator-approval registry as a
third system-agent kind (no parallel store), armed only by a human operator
in the Control UI — a delegated agent can never self-approve. Setup wizards
(channel/model/open-setup/open-tui) are refused in delegated mode so a
machine agent cannot complete setup or persist credentials unattended.
Vendor-neutral inference fallback ladder (requester route first, then other
authed providers by provider id). update.run removed from the gateway tool.
Caveman system-prompt fragment steers config/channels/plugins/agents/updates
to the openclaw tool. Doctor-owned state migration widens the approval-kind
constraint; runtime stays canonical-only.

Refs #107237
2026-07-15 01:56:31 -07:00
Peter Steinberger
20f145f9c1
fix(mac): enable terminal actions for paired-node sessions (#107361)
* fix(mac): enable paired-node terminal sessions

* fix(ci): align node worker bridge checks
2026-07-14 03:22:28 -07:00
Peter Steinberger
d8d2f83cc1 feat(terminal): open Codex/Claude catalog sessions in a terminal on their owning host
Catalog session rows (sidebar context menu + click), the built-in viewer
header, and a new "Open Codex/Claude sessions in" preference can launch the
native CLI (codex resume / claude --resume) in the operator terminal on the
machine that owns the session.

- Gateway-local sessions spawn through the existing terminal launch policy
  (sandbox/enabled gates preserved) with the resume command in the session cwd.
- Paired-node sessions run through a new seq-ordered node PTY relay: a
  duplex node-host command streams PTY output via node.invoke.progress and
  receives keystrokes/resize via a new node.invoke.input event, behind the
  unchanged terminal.* client protocol (TerminalSessionManager gains a backend
  abstraction; node relay reuses the streaming-invoke controller).
- Owner boundary: each plugin owns its resume command and builds argv from a
  validated thread id; the gateway routes node opens through the node command
  allowlist and plugin invoke policy (no advertisement-only trust), and nodes
  re-verify session eligibility before spawning.
- UI setting catalogOpenTarget + canOpenTerminal capability gate every entry
  point; capability requires the owning host to actually have the CLI.

Node PATH is normalized before command-availability probes, Windows .cmd/.bat
shims spawn via ComSpec, and catalog terminal opens reattach persisted tabs
before opening the new tab.
2026-07-13 22:20:50 -07:00
Peter Steinberger
4023eb92d2
feat(android): add foreground Voice Wake (#107081)
* feat(android): add foreground voice wake

* chore(android): refresh voice wake artifacts

* fix(android): refresh voice wake capability readiness

* chore(android): sync voice wake translations

* chore: keep release notes in PR context
2026-07-13 21:48:29 -07:00
Peter Steinberger
f167089d61
feat(nodes): continue Claude catalog sessions on paired nodes via streaming CLI agent runs (#105833)
* feat(gateway): stream node.invoke progress with idle timeouts and cancel

* feat(node-host): opt-in approval-gated claude cli agent run command

* feat(agents): run node-placed claude-cli turns through streaming node invoke

* feat(anthropic): continue claude catalog sessions on advertising nodes

* docs(nodes): document paired-node claude agent runs and continuation

* chore(plugin-sdk): admit spawn-invocation helper into surface budget

* fix(nodes): harden streaming invoke and node run policy from review findings

* test(node-host): prove multi-token tool-policy argv fails closed

* fix(nodes): allowlist cancel event for apps and break node-host type cycle

* chore(protocol): regenerate Swift gateway models for node invoke progress

* chore(node-host): drop test-only export surface flagged by deadcode ratchet

* style(node-host): merge invoke-types imports

* refactor(nodes): split node agent run modules to satisfy the LOC ratchet

* chore(protocol): regenerate android gateway models and sync export baseline

* chore(plugin-sdk): admit spawn-invocation helper and inherited deprecated drift into surface budget

* chore(plugin-sdk): tolerate inherited agent-core deprecated export

* fix(anthropic): drop node param parser orphaned by upstream catalog split

* ci: sync unused-export baseline with healed main

* test(agents): adopt renamed cli backend prepare helper

* style(node-host): format claude run spawn invocation

* ci: retrigger checks

* chore(plugin-sdk): pin surface budgets to healed-base counts

* fix(protocol): restore untyped lazy validators after upstream style change
2026-07-13 07:39:22 -07:00
Peter Steinberger
5b56a1f664
feat(gateway): land remaining durable-approvals stack and repair main gates (#104837)
* feat(gateway): propagate approvals to ancestor sessions with replay

Squash-rebased #103921 segment onto the native-clients tip on current main.
Session-scoped approval events publish sanitized pending/terminal
transitions to opted-in session audiences, with authoritative pending
replay on stream subscribe and durable-expiry reconciliation through the
owning manager. SessionApprovalEvent/Replay wire types export from the
approvals owner module; Swift models regenerated.

(cherry picked from commit 2d1dcf9747044710111d0c730fc46ba6013a165c)
(cherry picked from commit ccf88efd56b513d07599ffcee997bf0cddaf9adc)
(cherry picked from commit 8eb33f59f00ec9ee4625259b76bd7ae60d2cd480)
(cherry picked from commit c297cbc93c8401e9c79c20f242245a194f5dd236)
(cherry picked from commit 93d68f28601ff37d863da4db009504993a22533a)
(cherry picked from commit d285ccd8b2f212bb57e814107e7709c896e96b7d)
(cherry picked from commit 5c536982231402b1b58d1683794ea9948b32d077)
(cherry picked from commit 1646e5b6dc4dd20de54e10f110a5909be18d7d50)
(cherry picked from commit 35cf1b705b247fb282f4d56ffe74bd2fb238221f)
(cherry picked from commit 4f8ef3699de54d9135d0a36bee9c4c11f5a51173)
(cherry picked from commit 74fbdcc99d5fff062d67760a51dba4ee9e84479b)
(cherry picked from commit 222b285502681d2be14b1819798e9ee5555f431e)
(cherry picked from commit b9e744951f676b600e7c5322bfd04b0e99797c07)
(cherry picked from commit 9e904efde5b6407b2d4748313ffc3ea57b4f33a7)
(cherry picked from commit 678f2384fafa29ea3073aacf55de64e69f50565d)
(cherry picked from commit 72842e5cf6e9f4aba7ee8a68b48db0011e10db73)
(cherry picked from commit be74c25e80e84a1f065053766f23efb84822e811)
(cherry picked from commit 93ba8c4b096689b3c54d85745cd771f5147768dd)
(cherry picked from commit 8f23761372)

* feat(gateway): fail-closed plugin and tool approval gates

Squash-rebased #103932 segment onto the ancestor-propagation tip on
current main. Plugin node.invoke approvals claim a one-shot allow-once
decision before handing execution authority to the policy, so observation
or retry cannot replay a consumed approval; sibling tool gates bind
approval ids to their originating reviewer identity.

(cherry picked from commit 122df0d75281f572c012b6484ed5daf085d1d577)
(cherry picked from commit f23d8ac240a8dcf2a42c4daaae962263975a0ae8)
(cherry picked from commit 8632fb6436a224dac7a9a9ef0216884530de2c24)
(cherry picked from commit d9fe2dd5c53665e5732f5f3da5ed1907a686ade8)
(cherry picked from commit bb139f2c8aa36ddad70413e1ef79ff491a6f2ecd)
(cherry picked from commit 9b3e056b68ea515c7d4baef269289b6670570480)
(cherry picked from commit b11e66b59a7af1f3b08712de06864aed64d349e4)
(cherry picked from commit a12916ee592d03dfa35e86a2aaede4476a5d4e5d)
(cherry picked from commit d699de840fc8346892b9ae244fe3c3e8c4413032)
(cherry picked from commit 9b7e5a9608f94732226776b801b3a2ac65329e05)
(cherry picked from commit 4b507593f1b2f10b4496984c84a2803b853e5c5e)
(cherry picked from commit 56408a186733c4eeb5aa76bd5907cde7b0cd3d5b)
(cherry picked from commit a4051d6d8353d39d6fa0b5d69c36e06c3cd3e796)
(cherry picked from commit 76829805a7135ed3a7984ae372edd0d872cb0dfc)
(cherry picked from commit a8b493f934b644ef8688a8044c2bdcdcc08d686d)
(cherry picked from commit aceb990597affb8388c341a10cb386ef96bed3e4)
(cherry picked from commit a29b0e75482470c53a9fb4fe3f204e14cf71868f)
(cherry picked from commit 2e3be08653d9332095a10ec13237c838258c0265)
(cherry picked from commit c9ae3d7202)

* fix: main-gate repairs for the durable-approvals stack

- android: capture createdAtMs on the pending exec-approval write at
  registration; canonical readback after a refresh that already replaced
  the visible rows was dropping the approval instead of surfacing the
  still-pending reconciliation message (#104913 merged without this)
- android: generous CI timeout ceilings in GatewayExecApprovalRuntimeTest
- agents: hermetic model-discovery test via the plugins/provider-runtime
  boundary (lazy plugin-runtime resolution hangs vitest workers since #104770)
- cli: usage-cost settle-budget test asserts the budget bound on every call
  instead of pinning the poll count (fast hosts fit a second poll in 50ms)
- protocol coverage: allowlist session.approval for ios/android (native
  review rides exec.approval push/nudge delivery)
- docs map, native i18n inventory, plugin-sdk api baseline regenerated
2026-07-12 10:51:38 +01:00
Peter Steinberger
55254c3a0b
feat(nodes): route alerts to the active computer (#105083)
* feat(nodes): route alerts by active computer

* fix(ci): allowlist node presence mobile coverage

* test(prompts): refresh node presence snapshots

* fix(macos): await presence reporter actor hop

* fix(macos): type presence test delivery state

* docs(nodes): add active presence guide

* docs(nodes): format presence troubleshooting
2026-07-12 10:05:50 +01:00
Peter Steinberger
d628e35574
fix(apple): prevent stale thinking after model switches and reconnects (#103767)
* fix(apple): prevent stale model thinking state

* test(apple): complete attachment transport fixture

* fix(apple): scope model patch ordering by route identity

* chore(apple): sync native CI metadata
2026-07-10 16:48:11 +01:00
Peter Steinberger
5533d979d4
feat: add follow-up task suggestions (#102422)
* feat(tools): add follow-up task suggestions

* chore: leave changelog to release flow

* fix(tools): add task suggestion display metadata

* fix(tools): update task suggestion display snapshot

* docs: format task suggestion tool table

* test(gateway): expect compaction worktree workspace

* test(gateway): preserve configured compaction workspace

* fix(ui): translate task suggestions
2026-07-09 06:30:01 +01:00
Peter Steinberger
f623d81f52
feat(ui): show background tasks live in the web Control UI (#100789)
* feat(gateway): broadcast task ledger changes as task events

* feat(ui): add background Tasks page to Control UI

* fix(gateway): address tasks page review findings

* fix(ui): surface tasks.cancel refusals on the Tasks page

* fix(gateway): guard stale task observer disposal against replacement gateways

* chore: satisfy lint and docs-map gates for tasks page

* test(gateway): await async agent unsub in stale-dispose test
2026-07-06 14:36:33 +01:00
Peter Steinberger
e62cf76bd5
fix(ci): catch native-only mobile protocol drift (#100278)
* fix(ci): cover native-only protocol event drift

* fix(ci): ignore quoted Swift event constants
2026-07-05 09:23:45 -07:00
Peter Steinberger
5af24d16bf
chore(ci): fail CI when gateway events go unhandled by the mobile apps (#100206)
* ci: guard gateway protocol event coverage for iOS/Android clients (#100198)

Adds scripts/check-protocol-event-coverage.mjs, which derives the
server->client event catalog from GATEWAY_EVENTS in
src/gateway/server-methods-list.ts, extracts the events each mobile app
handles from Swift/Kotlin dispatch sites, and fails on gateway events no
client handles unless allowlisted with a reason in
scripts/protocol-event-coverage.allowlist.json. Wired as
pnpm check:protocol-coverage in the CI guards shard.

* fix(ci): scope Kotlin event extraction to handle*Event dispatch functions (#100198)

Bare event == "..." literals in predicate helpers outside the dispatch
path (gatewayEventInvalidatesNodesDevices in NodeRuntime.kt, which has no
production caller) counted as Android coverage, silently masking that
node.pair.requested/resolved have no live handler. Kotlin extraction now
only reads when(event) labels and event comparisons inside fun
handle*Event(...) bodies; node.pair.* moved to the Android allowlist with
a truthful reason. Swift extraction stays tree-wide because consumption
there always reads .event off a received EventFrame.

* fix(android): remove dead node pairing event helper

* fix(ci): preserve protocol allowlist parse errors

* test(ci): align tooling import plan
2026-07-05 03:06:52 -07:00