mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
eedca5ee1c00820a8bc4e735ea0e0807ff634904
316
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
da6d823ea0 |
fix(crash-reporting): renderer crash reports say whether the window was hung or blank (#30692)
* crash-reporting: say whether the main window was hung or blank when its renderer died Windows killed/exit-1 renderer reports read the same whether Task Manager, an EDR, or Orca ended the process. Field bundles show several of them follow minutes of renderer silence or a document that never loaded after an update, which points at users ending a frozen Orca. Record Electron's unresponsive/responsive transitions as durable breadcrumbs and attach rendererUnresponsiveForMs, rendererDocumentLoaded, rendererDocumentAgeMs and the window's visible/minimized/hidden + focus state to the renderer crash details. * fix(crash-reporting): a failed load reads as a blank window, and a crash ends the hang Electron fires did-finish-load after did-fail-load, and a failed load never commits, so track from did-start-navigation and flag main-frame load failures. Clear the hang at renderer death so the replacement renderer's responsive is not logged as the dead one recovering. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
19e45b7921 |
feat(sidebar): the desktop window lists main's sidebar inputs behind the build switch (dark) (#30461)
* feat(sidebar): the desktop window lists main's sidebar inputs, with its own writes shown until main does (dark) Co-Authored-By: Claude <noreply@anthropic.com> * fix(sidebar): a newer write wins in the pending layer, created rows hold the stamped record, writes name their host (dark) Co-Authored-By: Claude <noreply@anthropic.com> * chore(sidebar): move the focus-read allowance with the window list model it moved into (dark) * perf(sidebar): main's sidebar feed publishes a change after a quiet spell at once, not 50 ms later (dark) * test(e2e): the desktop reads-main spec skips on a switch-off build and waits for a frame after collapse --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
347d0e46a1 |
feat(cli): set the title of one terminal pane (#30485)
* feat(cli): set the title of one terminal pane Adds `orca terminal set-pane-title --terminal <handle> --title <text>`; `--title ""` clears it. The runtime resolves the live handle to its saved leaf and asks the desktop window to name that pane through a new optional notifier hook (`ui:setPaneTitle`). Mounted panes apply it through the same local setter the GUI uses; parked panes get it in the saved layout their next mount reads. The tab's own name is never touched. Rebuilt on current main from #24755. Co-authored-by: stevelliu <stevelliu@tencent.com> * fix(renderer): a main-owned layout takes a CLI pane title before the window shows it * fix(renderer): a CLI pane title reaches a never-mounted tab with a rootless layout --------- Co-authored-by: stevelliu <stevelliu@tencent.com> |
||
|
|
8c3775f8d6 |
fix(shortcuts): Cmd+C no longer pastes on remapped Latin keyboard layouts (#29587)
* fix(shortcuts): Cmd+C no longer pastes on remapped Latin keyboard layouts On macOS the main-process paste intercept matched `code === 'KeyV'` OR `key === 'v'`, so on Turkish F (where the C letter sits on the physical KeyV) Cmd+C was routed to ui:appMenuPaste. Trust the produced key when it is a Latin letter or digit and only fall back to the physical code for non-Latin layouts (Cyrillic etc.), matching the shared keybinding matcher. Fixes #29574 Claude-Session: https://claude.ai/code/session_01TYbS9P1pnW4WRN7bGpuNaW * test(shortcuts): cover Dvorak, Korean and IME composition for the macOS paste intercept Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(shortcuts): match the macOS paste intercept by produced key token, in its own module The produced key token wins on every Latin layout, punctuation included (BEPO puts . on KeyV); non-Latin and IME keys fall back to the physical key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: İzzetEmre <emreizzet@hotmail.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1938bd055a |
refactor(ipc): group SSH and runtime-environment IPC handlers into ipc/ssh/ and ipc/runtime-environments/ (#30362)
git mv plus import, vi.mock and path-literal rewrites; e2e route regexes and the Windows host lane filter follow the moved handlers. |
||
|
|
64e544a442 |
feat(mobile): Resume in New CLI from a phone chat tab (#29783)
* feat(mobile): Resume in New CLI from a phone chat tab A chat tab on the phone had only Close in its long-press menu, so a chat that would not load left no way to reach its conversation. The phone now offers Resume in New CLI, like the desktop tab menu: the chat's host prepares the conversation and the phone opens a copy in a new terminal tab. The resume and fork startup builders move to src/shared so the desktop and the phone build the same command. * test(mobile): type the fake terminal create instead of casting its calls * fix(mobile): pass the host's fork source as is; match the quoted args in the test * test(mobile): give the fake host client the untyped mock the other client fakes use * style: format the shared resume builder and phone fork files * Fix mobile chat CLI fork creation on owning host * Fix mobile chat fork lint and split create owner helpers * Keep chat fork beside phone anchor across tab groups * Combine mobile terminal runtime type imports * Repair mobile create CI contracts after chat fork extraction * Classify chat fork preflight refusals and preserve mobile create recordings * Keep route-owned create cleanup quiet after unmount * Preserve typed runtime repair refusal in chat fork preflight * Preserve checked store through mobile chat fork preflight * Register mobile chat fork runtime fixture before test setup * Remove unused mobile create recording value * style(mobile): format the chat fork create files; move fork reply messages into the fork feedback module Formatting pushed the create hook past its line limit, so the fork-only reply message, partial-success warning and host capability check now live beside the other fork messages. Same order and copy as before. * Keep quick command mutation aligned with formatted request * Keep mobile chat CLI forks in terminal view * Pass mobile chat resolution its existing controller arguments * Match the mobile create fixture mock layout * Keep mobile chat CLI forks open when placement is unavailable * Brace placement contract fixture checks * test(mobile): keep the prompt controller mock under its line limit after the main merge * Expose pane admission in the runtime test fixture type * Complete the chat fork contract controller fixture |
||
|
|
c4c03e89bb |
Compact the Space window layout (#29857)
* Compact Space window layout * Make Space summary and workspace rows single line * Expose confirmed single-workspace delete in Space rows |
||
|
|
fe41a9a5e8 | fix(clipboard): paste CleanShot JPEG screenshots stored under the macOS PNG type (#29685) | ||
|
|
7f8faae800 |
feat(space): open disk usage in a separate window (#29513)
* feat(space): open disk usage in a separate window * perf(space): move local disk scan off Electron main thread * fix(space): satisfy full PR quality gates |
||
|
|
0fac887dbc |
feat(terminal): show a paired server's terminal in this desktop on terminal switch (#28023)
* fix(terminal): log a failed pane reveal instead of rethrowing it The bridge already replies false when the reveal throws, so the rethrow only surfaced as an uncaught renderer error after the caller had its answer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(terminal): refuse terminal.reveal from paired clients It moves this host's window. terminal.focus lets a paired client do that only by asking for host navigation, and the callers this method serves (the CLI and companion apps on the same machine) never arrive as paired clients. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat(terminal): show a paired server's terminal in this desktop on terminal switch A paired `orca serve` host has no window, so `orca terminal switch --environment <name>` resolved the handle there, answered `navigated: false`, and the desktop stayed put (#24688). Add `terminal.reveal`, a desktop runtime method that names a pane by worktree, tab and leaf, as the owning host knows them, and runs the Agent Dashboard's click-to-focus sequence before raising the window. When a paired server cannot navigate, `terminal switch` asks the desktop on this machine to reveal the terminal instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(e2e): terminal switch on a paired server moves the desktop on this machine Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test: update terminal manifest count after main and assert the revealed pane renders Main added a terminal method since this branch was cut, so the characterization count is 39 with terminal.reveal. The e2e now proves the target pane is visible in the DOM, not only selected in the store. Co-authored-by: Velm14 <Velm14@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> Co-authored-by: Velm14 <Velm14@users.noreply.github.com> |
||
|
|
6a2b789b35 |
fix(browser): defer capture cleanup until streamed tabs finish closing (#29365)
* fix(browser): defer publication cleanup until native destruction completes * test(browser): reproduce the serve tab-close SIGSEGV with a hidden page window Headless serve backs each page with a hidden top-level BrowserWindow, whose Aura view is what crashes; the <webview> guest the fixture used never reached that path, so the test passed against the original code. With the serve shape the test segfaults (fault 0x1268, RIP +0x55a8d7b) without the fix. * test(runtime): wait for the deferred publication restore after paired create and close --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
93095c2f67 |
fix(orcad): hand macOS opens from the browser sidecar to the desktop app (#29258)
* fix(orcad): hand macOS opens from the browser sidecar to the desktop app On macOS 27, LaunchServices registers orcad's Electron browser sidecar even when orcad was started over SSH, and sends the user's opens to the first registered instance of the bundle regardless of its activation policy. Opening Orca on such a host showed onboarding on the sidecar's throwaway profile instead of the user's own. orcad now marks the sidecar with ORCA_ORCAD_SIDECAR=1. The app treats the marker as a background launch and hands app opens, skill share links and opened documents to a new desktop instance with `open -n` and an empty environment, one launch per batch. An older installed app ignores the marker and keeps its current behavior. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(orcad): an automated sidecar never launches the real desktop app --------- Co-authored-by: jbamlee <dev.jbam@gmail.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
88bd5db42e |
fix(windows): copy a file to the clipboard without a visible PowerShell window (#28781)
* Run clipboard file copy and TLS openssl through process-host * refactor(clipboard): inject runProcess, pass the Windows path as data, add an e2e - clipboard-file-copy builds the process spec itself, so the stdio policy sits next to the wl-copy/xclip knowledge it exists for; the IPC handler passes process-host's runProcess straight through and keeps no wrapper. - A helper's exit is read as data (clean exit or not) instead of being turned into a throw and caught again. - Windows runs a constant Set-Clipboard script and reads the path from the environment, so no path is ever parsed as PowerShell. - Drop the dead tls-certificate rewrite; nothing in the app imports it. - e2e: right-click Copy on Windows, then a real shell paste of the file. * ci(e2e): run the Windows explorer file-copy spec on the Windows runner Route it from the clipboard sources, keep it out of the Linux lanes, and run it in the existing Windows e2e job beside the other Windows-only specs. * refactor(clipboard): tighten the helper seam, tests and e2e after review - runClipboardHelper takes runProcess and a plain ProcessSpec. - One handler test pins the process-host wiring on every host; spec details stay in the unit tests, which now also pin the inherited environment. - e2e: native realpath so an 8.3 runner temp path still matches, paste via process-host, and no toast check that could never fail. - Route the renderer caller to the e2e too. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
1a19671ec5 | test: remove low-value coverage and shorten expensive fixtures (#29135) | ||
|
|
635d3853f1 |
fix(agent.launch): avoid blank shells when launching new workspaces (#28451)
* fix: avoid blank shells beside new workspace chats * Carry promised agent chat through worktree creation * Use live agent launch inventory for new workspace surfaces * Type live agent launch owner from runtime contract * Assert one recovery shell after failed agent launch * Keep worktree activation adapters out of creation flow * Restore ordinary workspace setup handoff after launch correction * Cover the phone's agent.launch workspace create in the first-surface flow test |
||
|
|
08257b94aa |
test: avoid unused imports while preserving export contracts
Preserve test behavior and public export coverage while removing unnecessary module loading from orchestration and pure-function suites. |
||
|
|
8f83f6b076 |
feat(wsl-mover): pause a moving project's terminals through Sleep and prove they exited (D1b-5d) (#28820)
* feat(wsl-mover): pause a moving project's terminals through Sleep and prove they exited (D1b-5d) * fix(wsl-mover): gate again under the fence, swap journal phases atomically, keep sessions frozen through commit (D1b-5d) * test(wsl-mover): the fence e2e walks the journal one phase at a time, as a move writes it (D1b-5d) |
||
|
|
c8ed629217 |
fix(cloud-auth): one owner for the cloud session; never drop a rotated token, stop after a 401 (#28523)
* fix(cloud-auth): never drop a rotated refresh token, and stop after a 401 even when the store cannot be written A refresh whose session save failed (write error, or a moved mutation fence) discarded the rotated token, so the relay broker's ~10 s renewal kept replaying the spent one until the server revoked the family. The 401 then could not clear the session because the revocation write failed the same way, so the broker looped on 401s indefinitely. * fix(cloud-auth): one compare-and-swap save for rotated tokens; org selection keeps them - Rotated tokens save only while the store still holds the spent token and the identity is not signed out; linkage (and a server-reported org change) moves only under the refresh's own fence. - Org selection refreshes through the shared single-flight and merges onto the latest stored session instead of writing back the tokens it started with. - A failed session write removes the stale file so a restart cannot replay it. - A cleared session refuses conditional saves until the next sign-in. * refactor(cloud-auth): one read-merge-save helper for stored sessions; tokens before relink - updateStoredOrcaCloudSession re-reads, applies an update and saves under the fence in one turn; org selection, capability refresh and rotation all use it. - Rotation saves tokens first and relinks best effort, so a failed fence write cannot leave the spent token stored. - Only a rotation removes the on-disk file after a failed write; other saves leave a still-valid token in place. - Capability refresh fences at request time, after any inner refresh relink. * fix(cloud-auth): roll the fence back when relinking after a refresh fails A fence left on the server-reported org while the profile link kept the old one made every later capability refresh return reconnect-required. * refactor(cloud-auth): one owner for each profile's cloud session profile-cloud-session-owner.ts is now the only code that reads or writes a profile's session. Renewal, org selection, capability refresh, sign-in and sign-out ask it for changes; it keeps an in-memory generation (sign-in/out) and link version (org changes) instead of the on-disk mutation fence. Deletes profile-cloud-session-mutation.ts (account-session-mutation.json), profile-cloud-session-rotation-save.ts, updateStoredOrcaCloudSession and the profile-switch fence write. * fix(cloud-auth): retry an unsaved rotation until it lands; sign-in save failures throw as before - The store keeps a rotation whose write failed and retries it on every read and at quit, so one failed write no longer signs the user out on the next restart. - Only a rotation keeps its session after a failed write; sign-in, org selection and capability writes throw as on main. - Org selection succeeds when the link already moved to the selected org. - The owner reads the profile's link itself and bumps its link version only on an org change; commitCloudSessionRelink returns the profile list. - Adds the auth characterization suite that pins main's behaviour. * fix(cloud-auth): a failed non-rotating save keeps a pending unsaved rotation Found in the real-app run: a capability save that failed while a rotation was unsaved dropped the pending rotation, so the next renewal resent the spent token. * fix(cloud-auth): flush unsaved rotations on OS shutdown; stale capability replies match main - Flush unsaved rotations on Windows session-end and powerMonitor shutdown, through the owner, which now also owns the quit flush. - Capability replies are refused once the org link moved at all, as on main; only org selection accepts a link that already reached the selected org. - Sign-in and sign-out reuse invalidateCloudSessionChanges; small store cleanups. - Pins the A->B->A stale capability reply in the characterization suite. * fix(cloud-auth): flush unsaved sessions before recording the Windows session end |
||
|
|
b428def8f1 |
feat(cli): open files at a requested line and column (#28240)
Accept positive safe-integer line and column flags through the existing file-open path. Reject invalid or incomplete positions before changing the editor. Bind deferred reveals to the requested workspace and tab, including floating panels, and clamp oversized positions without moving another editor or stealing late focus. Co-authored-by: Nikita Maketryuk <n.maketryuk@lince.studio> |
||
|
|
cfbf3610d5 |
fix(linux): keep Wayland clipboard text current (#27772)
Publish Linux Wayland text copies with an independent clipboard owner, then clear Chromium's stale source only after publication succeeds. Use data control where available and GTK's clipboard bridge on GNOME. Keep macOS, Windows and X11 on their existing Electron writers. Temporary workaround for https://github.com/electron/electron/issues/54645, with removal checks documented in the native README. Verified full build, macOS arm64 packaging, Linux x64/arm64 library floors, clipboard/platform tests, real Wayland copy/paste and CI packaging. Fixes #26917 Fixes #23229 |
||
|
|
e3ef95dc04 | feat(cli): launch agents in existing workspaces (#27794) | ||
|
|
aeffeb4cfb |
refactor(pty): no ipc/pty.ts barrel (#27798)
* refactor(pty): no ipc/pty.ts barrel * test(orcad): clarify preflight-order comment --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
ae0ff63152 |
fix(serve): initialize the updater in headless Electron serve so paired clients can update it (#27771)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
c5224f70d8 |
refactor(providers): delete the SSH git and filesystem providers and relay-only wiring (#27644)
* refactor(providers): delete the SSH git and filesystem providers and relay-only wiring * refactor(ssh): drop relay-protocol exports only the deleted stream readers used --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
0024735e82 |
fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling (#27409)
* fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling * refactor(ssh): drop snapshot apply tokens and terminal finalization only the sync used * test: drop leftover sync-status mock --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
0081ea0941 |
fix(clipboard): paste a macOS image copied beside its file URL (#27346)
* fix(clipboard): paste a macOS image copied beside its file URL When a screenshot tool saves a file and also copies the image, the pasteboard holds public.png and public.file-url, and Chromium reports only text/uri-list from availableFormats(). readClipboardImageSource() then saw no image and Cmd+V dropped the paste, although readImage() returns it. On macOS, also check public.png and public.tiff with clipboard.has() before giving up. Fixes #27345 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzCzr11hHmaEhiUoGJCrDy * test(clipboard): cover macOS file URL image paste safety --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
e4f599df05 |
perf(worktrees): preserve Git scans when saving sidebar order (#27343)
* perf(worktrees): preserve Git scans when saving sort order Credit @mmarabel for identifying the repeated scans and the host-only approach in #27271. Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> * perf(worktrees): skip Git scan invalidation for order saves on desktop hosts too The desktop window notifier ran the worktree-change invalidators unconditionally, so a desktop (or paired-host) Orca still cleared Git scan caches on every sidebar order save. Carry the invalidateGitScans flag through the notifier; every other caller keeps the default. --------- Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com> |
||
|
|
1912c90141 |
Extract subprocess handling into @orca/process-host (#26963)
Extract subprocess execution and process contracts into @orca/process-host with explicit public entry points, independent checks, and one Node 18 compatible CommonJS runtime. Migrate application consumers and integrate package builds, dependency selection, import enforcement, and runtime resource staging. Preserve daemon startup ownership and profile isolation, and qualify development rebuild recovery and concurrent output publication. Remove unnecessary aliases, test forwarding exports, and a duplicate output scan. Validation: full typechecks, changed-code quality, package and artifact checks, native Windows builds/types plus 306 runtime tests, and independent native reviews. Matched full E2E controls on original main and unchanged reviewed head both pass all 17 applicable tests (nine skips). Earlier intermittent E2E failures are documented in the PR; no assertions were weakened. The final combined-tree audit preserves both branches with no import-boundary violations. |
||
|
|
7b1242ba94 |
[CI] Reduce repeated test work and use pinned Bun for localization extraction (#26806)
* [CI] Reduce repeated test setup and retire source inventories * [CI] Give consolidated floating panel suites distinct titles |
||
|
|
d4acd11ae5 |
fix(window): stop a covered window drawing after a phone stream, and keep a streamed foreground tab live (#26657)
* fix(window): re-hide a covered window when the renderer-throttle lease is released Restoring background throttling only clears Electron's disable_hidden flag; the occlusion or minimize hide swallowed while the lease was held is never replayed, so a covered window kept compositing at ~60 rAF/s until the next cover cycle. After the last release, a discarded empty-rect stayHidden capturePage makes Chromium's capturer-count decrement re-apply the current visibility, so the pending hide lands without the page ever reporting visible. Skipped for a focused renderer, best-effort and never awaited. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(window): let a broken re-hide capture surface instead of swallowing it Drops the synchronous try/catch around the stayHidden capture so a missing capturePage fails loudly; only the promise rejection is swallowed. Test doubles that reach a final lease release now provide capturePage. Keeps the focused-window skip (a covered window loses focus) and the empty rect. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(browser): keep a streamed active tab painting, and re-hide it when the stream ends When the streamed tab is the foreground desktop tab, the guest's own widget is what a cover hides, and lifting the window throttle does not reach it, so the phone froze after one or two frames. The page stream now re-applies the guest's setBackgroundThrottling(false) at start and on the window's hide/minimize while live. At stream end it drops those listeners, re-throttles the guest and replays its swallowed hide with the same stayHidden capture the window lease uses (now exported as replaySwallowedHide), so the guest stops painting. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(browser): hold the streamed guest through the renderer-throttle lease The page stream now takes a rendererPublicationThrottle lease on the guest instead of a bespoke guest-paint helper. The start unthrottle sets the guest widget's disable_hidden flag (copied onto new widgets), so the window hide/minimize re-apply listeners were no-ops and are gone. The guest gets the lease's end-of-stream re-throttle and stayHidden re-hide, including the focus skip, which holds for a guest because its isFocused requires the embedder's window to be key and its root view focused. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(runtime): give the mobile-session window doubles a capturePage Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
cc291dba1d |
Keep picked skills and pending uploads when native chat composers close (#26538)
* Preserve native chat skills and uploads when composers close * Own menu image pastes before saving begins * Keep paste ownership independent of image presentation * Show pending pastes so stalled saves can be removed * Register confirmed image pastes and preserve preview ownership * Keep clipboard image presence and saving on one source selector * Describe clipboard image formats in upload routing fixtures |
||
|
|
e3639ef8d6 |
Phase 3.5: fix the top SSH/remote issues at the root cause (#26483)
* fix(remote-runtime): stop arm64 Linux servers dropping clients every heartbeat (empty WebSocket write EFAULT) (#26200) * fix(remote-runtime): never write an empty WebSocket payload on its own (arm64 Electron EFAULT) Patch ws@8.22.0 Sender.sendFrame so a frame with an empty payload is written as its header alone. Under Electron on arm64 kernels with 39-bit virtual addresses, socket.write() of a zero-length buffer fails with EFAULT and kills the connection, so every empty heartbeat ping, auto-pong and close dropped the remote runtime with 1006. Wire bytes are unchanged. * test(remote-runtime): run the ws empty-payload guard on real Node Bun resolves 'ws' to its built-in client, so under the Bun test project the guard exercised Bun's implementation instead of the patched package. --------- Co-authored-by: m4air <m4air@Mac.localdomain> * fix(orcad): ship the session scanner child so agent session history lists on orcad hosts (#26309) orcad built every forked child except session-scanner-service-entry.js, so aiVault.listSessions always failed on an orcad host. The resolver also skipped the adjacent entry whenever isPackaged was true, so shipping the file alone would not have been enough. - build every ORCAD_CHILD_ENTRY_POINTS entry from one loop and add the scanner - add it to ORCAD_ARTIFACTS so a deploy missing it fails integrity checks - share one forked-child path resolver (asar layout vs flat app root) between the watcher and the scanner - smoke-fork the built scanner during build-orcad under both runtimes Fixes #24105 Co-authored-by: m4air <m4air@Mac.localdomain> * fix(remote-runtime): one owner for a remote pane's binding and recovery (#26308) A remote pane no longer latches in connecting on terminal_not_found, holds keystrokes typed during same-terminal recovery instead of dropping them, reports transport-owned retries so the pane is not remounted mid-retry, and no longer infers absence from an inventory that predates the leaf. Co-authored-by: m4air <m4air@Mac.localdomain> * ci(e2e): move apt off the Azure mirror on runner images that use a mirror list (#26335) Ubuntu 24.04 runner images resolve apt sources through /etc/apt/apt-mirrors.txt, so rewriting only the source lists left package downloads on azure.archive.ubuntu.com, which intermittently fails ('Ign:' on every package). Rewrite the mirror list too, and retry the install once with --fix-missing. Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(terminal): remote snapshot replay keeps scrollback it cannot replace (#26319) Desktop subscribers of a remote terminal received screen-only snapshots, and every pushed snapshot replay cleared the pane's scrollback with ESC[3J. A reconnect, a remount, or a recovery after a dropped frame therefore wiped history the host never resent. - Host: desktop subscribe snapshots use the same scrollback-row ladder as mobile, and SnapshotStart now carries an optional scrollbackRows field. - Client: a pushed snapshot clears scrollback only when the host says it carries history; screen-only images (scrollbackRows 0, or older hosts that omit the field) repaint the screen and keep the pane's history. Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(daemon): clean launch env, validate client frames, fit the socket path (#26306) * fix(daemon): clean launch env, validate client frames, fit the socket path - Drop Chromium's DBUS_SESSION_BUS_ADDRESS=disabled: marker from the daemon, its systemd-run scope launch and every PTY env (#25580, #21119). - Use the durable user-manager scope only when that manager outlives the caller: lingering on, or the caller already runs inside it (#24201). - Shape-check hello and request frames, and catch request rejections, so a malformed frame closes one connection instead of the daemon (#17841). - Move the daemon socket to a short per-uid private dir when the data root would overflow sun_path (#17840). * fix(daemon): validate a relocated socket dir before any connect, not just before bind --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(ssh): keep crediting a PTY after its recovery delivery is canceled (#26341) A canceled recovery delivery released its source spans while the renderer and model could still settle them; the stale projection then threw on every later ACK, so the next delivery was never credited and the relay paused the PTY for good. Settling or transferring a span of a closed delivery is now a no-op. Co-authored-by: m4air <m4air@Mac.localdomain> * fix(rpc): deny-by-default caller permissions and a scoped SSH bridge credential (#26337) * fix(rpc): deny-by-default caller permissions and a scoped SSH bridge credential Every RPC method now declares a required permission, and RpcDispatcher checks it against the caller's scope on every transport (owner, mobile, paired runtime, SSH bridge). The SSH relay bridge no longer spawns the client CLI with owner authority: each invocation gets a short-lived credential bound to its SSH target, revoked when the child exits. Without the new per-host opt-in, that credential reaches only its own host's terminals; with it, workspace methods but never desktop control, accounts, settings writes, skill install/share, pairing or host admin. Paired runtime clients need a pairing-time grant (orca serve --grant-desktop-control) for computer.* actions. Fixes #23835 Fixes #25259 Refs #18269 * fix(rpc): let a bridged SSH worker report to its own coordinator; allowlist paired-runtime permissions Without the per-host opt-in, orchestration send/ask/reply/check/inbox now pass when the caller is a terminal on the bridged host, binding pane key, payload Dispatch, run and recipient to that caller's own orchestration (recipient off-host only as the creator of its active Dispatch). Paired runtime clients get an explicit standard permission set; pairing-admin and desktop-control are grant-only. * fix(rpc): resolve a bridged SSH caller's own Run and Dispatch mailboxes before admission Mail routing stores a worker's follow-ups under dispatch:<id> and worker questions under run:<id>, so comparing the original recipient to the caller's terminal handle refused an SSH worker replying to its coordinator, an SSH coordinator answering its own Run's question, and explicit sends to the caller's own run:/dispatch: address. The bridge now resolves the exact mailboxes the caller's live pane and process read (as check does) and admits replies and recipients against those. * refactor(rpc): parse SSH bridge selectors with a typed schema instead of Reflect.get --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(projects): remove a project only on the host the user picked (#26340) * fix(projects): remove a project only on the host the user picked removeProject now requires a hostId and matches that host's row exactly. The bare-id fallbacks (unique candidate, focused host) could resolve to another host's live project and delete it. Orphaned SSH purge and group deletion now pass each row's own host. Fixes #13071 * fix(projects): group delete removes only the group's member rows Same-id rows on other hosts outside the group were removed because membership was rebuilt from bare ids after the cascade cleared it. Capture (id, host) member pairs before deletion and remove only those. * fix(projects): never let a stale catalog widen a project removal to every host The renderer chose id-only `repos:remove` when its own catalog showed no same-id row on another host. A stale catalog (another window, a paired client, or a pending refresh) hid that row, and main then deleted every host's registration and saved workspace metadata. - The local catalog route now always calls `repos:removeForHost` with the owner row's host, for local, direct SSH, git and folder projects. - `Store.removeProject` refuses an id that rows on more than one host share, so no id-only caller can delete another host's row. - Setup rollbacks remove the just-created row by its host. --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(serve): load SSH targets on a headless serve; a user Quit only closes serve-hosted windows (#26348) * fix(serve): load SSH targets on a headless serve; a user Quit only closes serve-hosted windows - launchServeMode registers the SSH handler layer before RPC binds, so a windowless 'orca serve' lists and connects the profile's SSH targets (#25886, #8489). - Under serve, the app-menu Quit/Exit, tray Quit and renderer-recovery Quit close the desktop windows instead of quitting the process (#15537). Signals, supervisor stops, relaunches and update installs still quit normally; on macOS a signal reaches before-quit the same way Cmd-Q does, so the guard lives on the user commands, not in before-quit. * fix(serve): SSH profiles serve on Electron; native macOS quits and frozen windows no longer trap or stop serve - orca serve: a profile with saved SSH targets stays on the Electron host by default, since orcad has no SSH stack yet (#25886, #8489). ORCA_SERVE_RUNTIME=orcad still forces orcad. - macOS serve: Dock > Quit, app-switcher Cmd-Q and other native terminates only close the desktop windows (#15537). Signals, supervisor stops, relaunches, update installs and logout/shutdown still quit. - The serve signal listeners now reclaim SIGINT/SIGTERM/SIGHUP from Electron's native handler. Any signal that had a Node listener before app ready was left with Electron's handler, so these listeners never ran. - A serve user Quit closing a frozen renderer's window arms the same 10s ack deadline as a desktop quit, and skips minimize-to-tray. * fix(serve): stay on Electron when the profile's SSH targets cannot be read yet A valid older-schema profile is rejected by the read-only probe, and the selector took that as "no SSH", so a default serve before the first migration started orcad with an empty SSH registry. Fail closed instead. * refactor(window): type user-Quit close marks as a named window slice, not object * test(serve): pass the desktop-control grant default in the headless SSH launch fixture * test(serve): run the serve-selection SQLite fixtures in the Node runtime project --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(terminal): keep the main-process terminal tail linear under TUI redraw floods (#26316) * fix(terminal): keep the main-process terminal tail linear under TUI redraw floods Full-width in-place row rewrites rebuilt the row string per character (O(width^2)), a long chunk past the line cap spliced the row array once per newline (O(lines x cap)), and multi-frame chunks summed every cursor-up so they fell back to the whole-tail path. Rows now hold mutable cells joined once, capped rows are dropped in batches, and the redraw window uses the net cursor reach. Fixes #11315 * fix(terminal): do not rebuild a redraw row the cursor only revisits Rows stayed cell arrays once touched and re-joined on every newline, so a cursor revisiting one wide row thousands of times cost O(width) per newline. Runs now splice into the row string, a row switches to cells only after 32 edits between snapshots, and cells are joined once and dropped. * fix(terminal): keep one cached row per chunk so unchanged redraw revisits cost O(1) A row now keeps its cells, right-trimmed snapshot and content end across newlines and only invalidates them on a real content change; identical writes leave it clean. Adds a work-accounting matrix over restore, the append path and live onPtyData, plus a per-character row oracle fuzz. * fix(terminal): return redraw rows to flat strings with cached snapshots Cells made every changed snapshot a full-width join, which was far slower than the old flat-string splice. Rows are flat strings again, written one run at a time; each keeps a cached right-trimmed snapshot and content end that only a real change invalidates, so identical rewrites and cursor-only revisits stay O(1). A wall-time test now times every review probe against a vendored copy of the old row model on restore, append and onPtyData. * test(terminal): read runtime leaf and PTY tail through typed members --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(ssh): relay fallback runs Orca's pinned Node by default, never a host npm install (#26303) * fix(ssh): relay fallback runs the pinned Node ladder by default, never a host npm install A host whose connect falls back to the relay route now gets the A/B/C ladder (Orca's uploaded Node and prebuilt addons) unless it opts into Host Node. A refused rung C steps to rung D with an actionable reason instead of compiling native deps on the host. * fix(ssh): mark the opt-in Host Node relay runtime as unsupported on the SSH host card The relay fallback now defaults to Orca's pinned Node ladder; the host-npm path is reachable only by an explicit per-host Runtime or ORCA_SSH_REMOTE_RUNTIME override. When a relay actually launches on it, main publishes hostNodeRuntime on the connection state (optional, admitted field-by-field for paired clients) and the SSH host card shows an unsupported-configuration warning. * fix(ssh): keep the relay runtime ladder honest about what it cannot verify - NixOS's stub loader refusing Orca's generic Linux Node (exit 127, 'Could not start dynamically linked executable') is classified wrong_libc, so the ladder steps on to the host Node instead of failing the connect at rung A. - In plain SSH mode the shell provider answers absent only for shells it saw exit; a PTY id it never minted (one an earlier relay may still run) is unverifiable, never exited. - The rung D message offers Host Node only when the host's own Node refused the prebuilt addons, and calls it an unsupported configuration; a download, unresolved-host or Windows refusal gets no Host Node advice. * fix(ssh): keep the host-Node relay where the ladder can't judge the host A client without Orca's runtime artifacts (a dev, e2e or source build with no orcad template, or an offline client) refused rungs A and C with artifacts_unavailable, and the connect settled on rung D. That reason describes the client, so it now lands on the host-Node relay it had before the ladder, marked unsupported, and persists no host decision. A proved noexec still lands on D. Windows hosts have no rung B or C yet, so any rung A refusal there keeps the host-Node relay too. The rung D message is worded from the host OS: Windows gets no noexec-mount wording and is offered Host Node. * fix(ssh): a host-Node fallback with no Node settles rung D, and mutable refusals expire The host-Node relay the ladder chooses in place of D (a client artifact gap, or a Windows rung A refusal) hard-failed the connect when the host had no Node: the resolver threw a plain Error, which plain SSH mode does not admit. The fallback now proves host Node with a strict probe first; an answered "no Node" settles D with a classified, host-OS worded reason, and an unanswered probe stays a retryable failure. A gap no longer rewrites D to host Node once rung C proved host_node_missing. Mutable pinned-runtime refusals (missing_lib, security_software, and noexec on Windows) are no longer persisted and replay from memory for ten minutes only, so a repaired host gets rung A back. The in-memory refusal cache moves to ssh-relay-pinned-refusal-cache.ts. * fix(ssh): no host does worse than the pre-ladder host-Node default Restructure the relay runtime ladder around one invariant: A, B and C are tried first, and any refusal past them falls back to exactly the pre-ladder default, the host-Node relay marked unsupported. That covers an unidentified libc, a client artifact gap, Windows, mutable refusals and a C N-API floor the npm build would not have hit. Rung D is reached only when that fallback proves the host has no usable Node, or a proved noexec home would defeat it too. Strict host-Node resolution now treats only a command that ran and exited as an answer, so a refused channel (MaxSessions), session limit or timeout stays a retryable failure instead of a 'no Node' verdict. The launch reuses the Node the fallback probe found. Mutable refusals (missing_lib, security_software, Windows noexec) persist again with their proof time and replay for 24 hours, so a host that still blocks Orca's runtime is not re-uploaded to on every connect, while a repaired one gets rung A back. A table-driven test asserts the invariant per host class. * fix(ssh): every answered rung failure reaches the host-Node default, and D only past it Two shortcuts skipped the pre-ladder default. An answered but unclassified install failure (tar ENOSPC, an extract or hash marker, an unverified Windows promote, a self-test that failed outside every refusal class) aborted the connect on every attempt; it now steps down as a non-persisted install_failed. Only unanswered failures (abort, timeout, channel-open, session limit, an unconfirmed termination, SFTP connection loss, ssh exit 255) stay retryable, and nothing steps down once a relay launch was issued. A proved noexec no longer jumps to D: exec denial can be per binary (AppArmor allowing /usr/bin/node), so it skips B and C, which load from the same tree, and reaches the host-Node fallback. D now follows only that fallback answering with a failure, so its outcome is a superset of the default's by construction. The never-worse test is now the cross-product of every A, B, C and host-Node answer on Linux and Windows, plus named review cases. * fix(ssh): system-ssh upload failures and a broken cached runtime step down before launch System-ssh directory upload and system sftp threw plain Errors that dropped the host's exit status, so a remote tar ENOSPC or a Windows sftp permission refusal aborted the connect instead of stepping down. The remote side now throws SystemSshCommandExitError (ssh exit 255, signals and lost-connection diagnostics stay retryable), and a local tar failure is still blamed on the client rather than the remote tar that saw its truncated stream. The warm runtime check only tested that the verified executable existed, so a cached runtime that the host's exec policy or a removed library had since broken was launched and hung on every reconnect. The same command now also runs it and reports a refusal the way promotion does, so it steps down before any launch, with no extra round trip. * fix(ssh): keep the remote answer when local tar only failed writing to it When the host's tar exits early (ENOSPC, a refusal), BSD tar on macOS exits 1 with 'Write error' and no signal, so the upload rethrew the local error and dropped the typed remote exit, aborting the connect. The remote answer now wins whenever local tar finished, was signalled, failed after ssh closed, or reported a write or pipe error; a local tar that failed on its own input first is still the client's fault. * test(ssh): hostile-host cells allow read-only version probes, never installs; probe each Node once The noexec-home cell failed on 'npm --version': under the never-worse ladder a proved noexec reaches the strict host-Node fallback, whose probe asks npm its version before settling D. The cells now judge install and compile calls (npm install/ci, node-gyp, cc, make) and allow '<tool> --version' / '-v'. The probe ran twice because the login shell named the Node the path probe had already rejected. Candidate checks are now memoized per resolution, for the host-Node fallback and rung C alike. --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * chore(i18n): regenerate the runtime-required English catalog after the main merge * fix(serve): serve on the profile the CLI selected, and refuse any other before the instance lock (#26424) Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(remote-runtime): open terminal tabs reattach after a managed server update re-pairs the host (#26433) * fix(remote-runtime): a remote pane rebinds after a server update re-pairs its host A pane captured the host's pairing revision once, when it mounted. A forced managed-server update re-pairs the host under the same environment id, so every later request from that pane was refused as stale, and the refusal counted as fatal: the pane stopped retrying and Reconnect replayed the same stale revision. Each request now carries the current revision, a pairing refusal re-reads the catalog before the retry, and the pane treats it like lost contact. * fix(remote-runtime): close a stream left on the old pairing and ignore a 0x0 grid hold A stream that reopened before main re-paired stayed open while sendFrame refused its stale revision, so typed keys vanished. A revision change now closes it as recoverable so the pane rebinds on the new pairing. A just-restarted server also published a 0x0 hold that shrank the visible pane to one cell; holds with no grid are ignored. * fix(remote-runtime): a pane follows only its own machine's re-pair A re-pair under the same environment id to a different host key published its revision before the catalog retired the old tabs, and the pane recovered at the new revision, so its next call was authenticated as the replacement peer. The catalog now classifies each re-pair before publishing revisions: a replaced or removed machine's terminal streams end for good first, and a pane only advances its pairing along a proven same-host rotation (same registration and host key). An unresolved identity holds the pane until it is proven. --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * refactor(rpc-permissions): Phase 3.5 elegance pass (#26471) * refactor(rpc-permissions): drop never-read incarnation and invocation id from bridge credentials * refactor(rpc-permissions): define runtime device grants once and reuse bridge scope fixtures * refactor(rpc-permissions): decide terminal host membership in one predicate and share the opt-in hint * refactor(rpc-permissions): parse bridge selectors once per call * refactor(rpc-permissions): admit and bind unopted bridge methods from one table --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * refactor(remote-pane-binding): Phase 3.5 elegance pass (#26470) * refactor(remote-pane-binding): keep the pane's pairing revision in one slot * refactor(remote-pane-binding): share one hold decision across the input entry points * refactor(remote-pane-binding): define the persisted-handle adopt once in attachPane * refactor(remote-pane-binding): build the pushed-snapshot meta once * refactor(remote-pane-binding): drop the test-only peerReplacedEnvironmentIds wrapper * test(remote-pane-binding): share the input and subscribe frame readers via stream fixtures --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * refactor(relay-ladder): Phase 3.5 elegance pass (#26474) * refactor(relay-ladder): put the host-Node fallback in the ladder before D The Auto ladder now lists legacy as the rung before D, so the step function is one rule plus the next rung. Deletes nextRelayRuntimeStep, the hostOs step context and the mutable hostNodeFallback flag (now a constructor-time laddered field). Windows still ends at legacy: B and C refuse there before any host I/O. * refactor(relay-ladder): one rung-D reason and message over reachable states D is reached only from the host-Node fallback, so its refusal is host_node_missing or install_failed. Merges the reason and message functions, drops three message branches that could not run, and folds noexecRemembered/noexecProved into one noexec field. * test(relay-ladder): one never-worse cross-product with a remembered axis Drops the named cases (each matches a cross-product outcome), the transport case that only checked the model against itself, and the third case the rank check already implies. Adds rung B's noexec answer and per-rung remembered flags for A and B, the only rungs that replay a cached refusal. * refactor(ssh): one answered-miss rethrow for the node resolution probes Four catch blocks repeated the same session-limit, abort and strict checks. They now call rethrowUnlessAnsweredMiss and keep their own fallthrough. Also drops a stale 'Caches nothing' comment. * refactor(ssh): share the host-answered system-ssh exit rule; flatten the upload throw cascade isHostAnsweredSystemSshExit owns the 255/null transport rule for both the upload pipeline and isAnsweredHostFailure. settleUploadPipeline drops a branch that could not run: a failed tar is judged once, then ssh, then pipe. --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * refactor(serve-daemon-misc): Phase 3.5 elegance pass (#26478) * refactor(ssh-pty): build obligation transitions behind one closed-delivery guard * refactor(build-orcad): table-drive the shipped worker smokes * refactor(daemon): pass scope probe seams as one deps object and share cgroup line parsing * refactor(repos): route every project removal through removeProjectForHost * refactor(build-orcad): type the worker smoke table for the type-aware gate --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: m4air <m4air@Mac.localdomain> * refactor(terminal-tail): Phase 3.5 elegance pass (#26465) * refactor(terminal-tail): share mulberry32 and compare whole results in redraw equivalence tests * refactor(terminal-tail): keep one splice ceiling at the onPtyData entry point --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> * fix(terminal): bound the backing a redraw row can pin across chained erases (#26547) Each erase that keeps at least half the row skipped the copy, so a chain of them left nested slices pinning the original wide row. Rows now track an upper bound on the string they slice and copy once the cumulative shrink passes half. Co-authored-by: m4air <m4air@Mac.localdomain> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
e4cd14d914 |
Remove legacy OS file-drop routing (STA-6940 PR6/6) (#26385)
* feat(file-drop): add element owner preparation plumbing * Fix terminal and chat file drop destination ownership * fix runtime terminal drop ownership and queued chat retries * fix(file-drop): preserve feedback acceptance and destination ordering * fix(file-drop): attach chat and composer files at the drop surface * fix(file-drop): preserve project destinations and live chat availability * fix(file-drop): keep path resolution in filesystem namespace * test(file-drop): use filesystem path bridge in PR3 fixtures * fix(file-drop): bind terminal drops to pane elements * fix(file-drop): compare terminal pane identity across public views * test(file-drop): align composer lifecycle with element-owned drops * Fix interrupted chat composition and refuse unused quick-create drops * Type the quick-create drop regression transfer * fix(file-drop): let the explorer, project sidebar, tab strip and editor own OS drops STA-6940 PR5. The file explorer tree, the project sidebar, each editor group's tab strip and its editor area now take OS file drops on their own element through the shared owner hook, with identity from their own render (the explorer's shown workspace and target row, the tab strip's and editor group's worktree and group) instead of the active worktree. The explorer and sidebar broadcast subscribers and their legacy drop markers are deleted; editor-open moves out of useGlobalFileDrop into editor-dropped-file-open, which the legacy unmarked-chrome route still uses until PR6. * fix(file-drop): keep editor delivery tied to its destination group * fix(file-drop): guard delayed opens and share floating ownership * fix(file-drop): remove legacy OS drop routing (STA-6940 PR6/6) * test(file-drop): finish owner test and comment cleanup * Clarify dropped-file default editor destination * Retain UI test namespace for terminal resume coverage |
||
|
|
c785c986c2 |
feat(native-chat): teach chat agents to show inline visuals in their own folder (#26099)
* feat(native-chat): visual directive grammar and host read for a chat's visuals folder
A shared grammar for the ::orca-visual{file="..." title="..."} reply line,
the per-chat visuals folder location on the owning host, and the
agentSession.readVisual runtime method that reads one visual with lexical and
canonical containment, a 512 KiB bounded read and UTF-8 refusal.
* feat(native-chat): render chat visuals inline and in the right sidebar
Native-chat assistant replies render a ::orca-visual{...} line as the chat's
HTML visual in an opaque, scripts-only sandboxed frame: CSP first, the host
frame navigation guard registered before content runs, live theme without a
reload, fitted height, links opened in the viewer's browser only from a real
gesture, lazy mount, and one muted line when the visual cannot be shown.
Open in sidebar shows the same frame in the right sidebar, widened while it
is open and restored after.
* feat(native-chat): teach chat agents to show inline visuals in their own folder
Native-chat Claude and Codex sessions now get a per-chat visuals folder on
the host that runs them, write access to exactly that folder, its path in
ORCA_CHAT_VISUALS_DIR, and an Orca skill that teaches the ::orca-visual line.
- Skill ships as an unpacked plugin folder in desktop and headless builds.
- Claude: --plugin-dir via SDK plugins, behind the CLI version probe (now one
shared probe for every version-gated flag); folder added to
additionalDirectories beside the user's own.
- Codex: skills/extraRoots/set and the folder appended to the user's own
writable roots, between initialize and the thread open, under a 2 s budget;
unsupported, failed or hung setup opens the chat without visuals.
- A host sweep removes folders no chat record maps to, and folders whose
local workspace is provably removed; anything unproven is kept.
* test(native-chat): the visuals skill names only theme variables the frame sets
* fix(native-chat): visual CI fixes, shared height governor, live-turn streaming hold
Registers agentSession.readVisual from the methods index so the structured
method file stays under its line budget, replaces reflective reads with checked
narrowing, moves the pure height governor to src/shared for mobile, and holds a
half-written directive tail while the turn works (structured text rows carry no
running state).
* fix(native-chat): harden the visual read and link opening
Re-checks after the open that the chat's visuals folder is still the real
directory at Orca's path, reports unexpected filesystem faults by code without
host paths, and lets one click in a visual open at most one page.
* fix(native-chat): keep visual lines out of plain-text reply surfaces; review fixes
One shared helper drops visual lines (outside fenced code) from reply text where
it becomes plain text: the structured status summary that feeds the sidebar row,
dashboard, notifications, phone rows and handoffs, and AI Vault reply previews.
Review fixes: height also counts a pinned body's overflow, only the live
frontier row holds a half-written visual line, the runaway-height stop needs the
same step repeated, and any host refusal evicts the cached revision.
* fix(native-chat): review round 1 for chat visuals delivery and cleanup
- Visuals sweep: a workspace counts as removed only when no profile's
catalog holds it (chats and visuals are shared by every profile, catalogs
are not); a worktree in a known project is removed only when git no
longer records it; any unreadable profile decides nothing; one catalog
snapshot per run; a symlinked visuals root is never walked.
- The other-profile catalog reader moves out of window/ and also returns
project ids.
- A folder Orca itself inherited is stripped at the spawn layer for both
agents, not only from the launch overlay.
- Claude version probe: a probe that gave no version is never remembered;
the plugin check waits up to the probe's kill time so a slow first probe
no longer costs a chat its skill.
- Skill: kept out of Claude's / menu, filename and theme guidance matched to
the renderer, refused writes are not retried.
- Opt-in real Codex test for skill discovery and the writable root.
* feat(native-chat): ask the Claude CLI its version when native chat starts
The first chat after Orca starts usually finds the version known, so the
plugin and thinking-display checks answer at once instead of probing a
cold binary while the chat waits.
* fix(native-chat): resolve the visuals folder without the removed journal-paths helper
Main removed the per-chat journal paths and the journal database's state
directory; the visuals folder keeps the same sha256 layout on its own and the
read method uses the profile state directory the chat host is opened in.
* fix(native-chat): review round 2 fixes; copy a reply without its visual lines
Reply previews in Agent Session History drop visual lines per text part before
lines are folded; the frame adds a body's overflow only when the body really
overflows; fence tracking follows CommonMark closers and openers; the copy
button copies a reply without visual lines; a coded read fault keeps its cause.
* fix(native-chat): review round 2 for chat visuals delivery and cleanup
- Read git worktree records written relative to their own folder (git 2.48+),
so a worktree on an unmounted drive in such a repo is still kept.
- Skip the running profile by its own storage folder, not the profile index a
switch rewrites first; a profile never written to counts as empty, so the
workspace rule is not switched off by a profile that was never opened.
- Ask for readable thinking again once the plugin check has waited for the
version, so a slow first probe no longer drops it for the chat's life.
- Launch flag decisions move to their own module; tests use a typed record
fixture instead of casts.
* fix(native-chat): review round 3 for the visuals sweep and version check
- Resolve a relative git worktree record against its folder's real path, so a
project added through a link still matches and its worktree is kept.
- A profile with only backups of its data file is a lost file, not a fresh
profile: it still stops the workspace rule.
- The readable-thinking re-check reads what is known and never starts a
second version probe.
* fix(native-chat): update the frame's theme ref after render; read the visuals folder pair at once
* test(native-chat): declare agentSession.readVisual on the cross-version agent-session surface
* fix(native-chat): copying a reply keeps its code blocks and indentation
Removing visual lines now closes only the gap each removal leaves, instead of
collapsing blank lines across the whole reply and trimming its indentation; the
visuals folder is checked parent first again so a broken path answers the same
way every time.
|
||
|
|
995ef11ce7 |
feat(native-chat): say in the chat why Orca stopped a reply, and offer Continue (#25675)
* feat(native-chat): say in the chat why Orca stopped a reply, and offer Continue When the Orca that runs a structured chat (this computer or a paired server) quits, updates or crashes mid-reply, the chat's stopped row now names the cause and the machine, and a Continue button sends the existing restart continuation for that cut turn, with or without a restart offer. - Host: a quit/update writes one turn-scoped row for the turn its stop cut, in today's words, with an optional `orcaStop` cause on the providerExited fact; restart adjudication stamps how the previous runtime ended on the deaths it proves (crash, or the quit/update it began), so the crash row names it too. Older clients keep their single row. - Host: agentSession.continueInterrupted, capability-gated, rechecks under the session lock that the chat still sits on that cut, so a second click or a retry sends nothing. - Client: the row's copy names the cause and machine; Continue sits above the composer. * test(native-chat): Continue is not held by a recovery file that never answers * fix(native-chat): bind an Orca stop's cause to the runtime that held the agent; neutral row, Continue explains itself - The cause now rides on the host's row itself (`orcaStop` on the status row, beside today's words), the same row family and id scheme as the reopen's death row. - Each recorded owner is stamped with the Orca runtime that holds it; a death proven later (at restart, or when recovery stops a survivor) names how that runtime ended: the quit or update it began, else a crash. Owners an older build recorded, a terminal's claim, an agent that died while its Orca ran, and unreadable quit records all keep the generic words. - The quitting runtime's word is written first in teardown, before the recovery wait, through a bounded asynchronous writer apart from the chat database. - Row copy: one neutral sentence naming the machine and cause; it drops "You can continue in this conversation." while Continue is offered, and Continue's tooltip says what it does. * test(native-chat): type the Orca-stop test fixtures so the typecheck passes The cut turn's outcome takes the journal's outcome type, and the stand-in close reads the provider sink through a checked lookup instead of an index that may be absent. * fix(native-chat): call a cut a crash only when Orca's runtime started and never ended A chat said "Orca stopped unexpectedly" whenever its runtime left no quit record, and only the desktop quit wrote one, so a headless server's restart or update, the Settings relaunch, and a Windows logoff all read as crashes. Each runtime now records its own start when its chat store opens, and every graceful exit records its end through one synchronous entry point: the desktop quit's teardown, the headless server's stop, the in-app relaunch, the GPU-fallback restarts, the update-install watchdog, and Windows session end. A crash is a runtime that started and never ended; a runtime with no readable record (never written, pruned, unreadable) names no cause, so the chat keeps its generic words. One file per runtime, written durably and only by that runtime, so a damaged file never blocks a later write and two processes never lose each other's record. * fix(native-chat): Continue answers once Orca accepts it, not once the agent has started On a paired server, Continue waited for the agent to start before answering, and the client gives a paired call 15 s. A slow start (account switch, login shell, a long resume) showed "Couldn't continue this chat" while the agent was in fact continuing. Continue now answers when Orca has accepted the message, as a send does. The agent's start and answer settle afterwards, and a start that fails is the chat's own note, as before. The restart dialog's batch still waits for the handover, which is where it counts a start as done. The verdict helpers move to their own module to keep the continuation file within its size limit. * fix(native-chat): a reply the user steered, or a command run after the cut, still offers Continue The cut detector stopped at the first user message after the cut turn, so a steer the turn had taken, or a conversation command such as /context run after the cut, removed Continue while the row still named the cause. The rule for what is no request of its own (a conversation command, a row its turn produced, a send handed into a running turn) moves out of the latest-request reader into one shared predicate, which both that reader and the cut detector use. The host's "still wanted?" check reads the same detector, so the client and host agree. * fix(native-chat): a death proven after an earlier settle explains the turn it ends When a chat was read before the restart proved its old agent dead, the read could only call the turn unverifiable. The proof then revised the turn to interrupted, but the death row was scoped by the turn still marked running, and none was, so it landed on the conversation instead of the turn. That cut never offered Continue, and the row did not name it as the turn's explanation. The row is now scoped to the newest root turn the settle actually ends, running or revised. * fix(native-chat): the cause row's words stay put, and Continue waits out a resume already running The row's "You can continue in this conversation." came and went with the button: it showed while a paired host's answer was still on its way, vanished when the button appeared, and came back the moment Continue was clicked. Continue also appeared on chats the restart prompt or the launch's own resume was already carrying on. The row now drops that sentence wherever the chat's host can continue a cut, counting a host that has not answered yet as able (a host that writes cause rows has Continue), so its words never change on screen. Continue is hidden while a resume is carrying that chat on. * fix(native-chat): a refused Continue says so once, in the composer A Continue the host refused before accepting anything wrote a red note into the chat and brought the button back, so each retry added another identical note; a chat the host had no record of was refused with no word at all. Continue now reports every refusal the same way as a failed request: the existing composer line "Couldn't continue this chat. Try again, or send a message.", which a retry replaces rather than repeats. A refusal before acceptance writes no note. A failure after the message was accepted (the agent could not start) is still the chat's own note, as for any send. * fix(native-chat): the row naming Orca's stop carries its own presentation and never folds A client that re-words host rows it cannot name (the draft that makes these cuts read as interruptions) treated the cause row as an older red row and replaced its words, so the cause never showed there. The row was also folded away under its collapsed turn once shown muted. The host's cause row now names the presentation 'orca-stop' beside today's words, failure fact and red tone, so a client that predates both changes still prints exactly today's row, red and on screen, and a client that re-words unnamed rows passes it through. This build shows it muted, counts it as no failure (the reply it cut stays the turn's answer), and never folds it; the fold field becomes `explainsTurn`, as the other change names it. * test(native-chat): pass the session-end event without a type assertion * test(native-chat): the row naming Orca's stop renders neutral, whoever re-presented it Pins the rendered tone on this build: the stored red row, and the same row after a reader re-presents it in the neutral tone with its presentation and cause kept, both render muted and never fold. The phone draws chat rows without tone styling, so it needs no change. * fix(native-chat): the "Couldn't continue" line goes once the chat is continued The composer line a failed or refused Continue set stayed on screen while the agent carried on: after an answer lost in transit, or once another client or the restart prompt continued the chat. Only the next Continue click or the user's own send cleared it, and a click also wiped an unrelated composer error. The line is now derived: shown only while the chat still sits on the cut that Continue failed on, so it goes as soon as the journal shows the chat continued, from anywhere. A Continue click clears only its own line, and a retry answered "already continued" leaves none. * fix(native-chat): Continue waits while an opted-in launch may still resume the chat With "resume automatically" on, Continue showed on a quit or update cut while the launch was still waiting for its settings and reading the restart offer, then vanished when the launch's own resume began; a click in between sent a competing continuation. The launch's one decision (nothing offered, ask, or resume) is now published, and the chats it resumes are named the moment it decides, with no gap. Until it decides, and while the setting has not loaded or is on, Continue stays hidden on this machine's chats; a paired server's chats are not the launch's to resume and keep it. * fix(native-chat): a runtime's end survives a late reinstall, a failed write and any clean exit Three ways the runtime record could still read a graceful stop as a crash: - A chat host reinstalled during the quit (a request landing after teardown began) recorded the runtime's start again and erased the end it had just written. A second start of the same runtime now keeps that end. - When the end could not be written (a full disk), the start alone stayed and read as a crash. The runtime now removes its record, so its chats name no cause. - Each `app.exit(0)` had to remember to record the end. A process 'exit' with code 0 now records a quit when nothing else did: Electron emits it on every quit and exit once its loop runs (`app.exit` -> Browser::Shutdown -> the app's 'quit' -> process 'exit'), and Node on every `process.exit`. The relaunch and GPU-fallback calls it covers are dropped; the quit teardown, the headless server's stop, the update watchdog and Windows session end keep theirs, which run earlier or say more. * test(native-chat): build the re-presented row as the plain status item it is * fix(native-chat): a Continue click clears the composer's old error, so its own failure shows Since the "Couldn't continue" line became derived, an older composer error (such as "Remove attachments before using a chat-session command.") outranked it: a failed Continue showed the old error instead, and a Continue that went through left the old error on screen. A Continue click is the user's newer action, so it clears the composer's error again, as before; the line then shows the Continue's own failure, if any. That failure still goes away by itself once the chat is continued, and nothing but the user's own Continue click clears an unrelated composer error. * fix(native-chat): a chat start compares the owner process, not the runtime stamped on it A chat start checks that the process it just started is the one the record names, by a deep comparison of the stored owner with the adapter's process. The store stamps that owner with the Orca runtime holding it, so the check passed only because the store happened to return the record from before the stamp; returning the published record would have refused every chat start with agent_session_ownership_unknown. The start now compares the process identity without the runtime stamp, which says who holds the process rather than which process it is. * test(native-chat): count agentSession.continueInterrupted among the structured methods * refactor(native-chat): derive the structured chat's transcript session in its own hook Main's appearance work and this branch's Continue wiring together put NativeChatStructuredSession past the 400-line limit for components. The session the transcript reads moves, unchanged, to use-structured-chat-live-session.ts. * refactor(native-chat): keep the Continue capability in its own module Main grew protocol-version.ts to its line limit; the Continue capability moves to its own module, as other capability groups have, and the runtime list still names it. * refactor(native-chat): keep two shared files within their line limit after the main merge Main left agent-session-record.ts and structured-agent-session-params.ts just under 300 lines, and this branch's additions put them over. The account-home shape check moves next to the account-home type it checks (written without a type assertion), and the Continue params move to their own contract module; the params catalog is regenerated. No behavior change. * test(native-chat): compare the store directory's files without depending on listing order The corruption test checks that no file was created or removed by comparing two recursive listings. Their order is the runtime's: with the per-runtime record directory nested under the store, Bun returns the same entries in a different order than Node. Both listings are now sorted. * fix: share the path bound main's launch-directory check needs * test: give the stop-row fold rows the draws flag main's fold now reads * refactor: mark the launch's resume decision where the resume begins * test: count main's new structured method alongside agentSession.continueInterrupted * fix: the journal database keeps its folder, where runtime end records live Main's #26038 dropped stateDirectory from JournalHostDatabase; this PR's runtime end records are read from and written beside it. |
||
|
|
990c62e6c6 |
feat(native-chat): attach files to a structured chat on a paired server (#25146)
* feat(native-chat): a paired server keeps a store for chat attachments A structured chat on a paired Orca server had nowhere to put a file the client attached. The server now keeps a per-chat attachment store under its userData, filled through agentSessionAttachment.uploadStart/Append/Commit/Abort and read back for previews through agentSessionAttachment.read, behind the structured session gate and advertised as agent-session.attachments.v1. Nothing records cleanup as owed: a sweep re-derives what may go from the host's chat records and journal (unfinished uploads after an hour, uploads for a chat that never existed or that its journal never mentions after a day). The clipboard RPC's in-flight bookkeeping moves into a shared ChunkedUploadRegistry both use. * feat(native-chat): upload attached files into a paired server's chat store Main streams dropped or picked files (fs:uploadPathsToAgentSessionAttachments) and pasted images (clipboard:saveImageAsTempFile with agentSessionAttachment) into the chat's store on its paired server, reusing the file-import slice streamer and pinning every call to the pairing revision and server process. The browser client's paste takes the same route. * feat(native-chat): attach files to a structured chat on a paired server Pasted images, files dropped from Finder/Explorer and the file picker no longer refuse with "Local attachments are not available for remote sessions." in a structured chat on a paired server: they upload into that server's chat store and the chat gets only the stored path. Dropped files show as pending chips at once so Send waits for them; every attached item carries the server, pairing and chat it was stored for, and a send to anywhere else drops it with the existing "changed hosts" notice. Chips and transcript images read stored files back through the server, never from this machine's disk. An older server gets an update notice instead of an upload. The terminal-backed chat keeps refusing. * test(native-chat): type the attachment test doubles without bare casts * refactor(native-chat): keep the clipboard RPC on its own upload bookkeeping The chunked upload registry stays for the chat attachment store only, beside it. * feat(native-chat): claim chat attachments when the host admits a message A message's references into the host's attachment store are claimed in the same journal transaction that makes it durable (a direct send, a queued draft, a /clear carry). The sweep deletes an upload only after marking it in that database while no claim exists, so a send and a sweep can never both win: a client's send that names an expired or foreign upload is refused whole with the new attachmentExpired reason, before anything is recorded. This replaces scanning chat transcripts for paths. The store is now <root>/<upload id>/<name>, refuses uploads for chats the host does not hold, caps stored names in UTF-8 bytes and avoids Windows device names. The preview read goes through the protected bounded read and the request's reply budget, so an image too large for one reply is refused instead of closing the connection. * feat(native-chat): let structured Claude read the host's chat attachment store Attached non-image files live outside the workspace; the store root is passed as an added directory so the agent reads them without asking, the common pattern. * fix(native-chat): skip a dropped folder before staging walks into it * fix(native-chat): pin the browser client's chat paste to its server Every upload call goes to the environment the paste was meant for and checks its pairing and server process, as the desktop upload does; a re-pair ends the upload instead of storing the image on another server. * refactor(native-chat): let the host's claim be the only attachment check The composer no longer records which server and pairing each attachment came from, and Send no longer strips attachments it judges foreign: the host refuses an expired or unknown stored path when it admits the message, which also covers retries, Stop-restores and queued edits the client check missed. Previews of stored files route through the chat's own server by path. Removing a file's chip while it uploads now keeps its @path out of the draft. A dropped or picked file shows its name and kind on its chip while it uploads, with no separate progress toast, and files that did not attach are named in one notice. A rich-text paste into a chat on an older server no longer shows the update notice beside the pasted text. * chore(native-chat): keep the claim hook beside the draft consume and fit the line budgets The submission's claim runs in the same append hook as a draft's consume, owned by the queued-message collaborator, so the journal store stays within its size limit. Formats the new files and regenerates the runtime-required English catalog. * test(native-chat): pin the attachment store grant in the Claude launch, restore upload result types * fix(native-chat): create the attachment store at install and claim only exact store paths Claude drops an added directory that does not exist when it starts, so the store root is created when the host installs it, best effort. A commit keeps its upload in flight until the rename lands, so a sweep never takes a slow upload's part file. Only a path under this host's exact store root is claimed and required; any other mention of a store path is plain text and never refuses the message. * refactor(native-chat): reuse the newer-Orca notice and drop leftover attach options An older server's refusal to store attachments uses the notice every other write it refuses already shows, so one string fewer in every catalog; the attach callbacks lose an options parameter no caller passes since the provenance check went. * fix(native-chat): give a message refused for an expired attachment back to the composer Sending the same message again can never bring the attachment back, so instead of a Retry that always fails the message's text and images return to the composer, as a Stop's withdrawn message does, and the notice says what to remove. * fix(native-chat): keep uploads across a prompt, say why a file did not attach An upload that finishes while a prompt card has replaced the composer lands in the scope's attachment and draft caches, which the composer reads back when it returns, instead of the unmounted composer. The single failure notice names the cause the files share, such as the size limit. Rich text pasted into a chat on an older server no longer flashes an image chip: the chip waits for the server to take the image. A picked command waits, as Send does, while an attachment is still uploading. * fix(native-chat): keep a paste's upload across a prompt; pin the Claude grant hand-off A paste uploading into a paired server's store keeps its chip live when a prompt card unmounts the composer, and its result is filed for the composer's return, as a drop's is. A failure cause ending in full-width punctuation gets no extra full stop. A runtime test pins that the store root reaches Claude's launch resolver through the adapter. * test(native-chat): type the grant hand-off captures without casts * test(native-chat): pin that the composer files a paste's upload across a prompt * fix(native-chat): retry a held rename at commit and keep cut names Windows-safe A commit renames the part file with the Windows retry the app uses elsewhere, so an antivirus or indexer holding it briefly no longer loses the upload. A name cut to the byte limit is stripped of trailing dots and spaces again. The upload pump's result cast states why it holds. * fix(native-chat): keep attachments still on their way in the pane's attachment cache A prompt card unmounts the composer, and an attachment still saving or uploading lived only in that composer: one that came back showed no pending chip, so Send went out without the file and the file then landed in the next draft. Pending chips now live in the pane's attachment cache beside settled ones and settle there whichever composer is showing, so Send waits for them after a remount too. A stored file's @path goes into the pane's draft cache, which keeps it through an input-method composition and a remount. A rich-text paste's image is registered as a hidden pending chip before the server is asked, so Send waits for it from the start, and it shows only once the server takes it. * test(native-chat): a dropped file still uploading stays pending across a remount * fix(native-chat): reveal a rich-text image in a composer that came back; keep caret inserts A rich-text paste's held image is revealed through the pane's attachment cache, so a composer a prompt card remounted during the server's answer shows it rather than holding Send on an invisible chip. A stored file's @path goes in at the caret while the composer is showing and not composing, as every other attach does; only mid-composition or after a remount does it go to the pane's draft. * fix(native-chat): never evict a pane's attachments while a composer shows them or one is on its way The pane attachment cache is now where chips live, so its 128-scope bound passes over a scope a mounted composer subscribes to or that still holds a pending attachment, and evicts the oldest scope nobody uses instead. Protected scopes are bounded by mounted composers and attachments in flight. * fix(native-chat): never evict the scope just written when every older one is in use * fix(native-chat): keep the attachment store out of the RPC method table's imports The method table is imported far and wide (the SSH relay's CLI included), and the attachment RPCs pulled in the store, whose preview read loads modules that read fs constants at load: any test that mocks fs/promises without them failed to import. The installed store now lives in a small registry module; only the host wiring loads the store itself. * refactor(native-chat): the submission hook gets its own module Merging main added the operation receipt to the queued-message insert, which put journal-queued-messages.ts over the 300-line limit. The submission hook only uses the queued-messages object's public methods, so it moves out. * refactor(native-chat): fit the attachment sweep and paste upload into main's line budgets After merging main, the record store and the clipboard handlers each ran a few lines over the 300-line budget. The sweep now asks the record store whether a chat is recorded through the two lookups it already has (readable or unreadable) instead of a new id-listing method, and the paired-server paste upload lives beside the other attachment uploads. No behavior changes. * fix(native-chat): pending chips sit beside main's saved draft, which keeps server-stored images Main now saves a composer's draft (text and settled images) in one store that survives a reload or quit. This branch kept every chip, settled or not, in its own pane cache. After the merge the saved draft owns settled images and the pane cache holds only chips still on their way: they come back pending in a composer a prompt card remounted, hold Send, settle into the saved draft whichever composer is showing, and are never saved themselves, so a restored draft cannot bring back an upload as if it were attached. An image a paired server stored for the chat is saved with the draft as the real image (a pasted one is no longer turned into a "Not kept" placeholder), and the restore check leaves it to that server, whose claim at Send refuses one it no longer holds, instead of asking this machine's disk for a path that only exists on the server. Also: previews and the pending subscription move into their own small hooks to fit the line budget, the "local attachments" notice moves to the composer-target module so the attachments hook no longer pulls in the upload module's imports, and tests follow main's new mocks. * test(native-chat): give the claims test main's provider handle and message source * fix(native-chat): a paste still uploading dies with its tab or workspace A pasted image still uploading to a paired server sits in the pending chip cache, which by design outlives the composer and its tab. Removing the workspace deleted its saved drafts but left those chips, so the upload finishing afterwards recreated and saved the deleted draft. With the workspace's tabs gone it had no owner either, so no later workspace cleanup could ever remove it. A pending chip now records the owner its draft would have, resolved the way the draft store resolves one when the chip is added. Workspace removal and a user's tab close drop pending chips by the same owner, conversation and tab matches they delete drafts by, and a chip that settles after its chat's tab closed writes its draft under the owner it was begun in. * fix(native-chat): the claim type uses the journal's own SQLite type * refactor(native-chat): read the pending-image handlers off the composer's attachments Main's multi-file picker (#23956) and this branch together took NativeChatComposer over the 400-line limit; reading the two pending-image handlers the way the neighbouring ones already are keeps it at the limit. * test(native-chat): pass the launch-args resolver main now requires in the attachment grant test * fix(native-chat): grant the attachment store beside folders the saved Claude Arguments add Main (#25721) now builds additionalDirectories from the user's --add-dir arguments; this branch's attachment-store grant replaced that list instead of adding to it. Both are granted now. Moves the Claude session-id derivation to its own module to keep the resolver under the line limit. * test(native-chat): run the attachment store's SQLite tests in the Node runtime project * test(native-chat): follow main's attach ownership recheck (#25749) in the attachment tests * test(native-chat): a named upload chip still shows when the agent takes no images * fix(native-chat): a paired-server image paste failure keeps its error apart, as main's notice card does * fix(native-chat): the attachment sweep reads the chat list directly now that records have no import still owed |
||
|
|
98f0193afb |
feat(native-chat): show agent-written visuals inline in structured chats (#26103)
* feat(native-chat): visual directive grammar and host read for a chat's visuals folder
A shared grammar for the ::orca-visual{file="..." title="..."} reply line,
the per-chat visuals folder location on the owning host, and the
agentSession.readVisual runtime method that reads one visual with lexical and
canonical containment, a 512 KiB bounded read and UTF-8 refusal.
* feat(native-chat): render chat visuals inline and in the right sidebar
Native-chat assistant replies render a ::orca-visual{...} line as the chat's
HTML visual in an opaque, scripts-only sandboxed frame: CSP first, the host
frame navigation guard registered before content runs, live theme without a
reload, fitted height, links opened in the viewer's browser only from a real
gesture, lazy mount, and one muted line when the visual cannot be shown.
Open in sidebar shows the same frame in the right sidebar, widened while it
is open and restored after.
* fix(native-chat): visual CI fixes, shared height governor, live-turn streaming hold
Registers agentSession.readVisual from the methods index so the structured
method file stays under its line budget, replaces reflective reads with checked
narrowing, moves the pure height governor to src/shared for mobile, and holds a
half-written directive tail while the turn works (structured text rows carry no
running state).
* fix(native-chat): harden the visual read and link opening
Re-checks after the open that the chat's visuals folder is still the real
directory at Orca's path, reports unexpected filesystem faults by code without
host paths, and lets one click in a visual open at most one page.
* fix(native-chat): keep visual lines out of plain-text reply surfaces; review fixes
One shared helper drops visual lines (outside fenced code) from reply text where
it becomes plain text: the structured status summary that feeds the sidebar row,
dashboard, notifications, phone rows and handoffs, and AI Vault reply previews.
Review fixes: height also counts a pinned body's overflow, only the live
frontier row holds a half-written visual line, the runaway-height stop needs the
same step repeated, and any host refusal evicts the cached revision.
* fix(native-chat): resolve the visuals folder without the removed journal-paths helper
Main removed the per-chat journal paths and the journal database's state
directory; the visuals folder keeps the same sha256 layout on its own and the
read method uses the profile state directory the chat host is opened in.
* fix(native-chat): review round 2 fixes; copy a reply without its visual lines
Reply previews in Agent Session History drop visual lines per text part before
lines are folded; the frame adds a body's overflow only when the body really
overflows; fence tracking follows CommonMark closers and openers; the copy
button copies a reply without visual lines; a coded read fault keeps its cause.
* fix(native-chat): update the frame's theme ref after render; read the visuals folder pair at once
* test(native-chat): declare agentSession.readVisual on the cross-version agent-session surface
* fix(native-chat): copying a reply keeps its code blocks and indentation
Removing visual lines now closes only the gap each removal leaves, instead of
collapsing blank lines across the whole reply and trimming its indentation; the
visuals folder is checked parent first again so a broken path answers the same
way every time.
|
||
|
|
91d6ed0f3c |
fix(worktrees): a phone or CLI create puts the agent in the first orca.yaml default tab, as the desktop does (#26106)
* fix(worktrees): the host's create puts its agent in the first default tab, as the window does With orca.yaml default tabs, the window's create starts the agent in the first default tab: that tab takes the template's title and color, and the template's command does not run beside the agent. The host's create started the agent in its own untitled tab and then created every default tab, the first one running its command too, so the workspace had one tab more than the window's create. Now the agent's tab takes the first template's place. * fix(worktrees): title the agent's default tab as a tab, so the phone keeps the agent's status The host's create titled the agent's tab by renaming the terminal, which writes the pane's own title stamped to outrank every title the agent sets later. On a headless host the phone reads status from those titles, so the agent tab showed the template title forever and lost its working/idle status. The window's create only sets the tab's custom title. The host now titles the tab the same way: the window gets the tab's custom title (not counted as a user rename), and a headless host saves it on the tab and shows it on the phone until the agent titles itself. The pane's title is left to the agent. The host also finds a terminal's tab itself from its handle, so the create over SSH, which never passed the tab, colors the agent's tab too. Every default tab is dressed by the same step, so the other tabs' titles are now kept on a headless host as well, and a failed title no longer skips the color. * fix(worktrees): find a default tab through the window's leaf once the window adopts its handle The host found a provisioned terminal's tab only through the runtime's own pty record. Once the window's graph sync adopts the handle, that lookup misses, and the default tab's title and color would be dropped with only a log line. No create reaches it today (the tab is dressed before the window's first graph sync), but any wait added before provisioning would. It now falls back to the window's leaf, as renaming a terminal already does. Tests: the restart case now saves the title to a real session and rebuilds the phone's tabs from it; a create through createManagedWorktree with an agent and default tabs spawns the agent and the second template only and titles the agent's tab "Dev" without counting it as a user rename. * test(worktrees): drop type assertions from the default-tab tests Use a runtime subclass for the protected launch scope and provisioning host, a typed store and notifier, and typed mocks in place of casts, so the changed-code quality gate passes. |
||
|
|
5b1bb78d14 |
[STA-6940] Prepare file drops for element owners (PR 2 of 6) (#25748)
* feat(file-drop): add element owner preparation plumbing * fix(file-drop): preserve feedback acceptance and destination ordering * fix(file-drop): keep path resolution in filesystem namespace |
||
|
|
00e3027762 |
feat(agent-launch): show an agent's tab at once, where the caller asked (#25430)
* feat(agent-launch): host-assigned caller identity and a launch record written when the surface exists Step 1 of the agent-launch unification, on main. - The dispatcher stamps every request's caller from what its connection proved (runtime socket: the local CLI; the desktop's IPC: the desktop; a paired socket: its device). Params never set it. - The launch record is written twice: once when the tab exists (what creation settled: on the launch command, a draft, or a submit still `unconfirmed`), and again once the prompt's fate is known. A restart in between finds the running agent instead of answering "unknown". - A replay re-derives its terminal handle from the pane key in the running host, and shows `unconfirmed` only to callers that advertise agent.launch.prompt-unconfirmed.v1. - The record store opens in its own slot, without building the chat host; the chat host is built on that same store. Rebuilt from this PR's own commits ( |
||
|
|
f7c542c7a3 |
Keep profile saving alive after a stalled main loop (#25318)
* Keep profile saving alive after a stalled main loop After a long main-loop stall (overnight sleep, dark wakes), the profile writer's overdue 30s timeout could run before an acknowledgement that was already queued, permanently retiring the writer until restart. Terminal creation then failed because pane bindings could not be saved. - Writer deadlines measure lateness on the monotonic clock and grant a bounded fresh window when the callback is overdue or the system reports suspended; resume re-arms without spending grace. Applies to initialization, every command, and the close/exit wait. - The "Saving stopped" alert is parented to a visible main window (never a parentless synchronous macOS alert), deferred until shown, deduplicated, and says whether the latest change is unconfirmed. - Timeouts, grace, writer faults, and alert presentation leave sanitized durable breadcrumbs. * Fix profile writer timeout and shutdown races * Run profile writer stall regression on Linux and Windows * Keep Electron probes out of headless runtime qualification |
||
|
|
dc8b3934b2 |
Add chat appearance settings: text size, code size, width (#25657)
* feat(native-chat): add chat-scoped color tokens * feat(native-chat): soften transcript and composer appearance * fix(native-chat): refine code spacing and faint text styling * fix(native-chat): wrap prose links at word boundaries * test(native-chat): refresh background task strip snapshots * Add native chat appearance settings and card scaffold * Persist chat text and code sizes with appearance width controls * Fix chat appearance typing, shortcut routing, and scoped typography * Use chat call identities in typography regression fixture * Keep markdown metadata typography independent of chat text size * Preserve terminal undo chords and stabilize chat resize estimates * Show only primary shortcuts in chat appearance settings * fix(native-chat): preserve appearance edits and configured zoom shortcuts * Remove unused chat appearance summary translation * Preserve command classification when applying chat code size |
||
|
|
135c92e01f |
fix(native-chat): an unsent draft survives a reload or quit (#24905)
* fix(native-chat): an unsent draft survives a reload or quit The composer's draft text, its editor document and settled image refs lived only in module memory, so a reload, quit or crash lost whatever was typed, including text a Stop had just given back. They are now saved per pane scope in localStorage: typing is debounced and flushed when the window hides or closes, text given back to the composer is saved at once, an emptied or sent draft is removed at once, and only the newest drafts within the composer caches' existing bound are kept. * fix(native-chat): save draft images outside the state updater * fix(native-chat): one store owns each unsent draft The draft's text and its image chips were saved by two writers that each rebuilt the stored record from what storage held, so a draft that once went over the size cap, or one refused write, lost its images on the next save. One store now holds each composer scope's whole draft in memory and writes storage from it: typing is coalesced, returned text, chips and clears land at once, a refused write stays pending for the next flush, and all drafts together keep to a budget so the outbox can still save a send. Closing a tab drops its panes' drafts. * test(native-chat): returned text is stored before its images arrive * fix(native-chat): keep returned text and drop launch-seed copies across reloads A message given back from the outbox could be larger than one draft was allowed to store, so the saved draft was deleted and the returned text was gone after a reload. One draft may now use the whole draft budget, which holds the largest message a send accepts. An adopted launch link is also parked in the agent's input line. Restoring it after a reload, with no seed left to replace that copy, typed it twice, so the untouched copy is shown but not saved. A composer reused for another pane now shows that pane's images, so adding one never saves the previous pane's over them. * test(native-chat): a plain 260k given-back message stays saved * fix(native-chat): saved drafts drop pasted images and die with their worktree A pasted image lives in a temp file Orca wrote, and the permission to show it is held in memory only, so after a restart a restored pasted chip showed a generic icon, and once the temp file was cleaned it looked fine but the send failed after the composer cleared. Pasted images now stay in the draft for the current run only; images the user attached from their own files are still saved. Removing a worktree now deletes its terminal tabs' drafts, as closing the tab does. A paste that finished late in a composer that had already been replaced wrote that composer's old images back over a newer send. Dropping a placeholder chip no longer writes the saved draft. The pane-change reload in the images hook is removed: the composer is keyed by pane, so a pane change always mounts a new one. * fix(native-chat): the composer renders the draft store instead of keeping a copy A composer that had been replaced, for example by a question prompt, could still finish an attach or a send later and write its own old copy of the draft over the store, so a sent message or image came back. The composer now reads text and images from the draft store and changes the draft as it is now: an added image or file reference joins what is there, and a send that settles after a round trip, or after its composer was replaced, clears the draft only if it is still what was sent. Only chips still being written and an IME composition in progress stay local to the composer. Closing a tab now deletes drafts by the exact tab id: a second chat for one session has an id that extends the first one's, so a prefix match deleted its drafts too. * fix(native-chat): keep images pasted during a command and on shown composers An image pasted while /compact or /clear was still on its way to the host vanished when the command was accepted: the clear that followed also wiped the chip still being saved. A command that settles now clears only the draft it sent, and an image pasted meanwhile stays. A composer on screen could lose its pasted images, and its unsaved launch text, once 128 other drafts pushed its record out of memory, because those are never stored. A draft that a composer is showing is no longer evicted. * fix(native-chat): a draft being written is never evicted from memory A write to a pane while every other cached draft was on screen could evict the record it had just written, losing it. Also adds the test that a send settling after its composer unmounted clears the saved draft, through the real send hook. * fix(native-chat): restored images Orca couldn't keep come back to attach again A pasted screenshot in an unsent draft vanished after a restart with no notice, and a restored image whose file had been deleted looked fine until the send failed. Both now come back as a placeholder chip that names the image and says it wasn't saved with the draft. Send stays disabled, and the send button says to attach the image again or remove it. Attaching a file with the same name takes the placeholder's place. Two browser tabs of the web client on one chat now follow each other: when one sends or edits the draft, the other drops its copy and shows the new one, unless it has an edit of its own not yet saved. * test(native-chat): no send while an image waits to be attached again * fix(native-chat): pasted images in a draft come back after a restart A pasted screenshot lived in the OS temp folder with a read permission held in memory, so a draft could only bring it back as a placeholder. Local pastes are now written to Orca's own native-chat-pastes folder under the app's user data, and a draft keeps them as real images. On restore the composer asks the main process to re-grant each one; main grants a read only when the file's real path, symlinks and junctions resolved, is inside that folder, and reports anything else as not kept, which becomes the placeholder. Pastes older than 30 days are deleted at startup, far past the host's 24 hour window for a resent message; the sweep never follows a link and never blocks startup. SSH pastes and older temp-folder pastes still come back as placeholders. * test(native-chat): the paste sweep test ages the link itself * fix(native-chat): type the paste-folder path helper with PlatformPath * fix(native-chat): type the paste-folder path helper from path.posix * fix(native-chat): placeholder chips say what happened and only a user attach replaces one A placeholder now shows the image's name and a short hint on the chip itself, "Attach again" for a file and "Not kept" for a pasted image, with the longer explanation in its tooltip. The copy no longer says the image wasn't saved: it says it couldn't be brought back, and for a pasted image it asks only for removal, since a new paste can't match it. The send button now says to remove the image to send. Only an image the user attaches (picking, dropping or pasting) takes the place of a placeholder with its file name. An image Stop gives back is added beside it, so a different file with the same name no longer hides the reminder. * fix(native-chat): only composer pastes use the paste folder, and restore grants only real paths Every local clipboard image save had moved into the paste folder, whose macOS path has a space, so a screenshot pasted into a terminal or sent to a terminal-backed agent no longer attached. Only a native-chat composer paste goes there now, through an optional field on the existing save call; terminal, editor and phone pastes stay in the system temp folder as before. An image path sent to an agent's input is escaped the way a dropped image is. The restore re-grant now grants only the file's real path, and only when both the real path and the path as written are inside the paste folder, which must not itself be a link. The sweep deletes only Orca's paste files and skips a linked folder. * fix(native-chat): a restored paste is readable by the path its draft stored Restore granted only a paste's real path, but the composer reads the preview by the path its draft stored, so with user data reached through a link the restored chip showed a generic icon. The stored spelling is granted too; it is already proven to sit inside the paste folder. A test also pins that a composer paste asks for the paste folder. * fix(native-chat): grant a paste's stored spelling only when it names the same file A grant also covers its path's own real path, so a stored spelling that is itself a link inside the paste folder could have granted an outside file while the path as restored reached a real paste. The stored spelling is now granted only when it resolves to the same file; otherwise only the real path is, and the preview falls back to the generic icon. * test(native-chat): check the outside file by its real path too * fix(native-chat): a structured chat's draft belongs to its conversation Drafts of a structured chat were saved under the pane (tab id plus a hash of the session), so the follow-up that keys them by conversation would have left every draft saved by this build invisible after an upgrade, never shown and never deleted. They are now keyed by the conversation (`agent-session:<sessionId>`) from the start: every composer showing the conversation shares one draft, Stop and a queued card's Edit give text back to it, closing the tab keeps it, and removing the worktree deletes it. Terminal-backed chats keep the pane key and lose their draft when the tab is closed. A send now leaves whatever was added since it was sent, text typed or composed and images attached meanwhile, and clears only what was sent; a draft replaced in the meantime is left alone. Lifted from #25207 ( |
||
|
|
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> |