Skip to content

Sync upstream 58293445e (conflicts) - #3

Open
WrittenByAI wants to merge 277 commits into
mainfrom
sync/upstream-58293445e
Open

WrittenByAI wants to merge 277 commits into
mainfrom
sync/upstream-58293445e

Conversation

@WrittenByAI

Copy link
Copy Markdown
Owner

Upstream 58293445e merged into the fork with conflicts.

Unresolved markers remain in: src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json

Fork files upstream also touched: package.json, pnpm-lock.yaml, src/main/github/client.ts, src/main/ipc/github.test.ts, src/main/ipc/github.ts, src/main/runtime/orca-runtime.ts, src/main/runtime/rpc/methods/github.ts, src/main/runtime/runtime-rpc.ts, src/preload/api-types.ts, src/preload/index.ts, src/renderer/src/assets/rich-markdown-editor.css, src/renderer/src/components/editor/EditorContent.tsx, src/renderer/src/components/right-sidebar/SourceControl.tsx, src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json, src/renderer/src/lib/language-detect.ts, src/renderer/src/store/slices/diffComments.test.ts, src/renderer/src/store/slices/diffComments.ts, src/renderer/src/web/web-preload-api.test.ts, src/renderer/src/web/web-preload-api.ts, src/shared/types.ts


Conflict resolution report — upstream merge 58293445e

Merge of origin/main (upstream stablyai/orca) into fork branch
sync/upstream-58293445e. Five conflicted files, all i18n locale JSON.
Every conflict is the same shape and was resolved the same way: a pure
key union.

Summary

All five conflicts sit inside the SourceControl component namespace object
(in en.json it opens at line 9529 and closes just before
"SourceControlAgentActionDialog"). Both branches appended new keys to that
same object at the same spot — immediately after the sub-object that ends with
the "783a808870" ("Close") key — so Git could not tell the two insertions
apart and flagged a textual conflict, even though the changes are semantically
independent.

  • Fork side (HEAD) added two keys for its tldraw canvas feature:

    • "5c951f3e68": "Canvas"
    • "897d7c622d": "Canvas"

    Per CLAUDE.md ("Что добавляет форк: tldraw-канвасы"), the fork turns .tldr
    files into a first-class document type with viewer, canvas notes, and
    canvas→issue flows. These "Canvas" labels are part of that feature. The
    fork intentionally left them untranslated ("Canvas" verbatim) in every
    locale, including es/ja/ko/zh.

  • Upstream side (origin/main) added one key:

    • "97e7124eac" → "Could not refresh Source Control. Try again."
      (localized per file), a new Source Control refresh-failure message.

The two sets of keys are disjoint (5c951f3e68, 897d7c622d, 97e7124eac
are all distinct), and neither key appears elsewhere in the object, so merging
them is a lossless union.

Resolution

For each file I kept all three keys, ordering the fork's two "Canvas"
keys first (each with a trailing comma) followed by upstream's key (no trailing
comma, since it is now the last entry before the object's closing brace). This
preserves both upstream's new string and the fork's tldraw canvas labels.

Resulting block (values differ per locale):

            "5c951f3e68": "Canvas",
            "897d7c622d": "Canvas",
            "97e7124eac": "<upstream Source Control refresh-failure message>"

Per-file upstream value kept:

File "97e7124eac" value
en.json Could not refresh Source Control. Try again.
es.json No se pudo actualizar Source Control. Vuelve a intentarlo.
ja.json Source Control を更新できませんでした。もう一度お試しください。
ko.json Source Control을 새로 고칠 수 없습니다. 다시 시도하세요.
zh.json 无法刷新 Source Control。请重试。

The fork's "5c951f3e68" / "897d7c622d" values are "Canvas" in all five
files (untranslated), matching the fork's pre-merge state.

Verification

  • No conflict markers remain in any of the five files (grep and
    git diff --check both clean).
  • Each of the three keys appears exactly once in en.json — no duplicate-key
    collision was introduced by the union.

What a human must still decide

Nothing blocking. One optional follow-up, not a merge decision:

  • The fork ships the two "Canvas" keys untranslated in es/ja/ko/zh. That is
    the fork's existing choice (the merge preserved it, not introduced it); if the
    fork later wants localized canvas labels, that is a separate task independent
    of this sync.

VERDICT: clean

haohlin and others added 30 commits July 15, 2026 02:38
…blyai#8709)

Reconstruct remote POSIX commands with bounded printf arguments so non-POSIX SSH login shells can forward them without requiring remote base64. Preserve relay and system-SSH stdin, and centralize login-shell flag selection for csh/tcsh compatibility.\n\nValidated against real csh and tcsh OpenSSH targets with built-in and system SSH, including cold relay deployment, stdin upload, PTY I/O, file mutation, and reconnect.
…he sidebar roster (stablyai#8825)

* fix(agent-status): reap finished Claude named agents/teammates from the sidebar roster

stablyai#8522 stopped one-shot subagents from squatting as idle rows, but named
background agents (Workflow/orchestration/ultracode lanes and agent-teams
teammates) still piled up permanent "Idle - <type>" child rows for the rest
of the session — the reported regression (11 idle rows under an
"Orchestration Messages" pane, all idle 3-6h after finishing).

Root cause, confirmed against live hook captures (claude 2.1.210): named
agents get teammate-shaped ids (a<name>-<hex>) AND now appear in Stop's
`background_tasks` as `type: "teammate"` entries whose status stays "running"
forever — even after the agent finished. The old code read that shape as a
"resumable teammate", so SubagentStop only marked it idle and the fold never
reaped it (a present teammate task kept hasTeammateTypedTask true). The rows
never left.

Fix: the roster now tracks only WORKING children.
- SubagentStop removes the child outright (teammate-shaped or not) — it is
  the reliable finish signal; the teammate task's "running" status is not.
- TeammateIdle removes by name as the fallback when a SubagentStop is lost.
- A lead Stop's background_tasks still reaps unlisted children: hyphen-free
  one-shots always, and teammate-shaped rows once a complete inventory shows
  no teammate-typed task at all. A live named agent whose id never appears is
  kept only while a teammate-typed task is still present (the done-gate).
- Hydration drops persisted idle snapshots so a restart can't re-pile them.

Verified live in a dev Electron instance driving a real Claude TUI that spawns
four named background agents: pre-fix the pane resolves to done with four
persistent "Idle - <probe>" rows; post-fix each row disappears the instant its
agent finishes and the roster drains to empty (done, zero child rows).

Tests: roster + row-lifecycle + hook-listener suites rewritten to the
working-only semantics, grounded in the captured 2.1.210 hook stream
(126 passing). Typecheck + oxlint clean.

* fix(agent-status): prune persisted idle Claude children

* fix(agent-status): persist Claude idle-row hydration cleanup

* fix(agent-status): avoid ambiguous teammate idle cleanup

* fix(agent-status): reconcile replacement children at roster cap
…pears (stablyai#8491)

* fix: close Add Project host selector when an SSH credential prompt appears

The SSH password/passphrase dialog (z-50) renders below popovers (z-60),
so a host selector left open during a connect attempt covered the
credential prompt and its input field. Close the selector as soon as a
credential request is queued so the modal prompt is visible and usable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: raise SSH credential prompt above popovers instead of closing the host selector

The credential prompt is a preemptive global modal, but DialogContent's
default z-50 sits below popovers (z-60) and menus (z-70), so any surface
left open while a prompt fires covers the focused password input — the
Add Project host selector was one reproducible case (stablyai#8490).

Raise the prompt's overlay/content to the frontmost tier (!z-[140] /
!z-[150], matching link-routing-preference-dialog) so it renders above
every popover/menu, and drop the host-selector close effect: the picker
can stay open and is available again after cancel for an immediate retry.

---------

Co-authored-by: jonghoonlee <jonghoonlee@inocras.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…TA-1800) (stablyai#8706)

* fix(terminal): kill agent descendant processes on session teardown (STA-1800)

Agent CLIs spawn tool children in detached process groups that PTY
SIGHUP can never reach. Killing an agent session (tab close, retire,
sleep) left those children running as orphans — eight orphaned git
processes burned ~8 cores for up to 11.5h under the agents-running
keep-awake and drained a battery to 8%.

New pty-descendant-termination module: snapshot the ppid tree BEFORE
signalling (a dead root's descendants reparent to pid 1 and become
unfindable), SIGTERM the root group and every descendant, then after a
2s grace SIGKILL survivors gated on a pid+start-time identity re-check
so a recycled pid is never signalled. Snapshot is bounded and never
rejects; failures degrade to today's shell-only kill.

Wired for agent sessions only (plain terminals keep nohup semantics) at
all three POSIX kill sites: local provider shutdown, daemon
TerminalHost immediate kill (the pty:kill path — force-kill bypassed
Session.kill entirely), and daemon Session graceful kill.

Verified live in the built app: an agent pane with a detached-pgid
child; the child survived on the unwired build (three control runs) and
dies within ~5s with the fix. Windows ConPTY and SSH-hosted PTYs keep
the previous foreground-tree contract (documented follow-ups).

* fix(terminal): harden descendant teardown

* fix(terminal): require fresh process snapshots

* fix(terminal): close descendant teardown races

* refactor(terminal): preserve teardown line budget

* fix(terminal): keep descendant teardown fresh and identity-safe

* docs(reliability): record integrated descendant E2E

* fix(terminal): bound descendant teardown work

* fix(terminal): share descendant snapshot indexes

* docs(reliability): record descendant review evidence

* revert: remove speculative descendant hardening
* fix(sidebar): persist manual project order across hosts

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

* fix(sidebar): scope desktop repo reorder by host

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

* fix(web): cover host-scoped repo reorder API

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

---------

Co-authored-by: Orca <help@stably.ai>
…blyai#8862)

* Preserve original markdown style when editing in the rich editor

The rich markdown editor re-serialized the whole document into a canonical
style on the first edit, so a one-character change rewrote every non-canonical
construct (`_x_`->`*x*`, `__x__`->`**x**`, `* item`->`- item`, dropped trailing
newline) and produced a large, unexpected diff.

Reconcile the canonical getMarkdown() output back toward the original source
bytes at every disk-bound serialize site (debounced autosave, flush, Cmd/Ctrl+S)
so untouched regions keep their markup and only the edited region changes. A
divergent-base fuzzy patch (diff-match-patch) carries the user's edit onto the
original style; a safety re-parse requires the result to render-equal what the
editor shows, else it falls back to today's canonical output — so content
semantics can never change. The rich editor stays the default for markdown.

Fixes stablyai#6080

* Bound markdown reconciliation diff work
…ammer a self-hosted forge (stablyai#8858)

* fix(gitea): share and cache the /pulls scan so card refreshes can't hammer a self-hosted forge

Each worktree card resolved its branch by paginating the repo's full
/pulls listing (state=all, up to 5 pages) independently, with a 5s
timeout. Gitea/Forgejo have no head-branch filter, self-hosted Forgejo
takes ~5s per page (it loads reviewer data per PR), and a push event
refreshes every card at once — so one repo turned into hundreds of
near-simultaneous requests whose responses were aborted right as they
completed, and the burst OOM-killed a 512Mi Forgejo pod (stablyai#8807).

- Share one in-flight /pulls scan per repo across concurrent branch
  lookups and cache the result for 30s, so a burst costs a single page
  walk instead of one per worktree.
- Raise the list-scan timeout to 15s so slow-but-successful pages are
  used instead of discarded and retried.
- Invalidate the cached scan after Orca itself creates a PR (and before
  the post-create fallback lookup) so the new PR is seen immediately.

Fixes stablyai#8807

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gitea): don't let an in-flight scan re-cache a listing from before an invalidation

Review feedback: if a PR is created while a card-refresh scan is in
flight, the invalidation cleared the cache but the scan then finished
and re-cached the pre-create listing, hiding the new PR for a full TTL.
Track a per-repo generation: invalidation bumps it (and drops the
in-flight promise), and a scan only writes the cache when its generation
is still current.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gitea): bound PR scan cache and retry failures

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
…t label (STA-1860) (stablyai#8840)

* fix(native-chat): deliver AskUserQuestion answer by option number, not label

Claude Code's AskUserQuestion is an arrow-navigate selector: a bare Enter
commits the HIGHLIGHTED default (the first option), and pasted label text
does not move the highlight. Native chat delivered the answer as the option
LABEL text + Enter, so every non-first pick was silently committed as the
first option (STA-1860 Bug A).

Deliver the answer by each option's stable 1-based number instead (the marker
the selector commits on), paced so each keystroke lands after the selector
step renders. Covers single/multi-select, multi-question, and free-text
('Type something') answers. Verified live against Claude Code 2.1.210.

- native-chat-interactive-prompt.ts: buildAskAnswerKeys / AskAnswerSelection /
  AskAnswerKeyGroup / hasAskAnswer; formatAskAnswer kept (index-based) for the
  non-Claude label path
- native-chat-runtime-send.ts: sendNativeChatAskAnswer (paced keystroke groups)
  replaces the old label-line sender
- card + interactive hook thread structured { indices, other } selections

STA-1860

* fix(native-chat): harden AskUserQuestion answer lifecycle

* fix(native-chat): stop stale paced answer writes

* fix(native-chat): cancel paced answers during commit

Cancel stale AskUserQuestion timers before replacement UI can commit, and localize the multi-step sending state in every shipped locale.
…esses (stablyai#8849)

* fix(ssh): AND lsof selectors so relay reset can't kill unrelated processes

`forceStopRelayForTarget` resolved PIDs with `lsof -t -U "$sock"`.
lsof ORs its selectors by default, so this selects every process holding
ANY unix socket in addition to the socket's owner. On hosts where lsof
cannot match AF_UNIX sockets by path, the path term matches nothing and
the sweep degrades to all unix-socket holders — including systemd --user
— which the TERM+KILL loop then takes down (stablyai#8762).

Add `-a` to AND the unix-socket and path selectors: where lsof supports
path matching the result is exactly the socket's holders (verified: 1
PID with -a vs 97 without on macOS); where it doesn't, the result is
empty and the existing pgrep fallback — scoped to command lines that
reference the relay's per-instance socket name — takes over.

Fixes stablyai#8762

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: add relay reset process-safety coverage

Exercise the generated POSIX reset script with controlled lsof and pgrep commands plus a mocked kill, and keep the selector rationale concise.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix(status-bar): sync usage meters with display mode

* docs(status-bar): correct barColor comment now that fill follows display mode

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…tablyai#8800)

* fix(crash-reporting): coalesce repeated renderer error breadcrumbs

The crash-breadcrumb ring holds only 30 entries, and renderer breadcrumbs
arrived via the plain uncoalesced path. A repeating renderer error — like the
ResizeObserver and SSH-rejection storms in stablyai#8260 (50+ identical events) —
flushes the entire ring in seconds, erasing the pre-crash trail exactly when a
crash report is about to snapshot it.

Route renderer_error and renderer_unhandled_rejection breadcrumbs through the
existing recordCoalescedCrashBreadcrumb, keyed by name plus message prefix
with a 30s window. Repeats collapse into one entry carrying
suppressedSinceLast, so a storm shows up as "error X fired N times" alongside
29 slots of surrounding context instead of 30 identical copies.

Other breadcrumb names keep the plain path; telemetry trace spans are
unchanged. Renderer breadcrumb routing tests move to a focused
crash-reporting-renderer-breadcrumbs.test.ts (the main suite is at the
max-lines ceiling).

* fix(crash-reporting): coalesce durable renderer traces

* fix: harden renderer crash guards

* fix(crash-reporting): preserve distinct error sources
* fix(runtime): retain watcher and PTY teardown ownership

* fix(runtime): restore watchers after interrupted cleanup

* fix(runtime): prevent stale watcher revival

* test(runtime): cover watcher shutdown ownership

* test(daemon): model physical PTY exit

* fix(daemon): keep shutdown terminating when disposal cannot prove exit

A rejecting host.dispose() (unreapable child past its exit deadline) left
the shutdown RPC without its process.nextTick(shutdown) and skipped socket
cleanup in shutdown(), stranding the daemon as an unreachable orphan after
the stale-daemon replacement flow unlinks its socket. Log and continue:
daemon exit reparents the child to init instead of blocking on it.

* fix(runtime): keep local watching alive after an idle-kill deadline miss

An idle child that outlived the exit deadline set shutdownRequested on the
shared desktop supervisor, which has no retire-and-replace path — every
later subscribe rejected supervisor_disposed and the roots were cached
unwatchable, silently ending local file watching for the session. The idle
path owns zero records, so there is no double-watch hazard; the zombie
keeps its capacity reservation until physical exit and the next subscribe
gets a fresh child.

* fix(renderer): resync replayed paired-web file watches

Transparent replay removed the implicit resync the old close-and-rebuild
path provided: a replayed files.watch only reports changes from its own
native setup, so changes during the reconnect gap were silently lost.
Deliver a conservative overflow to consumers once the replayed watch is
ready, matching the overflow-after-interruption contract everywhere else.

* fix(runtime): address teardown review findings

* fix(runtime): retry watches after teardown deadlines

* Fix PTY descendant leaks on forced teardown

* Fix jitter-sensitive terminal lifecycle test
…ces (stablyai#8409)

* fix(worktrees): stop surfacing prunable git worktrees as live workspaces

A worktree still registered in git but whose directory was deleted
(git's `prunable` state) was enumerated as a normal workspace,
producing repeated pty:spawn DaemonProtocolError / fs:readDir ENOENT
loops and a blank pane.

- Parse the `prunable` porcelain field (Git >= 2.36) in both the main
  and relay worktree-list parsers.
- For Git < 2.36 (no `prunable` field), probe each linked worktree
  path for existence on the fallback line-block path, skipping locked
  registrations to mirror git's own prunable rules.
- Omit prunable worktrees from the detected-workspace enumeration only;
  removal/cleanup flows keep seeing them.
- Extend the real-binary compatibility contract with the 2.36
  `prunable` boundary.

Fixes stablyai#8389

Claude-Session: https://claude.ai/code/session_018Rg1Bpq4GGwmz613hq6RSD

* fix(worktrees): pin the prunable/locked porcelain annotations to their real Git 2.31 boundary

The prunable and locked annotations landed in Git 2.31, five releases
before `worktree list -z` (2.36); only -z defines the capability
fallback boundary. Correct the compatibility contract so a future
matrix entry in the 2.31-2.35 range passes, and reword the fallback
comments: on 2.31-2.35 the annotations still parse and the existence
probe is a backstop; only Git <2.31 relies on it outright.

* fix(worktrees): omit prunable registrations from the Space scan

A prunable registration has no directory to size or reclaim, so Space
rendered it as a dead "Missing" row whose checkbox stayed disabled with
no prune/remove affordance (reported on macOS after a reboot cleared
/private/tmp under 16 registrations). Skip prunable entries in the scan,
matching the workspace enumeration; removal flows list worktrees
separately and still see them.

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…witch (stablyai#8760)

* Fix PR checks sticking to a stale linked PR after a terminal branch switch

A worktree's linked PR is a branch-scoped hint, but two refresh paths race
when a terminal switches branches: the git-status identity path clears
branch-scoped review links, while the worktree-listing path rehydrates the
new branch together with the stale persisted link and clears nothing. When
the listing lands first (the common case — worktree listing is much faster
than git status), the identity path sees no branch change and the stale
link survives. Every subsequent refresh then re-fetches the linked PR by
exact number, which ignores the branch, so Checks stays pinned to the old
branch's PR and the Refresh button cannot recover.

Two-part fix:
- Prevention: listing refreshes now route observed branch switches through
  updateWorktreeGitIdentity before merging, so the existing link clear and
  tombstone machinery runs no matter which refresh path wins. Gated on the
  entry still carrying branch-scoped review context so a stale listing row
  cannot roll back a newer branch identity.
- Recovery: PRInfo now carries headRefName, and a fetch that returns the
  linked OPEN PR whose head branch matches neither the current branch, the
  worktree push target, nor the worktree HEAD clears the durable link and
  re-resolves by branch. Wired into both fetchPRForBranch and the
  background refresh coordinator, mirroring the merged-PR divergence clear.
  This also heals wedged workspaces persisted by earlier builds.

* Harden stale PR recovery across refresh races

* Avoid duplicate PR recovery refresh work

* Index linked PR refresh aliases once
* Reopen closed tabs with Cmd+Shift+T, including terminals

Cmd/Ctrl+Shift+T now reopens the most recently closed tab of any kind
(terminal, browser, or editor) in true recency order, walking back
through close history on repeated presses — Chrome/Ghostty semantics.
Terminal tabs previously had no reopen at all, and the shortcut picked
browser-vs-editor by focused surface instead of recency.

Terminal closes capture a snapshot (startupCwd, shellOverride,
customTitle, color) on explicit user closes only; PTY exits, launch
cleanup, onboarding, and floating-panel closes are excluded. Reopen
spawns a fresh shell through the same createTab path as the "+" button.
A per-worktree cross-type kind list (new recently-closed-tabs slice)
orders the three existing per-type stacks; both new maps are purged on
worktree removal and re-keyed on rename with startupCwd prefix remap.
createNewTerminalTab moved to terminal-tab-create.ts to keep
terminal-tab-actions.ts under the max-lines budget.

Requested via Discord feedback; STA-1287.

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

* Update close-tab assertions for captureRecentlyClosed flag

Floating-panel, launch-failure, and stale-agent-tab closes now thread
{ captureRecentlyClosed: false } so system-initiated closes don't pollute
the reopen stack. Update the affected test expectations to match the new
call shape.

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

* fix(reopen): skip local terminal reopen on remote-runtime worktrees

reopenClosedTerminalTab recreated terminals via a raw local createTab
without the isWebRuntimeSessionActive guard every sibling terminal-create
path uses, so Cmd+Shift+T on a remote-runtime-owned worktree (paired-web
or desktop-attached headless-serve) spawned an unbacked local phantom tab
not tied to the host session. Guard at reopen time via the cycle-free
getRuntimeEnvironmentIdForWorktree — importing web-runtime-session into a
store slice creates a store-index init cycle. Remote terminal reopen stays
deferred; the cross-type dispatcher falls through to browser/editor.

Adds a regression test and corrects an inaccurate removal-path comment.

* fix(test): seed runtime host id for remote-reopen guard test

The prior test set settings.activeRuntimeEnvironmentId by spreading the
store's settings, which widened workspaceDir to string|undefined and broke
`pnpm typecheck` (GlobalSettings requires string). Seed the worktree with a
`runtime:` hostId instead — getRuntimeEnvironmentIdForWorktree reads it
directly, so no settings spread is needed.

* Remove unrelated formatter changes

---------

Co-authored-by: Orca <help@stably.ai>
Fixed misleading Korean translation of "on"/"off" and "word wrap"
* docs(readme): fix Korean translation issues

* docs(readme): align agent list with English README across translations

Replace OpenClaude with Pi in Spanish, Japanese, and Chinese READMEs
to match the English README's agent list (Codex, ClaudeCode, OpenCode, Pi).

Portuguese already had the correct list.

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

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix: address pr-bug-scan validated finding from stablyai#6335

Replaced unguarded crypto.randomUUID() with the established createBrowserUuid() fallback in both markup shape-id call sites.

* fix(review): cover markup crypto fallback call sites with regression tests

PR stablyai#8804 replaced two unguarded crypto.randomUUID() calls with
createBrowserUuid(), matching the convention established in stablyai#5471, but
shipped without extending that PR's regression-test pattern to the new
call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown
and useMarkupEditor.commitPendingText under a simulated non-secure-context
crypto global.

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

* fix(review): consolidate annotation bridge token onto createBrowserUuid

BrowserPane had its own inline crypto.randomUUID guard with a
Date.now()+Math.random() fallback, duplicating the getRandomValues-based
fallback already centralized in createBrowserUuid() and used at every
other call site in the renderer.

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

---------

Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* feat(mobile): edit saved host endpoints

* fix(mobile): reject ambiguous numeric host addresses

* fix(mobile): label edit host inputs

* fix(mobile): make host edit save atomic and remove superseded mutators

Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.

Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (stablyai#8789) so this lands
without reverting the mobile terminal restore fix.

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

* Simplify save-race comment and reword host-removed error message

- Trims the redundant comment explaining the savingRef race guard down
  to one line.
- Changes the "no longer saved" load-error copy to "was removed" for
  clearer phrasing, updating the matching test expectation.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
…blyai#8909)

The native-smoke job ran bare `pnpm vitest run <files>` with no --config.
There is no root-level vitest/vite config, so vitest fell back to its
built-in defaults: a 5000ms testTimeout and no Windows worker cap. The
real csc.exe launcher-compile tests (build-windows-cli-launcher and
ssh-remote-cli-launcher) cold-start in 1.4-7.4s on hosted Windows and
tipped over the 5s default, failing the PR check.

Pass --config config/vitest.config.ts like every other vitest invocation
in the repo (pnpm test, pr.yml) so the job gets the shared 30s timeout and
Windows maxWorkers:4 cap (less CPU contention -> lower compile variance).
Positional file args still filter to just the listed files.
…f a dead zone (stablyai#8891)

* fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone

Fixes stablyai#8879

* fix(sidebar): bound header edge drops to measured content
…tablyai#8917)

Move toggle heading commands (toggle-h1..h4) into their own "Toggle headings"
group, separate from plain heading commands, so the slash menu distinguishes
static headings from collapsible toggle headings. Also shorten toggle heading
labels from "Toggle Heading N" to "Toggle HN" for consistency with the plain
heading labels.
…blyai#8918)

The remote CLI installer deleted the legacy orca.cmd bridge before it
confirmed csc.exe existed, so a minimal Windows host without the .NET
compiler lost its existing CLI command and got nothing back.

Move the legacy-shim removal to run only after the compile and the
launcher-existence guard both pass. Missing-compiler and compile-failure
paths now exit non-zero while leaving the existing orca.cmd untouched; a
successful upgrade still clears the unsafe %* bridge (orca.exe already
shadows orca.cmd via PATHEXT). No %* forwarding is restored.

Co-authored-by: Orca <help@stably.ai>
…API (stablyai#8680)

* fix(issues): replace cursor-based pagination with page-number Search API

Problem
=======
Issue pagination (stablyai#8649) had two bugs:
1. Pages 6-16 were unreachable — clicking page 16 highlighted page 5;
   clicking 6/7 did nothing. The old cursor-based approach
   (updated:<CURSOR) broke with Search API's relevance sorting —
   pages after the first few returned no items even though more
   issues existed.
2. Issue numbers appeared out of order on loaded pages (e.g. stablyai#1082
   between stablyai#1308 and stablyai#1499), because client-side sort used
   updatedAt instead of issue number.

Root Cause
==========
The pagination used two separate GitHub API strategies:
- Initial page 0 load: REST endpoints (repos/:owner/:repo/issues,
  repos/:owner/:repo/pulls) sorted by updatedAt
- Subsequent pages: Search API with cursor (updated:<DATE)

These two sources returned items in different orders, causing items
to go missing or appear on wrong pages across page boundaries.

Solution
========
1. Unified on GitHub Search API for all pages — initial load and
   pagination both use search/issues?q=...&page=N, eliminating the
   REST-vs-Search inconsistency.
2. Changed from cursor-based (update:<DATE) to page-number-based
   pagination (page=N), which the Search API supports natively.
3. Switched client-side sort from updatedAt to issue number
   (sortWorkItemsByNumber), matching GitHub's default Issues view.
4. Parallelized page fetches in handleLoadNextPage — clicking page
   16 now fetches all intermediate pages concurrently (~2s) instead
   of sequentially (~30s).
5. Cleaned up dead legacy gh issue list / gh pr list code path,
   extracted quoteForSearch helper, shortened overlong comments.

Files changed: 11 files, +140/-127 lines

Closes stablyai#8649

* chore: remove unrelated merge formatting

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
OrcaWin and others added 30 commits July 20, 2026 00:39
…eld (stablyai#9526)

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Updated README to guide Windows users to the latest RC release,
which includes critical Windows-specific bug fixes not yet in the
stable build. Added prominent notices at the top of the download
section and in the direct-download links.
Keep the callout secondary and clear that the RC has Windows-specific fixes.
…switch (stablyai#9002) (stablyai#9393)

* perf(renderer): stop full durable-state save on every top-level view switch (stablyai#9002)

Persist activeView in a tiny profile-scoped sidecar instead of mutating the monolithic recovery snapshot. Active-view-only updates now bypass the broad UI normalization and durable save scheduler, while a 100ms atomic writer coalesces rapid switches and a synchronous shutdown checkpoint closes the immediate-exit race. Legacy state remains a migration and downgrade fallback.

Coordinate renderer shutdown capture through one guarded checkpoint so workspace sessions and the active-view preference both survive graceful reloads, restarts, and quit cancellation.

Add a persistence-boundary test proving the sidecar stays below 64 bytes while orca-data.json remains byte-for-byte unchanged, plus repeated Windows Electron restart coverage and a path-normalization-safe restart fixture.

* harden active-view sidecar: prototype-safe validator, race-free async swap, independent shutdown flush

- isTopLevelView uses Object.hasOwn so a corrupt sidecar can't smuggle
  inherited keys (constructor/__proto__) through as a valid view.
- writeAsync guards the generation check and rename synchronously (renameSync)
  so a shutdown flushOrThrow can no longer interleave and let a stale async
  rename clobber the freshly-written view.
- shutdown checkpoint flushes the durable store and the active-view sidecar in
  independent try/catch blocks so one store's failure can't skip the other.
  Added regression tests for all three.

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

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
badgen.net rate-limits against GitHub and paints "429" into the badge.
Switch stars to shields.io (repo stargazers_count), use a static MIT
license badge, and drop /stargazers links which now 404 for the public
after GitHub's July 2026 stargazer access restrictions.
Add a native French README and put Chinese, Japanese, and Korean first in the language switcher.
Bump the Windows RC download notice and installer link to the newer RC.
…aemon/pty) (stablyai#9542)

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: main — core runtime, ipc, daemon, pty, providers. 73 files changed, 3206 insertions(+), 10475 deletions(-).

Co-authored-by: Orca <help@stably.ai>
…roviders/…) (stablyai#9543)

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: main — git, source-control, providers & integrations. 40 files changed, 1432 insertions(+), 4473 deletions(-).

Co-authored-by: Orca <help@stably.ai>
…oks/lib) (stablyai#9545)

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: renderer — store, hooks, lib, runtime. 36 files changed, 1673 insertions(+), 5677 deletions(-).

Co-authored-by: Orca <help@stably.ai>
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>
…stablyai#9544)

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: shared, cli, relay, preload. 26 files changed, 1039 insertions(+), 3300 deletions(-).

Co-authored-by: Orca <help@stably.ai>
…blyai#9546)

* refactor(comments): slim verbose comments in renderer components

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: renderer — components. 64 files changed, 2610 insertions(+), 8794 deletions(-).

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

* fix(comments): restore comment markers pinned by source-boundary guard tests

Four *-boundary.test.ts guards assert on exact comment strings in the source
(e.g. '// Why: issue stablyai#4756 keeps project-view actions on the direct'). Slimming
reworded them, breaking the guards. Restored the pinned prefixes while keeping
the comments to one line. Guard suite (16 files/110 tests) green locally.

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

---------

Co-authored-by: Orca <help@stably.ai>
…blyai#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
Stable v1.4.147 includes the Windows fixes, so point users at latest again.
…lyai#9469)

* Stop attaching stale closed PRs/MRs to default-branch checkouts

On the repo default branch, the implicit head-branch PR lookup (state=all)
could attach a historical closed/merged PR whose head ref was the default
branch name and show its wrong diffs and checks (stablyai#9171).

Add a shared default-branch guard: an implicit branch-name match on the
repository's default branch never surfaces a non-open review. Applied at
the branch-lookup choke point of all five provider clients (GitHub,
GitLab, Bitbucket, Azure DevOps, Gitea). Explicitly linked reviews are
exempt; open reviews from the trunk stay visible; resolution is lazy
(zero git calls unless a non-open candidate appears), TTL-cached,
transport-aware (local/WSL/SSH), probe-time-bounded, and fails open.

* Treat stuck-locked GitLab MRs as non-open in the default-branch guard

Three code-review lanes flagged (one reproduced) that 'locked' — normally
a seconds-long merge transition, but a known GitLab wedge state — leaked
past the closed/merged-only check and would re-create the stablyai#9171 symptom
for a stuck-locked MR whose source branch is the trunk.

* Bound default-branch lookup to one refresh budget

* Coalesce default-branch resolution probes
…i#9015)

Move managed agent-hook filesystem work behind one relay RPC so high-latency SSH connects pay one WAN round trip instead of hundreds. Keep installers serial, lock shared account config across relay processes, and fence cancelled connection generations from replacement state.

Co-authored-by: nasagong <zinho2000@gachon.ac.kr>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…blyai#9535)

* fix: never surface coding-agent scratch worktrees in the sidebar

Sub-agent runs (e.g. Claude Code worktree isolation) create throwaway
git worktrees at tool-internal paths like <repo>/.claude/worktrees/.
These showed up as workspace rows for repos whose non-Orca worktree
visibility is 'show', and would otherwise ping the discovery card and
new-worktrees inbox on every fan-out.

Classify them as 'agent-scratch' via a curated path-segment matcher and
suppress them unconditionally: sidebar, discovery card, inbox, dialog
counts, add-handoff reveal trigger, and the metadata fallback. Selected
checkouts and explicitly imported paths still win.

Fixes stablyai#9388

* fix: preserve scratch worktree visibility boundaries

* fix: hide scratch worktrees from linked checkouts

* docs: explain scratch root normalization
…ai#9238)

* feat(skills): ship orca-cli as a first-generation hybrid stub

Convert the installable orca-cli SKILL.md from a full fat guide into a
hybrid discovery stub: a safe CLI resolver, an `orca skills get orca-cli`
pointer, and a bounded read-only fallback for pre-guide binaries. The
version-matched command reference now lives only in the Orca binary
(embedded guide table, served by `orca skills get`), so the distributed
file can no longer drift from the binary that runs the commands.

- generator projects STUB_TOPICS from skill-stubs/<name>.md, reusing the
  guide's own frontmatter so the routing/description surface is unchanged;
  the embedded full guide (bundled-skill-guides.ts) is untouched.
- manifest regenerated: orca-cli releaseRevision 32->33 as an append-only
  snapshot; existing fat installs classify `outdated` and get the targeted
  `npx skills update` nudge (no in-app writes).
- tests: command-guidance assertions repointed to the guide source (their
  home now), plus stub-projection + safety coverage.

Only orca-cli converts; the other skills stay fat. Per
notes/skill-freshness-design.md, the E.3 pointer-compliance spike and the
E.5 RC window remain before any further thinning. allowed-tools is
intentionally not added yet (frontmatter kept byte-identical to the guide).

* fix(skills): distinguish guide lookup failures

* chore(skills): refresh released skill mapping
…tablyai#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 (stablyai#4444, stablyai#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 stablyai#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 (stablyai#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
(stablyai#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 (stablyai#8921) while
preserving the default-home env strip (stablyai#8828) and the narrowed missing-app-server
capability signal (stablyai#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 (stablyai#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 (stablyai#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 (stablyai#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>
…follow-ups) (stablyai#9606)

* fix(terminal): harden macOS login-preflight spawn path (stablyai#9301 follow-ups)

Follow-ups to the stablyai#9301 two-pass review. Re-derived severities: none are
release blockers; these are low-likelihood robustness/observability fixes.

F1 macos-tcc-login-shell: only cache a conclusive PAM verdict. A probe our
own SIGKILL timeout / ETIMEDOUT / maxBuffer killed proves nothing about PAM,
so it no longer sticks the whole process into direct-spawn — it fails open
for that spawn, re-probes next time, and self-heals once PAM answers.

F2 daemon-entry: prepareMacosTccLoginShell returns the outcome; the daemon
emits a structured macos-login-preflight {ok, reason} log on degrade, since
detached daemons destroy stderr and lose the console.warn.

F3 local-pty-provider: an atomic guard before the tracking write coalesces a
concurrent same-session-id spawn onto the winner instead of orphaning an
untracked, unkillable PTY.

F4 daemon-server: preparations carry their clientId; a control-socket close
cancels only that client's pending preps, so a mid-preflight disconnect no
longer leaves a durable orphan daemon PTY.

F7 warm the PAM probe at daemon startup (mirrors warmPwshAvailabilityCache);
note the pipe-vs-PTY probe fidelity limit; test that kill with no pending
prep still surfaces SessionNotFoundError.

Deliberately skipped F5 (self-limits to next reload; a real fix needs
speculative in-flight-generation tracking) and F6 (inherent to legacy
v22 attach; self-heals once daemons are v23).

* fix(terminal): bound preflight recovery work

* fix(terminal): cancel preflight when daemon stream closes
…yai#9489)

A busy PTY delivers up to ~200 terminal frames/s to the phone (the desktop
coalesces output at a 5ms window), and each frame paid a full RN-bridge +
WebView postMessage + WebKit IPC + xterm write + paint pipeline. Coalesce
stream writes in the RN layer: leading-edge immediate delivery keeps
keystroke echo instant, and sustained streams batch into at most ~21
WebView messages/s (48ms trailing window).

Measured (iOS Simulator A/B at ~200 lines/s, only this file flipped):
terminal WebContent CPU 8.0% -> 2.8%, app process 19.3% -> 15.2%,
combined continuous CPU -34%.

Ordering boundaries preserve today's semantics: resize/reflow flush
pending bytes first; init/clear drop superseded pre-snapshot bytes;
reload/content-process-termination/unmount clear the buffer. The
notification-dispatch extraction from TerminalWebView is a verbatim move
forced by the max-lines cap.
* fix(source-control): keep huge change sets responsive

* Fix cancellation and retry handling for capped status

* Harden capped status for conflict-heavy repositories

* Harden capped status recovery and cancellation

* fix(source-control): preserve capped status correctness

* fix(source-control): translate submodule status at render time

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…anges (stablyai#7770)

* Keep Jira linked work items attached when the composer project changes

Repo/project switches in the new-workspace composer cleared every linked
work item except Linear, so starting a workspace from a Jira task and then
picking the actual implementation project silently dropped the ticket link.
Route all three switch paths through a shared isRepoScopedLinkedWorkItem
predicate: GitHub/GitLab sources stay repo-scoped and clear on a switch,
Linear/Jira issues stay attached.

* test(new-workspace): drop dead isLinearLinkedWorkItem, harden preserve-predicate coverage

Follow-up to the Jira-preservation fix (review findings):
- Remove isLinearLinkedWorkItem: no production consumer remains after all three
  composer switch paths route through shouldPreserveWorkspaceSourceOnRepoChange.
- Pin the clear cases in workspace-source.test.ts (GitLab explicit + inferred,
  null) that both delegating paths depend on, not just GitHub.

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

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* feat(codex): drop "Not now" collapse from account-switch notice

The Codex account-switch overlay had a "Not now" button that collapsed
the loud dialog into a corner chip via component-local state. Remove the
button and all of its collapse state: the useState/useEffect, the
CollapsedRestartChip, and the now-dead collapse helpers.

buildCodexRestartNoticeKey still backs the focus effect, so the module is
renamed codex-restart-overlay-collapse.ts -> codex-restart-notice-key.ts
and its tests trimmed to match. The orphaned "Not now" i18n key is pruned
from all locale catalogs.

* test(codex): lock restart notice actions
…-render churn (stablyai#9615)

The UsagePercentageDisplayChangeNotice callout re-measures its fixed anchor
on every ResizeObserver/resize delivery and calls setAnchorPosition with a
fresh object each time. Equal-geometry deliveries (the common case) still
churned a re-render apiece because the object identity changed.

Bail when bottom/left are unchanged so React short-circuits before commit.
Measured: 20 identical-geometry deliveries → 20 re-renders before, 1 after.

This is defensive robustness/perf hardening for the status-bar subtree that
surfaced in crash f75ed81b (React stablyai#185 in overlay.status-bar). It is NOT a
proven fix for that crash — the reproduced stablyai#185 loop lives in radix Popper
internals and is not unit-reproducible; this removes one churn source feeding
that subtree.
Resolved by fork-sync agent; see report.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.