* fix(status-bar): seed Resource Manager closed badge from daemon inventory
The closed chip counted tab/layout PTY wake hints (inflating terminal
count) and never fetched memory until the popover opened (showing "—").
Cache listSessions for the badge, seed memory on session ready, and drop
the wake-hint closed selector.
* fix(status-bar): update session inventory ref in an effect
CodeRabbit/React Doctor flagged mutating sessionInventoryRef during
render; keep the write in useEffect after commit.
* fix(status-bar): follow daemon session lifecycle events
* perf(status-bar): skip inventory scans for known PTYs
* perf(status-bar): bound resource inventory refreshes
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
electron-builder 26 dropped the app-builder-bin Go binary, so the
signed-installer staging step failed with 'node_modules/app-builder-bin/
win/x64/app-builder.exe is not recognized'. Blockmap generation now lives
in app-builder-lib's pure-JS buildBlockMap; call it through a small script
in both the release-cut and signing-rehearsal workflows.
Co-authored-by: Orca <help@stably.ai>
Credits remote terminal output only after parse or intentional discard, with bounded adaptive windows, fair draining, recovery cleanup, and RTT/parser benchmarks.
* fix(linux): restore Ubuntu 20.04 launch by pinning node-pty glibc symbols (#9902)
The bundled node-pty pty.node is compiled from source in release CI on
ubuntu-latest (glibc 2.39). glibc's 2.32-2.34 libpthread/libutil merge
relocated openpty/forkpty (GLIBC_2.34) and pthread_sigmask (GLIBC_2.32)
into libc under new symbol versions, so the from-source build bound to
versions absent on Ubuntu 20.04 (glibc 2.31). The main process imports
node-pty at startup, so the app crashed on launch. pty.node is the sole
blocker (Electron needs GLIBC_2.25; other native modules <= 2.17).
- Patch node-pty: a .symver shim pins the 3 symbols to their pre-merge
version (GLIBC_2.2.5 x64 / GLIBC_2.17 arm64), and Linux-only ldflags
force libutil.so.1/libpthread.so.0 back into DT_NEEDED. Guarded to
Linux; macOS/Windows untouched.
- Add a packaging gate (verify-linux-glibc-floor.cjs, afterPack): reads
each bundled native binary's objdump -p version needs and fails the
Linux build if any strong GLIBC_/GLIBCXX_/CXXABI_ node exceeds stock
Ubuntu 20.04 (glibc 2.31 / GLIBCXX_3.4.28 / CXXABI_1.3.12). Catches
GLIBC_ABI_DT_RELR, rejects GLIBC_PRIVATE, skips weak needs, fail-closed.
- Docs + tests; the lazy sherpa-onnx speech prebuilt (GLIBCXX_3.4.29,
never loaded at launch) is a documented libstdc++-floor exemption.
* fix(linux): assert DT_NEEDED provider deps in the glibc-floor gate
Harden the packaging gate (flagged in adversarial re-eval): the version-floor
check alone can false-pass if the patch's forced `-l:libutil.so.1` ever silently
drops — the pinned openpty@GLIBC_2.2.5 still resolves from libc's compat alias at
build time, but fails to load on Ubuntu 20.04 where openpty/forkpty live only in
libutil. The gate now also asserts that any binary importing openpty/forkpty
keeps libutil.so.1 in DT_NEEDED. Validated on a real symver-pinned .so with
libutil dropped (now fails) vs. present (passes). Documents the recommended
real-host smoke-test follow-up.
* fix(codex): promote [tui] settings so they survive the managed-home remirror
Codex TUI preferences (/statusline, theme, terminal title) are written into
the [tui] table of the managed runtime config.toml, but the write-back
promotion allowlist only covered four top-level scalars — so the next mirror
pass rewrote the runtime config from ~/.codex and silently discarded them.
Extend promotion to the [tui] keys the Codex TUI persists (status_line,
status_line_use_colors, terminal_title, theme), keyed as structured tui.*
paths so the same three-way merge (runtime vs baseline vs ~/.codex) applies:
in-Codex changes promote into ~/.codex before the mirror, and outside edits
to ~/.codex still win over stale runtime values.
The byte-preserving upsert moves to codex-config-settings-upsert.ts (max-lines)
and learns [tui] placement: replace an existing bare or dotted key in place,
insert into the first [tui] body, insert dotted beside existing dotted tui.*
keys, or create one [tui] table at EOF — never defining tui twice, including
when the system config holds an inline tui = {...} table.
* Add codex-config-settings-upsert to the CLI tsconfig file list
* fix(codex): keep tui upserts out of array tables
* fix(codex): handle quoted tui config paths during promotion
* fix(codex): harden tui promotion writes
The Language setting's native word (语言 / 언어 / 言語 / Idioma) only reached
settings search via the localized title in that word's own UI locale — so a
Chinese speaker on the default English UI could not find it by typing 语言.
Always-index the native word for 'language' in every supported language (plus
the previously-omitted Spanish native name Español), so speakers can locate and
switch to their language from any starting locale. Native words are
locale-invariant constants, so they are plain keyword literals with reviewed
localization-coverage allowlist entries.
Co-authored-by: Orca <help@stably.ai>
* feat(linear): add MCP-style save issue
* fix(linear): harden save issue parity
* fix(linear): close save issue contract gaps
* docs(linear): bundle project discovery with save issue
* fix(rate-limits): unstick Claude "Limited" usage and feed live usage from session statuslines
The OAuth usage endpoint's 429 Retry-After (~50 min) was ignored, so the
30s-15min automated retry lanes kept landing inside the throttle window and
the status bar stayed on a bare "Limited" indefinitely while Claude itself
worked fine.
- Respect Retry-After on 429: carry it through usageMetadata.retryAtMs and
gate automated refetches (activation lane, poll cycles) until it expires;
user-directed refreshes still bypass.
- Keep the last-known usage snapshot visible through rate-limited windows
(24h) instead of dropping it after the generic 30-minute stale threshold.
- Add a managed Claude statusLine command that forwards each session's
rate_limits (Claude Code >=2.1.80) to a new /statusline/claude loopback
route, feeding live usage windows with zero usage-endpoint calls; OAuth
polling pauses while the live feed is fresh. User-owned statusLine
settings are never overwritten.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(rate-limits): keep last-known window when a statusline post carries only one
Statusline payloads may report five_hour and seven_day independently; a
partial post must not wipe the other bar to null. Also document the
seconds-vs-ms epoch heuristic.
Addresses CodeRabbit review on #9617.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(rate-limits): unstick Claude usage with live statusline feed
The OAuth polling endpoint is rate-limited; Claude's status often shows
"Limited" until the next poll cycle, even when quota remains. Live posts
from the statusline command update usage within 100ms, eliminating false
"Limited" displays during active sessions.
Manages install lifecycle via marker to respect user deletions. Handles
Windows payload buffering and guards before curl spawn. Protects against
live-post/OAuth-fetch races and cross-attribution during account switches.
Gracefully tolerates schema drift in statusline parsing.
* test(rate-limits): assert stale outgoing post doesn't affect incoming
Capture usedPercent before ingesting and assert it remains unchanged,
rather than checking for a specific value. This is more precise and less
brittle when testing session switch isolation.
---------
Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* feat(codex): backfill managed-home sessions into the real Codex home once per host
Orca-launched Codex sessions currently land only in the Orca-managed
runtime home, so the user's own `codex resume` picker and app history
never see them (#4444, #8612). Backfill the managed sessions tree into
the real ~/.codex/sessions/YYYY/MM/DD layout once per host:
- hardlink first (one physical rollout log), copy as the cross-volume
fallback; existing target files are always skipped, nothing in either
home is deleted or moved
- idempotent; per-file failures leave the completion marker unset so the
next startup retries cheaply
- JSONL audit log of every link/copy/failure under
<userData>/codex-session-backfill/
- honors the custom Codex session source home override, mirroring the
existing system->managed bridge
WSL managed homes are distro-local and need an in-distro variant; that
is a follow-up.
* feat(codex): flag-gated system-default real-home routing scaffolding
Staged internal flag (default OFF, no settings UI): route the SYSTEM-DEFAULT
Codex account at the user's real ~/.codex instead of Orca's managed runtime
home. Flag OFF is byte-identical to today; managed (multi-account) selections
are unchanged in either state.
Routing (flag ON + host system default = no managed account):
- CodexRuntimeHomeService.prepareForCodexLaunch / prepareForRateLimitFetch
return null so the PTY/env layer injects no managed CODEX_HOME and the
rate-limit fetcher + auth-presence gate fall back to ~/.codex (the background
poller stops spawning Codex against the managed home — the #5370 auth war).
- buildPtyHostEnv strips only a nested-Orca-inherited Orca-owned override
(CODEX_HOME matching the private ORCA_CODEX_HOME marker), preserving a
user-set CODEX_HOME. Shell-ready re-exports already no-op without the marker.
- The headless commit-message Codex path strips the same inherited override.
Hook install for the real-home lane (append-last into ~/.codex/hooks.json,
trust via the app-server client) lands with the trust plumbing; the managed
hook install is skipped for this lane meanwhile.
Credit @jellychoco (#8606) for the native-home routing direction.
Depends on the codex trust-rpc-grant plumbing for the real-home hook installer.
* fix(codex): strip the daemon-inherited Orca CODEX_HOME override for real-home routing
The daemon spawns PTYs from its own inherited environment and honors only
spawnOptions.envToDelete, so mutating the sparse env object was not enough to
strip an Orca-owned CODEX_HOME the daemon already carries. Add the strip to
envToDelete for both daemon host-spawn paths, preserving a user-set CODEX_HOME.
Verified live via CDP against a sandboxed dev instance (flag ON): an
Orca-spawned pane reports empty CODEX_HOME/ORCA_CODEX_HOME, so Codex resolves
its own ~/.codex. Adds daemon-path unit coverage (strip Orca-owned, preserve
user-owned, no-op when flag OFF).
* fix(codex): harden one-time session backfill
* test(codex): cover staged cross-volume install
* feat(codex): app-server trust-grant client, capability cache, and grant ledger
Short-lived codex app-server JSON-RPC client (hooks/list + config/batchWrite,
the same pair the Codex TUI 'Trust all' flow calls), run in a bundled
ELECTRON_RUN_AS_NODE entry so synchronous launch prep can block on it with a
hard deadline and guaranteed child reap. Capability cache modeled on
GitCapabilityCache, scoped per execution host (native vs each WSL distro),
with a narrow unknown-method/missing-subcommand unsupported predicate. The
grant ledger records verified grants so steady-state launches skip the RPC.
* fix(codex): grant managed hook trust via codex app-server RPCs in install/refresh
Host and WSL installs now grant trust for Orca's managed status hooks through
codex's own hooks/list -> config/batchWrite -> re-list verify, scoped to
exactly the managed entries; the previous computeTrustedHash lane is the
unchanged fallback for incapable/erroring CLIs. getStatus and the removal
paths recognize ledger-recorded codex hashes so drift between codex's real
algorithm and the replica no longer misreports or strands trust. SSH remote
install is untouched by design.
* test(codex): cover app-server trust grant client, cache, ledger, and lanes
* test(codex): cover commit-message real-home override strip/preserve
Adds the two cases for the headless commit-message Codex env under real-home
routing: a nested-Orca-inherited Orca-owned CODEX_HOME is stripped, and a
user-owned CODEX_HOME is preserved.
* test(codex): WSL grant-lane coverage — in-distro invocation and fallback parity
* feat(codex): real-home hook installer trusted via the codex app-server grant client
With the real-home flag ON and the system-default selection, install Orca's
status hook into the user's real ~/.codex before any pane spawns:
- entry APPENDED LAST per managed event: codex hook trust keys are positional
(source:event:group:handler), so appending keeps every user entry's position
and trust record intact; user entries and unknown top-level hooks.json fields
are preserved verbatim
- trust is granted exclusively through the codex app-server client
(hooks/list + config/batchWrite, verified by re-list); Orca never writes
[hooks.state] into the user's real config.toml itself
- if the grant lane is unavailable (old binary, unsupported RPC, verify
failure), the appended entry is rolled back byte-exactly and the host keeps
the managed-home lane end to end (PTY env, rate limits, commit messages)
via a lane gate on the runtime-home service
- one-time pristine backup of the user's hooks.json under Orca's userData;
a rolling .bak sits next to the file (existing atomic writer)
- hook opt-out sweeps Orca entries from the real home and drops Orca-owned
trust records; flag-off downgrade re-arms the existing legacy system-home
sweep, which removes the entry and its trust keys cleanly
- the legacy system-home sweep is suppressed only while the real-home lane
owns ~/.codex/hooks.json, so managed installs cannot delete the entry
* fix(codex): resolve the trust-grant entry without requiring electron
The grant bridge is reachable from plain-Node CLI entries, where the
plain-node entry guard rejects any chunk containing require("electron").
Resolve the bundled session entry from __dirname (root chunk and chunks/
layouts) with an app.asar -> app.asar.unpacked rewrite for packaged runs,
instead of electron's app path APIs.
* fix(codex): keep session backfill off main thread
Use asynchronous, sequential filesystem operations for the one-time rollout backfill, and avoid repeated target-directory probes. Treat inaccessible managed session roots as retryable failures instead of writing a false completion marker.
* fix(codex): harden app-server trust grant fallback
* fix(codex): install cross-volume session backfill copies atomically
On a real Codex home whose filesystem supports no hardlinks (exFAT/FAT,
some network mounts), the staged cross-volume copy was installed with a
non-atomic copyFile(..., COPYFILE_EXCL) straight into the final
rollout-*.jsonl name. An install interrupted mid-copy (app quit, crash,
ENOSPC during the deferred run) could strand a truncated rollout that the
next run then skips as already-present, defeating the staging design's own
guarantee that a failed copy never leaves a partial session behind.
Install the fully-staged copy with an atomic rename instead, guarded by an
existence re-check so it keeps the never-overwrite contract (and the rename
source is the same immutable managed rollout, so any clobber would be
byte-identical). Cover the no-hardlink-support target and an interrupted
install that must leave no partial in the user's sessions tree.
* fix(codex): resolve grant entry from __dirname so plain-node CLI entries stay electron-free
The build guard rejects any electron require reachable from plain-node
entries; the bridge now maps app.asar to app.asar.unpacked by string
replacement instead of consulting electron app paths. CLI typecheck project
lists the new trust-grant module graph.
* fix(codex): harden trust grant reconciliation
* fix(codex): restore trust config permissions on rollback
* fix(codex): harden real-home routing cleanup and retries
* fix(codex): preserve unicode trust RPC responses
* fix(codex): preserve remote env and complete real-home cleanup
* fix(codex): preserve real-home lane invariants
* test(terminal): isolate replacement idle reset assertion
* fix(codex): preserve real-home dotfile links
* fix(codex): preserve verified trust grants across launch prep
* fix(codex): preserve dangling config symlinks on rollback
* fix(codex): don't revoke a just-granted WSL home on a false 'missing' probe
The async wsl.exe canonical-path settlement could report the runtime home
'missing' immediately after a verified RPC grant (a false negative — codex
had just written and re-listed trust there), which drove the reconciliation
'remove' branch to delete all six granted [hooks.state] tables, leaving a bare
[hooks.state] the launching pane read as 'hooks need review'. A 'missing'
settlement now revokes only when no successful install ran this generation; a
genuinely moved home still resolves to a different path and reinstalls.
* test(codex): model codex config/batchWrite faithfully on Windows
The grant-lane stub simulated codex by calling Orca's upsertHookTrustEntries,
which writes both separator variants for a Windows key (a fallback-lane compat
shim real codex never does) — fabricating duplicate tables and whitespace the
RPC path never produces, so the byte-stable and no-duplicate assertions failed
on win32. Replace it with a single-variant, blank-line-separated writer that
matches the real 0.144.x binary's output.
* feat(codex): collapse duplicate session listings across Codex roots
Backfilled/bridged rollouts are hardlinked into both the real ~/.codex and
Orca's managed runtime home, so AI Vault listed each session once per root
(#7521). Dedup candidates by rollout file name pre-parse and parsed sessions
by session id post-parse, keeping the canonical root: host real home first
(unprefixed resume), then the managed runtime home, then other homes. Applies
to local, WSL, and SSH-remote scans.
* feat(codex): background sqlite index heal for backfilled sessions
Codex's own state-DB metadata backfill is one-shot, so rollouts hardlinked in
by Orca's session backfill never become visible to Codex's DB-driven surfaces.
Extract the app-server stdio JSONL transport into codex-app-server-session
(shared with the trust-grant client) and add a bounded, resumable background
pass that drives Codex's lazy indexing via thread/read per backfilled session:
recent-first, batched onto one short-lived server per batch with small
concurrency, ledger + marker so steady-state startups are a no-op, stop-aware
on quit, and capability-aware on CLIs without the app-server surface.
* fix(codex): preserve session identity during dedup heal
* fix(codex): preserve user trust during real-home cleanup
* fix(codex): harden real-home heal boundaries
* fix(codex): fail closed on unsafe backfill install
* fix: harden real-home hook cleanup
* fix(ai-vault): preserve execution boundaries and reap children
* fix(codex): narrow app-server unsupported detection
* fix(codex): bound user hook trust rebase retries per host
The rebase lane ran a codex app-server session on every launch prep while a
host was stuck (CLI without app-server support, or keys hooks/list cannot
match). Gate the transaction on the shared capability cache and add the same
5-minute transient cooldown the grant lane uses, so sweep and legacy-cleanup
retries cost plain fs reads instead of a codex session per pane spawn.
* fix(codex): enforce real-home resume and heal boundaries
* fix(codex): establish real-home lane before cleanup
* fix(codex): stop index heal before delayed spawn
* fix(codex): protect symlinked rolling backups
* fix(ai-vault): preserve resume env deletion through drag
* fix(codex): strip inherited Codex homes on mobile real-home resume
The mobile resume surface types a bare real-home codex resume into a
freshly created pane, but never asked for CODEX_HOME/ORCA_CODEX_HOME
deletion at pane spawn, so an agentDefaultEnv-pinned or daemon-inherited
Codex home rerouted the resume away from the user's real ~/.codex while
the same session resumed correctly on desktop. Share the deletion helper
from the AI Vault resume builders and forward it through the mobile
launch and session.tabs.createTerminal call.
* fix(codex): gate session migration on real-home lane
* fix(codex): stop session backfill after opt-out
* fix(codex): keep session heal failures retryable
* fix(codex): keep session migration state recoverable
* fix(codex): retry republished missing session heals
* fix(codex): preserve hook symlink trust path
* fix(codex): disambiguate POSIX trust paths
* fix(codex): align hook trust source paths
* fix(codex): harden trust grant lifecycle
* fix(codex): restore envToDelete on client invocation type after base reconcile
* test(codex): type child.stdout as PassThrough for oversized-output write
* Assemble RC: reconcile app-server transport API across PRs
Unify on the object RPC surface from the index-heal transport (#8921) while
preserving the default-home env strip (#8828) and the narrowed missing-app-server
capability signal (#8847): adapt the user-hook-trust-rebase consumer + tests,
port envToDelete stripping into the shared session, and route stderr
classification through the canonical capability-signal module.
* RC: enable system-default real-home routing by default (flag ON)
Flip codexSystemDefaultRealHomeEnabled to default ON for this RC's staged
rollout (a user can still opt out by setting it false, which stays byte-identical
to managed-home behavior). This is the only intended behavior difference between
the RC branch and the individual PRs. Updates the two tests that assumed the
prior OFF default.
* fix(codex): snapshot hooks.json bytes+parse in one read to close real-home clobber race
The install/sweep/legacy-cleanup paths parsed hooks.json, then did a separate
later read to capture the previous bytes for the pre-write generation guard.
A concurrent save (second Orca instance or the user editing the file) could
land between the parse and that second read and be silently overwritten.
readHooksJsonWithRaw returns the raw bytes and parse from a single read so the
guard compares against exactly what it parsed. Adds a regression test that
mutates hooks.json mid-RPC and asserts the sweep aborts without clobbering.
* fix(codex): sanitize managed account config trust
* fix(codex): guard OAuth add for custom providers
* fix(codex): persist outgoing managed tokens before real-home lane takeover (PR-C)
prepareForCodexLaunch returns null early for the real-home / system-default
lane before syncForCurrentSelection runs. If a managed account is still
recorded as synced when the selection has dropped to the system default
(nulled without a sync pass, or auto-deselect on missing managed auth), a
Codex-refreshed token stranded in the shared runtime home is never persisted
to its canonical per-account home -> token loss.
Read the outgoing managed account's refreshed token back before the real home
takes over. The real-home lane implies host === null, so running the
managed->system-default transition restores only Orca's runtime mirror from
~/.codex and never writes the real ~/.codex. It is a no-op once the selection
has already been reconciled, so the normal select path does not double-write.
* fix(codex): preserve refreshes across all default transitions
* feat(codex): show system-default/real-home account identity in switcher (PR-B)
The account switcher modeled the system-default Codex account as
activeAccountId:null with no identity fields, so the null row rendered
blank ("System default" / generic subtitle) even though its effective
login is whatever ~/.codex/auth.json currently is.
Add a CodexSystemDefaultIdentity descriptor {hasAuth, authKind, email,
providerAccountId, workspaceLabel} to CodexRateLimitAccountsState,
resolved live and READ-ONLY from ~/.codex by the accounts service and
returned from listAccounts()/getSnapshot(). The settings switcher now
renders the null (system-default) row as that real identity: the OAuth
email when signed in, "Custom provider — no usage tracked." for
env-key/custom-provider logins (auth.json with OPENAI_API_KEY, or an
OPENAI_API_KEY env with no auth.json), and the generic fallback when
signed out. Identity is host-scoped (per-distro WSL keeps the generic
label). Orca never writes ~/.codex; managed-account switches only touch
Orca-owned homes, so the system-default identity stays a stable,
displayed source of truth. Usage already routes to the real home via
getSystemCodexHomePath, so the switcher now attributes it to a real face.
Tests (sandboxed temp homes only): OAuth email/provider resolution,
api-key auth.json and env-key (no auth.json) as custom-provider,
signed-out, and select/deselect of a managed account never mutating
~/.codex/auth.json.
* fix(codex): parse multiline provider pins in OAuth guard
* fix(codex): harden managed trust sanitization
* fix(codex): harden system-default identity rendering
* feat(codex): give each managed account a self-contained CODEX_HOME; retire shared mirror (PR-E)
With the real-home flag ON, a host managed account now launches directly
against its own codex-accounts/<id>/home instead of the shared runtime
mirror + auth.json hot-swap:
- codex-home-paths: syncSystemCodexResourcesIntoManagedHome links system
resources into any managed home (ownership-marker discipline; never
symlinks into / mutates ~/.codex).
- runtime-home-service: prepareForCodexLaunch / prepareForRateLimitFetch /
syncForCurrentSelection route the per-account home directly and skip the
shared-home hot-swap + token read-back; each home keeps its own auth in
place (fixes GAP-5 concurrent auth race). Session discovery scans every
per-account home.
- hook-service / hook-trust-promotion: install/getStatus/refresh accept a
runtimeHomePath so hooks + RPC-granted trust land in the per-account home.
- service: config mirror into a self-contained home uses the trust-
preserving merge so granted hook/project trust survives account switches.
- codex-session-root-dedup: rank codex-accounts/<id>/home as canonical
managed alongside the shared runtime home.
Flag-OFF and the system-default real-home (null) lane are unchanged; the
nested-Orca CODEX_HOME===ORCA_CODEX_HOME daemon strip (#5370) is preserved.
Sandboxed tests only; ~/.codex is never mutated.
* fix(codex): validate per-account home ownership
* fix(codex): keep managed rollouts discoverable across real-home opt-out
WI-4 lossless migration/rollback validation for pre-E shared-mirror managed
accounts. Session discovery gated the per-account home scan on the real-home
flag, so opting back out (flag OFF) hid every rollout an account accumulated
while the flag was ON — the data stayed on disk but vanished from the AI Vault
until the flag flipped back on.
Scan a managed host home whenever it holds a sessions/ tree, independent of the
flag; a never-enabled install keeps its homes credential-only so opt-out stays
byte-identical to today. Forward migration was already lossless (the shared
mirror is always scanned) and the opt-out credential read-back already refuses
to overwrite a fresher per-account token; add tests locking all three
invariants. Sandboxed tests only; ~/.codex is never touched.
* fix(codex): migrate stranded shared auth on E takeover
* test(e2e): isolate Electron from developer Codex home
* test(codex): add real-account validation harness
* fix(codex): finish C and E matcher composition
* fix(codex): bound validation harness shutdown
* test(codex): isolate hook lifecycle user data
* test(codex): cover realistic account-home migration
* fix(codex): keep standalone home tripwire active
* test(codex): fingerprint system auth in validation reports
* fix(codex): bind managed homes to account ownership
* fix(codex): normalize Windows trust source identity
* fix(codex): make Windows trust upgrade transactional
* test(codex): use TypeScript pipeline for validation scripts
* test(codex): run validation modules through native node
* test(codex): allow slow Windows tripwire startup
* fix(codex): survive lingering Windows codex login processes in add-account
On Windows, codex login can keep running (with descendants) after it has
written auth.json, holding OS handles on the per-account managed home
(log/codex-login.log). That made doAddAccount's post-login cleanup fail
with ENOTEMPTY (rmSync) and left an orphaned codex-accounts/<id>/home.
- runCodexLogin now watches for auth.json on Windows and force-kills the
login process tree (taskkill /t) if it lingers past a short grace
period; the forced exit is treated as a successful login. The 120s
timeout path also kills the whole tree instead of only the direct
child. macOS/Linux behavior is unchanged.
- safeRemoveManagedHome now removes homes with rmSync maxRetries /
retryDelay (mirroring the local-worktree-filesystem Windows policy)
and no longer lets a cleanup failure mask the original add error.
- run-codex-real-account-validation.mjs accepts --temp-parent /
ORCA_CODEX_VALIDATION_TEMP_PARENT so the disposable root can live
outside %USERPROFILE% on Windows, and fails with an actionable message
before creating anything when the temp parent is inside the primary
home. The real-home guard is unchanged.
* fix(codex): preserve managed-account MCP .credentials.json on per-account-home migration (#8440)
Codex file-mode MCP OAuth tokens live in $CODEX_HOME/.credentials.json,
keyed by MCP server URL with no account identity of their own. The legacy
shared-mirror -> per-account-home migration only carried auth.json, so an
existing managed account with authed MCP servers had its tokens stranded on
upgrade and silently needed re-auth.
Carry the shared mirror's .credentials.json into the same identity-proven
per-account home alongside auth.json: only into the single uniquely-matched
active account (no cross-account leak), only when the destination has none yet
(never clobber a newer file the account authed in its own home), atomic 0600,
absent-source no-op. New MCP auth already lands in the per-account home since
that home is CODEX_HOME.
* fix(codex): preserve Windows reauthentication login flow
* test(codex): build real-account validation harness cross-platform on Windows
The harness built its app with execFileSync('npx', ['electron-vite', ...]),
but npx resolves to a .cmd shim on Windows that execFileSync cannot launch
(ENOENT), so the harness could not build its own app there and required
--skip-build with a prebuilt out/main/index.js.
Extract resolveElectronViteBuildCommand(repoRoot): it runs the repository-local
electron-vite JS entry (node_modules/electron-vite/bin/electron-vite.js) with
the current Node binary (process.execPath), which resolves identically on
macOS, Linux, and Windows with no shell. It throws a clear error if the local
entry is missing (install deps or pass --skip-build). --skip-build behavior is
unchanged.
Add regression coverage asserting the build command uses process.execPath and
the repo-local JS entry (not npx), and that a missing entry fails clearly.
* fix(codex): version the MCP creds migration independently of the auth marker
The auth carry and the MCP .credentials.json carry (#8440) shared one
existence-only v1 marker, so any build that stamped the auth-only marker
first would strand the MCP store forever. The MCP carry now concludes via
its own per-account-mcp-creds-migration-v1.json marker and runs even when
the auth marker is already present; ordering is code-enforced instead of
landing-discipline-enforced.
Also isolate per-account read failures: one stale or deleted account home
no longer aborts the whole migration. The broken account stays in the
unique-identity ambiguity gate via its stored fields but is never read or
written, so the active account still migrates.
* fix(codex): fail corrupt managed auth.json without echoing credential bytes
A raw JSON.parse SyntaxError from loadOAuthCredentials could carry auth
file fragments into logs and the add/reauth error surface. Throw a
sanitized error instead; filesystem errors still propagate unchanged.
* fix(mobile): give the pairing runtime a disposable home for the E2E boot guard
The main-process guard now refuses to start with ORCA_E2E_USER_DATA_DIR
set but the real user home, and this was the one caller not updated —
the temporary pairing runtime crashed before emitting its pairing URL.
* test(codex): canonicalize harness containment guards and retry cleanup
Resolve symlinks before the disposable-root containment checks so a
symlinked temp parent cannot smuggle the throwaway home inside the
primary home, and give the final cleanup rm Windows retry/force so a
briefly lingering codex handle cannot strand the credential-bearing
root.
* test(codex): add lane-aware containment mode to the real-account harness
The Windows gate-D run proved strict zero-event whole-profile containment
is structurally unreachable with the real-home flag ON: system-default
spawn sites deliberately delete CODEX_HOME so native codex resolves the
real ~/.codex, and on Windows the binary ignores the USERPROFILE sandbox.
Its own volatile runtime churn (root sqlite/WAL/SHM, tmp/, log/) is the
shipped Phase-1 design, not a candidate defect.
--lane-aware-containment records those designed events without aborting
while every other real-home write — auth.json, config.toml,
.credentials.json, hooks.json, sessions/, anything unknown — remains a
hard violation and still aborts the run. Default behavior is unchanged
(strict); the absolute zero-event claim stays carried by macOS runs,
where HOME does sandbox native codex.
* test(codex): allow the real-account harness to pin the real-home flag off
--system-default-real-home off seeds and env-pins the flag OFF so every
codex spawn gets an explicit managed CODEX_HOME and native codex never
resolves the OS profile. This is the only Windows configuration where the
strict zero-event whole-profile tripwire is reachable, and it matches the
stable-rollout default; flag-ON runs keep lane-aware classification.
* test(codex): correct the flag-off harness comment to kill-switch rationale
The rollout ships all codex-home changes at once (no phased rollout), so
flag OFF is the emergency kill-switch lane, not the stable default.
* test(e2e): canonicalize the isolated E2E home path
The disposable HOME lives under os.tmpdir(), whose spelling is an alias
on CI (macOS /var symlink, Windows 8.3 RUNNER~1). Git canonicalizes
worktree paths, so worktrees created under the aliased home never
matched the app's listing — golden core flows and the packaged
crash-survival harness failed with 'worktree created but not found in
listing'. Resolve the home to its canonical spelling at creation in
both the e2e helper and the packaged-app driver.
* fix(codex): address CodeRabbit review on the landing PR
- carry envToDelete through the mobile agent-resume startup plan so a
real-home Codex resume cannot inherit an ambient CODEX_HOME
- strip Orca-owned Codex overrides in the commit-message WSL fallback,
matching the host fallback
- strip ELECTRON_RUN_AS_NODE in the computer-e2e driver like every other
home-isolation caller
- drop the unused hooksEnabled parameter from isRealHomeCodexHookLaneUsable
* feat(codex): ship real-home routing unconditionally, remove the rollout flag
The codexSystemDefaultRealHomeEnabled setting is gone from types and
constants and the helper no longer consults settings — the system-default
real-home lane and per-account homes ship for everyone in one release.
This also un-strands profiles that rc-era builds stamped with false (the
setting had no UI, so every stored false was a seeded artifact that would
have silently kept those users on the legacy mirror forever).
The ORCA_CODEX_SYSTEM_DEFAULT_REAL_HOME env override survives strictly as
a test-rig control: the containment harness pins the legacy lane for
strict zero-event Windows runs, e2e home isolation pins lanes inside
disposable homes, and the legacy-lane test suites now route their
per-test lane selection through it.
---------
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
* feat(skills): ship orca-cli as a first-generation hybrid stub
Convert the installable orca-cli SKILL.md from a full fat guide into a
hybrid discovery stub: a safe CLI resolver, an `orca skills get orca-cli`
pointer, and a bounded read-only fallback for pre-guide binaries. The
version-matched command reference now lives only in the Orca binary
(embedded guide table, served by `orca skills get`), so the distributed
file can no longer drift from the binary that runs the commands.
- generator projects STUB_TOPICS from skill-stubs/<name>.md, reusing the
guide's own frontmatter so the routing/description surface is unchanged;
the embedded full guide (bundled-skill-guides.ts) is untouched.
- manifest regenerated: orca-cli releaseRevision 32->33 as an append-only
snapshot; existing fat installs classify `outdated` and get the targeted
`npx skills update` nudge (no in-app writes).
- tests: command-guidance assertions repointed to the guide source (their
home now), plus stub-projection + safety coverage.
Only orca-cli converts; the other skills stay fat. Per
notes/skill-freshness-design.md, the E.3 pointer-compliance spike and the
E.5 RC window remain before any further thinning. allowed-tools is
intentionally not added yet (frontmatter kept byte-identical to the guide).
* fix(skills): distinguish guide lookup failures
* chore(skills): refresh released skill mapping
Move managed agent-hook filesystem work behind one relay RPC so high-latency SSH connects pay one WAN round trip instead of hundreds. Keep installers serial, lock shared account config across relay processes, and fence cancelled connection generations from replacement state.
Co-authored-by: nasagong <zinho2000@gachon.ac.kr>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Make plain-text file:// links clickable in the terminal
Printed file:// URIs (e.g. a report path echoed by a tool or agent) were
neither http links nor bare filesystem paths, so the terminal's URL and
local-path detectors both skipped them and the link was dead.
Orca already resolves and opens file:// URIs for OSC 8 hyperlinks. Reuse
that exact resolver for plain-text URIs so a printed file:// behaves the
same whether or not the emitter wrapped it in an escape sequence:
- Promote the (dependency-pure) file-url target resolver into src/shared
so the OSC path and the new plain-text path share one implementation.
- Add a file:// detector that decodes the URI to a filesystem path and
routes it through the existing file-link pipeline (existence probe +
openDetectedFilePath), so line/col anchors, %20, Windows drive paths,
html-in-browser, editor reveal, and SSH/runtime resolution all just work.
Lines without file:// are unchanged: the pass short-circuits to the prior
result, so only file://-bearing lines gain a link.
- Add unit + integration coverage for detection, decoding, and no-double-link.
* Harden plain-text file URI detection
* Split terminal file link detection modules
* test(e2e): prove the terminal daemon survives a main-process crash on Windows (#7742)
Add a win-crash-survival e2e harness (sibling to win-update-e2e) that
force-kills ONLY the packaged app's real Electron main (resolved via
app.evaluate -> process.pid, /F no /T) and asserts the detached
orca-terminal-daemon.exe plus its ConPTY shell survive with no pwsh
0xE9 FailFast, then that a relaunch re-adopts the SAME daemon and the
reattached UI binds to the SAME survivor shell (proved via a per-shell
env sentinel read back through the restored terminal).
This guards the #7742 fix (standalone relocated daemon that outlives
main death) against regression. A directional `--expect orphaned`
profile fails on a fixed build, keeping the survival assertions honest.
Windows-only; reuses win-update-e2e app-driver/daemon-process modules.
* test(e2e): harden Windows crash-survival proof
* test(ci): keep crash survival gate durable
* test(e2e): tolerate restart hydration navigation
* test(e2e): prove exact shell input after crash
* perf(ci): avoid crash harness installer rebuilds
* test(ci): harden crash survival evidence and cost
* test(e2e): fail closed on authoritative crash target
* test(e2e): fail closed on crash liveness evidence
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(pty): stop daemon reattach/kill race from orphaning restored terminals
Two cold-start defects let a PTY kill silently orphan a live daemon session
after a renderer/app restart — the PTY-orphaning-on-restart half of #7742:
- DaemonPtyAdapter.shutdown() was the only session op that skipped
ensureConnected(). The daemon client connects lazily, so a kill arriving
before the first spawn (startup sleep/hibernation sweeps, closing a
restored tab) threw "DaemonProtocolError: Not connected" against a
HEALTHY daemon; the renderer swallows the rejection and the live session
survives detached. Fix: connect first, like every other session op.
- pty:kill had no startup barrier while pty:spawn did. A kill issued before
initDaemonPtyProvider swapped the provider resolved against the pre-daemon
LocalPtyProvider, whose shutdown() silently no-ops on daemon ids; the
handler then reported success and sent a synthetic pty:exit, permanently
clearing the persisted tab->ptyId binding so the session never reattaches.
Fix: pty:kill awaits the same getLocalPtyStartupPromise gate as spawn.
Both reproduced red-first (real DaemonServer socket rig for the adapter;
deferred-barrier IPC rig for the handler) and pass after the fix.
Scope: this restores the reattach/teardown invariant. It does NOT address
the renderer crash trigger or the daemon-process-death path that produces
the Windows PowerShell FailFast symptom (separate follow-ups).
* fix(pty): gate runtime shutdown on daemon startup
* fix(pty): keep shutdown independent of hook startup
* feat(terminal): add flag-gated render-desync sentinel for WebGL panes
Detects the buffer-clean/render-stale glyph garble class in the field: per
visible WebGL pane, compare the cells the xterm buffer says hold glyphs
against the ink actually present on the canvas, sampled in the same task as
a forced synchronous redraw so a divergence proves the render model/atlas is
wrong rather than a missed present. A trip requires the same screen cells to
stay divergent across three samples (real desync is pinned; scroll lag moves),
then records a webgl-render-desync breadcrumb, stashes evidence (canvas PNG +
buffer text) for bug reports, and runs the same shared-atlas recovery a tab
reveal performs, so a stuck-garbled pane self-heals within seconds.
Off by default; arm on any build via
localStorage.setItem('orca:render-desync-sentinel', '1') and reload.
* fix(terminal): invalidate glyph cache on atlas replacement
Reproduce the WebGL atlas identity mismatch with two live terminals and force cached geometry to rebuild whenever a different shared atlas is attached. Persist flag-gated render-desync evidence and retain the investigation tooling used to validate the field signature.
* fix(terminal): harden render desync diagnostics
* docs(reliability): clarify Linux WebGL evidence gap
The current manifest stamped package.json's version into itself (9 lines),
so every RC/stable version bump made the committed artifact stale on every
open branch: lint failed until authors committed content-free regeneration
diffs, which also dragged the resources/skills-filtered update-roundtrip
matrix onto unrelated PRs. Cutting a release tag whose skills tree changed
had the same effect through release-mapping.json.
- current-manifest.json is now schema 2 and content-only; the generator no
longer reads package.json. Registry and mapping stay schema 1 so the
append-only released-history guard keeps its schema gate.
- The running build's version enters at the IPC boundary
(skills:freshnessInventory passes app.getVersion()) and threads through
the inventory to placement observation; current-revision placements are
labeled with it while historical revisions keep resolving through the
release mapping. The artifact loader and its cache stay content-only.
- verify tolerates a committed release mapping that is a byte-exact prefix
of the derived one when every missing trailing row's revisions equal the
current manifest (a just-cut tag over unchanged-since bytes); such rows
are provably redundant until the next real regeneration adds them.
Artifacts now change only when skills/ content changes.
* ci(release): regenerate skill manifest on version bump
The release-cut "Bump package.json and tag" step bumped package.json but
never regenerated resources/skills/current-manifest.json, so its appVersion
stayed at the prior release. That drift shipped in v1.4.144-rc.1, rc.2, and
rc.3 (all carried an rc.1 manifest) and turns verify:skill-bundle-manifest
red on every branch after a cut, since that check runs in `pnpm lint` and
the PR `verify` job.
Regenerate the manifest right after `npm version` and stage resources/skills
into the release commit so the bundled manifest always matches the shipped
version. The generator is dependency-free (node builtins + git), so it runs
without a pnpm install, and the step's fetch-depth:0 checkout supplies the
tag history it reads.
* test(release): guard skill manifest regeneration
* test(release): require full history for skill manifest
* Add safe skill freshness detection
* Accept observed copy-mode rail outcomes
* chore(skills): regenerate snapshot artifacts for the merged guide content
The rebase onto main picked up the reviewed guide fixes (#8624), so the
current manifest hashes and a new appended snapshot generation must
match those bytes; the registry keeps all prior snapshots so existing
installs classify as outdated rather than unrecognized.
* fix(skills): canonicalize snapshot file order and guard released history
Historical snapshots kept git ls-tree byte-order while the working-tree
walk and runtime observation use the sorted depth-first order, so any
future multi-file skill would misclassify older installs as unrecognized
and churn spurious registry revisions; all producers now share one
canonical order (no digest changes for today's single-file packages).
Also rejects executable files from shipped skills (Windows observation
cannot see execute bits, which would misclassify pristine Windows
installs) and adds an explicit append-only invariant for released
snapshots so a generation-logic change cannot rewrite them silently.
* fix(skills): throttle focus rescans and correct self-blocked placement copy
Every window focus re-read and re-hashed all installed packages, and the
nudge and panel each forced their own trailing rescan for one event; a
15s cooldown plus a shared invalidation latch keep one bounded scan per
event while install-change events stay immediate. Bundle artifacts are
now loaded once per run instead of re-parsed on every scan. A read-only
or otherwise unsupported outdated placement now explains that it blocks
itself instead of blaming a phantom sibling placement; the supported
topology set moved to shared so eligibility and copy cannot drift.
* feat(skills): move freshness surfacing to a lingering toast and update modal
The Skills page has been unreachable since its toolbox menu entry was
removed (#4535), so surfacing freshness there buried the feature behind
its own nudge. The nudge now lingers until acted on (ignoring it records
nothing; only the explicit close persists dismissal keys) and opens an
update modal hosting the pre-filled editable terminal, an honest
current/blocked summary, and the per-placement rows in a collapsed
Details section. A compact 'Check for skill updates' row in CLI settings
is the manual re-entry point. Skills page restored to main; design-doc
surfacing section records the venue decision.
* fix(skills): mount update dialog inside the link-routing provider and fold freshness into the setup rails
The dialog hosts a live terminal pane that requires the link-routing
preference context; mounted outside the provider it crashed the renderer
the moment an eligible update existed (caught by live QA — unit tests
mock the terminal). It now mounts inside the provider behind its own
recoverable boundary.
The separate 'Check for skill updates' settings button is gone: the
setup rails' own pill now carries freshness (Update available / Up to
date, falling back to Installed for blocked or unrecognized copies and
for non-local runtimes the local-only scan cannot vouch for), and
Re-check refreshes both installation detection and the freshness
inventory. Wired for the CLI, Orchestration, Computer Use, and
Per-Workspace Environments rails.
* fix(skills): use the sleek scrollbar style in the update dialog
* chore(skills): regenerate manifest for merged main (v1.4.142-rc.1)
Main advanced to 1.4.142-rc.1 with a v1.4.141 release, so the embedded
appVersion and release mapping were stale on the PR's merged tree. Only
appVersion and the new release entry change; no snapshot digests move
(released history preserved).
* fix(skills): bound and batch freshness work
* fix(skills): harden freshness integrity checks
* fix(skills): accept observed copy topology outcomes
* chore(skills): regenerate manifest for current main
* fix(skills): preserve update terminal lifecycle
* chore(skills): regenerate manifest for current main
* fix(skills): fail closed on stale freshness scans
* chore(skills): regenerate manifest for current main
* fix(skills): preserve freshness safety under focus churn
* feat(skills): group the update modal by skill with plain-language status
The Update skills modal now lists only skills that will update or that can't
(with why), grouped by skill with their install locations nested underneath —
no more one row per placement.
- Statuses collapse to "Update available" / "Can't update" at the skill level.
- A location's problem is a chip (Duplicate, Unrecognized, Inaccessible, Read
only, In a repo, External/Broken link, Plugin cache) with a hover tooltip
that explains what it means for the user and what to do.
- Up-to-date, unrecognized-only, and unreadable-only skills are hidden; a
current/unrecognized/etc. location only appears when it explains a shown skill.
- Copy is de-jargoned (drops "copy"/"placement"/"snapshot"/"official copy") and
names the mechanism as the npx skills update command, not "Orca's update".
- Rename the section to "Update details"; drop the unreachable newer-known state.
Renderer-only: derivation is a pure module (groupSkillFreshness) with unit tests;
no IPC or main-process change. Locales updated for all five languages.
* chore(skills): regenerate manifest for current main (v1.4.143-rc.0)
* feat(skills): don't let a duplicate block the update; clearer skipped copy
- Eligibility: a clean standalone duplicate no longer poisons the whole
name — the canonical copy still updates and the duplicate is flagged;
a duplicate-only skill stays unoffered.
- Update modal: "Can't update" -> "Skipped" with a reason-specific
sentence (edited/read-only/in-a-repo/plugin/link); chips describe only
the location state; footer "Check now" -> "Re-check".
- Settings sidebar nav pills go amber "Update available" when a skill is
updatable, matching the setup cards.
- Localized new strings across en/es/ja/ko/zh.
* chore(skills): regenerate manifest for merged main (v1.4.144-rc.1)
* feat(native-chat): add native chat view across mobile
* fix(native-chat): address review findings and CodeRabbit threads
Correctness:
- Restore an independent initial readSession seed and surface initial-drain
errors as snapshot frames so the chat view can never strand on 'loading'
- Pair mobile tool results to calls by ordinal FIFO (parallel calls no longer
misgraft results); clear a pending ask only when its own call resolves
- Show a new streaming reply immediately (same-turn suppression, not length)
- Delegate mobile noise filtering to the shared harness-injected classifier
- Admit soft-leaving mobile clients in beginMobileInputFloor (parity with
mobileTookFloor) so grace-window writes aren't dropped
- Self-heal a stale 'working' status once this turn's reply lands
- Catch RPC rejections in mobile file-open helpers; guard sanitizeToolInput
key collisions; settle web/runtime transports on unrecognized first frames
and forward snapshot errors
Perf:
- Throttle the mobile streaming bubble (50ms) so per-part status frames stop
re-parsing the whole accumulated markdown
- Short-circuit markdown path detection on dot-less or oversized runs
(quadratic backtracking guard)
UX/minor:
- Wire hold-mode dictation through the native chat composer
- Allow scoped-package (@) paths in file-path detection
- Move caret after mid-text autocomplete insertion; index-prefixed ask option
keys; single scroll-to-end effect; bounded wait + toast when image attach
races a resubscribe; count-based pending reconciliation; cache-hit search
cancels stale debounce; chat-tab toggle wins over in-flight preference load
- Share shouldStepNativeChatAskAnswer between desktop and mobile; import
block guards/source priority from shared instead of local copies
- Defensive non-positive transcript limits; test strengthening (TTL expiry,
post-unsubscribe stale frame, lease readiness, filtered console.error)
* refactor(native-chat): share desktop/mobile chat logic in src/shared
Extract the parity-mirrored native-chat modules into shared implementations
both surfaces re-export: ask parsing (registry, parseAskFromStatus,
extractPendingAsk, formatAskAnswer), answer stepping offsets/scheduler, diff
detection/parsing, harness-noise filtering, tool fold/pair/split, and tool
summaries. Removes the hand-synced copies and their stale Metro comments.
Divergence reconciliations take the safer side of each: diffs truncate at
120 lines/32KB everywhere (desktop previously unbounded), tool-run summaries
cap at 3 parts with bounded-depth previews, nameless tool calls are skipped,
and basenames split on both separators.
Also: settle and kill every sibling quick-open pass when one reaches
maxResults (main rg/git and relay git; relay rg already did) so a capped
search cannot leave a scan walking a huge tree; fold window-bounding into
the shared merger's applyAppend; localize the web 'Pair a host' snapshot
error.
* fix(native-chat): address CodeRabbit follow-ups on shared modules
- Attachment lease gate re-checks connection/target/tab after the bounded
wait, so a tab/host switch or disconnect mid-wait can't send into a stale
terminal; a moved-away target drops silently like the pre-wait guard and
only an unrecovered lease surfaces the toast. Adds hook tests.
- extractPendingAsk parses transcript tool-calls through the same
registered-parser + canonical-shape fallback as live status, so a custom
question tool that rendered live survives reconnect/replay.
- Direct unit tests for the shared ask parser (FIFO ordering, fallback,
malformed payloads) and tool-summary bounded preview (depth/collection
caps, circular refs, basename/command branches).
* fix(native-chat): treat initialLimit 0 as a valid empty window
Both engine guards used truthiness, so an explicit zero limit skipped the
bounded tail reader and fell back to an unbounded incremental read. Latent
only (every caller clamps positive), hardened for consistency with the
tail reader's non-positive-limit handling.
* fix(mobile): native-chat composer lock UX + send-failure feedback
- Distinguish input-lock reasons: transport 'disconnected' shows Reconnecting…
instead of mislabeling a reconnect as locked-by-another-client
- Guard the composer lock behind a 600ms hold so connState blips / lease
hand-offs don't flicker the placeholder; unlock stays instant
- Surface a rejected send inline above the composer (a bottom toast hides
behind the keyboard); auto-dismisses after 4s
- waiting-session hint invites the first message instead of implying the
agent is still starting
* test(mobile): sync answer-send pacing test to the 500ms advance buffer
Missed in merge 8fe3c391c, which carried main's NATIVE_CHAT_ADVANCE_BUFFER_MS
300->500 (#8568) into the shared stepping module that mobile derives from.
* fix(mobile): restore terminal stream after chat cold start
* fix(native-chat): harden retries, optimistic sends, and file scans
* fix(mobile): deliver AskUserQuestion answers by option number (STA-1860)
Port #8840's fix to the mobile native chat: the Ask card now tracks
per-question option INDICES (+ free text) and the answer-send hook drives
Claude's arrow-navigate selector with buildAskAnswerKeys keystroke groups —
option numbers, next-tab arrows, Enter — paced one selector step apart, instead
of pasting label text that the selector ignores (which silently committed the
default option). Non-Claude agents keep the pasted-label path via the
selection-based formatAskAnswer.
Backcompat: keystrokes are built client-side and written through the EXISTING
terminal.send passthrough with enter:false — the same contract the permission
card already uses — so an older desktop runtime (SSH/relay included) replays
them verbatim; no RPC/contract change in either update order. Free text is
newline-sanitized because terminal.send has no paste framing.
Drops the now-unused formatCompleteAskAnswer from the shared module.
* fix native chat send and runtime races
* fix mobile native chat formatting
* fix(native-chat): mobile empty state matches desktop copy
Mobile showed a single generic line ('Send a message to get started') where
desktop shows a titled two-line empty state naming the agent ('Start a chat with
Claude' + 'Ask Claude to inspect code, explain output, or make a change.'). Align
them from one source of truth so they can't drift again:
- Extract the agent-type label map + formatAgentTypeLabel to
src/shared/agent-type-label.ts (desktop re-exports; mobile imports).
- Add src/shared/native-chat-empty-state.ts with the canonical English copy;
desktop uses it as its i18n fallbacks (localization unchanged — en/es/ja/ko/zh
keys still win), mobile substitutes the agent label and renders it directly
(mobile ships English only).
- Mobile: render title + subtitle for waiting-session AND ready-but-empty (both
are 'start a chat'), error copy for errors; keep the loading spinner.
Live-verified on the iOS sim against a pn-dev of this branch. typecheck node/web
+ mobile tsc clean; 30 mobile + 428 desktop/shared native-chat tests green.
* style: oxfmt the empty-state parity test (line wrap)
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
The three pr-evidence/*.png files were accidentally committed with the
sidebar fix (#8527). They are e2e evidence outputs regenerated on demand
by worktree-lineage-agent-expansion.spec.ts only under
ORCA_CAPTURE_EVIDENCE=1, and nothing reads them — contradicting the
.gitignore intent that PR evidence screenshots not be committed.
- Remove the tracked PNGs (unreferenced generated artifacts).
- Ignore pr-evidence/ alongside notes/artifacts/.
- Defensively exclude pr-evidence/ from the electron-builder app.asar
include surface so a stray local capture never bloats the bundle.
- Assert the new exclusion in electron-builder-config.test.mjs.
Co-authored-by: Orca <help@stably.ai>
* fix(runtime): retain watcher and PTY teardown ownership
* fix(runtime): restore watchers after interrupted cleanup
* fix(runtime): prevent stale watcher revival
* test(runtime): cover watcher shutdown ownership
* test(daemon): model physical PTY exit
* fix(daemon): keep shutdown terminating when disposal cannot prove exit
A rejecting host.dispose() (unreapable child past its exit deadline) left
the shutdown RPC without its process.nextTick(shutdown) and skipped socket
cleanup in shutdown(), stranding the daemon as an unreachable orphan after
the stale-daemon replacement flow unlinks its socket. Log and continue:
daemon exit reparents the child to init instead of blocking on it.
* fix(runtime): keep local watching alive after an idle-kill deadline miss
An idle child that outlived the exit deadline set shutdownRequested on the
shared desktop supervisor, which has no retire-and-replace path — every
later subscribe rejected supervisor_disposed and the roots were cached
unwatchable, silently ending local file watching for the session. The idle
path owns zero records, so there is no double-watch hazard; the zombie
keeps its capacity reservation until physical exit and the next subscribe
gets a fresh child.
* fix(renderer): resync replayed paired-web file watches
Transparent replay removed the implicit resync the old close-and-rebuild
path provided: a replayed files.watch only reports changes from its own
native setup, so changes during the reconnect gap were silently lost.
Deliver a conservative overflow to consumers once the replayed watch is
ready, matching the overflow-after-interruption contract everywhere else.
* fix(runtime): address teardown review findings
* fix(runtime): retry watches after teardown deadlines
* Fix PTY descendant leaks on forced teardown
* Fix jitter-sensitive terminal lifecycle test
* fix(terminal): kill agent descendant processes on session teardown (STA-1800)
Agent CLIs spawn tool children in detached process groups that PTY
SIGHUP can never reach. Killing an agent session (tab close, retire,
sleep) left those children running as orphans — eight orphaned git
processes burned ~8 cores for up to 11.5h under the agents-running
keep-awake and drained a battery to 8%.
New pty-descendant-termination module: snapshot the ppid tree BEFORE
signalling (a dead root's descendants reparent to pid 1 and become
unfindable), SIGTERM the root group and every descendant, then after a
2s grace SIGKILL survivors gated on a pid+start-time identity re-check
so a recycled pid is never signalled. Snapshot is bounded and never
rejects; failures degrade to today's shell-only kill.
Wired for agent sessions only (plain terminals keep nohup semantics) at
all three POSIX kill sites: local provider shutdown, daemon
TerminalHost immediate kill (the pty:kill path — force-kill bypassed
Session.kill entirely), and daemon Session graceful kill.
Verified live in the built app: an agent pane with a detached-pgid
child; the child survived on the unwired build (three control runs) and
dies within ~5s with the fix. Windows ConPTY and SSH-hosted PTYs keep
the previous foreground-tree contract (documented follow-ups).
* fix(terminal): harden descendant teardown
* fix(terminal): require fresh process snapshots
* fix(terminal): close descendant teardown races
* refactor(terminal): preserve teardown line budget
* fix(terminal): keep descendant teardown fresh and identity-safe
* docs(reliability): record integrated descendant E2E
* fix(terminal): bound descendant teardown work
* fix(terminal): share descendant snapshot indexes
* docs(reliability): record descendant review evidence
* revert: remove speculative descendant hardening