mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
8d5eacecb484ff2015b156409e7c781efd39cdee
371
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8d5eacecb4 |
Revert "fix(terminal): clear the render model on the plain-refocus repaint path"
This reverts commit
|
||
|
|
0f7ec4458d |
fix(terminal): clear the render model on the plain-refocus repaint path
`schedulePaneRevealPresent` — the atlas-preserving path a plain window refocus takes — only called `terminal.refresh()`. xterm's renderers are diff-based: `_updateModel` early-continues on any cell whose code/fg/bg/ext still match the cached model, so a refresh repaints nothing for a pane whose buffer never changed. When an occluded window loses its canvas contents while that model stays populated, the refresh skips exactly the cells that went stale and the pane keeps compositing pre-hide pixels — until a window resize reallocates the model, which is the repair users find by hand. Clear the model first (`RenderService.clear()` → renderer `clear()` → `_clearModel(true)`) so the refresh becomes a guaranteed full repaint. That drops cached cells and glyph vertices but NOT the texture atlas, which is shared by every same-config terminal and whose mid-stream wipe re-arms xterm's page-merge garble race (xterm.js #4480) — the reason this path is atlas-preserving in the first place. Also covers the DOM-renderer fallback in `resetWebglTextureAtlas`: `clearTextureAtlas()` is what invalidated the model on the WebGL path, so a pane without an addon had nothing invalidate it and hit the same skip. Scope note: the e2e spec guards buffer/geometry convergence across the hide/reveal boundaries and adds idle-agent and headful desktop-hide cases, but it cannot observe a stale canvas — both oracles built for that (canvas-vs-buffer ink sampling, screenshot-vs-forced-repaint) were proven blind by injecting the defect, and the spec header documents why. The unit tests pin the ordering and the atlas-preservation invariant. Refs STA-2694. Co-authored-by: Orca <help@stably.ai> |
||
|
|
ed1eaf55f1 |
fix(terminal): release an abandoned synchronized-output frame on reveal
Alt-screen agent TUIs (OpenCode/OpenTUI, Codex, grok) bracket every repaint in `?2026h … ?2026l`. Hiding a pane mid-bracket — which a worktree switch or cold-park lands on routinely, since these brackets are written many times a second — leaves xterm's `decPrivateModes.synchronizedOutput` latched. RenderService.refreshRows checks that latch *before* rendering, so while it holds, every repaint Orca owns is a no-op: the forced render-pause repaint, the plain `refresh()` fallback, and the shared glyph-atlas rebuild all render zero rows while the xterm buffer is perfectly correct. Release the latch at the two reveal repaint entry points so those repaints actually paint. Also adds an OpenCode-shaped alt-screen e2e fixture and spec. The existing inline-TUI convergence spec covers the normal-buffer shape (live block glued to the bottom, history scrolling into scrollback); this covers the full-screen alternate-buffer shape, where nothing scrolls and so no row ever self-heals through the scroll path. Scope note: xterm arms a 1s watchdog that clears this latch on its own, so this closes a bounded window rather than the whole STA-2694 report. The e2e spec passes with and without the production change for that reason; the unit tests are what pin the behavior. Refs STA-2694. |
||
|
|
c06bf64b48 |
test(e2e): make Codex typing-latency harness measure real echo latency (#10660)
* test(e2e): make Codex typing-latency harness measure real echo latency The local Codex typing-latency spec produced meaningless numbers. Four defects, all fixed here: 1. False-positive readiness. `/Ask Codex|OpenAI/i` matched "OpenAI's command-line coding agent" on the *sign-in* screen, so the test went "ready" against a login prompt and measured typing into a non-composer. Now gated on the composer status bar (`/Context \d+% used/i`), which only the live composer draws. Banner text is unusable: the serialized buffer interleaves ANSI escapes through those glyphs. 2. Missing auth. The E2E profile runs an isolated HOME with a managed CODEX_HOME that has no auth.json, guaranteeing the sign-in screen. The launch now pins the real ~/.codex, and skips with a clear message when auth.json is absent instead of silently measuring a login screen. 3. Measurement overhead swamped the signal. Per-key latency was measured by polling getTerminalContent() every 5ms, so each sample was real echo latency + full buffer serialize + CDP round-trip + poll granularity. Measurement now happens entirely in-renderer: an in-page hook stamps performance.now() on keydown (window capture phase, before xterm forwards to the PTY) and again in xterm's onWriteParsed once the glyph is in the viewport, with onRender giving a separate time-to-paint. Samples are drained in one page.evaluate after typing ends — zero CDP round-trips inside the measured window. 4. Thresholds were meaningless (median<150ms / worst<500ms). Replaced with p50<35 / p95<60 / max<120, based on 10 local runs. Also: 60 keystrokes instead of 24 with the first 10 discarded as warmup, p50/p95/max instead of a lone median, lowercase-only input so the slash and file-mention popups can't perturb later keys, an assertion that no keystroke went unechoed, and a terminal dump on readiness failure. Measured (10 local runs, headless, real Codex 0.145.0): echo (key->parse) p50 21.6-22.6ms, p95 23.2-41.5ms, max 23.4-58.7ms paint (key->render) p50 25.5-32.9ms, p95 34.3-49.7ms A plain-shell control on the same probe reads p50 2.0ms / p95 3.0ms, confirming the ~22ms is Codex composer redraw cost rather than a harness floor — the old harness reported ~29-30ms for everything. Co-authored-by: Orca <help@stably.ai> * test(e2e): widen Codex latency tail budgets and assert terminal focus Follow-up calibration over ~20 local runs: the per-key distribution is unimodal at p50 21.3-22.7ms with rare isolated spikes to ~90-125ms that are not a steady-state shift. Tail budgets move to p95<80 / max<150 so only a sustained regression fails; p50<35 still gates the steady state. Also assert the xterm helper textarea actually took focus. One run typed all 60 keys with only 5 parse events because focus was lost, which previously surfaced as an opaque sample-count mismatch. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
ca70be8318 |
Add {linkedIssue} template variable for commit and PR generation (#10640)
* feat(source-control-ai): add {linkedIssue} recipe variable for commit and PR prompts
Custom commit-message and pull-request recipes can now reference the GitHub
issue linked to the workspace, so a template like "Fixes #{linkedIssue}" lands
the closing trailer without the user retyping the number.
- register `linkedIssue` on the commitMessage and pullRequest actions only,
with the VARIABLE_INFO entry the chip hover card requires
- substitute unconditionally via `formatLinkedIssueTemplateValue` (empty string
when nothing resolves) so the token never survives into a prompt; enrich the
draft context conditionally via `withLinkedIssueDraftContext` so unlinked
workspaces keep their existing context shape
- attach at the 7 call boundaries (runtime commit x2, runtime PR shared, IPC
commit x2, IPC PR x2); the pure git gather stays pure
- validate the renderer-supplied worktreeId against the request path and repoId
before any meta read, comparing SSH paths as raw strings so a Windows host
cannot rewrite a remote POSIX path
- built-in prompts are unchanged; no GitLab dual-read and no default trailer
* fix(source-control-ai): resolve {linkedIssue} adversarial review findings
Addresses 13 of the 14 findings from the {linkedIssue} code review
(6 minor, 8 nit, 0 critical, 0 major); Issue 5 (GitLab provider naming)
is deferred to design Open Question 3 as product expansion.
Behavior:
- Dialog previews the workspace's real linked issue instead of the
synthetic 123, in both the chip hover card and the plan preview, so an
unlinked workspace previews the `Fixes #` it will actually generate.
Settings dry-runs stay fully synthetic.
- Reject non-positive, fractional and unsafe-integer issue numbers at the
IPC resolver via a shared isLinkedIssueNumber predicate, so corrupt meta
never reaches a draft context (previously -7 rendered `Fixes #-7` and
1e21 rendered `Fixes #1e+21`).
- Fail closed on an empty-string repoId instead of skipping the cross-check.
Structure:
- Split the variable registry into source-control-ai-action-variables.ts
and re-export it, restoring max-lines headroom with no consumer churn
and no lint disable.
- Constrain withLinkedIssueDraftContext to contexts declaring linkedIssue.
- Move the misplaced shared imports into their import group.
Docs and tests:
- Document that the IPC id/path validator guards relay/CLI/future callers,
not the renderer (whose path is id-derived), and rename the three tests
that read as proof of a protection that cannot fire.
- Add PR-side coverage that was missing: three git:generatePullRequestFields
handler tests, a built-in PR prompt no-leak guard, and the runtime PR
unlinked case.
- Replace the coincidental '42' assertion with a fixture-unique sentinel.
- Type the runtime worktree fixture with satisfies, which surfaced and
fixed pre-existing drift in its git sub-object.
- Add an e2e case covering the preload -> main -> meta -> template chain.
Co-authored-by: Orca <help@stably.ai>
* fix(source-control-ai): resolve {linkedIssue} adversarial re-review findings
Addresses all 8 findings from the {linkedIssue} code re-review
(2 minor, 6 nit, 0 critical, 0 major); none deferred.
Behavior:
- Revert the variableOverrides parameter on planSourceControlTextGeneration.
Its result is a Save/Generate gate, not a preview, and the recipe it
validates is saved repo- or globally scoped -- so rendering it against the
active workspace disabled both buttons with "Command input is empty." for a
{linkedIssue}-only template on any unlinked workspace, blocking a global
settings write. Validation is synthetic again; chip previews are unchanged.
- Make the chip hover card additive instead of either/or. A supplied preview
now appends a "This workspace" sample below the description and Example
rather than replacing them, so the GitLab-empty and dangling `Fixes #`
warning survives on the two dialogs where recipes are actually authored.
basePrompt keeps its preview-only shape, where the preview is the content.
Structure:
- Drop the registry re-export from source-control-ai-actions.ts and move the
last two consumers onto source-control-ai-action-variables, so one import
path per symbol keeps a grep of the registry's consumers complete.
- Split the registry/helper suites into source-control-ai-action-variables.test.ts
so each test file mirrors its module.
Tests:
- Cover the Save/Generate gate at the canRunGeneration level for a bare
{linkedIssue} recipe on linked and unlinked workspaces, with a negative
control proving the buttons can still be disabled.
- Cover the chip hover card directly; the dialog tests mock it away.
- Guard the PR mismatched-id test with toHaveLength(1) so it cannot pass
vacuously on an unrelated early return.
- Add an unlinked-workspace e2e case (saw-issue:empty), which is what
distinguishes a real resolver from one that always returns a number.
Spec now runs green: 3 passed.
- Rename the dialog test that claimed a synthetic-fallback assertion it did
not make, and route its renders through one shared helper.
Docs are worktree-local (.gitignore:84 ignores docs/**): the design doc's
plan-preview and chip-surface claims, the manual QA rows, and both reviews'
statements about pre-existing PR-handler tests are corrected there.
* fix(source-control-ai): make the {linkedIssue} e2e guard and dialog test falsifiable
The e2e unlinked case extracted the echoed issue with `ORCA_E2E_ISSUE=(\d*)`,
which matches zero digits in front of an unexpanded `{linkedIssue}` and reported
it as `empty` — so the case that exists to catch a literal token surviving into
a prompt passed on exactly that regression. Capture the whole line instead: a
literal now arrives as `saw-issue:{linkedIssue}` and fails, verified by dropping
the substitution key for unlinked contexts and watching the case go red.
Also drop the inert `not.toContain('Command input is empty.')` assertion — that
copy is click-driven `generationError` state and this suite renders statically,
so it could never fail; the claim it reached for is carried by the plan test.
Rename two plan tests off the "plan preview" framing the design now rejects.
Local review artifacts (design doc, implementation notes, final review) were
swept to match the tree in the same pass; they are gitignored here.
* Resolve {linkedIssue} from live metadata, not cache
Resolved worktrees are cached for a second, causing commit and PR
generation to use stale linked-issue state. Hosts now implement
getWorktreeLinkedIssue to provide fresh issue metadata by worktree id,
with proper fallback for unlinked workspaces. Updates both commit
message and PR field generation paths; includes integration and e2e
coverage.
* Keep cached linkedIssue when metadata is unavailable
Return undefined from getWorktreeLinkedIssue when live metadata cannot be read
(store not ready), distinguishing it from null (unlinked). The caller now falls
back to the cached worktree value instead of treating unavailable as unlinked.
Also extract the linked-issue echo generator as a shared e2e test helper.
---------
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
468f5b77b5 |
test(e2e): fix two stale E2E specs failing on main (runtime host seeding, alt-screen snapshot) (#10614)
* test(e2e): register a real runtime host and publish the alt-screen frame as its snapshot Two long-running scheduled-E2E failures on main were stale test setup, not product defects. `onboarding.spec.ts:420` seeded the Active Server by faking a runtime environment in the renderer store and writing `activeRuntimeEnvironmentId` through the generic `settings:set` IPC. Since #10011 that setter strips the key, and the dedicated `settings:set-active-runtime-environment-preference` handler resolves the id against the main-process environment store — CI logged `RuntimeEnvironmentStoreError: Unknown environment: env-e2e` from `runtimeEnvironments:subscribe`/`:call` alongside the assertion failure. Register the host for real via `runtimeEnvironments:addFromPairingCode` (offline; no live server) and write the preference through its own channel. `terminal-tab-switch-visual-restore.spec.ts:604` wrote alt-screen frames straight into the renderer's xterm, so those bytes never transited the PTY and main's model could not contain them. On cycle 0 the freshly spawned shell still has queued startup output, so hiding the pane makes main's hidden-delivery gate drop bytes and latch a reveal restore, which repaints main's snapshot over the fabricated frame; later cycles run against an idle shell and survive. Arm the existing `setHiddenSnapshotOverride` seam (already used by sibling tests in this file) with the same frame so the live-write and restore paths render identically, and keep the `markerPresent` assertion. Co-authored-by: Orca <help@stably.ai> * test(e2e): keep the alt-screen restore path observable Numbering the snapshot frame one higher than the live-written frame keeps the marker assertion path-agnostic while leaving the frame number on screen as the signal for which path painted. An unrecognised frame now fails, and the per-cycle path is recorded rather than asserted because which cycles latch a restore is load-dependent. Frame authoring and readback move to a helper module; the additions crossed the spec's max-lines cap. Co-authored-by: Orca <help@stably.ai> * Escape regex metacharacters in alt-screen marker pattern Marker is treated as a literal string, so escape regex metacharacters to prevent them from being interpreted as regex syntax. --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
e564603d54 |
Observe daemon health failures and fix e2e test races (#10595)
- Add E2E_FORCE_DAEMON_HEALTH_UNREACHABLE env to simulate failed health checks - Log when replacing a failed daemon, but stay silent on cold starts - Simplify daemon-slow-health-check-preservation: use forced-unreachable health instead of SIGSTOP/SIGCONT - Add --no-sandbox flag to electron launch args for Ubuntu CI - Support extraEnv option in restart session launches |
||
|
|
01929fde40 |
fix(ssh): open external host images from terminal links (#10323)
* fix(ssh): open external host images from terminal links * fix(ssh): keep external file ownership host-scoped * fix(ssh): reject blank external file owners --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
65f245f074 |
fix(editor): focus Explorer-opened Markdown for find (#8083)
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
fde063618b |
fix(remote): create paired agent sessions without host focus (#10193)
* fix(remote): create paired agent sessions without host focus * test(remote): assert structured resume request * test(remote): preserve provider-separated resume coverage * test(remote): assert paired agent focus authority * fix(remote): separate agent host creation from viewer focus * test(remote): harden agent-session authority validation * test(remote): validate retired pane identity --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
7ab601487c |
fix(remote): don't classify a stale/gone remote handle as agent completion (#9263)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
8cb0b8dcde | test(runtime): cover remote terminal retirement across paired viewers (#10053) | ||
|
|
5b7fdd7ef5 |
Capture daemon startup logs before window ready in e2e test (#10040)
The daemon health-check guard logs during main-process startup, which can complete before the renderer window resolves. Moved stderr listening to the launch options so early logs aren't missed. Also made the assertion regex pattern-based instead of exact-string matching to tolerate benign log rewording, and added a check that the replace path stayed off. |
||
|
|
41751dd90d | fix(runtime): route HUB-owned SSH worktrees through owning runtime (#9994) | ||
|
|
a356b9d5c2 |
fix(worktrees): show CLI-created local worktrees in the sidebar while a remote runtime is active (#6628)
* fix(worktrees): refresh local worktrees in the sidebar while a remote runtime is active When a remote runtime is active, a local `worktrees:changed` event for an unbound repo was dropped by the renderer guard in useIpcEvents. Worktrees created outside Orca for that repo (e.g. `orca worktree create` from a CLI or automation flow) therefore stayed invisible in the sidebar until an app restart, even though their sessions were already running. The guard existed because an unbound repo's list fetch routes to the active runtime (settingsForKnownRepoOwner's unbound fall-through), so refreshing with local worktree ids could query — and purge against — the remote host. Instead of dropping the event, pin the refresh to the local host (forceLocalOwner): fetch the worktree list against the local owner and merge additively. The merge is host-scoped and the deletion-purge is skipped on this path, so it only ever adds local-host worktrees and never overwrites the active runtime's worktree state. A genuinely-removed local worktree is reclaimed by the next unguarded full refresh. * test(e2e): regression — CLI-created worktree visible while a remote runtime is active Drives the real `orca worktree create` path: the CLI RuntimeClient calls `worktree.create` over the app's socket, registering a managed worktree and firing the `worktrees:changed` IPC the renderer listens for. Stages a remote runtime as active by injecting `activeRuntimeEnvironmentId` into the renderer store, so no real remote host is needed. Fails on the prior behavior (the worktree never appears while a runtime is active) and passes with this fix. * fix(worktrees): pin local lineage refresh during runtime activity Co-authored-by: Orca <help@stably.ai> * review: trim comments to house style, normalize queue coalescing to booleans * review: sweep rename-grace expiry before early returns in worktrees:changed handler * review: document accepted workspace-space gap, drop imprecise 'additive' wording * fix(worktrees): route duplicate local repo events locally * fix(worktrees): tag local worktree events at origin, gate purge skip on runtime overlap * test: pin origin-based forceLocalOwner with a no-runtime local event assertion --------- Co-authored-by: brennanb2025 <brennankbenson@gmail.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
0d82cc3dbb | Fix legacy worktree lineage projection after stable updates (#9913) | ||
|
|
9bdb8204e1 |
test(e2e): verify Claude is prefilled with issue URL on start (#9980)
* test(e2e): verify Claude is prefilled with issue URL on start Regression test for #6613: when starting a workspace from a newly created GitHub issue, ensure the issue URL is passed to Claude via `--prefill` and `--dangerously-skip-permissions` flags. This prevents context loss after issue creation. * test(e2e): fix GitHub-issue-start prefill test flakiness - Reorder mock API handlers to ensure `/labels` and `/assignees` paths match before the specific issue endpoint - Replace regex heading matcher with exact string for more reliable assertions - Refactor terminal content polling to capture text once and reuse in subsequent assertions |
||
|
|
300ee19950 | fix(terminal): make remote workspace sleep converge (#9874) | ||
|
|
b232df732b | fix(terminal): make remote agent sessions host-authoritative (#9687) | ||
|
|
f9f3cd2fbe | fix(terminal): prevent reconnect from killing live daemon sessions (#9804) | ||
|
|
05c32c4757 | fix(runtime): isolate navigation across paired clients (#9664) | ||
|
|
085fc6ad89 |
Keep embedded browser commands on the registered Orca target (#9633)
* fix(browser): keep embedded commands on registered target * fix(browser): cover internal helper ownership paths * test(browser): cover stale helper routing in Electron * fix(browser): preserve direct command result semantics --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
9680b22e60 |
fix(terminal): heal stale PTY grids on window wake (#9626)
* fix(terminal): heal stale PTY grids on window wake * fix(terminal): bound window-wake size recovery |
||
|
|
57865139d9 |
Fix 6 e2e test flakes: worktree teardown, window focus, and terminal cleanup (#9625)
* Fix 6 e2e flakes: worktree teardown, window focus, and terminal cleanup Tolerate selector_not_found during destructive worktree removal, retry transient window reopens with bounded attempts, collapse dead split leaves in parked tabs, stabilize tab-close persistence and diff-scroll assertions, and forward app logs for visibility into failures. Each fix addresses a root cause—not a test workaround —and includes a verification pass confirming no regressions. Fixes identified in shard-10 CI failure (oracle #29710852934). * Remove E2E fix verification document This smoke-evidence file was used to track adversarial verification of the 6 e2e fixes during development. Now that the fixes are committed and tested, the temporary verification notes can be removed. * Unify win32 window activation for sync and retry paths Extract activateWindow to prevent drift between sync and async paths on win32 reinforcement (moveTop, pulseAlwaysOnTop, retry focus). Adds test coverage for window recovery on retry. |
||
|
|
e218dfbf8b |
fix:Keep Jira linked work items attached when the composer project changes (#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> |
||
|
|
e109e78ebf |
fix(source-control): keep huge change sets responsive (#9477)
* 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> |
||
|
|
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> |
||
|
|
7386ef2857 |
perf(renderer): stop full durable-state save on every top-level view switch (#9002) (#9393)
* perf(renderer): stop full durable-state save on every top-level view switch (#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> |
||
|
|
b3b5031883 |
test(e2e): deliver node burst via temp file so PowerShell quoting can't break OSC title (#8521) (#9391)
The e2e terminal helpers typed `node -e ${JSON.stringify(script)}` into the
PTY. JSON.stringify emits POSIX-style \" escapes, which PowerShell does not
honor: it re-splits the program on `;` inside the payload, node throws
'Expected unicode escape' before emitting a single byte, and the OSC-title
assertions fail deterministically on Windows (default shell = PowerShell).
Stage the program in a unique temp .cjs file instead and send
`node "<forward-slash path>"` — no shell ever parses the program source, so
delivery is byte-identical on PowerShell, cmd, bash, and zsh (verified with
hexdumps: 07 1b 5d 30 3b ... 07 matches exactly across shells). Forward
slashes keep the quoted path valid in both POSIX shells and PowerShell; the
Codex startup marker moves from argv into the script body so no argument
quoting remains. macOS/Linux payload bytes are unchanged — only the delivery
mechanism differs. Test infrastructure only; no product code touched.
|
||
|
|
d628c9300e |
fix(terminal): harden revealed inline-TUI convergence (#9503)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
7adda25b0a |
fix(daemon): retire empty current-generation daemons (#9277)
* fix(daemon): retire empty current-generation daemons Co-authored-by: Orca <help@stably.ai> * fix(daemon): retire empty daemons on disconnect Co-authored-by: Orca <help@stably.ai> * test(daemon): authenticate Windows lifecycle harness Co-authored-by: Orca <help@stably.ai> * test(daemon): assert remaining shutdown budget Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
0b71f3bfba |
test(e2e): prove the terminal daemon survives a main-process crash on Windows (#7742) (#9311)
* test(e2e): prove the terminal daemon survives a main-process crash on Windows (#7742) Add a win-crash-survival e2e harness (sibling to win-update-e2e) that force-kills ONLY the packaged app's real Electron main (resolved via app.evaluate -> process.pid, /F no /T) and asserts the detached orca-terminal-daemon.exe plus its ConPTY shell survive with no pwsh 0xE9 FailFast, then that a relaunch re-adopts the SAME daemon and the reattached UI binds to the SAME survivor shell (proved via a per-shell env sentinel read back through the restored terminal). This guards the #7742 fix (standalone relocated daemon that outlives main death) against regression. A directional `--expect orphaned` profile fails on a fixed build, keeping the survival assertions honest. Windows-only; reuses win-update-e2e app-driver/daemon-process modules. * test(e2e): harden Windows crash-survival proof * test(ci): keep crash survival gate durable * test(e2e): tolerate restart hydration navigation * test(e2e): prove exact shell input after crash * perf(ci): avoid crash harness installer rebuilds * test(ci): harden crash survival evidence and cost * test(e2e): fail closed on authoritative crash target * test(e2e): fail closed on crash liveness evidence --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
d1532956fd |
fix(keybindings): AltGr-safe default for Add Review Note (#9257)
* fix(keybindings): use AltGr-safe default for Add Review Note The editor.addReviewNote default was Mod+Alt+N, which resolves to Ctrl+Alt+N (AltGr) on Windows/Linux. On diacritic layouts AltGr+N types a real character (e.g. Polish n-acute), so the editor-scope chord hijacked normal typing. Switch the default to Mod+Shift+A, which is AltGr-safe and keeps a mnemonic (A for annotate). * test(keybindings): cover Add Review Note chord end to end |
||
|
|
3f335efdb9 |
feat(agents): pi session resume support (#8876)
* feat(agents): pi session resume support
* fix(pi): require persisted session files for resume
* test(sleeping-agent): use non-resumable sentinel in malformed-record fixture
The 'drops malformed sleeping agent resume records' test used agent:'pi' as
its example of an unknown/non-resumable agent, expecting the record to be
dropped. This PR added 'pi' to RESUMABLE_TUI_AGENTS, making that fixture
valid and retained, so the toBeUndefined assertion broke. Switch the
malformed-case fixture to a genuinely non-resumable sentinel
('definitely-not-an-agent') so the drop-malformed path is still exercised;
no other assertions changed.
* Add durable resume identity for Pi sessions without fabricating turn sta
Pi's `session_start` hook now carries the session file needed to resume
a sleeping pane, but until now Orca either discarded it or treated it
as a fake status transition. Thread a `providerSessionOnly` envelope
through the hook listener, relay, main-process server, and renderer
store so resume identity (and its session-file-scoped equality/claim
key) can be persisted and replayed without emitting prompt telemetry
or a visible working/done row.
* Add durable resume identity for completed Pi sessions
Pi's agent_end hook marks a turn done, but the underlying TUI session
stays alive and resumable. Previously a `done` status wiped sleeping
records and launch config as if the session ended, so hibernation,
manual worktree sleep, and quit-capture all lost Pi's resume identity.
- Track a "live recovery" record for done-but-still-resumable Pi
sessions, exempting it from the usual done-state cleanup paths in
agent-status.ts and agent-hibernation-planner.ts
- Gate providerSessionOnly rows and sleeping-agent schema records on
actual resumability (getAgentResumeArgv) instead of trusting the
presence of a provider session
- Wait for Pi to persist its session file before advertising resume
metadata, and treat `/reload` as a non-terminal event so it doesn't
clobber visible status
- Extend SSH relay envelopes to carry providerSessionOnly so remote
hosts get the same behavior
* Add explicit periodic/quit mode to sleeping-agent session capture
Split captureAllSleepingAgentSessions into 'periodic' and 'quit' modes
so a background checkpoint can no longer downgrade a confirmed-quit
record or promote a completed Pi session without an authoritative
transcript path. Updates all call sites and tests accordingly.
* Use normalizeAgentStatusPayload for default pi status
Remove unnecessary JSON.stringify wrapper and call the appropriate normalization function directly.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
|
||
|
|
803a442868 |
test(e2e): harden terminal restart regressions on Windows (#9064)
* test(e2e): harden Windows terminal restart regressions * test(e2e): cover renderer replacement rejection * test(e2e): tolerate ESRCH when force-killing the daemon on POSIX * fix(ci): preserve Windows restart test selection --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
7c0b84f2b6 |
fix(terminal): invalidate cached glyphs when WebGL atlas changes (#8899)
* feat(terminal): add flag-gated render-desync sentinel for WebGL panes
Detects the buffer-clean/render-stale glyph garble class in the field: per
visible WebGL pane, compare the cells the xterm buffer says hold glyphs
against the ink actually present on the canvas, sampled in the same task as
a forced synchronous redraw so a divergence proves the render model/atlas is
wrong rather than a missed present. A trip requires the same screen cells to
stay divergent across three samples (real desync is pinned; scroll lag moves),
then records a webgl-render-desync breadcrumb, stashes evidence (canvas PNG +
buffer text) for bug reports, and runs the same shared-atlas recovery a tab
reveal performs, so a stuck-garbled pane self-heals within seconds.
Off by default; arm on any build via
localStorage.setItem('orca:render-desync-sentinel', '1') and reload.
* fix(terminal): invalidate glyph cache on atlas replacement
Reproduce the WebGL atlas identity mismatch with two live terminals and force cached geometry to rebuild whenever a different shared atlas is attached. Persist flag-gated render-desync evidence and retain the investigation tooling used to validate the field signature.
* fix(terminal): harden render desync diagnostics
* docs(reliability): clarify Linux WebGL evidence gap
|
||
|
|
1b331f282c |
feat(editor): bindable keyboard shortcut to add a markdown review note (Mod+Alt+N) (#8250)
* feat(editor): bindable shortcut to add a markdown review note
Adds editor.addReviewNote (default Mod+Alt+N) to the shared keybinding
registry and wires it into all three markdown surfaces: the rich editor
key handler invokes the annotation popover opener, the Monaco editor
installs a keydown listener that opens the composer for the tracked
selection target, and the preview maps the DOM selection to its
annotation block. openAnnotationPopover now prefers the live selection
target over synced state so the shortcut works even before the sync
render lands.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia
* fix(editor): cover list items and Monaco path for add-review-note shortcut
Tag the preview's list-item annotation blocks with data-annotation-block-key
so the shortcut resolves selections inside li blocks (review feedback), and
extend the e2e spec to drive the Monaco source-editor wiring.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia
* docs(e2e): explain store-driven view-mode switch in add-review-note spec
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia
* refactor(editor): extract add-review-note + selection-flush modules to satisfy max-lines after rebase
* refactor(editor): spread key-handler params and extract TOC hook to satisfy max-lines
* test(editor): move add-review-note installer test into its own describe
* fix(editor): pass add-review-note chord through when Monaco cannot act; cover preview surface e2e
* fix(editor): unify add-review-note chord consumption — consume only when a composer opens
* fix(editor): gate list-item annotation block key on composer availability
* fix(editor): require live selection for keyboard add-review-note
* chore: retrigger CI against current main (merge ref built during transient main breakage at
|
||
|
|
53af61160a |
fix(settings): keep Shortcuts rows visible on pane-title-only search (#9097)
Co-authored-by: 조재중 <126754298+m-a-king@users.noreply.github.com> |
||
|
|
6e91ca6c0e |
fix(browser): local HTTPS Try HTTPS + cert proceed (#8454) (#9104)
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
8461df2139 |
Fix Ctrl+Alt terminal input on Windows by repairing xterm's AltGr misclassification (#8810)
* fix(terminal): forward Windows Ctrl Alt chords * fix(terminal): route rescued Windows Ctrl+Alt chords through xterm's own key encoders Replace the hand-rolled Alt-prefix encoder in the custom key handler with a narrow repair of xterm's third-level-shift classification. xterm's keyboard service already computes the correct bytes for every input protocol (legacy ESC-prefixed, kitty CSI-u, win32-input-mode) before _isThirdLevelShift discards them on Windows Ctrl+Alt; rescuing only provably-genuine chords (Chromium's layout-wide AltGraph simulation, crbug 762557) lets those encoders deliver protocol-correct, layout-aware bytes with no duplicated encoding knowledge in Orca. Fixes vs the previous approach: kitty-mode TUIs now receive CSI-u instead of legacy bytes, digits/punctuation no longer alias to plain Alt chords, letters follow the logical layout (Dvorak/Colemak), Ctrl+Alt+Shift and Ctrl+Alt+F-keys gain Linux parity, and handled keys get xterm's stock preventDefault/stopPropagation. Firefox web clients keep stock behavior; a real-Terminal contract test fails loudly if an xterm upgrade removes the seam, degrading at runtime to the historical dropped-chord behavior. Co-authored-by: Orca <help@stably.ai> * Refactor Windows Ctrl+Alt chord test helpers and clarify AltGraph commen - Extract a shared getCore() helper in the test file to dedupe repeated `_core` casts across third-level-shift and keyboard-service lookups. - Correct the AltGraph comment: Chromium simulates AltGraph per composing keypress, not for the whole Ctrl+Alt press duration. - Warn via console when xterm no longer exposes `_core._isThirdLevelShift`, so a silent classification-repair failure is diagnosable in the wild. * Clarify comment explaining why Windows Ctrl+Alt chords bypass AltGraph c The comment previously implied Chromium always sets AltGraph=true for composable chords; the revised wording states the actual mechanism (Alt+Ctrl modifiers get replaced by AltGraph) so the inverse case is unambiguous. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
09173d02d2 |
fix(terminal): restore clickable links after returning to a worktree (#9061)
* fix(terminal): restore clickable links after returning to a worktree Clicking a terminal link (file path, URL, term_* handle, OSC-8) does nothing after switching to another worktree and back, until the terminal is scrolled a little. xterm's linkifier only re-runs link providers on mousemove when the hovered buffer cell changes vs its cached `_lastBufferCell`. Hiding the terminal surface fires `mouseleave`, which clears the current link but leaves that cache. On reveal the pointer returns to the same cell, so the mousemove handler short-circuits and the link is never re-established; a scroll shifts the buffer position and re-linkifies, which is the "scroll to fix" symptom. Reset the linkifier hover-cell cache when a terminal surface is revealed (resumeTerminalVisibility) so the next mousemove re-evaluates providers. Covers all link types, including file-path links whose geometry click fallback does not compensate after reveal. Verified in real Electron via a new e2e that fails without the reset and passes with it, for both a file-path link and a URL. * test(terminal): prove restored links activate after reveal |
||
|
|
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
|
||
|
|
e8a1674a75 | fix(terminal): keep divider resize active after capture loss (#9063) | ||
|
|
b8925657a0 | fix(terminal): release reattach fit on an unchanged stable grid (#9056) | ||
|
|
319ae4e9ea |
fix(terminal): make whole-tab close durable (#8958)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
f62ad69260 |
Click a link in orca browser should not open a new tab (#8782)
* Route new-tab link clicks to Orca tabs instead of popup windows - Adds an isolated-world click/auxclick listener that relabels browser-native new-tab intents (target=_blank, cmd/ctrl-click, middle-click) with a private frame name, so setWindowOpenHandler can distinguish them from opener-dependent window.open() calls without breaking OAuth popups. - Wires matched clicks through to a new browser:open-link-in-orca-tab IPC payload carrying foreground/background activation intent, so the renderer opens a worktree tab instead of a native window. - Adds unit and e2e coverage for modifier/middle-click routing, cancellation/rewrite handling, and cleanup on guest teardown. * Route new-tab link clicks to Orca tabs and stop background popups Plain target=_blank clicks (main frame and iframes) now navigate the current Orca tab in place instead of opening a new browsing context, while explicit new-tab gestures (Cmd/Ctrl-click, middle-click, Shift+modifier) route into Orca tabs via one-use private frame names, including child frames. Drops the foreground/background frame-name split and the activate flag now that all routed links always open active — no more silent background popups from a plain link click. |
||
|
|
69c14fadce |
fix(terminal): make viewport ownership transactional across output, replay, and reflow (#8674)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
24dc165b3c |
fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone (#8891)
* fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone Fixes #8879 * fix(sidebar): bound header edge drops to measured content |
||
|
|
40d0159926 |
fix(terminal): kill agent descendant processes on session teardown (STA-1800) (#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 |
||
|
|
59a7fffcd6 |
fix(terminal): keep WebGL glyph atlas pages within the shader sampler budget (#8672)
* fix(terminal): keep WebGL glyph atlas pages within the shader sampler budget The fragment shader has sampler slots for maxAtlasPages (16 on most Macs) and leaves outColor uninitialized for any higher page index, so glyphs rasterized onto pages past the budget render as garbled pixels. Long sessions grow past the budget via the merge fallback, and the previous wipe fix re-activated those unbindable pages, so every atlas wipe re-allocated glyphs onto them (post-wipe allocation prefers the last, highest-index active page) and garbled whole panes mid-stream. Fix, matching the direction xterm.js maintainers are pursuing upstream (xtermjs/xterm.js#6043): a shared _evictAllPages resets the atlas to one fresh page, called from clearTexture and from the two allocation paths that could otherwise push a page past the budget (merge fallback and oversized-glyph page creation), so the page count can never exceed the renderer's texture capacity. Defensive backstops: a one-time warn plus bind-loop clamp, and an else branch in the generated shader so an unexpected overflow renders blank instead of undefined pixels. * test(terminal): cover WebGL atlas sampler budget * fix(terminal): align WebGL atlas invalidation source |