* 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 cc5669f306.
---------
Co-authored-by: innocarpe <innocarpe@users.noreply.github.com>
* feat(browser): open target=_blank links and unnamed popups in new Orca t
- Treat target=_blank as a new-tab request matching browser behavior
- Route unnamed, featureless window.open() calls to Orca tabs instead of native popups
- Add rate limiting to prevent page-initiated tab loops
- Inherit session profiles when opening links to maintain isolation boundaries
* fix(browser): deny new-tab window.open when renderer is destroyed
Move deny action outside conditional to ensure new-tab intents are
safely rejected even if renderer vanishes mid-open, preventing native
popup fallthrough. Add test coverage and simplify comments.
* Share page-initiated tab budget across opener popup tree
Prevent pages from bypassing the new-tab rate limit by chaining popup
windows. The page-initiated tab quota is now shared by all popups in
an opener tree (root + named children), so child windows inherit their
root's budget instead of each getting a fresh allocation.
When Enter is pressed to confirm a rename, the input unmounts and its onBlur
handler fires as it detaches from the DOM. Without consuming this event, a
second commitRename call would attempt to rename against the old path. Setting
the cancel flag after capturing the new name causes the trailing onBlur to
return early, preventing the duplicate operation.
* fix(grok): stop Orca's Grok hooks from costing anything outside Orca
Orca registers Grok agent-status hooks in the global $GROK_HOME/hooks. Grok
loads that directory on every session, so a Grok run that Orca did not launch
still paid for the hook on every event, and Orca rewrote the file even after a
user had emptied it to opt out (#15518).
The registered POSIX command now guards on ORCA_PANE_KEY before doing anything.
That variable is part of the pane identity Orca injects into terminals it
launches, and unlike the port and token it never comes from the endpoint file,
so it is present exactly when the session belongs to Orca. A standalone session
short-circuits without spawning a shell for the managed script at all. The same
guard is applied to the remote install, because a remote host runs standalone
Grok sessions too.
PreToolUse is no longer registered. It is a blocking hook, so Orca sat on the
critical path of every tool call and doubled the per-tool spawns, for a
transition PostToolUse already reports.
Windows cannot use the guard: the command there must be a single spawnable
token, so it is a bare script path with no shell to evaluate a test. For that
case the hooks are removed when Orca quits -- locally, on WSL guests, and on
connected SSH hosts -- and reinstalled on the next launch. A config the user has
emptied is left alone on startup; turning the setting back on in Settings is an
explicit and later choice, so that path reinstalls.
Removal is careful about what it is deleting. It strips only Orca's own entries,
keeps user-authored ones, and deletes the file only when no hook entries remain
-- keying that off the whole object would leave a stray non-hook key behind, and
the emptied-config check would then read that remnant as a deliberate opt-out
and never reinstall. A config the user has symlinked into a dotfiles repo is
written through rather than unlinked, and is exempt from the emptied-config
check for the same reason: after a quit it is a file Orca emptied, not one the
user did.
Writes go through temp+rename. Grok refuses to build a sandbox profile for a
hook JSON with more than one hard link, so publishing by hard link would fail
any session that started during the write.
Install and removal on remote hosts now read the platform from the same field.
They did not, so a Windows remote whose bridge env was incomplete had hooks
installed and never removed.
Co-authored-by: Siddiqui Qamar <137684575+siddqamar@users.noreply.github.com>
* fix(grok): preserve hook state outside Orca
---------
Co-authored-by: Siddiqui Qamar <137684575+siddqamar@users.noreply.github.com>
* Add all-host automations with scoped ownership and multi-authority suppo
Enable automations to run on multiple hosts (SSH targets and local) with
owner-fenced mutations, scoped list queries per host, and conflict
resolution. Introduces desktop and runtime authorities as distinct
automation storage owners, with per-host caching, invalidation, and
retry scheduling on the renderer. Captures registration generations for
SSH hosts to survive re-adoption. Adds CLI support for destination
selection and conflict recovery.
* Filter automation create projects by destination host
Only offer projects available on the selected destination, preventing
the mismatches that would fail at submit time. Auto-adjust the project
selection if it becomes unavailable when the destination changes.
* Add runtime storage authority support for automations
- Support both runtime and desktop as automation storage authorities
- Make owner preconditions optional for legacy-client compatibility
- Cache automation list projections to improve performance
- Add per-row repo/worktree resolution for cross-authority collisions
- Extend automation.list RPC to always include owner metadata
* Replace child_process.execFile with runProcess for external automations
- Migrate external-manager to use cross-platform runProcess wrapper per child-process safety policy
- Abstract electron app/ipcMain APIs in orca-runtime via environment accessors
- Install fake app environment in automation tests for consistent setup
- Reorganize imports to use specific module paths (ssh-target-registry, agent-detection, browser-error)
- Remove external-manager from child-process import allowlists (no longer violates direct import)
* Unify desktop automation CRUD onto the local runtime RPC surface
The desktop authority now speaks the same automation.* RPC contract as
remote runtimes, via callRuntimeRpc({kind:'local'}) -> runtime:call ->
the shared RpcDispatcher. The automations:list/listRuns/create/update/
delete/runNow IPC arms, their preload members, and every renderer
desktop-vs-runtime transport fork are retired; the runtime methods are
the single implementation of scoped lists, owner fencing, and change
publication for both transports (mobile clients already exercised them).
The desktop probe scheduler's priority lease survives the move as an
AutomationService hook the IPC registration installs and the runtime
methods take, so Orca's own automation traffic still parks queued
external-manager probes.
External-manager scope arms and dispatch-loop plumbing stay on IPC by
design; automation change events keep their existing channels (renderer
ingestion already converges them by authority).
* Remove automation ghost SSH tombstone scanning
This functionality for synthesizing tombstones for automation-referenced SSH
targets is no longer needed as part of the automation system refactoring.
* Refuse orphan automations at dispatch time, not migration time
Remove migration-time disabling of orphan automations and the `enabledDecidedBy` field. Dispatch now refuses orphans at runtime instead, simplifying state management and UI. Orphans are left unstamped and enabled; dispatch refuses to run them via `resolveAutomationRunTarget`.
* Show all automations in flat table with unified filter menu
- Replace host picker component with comprehensive Filters menu supporting status, last run, agent, and host filters
- Flatten automation list layout to single table instead of host-grouped sections
- Add Host column to display execution host for each automation
- Display active filters as removable pills below toolbar
- Delete unused AutomationHostPicker* components
* Add automation owner fencing and destination validation
- New AUTOMATION_OWNER_FENCING_RUNTIME_CAPABILITY for owner preconditions; legacy clients get owner metadata snapshotted at RPC boundary for compatibility
- Editor captures and revalidates automation destination before save, preventing silent retargeting if SSH infrastructure changes mid-edit
- SSH target types now isolate renderer-authored fields; generation is server-owned and stripped by IPC handlers
* Route automation recovery actions to the origin host
When an automation action fails due to owner fencing, recovery verbs
("Update server", "Reconnect") must run on the host where the refusal
originated: the row's captured owner for row operations, or the
destination the create dialog captured, not the list's filtered host.
* Remove external manager scope limitation notices
Consolidate create destination eligibility checks with a unified predicate
and fix the bug where desktop repo IDs could be sent to runtime hosts where
they cannot resolve.
* Persist only store-derived automation contexts, not client-perspective o
Store contexts must never be based on client-provided runContext or sourceContext
values—clients speak a different perspective (e.g., 'runtime:<id>' for host IDs
they assign), and persisting those makes the store projection orphan automations
it actually owns. Derived contexts now take precedence in create and update paths,
with explicit null still honored to clear a value. Tests verify this by simulating
drift after storage and confirming that moves re-derive while toggles preserve.
* test: use explicit baseline for cross-version browser placement test
Pin to v1.4.184 to ensure consistent testing against the release
predating client placement. This avoids coupling the legacy-baseline
bump to unrelated schema refactors in newer versions.
* fix(windows): treat inaccessible processes as alive in tests
When checking process state on Windows, EPERM (permission denied) indicates
an inaccessible but live process. Only ESRCH (process not found) proves
exit. Correct isAlive() to distinguish these cases.
Also add windowsHide:true to child process spawns and use explicit SIGKILL
when force-killing the host process.
* Clarify upstream divergence stats for rebased branches
When a branch is rebased, it still tracks the pre-rebase upstream
while comparing against the new base. Move upstream arrows to the
head line to prevent them being confused with compare-base counts.
* Show upstream divergence stats independent of compare base
Measure HEAD against upstream regardless of compare-base state,
so divergence indicators stay visible even when comparison is
missing, loading, or failed. Also use cross-platform temp paths
in tests.
* Show commit counts against compare base, not upstream
Upstream divergence (↑/↓ against tracking branch) was confusing for
rebased branches — the counts appeared beside the base ref but measured
against the upstream branch. Show only the compare base count instead,
on the line that names it.
* Report branch divergence in both directions
Rebased branches are typically ahead AND behind their base; a single count
hides this case. Use symmetric range with --left-right --count to capture
both directions efficiently, then expose commitsBehind in the UI alongside
commitsAhead.
* Use semantic names for i18n keys and template variables
Rename hash-based translation keys to descriptive identifiers and replace generic value0/value1 placeholders with semantic variable names like `count` and `ref`. Improves code maintainability and makes translation strings self-documenting.
* fix: route terminal file links to sibling workspace tabs
Detect when a clicked file is already open in a sibling workspace and route
to that existing tab instead of creating a duplicate. Reorganizes workspace
activation to dispatch by both worktree id and execution host, allowing the
same worktree name across different remotes to be disambiguated and routed
correctly.
* test: validate terminal file link opens in correct sibling worktree
Enhance test to check both file path and active worktree ID, ensuring
the linked file opens in the intended sibling workspace.
* fix(browser): focus unified tab on browser page palette activation
When activating a browser page from the palette, find and focus the
corresponding unified tab before setting active state. Ensures the
tab group receives focus. Also increase e2e test timeouts to improve
stability on slower runners.
* test(e2e): read latest restored terminal frame
* Fail browser page activation when unified tab is missing
Without a unified tab, the workspace can't render in the pane. Reporting
success leaves the previous tab on screen. Fail the activation to prevent
this confusing state.
* fix(terminal): collapse identity group in the title churn signature
Replaces the ingest-time title rewrite from #16373 with a non-destructive
fix at the actual cause.
The churn suppressor `isDecorativeAgentTitleFrameChange` keyed on the
literal label, so `working:OMP` and `working:Pi` compared unequal and every
alternating frame from a wrapped harness committed a store patch. #16373
made the labels agree by rewriting the stored title to the tab's launch
owner — but `runtimePaneTitlesByTabId` is also the Windows Shift+Enter
byte-encoding input, so normalizing at ingest destroyed evidence other
consumers read (fixed separately in #16376).
Collapse the identity group inside the signature instead. Which member of
a group a frame names is decoration, exactly like the spinner glyph the
signature already strips, so frames compare equal without touching what is
stored. Suppression now changes only WHETHER a frame commits, never WHAT
it says.
Also fixes the flap under a multiplexer (#8032): the collapse runs over
wrapper segments, so "zsh | ⠋ Pi" and "zsh | ⠙ OMP" compare equal, which
the anchored owner-relabel in #16373 never matched.
Reverts the store changes from #16373 and drops the helper it added.
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* fix(terminal): fold only bare identity frames into the group token
A legacy "π - <session> - <cwd>" title is Pi-compatible too, so folding
every profile match collapsed two different sessions to the same signature
and suppressed the change outright — reintroducing #16093 through the
churn signature.
Fold only exact bare identity frames, matched per wrapper segment, so
semantic session titles keep comparing on their own text.
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* docs(terminal): correct the flap diagnosis in the repro header
Verified against the OMP source: it emits only π-glyph frames
(`DEFAULT_TERMINAL_TITLE = "π"`, title-generator.ts:25), and on an
Orca-hosted pane its native titler cedes to Orca's own injected extension,
which writes `⠋ π - <session> - <cwd>`.
So OMP emits neither "OMP" nor "Pi". Both flap sides are Orca's:
"OMP" from driveSyntheticTitleFromHook, "Pi" from normalizeTerminalTitle
collapsing our own extension's output to a hardcoded literal.
The prior header credited the wrapped harness for frames it never sends,
which is the same wrong narrative that produced eight fixes at eight
layers. No behavior change.
* fix(terminal): stop Orca mangling the OMP/Pi title it writes itself
Verified against the OMP source: it emits only π-branded frames
(`DEFAULT_TERMINAL_TITLE = "π"`, title-generator.ts:25), and on an
Orca-hosted pane its native titler cedes to Orca's OWN injected extension,
which writes `π - <session> - <cwd>` / `⠋ π - <session> - <cwd>` at 80ms.
So neither flapping string came from OMP. Orca made both:
"Pi" — normalizeTerminalTitle collapsing our extension's output to a
hardcoded literal, discarding the session name and cwd (#16093)
"OMP" — driveSyntheticTitleFromHook injecting over it every 80ms
Fixed at the source:
- normalizeTerminalTitle canonicalizes only the rotating braille frame and
keeps the rest, in both spinner positions and through a multiplexer
prefix (#8032). Status still round-trips through normalization.
- detectAgentStatusFromTitle reads the π state separator, so `π ! <label>`
is permission instead of the blanket idle that hid a blocked agent.
- normalizeCompatibleAgentTitleForOwner swaps only the brand for the
owner's label, so a pane still reads as its launch owner (#6689, #7633,
#9077) without losing the session text.
- pi/omp set synthesizeWorkingTitle: false — the agent animates its own
working title. Terminal states still synthesize; they carry the pane's
agent identity downstream.
Reverts the ingest-time title rewrite from #16373, whose normalization of
runtimePaneTitlesByTabId also changed Windows Shift+Enter bytes (#16376).
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* fix(terminal): match the state separator only in exact profile casing
The separator check runs on every title, so `omp - deploy notes` and
`pi - refactor the parser` read as an idle agent. The owner rewrite only
ever emits the exact profile labels, so dropping case-insensitivity keeps
`OMP - tmp` classifying while ordinary prose stops matching.
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* test(terminal): pin one real OMP turn to two committed patches
Drives 30 working frames as Orca's injected extension emits them plus the
idle transition, and asserts what survives the churn gate. Before the fix
every frame alternated "⠋ Pi"/"⠋ OMP" and each one committed — ~12 store
patches per second on a working tab.
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* fix(terminal): carry the permission guard inside the separator reader
`-` is both a π state separator and the delimiter in the synthetic
permission label, so `OMP - action required` read as idle. It resolved
correctly only because detectAgentStatusFromTitle happens to check the
synthetic label first — and the separator fn is exported, so a direct
caller inherited the bug.
Also pins the owner rewrite's fixed-point property, which holds only
because getAgentLabel does not tokenize omp/pi, and corrects a comment
that overstated how tightly the brand swap is scoped.
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* docs(terminal): name the flag the code actually sets
The suite header cited `synthesizeTerminalTitle: false`; the profiles set
`synthesizeWorkingTitle: false`. The distinction is the whole reason the
narrower flag was chosen — terminal-state frames still carry the pane's
agent identity downstream — so the wrong name buried the rationale.
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
---------
Co-authored-by: Seongho.Bak <49228032+psh4607@users.noreply.github.com>
* fix(i18n): localize the keep-awake corner chip
Route the status-bar keep-awake chip through the shared Agents copy
helpers and add missing locale entries for chip-only words.
Fixes#14490
* test(i18n): restore previous language after keep-awake locale suite
* test(i18n): render component in localization tests instead of static che
Converts the keep-awake localization test from static source-code validation to actual component rendering with React Testing Library, providing more reliable verification that the UI displays correctly across all supported languages. Improves translated descriptions for consistency and accuracy.
* test(i18n): add aria labels and descriptions to localization test
- Adds missing localization keys to test data for Spanish, Japanese, Korean, and Simplified Chinese
- Updates test assertions to verify `ariaLabel`, `onDescription`, `autoDescription`, and `offDescription` are properly translated
- Completes localization coverage for the keep-awake corner chip component
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
* Add keyboard shortcut for workspace deletion
Default Mod+Shift+Backspace (⌘⇧⌫ on Mac) lets users delete the hovered
worktree or folder workspace immediately. The shortcut targets the
sidebar hover state rather than requiring focus, and avoids terminal
pane D-based split shortcuts on all platforms.
Co-authored-by: Brennan Benson <brennankbenson@gmail.com>
* Omit delete shortcut from disabled Delete Worktree for primary checkout
- Remove shortcut badge from the disabled "Delete Worktree" action when it cannot be executed
- Only show shortcut in multi-context delete actions where the command is available
- Extract host identity parsing into reusable helper function to prevent inline string manipulation
- Fix folder workspace deletion to use correct host-qualified identity comparison
* Document host extraction safety for destructive worktree ops
Unqualified identities must stay undefined rather than defaulting to
'local'. Destructive operations depend on correct host identification.
Added tests and JSDoc to clarify this safety-critical behavior.
* fix test
---------
Co-authored-by: Brennan Benson <brennankbenson@gmail.com>
* test(e2e): gate the tab-bar agent launcher on Windows shells and WSL
The `+` menu agent launcher had no golden coverage in the Windows lane, so a
Windows-only break anywhere in its chain (detection row, startup-plan build,
tab create, PTY spawn, startup-command injection) could ship unnoticed.
Adds a golden spec that launches a stub agent from the menu and asserts the
agent's own banner reached the pane — a tab that spawned a bare shell instead
is indistinguishable at the store/tab layer. Runs two agents everywhere, and
on Windows also PowerShell, cmd, Git Bash and a WSL project runtime.
* test(e2e): track WSL stub agent staging state for precise cleanup
Refactor `stageWslGoldenStubAgent` to track which artifacts it creates
during setup, then only remove those artifacts during cleanup. This
prevents the test from destructively removing pre-existing symlinks or
state from previous runs, improving test isolation and idempotency.
* test(e2e): track WSL stub agent staging state for precise cleanup
- Back up and restore pre-existing stub agents to avoid destroying them
- Simplify verbose test comments to match project style guidelines
* test(e2e): serialize WSL stub agent setup with distributed lock
- Add mkdir-based lock to prevent concurrent staging invocations
- Reclaim stale locks after 10 minutes to recover from crashes
- Track lock ownership in stage state for safe cleanup
* test(e2e): track WSL stub agent staging state for precise cleanup
Track which stubs this test helper stages by writing a marker file, then
only remove stubs during stale-lock recovery if we created them. Prevents
cleanup from removing stubs left by other processes.
* refactor: split pty-connection.ts under 400 lines
* rm design doc
* refactor(pty-connection): extract reattach payload handlers as factories
- Replace bindApplyReattachPayload with createReattachPayloadHandlers factory that returns handlers instead of mutating session directly, enabling better composability and testing
- Extract waitForUserInitiatedSshConnect as standalone function for reuse across deferred session attach flows
- Create ReattachPayloadSession type to document and isolate required session capabilities
- Add test coverage for overlapping reattach payload attempts
- Clean up comments to remove redundant prefixes (session.pane → pane, session.transport → transport)
* fix(pty-connection): correct sequencing and state bugs in spawn and reat
- Fix terminal tail slice to take prefix instead of suffix, preserving escape
sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
preventing superseded results from canceling in-flight prepaint
* fix(pty-connection): correct sequencing and state bugs in spawn and reat
- Fix terminal tail slice to take prefix instead of suffix, preserving escape
sequence markers needed by next scan
- Clear pending pane serializer when direct SSH retry PTY is unclaimed
- Initialize interrupt status baseline to undefined so first input advances
sequence counter
- Bump reattach generation only after confirming current attempt owns the stream,
preventing superseded results from canceling in-flight prepaint
* fix(test): increase poll iterations to prevent Node 26 test leakage
Increase event loop turns from 40 to 200 in the timer settlement loop.
Node 26's libuv poll phase can briefly starve when concurrent workers
transform tests, causing cleanup to leak into the next test. The higher
iteration count ensures async operations complete before returning.
* fix(foreground-output-budgets): use >= for budget window boundary check
At the exact window boundary, the budget should roll over. Change the
comparison from > to >= so the window resets when now equals
windowStart + FOREGROUND_BUDGET_WINDOW_MS, not just after. Add tests
to verify budget rejection and rollover behavior.
* refactor(pty-connection): add status observations and routing improvemen
- Track agent status observations with origin and transition metadata
- Separate interactive redraw input timing from general terminal input
- Restore pane authority on bind and reattach
- Refine routing trust and confirmation state handling
- Invoke queued startup callbacks when PTY is bound
- Resolve Windows shell overrides with user settings
* refactor: extract resolveLaunchAgentCandidate helper
Consolidate duplicated launch-agent resolution logic into a shared helper to prevent future divergence between paneExpectsLaunchAgent and resolveExpectedLaunchTuiAgent.
* refactor(pty-connection): use model snapshot for direct SSH reconnects
Direct SSH reconnects now restore from the full SSH model snapshot (complete scrollback) when dimensions are compatible, instead of the bounded relay tail. Falls back gracefully when incompatible or alternate-screen was exited.
* refactor(pty): retry unverifiable SSH reattaches via preserved bindings
Preserve deferred SSH session IDs longer when they serve as the only retry binding,
allowing the system to attempt recovery through direct SSH retries or PTY remounts
when reattach fails in an unverifiable way. Simplify reconnect model restoration
by removing the conditional model snapshot probe and using relay replay directly.
* test: poll terminal readiness in expectSingleOwningPty
Retry the terminal list assertion with polling to account for timing
delays in PTY state reporting from the runtime.
* fix(status-bar): remove pet menu reserved space
* test(status-bar): add pet segment layout validation tests
- Unit test guards against pr-[6.5rem] padding reintroduction
- E2E test measures trailing overhang instead of total width delta
for more accurate layout validation
- Extract enableExperimentalPet helper for test clarity
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
* fix(runtime): classify tui-idle from the visible screen only
The adopted-PTY tui-idle probe added in #15569 read the provider snapshot as
`scrollbackAnsi + data`, and the Codex readiness classifier matches the startup
banner. For a daemon-hosted adopted worker — where the retained tail stays empty
forever — every wait re-probed and could resolve `satisfied: true` off banner
history while Codex was actively working, turning a loud timeout into a silent
false ready.
- probe now requests and parses the visible grid, never scrollback
- retirement of a timed-out provider acquisition is checked before the
re-acquire branch, so a wider row request can no longer resurrect a hung
provider
- probe builds its result before clearing the poll interval, so a stale handle
cannot leave the waiter with neither poll nor probe
Fixture follow-ups from the same review:
- resume legs pin the captured `launchConfig.agentCommand` to the fake instead
of bare `codex`, which resolved the machine's real Codex off PATH
- the command override is quoted for the Windows shell the runtime will actually
use, and specs pin that shell alongside the override
- fake agents acknowledge a bare submit after a short grace, so an unbracketed
delivery path fails with a diagnosable ACK instead of a suite timeout
Refs STA-4907, STA-4885
* test: assert tui-idle probes serialize visible grid only
- Verify idle timeout probes exclude scrollback from serialization
- Add test case for Git Bash shell path quoting with apostrophes
- Simplify verbose test helper comments
* test: improve fake agent paste protocol validation
Refactor paste end detection to properly track both begin and end markers,
validate bracketed paste protocol (RFC 2544) through chronological event
sequencing, and emit correct error messages for protocol violations. This
ensures reliable detection of when pastes complete even when delivered
across multiple chunks, and correctly distinguishes between bracketed and
unbracketed paste modes.
* fix(runtime): reject provider snapshots when live output advances
Provider snapshots become stale when live output is received after the
snapshot is requested. Reject snapshots where the current output sequence
exceeds the snapshot sequence, preventing callers from consuming outdated
terminal state. Add tests verifying stale frame rejection.
* fix(composer): close the Create Workspace dialog on the first Escape
The modal copied the page-level "Esc blurs the focused field, then closes"
rule from TaskPage/Automations. On a page that rule protects a focus the
user chose; this dialog auto-focuses the name input on open, so its
capture-phase handler preventDefault'd every first Escape (which also
suppressed Radix's dismissal, since DismissableLayer skips a
defaultPrevented event) and the dialog could only be closed with two
presses.
Drop the Escape branch and let the dialog's dismissable layer own it.
Radix dismisses only the topmost layer, so nested popovers, selects and
dialogs still consume their own Escape first.
* test(e2e): pin the composer's auto-focus as the reason one Escape must close it
* test(e2e): extract paired client window reveal into helper
Paired clients launch hidden, parking runtime subscriptions. Playwright-driven
clients must be revealed to test actual user interactions. Extract the reveal
logic into a reusable helper with error handling and unit tests.
* test(e2e): handle crash dialogs and isolate collision fixture IDs
- Recover from recoverable UI error dialogs in selectRuntimeHost
- Give the same-ID collision fixture unique repo and worktree IDs to avoid
reusing the runtime repo's ID, preventing fixture leakage
- Simplify verbose comments for clarity
* Add Artifacts and Skills pages to navigation history
- Record Artifacts and Skills visits in back/forward navigation like Automations
- Both pages properly rewind history when closed to the previous live entry
- Extract rewindHistoryIndexPastView() helper to deduplicate close-page logic across all page types
- Add test coverage for Artifacts/Skills navigation, separate entries, and shared link handling
* Add Artifacts and Skills pages to navigation history
Back/forward buttons now appear when navigating to Artifacts and
Skills pages, consistent with Terminal, Tasks, and Automations.
* fix(pty): answer a terminal colour query in its own turn
Root-cause follow-up to #15559, which stopped a CPR overtaking a deferred
colour reply but left the deferral itself in place.
Orca answers terminal queries by writing to the PTY master, which a line
discipline in ECHO copies straight back out as junk on a cooked prompt
(#12112). The guard was to withhold the write until an `stty` subprocess
proved ECHO clear — and forking is what forced the decision to be async.
Any deferral, however short, lets a reply written later in the same turn
overtake this one, so the async probe was the bug's root cause.
Read the bit synchronously instead. Linux and the BSDs redirect a
master's mode ioctls to the slave, so a `tcgetattr` on the master fd
node-pty already owns answers for the slave with no fork: measured 0.26us
against 2403us for the subprocess. With a verdict available inline, a
querying program that already cleared ECHO — every raw-mode prober,
including the colour probe behind the `gh auth login` report — is
answered in its own turn and can never be reordered.
The deferral stays for the genuinely cooked case, and the ordering
guarantee stays underneath it: hosts whose node-pty predates this patch
get no sync probe and fall back to the deferred path, which mixed
client/host versions make a live production path.
Reply routing is all-or-nothing: a payload needing neither containment
nor ordering stays on the host's own path, so a CPR answered during shell
startup cannot pass the daemon's post-ready flush gate and splice into
the buffered startup command.
Native side is fail-safe: a kernel that did not redirect would answer
from the master's own termios, whose ECHO defaults set, so the degraded
verdict is "echoing" — never a false "quiet". The JS half ships in the
pnpm patch while the binding needs a source build, so
ORCA_REQUIRE_NODE_PTY_ECHO_STATE=1 makes CI fail rather than silently
skip when it is handed an upstream prebuild.
Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>
* fix(pty): keep the flush ordered under synchronous re-entry
Three defects found in external review of the reply-ordering work.
node-pty delivers onData inside the master write, so a query can be
answered while the queue is mid-flush. `flushPendingWrites` spliced the
array off before writing, so that reply saw an empty queue, took the
same-turn path, and landed ahead of entries the loop had not written yet
— reproduced as 01, 99, 02, 03. It now shifts one entry at a time so a
re-entrant reply queues behind the rest, bounded by the length at entry
so a re-entrant push cannot spin the loop.
An overflow flush can re-enter as far as teardown. `answer` did not
re-check `closed` afterwards, so it queued behind a closed delivery,
returned true, and the reply was never written and never reported.
The payload router's ownership comment overstated its guarantee. The
`any` semantics are deliberate — returning false after a constituent was
already written would have the caller re-write the whole payload and
duplicate it into the child's stdin — so the residual mixed-failure drop
is now documented rather than implied away.
* fix(pty): delete the reply-withholding scheduler
Orca answered a terminal query by withholding the write until a probe
proved the slave's ECHO bit was clear. That was the wrong mechanism, and
it is now gone: replies are written in the caller's turn and their echo
is contained on the output side, where it always was.
Withholding never removed an echo. The wait was bounded and always ended
in a write, so the output-side projections were doing the work the whole
time — including the readline rewrite, which happens with the tty already
raw and which therefore no reading of the ECHO bit can predict. What
withholding did add was an asynchronous write path, and that is what let
one reply overtake another and land in the next program's stdin (#15559),
what produced a re-entrancy inversion inside its own flush, and what four
rounds of regressions have lived in.
The last thing it covered was the verbatim echo of a `stty -echoctl` tty.
That shape is now projected directly. It starts with ESC, so it is
matched only when complete and never held as a partial: holding it would
take a bare trailing ESC from the query parser and an expired hold would
release it raw, so a query torn at its own ESC would never be answered.
Complete-match-only is what makes the shape safe to project at all.
Measured on a real pty: a cooked-mode master write is both echoed AND
delivered — ECHO copies the bytes without consuming them from the slave's
input queue, so a program arming raw mode with TCSANOW/TCSADRAIN (libuv's
setRawMode, hence every Node agent) still reads them. Only a TCSAFLUSH
switcher discards it, which it does on every terminal, none of which
gates a reply on termios state.
Deletes the pending-write queue, the async stty probe, the poll budget
and probe rate limit, the deadline-driven flush, and the answer/
answerInOrder split. Replies now leave in call order by construction.
No packaging, native or CI surface is touched.
* test(pty): restore stty-probe coverage and pin the duplicate-query retry
Archaeology on how withholding got here, and what its tests were really
protecting.
Deleting the ECHO probe took four tests with it that were not about the
probe at all: they cover createSttyProbe, which the shell-readiness
line-editor probe still uses — in-flight sharing, the per-platform stty
flag, and transient-versus-permanent failure latching. Restored against
the line-editor probe, which is now their only caller.
Also pins the property that answers the one case an immediate write
cannot serve. A program that queries while cooked and then arms raw mode
with TCSAFLUSH discards the reply with the rest of its input queue.
Nothing can prevent that from the terminal side, and no terminal tries.
What matters is that such a program re-queries after its own timeout: the
ingress declines to answer an already-answered slot but forwards the
duplicate downstream, so the renderer's emulator answers the retry, by
which point the program is raw. The retry path is the recovery, not
withholding.
* ci(pty): keep the fish real-PTY test in the shell-contracts lane only
Reverting pr.yml to main dropped the exclusion for the fish query-reply
test, which this branch keeps, so it would have run in the sharded lane
as well. Restores it to the shell-contracts include list and the shard
exclude list, and drops the parallelism expectations for the deleted
cooked-querier suite and the echo-state env guard.
---------
Co-authored-by: Brennan <brennanb2025@users.noreply.github.com>
* fix(terminal): apply pane padding on all four edges
Move the configured inset onto xterm so the terminal fills its pane while the fit calculation accounts for both sides of each axis. Add a geometry golden that forces cell remainders and verifies dynamic padding without relying on renderer pixels.
* fix(terminal): normalize imported padding for fitting
* fix(terminal): align stored and fitted padding
* fix: update E2E tests for API changes and selector robustness
- Improve source control file locator specificity to avoid flakiness
- Fix board test to use correct worktree ID attribute
- Update removeWorktree calls to pass host ID parameter
- Simplify git status polling with timeout expectation
* fix: increase packaged-watchdog launch timeout and await git-status rows
Extract hardcoded 15s launch timeout to a 30s constant for better reliability under load. E2E test now waits for all git-status rows to render before asserting absence of status messages, preventing flaky passes when the list is still loading.
* test(e2e): keep the native Hangul reproduction harness
This is the spec that reproduced #15299: it drives a real ibus-hangul
engine through a real compositor and asserts the bytes reaching the pty.
It is the first setup here that can exercise an input method end to end,
and three IME issues this week were unreproducible without one.
It does not run in CI, and the header says so rather than implying
coverage. It needs a compositor session CI does not have, and this repo
already carries native IME specs that are skipped everywhere and were
mistaken for protection they never gave. The run recipe is in the header
so the next person does not rebuild it.
Recorded there too are the five things that decide whether a run is real
or a silent false negative - nested rather than headless, an unused
display, a session script that does not exit, forcing the window
visible, and sending Escape before the byte reader starts. Each cost a
failed attempt, and four of them are what defeated an earlier try.
Keys and expected text are environment-tunable so other IME issues can
reuse it unchanged.
Refs #15299
* test(e2e): record three more silent-false-negative traps in the native IME harness
A Hanja candidate-selection run on the same rig hit all three. Each produced an
empty or misleading event log that reads as "the IME ignored the key" rather
than as a broken harness, which is the failure mode this header exists to
prevent.
The panel one is the least obvious: a session whose ibus-daemon runs with
--panel=disable never draws a lookup table, so any run that depends on seeing
candidates measures nothing while appearing to work.
Refs #15299
* test(terminal): pin that the CJK block is the preedit overlay, not the cursor
A report described the cursor sitting on a wide character's first cell
and hiding its right half, with cursor style and opacity settings
ignored. Neither defect reproduces.
Replaying the reporter's own captured byte stream leaves the cursor at
column 11, exactly where the application asked, with correct wide and
continuation cells. A block cursor also cannot hide half a glyph: it
inverts the cell and the syllable renders inside the cursor span.
The black block is the IME preedit overlay. macOS 2-set Korean keeps the
trailing syllable composing until a terminator, so it sits in an opaque
absolutely-positioned box over the grid rather than in the buffer. That
box took stock upstream colours, black on white. It explains what no
cursor theory can: the block appears at the composing cursor cell, no
cursor option reaches it, it is identical with GPU acceleration off
since it is a DOM node above both renderers, Latin never triggers it
because Latin opens no composition, and Enter clears it because Enter
commits the composition.
Already fixed by the overlay theming in #15014, which landed a day after
the reported release, so the fix ships in the next one.
Tests only, no production change. Two pin the negative results so the
cursor explanation cannot be re-derived, and one pins the actual
mechanism at end of row, beside the existing mid-line arm.
Separately confirmed and not fixed here: the WebGL renderer drops the
cursor colour's alpha, so terminal cursor opacity genuinely does nothing
for a block cursor, which is the default style on the default renderer.
That is in the webgl addon rather than in xterm or in our code.
Refs #12729
* test(terminal): make the cursor precedence assertion real and measure the overlay
Review of the first pass found one assertion that could not fail. It set
options.cursorStyle and then read decPrivateModes.cursorStyle, which are
separate fields with separate storage, so it pinned that writing one does
not clobber the other. Deleting the precedence expression from both
renderers left it green.
It now asserts the rendered cursor class: the option style renders, a
DECSCUSR overrides it, and the reset hands control back. That fails if
the precedence is removed.
The overlay's rendered width is the one measurement in the report that
argues against our explanation, and no test here could reach it, because
the unit environment performs no layout. Adds an end-of-row browser arm
beside the existing mid-line one, asserting a single composing Hangul
syllable spans about two cells. That settles whether the block the
reporter measured at one cell can be this overlay.
Also scopes two DOM queries to the test container rather than the
document, and attaches the render listener before writing so a missed
render fails instead of hanging to timeout.
Records in the file header what it does not establish: composing the
opacity into the theme is not the same as it reaching the screen, since
the webgl renderer drops the cursor colour's alpha for a block cursor.
Refs #12729