* fix(tasks): rank terminal tasks by completion and keep Recent terminal-only
- updateTaskStateByRunId backfills lastEventAt from endedAt for terminal
finalizers (mirrors markTaskTerminalById), keeping activity monotonic
- both taskUpdatedAt projections rank terminal tasks by the maximum
available activity timestamp, healing stale rows while preserving later
delivery/terminal-outcome events recorded after completion
- Tasks page Recent fetch filters to terminal statuses so queued/running
rows cannot starve the Recent section
Related to #100911
* refactor(tasks): normalize completion at registry owner
Absorb terminal timestamp ordering into the canonical registry lifecycle boundary, remove duplicated projection and writer policy, and prove Recent remains visible behind 200 active tasks in Chromium.
Co-authored-by: SunnyShu0925 <shu.zongyu@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(ui): give embedded settings sections the shared section rhythm
The agents tab panel hosted settings sections in a bare div, so sections
stacked with zero separation; the bespoke .agents-main margin rule in
agents.css missed the nested tabpanel entirely. Descriptions also pulled
up to 4px under control-height header actions (squeezed Verify/Save rows).
Add a .settings-stack primitive to settings.css for embedded surfaces,
use it on the agent tab panel, delete the page-local margin fork, and let
section descriptions clear action-bearing headers.
* test(agents): export getRuntimeConfigSourceSnapshot from runtime-snapshot mock
Main's checks-node-compact-large shard is red: #126531 routed
provider-model-routes through projectConfigOntoRuntimeSourceSnapshot,
which reads getRuntimeConfigSourceSnapshot, and this suite's explicit
vi.mock factory did not export it (24 failures). Return null so the
projection no-ops and resolvers keep reading the provided config.
* refactor(ui): unify sidebar alerts with custodian
* refactor(ui): move custodian alert state
* fix(ui): use the defined mono token in the custodian alert card
* fix(ui): break the update-watcher import cycle
Type the watcher against a structural leaf contract instead of
Pick<ApplicationContext,...>; context.ts reaches this module through
overlays-types.ts, so naming the context type closed a madge cycle.
* fix(ui): re-arm the alert explanation when an incident recurs
Failed-automation and model-auth alerts keep one incident id across
recover-then-fail-again, so an id-keyed dedupe showed the renewed alert
and never explained it. Scope ask-once to the presentation instead.
* refactor(ui): share Control UI resource route contract
Gateway and Control UI now share browser-safe resource route grammar, while authorization and domain validation remain handler-owned.
* perf(ui): keep resource routes out of startup
Narrow browser-safe contract modules and the lazy authenticated avatar loader keep route/cache code out of the startup chunk while the stable Gateway barrel remains compatible.
* fix(ui): render one assistant reply when history and the terminal event race
A finished run's reply reached the Control UI twice: the durable transcript
row and the pane's own terminal projection shared no identity, so the
projection reducer kept both and the transcript collapsed them behind the
"x2" duplicate badge. The gateway's chat final event carries no message id,
and a durable assistant row carries no run id, so the reducer's same-run
promotion could never match them. A WeakMap side channel hid the duplicate
only when history happened to be applied before the terminal event arrived,
which made the badge look intermittent.
The pane now admits the durable assistant row of the run it is finishing
through the reducer, attributed to that run, so the durable row adopts the
terminal projection in place; the reducer refuses to downgrade a row that
already carries a transcript id back to an id-less projection.
Splits the run-terminal bookkeeping tests into a sibling file to stay under
the max-lines ratchet.
* fix(ui): bind a late reply row to its run only when it carries that reply
The terminal tombstone outlives its run by design, so attributing every
unowned assistant row to it could stamp a delayed older row with a newer
run's id; the reducer then matched that row to the newer run's terminal
projection and replaced the answer the user should see. The tombstone now
claims only the row carrying the reply already projected for that run.
* 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
* 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: 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
Scope embedded Desktop refresh and connection ownership to the active presented sidebar slot. Hidden retained panels now invalidate pending work and disconnect RFB, while reactivation returns to a freshly loaded picker without silently reconnecting.
* fix(ui): preserve workshop revisions until admission
Skill Workshop revision instructions now remain retryable until the Gateway admits the request, including navigation and overlapping admission failures.
* fix(ui): materialize workshop revision binding
Manifest-only Skill Workshop proposals now resolve their revision hash inside the durable admission owner, and retries reuse the same binding and idempotency key.
* chore(ui): keep revision types internal