* fix(gateway): bound audit and Codex backlogs
Live Gateway SQLite lock failures and process heap pressure exposed two
independent queue owners. Route best-effort audit persistence through the
canonical shared-state connection with bounded contention retries, and remove
the per-notification Codex yield so the keyed turn queue can drain directly.
Follow-up to #126033 and #126073.
* fix(gateway): annotate raw SQLite cold-open probe
* test(codex): register notification burst shard
* fix(copilot): add OpenClaw prompt guidance
Copilot append-mode system messages included credential safety, workspace bootstrap, and extra context but omitted OpenClaw delegation and reply-delivery policy.
Build guidance from the final policy-filtered tool surface so visible delegated work, Skill Workshop, and source replies follow the same behavior as Codex.
* fix(copilot): break prompt guidance import cycle
The CI architecture gate detected a cycle through attempt-config and prompt-guidance. Isolate raw-run mode detection in a leaf module.
* feat(ui): collapse automation sessions in activity feed
* feat(ui): show live run status on activity feed rows
* feat(ui): make run inspector deep-link only
* fix(ui): only link inspect run to a current active run
Addresses ClawSweeper P1: a stale observer digest could deep-link a live
row to an ended run's audit record; trust digest runId only when it is
still in activeRunIds.
* test(ui): seed mock activity sessions across owners
* feat(ui): consolidate activity filters into toolbar
Also restores the shared .input styling contract for standalone form controls.
* test(ui): update activity feed e2e for toolbar people popover
* test(control-ui): add --operator-scopes flag to the mock dev server
* feat(control-ui): simplified settings experience for non-admin operators
Non-admin browsers previously saw every settings page, many of which
dead-ended or rendered enabled controls whose RPCs fail with
'missing scope: operator.admin'.
- config.schema drops from operator.admin to operator.read: the schema is a
static document describing options whose values are already readable via
read-scoped config.get; admin-only schema only broke read-only settings
rendering (Automation/Infrastructure/AI Agents/Communications showed
'Schema unavailable. Use Raw.').
- Settings sidebar and settings search hide admin-only routes (custodian,
labs, updates, automation, infrastructure, mcp, security, secrets,
cloud-workers, communications, ai-agents, model-setup) for non-admin
viewers; legacy gateways without advertised scopes keep the full UI.
- Channels, Devices, Worktrees, Memory Import, Profile gate their mutation
controls on actual scopes with 'Browsing only…' notices instead of
enabled-but-failing buttons; Devices no longer fires device.pair.list /
exec.approvals.get without the scopes to call them (kills the two red
error callouts on page load).
- Scope-upgrade banner: dismissing it in the guidance phase (no in-app
upgrade path) now hides it fully instead of leaving a permanent chip.
- Config write coordinator surfaces scope refusals as a visible
admin-required error instead of silently resolving false.
* test(control-ui): advertise config.schema in the mock dev gateway
ensureSchemaLoaded now checks method advertisement + scope before loading
the schema; the mock harness must advertise config.schema like a real
gateway does or schema-driven settings pages render empty in the mock.
* fix(control-ui): close the worktree create draft on scope downgrade
* perf(doctor): isolate memory health artifact
Doctor lint loaded the broad Memory Core API barrel only to register health checks and read isolated check IDs. That synchronously pulled the full memory public graph into the first lint run, consuming most of the 120-second test budget.
Load a dedicated doctor-health public artifact instead and verify it is packaged. The bisect boundary was 9de3ca5fc9 (#125571); because that commit only adds upgrade-test assets, it exposed a pre-existing runner-sensitive cost rather than introducing the expensive import path.
* test(control-ui): restore device lifecycle test boundary
* perf(control-ui): lazy-load settings sidebar
* fix(ui): recheck access after confirmations
* fix(control-ui): gate presence-driven device reloads on pairing access
The presence connectivity-change path still called device.pair.list without
operator.pairing, the same invariant the pair-event and poller paths already
guard; a limited browser got a doomed RPC on every connectivity change.
* fix(control-ui): fail open on schema loads for legacy scope-less gateways
canCallGatewayMethod hardened to strict advertisement+scope checks (#125478),
which made the new ensureSchemaLoaded gate silently skip config.schema for
legacy hellos without advertised scopes or a method list. Schema loads now
skip only on a definitive denial (method advertised absent, or advertised
scopes without operator.read), reusing the fail-open hasOperatorReadAccess
semantics the rest of the non-admin UI uses; regression test pins the
legacy snapshot path.
* test(control-ui): split schema-access coverage into its own file
runtime-config-capability.test.ts crossed the max-lines cap; the legacy
fail-open regression and its denial counterpart move to a colocated
schema-access test file.
* fix(scripts): keep mapped Vitest lanes at their measured no-output floor
The codex extension shard legitimately works in silence beyond 300s under
the default reporter (measured 61s import + 293s testing at ~95% CPU); the
CI-wide OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=300000 env override shrank the
lane below that and the watchdog killed healthy runs, flipping with
incidental flake output (#125825). Per-config entries in
VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS now act as measured silence floors: a
global env value may widen a mapped lane's window but no longer shrinks it;
unmapped configs and the explicit '0' disable keep env verbatim. Adds the
codex extension lane to the map at the extra-long tier (same class as the
discord entry from #123025).
The `## Delegation` guidance added in #125691 lived only in
buildAgentSystemPrompt, so Codex-runtime agents never received it: the
Codex harness builds its own developer instructions in
extensions/codex/src/app-server/thread-prompt.ts and imports nothing
from the system-prompt builders. Live A/B on gpt-5.6-luna had the native
runtime answer "spawn a visible session" while the Codex runtime
answered "spawn a hidden subagent".
Move the policy into src/agents/delegation-guidance.ts, owning both the
main-session mode resolver and the section text, and export it through
the agent-harness plugin SDK barrel that the Codex harness already uses.
The hidden-delegation vocabulary is injected by each runtime, so core
never names a plugin-owned tool: native passes `sessions_spawn`, Codex
passes native `spawn_agent`. Visible sessions stay `sessions_spawn`
with visible=true on both runtimes because Codex-native children are
never OpenClaw sessions.
Also narrows the Codex line that told the model to use `sessions_spawn`
only for OpenClaw/ACP delegation; it now scopes that to internal
legwork, so user-facing deliverables still route to a visible session.
* fix(ci): stop codex lane cold-graph hangs
The side-question domain-policy test loaded the complete agent-harness tool graph inside a one-second readiness race, making the serial non-isolated Codex shard fail or stay silent under cold imports. Build the test's web_search marker and real web_fetch tool from the narrow implementation, then synchronize on turn startup before issuing the tool call. Cap each Codex test process at 12 files so CI gets bounded time-to-first-output as defense in depth.\n\nRefs #125839
* fix(test): keep codex web fetch fixture on sdk boundary
Load the real web_fetch factory on demand through the existing local-only plugin test runtime. This preserves the narrow cold-graph fix without letting a bundled plugin test reach into core internals.
* fix(agents): finalize guided creation safely
Run channel post-write hooks only after config publication, defer portable auth copying until agent creation succeeds without overwriting newer credentials, and provision existing workspaces before publishing updates.
Keep JSON-only guided creation interactive while routing wizard output to stderr so stdout remains one machine-readable summary.
* fix(terminal): preserve note call signature
* fix(agents): pass committed config to setup hooks
* ci: split heavy codex changed-test shards
Cap non-isolated Codex extension processes at 20 files so 4-vCPU changed-target jobs do not starve real-time watches or hit the no-output watchdog.
* test(ci): align codex shard cap fixture
* docs(cli): clarify agents add JSON mode
* feat(ui): promote session controls to toolbar
* test(ui): add session toolbar proof capture script
* chore: register session toolbar proof script
* test(ui): target ungrouped session drop zone
* fix(ui): trim session toolbar startup cost
* docs(ui): point new-session guide at the Sessions toolbar
Also name the accepted tradeoff for the inert ungrouped-collapse preference at the partition site (ClawSweeper P1/P3 response).
* test(ui): deflake new-session workspace-memory e2e
Menu-geometry assertions now measure relative to the picker anchor in one atomic evaluate (immune to unrelated page settles, still catches focus-induced moves). The post-reload refill raced the composer's async draft restore, which appended the stored draft to the typed text; waiting for the restored draft asserts the documented persistence instead. Failed CI shard checks-ui-e2e 5/12 on runs 32122284238 attempts 1-2.
* fix(ui): use canonical session owner filter state
* chore(ui): keep session toolbar proof artifacts ignored
* feat: credit linked session participants as co-authors
Authenticated profiles can link GitHub and receive automatic co-author credit in shared coding sessions.
* style: format rebased co-author registries
* fix: mark profile schema DDL boundary
* fix(ui): align session owner filtering terminology
Owner filters now use owner terminology consistently across the Gateway, Control UI, and generated native protocol clients. The old terms were prerelease-only, so no migration or compatibility aliases are needed.
* fix(gateway): preserve creator provenance filtering
Keep creatorId scoped to immutable createdActor provenance while ownerId queries the current projected assignable owner. This preserves independent creator and responsibility filtering after reassignment.
* test(ui): wait for remembered draft before reload
CI exposed an IndexedDB draft-persistence race: the test reloaded before the write completed and then re-filled the message while restoration arrived. Wait on the canonical durable-draft helper before reload so the reload cannot race the write.
* fix(ui): keep session menus clear of hovercards
Horizontal sidebar previews were positioned from the inner link rect instead of the complete session row, allowing the hovercard to overlap trailing actions.
Anchor preview placement to the full row while preserving the link trigger and ownership flow so the session menu remains clickable.
* perf(ui): keep hovercard fix within startup budget
Use the sidebar direct-row invariant to anchor the hovercard outside trailing actions without a selector lookup. This preserves the fix without increasing the startup budget.
* fix(qa): preserve Matrix CLI timeout precedence
Keep the primary timeout operator-visible when descendant settlement also reports secondary cleanup trouble. Preserve the complete AggregateError as the public wrapper cause so settlement evidence remains inspectable.
* test(ui): isolate Markdown table clipboard boundary
With isolate:false, the shared module registry can retain clipboard imports before this test installs its module mock, leaving the spy disconnected from the production call.
Stub navigator.clipboard.writeText instead so the test exercises the real copyToClipboard boundary regardless of module load order, then restore the original descriptor.
* feat(gateway): expose command lane diagnostics
* feat(ui): add live debug busyness overlay
* fix(ui): show newest events in debug overlay and update diagnostics call-list tests
* test(ui): add lane and status fixtures to the mocked dashboard
* feat(ui): add System busyness entry to the account menu
* fix(gateway/ui): bound lane diagnostics, append-only descriptor, fail-visible lanes load
Addresses ClawSweeper review findings on #125591: diagnostics.lanes moves to
the append-only tail of the descriptor table, the Control UI lanes request
fails visibly instead of masking errors, and the RPC exports only static
lane snapshots plus a bounded dynamic-session aggregate composed in the new
command-lane-diagnostics module.
* chore(protocol): regenerate Kotlin gateway methods for diagnostics.lanes
* test(gateway/ui): register diagnostics.lanes in the 2026.8 train and mock it in the debug e2e
* fix(ui): stop the model picker from nagging about refresh failures
A failed background catalog refresh rendered a "Couldn't refresh models"
banner plus a Retry button on top of a complete, working model list, and
replaced the composer trigger's model name with that error text. The
picker already keeps the last-known catalog and re-requests it on every
open, so the operator was being asked to press a button for something the
UI does by itself.
The error stays recorded on the host — it is what drops the stale
availability gate so the composer remains usable — but it is no longer
surfaced while there are models to show. Only a genuinely empty catalog
still says "Models unavailable". The new-session picker gains the chat
picker's open-triggers-revalidate behavior so re-opening it is the retry
there too, instead of dead-ending until a page reload.
Two adjacent simplifications in the same surface:
- Model rows reserve their provider-icon slot as an invisible stem, so
names line up with the provider heading label (same 34px stem grouped
and filtered, nothing shifts while typing).
- The "Using agent default" footer is gone: the default row already
carries a DEFAULT badge and the checkmark. Typing "default" in the
picker search now matches the default model instead.
Proof: scripts/capture-model-picker-proof.mts captures the open picker
against a mocked gateway; alignment delta 0px (was -24px), search
"default" matches the default row (was nothing), and a failed models.list
leaves no catalog-state element with all rows intact.
* fix(ci): register the model-picker proof script and await its picker revalidation
The unused-file scan needs every scripts/ entry point referenced, like the
sibling ui:proof:* recipes. The catalog-reconnect assertion also has to wait
for the picker's own metadata request instead of reading the log the moment
the rows render.
* test(ui): split model-catalog scenarios out of the new-session e2e file
The catalog-reconnect file hit the 1000-line cap. Its model-catalog metadata
failure/recovery pair is a separate surface from CLI-agent targets, terminal
start, and draft reconnect, so it moves to its own file rather than earning a
max-lines suppression.
* test(audit): give the pinned-reader contract test a realistic timeout
It adds a pinned-SHA git worktree and cold-compiles the audit and state
modules under tsx, which takes minutes on a contended runner. The 120s
default made it fail by construction; it timed out on an unrelated PR shard
while passing locally at ~55s.
* fix(setup): refresh Codex registry with staged install
* fix(macos): verify inference before onboarding handoff
* fix(setup): use native Codex home for subscription auth
* fix(codex): honor attempt-scoped setup config
* fix(macos): align onboarding handoff with reopen
* fix(setup): await prepared model convergence
* fix(ui): avoid false auth state for empty catalog
* fix(setup): scope catalog convergence to Codex gateway
* fix(setup): publish the committed runtime catalog
* fix(models): project configured static runtime models
* fix(codex): expose app-server model catalog
* fix(models): preserve Codex auth across reloads
* fix(ci): align Codex onboarding checks
* test(ui): stabilize dock suppression environment
* fix(codex): honor discovery config in app-server model catalog
The manifest documents discovery.enabled (bundled fallback list) and
discovery.timeoutMs (default 2500ms) for model discovery; the new catalog
path used the generic 60s request timeout and ignored the enable gate.
Also drop the test-only listModels injection seam in favor of vi.mock.
* fix(setup): refuse prepared Codex auth over an explicit remote transport
configureCodexCliPreparedAuth silently rewrote an explicitly configured
websocket/unix app-server to local stdio (keeping a dangling url), moving
the credential boundary onto this host. Fail setup with actionable
guidance instead; also surface the root cause when the prepared model
catalog refresh fails after activation.
* refactor(agents): one canonical model-catalog identity key
Three near-identical key helpers existed (models-list-result,
models-list-configured-static, harness/model-catalog). Export
resolveModelCatalogIdentityKey from the route-policy owner, collapse the
duplicate dedupe loops into dedupeByKey, make donor enrichment Map-based,
and inline the one-off harness-augment wrapper.
* fix(macos): restore custodian handoff for fresh activations
Landing every finish on the plain dashboard stranded the custodian
first-run flow (memory import, channels, permissions, hatch). Fresh
activations now hand off to custodian onboarding; live-verified
pre-existing setups reopen the normal dashboard, matching the removed
already-configured shortcut. Tests pin the destination per path.
Also isolate the post-startup Codex login test from developer machines:
ambient OPENAI_API_KEY and a real Codex login made it assert-fail.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Resolve retained dependency closures from each physical importer so Docker pruning preserves concurrent nested package versions required at runtime.
Add importer-bound regressions and a post-prune grammY import gate.
Co-authored-by: AmAzing129 <115673583+AmAzing129@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(ui): stop composer mic hover shift and stray red hover
The device-picker chevron collapsed to width 0 while idle and grew to
20px on hover, shoving the right-aligned mic button 20px left under
the cursor on every mouse-over. Detach the picker into its own
fixed-width ghost button beside the mic instead of morphing the mic's
shape into a split pill; hover only fades the picker's opacity, so the
reserved layout never shifts.
Also fixes the mic showing the send button's loud accent-red hover in
dark/openknot themes: those themes overrode .chat-send-btn:hover
directly, which outranked every variant's own hover rule (including
the intentional neutral mic hover and the stop button's danger hover).
Theme overrides now set a --chat-send-hover-background variable that
each variant's hover rule can still take precedence over.
* chore(scripts): register composer mic hover proof script
Missing package.json entry made the deadcode full-tree scan flag the
new capture script as an unused file, same as the existing
ui:proof:workboard sibling.
The lock auto-releases only while the pre-side-effect validation marker is active; review_init marked side effects before the read-only metadata fetch, so a transient GitHub failure retained the lock and forced a lock-recover loop. The read-only fetch now runs before the marker.
gh exit status was treated as proof that stdout held a PR object. The Octopool cache shim reports upstream 5xx responses as exit 0 with empty stdout, so reads now use a bounded-retry validation helper. Convert 9 of 28 gh pr view sites where an empty payload could produce a wrong decision.
* fix(voice-call): survive gateway in-process restart and stop CLI dead-ends
The gateway's in-process restart (SIGUSR1 config reload) reuses the cached
plugin registry, so service stop/start run on the same retained voice-call
registration. Generation fencing from #120289 treated that restart as a stale
actor: stop retired the generation forever, the next start silently bailed,
and every voicecall.* RPC answered UNAVAILABLE "runtime generation is
retired" while the webhook never rebound.
- Registrations now hold a replaceable generation: service start after stop
mints a fresh generation, takes over a running slot owned by a retired
predecessor, and reports start failures to service health instead of
silently returning.
- The voicecall CLI classifies gateway failures with typed guards instead of
message substrings: standalone/store fallback only when the gateway is
genuinely absent; reachable-but-failed (request errors, auth, timeout)
exits with actionable text; a standalone webhook port collision explains
that a running Gateway probably owns the port instead of raw EADDRINUSE.
- Plugin SDK gateway-runtime exports structural isGatewayTransportError /
isGatewayClientRequestError guards (+2 documented surface budget).
- Regression coverage: same-registration stop/start restart, retired-owner
takeover, typed CLI fallback classification, and a real token-auth gateway
server routing voicecall.status through callGatewayFromCli.
* refactor(voice-call): split CLI modules and dedupe gateway fallbacks
Collapse the four duplicated gateway-or-runtime command blocks (speak, dtmf,
end, continue fallback) into one generic runGatewayManagerCommand helper —
the continue command owns its legacy-method fallback and operation polling
via a gatewayCall closure, so the helper carries no per-command policy.
Smoke reuses the shared initiateVoiceCall path instead of a bespoke
fallback.
Split the 988-line cli.ts into concept modules (cli-gateway-call,
cli-call-log, cli-command-io) and drop its grandfathered max-lines
suppression plus the now-stale max-lines and assertion-safety baseline
entries (shrink-only ratchet maintenance).
Behavior-frozen: stdout/exit semantics unchanged; net -2 production LOC.
* fix(voice-call): redact gateway URLs in CLI operational errors
ClawSweeper P1: the operational-error formatter interpolated the raw
connectionDetails.url, so a configured gateway URL with userinfo or query
tokens would print credentials into terminal output. Redact the composed
message once with the canonical net-policy redactor (also covers
remote-controlled close-reason text), exported through the plugin SDK
gateway-runtime subpath (+1 documented surface budget). Regression test
covers a credential-bearing URL in both the URL and message fields.
* 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.
* 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.
* feat(dashboard): add session:progress board tile rendering the live progress card
Advertise the core-owned widget kind via hello controlUiWidgetKinds at operator.read.
Render it inline without an iframe from the session-progress-cards store.
Pin it with dashboard tool widget_put using pluginKind session:progress and optional props.sessionKey.
Follow up the progress-card unification from #125125.
* fix(dashboard): surface session progress load failures
Record protected progress-card read failures in the shared per-session store.
Render an actionable board-tile error with retry instead of indefinite loading.
Cover the rejected-read and successful-retry flow at the widget boundary.
* fix(dashboard): honor progress tile access and activity
Avoid progress-card reads while a retained board is inactive.
Distinguish sharing denial from transient load failures and show the correct remedy.
Qualify cross-session pinning docs and cover activation plus denial behavior.