* fix(remote): unthrottle host renderer while serving a paired client
A paired desktop host left in the background could not open or close
agent sessions for its remote/relay client: the action stalled and
eventually failed with the host-side "Timed out waiting for terminal
surface after creation" (10s) error, while an already-live terminal's
keystrokes stayed fast.
Root cause: creating/closing a session routes through the host
renderer's setTimeout-coalesced graph sync to publish the terminal
surface, but the host window runs with Electron background throttling
(the hidden-window default, reaffirmed on macOS). When the window is
backgrounded/occluded, those renderer timers are throttled to a crawl
and the surface publication misses the 10s deadline. Live keystrokes are
unaffected because PTY I/O flows through the main process, never the
renderer.
Keep the authoritative renderer unthrottled while at least one remote
client is connected and restore the throttled power-saving default once
the last one disconnects. Connect/disconnect are driven from the shared
MobileSocketWiring onReady/onClose, so both direct-WS and cloud-relay
clients are covered; headless serve has no window and is a safe no-op.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(remote): tidy renderer-throttle comment and test per review
Address automated review nits on #11581:
- Trim the module-level rationale comment to the non-obvious contract,
matching the repo's concise-comment guideline.
- Drop the dead `detachedThrottle` variable from the reapply test; the
detached-target scenario is already covered by the lazy-resolution
test, so the case now asserts only what it exercises.
No behavior change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(remote): scope paired terminal publication throttling
Keep headed paired terminal creation and close renderer-owned so host inventory, input routing, ACK recovery, and cleanup retain the established lifecycle. Hold a reference-counted background-throttle lease only while the renderer publishes a paired operation, and epoch-fence async resolution so renderer reloads reject before any request or PTY spawn. Preserve headless main ownership and prevent paired clients from falling back to a local terminal.
* test(e2e): verify minimized host terminal repaint
* fix(remote): preserve paired terminal inventory through graph gaps
---------
Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* [P1] fix(routing): keep the paired-HUB owner for the active workspace selection
#11346 made `activeWorkspaceExecutionHostId` authoritative for the active
workspace, but the short-circuits that read it never see the owner record, so a
project reached through a paired runtime HUB (`hostId: ssh:<target>` plus
`runtimeOwnerEnvironmentId: <hub>`) loses its transport and its repo:
- `resolveWorktreeOperationRouteResult` returned `runtimeEnvironmentId: null`
for any non-`runtime:` selection, bypassing the HUB recovery in
`resolveExactWorktreeRoute` — every owner-routed terminal/git/filesystem
operation on the active workspace lost the HUB that proxies the SSH target.
- `selectRepoByIdForActiveWorkspace` filtered `state.repos` by the selected host
with no fallback, but `withRepoHostOwnership` deliberately keeps the SSH host
on the worktree while the repo row stays HUB-owned — so the active repo read
as `null` and `useGitStatusPolling` stopped polling the whole workspace.
Route resolution now recovers the HUB owner from the owner rows on the selected
host, and drops it only when rival HUBs project the same host. The repo selector
opens exactly one hole in #11346's fail-closed rule — an `ssh:` selection over a
`runtime:` repo row — and every other host mismatch still returns `null`.
`worktree-owner-route.ts` is a pure extraction of the existing owner-route
helpers; no behavior moved with it.
* fix(routing): fail ambiguous paired-HUB repo selection closed
* feat(updater): name hourly releases by version, build number, time, and sha
Hourly releases were titled with their raw tag
(`v1.4.163-hourly.202607312054`), which reads as one opaque digit run and
does not say which commit it came from.
Title them `1.4.163 • 01 • 07-31 13:54 • e698241` instead, and show that
same string in the in-app build picker by having the picker render the
release's stored name rather than deriving its own label. Composing it in
one place means the two surfaces cannot drift.
The build number is monotonic across the channel. It is read as the
highest number already in use rather than as a count of releases: the
prune step trims to 72, so a count would roll backwards after three days
and reissue numbers. Drafts count toward it — unlike in the freshness
check, which asks whether a commit shipped, this asks whether a number is
free, and a stranded draft still holds one.
Times are Pacific while the tag's stamp stays UTC. The stamp is a sort
key and a local one would repeat an hour at every DST fall-back, making
two distinct builds compare equal; the title is only ever read.
* fix(updater): fail the hourly build when the release name is missing
The workflow checks out `ref: main`, but a workflow_dispatch runs the
workflow file from whatever branch was dispatched. A branch that edits
this step while main still carries the old script produces an empty name
and an untitled release — silent, and only visible once someone opens the
releases page. Verified by hitting exactly that on run 30665586904.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(codex): trust extended-length resume paths
* test(codex): cover both sides of extended-length resume folding
The provenance fix folds the extended drive spelling on the rollout path and
the trusted sessions root, and runs per entry in the legacy id scan. Only the
rollout-path side had coverage, so pin the rest:
- normal-form rollout under an extended-length trusted home (root side)
- compressed .zst sibling derived from an extended-length persisted path,
where a folded comparison copy could leak into the returned path
- legacy id-scan entries: extended-length accepted, device namespace refused
All three fail with the production change reverted.
* docs(codex): state accurately what the resume path fold rejects
The helper's comment called the rejected spellings "arbitrary device
namespaces", but \?\UNC\ is the extended-length form of \server\share\ —
a network share, not a device. Reading it as a security decision hides that
UNC is simply not folded yet, so say what is actually excluded.
Also trim the two new test comments to one line to match the file.
* fix(codex): reject rollout alternate data streams
* fix(codex): preserve resume provenance guard
---------
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(orchestration): gate methods route calls to the caller's Run with `f
Gates are Run-scoped state; every gate command now resolves the caller's active Run
(via pane binding or explicit --from flag) and authorizes within that Run's scope.
Settled adopted work no longer requires --takeover-legacy, and the legacy coordinator
fence respects both binding-based and attestation-based proof of authority.
* fix(orchestration): gate methods route calls to the caller's Run with at
Gate and run methods now verify that declared terminal handles match the caller's
attested identity, preventing spoofing of other coordinators. Extracted shared
`resolveRunScope` to enforce one authorization rule across all orchestration
mutations. Added comprehensive regression tests for #11745.