mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
6c8eea5ebeac6948998f769aa0ec4fa8a5ec72ab
675
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0352c239c2 |
Add Copy Session ID menu item to terminal tabs (#18039)
* Add Copy Session ID menu item to terminal tabs
Adds a menu item to copy the active pane's agent session ID when available.
The item only appears when the session is still live and has reported an ID.
* Add Copy Session ID i18n strings and e2e test
- Add localized strings for Session ID context menu item
- Add e2e test coverage for copying session ID from terminal tabs
- Fix dev build permissions when copying private Electron app bundles
* Drop the Electron dev-bundle fix from this branch
It landed on main as
|
||
|
|
f8a3f2c7c0 |
test(e2e): do not treat a destroyed renderer as a relaunched runtime (#17785)
* test(e2e): do not treat a destroyed renderer as a relaunched runtime waitForRelaunchedRuntime polled refreshAuthorityRuntimeId with expect.not.stringMatching(previousId). Playwright treats null as a non-match, so an Execution-context-destroyed miss ended the wait as if the client had already reconnected. Poll until a non-null id that differs from the pre-restart process. * test(e2e): wrap cookie-survival restart evaluates as pending misses The cookie spec still opened a post-restart page with a raw evaluate poll. A recycled renderer then timed out as "never materialized". Use the shared fixture helpers so destroyed-context is a miss, not a fail. * test(e2e): leave cookie-survival on its own wait for this PR The relaunch-wait fix made the cookie spec's post-restart echo render time out in CI. Keep that spec out of this change so the destroyed- context wait can land on the helpers restart-survival actually uses. |
||
|
|
a5796ec8eb |
refactor(runtime): split OrcaRuntimeService and compatibility tests (#17605)
* refactor(runtime): split OrcaRuntimeService into focused modules
* test(runtime): cover admission tiers and strict worktree reconciliation
* fix(runtime): preserve owner and structured session visibility
* fix(runtime): port post-extraction compatibility fixes
* fix(runtime): preserve skill-share cancellation barrier
* test(runtime): update identity inventory after extraction
* fix(runtime): preserve hook transport environment cleanup
* fix(runtime): consolidate idle probe imports
* test(runtime): retire split file process allowlist entry
* fix(runtime): route child process types through shared boundary
* test(runtime): preserve worktree host metadata precedence
* fix(runtime): update extracted test seams
* fix(runtime): gate the split's ts-nocheck set and restore the stop-confirmed contract
Audit follow-ups for the OrcaRuntimeService split:
- Freeze the 171 @ts-nocheck files behind a ratchet so no new file can disable
type checking. The split's linear mixin chain cannot express forward
references yet, so the existing suppressions are grandfathered; the baseline
may only shrink.
- Drop the stray @ts-nocheck at the end of orca-runtime-get-status.ts. It sat
after the first statement, where TypeScript ignores it, so the module was
already checked.
- Restore `retireRejectedPty(ptyId, stopConfirmed: boolean)` as a required
argument. The split widened it to optional and patched the resulting error
with `stopConfirmed === true`; an omitted argument would have silently taken
the unverified-stop path instead of failing to compile.
- Guard that every orca-runtime-tests fragment is imported by the compatibility
entrypoint. The fragments are .spec.ts, which no Vitest include glob matches,
so one left out of the list would silently stop running.
* fix(runtime): restore four behaviors the OrcaRuntimeService split dropped
Audit findings against the refactor's true base (
|
||
|
|
1efd4e1a97 | test(e2e): seed source control diff before opening panel (#17784) | ||
|
|
f116d2ca2a |
test(ci): retry Windows teardown EPERM and restart evaluate misses (#17780)
Restart-survival polls treated a recycled renderer as a hard failure. Wrap those evaluates so "Execution context was destroyed" is a pending miss. Windows package-lane teardowns after a force-kill used rmSync with force:true only, which does not absorb EPERM; put them on the shared maxRetries:8 policy. |
||
|
|
eff317939a |
fix(terminal): mount one surface per workspace id in the workbench (STA-4846) (#17432)
* fix(terminal): mount one surface per workspace id in the workbench (STA-4846) * test(terminal): pin the workbench projection against under-selecting Losing a surface unmounts live terminals, which is worse than the duplicate mount STA-4846 fixes, so cover every catalog shape that reaches the workbench: local-only rows that name no host, an unqualified row colliding with a host-qualified one, two SSH hosts on one id, folder rows across three hosts, folder ids alongside git worktree ids, and a whole-catalog assertion that the emitted id set equals the distinct input id set. Also pin the `useAllWorktrees` -> `useWorktreeMap` swap: both read the same WeakMap-cached snapshot, so the zustand compare is unchanged. Harden the folder tie-break to require the row to name its own host. `getCatalogOwnerHostId` defaults an unstamped row to `local`, which would let a row that never named a host win the `local` tie and mount another host's path; it now keeps first-wins instead of guessing. * fix(terminal): surface the unresolvable folder-surface collision When two hosts publish the same folder-workspace id and the active workspace's host cannot be resolved, the projection drops one row's folderPath first-wins. That path is the PTY cwd for any tab without a startupCwd, so the drop was silent. Warn on it, and pin the two tie-break branches the unit tests missed: a colliding row that is not the active workspace, and the same collision with the rows in swapped order (a host reconnect re-appends its rows, flipping which row is first mid-session). * test(e2e): ride out Playwright's spurious main-process evaluate rejection `e2e / changed e2e specs` failed on `pr11346-selected-runtime-add.spec.ts` with "Execution context was destroyed, most likely because of a navigation" from the paired client's first `app.evaluate` — the isolated-HOME assert that runs one millisecond after `electron.launch()` resolves, which is before the app is `ready`. Nothing navigates there: Playwright raises that message for any main-process CDP failure that is neither a JS error nor a closed session, and `ElectronApplication.evaluate` is unreliable on Electron 27+ (microsoft/playwright#33737). Reproduced locally, and a plain re-run of the same commit went green. Extract the retry `installTerminalPtyWriteSpy` already carried for this exact message into `retryTransientMainEvaluate`, and use it for the launch-time home read in all three launchers. The read is idempotent and a real boundary escape still throws on the first successful read. Also forward the paired client's process logs before the assert instead of after: this failure reached CI with none of the client's own output, because forwarding had not started yet. * test(e2e): wait on the owning group before asserting a Cmd-J browser tab is active `changed e2e specs` then failed at the remote browser-page step: the store poll had already seen `activeBrowserTabId` land on the mirrored workspace, but `[data-tab-id=...][data-active="true"]` never appeared. `data-active` on a `BrowserTab` is the strip's active tab, which comes from the owning group's `activeTabId` — not from `activeBrowserTabId` — so the DOM assert was racing an activation the poll never waited for. The simulator rows in the same spec already poll the group; the two browser-page rows did not. Poll the same triple for them, so a genuinely stuck group fails with the ids it ended on instead of a bare "element(s) not found". |
||
|
|
c558d7e083 |
Activate terminal splits before inherited CWD resolution (#17601)
* perf(terminal): activate splits before cwd resolution * test(terminal): prove split focus before cwd publish * fix(terminal): release stale split cwd fence * test(terminal): add visible split activation latency benchmark * docs(reliability): clarify split benchmark provenance * fix: preserve deferred split handoffs across remounts * fix: fence late deferred split closes * docs(reliability): record exact split benchmark runs * test(reliability): fail benchmark on artifact write errors * test(reliability): attribute split activation phases * docs(reliability): record schema-v2 split benchmark * refactor(terminal): collapse duplicated split-handoff and write-queue paths - Drop the discardDeferredSplitPaneHandoff alias for its identical clear twin. - Fold the deferred-cwd resolve/reject settle handlers into one applier. - Extract settlePaneCwdDeferredSpawn for the repeated read-clear-write pattern. - Share one head-index FIFO primitive between the ordinary and reply queues. * fix(terminal): stop retaining a promise reaction per acknowledged write Racing every accepted write against one queue-lifetime cancel promise kept a reaction record alive until that promise settled: 200k acknowledged writes retained 88.6MB, now 0.1MB. Give each in-flight write its own cancel, and split the shared FIFO primitive into its own module. Also sanitize the split-latency benchmark report at its single serialization point so shared artifacts no longer carry the machine-local repo path or unbounded cleanup error text. * fix(terminal): settle deferred split input when the spawn is abandoned An abandoned deferred spawn returns before transport.connect(), so nothing drained the pre-connect buffer: sendInputAccepted's promise never settled and a paste into that pane hung forever. Clear the buffer on the abandon fence. Also re-derive the pre-connect retention cap from the clipboard-paste ceiling rather than the 16MB single-write ceiling; it is held twice per pane across up to 64 deferred splits, so 5.59M code units guarded the wrong thing. * fix(terminal): release the deferred cwd fence on a rejected reattach A daemon createOrAttach can turn an apparent fresh spawn into a reattach; when that reattach is refused the spawn ends with deferredSplitSpawn/pendingCwd still set, permanently arming the pre-bind detach refusal. The release no-ops when a PTY did bind, so it only fires where the fence would otherwise leak. The stale-generation return above is deliberately left alone: a newer connect already owns the pane there, and the fence is not generation-scoped. |
||
|
|
bbbb59e18c | test: cover quick commands, catalog links, and long discard dialogs (#17489) | ||
|
|
d5d3c4898a |
perf(diff): defer large diffs until user loads them (#17521)
* perf(diff): defer large diffs until user loads them Rendering very large diffs would freeze the UI. Diffs exceeding MAX_AUTOMATIC_DIFF_CHANGED_LINES now show a prompt allowing users to load them on demand instead of automatically rendering. * perf(diff): defer large diffs until user loads them Diffs with >10,000 changed lines are now deferred and only rendered when the user explicitly clicks "Load diff" in a prompt. This improves initial render performance for large file changes while maintaining full access when needed. * perf(diff): defer large diffs until user loads them Prevents UI freeze when opening files with very large diffs by deferring render until the user explicitly loads them. * fix(diff-view): defer loading large untracked files and refactor fallbac Split on-demand load decision logic to distinguish tracked vs untracked files — large untracked files now properly defer loading while untracked images remain automatic. Extract fallback height computation into a dedicated function to centralize the logic for render-limited and in-flight-loading states, reducing code duplication and clarifying when to use bounded fallback heights. * fix(diff-view): defer loading large SVG files SVG renders as source text in the diff view rather than a preview, so should defer like other text files. Also fix Windows e2e test cleanup by using post-Electron shutdown. |
||
|
|
fbe94ceff6 |
fix: close readiness gaps found by merged-change audit (#17159)
* fix(ssh): fence stale kills and retired pane replay * fix(ssh): support cancellable interactive authentication * fix(ssh): await remote catalog before snapshot adoption * fix(pty): contain Windows ConPTY input failures * fix(power): avoid redundant macOS display blocking * perf(editor): narrow markdown override subscriptions * fix(quick-open): close directory handles after reads * refactor(linux): remove unused proc socket scanner * fix(usage): apply flat Sonnet 4.6 pricing * ci: prime Node next native test cache * docs(skills): resolve snapshot cleanup data path * fix(ssh): recover install locks after host reboot * test(ssh): recognize boot-aware install locks * test(ssh): prove previous-boot lock recovery live * test(wire): pin pre-metadata release coverage * fix(terminal): preserve remote tab ownership through recovery races * test(runtime): fence replaced terminal handles in agent guard * fix(ssh): preserve remote snapshot authority across polls * fix(pty): contain late ConPTY output EPIPE * test(pty): register Windows exit watcher before kill * fix: close SSH and tab readiness race gaps * fix(tabs): retain headless order and placeholder titles * fix(build): avoid parallel electron-vite config race * test(windows): avoid MSYS temp path rewriting * test(windows): avoid killing exited PTY * fix(pty): avoid late ConPTY input teardown race * fix(terminal): sync reconnect error ownership after commit * fix(runtime): use canonical worktree identity comparison * test(ssh): assert complete cold-hydration baseline * test(windows): invoke quoted retention fixture via PowerShell * test(windows): read ConPTY grid through mode con * fix(terminal): publish PTY replacements atomically * fix(terminal): infer stale identity on reattach * fix(terminal): fence stale pane PTY callbacks * fix(terminal): fence stale pane binds after rebind * fix(terminal): reject stale pane transport callbacks * fix(terminal): fence mirrored reattach spawn callbacks * fix(terminal): replace stale pane PTYs on remount * fix(ci): size the Windows launcher-compile test budget from measurement `native-smoke (windows-latest)` fails ~4.5% of runs on `preserves a multiline argument through the compiled remote launcher` with "Test timed out in 15000ms" — on unrelated PRs, for reasons that have nothing to do with them. Across 176 sampled attempts it is the only red that job produced, and it hit seven different PRs in two days: #16900, #16904, #16915, #16955 (twice), #16979, #17014, #17085. The test is six process creations: powershell.exe forks csc.exe, then the freshly compiled orca.exe forks node.exe, twice. Hosted Windows runners periodically slow process creation down, and this test amplifies that far harder than anything else in the job. Comparing the 80 attempts where it ran under 3s against the 12 where it ran over 12s, its own median goes 2198ms -> 15917ms (7.2x) while the same file's powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash process tests in the neighbouring file move 1.4x, and the other 35 files put together move 1.5x. Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms, correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%) exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s testTimeout, so deleting the override and inheriting the config is not enough on its own. 60s clears all 176 with 1.7x headroom on the worst. This is slow, not hung. Every body here is synchronous spawnSync, so Vitest cannot interrupt one — the timer fires only after the body returns and the reported duration is real elapsed time. That is why a failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The work finished; the stopwatch was short. Seven reruns at one identical head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the last of those would have been red on code that had not changed. The 15s came from #8897, which raised this test off Vitest's built-in 5s default because the job then ran bare `pnpm vitest run`. #8909 landed 3h27m later and pointed the job at config/vitest.config.ts, which is the real fix for that. The constant stayed behind and has been the binding budget ever since. * fix(terminal): fence stale remount reattach ownership * fix(terminal): reconcile mounted pane identity after replacement * fix(terminal): fence stale reattach fallback ownership * fix(terminal): fence deferred SSH reattach ownership * fix(terminal): fence stale split pane ownership callbacks * fix(terminal): keep stale spawns from consuming startup --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
46fa1a98d0 | fix(browser): show reload loading feedback (#17635) | ||
|
|
3060cf73b9 |
fix(tasks): restore scroll position when reopening GitHub item details (#17524)
Track scroll-restore generation to invalidate stale callbacks that were resetting the scroll position to 0 when reopening a detail page. Prevent restoration while a detail page is open. Update automation test to use runtime.call RPC instead of removed preload CRUD method. Hide browser import hint in E2E profile to prevent overlay from intercepting test setup clicks. |
||
|
|
0d8785b916 |
Prevent stale search commits from timer race conditions (#17495)
Validate scheduled values match current state before executing idle timeout callbacks. Use useLayoutEffect to synchronously update refs, preventing outdated searches when rapid keystrokes overwrite timers. |
||
|
|
f7d8d7f77a |
test(e2e): make the cold-hydration spec verify its own captured snapshot (#17031)
`adds no tab when the host workspace snapshot stalls across a relaunch` replays the bytes it reads off the relay, but only ever waited for the snapshot FILE to exist -- never for it to carry the tabs the test had just seeded. A capture that missed the baseline produced a failure that reads as a product regression and is not one: an empty `session.tabsByWorktreePath` places nothing, so it reports nothing unplaced, so `remote-workspace-snapshot-apply.ts` marks the target hydrated and `hydrateTabsSession` replaces the worktree's tabs with none. That is exactly the observed `baseline=3 duringStall=3 afterHydration=0`, and it is correct behaviour for a host snapshot that genuinely holds no tabs. Assert the precondition where it belongs -- on the capture, before the relaunch that consumes it -- so an empty or unparseable fixture names itself instead of surfacing later as a tab count the product appears to have lost. No assertion is weakened: `afterHydration` still has to equal the baseline, and no retry, sleep, or timeout was added. |
||
|
|
e84042572c |
Upgrade xterm to 6.1.0-beta.303 and generate addon patches
* Upgrade xterm to 6.1.0-beta.303 and generate the addon patches
Takes the current xterm beta line: xterm 287 -> 303, addon-webgl 286 -> 299,
addon-serialize 287 -> 300, headless 302, the remaining addons -> 300, and the
same set on mobile. All four packages stamp upstream commit d3e32b3.
The reasons are upstream #6042/#6043/#6055 (a shared glyph atlas no longer
garbles sibling panes on a page merge, clear, or sampler-budget overflow) and
Note that core 303 is not image-addon-only over 302: it carries the buffer perf
work, including the new BufferLineStringCache.
addon-webgl and addon-serialize move into the patch generator
--------------------------------------------------------------
Both were hand-edited minified bundles, which is what the Known Gaps section of
docs/reference/xterm-patch-regeneration.md described. Both reproduce byte for
byte from the pinned commit, so they are now manifest entries generated from a
source patch like @xterm/xterm already was. Their sourcemaps now move with their
bundles; before this they shipped maps whose offsets did not match the code
beside them.
The webgl patch shrinks from a 1.06 MB hand-edited bundle to a 6.6 KB source
patch, because upstream took the invalidation half Orca had backported. What is
left is only what upstream still lacks: the fragment-shader else branch for a
v_texpage past the sampler budget, the clearTexture guard that no-ops once a
merged page holds index 0, spending the merge retry budget before beginFrame
latches the version it saw, and Orca's font-weight probe.
The serialize source patch is byte-for-byte the same fixes as before; upstream
changed nothing in that addon between 287 and 300.
Generator fixes, each of which failed silently
----------------------------------------------
- `--relative` was appended after the `--` separator in CHECKOUT_DIFF_FLAGS, so
git read it as a pathspec and kept repo-root-relative paths, dropping every
source hunk from an addon's patch.
- `git apply` run from a package subdirectory still resolves patch paths from
the repo root, skips every hunk and exits 0. It now runs from the root with
`--directory=<packageDir>`, and a source patch that leaves the checkout
unchanged is a hard failure rather than an empty patch.
- An addon's own `tsgo -p .` has empty files/include and only project
references, so it emits nothing and the addon webpack then fails on a missing
./out/. The root build now runs first.
- versionStampFile is optional; publish.js stamps an addon's package.json, which
overlayBuildOutput never patches.
- On a version bump the lockfile has no entry under the new key yet, so --write
reports the gap instead of aborting mid-run. --check still fails on it.
Adding the two addons pushed the generator and the Electron packaging contract
test over max-lines, so the patch-text helpers move to xterm-patch-text.mjs
(pure text: no checkout, no build) and the vendored-xterm assertions move out of
the packaging contract into xterm-webgl-runtime-contract.test.mjs.
Tests
-----
Four tests asserted upstream bugs that are now fixed, not Orca behaviour:
- xterm-user-scrolling-contract pinned headless and core by version string.
Upstream bumps each package only when its own output changes, so headless 302
and core 303 are the same source. It now asserts they share a commit.
- Five CSI 3 J assertions expected a reader stranded at the top after an erase.
Upstream #6081 clears isUserScrolling there, so the erase releases them to the
bottom instead. Orca's pin still lands them correctly, because its parser
handler observes the erase before xterm's own handler runs.
- The IME transaction test hard-coded the xterm version; it now reads the
installed package, since the point is that bundle, map and version agree.
- The Electron runtime contract asserted Orca's old clearModelGeneration. Shared
atlas invalidation is upstream's now, so it asserts pageLayoutVersion on the
resolved dependency, plus the Orca-only hunks on the patch.
Verified: 66,008 unit tests, mobile's 3,863, the four WebGL atlas e2e specs, and
`regenerate-xterm-patches.mjs --check` in sync on all three packages.
Left alone deliberately: resetAllTerminalWebglAtlases still fans out globally
even though clearTexture now self-heals siblings, and upstream #6068
(WebglAddon.dispose leaks the GL context) is still open.
* Drop the two unused WebGL atlas fan-out exports
resetAllTerminalWebglAtlases and presentAllTerminalPanesWithoutAtlasClear have
no callers, and had none at
|
||
|
|
3ab9766e38 |
perf(worktree): prepare checkouts while the composer is open
Squashed merge of PR #17290. |
||
|
|
fd52e942bd | fix(tasks): keep the remembered GitHub scroll offset instead of clobbering it (STA-5949) (#17433) | ||
|
|
6677ae4e5e | test: correct 8 stale specs surfaced by the test-detected-bugs sweep (#17434) | ||
|
|
70df6f0224 |
fix(terminal): mask the agent composer's dim placeholder during a preedit (#17377)
Split out of #17170, which now carries only the xterm composition-overlay work. Codex and Claude draw an all-dim, full-row ghost placeholder. The opaque preedit overlay reproduces the committed row tail it covers, so without this the ghost is repeated to the right of the composing syllable instead of staying masked. The binding keys off the `.xterm-composition-remainder` class that #17170 adds and hides it through CSS while a composition owns a structurally verified placeholder row — bold prompt glyph plus a dimmed model footer below a blank gap for Codex, a frame line above the prompt for Claude. Arbitrary dim output, shell lookalikes, and any row carrying typed text keep their tail visible. readTerminalCursorLineContext moves from src/main/daemon to src/shared because the renderer now needs the same reader the daemon uses; the move is import-only. Depends on #17170. |
||
|
|
7f822a73e3 |
fix(terminal): render the IME caret and give the candidate anchor one owner (#17170)
* fix(terminal): render IME caret without placeholder overlap * fix(terminal): preserve dim mid-line composition tails * fix(terminal): keep IME caret visible at row edge * fix(terminal): harden IME overlay lifecycle and layout * test(terminal): type final-cell layout mock * fix(terminal): keep final-cell IME anchor on-screen * fix(terminal): bind IME masking to composer ownership * fix(terminal): bound IME placeholder session ownership * fix(terminal): track latest IME placeholder session * test(terminal): share IME session event fixture * fix(terminal): keep both writers of the IME candidate anchor in agreement `textarea.style.left` has two writers: xterm's patched CompositionHelper and Orca's terminal-ime-candidate-anchor.ts. The anchor module listens on terminal.element, so within a composition event it writes after xterm's textarea listener and reverted the final-column clamp the patch had just applied. Moving the clamp into the anchor module and dropping the patch hunk does not fix it, and the rendered e2e caught that: CoreBrowserTerminal.ts:444 drives updateCompositionElements from onRender as well, so xterm re-asserts the textarea position on every repaint, with no composition event for that module to hear. The anchor survived only when no render happened to follow — measured as a flake at the final column, 1561.28px against a 1557px screen edge, the fully unclamped value. So both writers now compute the same clamp. The patch keeps it, because it is the writer on the render path and already holds cursorLeft, maxWidth and the preedit bounds. The anchor module applies the same one, so its composition-event write no longer reverts the correction in the window before the next render. Both halves are individually necessary and both are mutation-tested. Also restores _getRowRemainderText's expression from main: translateToString(true, x, line.length) and translateToString(false, x, getTrimmedLength()) are the same call, since upstream does endCol = min(endCol, getTrimmedLength()) under trimRight. Adds the two missing tests — one installing both anchor writers in a single rig, one driving a render under an open composition — plus disposal cleanup and clamp-bound coverage, and moves the Codex/Claude placeholder mask to a follow-up PR. |
||
|
|
7b467bd0a6 |
ci: gate PRs on a real input method, and prove the lane engaged one (#17365)
* ci: gate PRs on a real input method, and prove the lane engaged one No job on the PR gate has ever run a real input method. pr.yml and e2e.yml are ubuntu-latest with CDP `Input.imeSetComposition`, which is a synthetic composition; the only job that drives ibus-hangul through xdotool is terminal-ime-e2e.yml, and it is schedule + dispatch only. A PR could turn the real-IME path red and merge green. Route IME source to that lane from pr.yml through the existing pr-e2e-source-routing mechanism, so it runs on IME-touching PRs and nothing else. The lane stays out of verify.needs — advisory, like `e2e` — because its reliability is known only from nightly main runs. Deliberately no continue-on-error: that reports green and hides the signal. The harness fails open in ways that all look like success: Playwright reports a skipped test as a pass, so an unset ORCA_E2E_NATIVE_IBUS_HANGUL, a renamed test, or a session with no engine all exit 0 having exercised nothing. The specs now append an engagement receipt only after observing real composition events, and the runner requires one per expected test before the lane may report success. Also drop the native spec from changed-e2e: it was already routed there by its own filename, where it self-skips for want of an ibus session and reported that skip as coverage. * ci: let the real-IME step report even when the synthetic step failed |
||
|
|
2259e06ff6 |
fix(tests): match showInactive() in paired-client-window-reveal spec (#17362)
PR #17347 switched the reveal helper from window.show() to window.showInactive() and updated the thrown message, but left the unit test's regex/title matching the old show() wording — failing deterministically in CI (which builds against current main) while passing on any stale checkout that predates #17347. |
||
|
|
09429768c5 | test(cross-version-wire): compare published fields per frame (#17301) | ||
|
|
252dbd60ea |
fix(terminal): restore lossy initial remote snapshots (#17113)
* fix(terminal): restore lossy initial remote snapshots * test(terminal): strengthen lossy snapshot causal oracle |
||
|
|
ae0f3675a1 |
fix(remote): focus host-delegated split panes (#16886)
* fix(remote): focus host-delegated split panes Return the authoritative leaf identity from terminal.split, record viewer-local focus intent behind the captured pairing revision, and replay the mirrored layout before focusing the exact pane. Preserve old-host fallback and prevent delayed split responses from stealing focus after the viewer moves away. Add deterministic runtime, renderer, concurrency, compatibility, and headed paired-Electron coverage for Cmd+D, header splits, and immediate PTY input routing. Fixes #16510 * fix(remote): preserve split focus across tab groups Resolve the initiating source tab and leaf from the remote PTY, while keeping the viewer's current focus as a separate anti-steal baseline. This lets context-menu/header splits from non-focused group tabs focus their result without allowing delayed responses to override a later navigation. * test(remote): drive split focus with key events * test(remote): use the platform split shortcut * fix(remote): fence concurrent split focus intent * fix(remote): harden split focus ordering * fix(remote): preserve split focus after runtime refactor * fix(remote): fence stale split focus gestures * test(remote): keep split focus regression within line budget |
||
|
|
5ea9daba97 | fix(window): keep automated Electron launches out of the foreground (#17347) | ||
|
|
f572ba34bc | feat(browser): address-bar convergence — previews and browser tabs convert in place (STA-5681) (#16998) | ||
|
|
73ff003147 |
test(e2e): cover session upgrade and Windows terminal recovery (#17289)
* coverage report * rm test coverage * test(e2e): cover session upgrade and Windows terminal recovery * fix stub |
||
|
|
c6641152f1 |
Split relay dispatcher layers (#17174)
* Split speech session lifecycle * Split terminal output scheduler pipeline * Split mobile browser pane modules * Prune resolved max-lines suppressions * Split pane tree equalization logic * Extract mobile troubleshoot screen styles * Split external automation manager * Split main window service attachments * Split hosted review creation checks * Split automation dispatch event handling * Split settings navigation metadata * Split daemon initialization lifecycle * Split GitLab item dialog * Split relay dispatcher layers * Fix F3-speech for #17123 * Fix F1-cycle for #17131 * Fix F4-navtest for #17157 * Fix F2-allowlist for #17161 |
||
|
|
2214d29f15 |
fix(browser): close guest-owned split tab (#17281)
* fix(browser): close guest-owned split tab * fix: check sourceId before toggling floating panel on close The empty-panel toggle is the ambient fallback only. Guest-initiated closes (with sourceId) target the main workspace and should not toggle the panel. * test(browser-split-shortcuts): remove terminal-mirrors close test and un Removes test case that verified Cmd+W closes guest-owned browser splits when active-tab mirrors point to a terminal, along with the helper function and unused fixture properties that only that test required. |
||
|
|
2dfaa676d8 | chore: update oxlint and oxfmt (#17150) | ||
|
|
b17f60d744 | build: upgrade to pnpm 12 (#17156) | ||
|
|
11d8673112 |
test(cross-version-wire): derive skew expectations from the baseline under test (#17178)
* test(cross-version-wire): derive skew expectations from the baseline under test The cross-version wire job pairs current code against whichever release tag is newest, so a hand-written "the old side does not have X" assertion expires by itself: v1.4.192 was the first tag containing the SnapshotStart `terminalOwner` field, and cutting it turned the new-client/old-server pairing red on unrelated pull requests with no code change anywhere. Read what each build publishes from that build. Each host is now paired against a client of its own version to produce a reference, and the skewed pairings are compared against that reference, so the expectation is whatever the release actually shipped. The same class of assertion in the agent-session suite — "the old build advertises no structured capability and registers no structured method" — becomes "each build's advertisement agrees with what it registers", and the "client too old to know this capability" is derived by removing the capability from the baseline's own list. The guard is unchanged in strength: a field the old host still publishes may not be dropped, skew may not change what a host puts on the wire, and a new pairing asserts the oracle still stalls when a peer cannot decode an opcode the other side sends. * test(cross-version-wire): exercise release structured methods * test(cross-version-wire): load the registered method manifest * test(cross-version-wire): assert execution, not registration, on both host gates The release-shaped checkout gate accepted any reply that was not method_not_found, so a registered-but-throwing handler passed it. The capability gate asserted a shared host spy had been called at all, so the second method mapped to that spy could stop reaching the host unnoticed. * test(cross-version): make the release-shaped skew cover the whole agent-session manifest The release-shaped checkout is the only place the "registered means usable" claim is executable today — the baseline release registers none of these methods — and it was exercising one of sixteen. A handler registered and returning an execution error passed the suite. - Declare each method's result in the manifest, so "answered" is the contract rather than "did not say method_not_found". - Give each build a seam to install a host into its own module slot; a release checkout has its own copy, so the working tree's host was never this dispatcher's, and every host-backed method answered structured_agent_session_unsupported — the capability gate's own words. - Run one execution contract over both skews instead of two divergent loops. - Pair the AI Vault never-called spy with a positive control; renaming the runtime method it watches left it green. --------- Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local> |
||
|
|
92ab618a11 |
Repair scheduled computer-use CI (#17122)
* Repair scheduled computer-use CI * Make Calculator E2E Windows-version neutral * Handle classic Calculator accessibility panes * Update Calculator E2E source contract |
||
|
|
fd9125ea8c |
feat(native-chat): Codex structured native chat restructure (#16729)
* feat(native-chat): port structured Codex sessions from restructure-recovery Rebuilds the desktop structured native-chat implementation from brennanb2025/native-chat-restructure-recovery (tip 4e31c08db3) on top of current main as a single commit, scoped to the local Codex path. Ported: - Structured agent-session core: durable record store + single-writer lease, canonical journal, agent-session wire host/attach/eviction/subscribers, `agentSession.*` RPC surface (registered via ALL_RPC_METHODS; host-side mobile allowlist included for wire compat), pty write gate, transcript additions, and the Codex app-server adapter/launch resolution. - Renderer: NativeChatStructuredSession view/composer stack, structured launch path with the single-flight guard, local structured session tabs sync, activation gate + structured inventory (read-only `agentSession.handoffStatus` probe), agent-session tabs in the tab strip, AI-vault structured session activation, and the settings pane with the parent Experimental Chat UI toggle plus the nested "Use updated structured native chat" toggle. New sessions require both flags, agent codex, no prompt, and a local non-WSL, non-Windows-host execution host (structured-native-chat-availability). - Fixes 72c013cea6 (verified Codex launch recovery), 8ddbaf5e3d (defer native terminal view switching affordances), and 4e31c08db3 (release the launch gate after a visibility retry) with their regression tests, including the third-launch-after-retry guard case. - Cross-version agent-session wire test + CI lane, packaging entries (proper-lockfile, agent-tooling asar excludes), and the wire-compat doc section. Deliberately not ported: mobile/ changes, the Claude structured runtime (only the claude-transcript-branch-proof and claude-structured-owner-identity leaf modules remain, backing the kept TUI-recovery arms), the terminal↔chat adoption/handoff flow (`agentSession.adoptTerminal`/`requestHandoff`, the handoff request engine, TUI adoption machinery, orca-runtime adoption methods), renderer switching affordances and their dead leftovers, the hook/subagent-status refactor cluster, and unrelated branch changes. The crash-during-acquisition recovery path (restart handoff adjudication, restore/reverse re-acquire, lease schema handoff keys) is kept because every plain direct launch depends on it; a trimmed handoff coordinator exposes only status/restore/close. Branch edits that targeted files main has since split (ipc/pty.ts, worktrees.ts, rpc/methods/terminal.ts, useIpcEvents, pty-connection, store/slices/terminals.ts, runtime-types, web preload) were re-applied to the split modules, preserving main's newer logic (Windows CIM fallback, browser tab close rework, cold-restore resume flow, dispatcher threading). Known seam: the mobile clipboard image-provenance CONSUMER gate ships (agentSession.send refuses unproven mobile image refs with agent_session_image_untrusted) but the producer hunk in rpc/methods/clipboard.ts stays with the unported mobile cluster, so mobile image sends into structured chat fail closed until that side ports. * fix(native-chat): trust only authenticated local image uploads * fix(build): preserve Windows process-tree patch application * test(windows): include process creation time in addon fixture * fix(build): run windows-process-tree node-gyp from the physical package dir gyp expands the node-addon-api dependency by probing node, whose cwd resolves to the package's physical directory in the store, so the emitted target is a store-relative ../../../../node-addon-api@... hop. gyp then resolves that hop against the rebuild cwd; from the node_modules symlink/junction it escapes the store and configure fails with "node_addon_api.gyp not found" (run 32999886072). Rebuild from realpath(package dir) so both bases agree, matching how the package manager itself runs native install scripts. The regression test replays gyp's expansion+resolution against the planned cwd and fails without the fix. * fix(native-chat): keep chat tabs visible through terminal closes and empty-worktree launches Two proven blockers in the native Codex tab contract: closeTerminalTab pre-empted the canonical unified close. With one terminal left it deactivated the worktree on a terminal/editor/browser-only check, blanking a workspace that still held a renderable agent-session tab; with two or more it pre-picked a successor from terminal entities only, re-stamping the group active before closeUnifiedTab's MRU/neighbor repair could land on the chat tab. Successor choice now defers to the unified contract whenever the terminal has a unified row, and deactivation is gated on the unified renderable count (matching leaveWorktreeIfEmpty), with the legacy pre-pick kept only for terminals without a unified row. A structured session created on an empty worktree was published into the host's headless group while preserveLocalLayout froze the local layout, leaving the tab in store but permanently off screen. A preserveLocalLayout owner now always takes client-owned placement — repairing a rendered leaf whose group record is missing, or materializing a rendered group on a truly empty worktree — and applies the client-derived layout repair while still rejecting host-authored layout. Regression tests drive the real store through closeTerminalTab (git worktree and folder workspace) and the real snapshot applier for the empty-worktree adoption states; all fail without the fixes. * fix(native-chat): close stale turns and retry rejected sends * fix(native-chat): retire hosted rows on structured tab activation * fix(native-chat): preserve rpc defaults across main merge * chore: format remote wire compatibility guide * test(native-chat): cover retry after unconfirmed send * fix(native-chat): reload outbox on session switch * docs(settings): disclose structured chat platform limits * fix(native-chat): await Codex launch-home preparation * fix(codex): align child-process allowlist with async trust bridge * test(identity): update inventory for tab surface refactor * fix(windows): preserve process-tree CRLF patch sources * fix(native-chat): anchor an unmatched chat echo where it was sent (#16117) * fix(native-chat): anchor an unmatched chat echo where it was sent The reported symptom was old user messages replaying below every new turn, so the conversation read as scrambled. The cause was not that the echo failed to match a transcript row. Claude consumes a mid-turn send through a `queued_command` attachment and writes no `type:"user"` record for it, so some echoes can never match, and no amount of matching will change that. The cause was WHERE an unmatched echo rendered: buildMobileNativeChatTransientData appended every pending item after the entire transcript, so it re-read below each turn that landed afterwards. Render each echo directly after the transcript row it was sent against, using the baseline the send already captures. An unmatched echo is then at worst a duplicate in the right position rather than a scrambled one, and it stays visible. Echoes sharing an anchor keep send order; a send with no baseline, or one whose anchor folding dropped, still falls back to the tail. Deliberately NOT fixed by deleting the echo. Inferring from send ordering that an echo can never match, then removing it, loses the user's own text for a message the agent did receive, and it cannot fire in the common case anyway - measured drain groups are 1,017 of size 1 against 55 larger. It also escalates an existing gap: the count pass has no baseline-tail guard, unlike the glue pass, while `messages` is a 40-row window that head-trims, resets on reconnect and grows at the front on loadEarlier, so a false landing there would license deleting a DIFFERENT outstanding message. That count-pass gap is real and left for a separate change; anchoring makes its worst case a duplicate in place rather than a scrambled conversation. * fix(native-chat): preserve folded echo anchors * fix(native-chat): preserve forward-folded echo anchors * fix(native-chat): keep leading folded echoes in place * fix(workspace-cleanup): show git status for every row (#16690) * fix(native-chat): refuse structured chat on every Windows execution path canUseStructuredNativeChat only refused win32 when a project runtime resolved, so folder-workspace keys (and other keys with no project runtime) failed open into structured chat on Windows. Fail closed on win32 unconditionally after the host check, matching the settings copy: local macOS/Linux only; Windows/WSL/SSH stay on terminal chat. * fix(native-chat): restore runtime refusals behind the win32 gate |
||
|
|
774ce22e84 |
fix(e2e): drop the pi-title spec's stale private connect fork (#17017)
`ssh-pi-compatible-agent-title.spec.ts` kept a private `connectDockerRemote`
that predates #11003. Commit
|
||
|
|
c4b39295c1 |
style: format codebase (#16935)
* style: format codebase * style: format codebase * refactor: extract skill install dialog footer and content Extract footer and content sections from SkillInstallDialog and SkillInstallManagementDialog into separate components for improved maintainability and clarity of component responsibilities. |
||
|
|
59515beb70 |
fix(release): recover immutable patch validation gates (#16984)
* fix(release): recover immutable patch validation gates * test(e2e): locate wrapped terminal file links * test(e2e): keep sibling file links on one terminal row |
||
|
|
b1fe9075db |
fix(ssh): never claim authority over host tabs this client could not place (#16956)
* fix(ssh): a host tab row this client cannot place is unverifiable, not absent
A degraded listLineage leaves worktreesByRepo empty, so exactTargetWorktreeIds
returns nothing, every host path fails to resolve, and importRemoteWorkspaceSession
silently dropped every tab row. The apply then marked the target hydrated and
'synced', freezing that emptiness in permanently — nothing re-pulls a hydrated
target (STA-3593).
The importer now reports unplaceable rows, the apply claims authority only when
every row landed, and a bounded chain re-pulls the missing input (catalog +
lineage). On exhaustion it settles back to the pre-fix behaviour so a genuinely
unplaceable path is never left worse off than today.
* fix(ssh): keep the re-pull chain bounded, unwedgeable, and announced once
Five defects in the first cut of the chain, all found before merge:
1. the caller owned the attempt counter, so an unsolicited host push re-armed
it at 0 and the chain never exhausted - an unbounded workspace.get loop;
2. exhaustion never cleared the counter, so after one bad connection every
later reconnect re-exhausted instantly and the retry was silently dead;
3. the exhaustion check preceded the armed-timer guard, letting a concurrent
report cancel the still-pending final retry;
4. a rejected host read left no timer armed and nothing rescheduled, stranding
the target on 'pulling' and un-hydrated forever - and an un-hydrated target
never uploads again, the exact permanent degradation this design avoids;
5. exhaustion re-announced on every later report, re-marking hydrated and
rewriting status on each host push.
The module now owns the counter, resetTarget gives each connection a fresh
chain, the armed guard precedes exhaustion, the timer body always reschedules
so any failure walks to exhaustion, and exhaustion is announced once.
* fix(ssh): never authorise uploads from a picture known to be incomplete
Reversal of this branch's own exhaustion fallback, on evidence.
Hydration authorises uploads (use-app-session-persistence.ts), and an upload is
a workspace.patch of kind 'replace-session' (remote-workspace-relay-sync.ts:66)
which wholesale replaces the host snapshot (relay/workspace-session-handler.ts).
So marking a target hydrated on a picture we know is missing rows does not
'settle back to the old behaviour' - it uploads an empty projection that DELETES
the host tabs we failed to adopt. Suppressed uploads are recoverable; a wiped
host snapshot is not. That data loss is reachable on main today, because today
the apply marks hydrated immediately.
Exhaustion therefore reports 'error' and leaves the target un-hydrated, so
terminal authority stays 'unverifiable' and no upload can be built from it.
Also closes two chain-lifecycle gaps found in review:
- the callback dropped its timer guard before awaiting the host, leaving a gap
in which a concurrent report armed a second overlapping chain and could trip
exhaustion before the pending apply resolved; an in-flight guard now spans it;
- resetTarget could not cancel a callback already past its await, so a stale one
rescheduled on top of the new connection's chain; chains are now generation
stamped and a stale callback exits.
* fix(ssh): scope re-pull in-flight ownership to a generation
Two races found in review of the previous commit:
- schedule(target,'placed') cleared timer/count/exhaustion but did not
invalidate an apply already in flight. When that apply later resolved
'unplaced' its generation still matched, so it started a fresh chain from
attempt 0; a host repeating placed pushes during each in-flight retry could
reset the budget indefinitely. Retirement now bumps the generation too.
- the in-flight marker was a bare Set, so a superseded callback's finally
deleted whichever marker was present - including one a newer generation had
since taken. A later report could then arm an overlapping timer while that
newer apply was still running. Ownership is now a target -> generation map and
a callback releases only the marker it still owns.
resetTarget deliberately no longer drops the marker: its owner is the only party
that may release it, and clearing it there would let a new chain arm while the
superseded apply is still running.
* fix(ssh): replay an unplaced report that was blocked by a superseded apply
Regression from the previous commit. Keeping the stale in-flight marker across
resetTarget stops overlap, but it also swallows the new connection's result: the
new apply reports 'unplaced', hits the guard because the superseded apply still
owns the marker, and the superseded callback then exits on its stale generation
without scheduling. Nothing replayed the dropped report, so no chain started -
the retry silently never ran for that connection.
A blocked unplaced report is now recorded, and the marker's owner replays it on
release. Only the stale path reaches the finally still owning the marker, so the
normal path - which released early and scheduled its own outcome - cannot replay
twice.
* refactor(ssh): drop the re-pull retry chain, keep the fix
The chain produced ten defects across review - unbounded retry, dead retry,
cancelled final attempt, wedged chain, repeated exhaustion, overlapping chains,
stale-callback cleanup, a lost report - every one in code that passed the full
suite at the time. It bought only faster recovery *within* one connection:
syncAfterConnect and applyUnsolicitedSnapshot already re-pull on the next
connect or host push, so dropping it costs a retry, never the data.
What remains is the part that was correct from the first commit: the importer
reports rows it could not place, and an apply that could not place them neither
marks the target hydrated nor sets 'synced'. Because hydration is what
authorises uploads, and an upload wholesale replaces the host snapshot, that
single rule is what stops a client from deleting the host tabs it failed to
adopt.
Status is now 'error' rather than 'pulling': with no chain pending, 'pulling'
claimed a request that was not in flight.
* test(ssh): name the upload-suppression case for the chainless design
* fix(ssh): revoke stale hydration and keep authority unverifiable when unplaced
Two holes in the previous commit, both found in review.
The hydrated set is add-only (ssh.ts), so withholding hydration only protects a
target that never synced. A target that synced cleanly and then reconnected with
a degraded lineage kept its flag, and hydration is what authorises uploads - so
it would still send a replace-session patch built from the incomplete picture
and delete the host tabs it had just failed to place. Hydration is now revoked,
not merely withheld.
The status phase was 'error'. workspace-terminal-host-authority.ts treats
'offline'/'error' on an un-hydrated target as its bounded floor and resolves
them to 'none' - which authorises seeding AND sleeping-agent resume, the exact
double-resume this gate exists to prevent. 'conflict' is the phase that actually
describes the situation, is excluded from uploads by use-app-session-persistence,
and is deliberately outside that floor set, so authority stays 'unverifiable'.
Both invariants are pinned by tests verified to fail when either fix is reverted
individually; the pre-existing tests passed with both reverted.
* fix(ssh): drop the mismatched message on the unplaced conflict status
The phase drives the user-visible label ('Workspace sync conflict'); carrying an
'unavailable' message alongside it only risked contradicting that wherever the
message is surfaced.
* docs+refactor(ssh): correct the authority floor's premise, drop a dead wrapper
Two findings from the post-merge correctness sweep.
The bounded floor in workspace-terminal-host-authority.ts justified itself on
'remoteWorkspaceHydratedTargetIds is add-only, clearRemoteWorkspaceHydrated has
no production caller'. This branch adds that caller, so the premise is now false
and a future reader would have been misled by it. The comment records the real
consequence: a target that later lands on offline/error reaches the floor having
demonstrably answered, so seeding is authorised over live host terminals. Not a
regression - before revocation existed the same target was marked hydrated and
synced, reaching 'none' sooner - but the floor should learn to tell a revoked
target from one that never answered. Flagged for the SSH-v3 consolidation, where
one authoritative liveness source replaces this pair.
applyUnsolicitedSnapshot had become a pass-through to applyPreparedSnapshot,
carrying a docstring about a re-pull chain that no longer exists. The two
collapse back into one function.
* refactor(ssh): delete the DirectSshSnapshotPlacement union
Consolidation pass finding. The union was exported and threaded through two
modules, but no production consumer ever read it: remote-workspace-ipc-bridge.ts
discards the promise's value and syncAfterConnect ignored it. 'not-applied' was
not a placement at all, only 'this apply did not happen'.
That is a parallel verdict concept with no consumer - precisely what the SSH-v3
consolidation would have had to unpick. It collapses to a local
hasUnplacedTerminalTabs boolean and a void return.
The one test that asserted the return value now asserts adoption instead, which
is the observable outcome rather than a proxy for it. All five unplaced oracles
still fail when the placement decision is forced, verified individually.
* docs(ssh): compress the tombstone rationale to its load-bearing WHY
Elegance pass. Kept the two non-obvious claims - absence cannot distinguish
'never told' from 'user closed', and uuid tab ids make a tombstoned id safe to
drop - and cut the incident narrative around them. The twice-reverted history in
remote-workspace-session-merge.ts is deliberately left alone: that one is
institutional memory about regressions, not restatement of the code.
* test(ssh): pin the fixed behaviour instead of the defect it replaced
The spec was a characterization test whose own title said 'because hydration is
marked even when adoption wrote nothing', and whose comment described exactly
the defect this branch fixes: markRemoteWorkspaceHydrated ran unconditionally
after the hydrate calls, so in the same tick adoption yielded zero, authority
flipped unverifiable -> none, and Terminal.tsx seeded a phantom tab. It polled
for hydrated === true, so the fix turned it red.
It now asserts hydrated === false, phase === 'conflict', and zero tabs - the
count measured before asserting rather than assumed, confirming the phantom seed
is gone. The phase is re-read after the tabs settle and asserted a second time,
because a conflict verdict a later apply flipped back would silently re-authorise
seeding and a single poll would miss it.
The fixme stays a fixme: this branch stops the client claiming false authority
and overwriting the host, but adoption is still the open gap. Declining to seed
is a safe wait, not the destination.
Three-legged A/B against fork point
|
||
|
|
cb848647e5 |
fix(browser-preview): require explicit preview capabilities (STA-5758) (#16921)
* fix(browser-preview): require explicit preview capabilities (STA-5758) Scope document reads to approved directories, confirm external links before opening them, revoke grants with tab lifecycle, and keep document-preview session state rollback-safe across mixed client/runtime versions. * Harden document preview lifecycle and permissions * Document preview DNS prefetch residual * Make preview E2E guest focus explicit * fix(browser-preview): entry-file-only authority for root-level docs, contained chip layout, re-issued gate paths (STA-5758) A grant whose document directory is its own request base — a doc at the workspace root, or outside any workspace — now reads nothing but the entry file until the reader approves a directory, at both the lexical and the canonical containment pass. The DNS-prefetch residual can only beacon what the page can read, and a root-level document could previously read the whole worktree silently. The identity chip's host badge overflowed the chip's layout box under squeeze (Linux CI): every row member can now shrink and truncate, verified by a width sweep in isolated Chromium down to ~120px chips. The Allow banner says what it grants: 'Allow folder', reading files in the named directory, for the life of the preview. The reliability-gate manifest command, testFiles entry, assertion refs and dated evidence naming the deleted doc-preview-external-link-bridge.test.ts are re-issued at doc-preview-external-link-confirmation.test.ts with a fresh 189/189 run; the focus-gate assertion text follows the shipped gate. * fix(browser-preview): hide the chip identity row below 24rem instead of clipping it, ellipsize the host badge, catalog the new i18n keys (STA-5758) CI's preview pane leaves the chip ~40px: no truncation shows anything there, so the Workspace-file label and host badge now hide whole below a 24rem container threshold sized so that visible implies contained. The badge text gains an inner text box — text directly inside the flex pill clipped both ends with no ellipsis. The e2e geometry oracle asserts containment when the row shows and the threshold when it does not. verify:localization-catalog: the hardening's new preview keys (and the renamed allowDirectory) join en.json via sync:localization-catalog. * feat(browser-preview): batch blocked folders into one access decision (STA-5758) Sequential per-folder banners trained the allow reflex without adding judgment — a reader cannot weigh assets/ against data/. The banner now accumulates every folder a load surfaces, names them (three, then a count, full list in the title), and grants exactly that set with one Allow-N-folders click and one reload. Dismiss fences the whole named set. The map lives behind a ref with a version tick so a dismissal fences an offer landing in the same event batch. |
||
|
|
b19a397d3e |
feat(browser-preview): reland remote HTML document previews (STA-5758) (#16920)
Reapply the reverted remote HTML document preview implementation so remote workspace files render locally over the orca-preview scheme. |
||
|
|
2b391652b1 |
fix(terminal): a close the host never heard must survive the reconnect (#16752)
An enterprise user: "Every day I open orca and it opens more tabs daily at a linear scale." Three reports over a week, told on 08-19 that a PR had fixed it, reported twice more after. STA-4658 (P0), GH #12447, #15136, #10342, #9585. One install held 39 zombie tab records. The revived tab's sleeping-agent record still holds the pre-close session id, so it boots `claude --resume <old id>` -- two agents on one transcript. ## The chain, measured Reproduced deterministically in `ssh-lost-kill-tab-resurrection.spec.ts`: close an SSH tab, kill the relay daemon in the container so `pty.kill` rejects with a transport-class error, reconnect. drop 2 resurrected the closed tab <id>: baseline=1 drop1=1 drop2=2 (closed tab returned) drop3=1 The trigger is narrow and had to be measured rather than assumed: killed relay daemon reproduces **6 of 6 runs**; an orderly `ssh.disconnect` **passes**. Only an ungraceful loss -- network partition, host reboot, relay crash, a laptop sleeping mid-session -- strands the close with the RPC rejecting on a transport-class error. Both variants live in the spec behind one `runResurrectionCycles` parameterized solely by the disruption, so the difference is attributable to that single variable. What actually carries the tab back, from the pull path (`workspace.get` -> `getRemoteSnapshot`, `remote-workspace-relay-sync.ts:29`): pullSnapshot rev=3 tabs={repo:["16c4a3e1","06aba6b6"]} pullSnapshot rev=4 tabs={repo:["16c4a3e1","ff72768e"]} <- ff72768e IS the resurrected tab pullSnapshot rev=5 tabs={repo:["16c4a3e1","ff72768e","da21b76c"]} The client uploaded the session containing the tab; the user closed it; the kill RPC rejected so the close never reached the host; the host's snapshot still lists it; the client pulls it back and the merge restores it -- **correctly, by its own rule that the host is authoritative for what it knows.** A pane then mounts, respawns, and takes the recycled pty id. Client-side correlation from the same run, two controls and one positive in one run differing in exactly one variable: | Tab | Close events observed | Resurrected? | |---|---|---| | `6305cc07` | `user` + `pty-exit` | No | | `ed56f66c` | `user` + `pty-exit` | No | | `2036e760` | `user` only | **YES** | ## The fix `src/shared/closed-terminal-tab-tombstones.ts` (99 lines). A client-recorded close is first-party intent and must survive until the host acknowledges it. Per `docs/reference/ssh-execution-boundary.md` the remote verdict is `unverifiable` -- which may not authorise declaring the process dead, but equally must not authorise resurrecting the tab. This is SSH-v3 principle P2, "durable tombstones with a monotonic per-scope revision", reusing the existing `RemoteWorkspaceSnapshot.revision` rather than adding a twelfth per-tab identity field (the codebase carries eleven, 784 refs, that SSH-v3 Phase 3 deletes). - **Recorded** only on `closeReason === 'user'` (`terminal-tab-close.ts:69`). - **Suppresses** a host-sourced tab only when `tabId in tombstones && !currentTabsById.has(tabId)` -- a live local tab always wins, because deleting a live pane is the one outcome the merge exists to avoid. - **Retires** on positive acknowledgement: `!hostKnownTabIds.has(tabId) && hostRevision > observed`. Strictly newer, so a pull already in flight at close time cannot ack a close it predates. - Three never-retire guards: no revision retires nothing; a worktree the snapshot has no row for retires nothing; the first omitting snapshot only stamps the watermark. - TTL (30d) + cap (500) are **backstops** for a target the user never returns to, not the mechanism. - **Client-local only** -- never crosses the wire, so there is no mixed-version exposure. - Suppression is scoped to `replaceWorktreeIds`, which is what makes the live-tab check meaningful. A final whole-map sweep over the assembled `tabsByWorktree` would break that (a live tab is absent from `currentTabsById` outside the scope and would look suppressible); it is deliberately not there, and the comment at the top of the function says so. ## Evidence The load-bearing evidence is an A/B control on one tree, not the oracle's assertion. Flipping `isSuppressedByClose` to `false` -- one character -- reproduces the resurrection on demand: --repeat-each=2: 1) drop 2 resurrected the closed tab ab0e305d-…: baseline=1 drop1=1 drop2=2 2) drop 2 resurrected the closed tab 51533e34-…: baseline=1 drop1=1 drop2=2 2 failed With suppression on: **0 occurrences of "resurrected the closed tab" across five runs plus one independent run by a second agent.** Provenance verified positively, not by mtime: `closedTerminalTabTombstonesByTabId` appears 13x across 3 renderer chunks including `store-Do3KBvRE.js`; for every red control run `mayCreate` appeared 0 times in `out/main/index.js`. At the unit layer, disabling the same predicate: 3 failed | 39 passed. Restored: 42 passed; 287 across the workspace-session, terminal-store, remote-workspace, shared-tombstone and profile suites; 24 in the four tombstone suites. ## The oracle spec: GREEN in the full lane `ssh-lost-kill-tab-resurrection.spec.ts` passes both tests at this commit. Full Docker-SSH lane, clean tree: BUILD_SHA=49bb96e0b4c DIRTY=0 PROVENANCE tombstone=13 hasLocalTabsRow=2 hostAuthority=4 mayCreate=3 14 specs / 20 tests -> 17 passed, 2 failed, 1 skipped (10.7m) [12/20] :178 does not resurrect tabs whose kill was lost to a killed relay daemon PASSED [13/20] :190 does not resurrect tabs closed while the host is disconnected PASSED grep -c "resurrected the closed tab" (whole lane) -> 0 It passes WITHOUT PR 7 in the build (`mayCreate` present, `SshPtyAbsentFromRelayError` absent), so the bug-2 fix below is not required for it. Test 1 fails intermittently in ISOLATED single-spec runs, where a third defect blocks its cycle-2 setup. The resurrection assertion itself has never failed with this fix in place -- the intermittent failure is always a setup failure, never a resurrected tab. A reviewer running the spec alone may see it red; that is not this fix regressing. Three defects sit under STA-3374 and should not be conflated: - Bug 1 -- the closed tab resurrects. Fixed here. - Bug 2 -- `ssh-pty-session-reattach.ts:227-231` rewrites the relay's `PTY "pty-1" not found` into a bare `SSH_SESSION_EXPIRED`, so `isPtyAlreadyGoneError`'s `/PTY ".+" not found/` cannot match and `attachStablePaneOwner:242`'s already-correct fallback never runs. Owned by PR 7 (`nwparker/ssh-07-absent-from-relay`). Not required for the oracle above. - Bug 3 -- after the daemon is killed and the client launches a replacement, the client's OWN SSH transport drops and does not reconnect within 60s: no "delay step 2/9", no handshake failure, nothing. `ssh-connection.ts:1533` only logs on an SSH-level close. Unfixed, its own ticket. This is what makes test 1 intermittent in isolation. Discriminator for bug 3, measured in the isolated runs (the lane above ran without `ORCA_E2E_FORWARD_APP_LOGS=1`, so it was not re-confirmed there): `[ssh-relay] Socket probe result:` reads "DEAD" on every cycle of test 1 (daemon killed, a NEW relay must be launched) and "ALIVE" on every cycle of test 2 (daemon survived). Whenever a new daemon must be launched, the SSH transport drops afterwards and does not recover. An earlier reading blamed `kill.ts:82-84` for skipping `finishPtyShutdown` on a non-already-gone error. That was eliminated by direct test: the implied fix, `markSshRemotePtyLease(…, 'expired')` in that branch, was implemented, changed nothing, and was reverted rather than shipped unproven. Recorded so the path is not re-walked. The `SSH_SESSION_EXPIRED` rejection is real but fires during cycle 1 for the baseline pane, after which cycle 1 completes; the 60s silence begins only after `Relay channel lost ..., triggering reconnect`. The spec is claimed by the Docker-SSH lane, and that lane does not gate merges today. ## Persistence: the tombstone must survive a relaunch `closedTerminalTabTombstonesByTabId` is declared on `WorkspaceSessionState` but was missing from `workspaceSessionStateSchema` (`src/shared/workspace-session-schema.ts`), which is the load boundary for BOTH partitions -- `normalize-loaded-state-collections.ts` for `local` and `workspace-session-partitions.ts` for `ssh:<target>`. Zod strips unknown keys and the write side does not validate, so the map reached disk and was discarded on the next launch. Measured with the repo's own parser: input : closedTerminalTabTombstonesByTabId: { 'tab-1': {...} } ok = true tombstones after parse = undefined That made the fix ineffective in the exact reported scenario: close an SSH tab with the transport down, QUIT, relaunch, reconnect -- the merge runs with an empty map, the host still lists the tab, and it resurrects. "Every day I open orca and it opens more tabs" is a claim about restarts. Neither the green oracle nor the A/B control could see it: both run entirely inside one app process. It also made the 30-day TTL and the 500 cap unreachable. Fixed by adding the field with a `salvagingRecord` matching its sibling `terminalSurfaceTombstonesByPaneKey`, so one malformed entry drops that entry rather than the map. `workspace-session-schema.ts` was one line under its 300-line max-lines limit, so adding the field required room rather than a suppression (the project forbids max-lines disables and per-file bumps). Two value schemas were extracted to modules named after what they contain: `terminal-tab-id-schema.ts` and `terminal-surface-tombstone-schema.ts`. The closed-tab tombstone's own schema is colocated with its type in `closed-terminal-tab-tombstones.ts`, which is where it belongs -- omitting it from the session schema is exactly the drift that caused this bug. `workspace-session-schema-field-coverage.test.ts` is the ratchet. Two sibling tables already pin themselves with `satisfies Record<keyof WorkspaceSessionState, ...>`; this schema had no such guard and is the one that fell behind. The new file adds both halves -- a `satisfies` list that makes a forgotten field a compile error, and a runtime assertion that names it -- plus a `parseWorkspaceSession` round-trip. Without the schema entry: 3 failed. With it: 3 passed. ## A host tab the user never closed could be deleted `tabId in closedTerminalTabTombstonesByTabId` answers true for every `Object.prototype` key even on an EMPTY map, because the map is a plain object from `Object.fromEntries`. A host tab whose id is `toString` was filtered from the reconciled list, blocked from the host-unknown branch, and stripped of its layout and session id. Tab ids are validated only as non-empty and colon-free, and `createTab` honours caller-supplied id hints, so the id is reachable rather than theoretical. This was the only path in either direction that could delete a tab the user never closed. Now `Object.hasOwn`, as the same file already uses elsewhere. Suppression is also scoped structurally: `isSuppressedByClose` compares the tombstone's stored `worktreeId`, which it already carried, so it cannot reach another workspace's tab. The two sweeps that have no worktree in scope (`terminalLayoutsByTabId`, `remoteSessionIdsByTabId`) now consult the set of ids this merge actually suppressed rather than re-deriving a verdict without that scope. The scope comment at the top of the function was also wrong and is corrected. It claimed every use of suppression sits inside `replaceWorktreeIds`; it does not -- the tabs pass walks all of `orderedWorktreeIds` and the two sweeps cover the whole remote maps. What actually makes it safe is that `closeTab` strips the id from every worktree row before recording the tombstone, plus the worktree match above, plus `closeReason === 'user'` being the only writer. Real guarantee, different from the documented one. ## Divergences from open PR #16571 #16571 implements the same concept. Three deliberate changes: 1. It never retires on acknowledgement -- TTL+cap only, so it never converges. Ack retirement added. 2. It crosses the wire and lets a HOST-sourced tombstone delete a LOCAL tab in a final whole-map sweep. After #14361 that is the wrong risk; dropped. This also removes the mixed-version regression its own body flags. 3. Its hydration unions rather than replaces the map -- a union resurrects every tombstone the merge just retired, so it never converges. Its `activeTabId` nulling is also dropped as redundant: `workspace-terminal-hydration.ts:99-105,126-138` already revalidates both pointers against the tab rows it just built, and nulling twice would add a second rule that has to stay in step with the first. ## Can a tab the user did NOT close disappear? No, but the guarantee needs stating precisely. The only writer is `recordClosedTerminalTabTombstone` (`terminal-tab-close.ts:69`), reachable only on `closeReason === 'user'`; suppression additionally requires the tab not be live locally. Reopen (`recently-closed-tabs.ts:122-166`) calls `createTab` and restores cwd/shell/title/color/position, never the old id. **Caveat, stated because the slogan is not literally true:** `createTab` honours a caller-supplied id hint (`terminal-tab-creation.ts:53-65`, used by `useIpcEvents` for host-admitted tabs), so "tab ids are uuids that never recur" does not hold in this codebase. The guarantee rests on the `closeReason === 'user'` writer plus the live-local-tab check, not on id uniqueness. ## Risk Renderer-side, client-local, no wire change. The blast radius is `mergeDirectSshRemoteWorkspaceSession` and the persisted session field. Worst case if the ack logic were wrong in the retiring direction: a tombstone outlives its usefulness and suppresses a host tab whose id the host re-issues -- bounded by the live-local-tab check, the 30d TTL and the 500 cap. Worst case in the other direction is today's behaviour. `profile-project-session-field-disposition.ts` records the new field as `notRepoScoped` / `notTransferred` residue, bounded by the same TTL and cap. ## Verify pnpm test src/shared/closed-terminal-tab-tombstones.test.ts \ src/renderer/src/lib/workspace-session-closed-tab-tombstones.test.ts \ src/renderer/src/store/terminals/terminal-tab-close-tombstone.test.ts \ src/renderer/src/hooks/remote-workspace-session-merge-close-tombstones.test.ts To reproduce the bug this fixes, set `isSuppressedByClose` to `() => false` in `remote-workspace-session-merge.ts` and run `pnpm test:e2e:ssh-docker -- tests/e2e/ssh-lost-kill-tab-resurrection.spec.ts --repeat-each=2`. |
||
|
|
e06a8667a9 |
fix(terminal): do not seed or resume while the execution host has not answered (#16750)
Two client behaviours read local tab rows as the verdict on what the execution host is running. Before the host answers, "I hold no pane for this" is `unverifiable`, not `exited` -- the collapse `docs/reference/ssh-execution-boundary.md` forbids. Symptom 1, seeding. `worktree-initial-terminal-seeding.ts:47,128` seeds a terminal when `renderableTabCount === 0`. Its only bail-out (`:72-77`) covered the paired-web-runtime flavor -- "while that session is live the host owns terminal creation" -- with no equivalent for direct SSH. So a client that has never held the workspace runs the predicate during the hydration gap and creates a tab from nothing. The snapshot then arrives, the merge rightly keeps the tab it was never told about, and the union uploads as the new host truth. Measured on a fresh client against a host owning 3 tabs: **1 tab created from nothing, 0 of the host's 3 adopted.** (A restart never reaches the predicate -- local state restores the row first -- which is why restart-only repros came back flat.) That guard was also the wrong question. It asked "am I a client of a live paired session?", which a host desktop window answers "no" and a paired client answers "yes", so both seeded -- #15556. Symptom 2, sleeping-agent resume, and the data-corrupting half. `Terminal.tsx:1554` calls `resumeSleepingAgentSessionsForWorktree` twenty lines after the seeding call at `:1529-1534` -- same startup path, same pre-hydration window, and not SSH-gated at all. Seeding produces a spare empty tab; the sweep launches `claude --resume <id>` for a session still running on the remote and still owned by a live pane. Two agent processes writing one transcript; STA-3498 observed five. STA-3500 files exactly this race. Failure is asymmetric: declining to resume is user-recoverable, a duplicate resume corrupts a transcript irreversibly. `workspace-terminal-host-authority.ts` answers the one ownership question both paths ask, in the three-verdict vocabulary the renderer already uses for host terminal inventory (`HostLiveTerminalProbeVerdict`, aliased rather than restated so the two cannot drift): `live` (a remote host owns creation here), `unverifiable` (there is a remote host and it has not answered), `none` (local, or the host answered and holds nothing). Seeding requires `none`; the sweep declines on `unverifiable` without consuming its one-shot, so the agents are not stranded for the session once the verdict lands. Shape notes: - An ownership question, not a client-liveness one -- that is what fixes #15556. - Folder workspaces resolve to `none`: the snapshot replaces exactly `DirectSshTargetScope.gitWorktreeIds`, so a folder's rows are never replaced by the host and waiting for an answer that will never name them would leave it terminal-less for good. - A `conflict` sync phase is `unverifiable`, matching the pair `use-app-session-persistence.ts` already gates uploads on. - Explicit launch work (setup/issue commands) stays ungated -- that is a request to create a terminal now. - `Terminal.tsx` subscribes through a retained selector rather than reading in the effect: the verdict flipping to `none` is what must re-run the passes, and resolution walks the owner catalogs, so recomputing per store write would be the STA-3363 render-path multiplier again. The `unverifiable` verdict is BOUNDED, and must be. `remoteWorkspaceHydratedTargetIds` is add-only in practice -- `markRemoteWorkspaceHydrated` has two production call sites, both on success paths, and `clearRemoteWorkspaceHydrated` has NONE. Four paths return without marking: local-hydration timeout (`remote-workspace-target-sync.ts:136-145`), a null `remoteWorkspace.get` (`:160-169`), a falsy apply token (`:172-185`), and never connecting at all. Without a floor, any of them would leave every git worktree on that target `unverifiable` for the rest of the app session: no initial terminal, no sleeping-agent resume, escapable only by creating a tab by hand. That is strictly worse than the behaviour it replaces -- on main the user got a terminal. So a sync that terminates in `offline` or `error` without ever hydrating resolves `none`: declining to seed is meant to be a wait, not a permanent refusal. `pulling` still declines, and a target that HAS hydrated stays `none` even if a later sync errors. Scope, stated because the doc comment previously overstated it: this gate is first-hydration-per-target, not per-connection-generation. Since nothing clears the flag, a disconnected target that hydrated once reads `none`. It does not cover mid-session reconnect or sleep/resume. The memo's input list is checked for COMPLETENESS, not just membership. `satisfies readonly (keyof State)[]` only proves each listed key exists; a field added to the state and forgotten from the list would type-check while making the memo return a stale verdict -- silent, and it looks like "the gate did not fire". A conditional type now names the missing key at compile time. Deliberately not `const x: Missing[] = []`, which passes regardless because an empty array literal is assignable to every array type. Known limitation, stated rather than hidden: the SEEDING half of this change has no measurable end-to-end effect today, and the branch's own e2e spec says so. `applyDirectSshRemoteWorkspaceSnapshot` calls `markRemoteWorkspaceHydrated` unconditionally AFTER the hydrate calls -- including when they wrote nothing. So in the same tick adoption yields zero, the verdict flips `unverifiable` -> `none`, `Terminal.tsx` re-runs the effect, and it seeds. The gate cannot outlive the failure it guards against, because the same function that fails to adopt is the one that lifts it. `ssh-cold-hydration-gap-tab-seeding.spec.ts:218` is named for what it asserts -- one tab, adopted none -- rather than for the behaviour we want. The fixme at `:293` pins the intended behaviour. Making the seeding half effective needs hydration resolved PER WORKTREE (or a refusal to say `none` when the completed apply's `replaceWorkspaceKeys` did not name this worktree) rather than a per-target "some apply finished" flag. That is deliberately not in this commit. The RESUME half is the valuable half and is unit-proven: it declines while the host is unanswered and wakes the same session once the verdict lands, without consuming its one-shot. Preventing one duplicate `claude --resume` on a live transcript is worth more than preventing one spare tab -- declining to resume is user-recoverable, a duplicate resume corrupts a transcript irreversibly. Before: 7 failed | 3 passed. After: 10 passed; 103 across the seeding, resume, authority and remote-workspace suites. |
||
|
|
971d987c4b |
ci(e2e): trigger the Docker-SSH lane from SSH source and claim every gated spec (#16746)
The Docker-SSH e2e lane only ran when a PR's changed specs happened to include
`ssh-startup-exec-readiness.spec.ts` or `paired-startup-exec-readiness.spec.ts`.
Editing SSH source itself did not trigger it, and pruning either spec from a
route's list would have silently retired the whole lane. Meanwhile the sharded
lanes set no `ORCA_E2E_SSH_DOCKER`, so every Docker-gated spec skipped itself
while the shard still reported green -- the exact silent-skip shape
`docs/reference/ssh-reconnect-source-recovery.md` blames for four regressions
that reached users.
Separately, the modules that actually own direct-SSH workspace and tab restore
carry no "ssh" in their names, so the `ssh-terminal-source` route never reached
them. Measured on the real script before this change:
printf '%s\n' src/renderer/src/hooks/remote-workspace-session-merge.ts \
src/main/ipc/remote-workspace-snapshot-normalization.ts \
src/renderer/src/lib/worktree-initial-terminal-seeding.ts \
src/shared/remote-workspace-session-projection.ts \
| node config/scripts/pr-e2e-source-routing.mjs
=> []
Three changes, all pinned by the executable gate contract:
- `hasSshSourceChange` derives an `ssh_source_changed` signal from the SSH
routes themselves, plumbed pr.yml -> e2e.yml, so the lane triggers on source
rather than on a spec name surviving in a list. One list, so the two cannot
drift.
- A sibling `ssh-workspace-session-restore` route names the restore seams
(`remote-workspace-*`, `worktree-initial-terminal-seeding`,
`worktree-default-terminal-tabs`, `initial-terminal`) and routes them to the
two restore specs -- a sibling rather than more paths on `ssh-terminal-source`
so a tab-tombstone edit does not run the whole SSH terminal list.
- A new `test:e2e:ssh-docker` runner claims the remaining Docker-gated specs on
the one VM that sets the flag, and the contract now fails by name when any
Docker-gated spec is claimed by no runner. `ssh-docker-relay-perf` and
`ssh-codex-display-artifacts-repro` are recorded exemptions (wall-clock
budgets; needs a real remote codex binary) and the contract asserts each
exemption still corresponds to a real gated spec, so a stale one cannot
quietly excuse a gap. Lane timeout raised 35 -> 60 minutes for the added
serial specs.
The lane's first act was to surface four latent bugs in a spec that had been
silently skipping. `ssh-docker-bulk-open-freeze-repro.spec.ts` is four call sites
out of date against `tests/e2e/helpers/terminal.ts`: `startDockerSshRelayTarget()`
is called with no argument though the helper dereferences `testInfo.workerIndex`
(a 100% failure, not a flake), `execInTerminal` gained a `ptyId` parameter, and
`splitActiveTerminalPane` gained a direction. It was invisible because it ran
nowhere and `typecheck:e2e` is red on main with 240 pre-existing errors, so four
more could not be seen.
The `testInfo` bug is fixed here -- correct on its own, and it removes one real
error from `typecheck:e2e` (240 -> 239). The other three are not, because they
are not argument plumbing: repairing them requires choosing which ptyId to
capture and which split direction to use, and both change what the repro
measures.
The spec is therefore added to the exemption list rather than repaired, for two
independent reasons recorded in the runner: it is a perf oracle, not a
correctness one (`SOFT_FREEZE_LAG_MS=2500` / `HARD_FREEZE_LAG_MS=5000` measured
under a deliberate 5-pane flood on a 420s budget -- the same rule already applied
to `ssh-docker-relay-perf.spec.ts`), and it is known-rotted. Repair is tracked in
stablyai/orca#16764. Applying an existing written rule to a sibling that plainly
meets it is consistency; inventing a new exemption to dodge a red would not be.
Three hardening fixes to the contract itself:
- Runner text is comment-stripped before the claimed-by-a-lane scan. A substring
scan over raw text lets a spec merely *discussed* in a runner comment count as
claimed -- the silent skip this assertion exists to catch, re-entering through
the documentation. Not live today only because the existing comments write the
spec names without their `tests/e2e/` prefix.
- An exempt spec must not be invoked by any runner. `unreachableSpecs`
short-circuits the unclaimed check, so a spec could be documented as exempt
while a runner still ran it -- an exemption that reads as coverage removal but
changes nothing, leaving the lane red for a reason the file says it excluded.
This is not hypothetical: adding the bulk-open exemption without removing it
from the runner's spec list produced exactly that state, and this assertion is
what caught it.
- The Docker-gate detector is now `/ORCA_E2E_SSH_DOCKER\s*[!=]==\s*['"]1['"]/`
rather than one fixed string, so a double-quoted or `!==` spelling can no
longer escape the contract.
`ssh-restart-tab-accumulation.spec.ts` is a new three-cycle restart fence
asserting tab-id set identity, not just the active pane's reclaimed ptyId as
`ssh-cold-activation-restore.spec.ts:241` did. It passes today; it was validated
by a negative control that injected one tab after cycle 1 and correctly failed.
|
||
|
|
1320a2a953 |
Support nested toggles as editable blocks with recursion guards
Previously nested details blocks were preserved as inert passthrough HTML. Now, nested details that themselves meet editability criteria are opened as editable toggle nodes. Recursive validation includes a 16-level nesting limit to prevent stack exhaustion on pathological input. Refactors common markdown editor test helpers into a reusable fixture module. |
||
|
|
551fbb9ac7 |
Revert "feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679)"
This reverts commit
|
||
|
|
6c0de76ced | Split port scanning and terminal helpers (#16765) | ||
|
|
913509edeb |
fix(orchestration): prevent slow worker-start stalls (#16300)
* Extend orchestration agent submission timing budgets * fix(orchestration): preserve mutation recovery identity * fix(orchestration): preserve recovery executable identity * fix(orchestration): keep worker starts and recovery commands safe * test(orchestration): cover federated worker preflight * fix(orchestration): harden mutation recovery * fix(orchestration): redact dispatch recovery credentials * chore: preserve upstream skill dialog formatting * test(orchestration): stabilize agent prompt submit e2e * fix(orchestration): validate federated start receipts * perf(runtime): cache unchanged prompt verification tail * fix(orchestration): reject worker-start timer overflow * fix(orchestration): normalize worker-start timeout defaults * fix(orchestration): normalize worker-start readiness budgets * fix(orchestration): normalize federated readiness timeout * test(runtime): tolerate current-main degradation exports * chore: preserve current-main orcad formatting * chore: drop unrelated formatting carryover |
||
|
|
249d93bc5d | feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679) | ||
|
|
f400f8fd5f |
fix(macos): opt out of press-and-hold so held keys repeat (#14746) (#15589)
* fix(macos): opt out of press-and-hold so held keys repeat (#14746)
macOS routes press-and-hold to the accent picker unless an app sets
ApplePressAndHoldEnabled=false for its own bundle, so holding j in vim
inserted one character instead of repeating. Orca never set it.
Written at most once, and never over an explicit value: `defaults read`
is domain-scoped and exits 1 when the key is absent, which is the only
way to tell "unset" from a deliberate false — Electron's
systemPreferences.getUserDefault reports false for both. A recorded
decision in userData keeps a later launch from re-clobbering a user who
deletes the key to get the accent picker back.
* docs(macos): record the revert hazard and CI's macOS test gap
Two things a reader of this module cannot otherwise know.
A revert leaves the key written in every user's domain forever. AppKit reads
the plist, not this file, so removing the code alone keeps press-and-hold
disabled for everyone who ran an affected build. The sibling period-substitution
module carries the same warning because that fix was already lost once this way.
And the real-binary test file that pins the defaults(1) exit-code semantics this
design rests on never runs in CI: the e2e workflow and both unit-test jobs are
ubuntu and windows, and the only macOS runners in the repo are build and
packaging jobs that run no tests. Those six tests plus the real-bundle e2e case
pass on a developer Mac and execute zero times in a green PR, so the comment
should not imply enforcement that is not there.
Refs #14746
* feat(macos): let users turn the accent menu back on (#14746)
Orca disables press-and-hold for its own preferences domain so held keys
repeat. That is the right default, but the way back was a `defaults write`
buried in a source comment: nothing in docs/ or the README mentioned it, and
the preference is per-application, so it silently takes the accent picker
away from the Markdown editor and every other text field too.
Terminal -> Advanced now carries a "Character Accent Menu" switch, macOS and
desktop only. A web client cannot write a macOS preference for the machine the
user is looking at, so the control and its search-index entry are both gated on
that, not on the client's platform alone.
Precedence, which is the part that is easy to get wrong: the setting is
`undefined` until the user touches it, which is what keeps a hand-run `defaults
write` in charge for everyone who never opens the toggle. Once used, Orca owns
the key and writes exactly what the switch asks for -- `ApplePressAndHoldEnabled`
*is* the accent-menu switch, so it maps straight through with no inversion. The
choice is compared against `appliedSetting` in the existing decision record
rather than against the domain, so a `defaults write` made *after* using the
toggle is still the newer choice and survives the next launch. Re-asserting the
value every launch would have reintroduced the clobbering the record exists to
prevent.
The write lands for the next launch, since AppKit reads the preference as the
process starts, so the toggle shows the same restart banner the window-blur
setting uses. That banner is now a shared component, keeping its original
translation keys.
docs/reference/macos-press-and-hold.md records the precedence rules, the
`defaults read` rationale, the revert hazard, and the fact that none of this
executes in CI: every macOS job builds or packages and runs no tests, so the
real-binary and e2e coverage here passes only on a developer Mac.
* docs(macos): stop asserting when AppKit re-reads the press-and-hold key
Five places stated "AppKit reads the preference as the process starts" as
fact. That is the reason given for requiring a relaunch, and it is not
something this change ever measured.
Evidence points the other way: terminal emulators that register this key
after their process has started get key repeat in that same launch, which a
read-once-at-startup model cannot explain.
The relaunch requirement itself still looks right, but for a different and
verifiable reason: the write goes out through a separate `defaults` process,
so this app's own cached copy need not observe it. That is what the comments
now say, with the AppKit question left open rather than answered.
Refs #14746
* docs(macos): correct the startup comment's launch-timing claim
The comment said this call site is "the last point that can still matter for
this launch", which contradicts the rest of the module: the write is assumed
to land for the next launch because it goes out through a separate `defaults`
process. Reported on the PR by @innocarpe, who also supplied the replacement
wording.
Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>
* refactor(macos): probe press-and-hold through the shared spawn chokepoint
`src/shared/child-process/child-process-import-boundary.test.ts` forbids a
direct `node:child_process` import outside its allowlist, and the allowlist only
shrinks — so this module moves to `runProcessSync`, which exists for callers
that genuinely cannot await. This one runs before `app.whenReady()`.
`runProcessSync` returns a non-zero exit instead of throwing it, so the
three-way read decision is re-expressed against `ProcessResult`: exit 0 is an
explicit value, exit 1 is a missing key, and a timeout, a signal kill, any other
exit, or a child that never started all stay 'unknown'. The throw path is now
inside `interpretDefaultsRead` so a spawn failure is reachable from a test
rather than hidden in an untested catch, and the write checks the exit code —
a refused `defaults write` no longer looks like success.
Both boundary-test failures were the same import: with it gone the offender
count returns to 155, so no ratchet baseline is bumped.
* Revert "feat(macos): let users turn the accent menu back on (#14746)"
This reverts commit
|