Commit Graph
76 Commits
Author SHA1 Message Date
Brennan Benson 06f6e3bed2 fix(mobile): make image send retries safe (#10228) 2026-07-23 16:04:05 -07:00
OrcaWinandOrcaWin 801ff57e83 fix(mobile): unblock iOS releases (#10224)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-23 14:23:44 -07:00
NeilandJinjing 9500ca7a65 fix(mobile): show attached images in native (rich) chat (#10135)
* fix(mobile): show attached images in native (rich) chat

Attaching an image in the mobile native chat did nothing visible — it reused
the terminal attach flow, which pastes a bracketed host path into the hidden
terminal, so there was no composer preview and nothing in the transcript.

Give native chat the desktop model instead:
- pick + upload shows a removable thumbnail chip in the composer (no early paste)
- on submit, images ride along: Ctrl+U clear -> bracketed paste(s) -> settle ->
  text + Enter (idempotent on retry)
- the optimistic echo carries the local preview URIs and the message renderer
  draws image-ref blocks as real thumbnails when the URI is loadable, so the
  sent photo appears in the conversation immediately
- image-only echoes reconcile by ordinal against user turns after their tail
  (ignores agent replies / paginated history / the 'unknown' ack-loss path)

Terminal chat attach is unchanged (both flows consolidated behind
useMobileSessionImageAttachments). Adds unit coverage for pick+upload, the
ride-along byte order, chip render/remove, and echo reconciliation.

* test(mobile): interactive native-chat image proof (real hooks, click-driven)

Replace the hand-fed component render with an interactive harness that mounts
the real MobileNativeChatComposer/Message + useMobileNativeChatImageAttachments +
drafts under react-native-web and drives the actual flow via clicks. Only the two
OS boundaries are faked: the photo picker and the paired-host RPC socket.

Screenshots (mobile/docs/native-chat-image-attachment/) are produced by real
clicks, not props:
- attach -> real upload pipeline -> chip appears, nothing pasted yet
- send -> real ride-along emits Ctrl+U clear, bracketed image paste, text+Enter
  (shown in the live byte trace) and the sent bubble renders the photo thumbnail

* fix(mobile): scope native-chat image attachments by active tab

Images are now scoped to the tab that initiated the pick, so switching tabs
during upload cannot ride an image into another terminal. Chips stay with
their original tab, and only the active scope's images send with text.
Improved error handling with user-facing toast messages for disconnection
and send failures.

* test(mobile): add image attachment tab-scoping and error tests

Add comprehensive test coverage for tab-scoped attachment behavior,
error handling when transport fails or lease is gated, and edge cases
like attaching images during an in-flight send. Extract baseArgs and
update helpers to reduce boilerplate across test cases.

* fix(mobile): show attached images in native rich chat

Images attached in the mobile native (rich) chat now display as:
- Removable composer chips while composing
- Thumbnails in the sent user bubble after sending (desktop parity)

Implements proper image echo reconciliation by distinguishing
image-source marker turns from text echoes, so an image send isn't
cleared by an unrelated text echo. Adds scope isolation to prevent
chips and drafts from leaking between tabs, and detects tab switches
during the image-paste settle window to abort the send.

Fixes Android tap-target positioning for the image removal badge and
clears stale terminal input after failed pastes to avoid gluing
fragments onto the next message.

* rm stubs

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-23 13:25:50 -07:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -07:00
Brennan Benson 4fce2de494 fix(mobile): keep native chat from resizing the covered terminal PTY (#9988)
* fix(mobile): keep native chat from resizing the covered terminal PTY

Native chat reads the agent transcript stream and never renders the
terminal grid, but two paths still pushed phone dimensions into the
covered PTY, reflowing the desktop terminal for no benefit:

- The covered lease-only subscribe carried the cached viewport, and
  handleMobileSubscribe phone-fits the PTY whenever a viewport is
  present. The lease now omits the viewport so the host keeps the
  desktop baseline and late-binds on return to the terminal tab.
- useTerminalViewportRefit measured the still-mounted WebView under
  the chat overlay and sent terminal.updateViewport on rotation,
  keyboard, text-scale, reconnect, and iOS-resume triggers. Refits
  are now suppressed while native chat covers the active terminal;
  the triggers already mark the viewport stale, and the
  return-to-terminal resubscribe re-measures.

* fix(mobile): harden native-chat resize suppression
2026-07-22 19:36:22 -07:00
JinjingandOrca 3708c4f6ce fix(mobile): report interrupted native chat sends as delivery-unknown, not failed (#10021)
* fix(mobile): report interrupted native chat sends as delivery-unknown, not failed

A terminal.send interrupted mid-flight showed a definite "Message not sent"
even when the desktop may have already delivered the text. Three paths were
misclassified as definite failures:

- Logical relay/direct cutover: migrateTo rejects in-flight requests with
  LogicalClientCutoverError, which mapped to 'rejected'. Now maps to 'unknown'
  (held unconfirmed + transcript-echo verification; never retried since
  terminal.send is non-idempotent).
- Suspend/close of a half-open session: the stable logical client blanket-
  rejected in-flight pendings with plain 'Client suspended'/'Client closed',
  preempting the physical layer's delivery-unknown marking. It now lets the
  physical close settle them, so post-write failures stay marked and pre-write
  failures stay definite.
- Relay path: mobile-relay-rpc-session never marked delivery ambiguity at all
  (timeout, close, link failure). Post-write rejections are now marked;
  pending entries only exist after the frame reached the authenticated link.

Permission, ask-answer, and cancel-Escape surfaces now show "unconfirmed —
check chat before retrying" instead of a definite "not sent" on ambiguous
outcomes (still not-accepted, never retried). Also consolidates a private
copy of isLogicalClientCutoverError in worktree-create-retry.

Co-authored-by: Orca <help@stably.ai>

* chore(skills): regenerate skill-bundle manifest artifacts

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-22 19:28:21 -07:00
Brennan Benson 11310eef63 fix(mobile): keep quick-commands button steady while capabilities load (#9979)
* fix(mobile): keep quick-commands button steady while capabilities load

The tab-row quick-commands button only rendered once the capability probe
resolved true, so it popped in after the row was already visible (and
vanished during reconnect re-probes). Render it whenever support is not
confirmed absent and disable it until the probe settles — pre-quick-commands
hosts strip agentPrompt, so the action (not the button) must wait for
confirmation. Confirmed-unsupported hosts still hide it entirely.

* fix(mobile): explain unsupported quick commands on tap instead of hiding

Per feedback on the disabled/hidden states: the button now always renders
and stays tappable. Tapping against a desktop that confirmed no support
shows "Desktop update required for quick commands" (mirroring the browser
streaming copy); tapping while the capability probe is still resolving says
to try again in a moment. The sheet still opens only once support is
confirmed, since pre-quick-commands hosts strip agentPrompt.

* docs(pr): add QA screenshots for quick-commands button states

* test(mobile): lock quick-commands button stability

Add a focused source-contract test for the always-mounted tab action and confirmed-support sheet gate. Keep the non-obvious safety comment concise, and remove PR screenshots now hosted as GitHub user attachments.

* test(mobile): structurally guard quick-command action mount
2026-07-22 14:18:59 -07:00
NeilandOrca 76f5b8318c fix(mobile): pause session polling in background (#9875)
Co-authored-by: Orca <help@stably.ai>
2026-07-22 14:12:59 -07:00
Brennan Benson d6c9fcd537 feat(mobile): surface pairing-auth failures on desktop and mobile (#9782)
* feat(mobile-pairing): surface unpaired-device auth failures instead of silent 4001 loops

Desktop: when a phone repeatedly fails direct-transport E2EE auth with a
token missing from the device registry (pre-v1.4.106 pairing-path bug left
desktops that regenerated their registry rejecting paired phones forever),
throttle to one notification per session and show an actionable toast
pointing at Settings -> Mobile to re-pair.

Mobile: map a bare 4001 close onto the existing auth retry budget (the
encrypted e2ee_error is undecryptable when the desktop keypair changed, so
the close code is the only surviving signal) instead of looping the generic
reconnect forever, and make the auth-failed verdict say 'Pairing invalid -
re-pair with your desktop' instead of a bare 'Auth failed'.

* fix(mobile-pairing): handle stale keys and startup notification races

* fix(mobile-pairing): isolate auth notification failures

* fix(mobile-pairing): keep recovery alert actionable
2026-07-22 14:05:37 -07:00
Brennan Benson 0121f571e4 fix(agent-status): map codex request_user_input questions to Needs You (#9861)
* fix(agent-status): map codex request_user_input questions to waiting

Codex 0.145 asks user questions via the auto-allowed request_user_input
tool (experimental default_mode_request_user_input): PreToolUse fires
while blocked on the answer with no Stop, so Orca showed the pane as
working/idle instead of Needs You. Map that PreToolUse to waiting
(mirrors grok's ask_user_question), exempt question waits from the codex
yolo auto-approval suppressor, and deliver native-chat answers to the
digit-commit selector by option number (typed labels are ignored and
Enter commits the highlighted first option). Older codex versions emit
no such event and are unchanged.

* fix(native-chat): preserve codex question answer semantics
2026-07-22 12:00:14 -07:00
Brennan Benson dfbc2e8ba7 fix(mobile-quick-commands): replay sheet load killed by connection migration (#9798)
Opening the Quick Commands sheet right after connecting over relay races
the relay->direct cutover, which rejects the in-flight one-shot
settings.getTerminalQuickCommands with LogicalClientCutoverError while
connState stays 'connected'. The sheet then strands on "RPC interrupted
by connection migration" with an empty list until closed and reopened.

The read is side-effect-free, so replay it on cutover (capped at 5,
cancelled if the sheet closes or the client is replaced). Same failure
class and pattern as #9794 (capability probe) and #9796 (terminal
create).
2026-07-22 11:45:59 -07:00
Brennan Benson ac909c8d83 fix(mobile): stop reporting delivered chat messages as "Message not sent" (#9792)
* fix(mobile): stop reporting delivered chat messages as "Message not sent"

A relay drop or response timeout while terminal.send is in flight rejects
the RPC even though the request usually already reached the desktop — only
the ack was lost. The chat composer treated every failure as definite,
showing "Message not sent" and keeping the draft for a message that is
visibly in the transcript after resync (and baiting a duplicate send).

Mark transport failures that happen after the request frame hit the wire
as delivery-unknown, and hold those sends instead of erroring: when the
transcript echo lands the draft clears silently; only if no echo arrives
within 20s is the failure surfaced. Failures before the frame was written
(and host rejections) still error immediately.

* fix(mobile): close delivery ambiguity races

* fix(mobile): harden ambiguous send reconciliation

* test(mobile): assert ambiguity deadline boundary
2026-07-21 18:39:26 -07:00
Brennan Benson 6e6b7d8195 fix(mobile): retry session capability probe so tab-row actions survive relay cutover (#9794)
* fix(mobile): retry session capability probe so tab-row actions survive relay cutover

The session screen learned host capabilities (quick commands, browser
screencast, agent history, query-reply input) from a single status.get
fired when the screen connected. Over relay, a relay-to-direct transport
cutover rejects every in-flight request while connState stays
'connected', and a request timeout does the same — so one transient
failure latched the capability flags false (or left them null on an
ok:false reply) and the quick-commands tab-row button stayed hidden
until the screen was remounted.

Replace the one-shot probe with startRuntimeCapabilityProbe: retry
promptly after a cutover (the replacement transport is already
authenticated) and with capped exponential backoff on other failures,
until a probe lands or the effect is cleaned up. Also export the
cutover-error predicate from stable-logical-rpc-client and reuse it in
worktree-create-capability instead of a local copy.

* fix(mobile): reset runtime gates before capability reprobe
2026-07-21 17:55:02 -07:00
OrcaWin 05c32c4757 fix(runtime): isolate navigation across paired clients (#9664) 2026-07-20 21:36:15 -07:00
OrcaWin 1293e1c0d8 Fix mixed-version mobile Codex session resume (#9678) 2026-07-20 21:16:08 -07:00
Brennan Benson e3c8d96638 Access the Floating Workspace from mobile (#8405) (#9523)
* Access the Floating Workspace from mobile (#8405)

Surface the desktop Floating Workspace (the global, repo-less scratchpad
of terminal tabs under the synthetic `global-floating-terminal` id) on the
mobile app so a Claude session left running there is reachable from a phone.

Adds a terminal-icon button to the mobile host header (phone + tablet
sidebar) that opens the existing Session screen for the floating id. The
sentinel already had host-side RPC support (#5946: local runtime, homedir
cwd, explicit-id fast paths in session.tabs.*); this wires up the mobile
surface and gates it on a new `floatingWorkspaceEnabled` status flag so the
entry hides on hosts that predate it or where the feature is disabled.

The Session screen learns an `isFloatingWorkspaceRoute` flag (mirroring the
existing `folder:` route pattern) that hides repo-backed surfaces — Files,
Source Control, PR/checks, agent history — skips the diff-comment and GitHub
probes, routes terminal URL taps to the phone browser, and limits the New
Tab drawer to terminals + agents (browser/markdown creation resolves a real
worktree host-side and stays desktop-only). useLiveWorktreeName
short-circuits for the sentinel so it no longer polls worktree.show forever.

Extracted the host status.get gating into a useHostStatusGates hook to keep
the host screen under the max-lines ratchet.

* Harden mobile Floating Workspace routing

* Fix mobile host gate reuse race

* Harden floating mobile session polling

* fix(mobile): harden floating workspace route reuse

* fix(mobile): skip floating workspace repo lookup

* fix(mobile): clarify floating workspace header action
2026-07-20 21:05:40 -07:00
971b167548 fix(github): load PR diffs for Enterprise remotes (#8932)
* fix(github): load PR diffs for Enterprise remotes

* fix(github): encode PR content paths by segment

* Fix PR review actions failing on GitHub Enterprise remotes

- Threads GitHub host identity (not just owner/repo) through the client,
  work-item-details, issues, and RPC layers so gh commands target the
  correct Enterprise server instead of silently falling back to github.com
- Adds a shared github-api-repository helper to resolve/host-qualify repo
  identity consistently across REST, GraphQL, and CLI shorthand calls
- Scopes the gh rate-limit breaker and singleton rate-limit snapshot by
  host/runtime so a github.com block or probe can't affect GHES or WSL
- Coalesces concurrent host-auth probes and paginates PR file fetching
  beyond 100 results
- Propagates `host` through renderer PR caches, checks-panel keys, and
  preload IPC types so Enterprise and github.com data never collide

* Route gh host qualification through runner options instead of argv sniff

Move GHES/GH_HOST resolution from parsing --hostname/--repo out of gh argv to an explicit options.host passed through ghExecFileAsync, since SSH-backed repos spawn gh with no cwd and argv sniffing couldn't reliably detect the target host. The runner now injects --hostname and qualifies --repo/-R at spawn time from options.host, and rate-limit scoping/guards use the same explicit host instead of inferring it. Also adds a shared githubRepoIdentityKey helper to keep cache/store keys consistent with the new host-aware repository identity.

* Fix gh CLI GHES host pinning and rate-limit scope leaks

- Pin `--host` on every gh call site so a process-level GH_HOST can't
  silently redirect requests, and qualify `-R`/`-R=` repo shorthand
  alongside the existing `--repo=` handling.
- Check the target scope for an active rate-limit block before each
  WSL/native or host fallback retry, not just on the initial attempt,
  so a blocked scope can't be hit again through a fallback path.
- Compute idempotency once per call instead of re-deriving it after
  fallback reassigns args.

* Fix GitHub Enterprise host identity loss across PR/work-item paths

- Thread `host` through mobile PR RPC params, IPC work-item lookups, and
  RPC schemas so GHES identity survives the renderer/mobile/main boundary
  instead of silently falling back to a same-named github.com repo.
- Qualify `--repo`/`-R` args for github.com too (not just GHES), since
  gh resolves bare shorthand against a process-level GH_HOST that can
  redirect pinned github.com commands.
- Cache `getOriginGitHubApiRepository` to avoid a per-call uncached
  `git remote get-url` round trip on connection-backed repos.
- Add a local-fork fallback in `getWorkItemDetails` so PRs living on a
  base repo (not visible via the origin slug) still resolve via cwd.
- Centralize the github.com-vs-GHES host predicate in
  `isDefaultGitHubHost` so cache keys, quota scoping, and identity
  checks can't drift out of sync.

* Make repository identity host-aware across all GitHub surfaces

Generalize the auth-gated enterprise resolver to any remote and build a
cached hosted-identity family (origin/issue/candidates/source) on top of
it, then migrate every github.com-only consumer: Tasks listing/counting,
branch-to-PR discovery, push targets, fork upstream, issue operations,
Projects, web links, avatars, and PR-link facts. Scope the rate-limit
breaker probe per runtime:host and classify WSL UNC cwds correctly.

Co-authored-by: Orca <help@stably.ai>

* Fix expected slug to include host field in GitHub PR link test

Updates the smart-source paste-intent test fixture to match the
repository slug shape that now carries a `host` field, keeping GHES
host identity intact through the paste-intent parsing path.

* Surface per-host gh auth state for GitHub Enterprise

diagnoseGhAuth accepts the host a surface needs credentials for, scopes
the account/scope diagnosis to that host, and reports whether gh has any
login there; GhAuthErrorHelp renders host-qualified login/refresh
commands so an unauthenticated GHES host stops masquerading as a
github.com scope problem. Also fixes the mobile paste-intent expectation
for host-carrying parsed links.

Co-authored-by: Orca <help@stably.ai>

* Bound GHES identity caches and preserve non-default ports in host identity

Cap the origin-repo and host-auth caches like ownerRepoCache; keep ports
from remote/link URLs so GHES on a non-default port is a distinct
identity; make positional github.com slugs explicit against GH_HOST;
compare work-item sources by host-aware identity key; bail cwd-less
branch lookups when no repository candidate resolved; thread host
through the renderer work-item slug lookup.

Co-authored-by: Orca <help@stably.ai>

* Thread GitHub host through issue detail requests

Incorporates ghes-issue-host-support (ed6bb96ef): one hosted issue
repository identity is resolved before the details fan-out so comments,
timeline, participants, and mention lookups cannot drift across hosts,
with SSH guards so unresolved issue/PR repositories never fall through
to gh's default host.

Co-authored-by: Orca <help@stably.ai>

* Scope remaining GitHub rate-limit accounting

* Resolve typed PR lookups across hosted repository candidates

getWorkItem's PR path probes upstream-then-origin hosted candidates
instead of origin alone, so fork checkouts resolve the base repo's PR
with the right host; issue detail resolution reuses the up-front hosted
identity and keeps the SSH unresolved-host guards.

Co-authored-by: Orca <help@stably.ai>

* Refactor GitHub repository execution setup

* Carry host on smart-submit link intents

Co-authored-by: Orca <help@stably.ai>

* Carry the project host on GitHub item dialog origins

Co-authored-by: Orca <help@stably.ai>

* Keep GHES web ports but drop SSH transport ports in host identity

Supersedes PR #9118 on this branch: http(s) remote ports identify the
Enterprise web/API endpoint and are preserved, while ssh/git transport
ports (including ssh.github.com:443) never leak into gh's host identity.
Replaces the ssh.github.com:443 special case with the structural
protocol split and ports the PR's parsing test suite.

Co-authored-by: Orca <help@stably.ai>

* Support GitHub Enterprise diffs and mutations with host-scoped caches

Parse GitHub host identity from work-item URLs and carry it through PR/issue mutations, labels, and assignments. Bound rate-limit and scope-probe caches (1024 and 512 entries) to prevent unbounded growth when interacting with multiple GHES instances. Normalize repository identity keys to include host so github.com and GHES slugs don't collide in cache and equality checks.

* Support GitHub Enterprise diffs and mutations with host-scoped caches

- Carry host identity through PR mutations and reads so fork PRs on
  different GHES instances don't collide in cache or state tracking.
- Validate host authentication before routing requests to unconfigured
  Enterprise servers; ambient credentials must never reach untrusted hosts.
- Scope rate-limit guards and spend tracking per host so GHES quota stays
  independent from github.com quota.
- Respect explicit --hostname arguments in gh CLI calls ahead of GH_HOST or
  ambient defaults, so breaker state follows the actual request target.
- Detect implicit WSL runtimes from UNC paths for consistent host auth and
  execution-options scoping across mobile and desktop clients.

* Support GitHub Enterprise work-item diffs with host-scoped execution

Enterprise PRs must use their selected host consistently across diff, comments,
and file-content loads. Validate repository slugs before authenticated execution
to prevent path-injection via renderer overrides. Scope project browsing cache
and rate-limit tracking by host to prevent cross-host pollution. Use parsed
URLs as authoritative over ambient hosts for project resolution.

* Support GitHub Enterprise work-item diffs with host-scoped execution

Preserve host identity on PR/issue work items throughout the mutation and diff
pipeline so Enterprise instances (including ported endpoints like
github.acme.test:8443) can execute mutations without ambiguity. Rate-limit gh
commands by the pre-qualified --repo host, cache auth state per ported host,
and surface Enterprise hosts in project metadata and error messages.

* fix(review): drop dead rateLimitGuard/noteRateLimitSpend re-export

Both callers (project-view.ts, mutations.ts) moved to the host-scoped
repositoryRateLimitGuard/noteRepositoryRateLimitSpend; the bucket-only
re-export in internals.ts had zero importers left.

Co-authored-by: Orca <help@stably.ai>

* fix(ci): split Enterprise host work-item tests under max-lines

Move GHES/SSH host-routing cases out of work-item-details.test.ts so
the suite stays within the 800-line test max-lines budget.

* test(github): align mocks with host-scoped repository resolution

- Route origin repository resolution through getOwnerRepoForRemote, not getOwnerRepo, to match production path
- Pin github.com host on origin results so host-less fixtures pass host gate in resolveGitHubApiRepository
- Add generation-based invalidation to prevent stale slug-cache writes from in-flight resolutions
- Fix ref-sync race in ProjectPicker: use useLayoutEffect so committed tree owns browse cache key
- Defer handledCrossRepoUrlRef assignment in SmartWorkspaceNameField until resolution succeeds
- Update Enterprise host routing: found work items must not silently fall back to default host when unresolved
- Normalize GHES avatar URLs: accept explicit port 443 as canonical form, not a fallback trigger

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-20 18:55:45 -07:00
Brennan Benson 0ed1d04b3b fix(codex): migrate legacy shared-home sessions before resume (#9624)
* fix(codex): migrate legacy shared-home resumes

* fix(mobile): allow legacy Codex resume preparation
2026-07-20 17:10:07 -07:00
Brennan BensonandOrcaWin e58de71f5e feat(codex): real-home routing + self-contained multi-account homes (#9501)
* 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>
2026-07-20 14:34:53 -07:00
Brennan Benson ccd72f5909 Add unified mobile onboarding for session view and notifications (#9478)
* Add a mobile native-chat opt-in so users pick terminal vs chat once

Mirror the notifications one-time opt-in for the native-chat default view.

After pairing, a full-screen modal (modeled on notification-opt-in) lets the
user choose whether supported agent sessions open in the terminal or in native
chat, then persists the choice to the existing orca:defaultSessionView key.

- Expose readDefaultSessionViewPreference() (tri-state; absent key = undecided)
  so the gate can prompt exactly once; loadDefaultSessionView() is unchanged.
- shouldPresentSessionViewOptIn() gates the screen; the home focus effect shows
  it after the notification opt-in.
- Settings -> Native chat toggle (already shipped) remains the recovery path.

* fix(mobile): preserve onboarding flow after pairing

* refine mobile session view opt-in copy

* Unify mobile onboarding prompts
2026-07-20 11:11:23 -07:00
NeilandOrca c6f0ac4040 refactor(comments): slim verbose comments in mobile (#9547)
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.

Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.

Area: mobile. 11 files changed, 339 insertions(+), 1137 deletions(-).

Co-authored-by: Orca <help@stably.ai>
2026-07-20 03:18:35 -07:00
Brennan Benson 5fcf777617 feat(mobile): Quick Commands (terminal + agent-prompt presets) (#9298)
* feat(mobile): add Quick Commands (terminal + agent-prompt presets)

Brings the desktop Terminal Quick Commands feature to mobile: saved
agent-prompt or terminal-command presets that launch a new terminal tab.

Entry point sits in the session tab strip next to the "+" new-terminal
button (with a divider) — quick commands spawn a tab, so they live with
tab creation, mirroring desktop's tab-bar split button.

- Launcher button + Quick Commands bottom sheet (search, This project /
  Global groups, run/edit/delete rows, add row).
- Add/Edit sheet mirroring desktop TerminalQuickCommandDialog: Label,
  Action toggle (Terminal Command | Agent Prompt), Agent select, Prompt /
  Command Text, Advanced (Append Enter, Scope Global/Project), validation
  and save-failure feedback.
- Launch reuses handleCreateTerminal (extended with enter + toast copy):
  agent prompts launch the agent then deliver the prompt; terminal
  commands run the (Enter-appended) command text.
- Expose terminalQuickCommands over the remote/mobile RPC surface
  (getClientSettings/updateClientSettings allowlists, RuntimeStore type,
  and the strict SettingsUpdate zod schema).
- Mirror the agent-prompt support predicate mobile-side (stdin-after-start
  agents are unsupported) with a parity test guarding drift from desktop.
- Mock server: sample quick commands + settings.update handler for QA.

* fix(mobile): harden quick command execution

* fix(mobile): harden quick command persistence and launch

* test(mobile): preserve unexpected quick command errors

* fix(mobile): harden quick command launch performance

* fix(runtime): reject malformed quick command updates

* refactor(mobile): reuse shared quick-command logic instead of mirroring

The mobile quick-commands mirror was built on a false premise — that
runtime-importing src/shared/terminal-quick-commands breaks the RN bundle
/ Vitest. It doesn't: tui-agent-config → orca-cli-command-name is a pure
leaf with no module-load Node APIs (verified via probe + bundle-graph).

- Mobile now reuses the canonical desktop helpers (action/agent/scope/
  matchesRepo/support/flatten) directly from src/shared; only genuinely
  mobile-specific pieces (agent-branded labels, native row truncation,
  the launch plan) stay local.
- Multiline runnable terminal commands now flatten via the shared
  flattenTerminalQuickCommand (";"-join) — unity with desktop, so a
  command saved on one runs identically on the other.
- Drop the MOBILE_TUI_AGENT_PROMPT_COMMAND_UNSUPPORTED mirror + its parity
  test; use the shared supportsTerminalAgentQuickCommand predicate.
- Export the shared MAX_QUICK_COMMAND_* length caps for reuse.

* fix(mobile): protect quick command data boundaries

* fix(mobile): enforce quick command limits

* fix(mobile): make quick command updates atomic

* fix(mobile): keep quick command filters recoverable

* fix(mobile): use filled play icon for quick commands

* Revert "fix(mobile): use filled play icon for quick commands"

This reverts commit 169bf053b0.

* fix(mobile): gate quick commands on host capability
2026-07-19 23:42:31 -07:00
Brennan Benson 44686f324c feat(native-chat): add mobile default-view toggle for native chat (#9084)
* feat(native-chat): add mobile default-view toggle for native chat

Adds a per-device "Native chat" setting on mobile (Settings → Native chat)
that controls whether supported agent sessions open in the native chat view
or the raw terminal. Default stays terminal; flipping it on makes eligible
sessions render as native chat, mirroring the desktop default-view control.

- New orca:defaultSessionView preference (loadDefaultSessionView/save).
- Per-tab chat set refactored into a tri-state override map so a session can
  be pinned to terminal or chat regardless of the default; the legacy array
  format migrates to chat overrides on load.
- useMobileSessionViewMode resolves each tab as override ?? default and
  reloads the default on focus so a Settings change applies without remount.
- Toggle/action-sheet builders take an isTabChatView predicate.

* chore(skills): refresh skill bundle manifest for v1.4.144-rc.2

The v1.4.144-rc.2 release bump left resources/skills/current-manifest.json
pointing at rc.1, so verify:skill-bundle-manifest fails on any branch that
reaches it. Regenerated (appVersion only; no skill content changed) to
unblock CI. Unrelated to the native-chat toggle in this PR.

* fix(native-chat): harden mobile view preferences

* fix(native-chat): harden default view persistence

* fix(native-chat): serialize mobile view persistence

* fix(native-chat): reconcile failed mobile view saves

* fix(native-chat): fail closed on unreadable view overrides

* fix(native-chat): honor fail-closed view races
2026-07-17 00:34:46 -07:00
64181fdd42 feat(native-chat): native chat view across mobile, desktop, and web (#5824)
* 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>
2026-07-16 13:26:15 -07:00
Jinwoo HongandOrca 42ee45f392 Fix restored mobile terminals and workspace visibility parity (#8789)
* Fix mobile cutover activation and usage refresh loops

Co-authored-by: Orca <help@stably.ai>

* Fix restored mobile terminal state parity

Co-authored-by: Orca <help@stably.ai>

* Fix migrated PTY workspace attribution

Co-authored-by: Orca <help@stably.ai>

* Fix overlapping mobile terminal surface swaps

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-14 19:36:47 -07:00
7fe6742b8b fix(pr-comments): let users mark comment authors as bots for the Humans/Bots filter (#7598)
* fix(pr-comments): let users mark comment authors as bots for the Humans/Bots filter

Some review bots post from regular user accounts that defeat both provider
bot metadata and login heuristics, so their comments were misclassified as
human. Adds a persisted prBotAuthorOverrides setting with a "Mark author as
bot" comment action, applied consistently across desktop and mobile.

Fixes #7597

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(pr-comments): address review feedback on bot-author overrides

- Cap sanitized prBotAuthorOverrides at 500 entries so malformed payloads
  can't bloat GlobalSettings or slow comment classification
- Reuse the shared normalizePRCommentAuthorLogin in isBotPRComment on
  desktop and mobile instead of duplicating the normalization inline
- Pass botAuthorOverrides from CommentRow to CommentMoreMenu instead of
  re-subscribing per menu instance
- Re-fetch mobile bot-author overrides alongside each PR refetch so they
  don't stay a stale one-shot snapshot for the whole session

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(pr-comments): harden bot author override sync

* fix(pr-comments): bound and recover override updates

* fix(pr-comments): merge overrides from canonical settings

* fix(pr-comments): make bot override updates atomic

* fix(pr-comments): surface rejected bot overrides

* fix(i18n): translate bot override warning

* fix(i18n): translate bot author actions

---------

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: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
2026-07-12 14:09:15 -07:00
NeilandOrca fd6805a299 Fix mobile terminal query reply authority (#8227)
* Fix mobile terminal query reply authority

* fix(terminal): harden mobile query reply handoffs

* fix(terminal): exclude passive mobile query responders

* fix(terminal): gate mobile query replies on host capability

Older hosts strip terminal.send's inputKind (zod drops unknown keys), so a
forwarded xterm reply would land as ordinary floor-taking shell input. Hosts
now advertise terminal.query-reply-input.v1 via status.get and mobile drops
replies unless the host advertises it (pre-fix behavior). Also documents the
bounded desktop-to-mobile handoff double-reply residual.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): advance snapshot seq across recovery snapshots

The pending-overflow recovery loop trims buffered output against
recovery.seq while query replay and boundary strips kept using the
initial snapshot seq. Unreachable under today's control flow (no await
separates the initial-overflow consume from the loop), but the stale
seq would silently drop covered query replies if that ordering ever
changes. Track the seq that actually covered the buffered chunks.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-11 02:13:15 -07:00
Neil 20ebe858af fix(mobile): bump Android versionCode to 7 for 0.0.27 (#8249)
* fix(mobile): bump Android versionCode to 7 for 0.0.27

Android 0.0.26 shipped with versionCode 6. Align Android on marketing
version 0.0.27 with a higher versionCode so side-loaded upgrades install.

* test(mobile): expect direct cmd syntax for live Windows resume

Resume commands are typed into the host terminal; when that shell is
already cmd, wrap with cmd /d /s /c is wrong. Align the mobile unit
test with shared buildAiVaultResumeShellCommand behavior.
2026-07-11 01:39:41 -07:00
NeilandOrca 4f7fe78921 perf(mobile): stop polling live worktree names (#8051)
* perf(mobile): replace worktree name polling with events

* fix(worktrees): push rename invalidation to remote clients

worktrees:updateMeta deliberately skips the renderer notifier (PR #209),
but paired mobile clients no longer poll for titles, so a manual rename
would never reach them. Emit the remote-only worktreesChanged client
event (with resolved-cache invalidation), gated on displayName so
per-click isUnread writes stay event-free.

Co-authored-by: Orca <help@stably.ai>

* test(worktrees): add missing runtimeStub type member for typecheck

Co-authored-by: Orca <help@stably.ai>

* fix(worktrees): derive rename event repoId with the shared non-throwing parser

getRepoIdFromWorktreeId matches the mobile client's event filter exactly
and cannot throw after the meta write already persisted.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-10 22:44:59 -07:00
Jinjing 5e100914d3 fix(mobile): recover terminal WebView/WebGL/viewport/theme state after iOS resume (#8196)
* fix(mobile): recover terminal state after iOS resume

* Refactor terminal record merge to extract snapshot-reconciliation helper

Split the inline merge logic in mergeTerminalRecordsByCurrentOrder into a
named mergeTerminalSnapshotWithKnownRecord function for clarity, preserving
the existing behavior of keeping the last known theme when a snapshot omits it.
2026-07-10 20:22:16 -07:00
8f6e44ed53 Show agent session history on mobile (#6786)
* Show agent session history on mobile

Bring the desktop "Agent Session History" panel to Orca Mobile as a
per-worktree screen: browse past agent transcript sessions across the
host with scope tabs (Workspace/Project/All), search, grouping, session
cards, and tap-to-read message previews.

The transcript scan previously ran only over Electron IPC, so mobile
could not reach it. Expose it over the runtime RPC protocol mobile
already speaks (aiVault.listSessions) so the scan runs on whichever host
owns the transcripts — correct for local and SSH/remote hosts. Both the
desktop IPC handler and the new RPC method share one cache, so opening
the desktop panel and the mobile screen never double-scan.

The pure filter/group/display logic is lifted into /shared (the renderer
re-exports it) so the standalone mobile package can reuse it. Mobile
narrows scoped tabs client-side by cwd path-prefix because the host scan
treats scope paths as a widening union.

Resume-from-mobile is intentionally a follow-up.

* Fix mobile agent history list rendering and RPC authorization

- Authorize aiVault.listSessions in the mobile RPC allowlist so the
  mobile client's call is not rejected before dispatch (without this the
  screen could never load sessions at runtime).
- Name each SectionList section's rows `data` (the field React Native
  reads) instead of `cards`, fixing a type error and silent empty-section
  rendering.

* Address review feedback on agent session history

- Match quoted repo:/path: search operator values so labels and paths
  with spaces match (e.g. path:"/Users/ada/My Project").
- Hold a scoped tab in loading until the worktree list resolves instead
  of firing an unscoped fetch that briefly shows unrelated host history;
  proceed once loaded even if the worktree is absent (no stuck spinner).
- Clear cached host capabilities on disconnect/host-switch and failed
  status.get so a capability-gated action can't linger for a host that
  doesn't support it.
- Cover the real OrcaRuntimeService codex-home forwarding path and the
  quoted-operator parser with tests.

* Hide redundant mobile current worktree badges

Co-authored-by: Orca <help@stably.ai>

* Resume agent sessions from mobile history (#6969)

Co-authored-by: Orca <help@stably.ai>

* Adapt merged seams to main's lint and reply-sender hardening

Co-authored-by: Orca <help@stably.ai>

* Cap mobile project-scope paths to the aiVault RPC bound

Co-authored-by: Orca <help@stably.ai>

* Share the aiVault scopePaths bound between the RPC schema and mobile

Co-authored-by: Orca <help@stably.ai>

* Guard shared AI Vault inflight cleanup against concurrent key replacement

The extracted cache module's .finally() cleared inflight tracking
unconditionally, dropping the if (inflightKey === key) guard its sibling
outer cache kept: an older scan resolving after a different-key scan
replaced the tracking would null the newer scan's dedup slot, so a
re-request started a duplicate transcript rescan. Mirrors the sibling
guard; the regression test flushes a macrotask so a reverted guard fails
fast on the call count instead of hanging.

Co-authored-by: Orca <help@stably.ai>

* Harden aiVault.listSessions contract and gate mobile header entry on capability

- Clamp scopePaths (64) instead of rejecting, cap limit at 2000, and make
  executionHostId optional so mobile can omit it; restamp per caller.
- Retain successful mobile terminal-create mutation ids for 60s so resume
  retries dedupe after transient socket drops.
- Gate the session-header Agent History action on the aiVault.v1 capability
  (mirrors the host-list action) so old hosts never show a dead-end entry.
- Fix stale contract comments (scopePaths clamp semantics; filters move
  includes quoted repo:/path: operator parsing).

* Add subagent field to session test fixtures after #7423 merge

AiVaultSession.subagent became required on main; the five fixtures added on
this branch predate it. Top-level scanned sessions carry null.

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
2026-07-10 13:48:10 -07:00
Jinjing 3a3e33f14b The --sandbox flag (or terminal sandboxing) in Google Antigravity is a (#8017)
Here is a summary of how the sandbox behaves on your macOS system:

### ⚙️ How it Works
When `--sandbox` is enabled (either via the launch flag or the `enableTerminalSandbox` setting in your `settings.json`), terminal commands run inside a lightweight containment boundary:
- **macOS Native Isolation**: It utilizes macOS's native `sandbox-exec` utility to restrict system calls, network sockets, and directory access.
- **Secure File Boundaries**: File system writes are locked down to designated safe zones (such as your designated workspace or scratch directory). Access to critical system paths, private user data, and external network resources is restricted.

---

### 🛡️ Active Permissions for this Session
In this current session, the permission model is configured as follows:

| Action / Resource | Permission Status | Details / Paths |
| :--- | :--- | :--- |
| **Command Execution** | ✅ **Allowed** | Terminal command execution is enabled. |
| **File Reads (Allowed)** | ✅ **Allowed** | `/scratch`, `/browser_recordings`, `/html_artifacts`, `/knowledge`, `/worktrees`, `/skills`, `/builtin` |
| **File Writes (Allowed)**| ✅ **Allowed** | `/scratch`, `/browser_recordings`, `/html_artifacts`, `/knowledge`, `/worktrees` |
| **Sensitive Files** | ⚠️ **Ask** | `.env`, `.npmrc`, `.vscode`, `.git-credentials`, etc. |
| **Root/App Settings** | 🚫 **Denied** | Direct modifications to `/config` and main `.gemini` configurations |

---

### 🔧 Configuration and Management

* **Persistent Settings**:
  To enable sandboxing by default for all future sessions, configure the `enableTerminalSandbox` setting in your `~/.gemini/antigravity-cli/settings.json`:
  ```json
  {
    "enableTerminalSandbox": true
  }
  ```

* **Dynamic Adjustments**:
  Within an active CLI (`agy`) session, you can run the `/permissions` slash command to view or modify your autonomy and sandboxing levels on the fly.

> [!NOTE]
> Running in sandbox mode provides an excellent balance of autonomy and security, allowing me to execute build commands, run test scripts, and manage project files safely without risk to your primary host environment.

Please let me know if you would like me to set up a new project workspace or run any specific tasks within this session!
2026-07-09 22:01:22 -07:00
Jinjing 44d5ed0439 1.4.131 rc2 release prep (#8020)
* Support WSL Codex settings promotion and harden config write-back

- Enable settings promotion for WSL runtimes using per-distro baselines.
- Create parent directories if missing to prevent promotion ENOENTs.
- Keep restrictive permissions (0600) and follow symlinks on promote.
- Respect CRLF line endings when inserting keys into CRLF config files.
- Skip redundant baseline file writes when settings are unchanged.
- Include the release scan report for the 1.4.131-rc2 prep.

* Refactor sleeping agent wake flow and fetch rate limits via backend

- Background-mount only targeted terminal tabs during passive wake to
  prevent spawning unnecessary PTYs for unvisited tabs.
- Latch edge-triggered wake requests that arrive mid-hibernation and
  track active claims to prevent double-resuming a provider session.
- Query the ChatGPT wham usage backend API directly with fetch for
  rate limits, avoiding launching Codex or WSL login shells.
- Asynchronously probe and serialize WSL auth files with timeouts to
  prevent synchronous I/O from stalling Electron's main process.
- Fix config promotion edge cases such as missing parent directories,
  dangling symlinks, and atomic write permission widening.

* Support WSL dotfile-symlink write-back and lengthen redeem timeout

- Preserve symlinked Codex config on WSL by writing through the
  existing file instead of atomic-rename, since \\wsl$ symlink
  metadata isn't reliably detected and rename would clobber the link.
- Tighten new ~/.codex directory creation to 0700 (holds auth.json).
- Give explicit reset-credit redemption a 30s backend timeout instead
  of the 10s background-poll default, since it's user-triggered.
- Read sleeping-agent session state from the worktree's actual
  execution-host partition instead of always the local one, so the
  headless-wake check works correctly for SSH-hosted worktrees.
- Isolate serve-sim watcher tests from the real $TMPDIR/serve-sim
  state file to avoid leaking unrelated events.
2026-07-09 21:54:22 -07:00
Jinjing 43f639ddda Consolidate mobile source control into a single tabbed hub (#7923)
* Consolidate mobile source control into a single tabbed hub

Unify the changes list, pull request details, and commit history into
a single multi-segment panel. This improves navigation and state sharing
across different lenses of a worktree's source control.

- Add a segmented control to switch between Changes, PR, and History
- Introduce a persistent branch status card with an integrated PR chip
- Redirect standalone PR and history routes to the new unified hub
- Extract reusable UI and logic for the history list and PR summary

* Keep mobile source control tabs mounted to preserve view state

* Keep PR and History segments mounted (using display: 'none' when hidden) to preserve fetch, scroll, and expand states during tab switches.
* Decouple the History list from blocking on Git status loading.
* Support deep linking directly into the history tab of the main panel instead of using a standalone route.
* Enable retrying failed loads by reviving the transport loop if parked.
* Fix PR chip accessibility label and comment check.

* Optimize and integrate mobile PR view within source control hub

- Lazy-load heavy PR comments and descriptions (Phase 2) only when the
  PR tab is active, using fast metadata (Phase 1) for the branch chip.
- Unmount the PR body when inactive to avoid unnecessary comment tree
  re-renders and preserve WebView resources during commit text editing.
- Implement soft-refresh on HEAD advancement to keep the ready UI
  visible while re-fetching checks post-commit.
- Display the "Aborting..." label only when a merge or rebase abort
  is actively in flight.
- Memoize the git history list and skip branch identity RPCs when
  gating the dock icon.

* Improve mobile git views and concurrent rendering safety

- Pass the `origin` parameter through history and PR redirect routes.
- Move source control panel ref updates to `useEffect` to prevent side
  effects during concurrent renders.
- Resolve commit file changes to empty if disconnected to avoid a stuck
  loading spinner.
- Standardize PR sidebar header button styling and accessibility labels.

* Resolve PR repo probe without active branch to avoid forever spinner

Previously, checking if a repository is a GitHub remote required an
active branch. In a detached HEAD or mid-rebase state (where the branch
is null), the probe never resolved, leaving the PR panel on a forever
spinner.

Decouple the repository probe from the branch presence so the panel
can correctly display the "Current branch unavailable" state. Also,
hide the PR status chip when no branch is active to avoid a spinner
on the chip.
2026-07-09 19:23:36 -07:00
Neil ce687221d3 lint(unicorn): enable prefer-number-properties, prefer-array-find, prefer-array-index-of (#7516)
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.

prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
  argument, Number.isNaN does not. Verified every call site already passes a
  number (Number.parseInt results, number-typed fields, Date.getTime()), so the
  conversion is behavior-preserving today and guards against a future non-numeric
  argument silently coercing.

prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
  Drops the intermediate array and short-circuits.

prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).

Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
2026-07-05 23:56:37 -07:00
e8c2b79a93 Fix mobile live input keyboard and Korean IME (#7273)
* feat: 모바일 터미널 한글 미러 스텝 순수 모델 추가

* feat: 미러 델타 순서 보장용 send 체인 추가

* fix: 모바일 터미널 한글 입력을 미러 모델로 전환

* fix: 탭 상태 지연 중 한글 조합 상태 소실 방지

* fix: 미러 가드와 send 체인 리뷰 지적사항 반영

탭 상태 지연으로 활성 탭 타입이 일시적으로 null이 될 때 runMirrorStep의 stale-handle 가드가 조합 중 음절을 버리지 않도록 pending-clear 효과와 동일한 null 허용 패턴 적용. 테스트 하네스가 ref와 prop을 동일 소스에서 파생하도록 결합해 실제 경로의 lag 프레임을 검증. queueTerminalLiveMirrorSend의 previousSend await를 catch로 보호.

* refactor(mobile): drop dead queueTerminalLivePendingFlush orphaned by the mirror model

The mirror model migrated all live-input sends to queueTerminalLiveMirrorSend,
leaving queueTerminalLivePendingFlush referenced only by its own tests. Remove
the dead function and its three tests.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): expose live terminal keyboard target

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): refocus live keyboard after dismissal

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: realitsyourman <wongil@demodev.io>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-03 16:47:59 -07:00
Siddiqui Qamar f0278c116e fix(mobile): preserve terminal input mode on reentry (#7129)
Fixes #6972.\n\nPreserves mobile terminal buffered/live input mode across Android terminal re-entry and session refreshes. Includes follow-up hardening for pre-hydration preference edits and failed storage reads.
2026-07-02 23:22:30 -07:00
Brennan BensonandOrca c06507ee30 Open terminal file links on mobile (#7134)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 18:17:47 -07:00
WONGILandJinjing 4f0a141951 Fix mobile terminal Korean IME composition on Android (#7011)
* Fix Korean IME composition by deferring live terminal preedit

The mobile terminal capture field previously sent and cleared every TextInput change, which can break Hangul composition on Android keyboards. Introduce a small commit model and extracted live-input hook so composed text is flushed deliberately while ASCII remains immediate.

Constraint: React Native TextInput has no portable composition event for this path; the fix uses a bounded commit delay for likely IME text.

Rejected: Native-module IME integration | unnecessary for the confirmed JS dispatch/clear failure and higher maintenance risk.

Confidence: high

Scope-risk: moderate

Directive: Keep terminal.send payload shape and buffered command input unchanged; do not claim physical Samsung Keyboard QA without device evidence.

Tested: cd mobile && pnpm exec vitest run src/terminal/terminal-live-text-commit.test.ts src/terminal/terminal-live-input.test.ts src/terminal/terminal-text-input-normalization.test.ts src/terminal/terminal-keyboard-type.test.ts --reporter=verbose

Tested: cd mobile && pnpm exec tsc --noEmit

Tested: cd mobile && pnpm exec oxlint src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx

Not-tested: Physical Galaxy Fold7/Samsung Keyboard and Android emulator/Gboard QA were unavailable; device probes recorded no attached Android device.

* Preserve pending Korean IME text before mobile accessory controls

Accessory keys share the same pending live-input commit gate as TextInput keypress and submit paths, so control bytes cannot race ahead of composed Hangul.

Constraint: React Native mobile input does not expose portable composition events for Samsung/Gboard IME paths.

Rejected: Let accessory buttons keep sending directly | Direct sends can drop pending Hangul before Tab/Esc/Enter/Backspace reaches the PTY.

Confidence: high

Scope-risk: narrow

Directive: Keep all terminal control-byte paths behind the pending live-input flush/local-edit decision before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --cached --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA is still external-device only.

* Prevent stale IME timer flushes after mobile terminal teardown

Pending live-input timers now clear on hook unmount, and accessory Delete documents why it stays local without trimming pending IME text.

Constraint: React Native TextInput lacks a portable composition lifecycle, so pending IME text is guarded by a bounded timer that must not survive screen teardown.

Rejected: Use clearPendingLiveInputCommit during unmount | it would also touch React state/native props during teardown when only timer/ref cleanup is required.

Confidence: high

Scope-risk: narrow

Directive: Any delayed terminal input commit must have an owner-lifecycle cleanup path before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec vitest run src/terminal/terminal-live-text-commit.test.ts --reporter=verbose; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/use-terminal-live-input-commit.ts; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Use semantic accessory edits for mobile IME commits

Accessory Backspace/Delete now carry semantic local-edit intent from built-in keys instead of inferring intent from raw bytes, and submit handling is reconnected to the pure submit-sequence model.

Constraint: Custom terminal accessory keys may produce the same bytes as built-ins but should still flush pending IME text before sending rather than being silently treated as hidden-input edits.

Rejected: Classify local accessory edits by raw bytes | That couples future custom controls to current built-in byte encodings.

Confidence: high

Scope-risk: narrow

Directive: Keep semantic input intent separate from terminal byte payloads when pending IME text is present.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Respect IME flush failures before control input

Propagate terminal.send success from pending Korean IME text before sending Enter, Tab, or accessory bytes, while keeping custom no-pending accessory bytes on the original direct path.

Constraint: PR #7011 review required follow-up control bytes only after the pending composed text send actually succeeds.

Rejected: Treating send invocation as success | It can still reject or no-op when RPC state changed.

Confidence: high

Scope-risk: narrow

Directive: Keep pending IME flush paths async-success-aware before adding new terminal control inputs.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; targeted no-excuse clean for mobile/src/terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Serialize mobile IME flushes before live controls

Treat terminal.send as successful only when the RPC response is ok and the runtime send result is accepted, then route all live-input control sends through a shared in-flight pending-flush barrier.

Constraint: PR #7011 review found that resolved RPC promises and per-call sequencing were not enough to prove pending Hangul text reached the PTY before follow-up controls.

Rejected: Only awaiting each flush-then-send call | Repeatable accessory keys and no-pending sends can arrive while the first flush is still in flight.

Confidence: high

Scope-risk: moderate

Directive: Keep future mobile terminal control paths behind the pending-flush barrier whenever IME text may be in flight.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Queue current IME snapshots behind active flushes

Drain the pending snapshot captured by a control action after any already-active terminal send, and make accessory commit handling explicit so raw fallback is not encoded as an inverted boolean.

Constraint: Architecture review found the previous single-slot barrier could wait for an older flush while skipping newly pending Hangul text.

Rejected: Reusing the prior in-flight promise as the current flush result | It proves only an older snapshot, not the current pending buffer.

Confidence: high

Scope-risk: narrow

Directive: New mobile terminal control paths must distinguish allow-raw, handled, and suppress-raw outcomes explicitly.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Preserve accessory raw-send terminal targets

Capture the terminal handle at accessory keypress time and suppress raw fallback if the active live terminal changes while waiting for pending IME flushes.

Constraint: Independent review found raw accessory bytes could retarget to a different terminal after an async IME flush barrier.

Rejected: Re-reading activeHandleRef as the send target after await | It can point at a different terminal than the keypress belonged to.

Confidence: high

Scope-risk: narrow

Directive: Raw accessory fallback must use the keypress-time target and revalidate it after any await.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Document accessory flush barrier intent

Make the non-obvious raw accessory wait/suppress behavior explicit so future changes preserve IME-before-control ordering.

Constraint: CodeRabbit requested a why-comment for the send-now accessory branch.

Rejected: Leaving the barrier semantics implicit | The branch can otherwise look like unnecessary async defensive code.

Confidence: high

Scope-risk: narrow

Directive: Keep comments focused on why raw accessory bytes wait behind IME flushes.

Tested: targeted terminal vitest suite; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt check for changed file.

Not-tested: Physical Galaxy Fold7 Samsung keyboard.

* Preserve buffered accessory raw sends

Keep the stale-handle guard focused on the captured active terminal instead of live-input opt-in state, so buffered mode keeps existing accessory key behavior while async live-input waits still cannot retarget to another terminal.

Constraint: Buffered command input behavior must remain unchanged while fixing mobile Korean IME live input ordering.
Rejected: Requiring live-input enabled handles for raw accessory fallback | suppresses valid buffered-mode accessory sends.
Confidence: high
Scope-risk: narrow
Directive: Do not use live-input opt-in state as terminal liveness for raw accessory sends; validate captured target, active terminal tab, connection, and client instead.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep Hangul IME text pending until explicit flush

Avoid timer-driven PTY writes for Hangul candidates so paused Korean composition cannot leak intermediate jamo, while preserving the bounded settle timer for non-Hangul IME text. Also keep disabled live-input accessory fallback behind any existing pending flush barrier.

Constraint: React Native TextInput does not expose a portable composition lifecycle on this mobile surface.
Rejected: Fixed 150ms auto-flush for Hangul | can emit ㅎ or 하 if the user pauses mid-composition.
Confidence: high
Scope-risk: narrow
Directive: Treat Hangul candidates as pending until submit/control/accessory flush; do not reintroduce idle timer commits for Hangul without device-level composition evidence.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Gate dictation toast on accepted live send

Honor the async live-input sender contract so the mobile UI reports dictation insertion only after terminal.send is accepted.

Constraint: sendLiveTerminalInput now returns false for stale, disconnected, oversized, or rejected terminal sends.
Rejected: Toasting immediately after dispatch | reports success for sends that never reached the PTY.
Confidence: high
Scope-risk: narrow
Directive: Treat live-input UI success as terminal.send acceptance, not request dispatch.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check app/h/[hostId]/session/[worktreeId].tsx.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep accessory edits on Hangul pending path

Make accessory local edits reuse the Hangul-aware defer policy so built-in Backspace/Delete cannot reintroduce timer-driven Hangul PTY writes.

Constraint: Hangul IME candidates must remain pending until explicit submit/control/accessory flush.
Rejected: Reusing the non-Hangul 150ms settle timer for accessory local edits | can leak pending Hangul after Backspace/Delete.
Confidence: high
Scope-risk: narrow
Directive: Any future pending-text reschedule must use getTerminalLiveDeferredTextDelayMs instead of a hardcoded timer.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Prove Hangul live-input hook ordering

Add a direct hook-level regression so Android Korean IME fixes are covered at the orchestration boundary, not only by lower-level helpers.

Constraint: React Native mobile TextInput lacks portable composition lifecycle events in this path.

Rejected: Relying only on helper tests | misses hook-level pending flush and submit ordering.

Confidence: high

Scope-risk: narrow

Directive: Keep Hangul candidates pending until an explicit terminal action flushes them.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; no-excuse on terminal modules

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep accessory raw-send tests precise

Remove a duplicate raw-target assertion whose title implied disabled live-input behavior that is covered at the accessory commit boundary instead.

Constraint: Anti-slop cleanup must preserve existing Hangul/accessory behavior and stay within changed terminal tests.

Rejected: Keeping the duplicate disabled-input wording | it tests the same active-terminal predicate as the preceding case.

Confidence: high

Scope-risk: narrow

Directive: Test disabled live-input buffering in the accessory commit layer, not in the raw-target predicate helper.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Explain stale mobile terminal send gates

Document why async IME flush paths re-check terminal/client refs before sending raw bytes or reporting live-send success.

Constraint: CodeRabbit review requested short why comments for non-obvious stale-send safety gates.

Rejected: Leaving the gates undocumented | future edits could remove the stale-target suppression contract.

Confidence: high

Scope-risk: narrow

Directive: Keep async terminal sends guarded by current client, active handle, tab type, and connection state.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Run mobile IME hook tests through effects

Move the Hangul live-input hook regression from server rendering to react-test-renderer so effect cleanup and unmount timer cancellation are exercised.

Constraint: @testing-library/react-native imports React Native's Flow entry under this Vitest setup, so the narrow effect-running renderer is the compatible test surface.

Rejected: Keeping renderToString | it never runs useEffect cleanup and missed the pending timer cleanup path.

Rejected: Adding @testing-library/react-native directly | it failed before tests with React Native Flow syntax under the current Vitest transform.

Confidence: high

Scope-risk: narrow

Directive: Hook-level IME tests must use a renderer that runs effects when asserting pending flush cleanup.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep hook lifecycle tests quiet

Suppress only the react-test-renderer deprecation warning around the effect-running hook harness so real console errors still surface.

Constraint: CodeRabbit flagged React 19 renderer warning noise; @testing-library/react-native remains incompatible with the current Vitest/RN Flow transform path.

Rejected: Global console silencing | it would hide unrelated test failures.

Confidence: high

Scope-risk: narrow

Directive: Keep the renderer warning suppression scoped to this hook harness and pass all other console errors through.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* fix: flush pending mobile IME input before external sends

* fix: guard terminal command finished event dispatch

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-02 02:31:19 -07:00
JinjingandOrca f6db7897d7 fix(mobile): import Buffer from 'buffer', not 'node:buffer'
The oxlint 1.71 upgrade (#6841) autofixed these imports to the node:
protocol, which Metro can't resolve in a React Native bundle, breaking
the Android release build ("Unable to resolve module node:buffer").
Revert to the npm 'buffer' polyfill and disable prefer-node-protocol for
the mobile package so the autofix can't reintroduce the regression.

Co-authored-by: Orca <help@stably.ai>
2026-06-30 01:15:56 -07:00
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00
Brennan BensonandOrca a6f12f6120 Show the git primary action on mobile Source Control (#6659)
Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:30:46 -07:00
Jinjing 7caa582841 Show coding agent icons on mobile terminal tabs (#6792)
* Show coding agent icons on mobile terminal tabs

Move agent title decoration and terminal title parsing utilities
from the desktop renderer to shared code for reuse on mobile.

* Extract agent title stripping and terminal agent resolution to shared
* Implement mobile agent identity resolution and title-cleaning helpers
* Render agent icons on mobile terminal tabs when an agent is active
* Strip leading status glyphs from tab titles when showing an icon

* Suppress PTY resize on resume for mobile-driven terminals

Avoid reasserting the PTY size on resume if desktop resizing is
suppressed. This prevents overriding the intentional drift from
desktop dimensions for parked or mobile-driven terminals.
2026-06-29 16:06:27 -07:00
Jinjing c50391df84 Support dictation and image attachments in mobile live input mode (#6603)
* Support dictation and image attachments in live terminal input mode

- Unify image attachment and voice dictation actions across both live
  and buffered terminal input views using a shared action bar.
- Route completed dictations directly to the active PTY (matching
  live keystroke semantics) when live mode is active, or append
  them to the input field in buffered mode.
- Add live terminal status headers to indicate mic activity and
  image upload progress.
- Include unit tests for the dictation routing logic.

* rm unused file
2026-06-28 01:20:00 -07:00
Jinjing 8a39450b18 refs/heads/handle-mobile-pull-request-issues (#6598)
* feat(mobile): add commit failure recovery panel with AI fix action

- Surfaces a "Commit failed" panel with a one-tap AI fix button when a
  git commit fails in the source control view or PR creation flow
- Detects commit failures specifically during the committing progress
  step and captures staged entries and commit message for context
- Extracts commit failure summary and prompt logic into
  `src/shared/source-control-commit-failure.ts` and PR checks prompt
  into `src/shared/pr-checks-fix-prompt.ts` so both desktop and mobile
  share the same implementations
- Adds auto-find of an available Metro port starting from 8081 and
  extracts expo CLI bootstrap into `mobile-expo-cli.mjs` shared by
  `start-emulator` and a new `start-expo.mjs` wrapper

* Share source-control AI prompts and simplify mobile PR actions

- Extract conflict, check-fixing, and commit-failure prompt builders
  to shared modules for reuse by both desktop and mobile.
- Configure Metro in the mobile package to watch and bundle modules
  from the repository-root shared directory.
- Remove the desktop-style merge method picker from the mobile PR
  actions panel, opting to use repository defaults automatically.
- Refactor mobile hosted review creation and git preparation logic
  into dedicated helper files.
2026-06-28 00:58:17 -07:00
Jinjing 76331ff597 Create pr not working on mobile (#6523)
* Implement automated git preparation workflow for mobile PR creation

Introduce a structured hosted review intent preparation workflow to handle
staging, AI commit message generation, committing, and pushing changes
automatically before displaying the pull request composer on mobile.

- Map creation block reasons to descriptive user-facing validation errors
  (e.g., dirty working tree, default branch, detached head) to match desktop.
- Decouple hosted-review business logic into a dedicated service helper.
- Update source control runner hooks to handle the new preparation flow.

* Refactor mobile PR creation to run intent and open URL directly

Remove MobilePrComposeSheet and the local compose form, moving instead
to a direct PR creation workflow that matches the desktop experience.

- Add runMobileHostedReviewCreateIntent to handle the full prepare,
  push, and create sequence.
- Replace useMobileOpenPrSheetRunner with useMobileCreatePrRunner to
  trigger the creation workflow and directly open the created PR URL.
- Simplify state management by removing showPrSheet, prPrefill, and
  associated local compose sheets.

* Propagate git status and commit state on PR creation failure

Update `MobileHostedReviewCreateIntentOutcome` and the local change
commit helper to include optional `committed` and `status` fields in
their failure results.

This ensures that if PR preparation fails, callers still receive the
current repository status and know if their local changes have already
been committed.

* Add tests for mobile hosted review creation flow

Introduce unit tests for runMobileHostedReviewCreateIntent to verify
different scenarios of creating a hosted review on mobile, including:

- Successful flow including staging, committing, pushing, and creating
- Eligibility block handling (e.g., authentication requirements)
- Error reporting when creation fails after an automatic commit

* Block mobile PR creation on unresolved conflicts and refresh status

Prevent creating a hosted review on mobile when there are unresolved
merge conflicts. Also, return the latest git status on failures and
reload it in the UI to keep the source control screen in sync.

* Prefer fetched PR head SHA over cached status SHA for PR checks

On mobile, a create command can commit before opening the review,
meaning the fetched PR's head SHA is fresher than the route's cached
status SHA. Prioritizing the fetched PR head SHA ensures we fetch checks
for the most up-to-date commit.

* Fix mobile PR creation errors and validate branch presence

- Reject branch matches when the status branch is null or missing to
  prevent PR creation when the branch is lost.
- Display actual PR creation errors in the sidebar instead of silently
  ignoring them on failure.
- Trim leading and trailing whitespace from the base branch reference
  before persisting the worktree link.
2026-06-27 18:34:58 -07:00
Brennan BensonandOrca 5fd9f0c62c Prevent mobile worktree selection from focusing desktop (#6461)
Co-authored-by: Orca <help@stably.ai>
2026-06-26 19:34:33 -07:00
b58478eae0 fix: close mirrored editor tabs on the host so they stop reopening (#5975)
* fix: close mirrored editor tabs on the host so they stop reopening

On web and mobile companions, editor file tabs are mirrored from the
host's runtime-session snapshot, which the host derives from its
authoritative `openFiles`. Closing a mirrored tab only removed it
locally, so the next snapshot re-mirrored the still-open host file and
the tab immediately reopened.

Route the close to the host from the single chokepoint every editor
close funnels through (`store.closeFile`): when the file is mirrored and
a web runtime session is active, send `session.tabs.close` for the host
tab id. The RPC's recorded close-intent suppresses re-mirroring until the
host snapshot catches up, so the local removal is not undone. No-op for
the host's own (non-mirrored) files.

Also fix the desktop `ui:closeSessionTab` handler so a companion-driven
editor close goes through `closeFile` (clears `openFiles`) instead of
`closeUnifiedTab` (tab strip only), which had the same re-mirror bug for
mobile-originated closes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix mirrored editor close import cycle

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-06-22 14:51:43 -07:00
JinHyeok Jeong e253d46e72 Harden mobile session tab sync
Harden mobile session-tab snapshot reconciliation and terminal creation. Rejects stale mobile snapshots, tombstones locally closed tabs until the publisher catches up, rolls back half-created terminal tabs when their surface never appears, and scopes terminal-create idempotency by worktree.
2026-06-22 13:31:10 -07:00
Jinjing 8a4f10b35c Refresh live worktree display name in mobile session (#5934)
Query the `worktree.show` RPC endpoint on screen focus and poll every
3 seconds to retrieve up-to-date worktree metadata. This ensures that
task-generated names and subsequent displayName updates are reflected,
rather than relying solely on the initial route parameter entry hint.
2026-06-20 16:40:34 -07:00