mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 08:02:32 +00:00
ccfc8fe2c464bdfb052fcea9a5206b4e0acddd2e
273
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
51fe6f3fba |
fix(editor): restored tabs for files outside your projects no longer fail with Access denied (#24489)
* fix(editor): read files outside projects without a grant a restart loses
A file opened from outside every project (e.g. ~/notes.txt from the floating
workspace) read through an in-memory grant. After a restart the restored tab
only renewed that grant when it stored a full path, so a tab saved relative to
the floating workspace folder failed with "Access denied" and Retry repeated it.
Single-file reads (read, stat, exists) and open-editor-tab saves now resolve a
path outside every project in place. Paths inside a project keep the full
containment check, so a project's symlinks still cannot escape it, and every
other write stays inside projects.
* fix(editor): re-grant restored floating-workspace tabs by owner, not path shape
Problem: a file opened from the floating workspace (e.g. ~/notes.txt via
Cmd-click in the floating terminal, the floating markdown picker, or a .md
opened from the OS) loads until restart, then shows "Access denied: path
resolves outside allowed directories". Main's external-path grants live only
in memory. On restore the editor re-granted only tabs that stored an absolute
path, but floating tabs store a path relative to the floating root (~ by
default), which is deliberately not an authorized root, so they were never
re-granted. Restored floating notebooks also failed to start a kernel.
The previous commit on this branch let main read and save any path outside a
project without a grant. That widened fs:readFile/stat/pathExists for every
caller, including automatic reads of untrusted content (markdown preview
images), which opened a Windows UNC credential leak and a /dev/zero
main-process memory blowup. This reverts that model entirely.
Fix: one helper decides which client-local path a tab needs re-granted by
ownership: a floating-workspace tab, or a tab stored outside its own project.
It never grants paths a local project root covers (a grant would also
authorize a project symlink's outside target), and skips SSH-owned,
runtime-owned and not-yet-hydrated owners. Every reader that can touch a
restored tab before or without the editor loader uses it: the loader, the
restored dirty-tab conflict scan, and the paired-mobile markdown bridge.
* fix(editor): let main decide which restored-tab paths a project already covers
Problem: the restore re-grant helper decided "already inside a project" in
the renderer from its worktree list. At startup that list only holds repos
the session references, so a floating tab inside an unlisted repo was granted
(including a symlink's outside target), and the renderer's path matcher
disagrees with main's on WSL \\wsl$ vs \\wsl.localhost, which stranded a
folder-workspace tab with "Access denied" after restart. The helper also
treated a folder workspace with a missing or ambiguous host as local.
Fix: the renderer now decides only by owner (a floating-workspace tab, or a
tab stored outside a project whose owner is explicitly local) and asks main
with `skipIfInsideAllowedRoots`. Main checks the path against its own allowed
and registered roots, in both the named and canonical-parent spelling against
both root spellings: a path a project covers gets no grant, an alias spelling
of a project path gets only that spelling, and a project symlink's outside
target is never granted. Explicit-open grants (Cmd-click, drag, explorer) are
unchanged. Tests now prove each reader waits for the grant before reading.
* fix(fs): decide a restored tab's project membership from every ancestor's real path
Problem: the restore re-grant decided "inside a project" from the named path
and the real path of its parent only. When a tab path crossed a project
directory symlink and named the project through a spelling that was neither
the registered root nor its realpath (a second alias, a `..` segment, a case
variant on a case-insensitive disk, a /var-style alias of an ancestor), no
check matched, the path took the full grant, and the symlink's outside target
became readable and writable.
Fix: a path is inside a project when the named path is inside a root, or the
real path of any ancestor folder is inside a root in its registered or real
spelling. Such a path gets at most its named spelling, never its realpath. An
ancestor that fails to resolve for any reason other than "missing" now fails
closed to the named-spelling grant instead of falling through to the full one.
Tests cover each spelling; the non-symlink case also runs on Windows.
* fix(fs): read local files as regular files only, from one bounded handle
Problem: fs:readFile stat'ed a path and then read it to EOF. A character
device such as /dev/zero reports size 0, passes the size limit and never ends,
so the main process buffers until memory runs out; a FIFO hangs the open.
Writes could also target an existing device or FIFO.
Fix: every local fs:readFile (editor and log snapshot) opens the path once,
non-blocking, refuses anything but a regular file, and reads the size check,
binary probe and content from that same handle, capped at the limit even if
the file lies about its size. The AI Vault log tail opens non-blocking too,
and fs:writeFile refuses an existing non-regular target.
* feat(fs): let desktop file requests declare their shape
Problem: main decided every local file request against one allow-list plus a
set of in-memory grants the renderer had to recreate after every restart, so
a file the user opened outside a project (for example from the floating
workspace) was denied once Orca restarted.
This adds the request shape the common pattern uses, alongside the grants for
now:
- no shape (the default): the path must be inside a project root main
recognises, symlinks included. Desktop requests also accept the app-owned
floating-workspace folder; paired-client RPC never does.
- user-file: a single file the user named by absolute path, used in place.
Only fs:readFile/stat/pathExists and saving (fs:writeFile) accept it.
- document-resource: an image or PDF a document references, limited to every
project root when the document is in one, else to the document's folder,
and refused by path text before any disk or network access.
Notebook kernels and AI Vault log tails check their open file as user-named.
* feat(editor): send each local file request's shape from the renderer
Problem: after a restart, a tab opened outside every project (a floating
workspace file, a file opened by absolute path, an OS-opened markdown) could
only be read if the renderer first re-granted its path, and readers that ran
before the editor loaded the tab had no grant at all.
The renderer now says what kind of request it is making, and main checks that:
- A persisted tab opened outside its owner's root (floating workspace, or an
absolute stored path) whose owner is explicitly local reads and saves as a
user-named file, from every reader: the editor loader, the restored-tab
conflict scan, the change banner and compare dialog, the paired-phone
markdown bridge and the save queue. Project tabs stay inside their root.
- Clicks, drops, typed paths and browser-opened notebooks stat as user-named.
- Markdown preview and rich-editor images are document resources, limited to
the document's roots or folder. Images the user pasted or attached into a
chat show as user-named; agent images stay inside the project.
- The image cache keys on the shape, so one shape's image never answers
another's request.
A ratchet test lists every renderer file allowed to create a user-named
request.
* refactor(fs): delete the in-memory path grant system
Problem: main kept a set of paths the renderer had asked it to allow
(fs:authorizeExternalPath). The set lived only in memory, so a file the user
opened outside every project could be read until Orca restarted and was then
denied, and every new reader of a restored tab had to remember to recreate
the grant first. Three rounds of re-deriving grants at restore each found
another reader or path spelling it missed.
Now that every desktop request declares its shape, nothing needs a grant:
- delete the grant set, authorizeExternalPath, the restore re-grant from the
earlier commits on this branch, the fs:authorizeExternalPath channel and its
preload and web-client entries;
- delete every renderer grant call (terminal and markdown link clicks, drops,
typed paths, the file explorer, AI Vault logs, chat attachments, browser
notebooks) and every main one (floating markdown picker and folder, OS-opened
markdown, keybindings.json, pasted images, import and upload sources);
- the floating workspace's picker-approved folders stay a terminal-cwd
allowlist only.
Main now holds no per-path permission, so a restart can't change any answer.
* feat(editor): open project links that lead outside the project as named files
Problem: a file inside a project that is a symlink to something outside it
opened fine from the file explorer or a terminal Cmd-click, then showed
"Access denied" after a restart: its tab was stored as a project file, and a
project request is refused when it resolves out of the project. A folder link
out of the project expanded in the explorer until restart and then failed with
a raw access error.
Now the click decides and the tab keeps that decision. Both gestures stat the
path inside the project first; if only the user-named check passes, the path
leads out of the project:
- a file opens by its absolute path, so it reads and saves as a file the user
named, the same before and after a restart;
- the explorer does not follow a folder link out of the project and says so
("This folder links outside the project, so it can't be opened here.").
Paths that stay inside the project still open as contained project tabs. Also
drops the AI Vault "path not authorized" message, which nothing shows now.
* chore: drop the casts the changed-code quality gate flags on this branch
The FileContent casts in the editor loader and the paired-phone markdown
bridge were never needed (the read result is already assignable). Tests stub
window.api through vi.stubGlobal and pass narrow stores without casting; the
one test store that still needs a cast states why.
* fix(fs): load chat images by type, and keep escaping project links readable
Problems found in review:
- Chat transcript images were trusted by message role: any user-role
"[Image: source: <path>]" (an injected Claude record, `orca terminal send`,
a paired client's image-ref) became an automatic user-named read as the row
scrolled into view, of any file type, and on Windows a network-share path
would have opened an SMB connection to that host.
- A document image named like an image but linking to a text file
(logo.png -> .env) was read as text.
- Windows device names (NUL.png, COM1.jpg) passed the path-text check of the
automatic image loads.
- A project symlink leading out of the project, opened by a typed path, a
tab-strip drop or a browser file:// notebook, was stored as a project tab
and immediately refused.
Fix:
- New chat-image request shape for every transcript image and the composer
preview, whoever's turn named it: an absolute local path whose requested and
real targets are image files, a regular file, size-capped; network-share and
device-namespace paths and Windows device names are refused by path text
before any filesystem call. Pasted screenshots still show after a restart,
and agent images outside the project now render.
- Document resources check the real target's type too, and refuse Windows
device names by path text.
- Typed paths, tab-strip drops and browser notebooks stat through the same
check as the explorer and terminal, and open an escaping link by its
absolute path.
- Tests pin the shape at the change banner, compare dialog, markdown preview
and image prewarm; a second ratchet lists every file that can open a tab the
tab rule reads as user-named, and its comment says what it can't see.
- Stale grant wording removed.
* fix(fs): tighten automatic image loads and the project-link check
Problems found in review:
- Two unit tests went red on this branch: the browser-share test still
expected reads without a shape, and the rename test's electron mock had no
app, which the desktop root check now needs.
- The device-name check ran on the raw path, so `NUL.png\.` or
`COM1.png\x\..` (reachable from markdown ``) reached the
filesystem; a document image whose real target was a device name passed.
- Chat images in a project that lives on a Windows network share no longer
rendered, though the markdown preview showed them.
- Any failed project check (a missing file, a dropped connection) was taken
as "this link leads out of the project" and opened as an absolute tab.
- Every local read allocated about 2 MiB, even for a tiny image.
Fix:
- Device names and device-namespace paths are checked on the resolved path
and on the real target, for chat images and document resources alike.
- A network-share path in an automatic load is read only inside a project
root (the user chose that share when adding the project); anywhere else it
is still refused by path text before any filesystem call.
- Only main's "outside allowed directories" refusal marks a project path as
leading out of the project; other errors surface as before. The message now
lives in shared code so both sides agree on it.
- Reads size their first buffer from fstat and confirm EOF with a 1-byte
probe; a file that grows past its reported size is still read in bounded
chunks up to the cap.
- Fixed the two red tests.
* refactor(fs): name file access by its role, not its structure
Problem: the static-analysis anti-slop check failed the PR because the new
code named the request's file access a "shape" (`shape`, `RequestShape`,
`TabShape`), which describes structure rather than the role.
Rename the main-process module filesystem-request-shape.ts (and its tests) to
local-file-access-resolution.ts, rename the symbols to fileAccess,
FileAccessResolution and TabFileAccessFields, and say "file access" or
"access kind" in the comments and test names. No behaviour change.
* fix(fs): refuse every Windows device-name spelling in automatic image loads
Problem: the device-name check split a file name only on '.', so names such
as NUL:.png, COM1:.png, NUL:stream.png (an alternate data stream) slipped
through, and CONIN$, CONOUT$, CLOCK$, COM0 and LPT0 were not listed. Those
reached the filesystem from a document or chat image before being refused.
Split on ':' as well, list the missing device names, and test each with
Windows path rules and zero filesystem calls. Also cover the case of a local
link that leads onto a network share outside every project (refused for chat
images), and correct the shared comment on chat-image access.
* fix(editor): let users rename and insert images into files opened outside projects
Renaming a file opened outside every project (tab double-click, editor
header) and inserting an image into such a markdown document failed with
"Access denied", even before a restart: both writes only passed the
project-root check. Document resources and chat images were also limited
by file type more strictly than users expect.
- Add a "document-folder" access kind for writes beside a document the
user opened: main allows renaming only that document, to a name inside
its own folder, and importing new files only into that folder, checked
by path text and again by real path, with Windows device names refused.
The renderer sends it only for local user-named, writable tabs (rename,
its undo/rollback, image insert); SSH and runtime requests never carry it.
- Document resources: drop the image/PDF type allowlist; folder
confinement, regular-file reads, the cap and path-text refusals remain.
- Chat images: judge only the real target's type, against every
previewable image type (AVIF added).
* fix(fs): a declared file-access kind never refuses what the project check allows
A full-path tab for a file inside a project (for example a link that
leads out, opened by its absolute path) was renamed under the
document-folder rule, which limited the new name to the file's own
folder, although the same rename with no declared access could move it
anywhere in the project. Any declared kind could be stricter than the
default in the same way.
Every desktop local file request now goes through one resolver,
resolveLocalRequestPath: it runs the default project check first (roots,
Orca's floating folder, symlink containment, outside-root path text
refused before any filesystem call) and only on a refusal applies the
declared kind's rule, which adds paths outside projects. Reads, saves,
rename source and target, and import destinations all use it, so a new
kind gets the rule for free. Automatic loads (document and chat) still
refuse Windows device paths and names by text first, even inside a
project; a device is never a file to show.
The document-resource rule no longer needs its own project branch, and
chat images no longer re-run the roots check for shares.
* fix(fs): symmetric outside-project renames, notebook real folder, same-share images
- Renaming a file opened outside every project accepted a name in a
subfolder (`archive/todo.md`), but the Undo and the rollback rename,
declared from the moved file, were then refused and the file stayed
moved. A rename under document-folder access must now land directly in
the document's own folder (checked by path text before any filesystem
call), so rename, Undo and rollback are symmetric. Image import still
accepts the folder or a folder under it. Inside projects the default
check still allows any in-project target.
- A notebook opened through a link inside a project started its kernel in
the link's folder instead of the real file's folder (main's behaviour),
because notebook and AI Vault log-tail paths skipped the project check.
Both now resolve through resolveLocalRequestPath (project check first,
then the user-file rule).
- A markdown file opened from a Windows share outside every project could
not show the images beside it. Document images on a share are now
allowed inside the document's own folder; the folder text check refuses
every other host and share before any filesystem call.
- resolveDesktopAuthorizedPath is async, so a synchronous failure in the
default check rejects like any other refusal.
* fix(fs): refuse share images outside projects again; keep renames and kernels as on main
- Reverts the same-share document image rule from the previous commit.
Its folder check compared hosts case-insensitively, so a host spelled
with U+212A KELVIN SIGN (or a decomposed accent) passed as the
document's own share and was contacted, reopening the network
credential leak. Document and chat images on a share outside every
project are again refused by path text before any filesystem call;
tests now cover the look-alike hosts with zero filesystem calls.
- Renaming a file opened outside every project into a project folder
passed the project check, but its Undo (declared from the new path)
was refused and the file stayed moved. When the rename source is
allowed only as the opened document, the new name must now land
directly in the document's folder even if a project would accept it
(resolveLocalRenamePaths).
- A notebook opened through a link outside every project started its
kernel in the link's folder; main used the real file's folder. The
kernel cwd is now the real file's folder in every case.
* fix(fs): a file opened outside every project renames to any path, and keeps its access
Renaming a document the user opened (floating workspace or full-path tab) now
follows the user-file rule: the source must be the opened document, and the
new path can be any absolute path, so a rename into another folder, a
subfolder or a project works, and its Undo (declared from the moved file)
comes back from there. Any other rename keeps the project check only. Remove
the same-folder rename rule and its tests; image import stays in the
document's own folder.
After a move, a tab stored by its full path keeps its full path instead of
being recomputed project-relative, so it keeps user-file access for save,
the next rename, image insert and restore after restart. Folder moves go
through the same remap.
Also un-export unused resolver exports and avoid a copy for single-chunk reads.
|
||
|
|
70cf91299b |
Clean up retired OpenCode configuration copies safely (#25222)
* fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): confine overlay manifest cleanup to owned directories Co-authored-by: Adnan Khan <adnank11427@gmail.com> * fix: wait for OpenCode worker composer before first dispatch Reuse captured composer readiness on local and paired execution hosts and revoke launching-shell paste anchors. Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * feat(opencode): probe execution-host CLI capabilities * fix(opencode): select plugin default for execution host loader * fix(opencode): limit prompt prefill capability to verified release * feat(opencode): probe launch capabilities on the execution host * fix(opencode): select plugin loader for the launched host binary * fix(opencode): match WSL probe cwd and declared guest environment * fix(opencode): preserve launch environment deletion boundaries * wip(opencode): authorize native startup prompt intent at execution owner * fix(opencode): atomically replace status plugin entrypoints * fix(opencode): retain plugin permissions across restrictive umasks * test(opencode): resolve permission fixture from primary cwd * feat(opencode): install startup prompt plugin independently of status hooks * fix(opencode): wait for admitted startup intent and preserve failed-launch briefs * fix(opencode): unsubscribe hook settings during async host shutdown * STRICT launch CI contract correction * CAPS launch CI contract correction * INTENT launch CI contract correction * test: initialize Claude prompt state in output retention fixture * Wait for OpenCode location hydration in intent startup * Bind OpenCode startup readiness to the current location in intent startup * Collect retired source-scoped OpenCode configuration overlays conservatively Credit brennanb2025 for the original bounded, delayed overlay garbage-collection contribution in PR #7627. Preserve ambiguous legacy and shared-service state. * Correct inaccessible-source fixture without spying on native ESM exports * Keep delayed OpenCode cleanup within existing file limits * Reuse the overlay manifest module for existing owned-entry operations * Use the existing filesystem import in the ownership mock * test(opencode): keep overlay GC link tests portable --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com> Co-authored-by: Adnan Khan <adnank11427@gmail.com> Co-authored-by: Orca startup hydration review <agents@stably.ai> Co-authored-by: OpenCode Campaign <opencode-campaign@users.noreply.github.com> |
||
|
|
87bc51d371 | Reduce test deadline waits and exact byte comparison costs (#25187) | ||
|
|
8cd9751963 |
fix(updater): keep macOS Orca open when background instances block updates (#24952)
* fix(updater): guard macOS installs against running app instances * fix(updater): match native app blockers and preserve quit lifecycle * fix(updater): keep ordinary macOS quit on Squirrel's install-on-exit path Converting every quit with a staged update into quitAndInstall made Cmd+Q relaunch Orca, refused the quit when background instances existed, and hijacked app.relaunch()+app.quit() restart flows (profile switch, admin restart) into an update install racing the relaunched old app. Only Update & Restart runs the running-instance preflight now; the quit-without-install allowance is no longer reachable and is removed. * fix(updater): preserve quit intent through macOS staging * test(native-chat): explicitly model legacy published tab ownership --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
73904fcdac |
Release completed updater setup timers and callbacks (#24920)
Cancel completed deferred updater fallbacks in finally, clear only their exact pending callback, and drop the captured timer before the original fallback guard runs; preserve destroyed admission, successor ownership and updater/quit callbacks. |
||
|
|
b49abdb1f4 |
fix: recover renderer launch failures in the running app (#24250)
* fix(recovery): back off a launch-failed renderer instead of tripping the crash breaker
A renderer that the OS refused to spawn (macOS exit 1003 = LAUNCH_RESULT_FAILURE; field
cause: per-user process limit, posix_spawn EAGAIN) burned the 3-reload crash-loop budget
in ~750ms and raised a "graphics driver" prompt, while the condition lasted minutes.
- launch-failed retries in place on a 250ms..60s backoff (~2 min), outside the breaker;
a loaded document resets it. Other crash reasons keep the breaker.
- Each launch failure records renderer_launch_failed_probe {spawnError} from a cheap
spawn probe, so bundles name EAGAIN/EACCES/ENOENT directly.
- The exhausted prompt says the process limit was hit (probe EAGAIN), drops the
graphics-driver wording, keeps Try Again as default, and offers no Restart:
app.relaunch also needs a free process slot and silently fails without one.
* fix(recovery): skip the launch probe on Windows and probe the prompt once
- Re-check quitting after the prompt's probe; don't re-probe on Copy Commands.
- recordRendererLaunchFailureProbe never rejects (breadcrumb write guarded).
- Windows: no spawn probe; a child per failed launch is the per-operation burst EDR scores.
* test: cover quitting and duplicate renderer launch failures
* test: use typed access in PTY delay regression fixture
* fix: scope extended launch retries to POSIX hosts
* test: cover launch probe behavior on native Windows
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: m4air <m4air@Mac.localdomain>
|
||
|
|
f20836c296 |
fix(recovery): prompt instead of reloading into a repeat Windows OOM when commit is exhausted (#23886)
* fix(recovery): ask instead of reloading into a repeat Windows OOM with exhausted commit When another program exhausts Windows commit (RAM + page file), the renderer OOMs, Orca auto-reloads 250 ms later, and the new renderer OOMs again within seconds (launch 13084: 3.5 s after the reload; launch 22912: 34 s). The crash-loop breaker (3 in 60 s) never opens for this cadence, so the user is never told the machine is out of memory. Keep the first automatic reload, but when a win32 reason=oom death follows another OOM within 5 minutes and the pre-gone host sample shows under 512 MB of available commit, escalate to the existing recovery prompt with a new 'low-commit' cause that names the MB left and suggests closing apps or growing the page file. Records renderer_recovery_low_commit_prompt. No-op on macOS/Linux and when commit is healthy. * fix(recovery): gate low-commit prompt on post-OOM readings and recovered deaths only - Reject pre-gone samples taken at or before the previous OOM; they miss the commit that corpse released. - Record an OOM for the repeat window only once recovery actually runs, so skipped teardown OOMs cannot suppress the next first reload. - Skip the install-ACL diagnosis on the low-commit prompt, whose text would not explain Copy Commands. * fix(recovery): read commit at gone time when no sampler tick followed the previous OOM The 10 s pre-gone sampler lands between ~3.5 s repeat OOMs only ~35% of the time, so the gate usually fell back to a silent reload. A gone-time read can only over-report free commit (the corpse already released its pages), so it can miss a prompt but never raise a false one. * fix: reject invalid low-commit readings and clarify recovery advice --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
449b8ca17d |
fix(drop): route local terminal and composer drops through the resolver (#24009)
* fix(drop): copy macOS drag-temp files so the PTY daemon can read them
macOS screenshot thumbnails live in $TMPDIR/TemporaryItems/NSIRD_*, which
only processes attributed to Orca main may open. The detached PTY daemon is
not, so agents in local terminals get EPERM and Claude Code silently drops
the paste.
fs:resolveDroppedPathsForAgent now copies those files, and only those, into a
private per-user orca-drops-<uid>/orca-drop-XXXXXX/ directory, keeping the
original name. It streams from an O_NOFOLLOW handle capped at the inspected
size, so no xattrs (com.apple.macl) come along. The copy is 0600 in a 0700
directory, bounded by the remote-import per-file and per-drop limits, and
rechecked for changes. Other paths pass through. The local branch returns
per-item results, authorizes what it returns, and accepts no worktreePath.
Expired copies are swept 7 days later.
No renderer calls the local branch outside WSL yet, so this ships dark
until the renderer routes local drops through it.
* fix(drop): route local terminal and composer drops through the resolver
Local terminal drops pasted the dropped path directly, and composer drops
attached it after a per-path authorize call. So a macOS screenshot thumbnail
reached Claude Code as a path in a folder the PTY daemon can't open, and the
paste was silently dropped.
Every local terminal drop now calls fs:resolveDroppedPathsForAgent, pastes
what it returns, and reports skips and failures with local wording ("Could
not prepare N dropped files"). The WSL-only branch and the direct-paste
branch are gone; the local-WSL path mapping stays as a step after
resolution. The composer resolves the whole drop in one call, without a
project path so its attachments never get the WSL rewrite, stats only the
resolved paths, and folds resolver skips and failures into its existing
toast. The pane, transport, mounted and owner checks still run after the
await.
* test: verify resolver and write errors surface independently on drop
Add a test case ensuring that when path resolution and PTY write both fail during a file drop, the UI reports both failures separately rather than letting the write error mask the resolution issues. Refactor error handling in pasteLocalDropPaths to catch IPC resolution errors immediately, then handle paste errors separately, so skipped/failed files are always reported via the finally block regardless of outcome.
* test: extract transport variable in drop resolution test
Improves readability by extracting the terminal transport creation from the Map initialization.
* refactor(drop): extract native file drop relay and temp staging utilitie
- Move the native file drop relay queue from attach-main-window-services into
a dedicated native-file-drop-relay module so it owns the async copy and
forward pipeline for drag-temp files, separate from main window setup.
- Extract shared temp-directory management (ownership checks, sweeps,
permissions) into owned-temp-staging-root, used by both drag-temp copies
and remote clipboard staging.
- Simplify dropped-path-resolution to handle only the WSL path rewrite on
local worktrees; the relay handles macOS drag-temp copying before it
reaches terminal/composer drop handlers.
* fix(drop): pass drag-temp files through uncopied with timeout and budget
Large files exceeding the copy budget are now passed through uncopied instead
of rejecting the drop, so copy failures don't lose the entire interaction.
Copy timeout prevents hung copies from blocking subsequent drops, and budget
tracking accounts for retained copies to prevent disk fill.
Extract darwin-user-temp-dir to resolve the correct macOS per-user temp dir
rather than relying on $TMPDIR.
* fix(drop): discard queued drops on renderer reload
Capture the renderer's lifetime when a drop is enqueued. When the
renderer reloads before a copy completes, the operation cancels and
queued drops are discarded, preventing stale content from reaching
the reloaded document.
* fix(drop): serialize drag-temp copies and localize failure reasons
Main no longer sends user-facing failure messages; instead it sends reason tokens
that the renderer localizes. Drag-temp copies run serially under one byte budget
with a pending-copy limit, so non-temp drops can overtake. When a copy stage
aborts, remove any partial copies made so far. Distinguish 'uncopied' (original
handed over) from 'failed' (couldn't get it at all), and add specific reasons for
storage, permission, timeout, and budget exhaustion.
* fix(drop): serialize drops and extend TTL to 7 days
Ensure drops reach the renderer in arrival order by queuing all path drops,
not just copies. Extend TTL from 24h to 7d to support lazy readers like
drafts and startup prompts. Withhold uncopied files from agents that can't
open originals (terminal, composer), keeping editor-only access working.
|
||
|
|
a4606ccae3 |
fix(cli): orca file open no longer moves your view unless you pass --focus (#24244)
* docs(cli): file open/diff/open-changed say they switch the user's view and are for user requests only Refs #9944 * fix(cli): file open/diff/open-changed leave the user's view alone unless --focus `orca file open`, `file diff` and `file open-changed` always switched the desktop to the target worktree, selected the tab and revealed it in the sidebar. An agent skill that opens its answer pulled the user out of whatever they were typing in (#9944), and a phone opening a file moved the desktop too. The commands now add the tab in its worktree without changing anything on screen, including when that worktree is the one being viewed: the new tab is added to the tab bar but the active tab, tab type and focus stay put. In a worktree the user is not viewing, the tab becomes that worktree's selection so it is in front when they go there. `--focus` keeps today's behavior. files.open / files.openDiff take an optional `navigation` target (the existing RUNTIME_NAVIGATION_TARGETS vocabulary); the CLI sends 'all' for --focus, like `worktree create --activate`, and nothing otherwise. The renderer moves the host view only when the target reaches the host; a missing field (phones, older CLIs) leaves it still. Editor opens for a worktree other than the on-screen one no longer write the global activeFileId/activeTabType. Refs #9944 * test(cli): justify the window and runtime stubs in the file-open notification test * fix(cli): keep phone file opens switching the desktop; the CLI asks for 'caller' Phone opens send no `navigation` field, and the phone's diff-review "Open in session" relies on the desktop selecting the diff it opened. A missing field now keeps the original switch exactly; the CLI says what it wants instead: 'caller' (no host move) by default and 'all' for --focus. Older CLIs, which send nothing, keep switching as they always have. Refs #9944 * fix(cli): background file opens select the tab without counting as a visit A CLI open into a worktree the user is not viewing selected the new tab with the same activation a user click uses, which stamps lastFocusedAt and the group's recency list. The worktree jump palette sorts recent tabs by that time, so every agent `orca file open` into another worktree jumped to the top of the user's recent tabs. Editor opens now take a selection mode: 'focus' (default, unchanged), 'background' (select within its worktree without recording focus or recency) and 'none' (add only). createUnifiedTab and activateTab gain recordFocus:false for the background case. Also: tests for reopening an already-open file or diff without --focus, a comment that file opens move only the host window ('all' acts as 'host'), root help lines back under 100 columns, and an accurate remote test title. Refs #9944 * fix(tabs): a background-selected tab still joins its group's tab history recordFocus:false skipped both the focus-time stamp and the group's recentTabIds append while still making the tab the group's active tab. Ctrl+Tab looks the active tab up in that history, so after a background CLI open it did nothing (or went to the wrong tab) once the user switched to that worktree, and hydrate kept the broken history across a restart. Only the focus-time stamp is skipped now; the jump palette's recent rows sort by that alone, so the palette fix stands. Refs #9944 * fix(cli): file open/diff/open-changed --focus help says it brings the user to the file The three commands borrowed the shared --focus line written for terminal create ("Reveal the created terminal session in Orca"). They now use the per-command flag help table; terminal create's line is unchanged. Refs #9944 |
||
|
|
5b93c6216a |
Fix Chat UI paste intake and pane routing (#23784)
* fix(chat): separate text paste from attachments and route by pane Keep composer text independent of image checks and saving, and route pastes caught underneath chat to the originating pane's mounted input. Preserve native event data, selection replacement, undo, and target lifetime checks. Co-authored-by: Wooseong Kim <innocarpe@gmail.com> Co-authored-by: lurunzi <lurunzi@gmail.com> * fix(chat): keep focus and quiet text paste after routing it to chat - A paste inserted into the composer now moves focus there, as the old menu-paste insert did; otherwise a paste routed from the hidden terminal left the next keystrokes going to that terminal. - With a remote-server or not-ready workspace, pasted text no longer shows the "Local attachments are not available" refusal because the clipboard also held an image rendition (common for Office copies). The menu path probes for an image only when the text read is empty, so a paired browser does one permission-gated clipboard read for a text paste, not two. - Latest-value refs update in a layout effect instead of during render. * perf(clipboard): answer "is there an image?" from the format list The chat composer asks the main process whether the clipboard holds an image before explaining an image-only paste on a remote-server workspace. That probe decoded the whole image (readImage().isEmpty()) on the main thread just to return a boolean. Read clipboard.availableFormats() instead, and share the MIME check with the paired-web probe. * fix(chat): a chat cover owns focus, so input never reaches the hidden terminal When a Chat UI tab opened over its terminal, the terminal's xterm kept keyboard focus until the composer claimed it a frame later, and forever if the composer never became ready (still starting, a question card, a phone holding input). The previous commits rerouted paste from that hidden terminal to the chat, but typing and Enter still went to the terminal, an image-only or refused paste left focus there, and about twenty terminal.focus() call sites could put it back. Make "a covered terminal cannot hold focus" structural instead: - The chat cover takes focus in the commit that mounts it and marks the covered xterm inert, so every terminal.focus() path is refused by the browser. Split siblings are untouched. When the chat goes away the xterm is un-inerted, and gets focus back only if focus was inside that chat. - Terminal paste listeners skip anything inside a chat cover (previously only inside a mounted chat root). The reroute from terminal to chat is gone; terminal-only paste is back to main's code. - A paste that finds no chat input (before the chat mounts, or an approval card with no text field) gets a visible refusal from the cover. A disabled composer shows the same notice inline instead of dropping the paste. New copy: "Can't paste — this chat isn't accepting input right now." (the old "Worktree not ready" toast was wrong for a chat that is still starting). - The terminal context menu, which names its pane, keeps a small request event to that pane's chat, now without a clipboard payload and using the existing covered-pane check. - Cmd/Ctrl+V or Shift+Insert on a non-input part of the chat focuses the composer (or question answer) first, so the paste lands there. - The composer-scope check used to decide whether a text field inside the chat keeps its own paste matched the whole pane (the file-drop surface carries the same attribute). It now asks the composer whether the target is inside its input. * fix(chat): don't paste a copied file's name next to the file Copying a file in Finder or another file manager puts its name on the clipboard as text/plain beside the file itself. Since text and images are now pasted independently, pasting such a copy into a local or SSH chat inserted the file name into the prompt as well as attaching the image. On the paste-event path, text/plain that is exactly the names of the pasted files (one per line) is the file's label, not prompt text, so it is dropped when an image from that paste is being attached. Rich-text copies (text plus an image rendition) still insert their text, and a copied non-image file, which is not attached, still pastes its name as before. * fix(chat): don't type a Finder file's name on Cmd+V either On macOS, Cmd+V in the chat goes through the app-menu paste, which reads the clipboard text and saves the clipboard image separately. A file copied in Finder also puts its name on the clipboard as text, so the composer typed the name next to the attachment. On main the menu path never read text once an image saved. The main process now reports the paths of the files a file manager copied (macOS filenames plist or file URL, Explorer's FileNameW, a Linux uri-list). Text that only labels those files waits for the image outcome: dropped when an image is attached (or refused on a remote owner), typed when none came. The same label rule now also accepts a path or file URL per line, which is how Linux file managers label copied files on the paste-event path. * fix(chat): pane focus aimed at a chat lands on the chat Since the covered terminal became inert, focusing a pane that shows a chat (keyboard pane navigation, focus-follows-mouse, split activation) was refused and focus stayed on the pane the user left, so typing went to that visible sibling terminal. The one place a pane's focus is requested now puts it on the pane's chat cover, which hands it to the composer when the pane is revealed. Focus already inside the chat is left alone. * test(terminal): give fake panes the container pane focus now reads Pane focus checks the pane's container for a chat cover, and these two fixtures built panes with only a terminal, so four tests threw. * refactor(native-chat): move composer paste handle and chat-root key routing into their own modules Brings NativeChatComposer.tsx and NativeChatResolvedView.tsx back under the 400-line limit after merging main. No behavior change. --------- Co-authored-by: Wooseong Kim <innocarpe@gmail.com> Co-authored-by: lurunzi <lurunzi@gmail.com> |
||
|
|
59ef74876f |
fix(terminal): the terminal's owner answers colour queries for the terminal's whole life (#23925)
* fix(terminal): the PTY owner answers OSC 10/11 for the terminal's whole life Codex and Claude's `theme: auto` ask the terminal for its foreground and background colours (OSC 10/11) and pick their colours from the reply. Orca answered in the process that owns the PTY only for agent launches and only for 5 s; after that the query was handed to whichever viewer was attached. On Windows ConPTY the owner kept swallowing the query but stopped answering it, so a Codex started from an older shell tab lost its message shading (#22332). On a headless `orca serve` host no viewer existed yet, so a Codex started before anyone attached got no reply at all (#22500). The owner (in-process provider, terminal daemon, SSH relay) now answers every OSC 10/11 query for the PTY's whole life and strips it, so no downstream view ever sees one to answer twice. It answers from, in order: the host-wide viewer theme pushed to that process, the creating viewer's colours sent at spawn (now for every PTY, not only agents), and Orca's default dark theme. The desktop pushes its renderer theme to every owner on change and on (re)connect: a daemon request gated on protocol v38, and an SSH relay notification that older relays ignore. The answer-once rule, the 5 s colour window and the colour-authority handoff are removed; Kitty keyboard queries keep their startup window. Viewer-side answerers (renderer xterm, main's hidden-pane model responder, the mobile webview) stay as the fallback for older owners, which still hand queries off; they are never reached for a new owner. * fix(terminal): answer OSC 10/11 with the colours the pane is really painted with Review follow-ups to the lifetime PTY-owner colour answerer. - The theme catalog moves to src/shared so the renderer and the PTY owners read one source; the owner's last-resort default is derived from it rather than copied. - Main seeds every owner from the host's saved theme settings (light or dark, custom themes, colour overrides) at startup, so a headless host and a desktop pane that queries before the renderer's first push are not told dark to a light-theme user. The renderer's push replaces it. - Colours an app sets with OSC 10/11, and clears with OSC 110/111, are tracked per terminal and reported back, as a viewer paints them; a theme change drops them, as a viewer's theme apply does. - A terminal a paired client created with its own colours answers with those, not the host's theme (`colorSource: 'remote-viewer'` on the spawn intent), so a light client on a dark host is told light. - After the 5 s startup window a reply's echo is watched for 512 bytes instead of 256 KB, and a torn query candidate is released after 500 ms rather than held indefinitely. * fix(terminal): keep the long echo watch for relayed replies; one theme lookup The 512-byte post-startup echo watch now applies only to replies the PTY owner produced itself. A viewer's reply relayed through answerLiveQueryReply keeps the 256 KB watch, because a cooked-mode app can keep printing after it queries and the echo then trails that output. The renderer's getTerminalTheme now calls the shared lookupTerminalTheme, so the custom-vs-built-in theme lookup exists once. * perf(terminal): scan colour overrides in one pass over each PTY chunk Two indexOf searches per OSC went quadratic on long runs of ST-terminated hyperlinks, and the tracker now sees every chunk of every terminal. * fix(terminal): one host viewer colour value, set by whichever viewer acted last A paired client's colours reached the host only as frozen spawn colours on terminal.create, tagged remote-viewer. UI-started agent sessions on a headless host answered OSC 10/11 with the host's saved theme, and a client's theme flip never reached panes it had created. The host now holds one viewer colour value that every PTY owner answers with. The desktop renderer's push, a window focus on the host, the new terminal.setViewerColors RPC, and terminal.create colours from older clients all set it; equal values do not re-notify daemons or relays. The remote-viewer tag (colorSource / terminalColorQuerySource / spawnFromRemoteViewer) is gone; it never shipped in a release. * fix(terminal): paired clients push their terminal colours on connect, change and focus The renderer publisher now hands each published fg/bg to subscribers. A new remote-runtime-terminal-color-push module calls terminal.setViewerColors on every host this client is connected to when it connects (or the host restarts), when the colours change, and when the window gains focus. A host that answers method_not_found or forbidden is not asked again until it reconnects. The app shell also republishes terminal view attributes on settings and system theme changes, so a theme change reaches main and paired hosts with no terminal pane open. * fix(terminal): a host with its own window answers OSC 10/11 with its own theme Round 1 kept one host-wide viewer colour value set by whichever viewer acted last, so a paired client's push (reconnect after sleep, a dusk theme flip) took over the host desktop's own panes until its window regained focus. The value is now derived: this host's renderer colours when a local window has pushed, otherwise the last paired client's push (terminal.setViewerColors or terminal.create colours), otherwise the saved theme. Only a headless host takes a client's theme. The window-focus reassert and the identical-re-push takeover are gone; owners are notified only when the derived value changes. * perf(terminal): scan only OSC starts for colour queries once the Kitty window closes The PTY owner answers OSC 10/11 for the terminal's whole life, and it tried every ESC as a query start: a 240 KB SGR-heavy read cost about 2 ms and 256 KB of bare ESC about 15 ms, long after startup. Once the Kitty query window closes only an OSC colour query can match, so the scan jumps between ESC ] starts, plus a trailing lone ESC so a query torn right after its ESC still resolves on the next read. Output and replies are unchanged. |
||
|
|
993183afd7 |
fix(terminal): every explicit terminal close commits through one main transaction (#22929)
* fix(terminal): every explicit terminal close commits through one main transaction
A renderer save cannot shrink terminal membership once main owns a repo's
topology, so desktop tab and pane closes, CLI split-pane closes and mobile
split-pane closes only became durable when the killed process's exit retired
the surface. A close whose kill failed or threw, or whose exit was never
certified, came back after a reload.
Every close now reaches closeTerminalSurface: the renderer sends an explicit
intent for user and cleanup closes, the CLI and mobile split-pane closes commit
the pane after their stop, and the headless and relayed mobile closes reuse the
same commit. A failed flush keeps the in-memory removal and no longer cancels
the kill. Exit retirement is unchanged.
* fix(terminal): tell the desktop renderer to drop a split pane main closed
A CLI or mobile close of one pane in a split commits the pane in main, but the
desktop kept showing it until reload when no exit arrived to remove it. The
close now sends a leaf-addressed notice: a mounted pane closes by leaf id, and
a parked tab collapses its stored layout. Addressing by leaf makes the notice
and the renderer's exit handling no-ops after each other, which replaces the
numeric pane-id notice that could close the whole tab when the exit won.
* fix(terminal): a pane close never widens into a whole-tab close
A leaf-addressed close fell through to the whole-tab close whenever main's layout no longer
held that leaf as one of several. Main's exit handling retires an exited split pane from the
saved layout, so closing that pane afterwards (the exited-pane overlay's Close, or a CLI close
whose stop delivers the exit first) removed the whole tab, live sibling included, and the
next renderer save could not restore it. A pane close is now a no-op unless its leaf is in a
multi-pane layout.
Also updates two mobile split-close assertions to expect the leaf-addressed notice, and adds a
test that a relayed mobile close of a renderer-listed tab still reaches the renderer's pin guard.
* test(terminal): cover the PTY-handle branch of a CLI split-pane close
The existing CLI split test resolves its handle through the renderer graph, so the branch
that closes a runtime-owned pane by its PTY handle had no test failing without its commit.
* fix(terminal): a CLI pane close with an unconfirmed stop closes only that pane
`orca terminal close <handle>` on one pane of a split used to close the
whole tab, live sibling included, whenever that pane's stop could not be
confirmed (for example an unreachable SSH host). An unconfirmed stop is
unverifiable, not a reason to drop siblings: the close now commits only
that leaf, tells the renderer to drop that leaf, and leaves the owed kill
to the controller's existing SSH pending-kill path.
On a host where no renderer lists the tab, main now also removes the
closed pane from the paired-client snapshot (with its retirement proof),
since no exit may arrive to do it.
* refactor(terminal): one resolver decides whether a pane close becomes a tab close
Every explicit close now states its target as `{kind:'tab'}` or `{kind:'pane', leafId}`; no
optional leaf id silently means the whole tab. Main resolves a close it started in exactly one
place, reading the copy of the tab's panes its layout owner holds (the renderer-published layout
for tabs the desktop renderer lists, main's session layout otherwise). Only `last-pane` escalates,
through the existing tab path so the renderer's pin guard still runs; an unknown pane never widens.
- The CLI and phone paths drop their per-site sibling counts for the resolver.
- The notifier splits into a tab-only close and a leaf-addressed pane close.
- The headless tab closer takes a parent tab id, so a pane row cannot reach it.
- A phone close of one pane on a host with no desktop window now stops and closes only that pane.
- A phone close of one pane with no live process record closes that pane, not its tab.
* fix(cli): an unverifiable stop says the close happened
`orca terminal close` still exits 1 when the process stop cannot be verified, but its message now
says the terminal was closed and names the host's reason, instead of "close failed". It promises
that the kill retries on reconnect only when the SSH relay itself never answered the stop, the one
case a recorded kill order backs.
* fix(terminal): a phone pane close commits even when its kill fails
A paired client's close of one pane threw `terminal_close_failed` before committing anything when
the controller reported the kill failed, so the pane stayed. The kill is now best-effort, as it is
for a whole-tab close: the pane's removal always commits and the failure stays on the PTY's
liveness verdict.
* fix(terminal): a pane close widens only when a copy shows it is the last pane
The close resolver read an owner copy that records no panes as "the tab has
one pane", so a CLI close of one pane of a split, addressed while the
renderer listed the tab before publishing its panes, closed the whole tab.
Every copy now counts only if it records at least one pane, read in the
owner's order with the published rows as the last fallback, and a pane
close widens only when a copy lists that pane as the tab's only one. An
unsplit tab whose saved layout predates its pane still closes: its
published row names the pane.
* fix(cli): promise a kill retry only when the host recorded the kill
The close receipt inferred "the kill retries when the host reconnects" from
the stop reason's text, which a new transport message or a reworded error
would silently break.
An explicit close now records the replayable kill order when its stop goes
unconfirmed, before sending the follow-up kill (whose own failure is
recorded only once its RPC settles), and reports that on the receipt as an
optional `pendingKillRecorded`. The CLI promises the retry only from that
field, so an older host, which never sends it, gets no promise.
* test(pty): justify the controller cast the recorded-stop tests extend
* fix(terminal): parse the close target with typed narrowing
The low-evidence lint gate rejects Reflect.get and broad object parameters,
which failed static analysis. Narrow with 'in' checks instead and cover the
boundary parser's accept and reject cases.
* fix(terminal): a desktop tab close is not refused by a split that bound while it waited
The renderer has already removed and killed a tab it closes, so its close intent now skips the
owner fence phone and CLI closes use. Before, a split pane whose binding was admitted between the
close request and its durable write made main refuse the close, and the tab came back on the next
launch whenever its processes did not exit.
* chore(terminal): note that closedByLayoutOwner goes away once main owns the terminal layout
* test(terminal): reload the close-intent fixture through the SQLite profile store
Main now requires a SQLite profile-state authority for a writable Store, so the save-and-reload
close tests build and reopen their store through the shared SQLite test harness.
|
||
|
|
f5f537ef14 |
Revert "Support mouse Back/Forward buttons in shortcuts (#23287)" (#23350)
This reverts commit
|
||
|
|
a86fae0889 |
Support mouse Back/Forward buttons in shortcuts (#23287)
* feat: support mouse Back and Forward shortcut bindings * fix: ignore duplicate mouse shortcut presses until release |
||
|
|
4cafa50ec0 |
fix(windows): reuse shared PowerShell literal quoting at every hand-rolled escaper (#23083)
Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
82412dab8b |
Persist profile state in SQLite with background writes (#22612)
Migrate profile state to SQLite and move writes and backups into a background worker. Acknowledge terminal, SSH and automation changes only after durable saves. Preserve JSON import, recovery, rollback and compatibility exports. Validate migration, worker failures, maintenance, cross-profile moves and terminal lifetime races with unit, integration and end-to-end coverage. |
||
|
|
f2ac9f29b2 |
fix(browser): let pixel capture hold its own page drawn, without the desktop window (#22534)
* fix(browser): let pixel capture hold its own page drawn, without the desktop window Screenshots were the last browser commands that still borrowed the desktop window: they took the per-page automation-visibility lease, which waits for two desktop-window animation frames (capped at 2 s) and never arrives when the window is minimized or throttled. Only pixel capture actually needs a page drawn — input, scripts, layout, the accessibility tree and PDF all work on a hidden page. Capture now takes a main-owned paint hold: a synchronous, per-page ref-count that tells the renderer one way (no reply awaited) to keep the page drawn and keeps the desktop renderer unthrottled while held. Both Orca's full-page capture and the agent-browser helper's screenshots take it in cdp-screenshot.ts and retry on a bounded schedule until the page answers with a frame; a CDP error fails fast. Deleted: the queue's needsPaint lease, the executeJavaScript acquire path and its two racing 2 s timeouts and late-token cleanup, the renderer's rAF wait and window bridge, the capture commands' own leases, the fixed 300/500 ms settle waits, and the global one-screenshot-at-a-time lock. Rebased onto main after #22528 landed; content identical to the reviewed branch head 7b390ed6a8. * fix(browser): probe for a frame instead of repeating the full capture Retrying a capture resent the caller's full request, so on an already drawn tall page (a full-page capture takes ~0.5 s) the 250 ms retry started a second full beyond-viewport capture while the first was still running. Measured on Electron 43: any later request makes a held page produce a frame, and that frame answers every pending capture with a full, correct image. So the capture is sent once and 1x1 probes follow until it answers; their results are ignored. Also report a detached debugger as detached rather than destroyed, and give the layout-metrics timeout its own "did not respond" message, since that request doesn't need a drawn page. |
||
|
|
800d33e5c9 |
feat: name runtime machines (#22094)
* feat: name runtime machines
* fix: preserve pairing address optionality
* fix(cli): keep host and environment listings local
Listing paired servers read each one's machine name by dialing it, so both listings made a network
round trip per server and waited out a timeout on any that were offline. They answer from this
machine's own pairing store; `orca host name --environment <name>` reads one server's name.
* fix(settings): caption the machine name paired devices actually receive
The caption read the runtime's published name once, when the pane opened, so saving an override
left it naming the old computer while phones already showed the new one. It now re-reads whenever
the saved override changes; the settings write lands in the main process before the store publishes
it, so that read already sees the new name. The name is interpolated rather than baked into the
fallback, and the caption, label and placeholder are in the English catalog.
* refactor(settings): normalize the machine name in one place
The trim and length rules for `machineName` were spelled out separately at the
renderer IPC (trim + 255), the settings load path (trim only, no cap), the RPC
schema (zod trim + 255) and the runtime reader (trim). A hand-edited or legacy
profile could therefore load a longer name than any writer accepts.
`src/shared/machine-name.ts` now owns `MACHINE_NAME_MAX_LENGTH` and
`normalizeMachineName`, and every writer and the load path use it. The RPC
schema keeps rejecting over-long names but derives its cap from the constant,
and the runtime settings controller normalizes an RPC write before storing it.
* fix(runtime): detect the machine name once and label handoffs with it
Every runtime constructed in a process (the app, plus each one a test builds)
ran its own `scutil` lookup. The friendly name is a property of the host, so the
lookup is now a single shared promise; construction still never blocks on it,
and a rejected lookup can no longer surface as an unhandled rejection.
The structured-chat handoff banner ("Agent is open in terminal on X") named this
host with the bare `os.hostname()` while paired devices saw the published name.
The transport now reads the same `RuntimeMachineName`, through a getter so a
rename in Settings is reflected without rebuilding the transport.
* fix(cli): print the name the runtime publishes and keep its envelope
`orca host name --name X` printed `undefined`: `settings.update` replies with
`{ settings }`, but the handler read a bare `machineName` off the reply, and the
test fixture mirrored the wrong shape so it passed. After a write the command
now re-reads `status.get` and prints what the runtime publishes, so a blank
`--name` prints the detected name it returned to rather than an empty string.
The read path wrapped a possibly routed answer in a local envelope, stamping
`_meta.runtimeId: "local"` on a reply from another server. It now returns the
`status.get` envelope itself, and an unreachable runtime is reported as the
usual error instead of an invented "unknown" name.
`environment list` had gained machine-name and platform columns that no caller
populated, so every row printed "platform unknown"; the columns are removed.
* refactor(settings): give the machine name field its own component
The caption under the field re-read runtime status every time the saved value
changed, relying on a comment about write ordering to show the new name. A saved
override already is what paired devices see, so the hook now derives the caption
from it and asks the runtime only for the detected name; a stale status read can
no longer show the previous name.
`MobileMachineNameField` owns the store read, the published-name hook and the
debounced input, so `MobilePairingSetupSection` returns to its prop shape and
the pass-through `MobilePanePairingOutput` wrapper is gone. Paired-device
revocation moves into `useMobilePairedDeviceRevocation`, which keeps
`MobilePane` within its line budget with an extraction that carries behavior.
The web client mounts this pane too, but its settings store kept the name
locally where nothing published it. `machineName` now rides the existing
runtime-backed settings sync so the field renames the paired runtime.
* refactor(settings): normalize the machine name at the store boundary
Every writer (desktop IPC, web RPC, CLI) reaches the store through
updateSettings, which already normalizes the other free-text settings
there. Trim and bound the machine name in that one place instead of at
two upstream edges, so a future main-process writer is covered too.
* test(settings): pin machine-name routing and detection, and make the field searchable
The shared machine-name lookup test spawned the real `scutil` twice and compared the answers, so a
slow runner could time one spawn out to the hostname and fail. It now mocks the subprocess, proves
the hostname answers until the one shared lookup lands, and that a second runtime does not spawn
again.
`host name` is no longer pinned local, but only the explicit `--environment` route was covered; an
ambient `ORCA_ENVIRONMENT` now has its own test so the pin cannot silently grow back.
The Machine name field is added to the Mobile pane's search catalog at the tail, keeping every
existing row's tie-break index.
* fix(runtime): wait for the machine-name lookup before publishing status
A status read answered in the first few milliseconds after launch published the bare
hostname because the friendly-name lookup had not landed yet, and a caption fetched in
that window never corrected itself. RuntimeMachineName now exposes the settled lookup
as a promise, and both status publishers (the status.get RPC and the desktop
runtime:getStatus IPC) await it before reading. Construction, listen, and every other
method stay unblocked; the worst case is one wait of at most a second on the first read.
* fix(cli): refuse to rename a runtime that does not publish a machine name
An older Orca runtime rejects the unknown settings field with a bare invalid_params, so
'orca host name --name' routed at one failed with no explanation. The runtime that does
not publish machineName on status cannot store one either, so the CLI reads status first
and refuses with incompatible_runtime and a message that says to update that host,
before writing anything.
* fix(ipc): introduce this desktop to remote hosts by its machine name
When this desktop connected to a remote workspace host it announced itself under a
hostname captured once at module load, so a renamed machine kept its old name on every
other device's connected-clients list. The client name is now read at send time from
the runtime's machine name (the configured override, else the detected one), passed in
where the remote workspace handlers are registered, so a rename reaches the next
presence frame without a relaunch.
* fix(runtime): keep the machine-name lookup under the status probe budget
Status publishers now wait for the one-time name lookup, and `orca status`
probes them with a one-second budget. scutil answers in milliseconds, so a
half-second cap keeps a stalled lookup from making a healthy runtime read as
"starting" while still preferring the friendly name.
* refactor(web): drop the unreachable machine-name write path
The Mobile settings section is desktop-only, so the paired web client can
never render the field. Forwarding the name through the web settings sync was
dead code, and against an older host the strict update contract would have
rejected it while the local mirror kept the value. Remove it until a web
surface exists.
* chore(i18n): translate the machine-name strings and document paired-server rows
Add the Machine name field and its Settings search entry to the five non-English
catalogs, explain in the host list spec why paired-server rows report an unknown
platform, and drop a stale timeout figure from a test comment.
* refactor(settings): make the machine name a machine-wide setting with a General home
The name other devices and hosts list this computer under is not a mobile
setting. Rename MobileMachineNameField to MachineNameField, give it a per-mount
id, and put its primary home in Settings > General under "This computer". The
Mobile pane keeps the same field. One shared search entry feeds General, the
Mobile pane, and the copy now says "other devices and hosts" in all six locales.
The web client has no machine of its own to name and its settings mirror cannot
persist one, so the field renders nothing there and General omits the section.
* feat(mobile): name this computer in the Orca Mobile pairing step
The "Pair this computer" step now shows the same machine name field above the
connection choice and code, so a user pairing a phone from the sidebar page can
name the computer right there.
* feat(settings): name this host when sharing it with other devices
Share this host produces the access link other devices use to reach this
machine, so it mounts the machine name field first. The pane's search entry
takes the shared machine-name keywords so a search lands there.
* feat(sidebar): name this desktop when adding a remote host
This desktop introduces itself to a new SSH host or remote server under its
machine name, so the Add Remote Host dialog mounts the field once, between the
header and the host fields, in both modes. Submit logic is unchanged.
* feat(settings): name this computer in the SSH pane add form
The SSH pane's add form mounts the machine name field above the host fields.
Editing a saved host leaves it out; that host already met this computer.
* fix(mobile): drop the empty machine-name grid row on the web client
The pairing step wrapped MachineNameField in its own grid-area div. On the
web client the field renders nothing, so the wrapper left an empty row and
an extra row gap between the copy and the connection options. The field now
takes a className for its root, so the grid slot disappears with it.
* fix(settings): let Enter in the machine name field submit its form like sibling inputs
The field intercepted Enter to blur and commit instead of submitting the enclosing
SSH add form. The draft is already flushed on blur and on unmount, and the name is
read from the store whenever a peer asks, so nothing is lost when the form submits
first. Enter now behaves like the neighbouring inputs; the test proves the submit
fires and the name still commits when the form closes.
* fix(mobile): keep the machine name inside the pairing copy cell
A dedicated grid row stayed in the template on the web client, where the field
renders nothing, adding an empty track and a second row gap between the copy and
the connection options. The field now sits at the end of the copy cell with the
same 18px rhythm, so an absent field leaves nothing behind.
* fix(runtime): retry a failed machine-name lookup instead of latching the hostname
On a loaded Mac the scutil lookup missed its 500 ms cap during app boot, and
because the fallback was memoized for the process, every status read and the
Settings caption showed the bare hostname for the rest of the session.
The lookup now gets a 5 s timeout, a failed attempt (timeout, spawn error,
non-zero exit, empty output) clears the shared memo so a later ready() retries
after a 30 s interval, and status publishers wait only up to a 750 ms publish
budget before answering with what read() has now. A friendly name and the
non-darwin hostname stay final.
* refactor(settings): show the machine name only where other devices join this computer
The Add Remote Host dialog, the SSH pane add form, and General all describe
another machine, so a field about this computer's own name read as a third
kind of label there. The field now mounts only where other devices pair with
or connect to this computer: the Mobile pane, the Orca Mobile pairing step,
and Remote Servers > Share this host.
|
||
|
|
35fe67b610 |
fix(perf): measure terminal latency with presented CI frames (#22096)
* fix(perf): present benchmark frames only on isolated CI display * fix(perf): wait for the benchmark page before presenting its window * docs(perf): record full scale pass with unchanged latency budgets * test(perf): document and verify the isolated display exception |
||
|
|
7da9788c83 |
fix(updater): send the gh token and cache the release picker's build list (#21902)
* fix(updater): send the gh token and cache the release picker's build list The dev build picker listed releases through api.github.com with no Authorization header, so it spent GitHub's 60/hour per-IP bucket that every unauthenticated caller on the same network shares, and it refetched on every settings mount and channel click. When that bucket ran dry the picker showed "No builds found" with a rate-limit line even though GitHub was healthy and the user's own token had its full quota. Attach the local `gh auth token` when there is one so the request draws from the user's 5000/hour bucket, fall back to unauthenticated on a rejected token or a spent token bucket, cache the list per channel for five minutes in the main process (the refresh button forces a reload), classify 403 by the rate-limit headers, and say when the limit resets. Fixes #21898 * fix(updater): don't trip breaker for secondary rate limits GitHub sends x-ratelimit-remaining: 0 on both primary and secondary limits. Secondary limits carry Retry-After and shouldn't block all core gh commands — only the primary limit should trip the shared breaker. * Scope gh rate limits to execution environment * Add build list cache hint to release channel settings Inform users that build lists are cached for 5 minutes and they can refresh to check for new builds immediately. This makes the cache behavior visible and explains why a manual refresh is necessary to bypass the cache. |
||
|
|
1ff4fe677c |
fix(main,preload): tear down renderer relay and preload listeners (#20909)
* Clean up renderer relay listeners on teardown * fix(main): guard empty markdown relay results * test: document relay window test double safety * fix(relay): retain web contents through window destruction --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
78609330d1 |
Fix browser viewport presets incorrectly scaled by UI zoom (#20962)
* Fix browser viewport presets scaled incorrectly by UI zoom Browser viewport presets must remain in window DIP (native) coordinates but scale in CSS pixels as UI zoom changes. Store preset dimensions as CSS variables in DIP, then divide by the live UI zoom factor in the stylesheet. Also consolidate zoom factor calculations across the app to use a shared `uiZoomFactorFromLevel()` function and add `windowDipToCssPx()` for converting native coordinates to CSS pixels. * Move viewport preset zoom compensation to CSS class Inline width/height styles outrank class rules, preventing the zoom compensation from applying. Using a class rule ensures the viewport scales correctly as the UI zoom factor changes. |
||
|
|
47bb473ec6 |
Remove agent map from dashboard popout (#20929)
The agent map view was not functional and its components have been removed entirely. The dashboard popout now only supports the kanban board view, with all map-related code, utilities, types, and translations cleaned up accordingly. |
||
|
|
6fe140ded8 |
Report clipboard and composer drop failures (#20795)
* refactor(renderer): give the IPC error reader a clamped and an unclamped shape * fix(composer): name the attachments a drop could not add, in one toast * fix(composer, source-control): use one stable failure toast slot - Replace per-worktree toast IDs with single slot that replaces on each failure - Remove destructive retry actions; discard must confirm in dialog - Consolidate filesystem import types to shared location - Add compactIpcErrorMessage for string error handling * refactor: centralize filesystem import types and clarify failure naming Move import result types from main/ipc to shared layer so they're available across preload and renderer. Rename uniformFailure → commonFailure and skippedOrFailed → failureCount for clarity. Simplify preload/API type definitions by reusing shared types directly instead of duplicating inlined union shapes. * Reuse single toast slot for composer drop failures Multiple drop failures now replace the previous toast instead of stacking, preventing notification clutter. Uses a dedicated toast ID separate from Source Control's stage/discard notifications. * fix(source-control): surface a failed notes copy instead of swallowing it * Simplify diff comment notes copy error message Replace parameterized translation template with a direct string. Add explicit type annotations in tests to improve type safety. * Sanitize clipboard write error messages for user display - Only user-friendly messages for recognized errors - Native failures logged but not exposed to UI - Prevents information disclosure (CWE-209) |
||
|
|
12f53da542 |
Remove settled-worker automatic resume and hibernation fences (#19544)
* Remove settled-worker automatic resume and hibernation fences * test: retirement rollback case follows the no-fence policy Case 4 seeded and asserted automaticResumeBlockedBy, which this branch deletes. A rolled-back settled worker is now an ordinary done record that wake clears as passive evidence, same as any finished agent pane. * chore(i18n): regenerate the runtime-required catalog for the contrast floor strings * test(orchestration): give the stopping-worker guard fixtures a Run |
||
|
|
1e693edee4 | fix(clipboard): route runtime-owned SSH image paste through the runtime (#17679) (#19352) | ||
|
|
06a607a1d7 |
feat(orchestration): make multi-agent workflows durable (#16904)
<!-- orca-pr-loc -->
<!-- Programmatic LoC summary. Do not edit by hand; rewritten on every commit. -->
| | Files | Added | Deleted | Net |
| :--- | ---: | ---: | ---: | ---: |
| Test | 225 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$21666 | $\color{#cf222e}{\Huge{\mathbf{−}}}$2820 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$18846 |
| Prod | 348 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$17107 | $\color{#cf222e}{\Huge{\mathbf{−}}}$4706 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$12401 |
<!-- /orca-pr-loc -->
## ELI5
Orca now treats orchestration like a durable control plane instead of inferring success from terminal keystrokes. Agents can tell whether a prompt was accepted or a turn started, replay an ambiguous request without sending twice, and recover coordinator mail after a crash. Completed workers can be inspected, released, or retained, and their panes no longer auto-resume as if the work were still running.
## What changed
- **Run receipts** from `run-create/use/current/show/list` are the row without routing plumbing (`home_database`, `coordinator_pane_key`) and without the duplicate `binding` object.
- **`terminal send` receipts are honest and idempotent.** `input_accepted` and `turn_started` are the only stages; `--wait-submit` observes without resending; `--retry-request <uuid>` replays the exact request against the same process incarnation. A transport timeout keeps the retry ID; only a different runtime answering strips it. Value-less or non-UUID `--retry-request` is rejected on the CLI and the SSH shim.
- **Mailbox delivery is committed before wakeup.** Pointer writes are staged in the DB before any PTY byte, replayed once after restart, and never emit a naked Enter. The watermark that parks concurrent deliveries is released with the DB reservation. Restart rescans pointer-pending and `dispatch:` mailboxes.
- **Lifecycle is a guarded transition graph** (`lifecycle-transition.ts`) with a table-driven test over every caller edge. Task reopen/overturn stays in the public contract. A PTY exit during `worker-stop` is the stop succeeding, not a failure.
- **Worker lifecycle CLI:** `worker-start` (`--spec` creates Task + attempt in one call), `worker-show`, `worker-read` (provider transcript first, bounded terminal fallback with a typed reason, local/WSL/SSH), `worker-stop`, `worker-abandon`, `worker-release`, `worker-retain`, `worker-list` (rowid-fenced pagination, fleet liveness, `attention`, literal `nextAction`).
- **Release is an explicit ownership table** (`decideWorkerTerminalRelease`): only an `owned` resource can be settled, the archive is mandatory where reachable, and an owner whose process is proven exited can always get out of `retained` via `archive_status: unavailable`. User-taken-over, external, and transferred panes stay retained.
- **Settled-worker resume fence** (folds in #17651): a settled dispatch whose pane is still open is fenced at settlement, on stop/abandon/exit, and at startup; lifted on release, retain, takeover, and pane reuse.
- **Liveness is `live` / `unverifiable` / `exited` only**, from execution-host evidence. Fleet projection reads the evidence clock, not the relay delivery clock. A host-certified exit outranks the worker's settled state. `unverifiable` never authorizes stop, abandon, retry, or release, in code or in the guide.
- **Federation:** structured reads negotiate by `method_not_found` so every shipped host keeps transcript-first output; exited remote workers are closed before being reported closed; epoch fencing holds across peer restart, downgrade, and pairing rotation; no per-second forced capability probe.
- **Schema v35:** repairs databases stamped v34 by the pre-fix branch (mailbox_handle default, index predicates), drops the write-only `lifecycle_transition_receipts` ledger and five never-read v31 identity columns.
- **Schema v36:** `dispatch:<id>` mailboxes get a real consumer generation on `dispatch_contexts` and `remote_dispatch_attachments`, bumped and fenced in the same transaction on every re-attach (manual inject, worker-start, federated attach). A stale worker whose Dispatch moved to another process now gets `consumer_fenced` instead of silently acking the new worker's Delivery. Run mailboxes already worked this way.
- **Schema v37:** `dispatch_contexts` records its creator (`creator_handle`, `creator_pane_key`), so a coordinator's context-only self-dispatch is bookkeeping rather than a nesting parent; before this, one self-dispatch made every later `worker-start` from that coordinator fail the depth cap. Pre-v37 rows keep counting (fails closed).
- **Dispatch-mailbox ownership is checked, not inferred.** A `check` from a process whose pane no longer holds the Dispatch, or whose last Attempt was abandoned/failed and moved to another terminal, gets `consumer_fenced` instead of an empty inbox that reads as "no mail yet". `--peek`/`--all` stay readable. A paneless caller still gets `stable_pane_required` with the rebind recovery.
- **Liveness certification is stricter:** a `process_exited` stage whose termination reason is `unknown` (a stop that was issued but never observed) projects `unverifiable`, not `exited`. Federated `worker-show` carries the execution host's verdict and host kind instead of a local guess. A live, ready worker with nothing pending has `nextAction: none` rather than pointing at the `worker-show` that produced it.
- **Wire:** `workerShow` keeps `dispatch.task_id` next to `taskId` for shipped CLIs. `ask --json` uses the standard `{ok, result}` envelope like every sibling verb.
- **Migration start-version detection** treats the two v32 recovery columns as versioned. Before this, every shipped database stamped below 32 resolved to the v6 floor and replayed the whole chain (the v23 backfill synthesized 68 phantom retained workers on a real v30 profile). Verified on a copy of a real 62 MB v30 profile: starts at 30, no row delta, integrity ok, 11 ms.
- **Skill guide** rewritten as a ≤200-line kernel plus seven references, to the outcome-first standard (Result / Done / Safe failure first, conditions not case lists, one done bar, references loaded at the point of use). The canonical loop uses `worker-start --spec`, names `worker-list` for completion accounting, documents `--retry-request` / `request-show` / `--wait-submit`, and requires positive evidence before any stall action. The other seven guides get the same treatment in #18724, split out so this PR stays orchestration-only.
- **`rpc/methods/orchestration-*`** (126 flat files) regrouped into `orchestration/{worker,federation,messaging,runs,gates}/`.
## Why
User reports showed the same boundary failures: false `agent_prompt_stalled` causing duplicate sends (#15180), coordinators unable to trust screen scrapes, cold-parked terminals receiving a pointer without the submit, settled workers accumulating as live tabs and auto-resuming after restart, and no way to tell a stalled worker from a working one.
## Linked issues
Fixes #15180. Fixes #17935 (orchestration skill description is 866 characters; a guard now caps every bundled skill at 1,024). Supersedes #17651 (fence folded in). Advances #16660, #16522, #14907, #13047.
## Review record
This PR was reviewed adversarially after revival: eight independent lenses (lifecycle, mailbox, send, worker, federation, transcript, complexity, live ergonomics), each required to prove findings with a failing test. That produced 16 proven blockers, all fixed with red-then-green regression tests, followed by two re-review rounds and a third fix wave that caught 3 regressions introduced by the fixes and 7 fixes that missed their target; all closed. A final pass (five lenses incl. a live built-runtime smoke, then a re-review of the fix wave) found and fixed seven more, chiefly the stale-worker mailbox steal, the self-dispatch depth wedge, and the unproven-exit certification. Three independent Codex (gpt-6-astra) passes followed: the first found nothing new, the second found and fixed 3 defects (task-status reachability, WSL-local host classification, peer-capability epoch), the third found and fixed 6 (production PTY controller never installed settled writes, ambiguous in-flight pointer failures allowed duplicate replay, SSH/relay deadlines cut off a valid `--wait-submit`, stop-vs-exit race during inspection, and two release-recovery paths for vanished or exited terminals). The full record (findings, proof tests, triage, declines with reasons) is archived outside the repo.
**Rework after the live smoke.** A first live cross-host run on the shipped adhoc build (this Mac, a paired Windows host on the same build, a paired Mac on 1.4.195, and an SSH host) found a P1: a running local worker read `unverifiable`/`missing_status` because the fleet snapshot rows lacked the terminal handle the matcher keyed on. A 59-row failure table over every bug fixed during review showed the same two classes recurring: a fact dropped in transit through optional fields, and two authorities for one fact. Two blind designs (Opus, Codex) converged on the same mechanisms, and the scoped tranches landed here with red-then-green seam tests from the real producer to the real consumer, faults injected only at the transport or hook-ingest boundary:
- **Settlement (data-loss class):** one three-valued `WriteSettlement` (`accepted | refused{reason} | unverifiable{reason, bytesHandedToTransport}`) from the SSH multiplexer through daemon client, providers, controller, to pointer staging. No boolean, no rejection-as-third-state. The two silent degrades that fabricated a handoff are deleted; a provider that cannot settle refuses before any effect. Pointer text and Enter share the contract; a partial flush is `unverifiable`, never `refused`.
- **Evidence identity (false-liveness class):** fleet agent-status evidence is a tagged union (`binding: worker | pane | unresolved{reason}`, `clock: observed | delivery`) minted once at ingest, so a hook row captured on one process incarnation can never bind to a later dispatch on the same pane. The matcher's `!worker.paneKey ||` defaults are gone. One host-scope parser replaces two.
- **Small pre-merge items:** `capability_unsupported` from an old peer is no longer relabelled `host_unavailable`; a producer census test asserts every agent-status consumer path projects a pane-only hook row as `live`.
Two ergonomics defects the second live run surfaced on a real database are fixed here too: a pre-v3 dispatch already marked `completed` projected as `outcome_unknown` / `requiresAction: true` forever (three copies of the outcome ladder disagreed on legacy rows; now one resolver, legacy `completed` reads `succeeded` with nothing to act on, legacy `failed` stays actionable on the failure), and an unscoped `worker-list` enumerated the entire database (now defaults to the Run bound to the calling terminal, `--run` overrides, and the receipt's additive `scope` field says which).
A third live round on the shipped adhoc build of `b082443e1f` (same four hosts) plus an unscripted run in the user's own prompt style (a plain Claude Code shell, `/orchestration`, three workers, zero errors, bound-Run default confirmed) found two more branch defects, fixed with red-then-green tests: a worker freshly started on a paired server projected `unverifiable`/`host_indeterminate` with `requiresAction` for ~3 minutes, including after its own `worker_done`, because the host's federation observation returned `missing_liveness_verdict` for any PTY the liveness register had not yet swept (the host now reads a connected pane it owns locally as `live`; disconnected or SSH-scoped panes stay `unverifiable`); and six pre-v3 completed rows still carried an `input` category because settling through the task-status path or `failDispatch` never closed the Dispatch's pending question threads (both paths close them now, and schema v38 closes threads already pending on settled rows). The guide's `worker-start` examples now show `--model sonnet`, since an omitted model inherits the launcher's default.
A Codex adversarial pass on the tranche diff found one real design hole (identity minted at read time instead of ingest, now closed) and two daemon settlement paths that threw instead of settling (fixed). Two `@ts-nocheck` runtime mixins on these paths were extracted into checked modules; the repo-wide `@ts-nocheck` count is unchanged at 171.
Deletions during review: ~1,900 lines (write-only ledger, unread columns, dead v1 archive path, test harnesses shipped in prod, duplicated liveness and state-machine copies, self-capability checks that were compile-time true).
## Testing
- `pnpm typecheck:tsc:node|cli|web` clean
- `pnpm run check:code-quality:changed` 0 findings; `check:react-doctor:changed` 0
- `pnpm verify:bundled-skill-guides`, `verify:skill-bundle-manifest`
- full `pnpm test` on the integrated head: 72,332 pass / 292 skipped; the only failures were three non-PR files (two zsh live-shell suites hit a node-pty spawn-helper ENOENT while a concurrent native rebuild ran, 44/44 in isolation; `release-checkout.unit.test.ts` is a known 30 s load timeout that passes in isolation on `origin/main` too).
- CI on
|
||
|
|
681119dc05 |
test: isolate window mocks from inherited launch flags (#18989)
* test: isolate mocked window activation from inherited launch flags * Preserve background window regressions added on main |
||
|
|
a730becd7a | fix(automation): keep explicit background launches off screen (#18898) | ||
|
|
58553bfe1c | fix(recovery): fail a renderer recovery reload that never loads, instead of leaving a dead window (#18466) | ||
|
|
436ef827dd |
fix(browser): present Electron's own user agent so Cloudflare Turnstile clears (#18749)
Orca rewrote every browser session's UA to look like plain Chrome by stripping the Electron and app tokens. That rewrite is what Cloudflare rejects: a Chrome UA that ships no client hints reads as a spoof and Turnstile returns 600010, while the same binary on the same IP clears every challenge with its stock UA. PR #885 added the rewrite to fix 600010 and was treating a symptom it created; issue #11518 later found the same rewrite is what broke Google sign-in. - Keep the stock Electron UA on every partition. The webRequest handler now only owns the host-scoped Google auth Firefox switch, which stays unchanged. - Delete the anti-detection script. Measured on Electron 43: plugins are already a real PluginArray, window.chrome exists, and navigator.webdriver is false even with the debugger attached, so three of its four premises were wrong, and the overrides it installed (instance-level webdriver, non-native Permissions.query, stubbed chrome.csi/loadTimes) are themselves published bot signatures. - Stop attaching a CDP debugger to every browsing guest. Only the auth-UA detach listener remains, because a detach clears Chromium's standing UA override. - Stop sending Runtime.enable into cross-origin iframes when the agent bridge auto-attaches. The challenge widget is one, nothing reads iframe Runtime events, and the Runtime domain's serialization side effect is the documented Cloudflare CDP tell. - Add a real-Electron test proving the wire identity: stock UA to ordinary hosts, Firefox with no client hints to accounts.google.com. Verified in the dev build: dash.cloudflare.com/login no longer shows "There was a problem with verification" and scrapingcourse.com's managed challenge clears, both failing deterministically before. Fixes #13822 |
||
|
|
f36c03e84a |
fix(windows): make the install-dir ACL repair rescue the launch it runs in (#18361)
* fix(windows): repair the poisoned install-dir ACL before the window, not after The install-dir LPAC ACL poison (electron/electron#51761) still costs every affected machine at least one crash: the probe that detects it is setImmediate-deferred and answers 0.9-3.0s in, while createMainWindow runs synchronously in the same frame and its renderer dies at init 48-1373ms later. - Persist the poison verdict the moment the probe reports it, and await the repair (bounded at 20s) before any window is created on a launch that already carries the marker. - Do not engage the GPU safe-graphics fallback while the install-dir ACL verdict is poisoned or still outstanding. Safe graphics does not rescue a poisoned tree, and --in-process-gpu removes the GPU child, erasing the sibling-death evidence that identifies the shape (4 field reports landed in 'misc' this way). - Clear the safe-graphics marker once the repair lands, so a repaired machine stops launching software-rendered for the rest of that build. - Give the repair marker a bounded retry budget: it was written on failure and matched regardless of outcome, so one transient failure pinned a machine to 'marker-hit' for the life of that version. * test(windows): pin the install-dir ACL repair against the real icacls binary * fix(windows): stop the install-DACL verdict from outliving the evidence Adversarial review round 1. Five blocking findings, all addressed. 1. gpu-lifecycle guard had only a source grep (green with the polarity inverted). The stated justification -- that gpu-lifecycle's import graph cannot be driven in-process -- was wrong: mocking `electron` plus `@electron-toolkit/utils` imports it fine. Replaced with gpu-lifecycle-install-dir-acl-guard.test.ts, which drives the real handleGpuChildCrash against a stub tracker. All four cases go red when the guard is flipped to `if (!isInstallDirAclSuspect())`. 2. A clean probe verdict retired the on-disk marker but not the in-memory `poison` verdict, so a machine the probe just proved healthy kept suppressing the GPU safe-graphics fallback and kept the dialog accusing the install folder -- permanently, since a `status:'failed'` probe deliberately keeps the marker. A positive clean reading now latches `installDirReadClean`, drops the verdict, and outranks a repair result that lands after it (a 'failed' from a repair with nothing left to fix must not re-accuse). 'repaired' is kept: it is not a contradiction and it is what tells the user to reload. 3. `noteWindowsInstallDirAclProbePending()` ran on every `openMainWindow` while the probe is once-per-process, so every tray/second-instance reopen armed a 15s window in which `recordGpuCrash` was never called at all -- on healthy machines. `probeWindowsInstallDirAcl` now reports whether THIS call dispatched, and only a dispatch arms the grace window. 4. The pre-window ordering guarantee was defeatable and untested. `focusExistingMainWindow` opens a window whenever there is none and the app is ready -- true for the whole 20s gate, which is exactly when a user double-clicks the shortcut again. Added a `canOpenWindow` seam (same 'pending' semantics as the existing `!app.isReady()` case) wired to `isBlockingInstallDirAclRepairInFlight()`, plus windows-install-dir-acl-startup-wiring.test.ts pinning the await ahead of both window-creation paths and both new call sites. 5. windows-install-dir-acl-repair.win32.test.ts was absent from the pr.yml win32 allowlist, so it ran nowhere. Added. Also from the non-blocking list: - The repair no longer clears a `userConfirmed: true` safe-graphics marker; "keep safe graphics" is a user choice, not Orca's automatic latch. - `repairWindowsInstallDirPackageAcl` now reports its dispatch too, so a second entry into the gate resolves immediately instead of eating the full 20s budget waiting on an `onDone` that is never coming. - The gate is wrapped in try/catch/finally, matching the contract the probe documents as mandatory for anything upstream of window creation. Rebutted, not applied: - "Gate should be conditioned on app.isPackaged." A dev launch only carries the poison marker if a dev launch actually probed that tree and found the signature, in which case the dev renderer is dying the same way and the repair is exactly what is needed. The adjacent `isPackaged` check guards a packaged-only early-window optimisation, not a correctness boundary. - "Fold the poison marker into the repair marker's `outcome`." They answer different questions with different lifetimes. The repair marker is a retry budget (`attempts >= 3` disables the repair for that version) and is never cleared; the poison marker is cleared by a successful repair and by a clean probe. A `'pending'` outcome written before the attempt would bump `attempts`, so three launches killed mid-repair would permanently disable a repair that never once ran icacls to completion. * fix(windows): keep counting GPU crashes while the install-DACL verdict is pending Adversarial review round 2. Both blocking findings addressed. 1. handleGpuChildCrash early-returned on isInstallDirAclSuspect() BEFORE recordGpuCrash, so the crash left no trace in the 30s rolling window. The suspect window is armed on every win32 non-serve launch, and the field bundles put it at 0.8-1.7s after main_window_created on hosts whose DACL is clean (matchesPoisonSignature=false) -- squarely inside the 2.1-6.2s bad-driver bursts this repo already pinned in gpu-crash-fallback-field-sessions.test.ts. A healthy machine with a failing driver could lose an entire coalesced burst and never engage safe graphics. The crash is now always recorded; only the engagement consults the verdict, and it waits for the verdict rather than acting on the suspicion (waitForInstallDirAclVerdict, resolved by the probe's onDone or by the existing 15s grace, whichever lands first). Deviation from the review's suggested shape, deliberately: awaiting the verdict before persisting anything reintroduces the exact race gpu-fallback-engagement.ts documents -- Chromium aborts the whole browser process on the 6th GPU crash, ~1.3s after the 3rd, which is less than the probe takes to answer. So the unconfirmed marker is written up front and withdrawn if the verdict comes back poisoned. A machine killed mid-wait still comes back software-rendered, and its marker is unconfirmed, which is the state the repair's own clear already retires. gpu-lifecycle-install-dir-acl-guard.test.ts now drives the real GpuCrashFallbackTracker and the real engagement path (the restart prompt firing is the signal) instead of a stub tracker, and covers the case the previous suite could not express: a burst that lands entirely inside the pending window still engages once the probe reports clean. Four reverts go red -- restoring the pre-record guard (2 tests), dropping the wait, dropping the post-wait re-check, and dropping the pre-wait marker write (2 tests). 2. The round-1 evidence block quoted commits, a test name and pass counts that no longer exist, and its real-icacls Windows run predated the commit that rewrote the gate. Re-run at this commit; counts and the live-Windows result are restated in the handoff rather than carried forward. Also from the non-blocking list: - 'marker-hit' conflated "already repaired" with "retry budget spent", because hasMarkerFor matches outcome === 'repaired' too. The result now carries alreadyRepaired, and the recovery maps that to stage 'repaired' -- so a launch killed between a successful repair and its marker clear no longer tells the user the folder needs an administrator, no longer latches isInstallDirAclSuspect() for the session, and does retire the poison marker. Not applied, with reasoning: - "clearGpuFallbackMarker narrowed to userConfirmed === false leaves the target population software-rendered after a repair." The summary was overstated and is corrected, but the narrowing stands: a userConfirmed marker now requires a clean DACL verdict, because the restart prompt that writes it is exactly what the gate above withholds while the install is a suspect. The population this family targets can no longer reach confirmMarker while poisoned. - "writeInstallDirAclPoisonMarker re-stamps on a budget-exhausted machine forever." True, but on that machine the tree really is still poisoned and the gate resolves immediately ('skipped', no icacls spawn, no 20s wait), so the marker is telling the truth. Retiring it would be wrong; only a clean probe reading should. * fix(windows): register the real-icacls spec and stop its teardown racing icacls Two ratchets were red: - windows-lane-tree-removal-boundary: the win32 spec's afterAll used raw rmSync on a tree two icacls.exe children had just rewritten DACLs on, which is the EPERM race removeTreeSync exists for. - win32-test-lane-registration: the spec was in the pr.yml argv but not in WINDOWS_PACKAGE_TESTS, so a future diff touching only test files would not select package_windows and the spec would self-skip on ubuntu and report success. * fix(windows): re-arm the GPU fallback latch when the install-DACL verdict withholds it recordGpuCrash reports the threshold crossing exactly once and latches `engaged`. handleGpuChildCrash consumes that report before consulting the DACL verdict, and installDirAclClearsGpuFallback then discards it — so nothing could ever engage safe graphics again in that process. A machine whose tree the repair fixes and whose driver is genuinely broken stayed hardware-accelerated through an unbounded crash loop, with no prompt and no marker. disengage() releases only the one-shot latch; the crash window is untouched, so a real driver burst is still never erased. Test is RED without the re-arm. * fix(windows): keep the safe-graphics marker while an install-DACL repair is in flight The gate dispatches a repair without arming the probe clock, so waitForInstallDirAclVerdict() returns immediately and the withdrawal deleted the marker inside Chromium's FATAL window (crash 6 lands ~1.3s after crash 3, well inside the 20s gate). The process then died mid-repair, spent no attempt, and relaunched hardware accelerated into the same gate — spawning the same GPU children, FATALing again, forever. Hold the marker while poison.stage is 'pending' so that launch comes back software rendered and the next gate runs to completion. Still not engaged this launch, so --in-process-gpu does not erase the sibling-death evidence. A terminal verdict has no next step to rescue, so it still withdraws. Both new tests are RED without the retention. * fix(windows): stop a repaired marker outranking a fresh poison verdict The probe reads the install DACL and finds it poisoned; `startRepair` dispatches; `markerHitFor` sees a repair marker recording `outcome: 'repaired'` for the same installDir+appVersion and reports `alreadyRepaired`, which the recovery module maps to stage 'repaired'. So the launch that just proved the tree poisoned runs no icacls, deletes the poison marker that arms the next launch's pre-window gate, clears the suspect flag so `--in-process-gpu` can engage on a tree safe graphics cannot rescue, and tells the user "Orca repaired the permissions." Reachable whenever the tree is re-poisoned after one successful repair of the same version, and whenever a repair reports success without clearing the tree — the silent icacls no-op this module exists to document. A DACL reading taken this launch now outranks the marker: `probeConfirmedPoisoned` stops `outcome: 'repaired'` short-circuiting the repair. The attempt budget still bounds it, so an unrepairable tree does not re-spawn icacls forever. The pre-window gate does not set the flag — it acts on a marker from an earlier launch, not on evidence of its own, so a recorded repair still outranks it there. Also drives the GPU-fallback re-arm test through a repair that actually completes 'repaired', rather than a later clean probe, which is the route the review exercised. * fix(windows): make the pre-window ACL gate act on the poison evidence it fired on The gate fired on a poison marker — an earlier launch's DACL reading that nothing has retired — but withheld `probeConfirmedPoisoned` from the repair, so a repair marker recording an older success still short-circuited it. On the three-launch shape the gate exists for (repair succeeds; tree is re-poisoned; the next launch's probe records the poison but dies before writing its repair marker) the gate ran no icacls, deleted the poison marker that arms every later gate, un-suspected the tree so --in-process-gpu could engage, and told the user "Orca repaired the permissions." `applyInstallDirAclProbeVerdict` then swallowed that launch's own reading behind `if (poison) return`. Both callers of `startRepair` hold outstanding poison evidence, so the flag is now unconditional (renamed `poisonEvidenceOutstanding`) and `marker-hit` means only that the attempt budget is spent. The probe guard is narrowed to an in-flight gate repair: a reading taken after the gate finished re-arms the poison marker and downgrades a claimed repair. Also: withholding safe graphics now ends with the repair budget. A machine whose attempts are spent while the signature persists was denied safe graphics on every launch for the life of that appVersion — and had its marker deleted each time — including the healthy installs the probe's flag-blind ACE match over-matches, where the driver really is broken. Non-blocking, same lane: re-read `isQuitting` after the up-to-15s verdict wait, and skip the recovered-launch prompt when the ACL gate retired the marker read before whenReady. * fix(windows): stop a timed-out gate repair outranking a later poison reading The gate's 20s budget expires while icacls runs on under its own 120s cap, so the probe can read the tree poisoned while that repair is still in flight. Its success claim then deleted the poison marker, un-suspected the tree and told the user their permissions were fixed. The reading is now latched and outranks it. * fix(windows): stop a gate repair claim pre-empting this launch's probe reading Round-7 adversarial findings, both driven against the real modules: - isInstallDirAclSuspect returned false the moment the pre-window gate set stage 'repaired', short-circuiting ahead of the probe-pending grace check. The GPU children die 48-1373ms after window creation while the probe answers 0.9-3.0s in, so an icacls that silently no-opped (exit 0, tree untouched) opened exactly that interval to --in-process-gpu on a still-poisoned tree - and a 'keep safe graphics' answer then pinned a userConfirmed marker no later repair may clear, with the poison marker already deleted so no later launch gates. The claim now stays provisional until this launch's probe corroborates it or the grace window lapses. - A probe reading that disproves a 'repaired' claim re-armed the poison marker but never restored the unconfirmed safe-graphics marker the claim had cleared, so the next launch relaunched hardware-accelerated into the re-armed gate. The clear is now captured and handed back on disproof. * test(windows): pin the nested and update-inherited grants against real icacls The live spec asserted the grant landed on the root-level module file only. It now also pins that the flagless /T pass reaches a nested file carrying its own protected DACL (the shape app.asar.unpacked and node_modules have), and that a file written after the repair inherits the (OI)(CI) root grant - the stated reason that grant form exists. * fix(windows): keep the recovered-launch prompt silent while the tree is the suspect Round-8 fresh-eyes finding, driven against the real modules: the prompt re-read the marker the pre-window gate may have retired, but never consulted isInstallDirAclSuspect() - so after a FAILED gate (tree still a live suspect, window blank behind the 10s reveal fallback, Keep as both defaultId and cancelId) a 'keep it' answer pinned a userConfirmed marker no later repair may clear, on the exact victim class the repair cannot help. The guard now covers both gate outcomes; staying silent leaves the marker unconfirmed, which a successful repair still retires. --------- Co-authored-by: Orca Worker <orca-worker@localhost> Co-authored-by: OrcaWin <alpha-eng@stably.ai> |
||
|
|
a9f2fbb684 | chore(workspaces): drop the dead workspaceCleanup:hasKillableLocalProcesses IPC (#18386) | ||
|
|
573537ecd4 |
feat(cli): make terminal close the canonical workspace teardown (#18073)
* fix(runtime): recover stale session owners and await retirement * fix(runtime): preserve session hydration and smoke compatibility * test(runtime): cover empty and unindexed session owners * feat(cli): make terminal close the canonical workspace teardown * fix(preload): align ssh termination result type * test(runtime): assert folder hydration owner * fix(runtime): fence legacy terminal stop by worktree host * fix(preload): reconcile ssh result import with main * fix(runtime): keep same-id sibling hosts out of workspace close The stale-owner fallback in the session controller re-routed any worktree whose catalog partition had no tabs to whichever other partition held tabs. Only `runtime:` environment ids rotate across relay restarts; `repoId::path` legitimately repeats across hosts, so an SSH workspace close could retire the local copy's tabs and resume records, or flip owners mid-close and strand the SSH PTY. Restrict the fallback to runtime hosts, and pin the session partition once per workspace close so record clearing targets the partition that owned the tabs. * test(runtime): give the cross-host close fixture a real resume record * fix(preload): take main's ssh-bridge import order so the merge stays duplicate-free |
||
|
|
623d58e386 |
fix(native-chat): show pasted images while they save, and make them previewable (#18118)
* fix(native-chat): show pasted images while they save, and make them previewable Pasting an image into the native chat composer showed nothing until the clipboard image finished being written to disk, and the resulting chip could never render the image at all. Preview was blocked by path authorization, not by rendering. Clipboard pastes are written to the OS temp dir, which sits outside every allowed root, so the composer's own `fs:readFile` of the file Orca had just written was denied. `saveClipboardImageBufferAsTempFile` now authorizes the path it writes, the same way other Orca-produced external files are handled. The delay is the macOS paste route: Cmd+V is intercepted in main and delivered through the app-menu paste channel, which has no clipboard blob in hand, so the composer only learned an image existed after the save round-trip. A new `clipboard:readImageThumbnail` probe reads the clipboard in memory and returns a downscaled preview; it runs alongside the save rather than before it, so text paste gains no latency. The DOM-paste route needs no probe — it mints a blob URL from the clipboard file on the same tick. Attachments now carry `pending` and `previewUrl`: the chip appears immediately with the real image dimmed under a spinner, then settles in place on the saved path. Send is blocked while anything is pending, because a pending chip has no agent-readable path yet. Pending chips are kept out of the pane attachment cache so a mid-save unmount cannot strand one, and blob previews are revoked on remove/clear. SSH pastes now carry their connectionId onto the chip so remote previews read over SFTP. Verified in a real Codex native chat under an isolated dev instance: the chip appears in 42-61ms with a spinner, settles at ~141ms, three rapid pastes produce three independent chips with Send disabled throughout, and the lightbox opens the full 5120x2880 image read from disk. Ablation confirms the authorization fix: the written path reads back, an unauthorized sibling in the same temp dir does not. Claude-Session: https://claude.ai/code/session_01NnEfY8NpfFtVnboLKnmgdW * fix(native-chat): avoid stale image attachments and preview cache growth --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
bed9734a9d |
Prevent deleted workspace browser snapshot resurrection (#17779)
* Prevent deleted workspace browser snapshot resurrection * fix: tear down folder workspace browser tabs * fix: fence pre-publication browser snapshots * fix: route folder deletion through runtime cleanup * chore: retrigger CI * fix: sweep folder PTYs on runtime deletion * fix: restore deletion fences after runtime refactor * test: cover deleted renderer snapshot after recreation * fix: avoid publishing ambiguous worktree snapshots * fix: preserve optional worktree index state * fix: fence paired PTYs on worktree removal * fix: harden deletion fence and folder-delete teardown - Folder-group delete no longer fails on a mixed-host group: an ambiguous connection skips the PTY sweep instead of rejecting the delete. - Share one folder-workspace PTY teardown helper between the runtime removal path and the project-group controller. - Simplify the mobile snapshot fence: identity-carrying frames are judged against the live catalog instanceId and clear the fence once the successor is accepted; identity-less frames are fenced by renderer generation. Drops the unbounded epoch bookkeeping. - A fenced frame no longer triggers a resync request on every sync while the renderer still lists it as unchanged. - Cross-host id collisions publish without an instanceId rather than blanking the mobile session for that workspace. - Folder delete IPC always routes through the runtime; the store-only fallback and double notify are gone. - Drop the redundant rescue-path tombstone check; ownership is purged at removal. - Fence tests drive removeWorktreeMetadataAndHistory + syncWindowGraph instead of seeding the fence map, and add accept-after-recreate, no-resync, and ambiguous-host folder delete cases. |
||
|
|
28373fcea7 |
fix(windows): repair the install-dir package ACL that blanks the window (#17740)
* fix(windows): repair the install-dir package ACL that blanks the window An install tree carrying an orphan AppContainer ACE (S-1-15-2-<x>) with no ALL RESTRICTED APPLICATION PACKAGES grant denies Chromium's LPAC children read on the shipped modules; they die at init with 0x80000003 and the window stays blank forever (electron/electron#51761). - Tighten the probe verdict to require the S-1-15-2-2 grant specifically: an ALL APPLICATION PACKAGES (S-1-15-2-1) ACE, the Program Files default, does not appear in an LPAC token and cannot satisfy the orphan. - Drop BUILTIN from the English-locale heuristic (fr-FR/es-ES print it verbatim) so a localized icacls is correctly reported as un-name-checkable. - Add an additive, marker-guarded icacls self-repair: an inheritable root grant plus a flagless (RX) /T pass, never /grant:r. - Route the crash-loop dialog through a testable prompt module that names the permission cause, offers Copy Commands without dismissing itself, and keeps the graphics-driver hint. The repair only runs on win32, off serve mode, and only on the exact probe verdict that reproduced the crash. * docs(windows): correct the install-tree ACL walk cost model * fix(windows): keep the install-ACL poison gate at the reproduced shape An orphan package ACE alongside the Program Files ALL APPLICATION PACKAGES default launches clean on win32 10.0.26200 / Electron 43.4.1, so requiring S-1-15-2-2 specifically declared poison on healthy installs - and this branch acts on that verdict with a tree-wide icacls write and the crash-recovery dialog's primary cause. hasRestrictedPackageGrant stays reported for triage; only the verdict reverts. |
||
|
|
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> |
||
|
|
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) | ||
|
|
ecee14bdbc |
Split main window service attachments (#17151)
* 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 * Fix F3-speech for #17123 * Fix F1-cycle for #17131 |
||
|
|
3457acb647 |
fix(memory): hydrate retained PTYs before diagnostics (#17308)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
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. |
||
|
|
c54bf92181 |
fix(window): stop will-navigate from blocking the lazy-chunk recovery reload (#16944)
* wip: lazy-chunk-reload * fix(window): reject non-document navigation schemes |
||
|
|
551fbb9ac7 |
Revert "feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679)"
This reverts commit
|
||
|
|
fe71487895 | test(main): isolate deferred worktree watcher setup (#16836) | ||
|
|
249d93bc5d | feat(browser-preview): render remote HTML docs locally over an orca-preview scheme (STA-5557) (#16679) | ||
|
|
0e10fc5925 | fix(browser): retire helpers with page owners (#16564) | ||
|
|
cda2280d63 |
Show all automations (#16532)
* 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.
|
||
|
|
868fc39d32 | fix(worktrees): refresh paired clients after external discovery (#16557) |