* refactor(gateway): interrupt at admission; sessions.steer delegates to chat.send
chat.send queueMode interrupt now aborts the captured current operation
at the session-work admission owner (exact-instance abort, never a
same-key successor) and dispatches the new turn after the abort
settles; channel-originated interrupts share the same primitive.
sessions.steer keeps its validation, sharing/access checks, and
response shape but becomes a thin deprecated adapter over that path -
its private inflight/retry and hard-abort machine is deleted. The
Control UI /redirect command rides canonical chat.send. Compaction's
use of the old hard-interrupt helper moves unchanged to
session-run-interruption.ts.
* fix(gateway): interrupt non-reply admissions
Resolve and persist the exact same-origin repository that owns a live managed checkout before snapshot refs, branch deletion, or worktree removal. Fail closed on different-origin substitution. Closes#126304
* feat(ui): unify focused presentation routes
/focus/<target> replaces unshipped standalone query links across dashboard, terminal, desktop, and native apps.
Gateway-served index assets are anchored so nested documents resolve their bundles from the Control UI base path.
* test(gateway): narrow emitted asset URLs
Fixes check:test-types TS18048/TS2322 by dropping unmatched optional captures before comparing emitted asset URLs.
* test(docs): follow centralized cloud secret guidance
Fixes the stale current-main docs test after #126132 centralized GCP and Hetzner setup in docker-vm-runtime.
* test(ui): retry missing locator reads
The 500ms locator text read can time out while the menu label is still rendering, causing expect.poll to reject instead of using its owning 10s retry window. Treat only Playwright TimeoutError as a missing value so the outer poll retries while page-closure and arbitrary failures still surface.
* test(android): capture TLS probe coroutine
The TLS probe test inferred its coroutine from mutable scope children, racing unrelated child startup and teardown in CI. Capture the exact Job from inside the probe coroutine and join that owner before asserting the stale-attempt guard.
* fix(gateway): preserve plugin focus routes
Keep approval handling ahead of plugin dispatch, but treat focus documents as an unclaimed Control UI fallback after plugin authentication and routing. Exact and prefix plugin routes therefore retain ownership, while unclaimed reads serve the focus document and other methods return 404.
* fix(ui): migrate released terminal links
Preserve stable v2026.7.1 terminal query compatibility by rewriting the root/base ?view=terminal URL once to the canonical /focus/terminal path with history.replace. Keep URL parsing path-only, and leave the removed desktop and dashboard query forms as a hard cut.
* test(codex): assign run-attempt tools shard
Cached filtered configs caused duplicate ownership, and the test lacked a canonical full-suite owner.
* test(ui): keep cloud recovery proof state-owned
The recovery test should assert owner state and reload identity, while dedicated tests own transient alert visibility.
* test(qa): wait for outbound bus state
* fix(qa): reserve gateway ports through staging
* refactor(qa): keep socket creation in gateway owner
* fix(update): validate target config before handoff
Run the existing dev candidate preflight from both managed update entry points while the serving Gateway remains alive. Reject target-incompatible config before starting a handoff or scheduling restart. Closes#126269
* fix(protocol): restore gateway barrel line budget
Route SessionsPatchResult through the existing type-only schema barrel so the public export stays intact without exceeding the core max-lines gate.
* fix(ci): restore required PR gates
Keep schema-types as the registry-free wrapper, drop a redundant project schema re-export to restore the line budget, claim run-attempt-tools in one canonical shard, and align campaign tests with the new preflight boundary.
* fix(gateway): make activeRunIds presence mean a complete exact run set
Session rows no longer emit activeRunIds: [] while hasActiveRun is
true. Presence now means the complete exact set of direct run ids;
omission means identities are unavailable (projected/embedded owners);
[] only ever represents proven idle. Consumers stop guessing:
soleActiveSessionRunId() replaces the arbitrary [0] fallbacks in the
observer digest, transcript cache key, activity inspector, and
stale-terminal reconciliation, each falling back to its owner fact.
Follows the maintainer direction from #125983: the field stays as
Gateway-owned exact facts; producer-side liveness/observer projections
are a named follow-up.
* fix(gateway): clear unavailable active run ids in events
* fix(gateway): preserve idle active run sets
* fix(clients): close active run id cache gaps
* test(android): isolate history run snapshot
Use the durable run-session identity for nested session lookups on multi-agent hosts while retaining existing visibility and agent-to-agent authorization.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix: capture GitHub identity from authenticated sign-in
Automatically persist verified GitHub identities from Cloudflare Access and Tailscale Serve while keeping public Git co-author credit as a separate opt-in.
* test: stabilize cleanup and activity capture
* fix(security): bind GitHub profiles by account id
* test: scope activity capture to route
* fix(security): gate profile requests on identity sync
* fix(security): close pending profile authorization gaps
* test(ui): stabilize terminal continuation menu
* test: stabilize startup recovery timing
* test: keep one Codex attempt tools owner
* fix(plugins): allow profile-independent gateway reads
* fix(qa): mark partial suite artifacts as running
Isolated QA suite progress artifacts now identify themselves as running in JSON and Markdown, so completed-prefix results cannot be mistaken for terminal teardown. Final artifacts keep their existing completed shape, with process regression coverage for clean exit and closed Gateway listeners.
* test(qa): decouple suite runtime from teardown deadline
Allow the real QA scenario to finish under a contended extension shard while keeping the post-summary process exit requirement fixed at 45 seconds.
* test(qa): keep lifecycle proof observable
Emit a bounded progress heartbeat while the real QA child is still producing its terminal summary so the extension shard watchdog does not mistake a long, active process proof for a stalled Vitest run.
* test(qa): isolate lifecycle process environment
Run the real QA child outside Vitest and shared compile-cache markers, and fail fast with bounded process output when it exits before publishing a terminal summary.
* test(qa): run lifecycle proof on repo gateway
Build and launch the real repository Gateway when dist is absent, avoid package-candidate auth bootstrap, and keep the teardown regression bounded and observable in unbuilt extension-test jobs.
* test(qa): terminate Windows lifecycle process trees
* fix(qa): reject running confidence summaries
Keep the writer-owned idempotency key stable across transcript redaction so final Codex snapshots replay against the admitted SQLite row instead of dropping mirrored history.\n\nCloses #126244
Ambient heartbeat ownership now follows the cron owner chain: heartbeat.agentId, legacy default, systemAgent.agentId, then sole agent.
Ownerless multi-agent rosters disable heartbeats with gateway and config-validate warnings instead of throwing AgentSelectionRequiredError during gateway startup.
* fix: stop Full access sessions from requesting exec approval
* fix: propagate Full access policy to compaction
* fix: source compaction permissions from session state
Three PRs fixed the same orphaned test within minutes of each other -- #126222,
#126225 and #126190 -- so the full-suite ownership audit flipped from "missing" to
"duplicated" and main went red at 554dfbe0a2.
Keeps the claim from #126225, whose entire purpose was routing this test, and drops
the two incidental ones added while landing unrelated UI work. The audit passes 18/18
and the test still runs, once, in the attempt-light shard.
Root cause is the shard layout: the support shard globs app-server/**/*.test.ts but
excludes the whole run-attempt* pattern while the attempt shards enumerate by
filename, so every new run-attempt-* file is unowned until someone hand-adds it, and
concurrent fixers then collide.
* feat(skills): add custodian-only skill library
* docs: document custodian skill library
* refactor(skills): make custodian skills concrete and non-interactive
Replace docs-link-first playbooks with verified openclaw config/message/infer
one-liners; drop interactive onboard references; encode the in-session
config-write policy boundary (models.*/secrets.* via trusted shell).
* fix(skills): corrections from live A/B testing of custodian skills
--agent required for models list/auth list in multi-agent rosters; drop
hanging channels capabilities probe; telegram target is chatId; roster-safe
prove via agent turn (infer model run has no --agent and dead-ends
multi-agent setups); note expected not-found on pre-setup config get.
* fix(skills): front-load harness plugin check in add-model-provider Gather
A/B timing showed the codex plugin dependency surfacing mid-Prove, at the
most expensive point (approval gate + turn boundary). Checking and
remediating during Gather removes the stall.
* fix(skills): keep status inventory unfiltered while scoping custodian source
buildWorkspaceSkillStatus forwarding agentId activated the loader's agent
allowlist filter, dropping excluded skills from the workshop's status view
(collection-review regression on CI). New closed agentSkillFilter mode lets
agentId scope custodian-source discovery without filtering the entry list,
per the documented status invariant.
* fix(agents): clear sub-agent failure warning after successful spawn retry
A failed sessions_spawn followed by a successful retry in the same run
kept appending the durable "Sub-agent failed" warning to channel
replies. The recovery seam (lastToolRecovery) already existed, but
sessions_spawn has no stable-target arg key, so its recovery
fingerprint fell back to display meta built from label/task/model.
Retries adjust those args (drop a rejected cwd, reword the task), the
fingerprints never matched, and recordSuccess could not clear the
failure.
Collapse the sessions_spawn recovery identity to tool level: any later
successful spawn in the same run is recovery proof. A lone failed spawn
still warns, and a failure after recovery still invalidates the
receipt.
Observed 2026-08-19 on team.openclaw.ai: Roboclaw's first nested spawn
returned forbidden (visible-session cwd outside workspace), the retry
without cwd succeeded, the reply said "Started Investigate…", and
Discord still showed "⚠️🧑🔧 Sub-agent failed".
* ci: register run-attempt-tools test in the attempt-light lane
#126189 added extensions/codex/src/app-server/run-attempt-tools.test.ts
without assigning it to a full-suite lane, so the Vitest ownership audit
(test/vitest-projects-config.test.ts) fails on main and every PR head.
Register it in the codex app-server attempt-light shard next to its
run-attempt siblings.
* test(gateway): drop the chat image-capability mask
The vi.mock of resolveGatewayModelSupportsImages in
server.chat.gateway-server-chat.test.ts was added by #123847 as an
isolation workaround while the gateway shard's chat failures were
unexplained. The producers are fixed on main (#126183 for the
root-work drain race; #126062/#125946/#126113 for detached completion
binding), and the exact #126030 shard invocation is green with the
mask removed (5/5 on 95ac5b27fa, 2/2 on this head), so the mask now
only hides the real capability-resolution path from the suite.
* test(gateway): assert inline-image dispatch after mask removal
ClawSweeper P2 on #126217: the two image sends only asserted RPC success
and drain, so a silent false from the real capability resolver would
offload the images and the suite would stay green without proving the
discovered model's image capability. Capture the dispatch args and
assert both runs deliver exactly one inline image. Teeth check: with the
discovered model narrowed to text-only input, the assertion fails with
images undefined (offloaded).
* feat(discord): auto-join occupied voice rooms
Add opt-in voice.autoJoin[].whenOccupied residency so Discord voice bots join for the first human and leave when the room becomes empty while preserving existing always-on, manual, transcript, and follow-user behavior.\n\nCloses #125973
* test(discord): isolate process runtime mocks
Use stable hoisted runtime-env mocks so isolate=false Discord test ordering cannot turn sleepWithAbort back into an unmocked function.
* fix(discord): defer unknown voice occupancy
Treat memberless voice states as unresolved instead of human so bot-only rooms cannot trigger occupancy-managed auto-join. Add cache-to-listener and manager regressions.\n\nCloses #125973
* test: isolate shared module mocks
Replace ineffective non-isolated module spies with stable hoisted mocks and a child-process SQLite connection-reuse probe so gateway and Discord shards are order-independent.
* test(gateway): inline connection reuse probe
Keep the child-process SQLite ownership probe in its owning Vitest file so Knip sees the full test surface without weakening process isolation or the original order regression.
Pinned sessions inherited the nav zone's muted colour on the session *title*, so a
pinned row's preview line outshone its own name and the same session read dimmer
pinned than unpinned. A title is content in every zone; only glyphs, meta and the
hover fill follow the zone. The subtitle drops to plain --muted, already proven AA
against every surface by theme-contrast.test.ts.
Rows with no preview text reserved a fixed 18px second line anyway, leaving a dead
band or a lone spinner hanging below-right of the title. They now collapse to one
line via a --single-line class plus a CSS variant, so the endcap rides beside the
title. Rows also gained a little vertical air and reclaimed the right-hand gutter
inside the row, leaving the deliberate scrollbar clearance from #124879 intact.
Adds a localStorage-backed "Show message preview" toggle to the session sort menu
(default on). Operator-actionable state is exempt: attention, the queued
concurrency-slot explanation, and critical observer headlines (stuck /
waiting-on-user) always show, because hiding them behind a display preference is
the silent-failure class.
Also registers extensions/codex/src/app-server/run-attempt-tools.test.ts, which
arrived in #126189 with no shard claim and left main red.