Commit Graph
3992 Commits
Author SHA1 Message Date
Neil 5018241866 feat(plugins): app and terminal themes in one Theme list, painted safely (#30719)
* plugins: app and terminal theme contributions

Manifests can contribute themes (public CSS tokens over a light or dark
base) and terminal themes (an ANSI palette), each at most 16 KiB. App
themes must set --background and --foreground; omitted tokens are derived
from them (surfaces copy the background, muted text, borders and accents
mix the foreground into it), and values must be colors (no keywords,
lengths, calc, var or url). Both load through the shared artifact-pack
registry with the plugin's display name and a content revision, reach the
renderer over IPC and are re-validated there. Catalog releases can declare
app-theme and terminal-theme, and marketplaces stop hiding theme listings.
docs/reference/plugin-themes.md documents the format.

* plugins: resolve a selected plugin terminal palette on every host

Selecting a plugin terminal theme saves a copy of its palette in settings
(terminalPluginPalettes). The shared lookup reads it, so main, headless
serve and relays answer terminal color queries and native chat matches the
terminal without the plugin loaded; a malformed snapshot falls back to the
default theme.

* plugins: one Theme list with plugin themes, painted safely

Appearance keeps the System/Dark/Light control until a plugin offers a
theme; then Theme becomes one list with plugin themes after a divider,
labelled with the publisher when names collide. Choosing one stores its
base in theme and remembers the earlier built-in choice, which comes back
while the plugin is off or after it is uninstalled; a plugin update that
changes the base re-syncs theme. It also offers the plugin's terminal
colors in one click.

The renderer paints only values CSS parses as colors, checks every
text/surface pair, ring and border against the effective set (plugin
values over this mode's built-ins) and repeats until nothing changes, so
dropping one pair cannot expose another. The applied theme is cached with
its id and revision, painted before first render, and dropped as soon as
settings show it no longer applies. Main bounds the new settings on every
write and load, and uninstalling a plugin clears its theme choices and
palette copies. Terminal themes appear under Plugins in the picker with the
plugin's name and are passed to lookups instead of read from a store.

* plugins: type the theme settings normalizer input; let the store own its normalization

* fix(plugins): check text contrast on the editor surface and clear terminal overrides when adopting a theme's terminal colors

* fix(plugins): reject theme colors the canvas probe silently ignores
2026-10-28 15:13:10 -07:00
Jinwoo Hong 93ff4832e4 feat(orchestration): worker-start new worktrees default to a generated name (#35363) 2026-10-28 17:51:08 -04:00
af14a5b50b fix(i18n): localize provider errors and shared/native UI labels (#34680)
* fix(i18n): localize provider failures and remaining shared UI copy

Translate Linear/Jira failures at the renderer boundary while preserving
English errors and adding optional stable codes for existing clients.
Localize host status, source-control metadata, discard confirmations,
native dialogs, and catalog gaps across all six shipped locales.

Inventory and reproduction paths contributed in #32207.

Co-authored-by: Seiji Kohara <9543980+seijikohara@users.noreply.github.com>
Co-authored-by: Evgenii <413011+smwbev@users.noreply.github.com>

* test(i18n): assert stable task provider failure codes

* test(i18n): exercise goal notice fallback with an older language pack

* fix(i18n): address provider error and live title review findings

Handle missing remote error text, type provider codes and keep codes intact
through connection tests and optimistic edits. Fix the remaining Linear
load fallback and redundant translation calls, apply Japanese wording
corrections, and refresh native titles on language changes.

Co-authored-by: Seiji Kohara <9543980+seijikohara@users.noreply.github.com>

* fix(i18n): finish provider and shared-copy localization for all locales

- Translate the branch's new keys into de, ru, tr and zh-Hant (added to main
  after this branch was cut), with Russian few/many forms.
- Use whole sentences for file deletion failures and the mixed batch notice
  instead of inserting a 'Recycle Bin'/'Trash' word, and count keys for the
  batch titles.
- Translate the workspace/activity options filter count (was a shared
  'N filter(s)' literal) at the renderer call sites.
- Use _one/_other families for MCP server counts, untracked-file discard copy,
  resume-chat counts, discard file counts and visible host counts.
- Localize PR/MR card status labels, the unsupported-agent hint, Linear title/
  description update failures, the Linear 'showing first N' notice and the
  Linear Connect button.

Co-authored-by: Evgenii <413011+smwbev@users.noreply.github.com>

---------

Co-authored-by: Seiji Kohara <9543980+seijikohara@users.noreply.github.com>
Co-authored-by: Evgenii <413011+smwbev@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 14:49:17 -07:00
OrcaWinandOrcaWin 7375d3abd5 Avoid starting file scans for canceled listings (#35287)
* fix(search): reject canceled file listings before process admission

* test(search): trim listing admission cancellation cases

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 14:43:34 -07:00
Brennan Benson bb1b05e3bd Clear Google cookies from a browser profile, as often as you need (#34158)
* Add repeatable Google cookie clearing for browser profiles

* Bind Google cookie clearing to its successful import target

* Repair cookie-clear contract test boundaries and positive flows

* Correct cookie-clear test fixture typing

* test: use checked runtime bindings in browser profile RPC fixture

* test: provide page registry to sidecar forwarding fixture

* fix: use selected browser profile owner for cookie clear menus

* test: provide confirmation context to link materialization fixture

* test: classify cookie clear session channels

* refactor: consolidate browser command property guards

* test: update cookie clear fixture for merged registry API

* feat: move Google sign-in recovery to browser settings

* Freeze Settings Google cookie clearing intent and refresh availability

* Restore baseline browser import disclosure coverage

* Pass the selected browser profile into cookie settings

* Localize Google cookie settings search keywords

* Update cookie restart fixture policy exports

* Keep localized browser search results visible

* fix: make Google cookie clear a red right-aligned setting that explains when to use it

* Explain why Google cookies are handled separately in the clear row caption
2026-10-28 14:40:57 -07:00
Neil 9015f13735 feat(plugins): bind worker and panel approval to exact bytes and recheck before every start (#30637)
* plugins: bind worker and panel approval to exact package bytes

Consent for any plugin that runs code (a v2 sandboxed-ui or trusted-worker
profile, or a v1 manifest with a worker or panel) now names its installed
content hash, so an update cannot swap code under an old approval. Shipped
v1 installs re-prompt once. Data packs keep their fingerprints, and dev
trees stay unbound so a save doesn't re-prompt. The re-review notice is one
short line.

* plugins: integrity-check reviewed bytes before every worker spawn

The content verifier cached its first check, so bytes swapped between a
crash and its restart still ran. The controller now hands the manager one
uncached verifyBeforeSpawn hook that runs before every spawn, including
crash restarts, and replaces the cached first-start check, so nothing is
hashed twice and a running worker is reused without hashing. It is an
integrity check, not a sandbox: Node still reads files after it. Approval
is re-asserted at the same points, so a revoked plugin stops restarting
instead of counting as a crash, and cancellation does not wait on a hash.

* plugins: restart a running worker on its updated code

A refresh that changes a running worker's spec used to stop it and wait for
the next command or event. Reconcile now restarts it once on the new spec
after every stop, through the same approval and integrity path, and skips
the restart if a newer reconcile or dispose has taken over. Idle workers
stay lazy.

* test(plugins): re-review a dev copy that takes over an approved install

Approval of worker and panel plugins now names their exact bytes, so
switching the demo plugin from its installed copy to the dev folder asks
for review again; the journey approves the dev copy before using it.

* fix(plugins): admit worker restarts against current reviewed spec

* test(plugins): say why the worker refresh tests read the clock and wait out a backoff

* fix(plugins): refuse custom Git remote helpers in plugin downloads and test smudge filters for real

* fix(plugins): keep crash restarts through refresh pauses, stop workers before awaiting refresh on shutdown, and narrow inherited Git protocol allowlists

* test(plugins): shrink the flaky-test baseline for the worker controller test
2026-10-28 14:28:26 -07:00
OrcaWinandOrcaWin 6ed75d810b fix(search): keep later hits after an oversized record (#35336)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 14:23:58 -07:00
OrcaWinandOrcaWin 88e23db6b5 test: remove 16 low-value audience, notice and export cases (#35128)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 14:12:18 -07:00
Neil 7646849038 fix(terminal): a paired client's terminal answers theme queries with that client's theme (#35340)
A windowed host answered OSC 10/11 for every terminal with its own window's
theme, so a Codex started from a dark client on a light-appearance host baked
a light input area. On such a host the launching client's colours and palette
now travel to the PTY owner as launcherColors and launcherAttributes. That
owner becomes the terminal's only theme responder: it answers OSC 10/11/12,
OSC 4 and ?996n with the launcher's theme (plus anything the app set) and
strips them, so neither the host's hidden-pane responder nor a viewer answers
with another theme. The startup query scanner gains these queries for pinned
terminals only; without launcherColors they pass through as before.

Terminals no client launched keep following the host-wide theme, and a
windowless host still follows its clients' later pushes. Older owners drop the
optional fields and behave as before.

terminal.setViewerColors also accepts an optional full appearance, so a
windowless host's hidden panes answer OSC 4/12 and ?996n like the client. A
colours-only update that contradicts the kept palette drops it rather than
mixing two clients' themes.

Fixes #34340
2026-10-28 14:07:04 -07:00
Kelvin Amoaba 45364ef151 fix(native-chat): retry a pending decision read after a temporary failure (#35339)
A failed agentSession.currentRequests read marked its version as tried for good, and the live read only reopened the subscription once per version. Two slow or dropped reads in a row left a pending approval or question without controls on remote desktop, web and phone clients until the decision changed.

The reader now re-reads a version whose read failed in transit, with the stream's reconnect backoff, and stops only when the answer breaks the contract or the host refuses for good. The transcript subscription is no longer reopened for a decision-read failure.
2026-10-28 21:01:56 +00:00
Neil 203bf56dfd feat(wsl-mover): report how a move's undo ends (#35322)
* feat(wsl-mover): report how a move's undo ends (wsl_move_undo)

* fix(wsl-mover): tell a thrown undo apart from a failed one; clamp its duration
2026-10-28 13:58:17 -07:00
Brennan Benson d0bfbd22ac Keep host-removed rows out of desktop and phone chats (#34996)
* Keep removed chat rows out of coalesced updates

* test: bound destructive history burst work

* test: verify removed history through transcript projection

* test: count retained row reads with typed getters
2026-10-28 13:50:25 -07:00
Neil 7b3fb541f4 Built-in syntax highlighting for .env files (#34542)
* feat(editor): built-in syntax highlighting for .env files

.env, .env.* and *.env files now open as a dedicated dotenv language with
comment, key, export, value, quoted-string and ${VAR} colouring instead of
INI. .envrc opens as shell, since it is sourced as a shell script.

* fix(mobile): map dotenv to the ini grammar for mobile highlighting

* fix(editor): keep quotes after the first dotenv value character literal
2026-10-28 13:40:03 -07:00
Neil 751e974f88 feat(plugins): download sources without checkout and pin catalog releases in a v2 manifest/catalog (#30636)
* plugins: download Git sources without checkout, within a protocol allowlist and size cap

Plugin and marketplace sources were a real clone/checkout, so user hooks,
smudge filters and core.hooksPath ran, insteadOf rules could reach any
transport, and nothing bounded the download. Now a --no-checkout depth-1
clone (or exact-commit fetch) runs with an empty hooks path, only the https
and ssh transports, and Orca writes the commit's exact blobs from ls-tree +
cat-file --batch. Symlinks, submodules, .git aliases, colliding portable
paths and SHA-256 repositories are refused. A size watcher stops the whole
Git process tree once the pack outgrows an approximate cap.

The exact argv lives in shared code and joins the real-Git compatibility
matrix, so the Git 2.25 lane runs it. Tests reach fixtures over an SSH
shim, since file transports are now refused.

* plugins: manifest v2 declares an execution profile

data < instructional < sandboxed-ui < trusted-worker. A v2 manifest is
strict and fails when it carries contributions beyond its profile; v1
manifests keep loading with the least profile inferred from what they
declare.

* plugins: pin exact catalog releases in orca-marketplace.v2.json

A v2 catalog entry carries the release it vouches for: commit, content
hash, version, profile, minimum Orca and contribution types. Preview and
install fetch that commit directly and refuse bytes that differ. The v1
file stays strict and frozen for shipped clients; v2 entries strip unknown
keys, and entries this client cannot install are dropped at parse time.

Catalog fetches read only the index blobs under their own download cap,
so a catalog repository is not held to one plugin's limits. Release-bearing
snapshots are cached under a new key, so an older build never reads them
and simply refetches.

readMarketplaceJson and parseMarketplaceFile from #30612 are replaced by
parsePluginMarketplaceIndex: catalogs are now read as Git blobs, so the
on-disk JSON read has no caller left.

* test(plugins): serve marketplace e2e fixtures over SSH

Plugin downloads now refuse the file transport, even through an insteadOf
rewrite, so the fresh-profile marketplace journey reaches its fixture
repositories through the GIT_SSH_COMMAND shim the unit tests use.

* test(plugins): give the Git compatibility plugin source its own identity

* test(plugins): record the plugin fixture's executable bit in the Git index on every platform
2026-10-28 13:36:49 -07:00
Brennan Benson f869c5580c fix(agents): preserve Kanban verdicts and AI Vault monitoring (#34649)
* fix(agents): preserve turn verdicts and monitoring in secondary views

* fix(agents): publish accepted native status changes to dashboard

* fix(agents): suppress equal rebuilt status notifications

* test(agents): explain proxy read forwarding in perf fixture

* test(agents): type production status fixtures completely

* test(agents): stabilize status fixture formatting

* Align web session test mocks with status transactions
2026-10-28 13:11:13 -07:00
Kelvin Amoaba 1f45c87c50 fix(dictation): say dictation is in use elsewhere instead of dictation_already_active on desktop (#35269)
Move the host dictation error map from mobile to src/shared so the desktop start toast and the phone read the same sentences.
2026-10-28 20:10:43 +00:00
Brennan Benson 7e0eca67c2 Keep saved folder workspaces under groups without a folder (#34652) 2026-10-28 13:09:13 -07:00
Brennan Benson 5f314d05c3 @file mentions and Quick Open keep up with typing in large repositories (#34820)
* perf(quick-open): rescore only the previous matches while a file query is typed

Each keystroke in a native chat @file mention or Quick Open ranked the whole
workspace listing (up to 20,001 paths) on the UI thread and rebuilt a set of
every path. A path that matches a longer term always matches its prefix, so
when the new query only extends the old terms, score just the old matches;
cache the listing's membership set per listing. Results are identical.

* refactor(quick-open): keep separator-variant matching beside the other separator matching

* test: cover mention ranking reuse and inventory replacement

* test: type mention query props for rerenders
2026-10-28 12:55:08 -07:00
OrcaWinandOrcaWin 6d745ad1b9 perf(explorer): bound deferred filesystem watch events (#34857)
* perf(explorer): bound deferred filesystem watch events

* test(explorer): drop duplicate watch buffer storm and GC retention tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 12:54:49 -07:00
Neil 80bbbaff7f refactor: give native chat readability one concrete owner (#35231) 2026-10-28 11:28:03 -07:00
Neil e03057de40 fix(native-chat): name a chat start that failed preparing its launch, with the host error in Details (#35186)
A start that failed on the host while Orca resolved or prepared the launch
(workspace or launch folder, command or account home, ACP prepare or temp
folder) reached the chat only as "Claude couldn't start" with no Details.
The adapters now mark that failure where it happens as the typed reason
launchPreparationFailed, and the host error is shown behind Details. A
reason already named there is kept, and a start withdrawn by Stop or close
keeps today's words. Older clients drop the unknown reason and kind.

Refs #34795
2026-10-28 10:47:58 -07:00
Neil 49695f37b1 refactor: group native chat subagent transcript rules (#35200) 2026-10-28 10:24:47 -07:00
Neil 66b2478224 fix(managed-server): let the update candidate name the daemon generations an old host cannot (#34908)
Hosts that predate the census protocol set report a null protocol whenever
terminals span daemon generations, so a forced update stayed refused there
even though the incoming build could reach every daemon. When the census
cannot answer, the candidate's profile preflight now lists the daemon
generations alive under the data root the server serves, passed to it
explicitly, and the planner checks attachability against that list. An
uncounted census still refuses, and any generation the probe cannot rule in
or out leaves the update unverifiable.
2026-10-28 09:58:12 -07:00
Neil 045f8c8266 feat(files): show images and PDFs from the byte-range preview path, local and remote (#35165)
* feat(files): show images and PDFs from the byte-range preview path, local and remote

ImageViewer and PdfViewer read an orca-media:// URL instead of base64 when
the editor opens a file: images load by URL, and pdf.js reads PDFs by
range, so a large PDF no longer arrives whole through files.readPreview or
pages into memory first. The grant mints only for the editor's viewers;
diffs, git blobs, markdown images and the paired web client keep base64.

- Minting an image checks its header with the base64 path's rule, so an
  image proven too large to decode safely never reaches the decoder.
- orca-media gains fetch and CORS privileges, and range responses expose
  Accept-Ranges and Content-Range, so pdf.js can read by range.
- Grants report the file size the viewer used to estimate from base64.

* fix(files): read PDFs by real range, check served images, keep old hosts' previews

- pdf.js fetches ranges only over http(s), so an orca-media URL was read
  whole; a small range transport now hands it bytes=a-b reads from the
  grant, and a failed range fails the preview instead of hanging it.
- A file can change after mint, so the raster header is checked again on
  the bytes each image request serves (the same handle locally).
- A paired host without files.readChunk falls back to files.readPreview
  for images and PDFs, as before; only audio and video need the update.

* fix(files): mint no local document grant for a renderer gone mid-mint

The local image/PDF mint is async now (authorize, open, header check), so
a renderer that closed meanwhile could leave a grant no teardown drops. It
binds the renderer lifetime first and refuses to mint once it's gone, as
the remote mint does.

* fix(files): keep the 50 MB cap for URL images, and probe remote headers incrementally

An <img> decodes the whole file, so image grants keep the base64 path's
50 MB cap; PDFs, now read by range, don't need one. A remote header probe
reads only each wider step's new bytes instead of re-reading from the
start. A replaced image of a different size resets its zoom.

* fix(files): tear down a PDF load whose range failed, and read 256 KiB ranges

A failed range only showed the error and left the pdf.js task, its worker
and pages alive; it now destroys that load first. pdf.js asks for one
readChunk-sized range at a time instead of 64 KiB, and a Range preflight
gets its CORS answer instead of 405.

* fix(files): read PDF ranges over IPC so orca-media needs no fetch or CORS

orca-media had gained supportFetchAPI and corsEnabled, and its responses
sent Access-Control-Allow-Origin: *, so pdf.js could fetch ranges. That let
any default-session frame holding a token read the bytes. pdf.js now reads
a PDF grant's ranges through fs:readPreviewRange, which main answers only
for the renderer that minted the token. The scheme is back to element-load
privileges (standard, secure, stream), responses carry no CORS headers, and
preflights get 405.

Grant bookkeeping moves to preview-grant-registry.ts. An e2e spec proves an
orca-media URL can't be fetched from the app page or read from another
session.

* ci(e2e): route the byte-range preview sources to the viewer specs that exercise them

Changes to the orca-media grant path, the image/PDF viewers and the PDF
range transport now run the PDF reload, rebuild and find specs, the remote
large-file open, media playback and the partition-isolation spec.

* fix(files): stop PDF range reads on abort, abort remote reads with the window, fix the CI route

- A closed or reloaded viewer schedules no further pieces of a merged range,
  and a remote range read stops when its window goes away.
- A URL without a size is an error rather than a fetch the scheme refuses.
- The PR guard job's sparse checkout and its admission test include the new
  route module, and the route now also covers the mint handler, preload
  bridge, editor surfaces, scheme registration and read client.

* fix(files): classify linked previews by their target, keep range reads off stopped WSL distros

A local image or PDF reached through a link is classified by the file it
resolves to, at mint and on every read, and a re-mint after the link is
retargeted gets a fresh grant of the new type. A paired host's document
whose first bytes are another kind defers to files.readPreview, which
classifies the target there. Lazy local reads go through the non-waking
WSL guard, as fs:readFile and fs:readFileChunk do.
2026-10-28 09:31:22 -07:00
Neil 74937b50aa refactor: group native chat transcript retention (#34761) 2026-10-28 09:22:44 -07:00
NeilandClaude 6ddcf1d453 feat(files): save a remote text file larger than one request (#35073)
* feat(files): save a remote text file larger than one request

The editor opened files up to 50 MB from a WSL host or SSH server but refused to save anything
over 720 KB. A staged save sends the content in chunks to a temp the host makes beside the
target, and the commit replaces the target by rename under the same locks and disk
precondition as files.writeIfUnchanged. Bounded per save, connection and host; idle saves are
dropped; a journal lets host start remove temps a crash left. Older hosts keep the refusal.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(files): stage large remote saves privately and commit them with the one-request save

Review of #35073: chunks now go to the profile's private staging folder, the commit
re-authorizes the target and writes it in place through writeLocalTextFileWithDiskPrecondition
(same locks, precondition and inode as a one-request save), ops on a save run one at a time so
an abort or commit never races an append, a closed socket's saves are aborted, and host start
clears the staging folder in the background.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(files): check a staged save's temp size at commit, and stage per process

Review of #35073: the commit refuses a temp whose size on disk isn't the declared total, and each
process stages in its own subfolder; the start sweep removes only its own folder and those whose
owner has exited, so runtimes sharing a profile never wipe each other's saves.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(files): a runtime without staged saves closes a socket cleanly

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-28 09:03:53 -07:00
OrcaWinandOrcaWin bc80bc59fb fix: retire extension iframe event listeners with native frame (#35011)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 08:42:02 -07:00
Vyacheslav ScherbininandNeil b654c3454a fix(persistence): stop rewriting migrated Compact profiles on every load (#34736)
The legacy Compact card-property migration returned the Compact preset even
when the stored list already was a current Compact preset, so every clean
reload of a Compact profile scheduled a full profile write. Treat both the
raw ['status'] and the normalized ['status', 'unread'] form (persisted by any
UI write) as already migrated.

Fixes #34828

Co-authored-by: Neil <neil@stably.ai>
2026-10-28 08:00:38 -07:00
Samuel VilaandNeil 3fd928373c fix(source-control): flag failed git status reads instead of showing a clean workspace (#28079)
When git status failed (e.g. a corrupted index), the read returned an empty
result and Source Control said the workspace was clean. getStatus now carries
Git's error as an optional readError; the store keeps the last known entries
and conflicts while it is set, and the panel shows a status-read banner with
Git's message and Retry instead of the clean empty state. The Retry logic is
shared with the too-many-changes banner, and the per-worktree error is cleaned
up on teardown and migrated on worktree rename.

Fixes #28033

Co-authored-by: Neil <neil@stably.ai>
2026-10-28 06:56:38 -07:00
Neil a7d9a10e4a feat(files): page a remote server's big folders in the explorer (#35115)
* feat(files): page a remote server's big folders in the explorer

* fix(files): list a remote folder whole unless the host refuses it as too large, then page it

* fix(files): page a remote folder after a cursor entry, not at an offset

* fix(files): release expired folder listings, share one in-flight listing, keep an oversized one alone

* fix(files): cap and time out cached folder listings, bound the cursor name, match id: selectors
2026-10-28 06:51:12 -07:00
Brennan Benson ab512bfe13 Show a chat's saved permission mode while its agent isn't reporting (#34546)
Chats with an explicit mode showed a disabled 'Permissions' placeholder whenever no running agent had reported, which is every idle or resumed chat, so the mode could not be read or changed. A live report still wins; chats that follow the agent's own settings still wait for the report.
2026-10-28 05:44:03 -07:00
Neil daf806bb41 fix(wsl-host): refuse Windows-only agent CLIs in phone, resume and fork launches (#35016)
* fix(wsl-host): give phone, resume and fork agent launches the Windows-only refusal

* fix(wsl-host): hint the Command setting on required-CLI refusals

* fix(wsl-host): refuse explicit resumes of a Windows-only CLI with the fix-it message
2026-10-28 05:15:44 -07:00
f231d1cde6 feat(files): play remote audio and video through one byte-range preview path (#35038)
* feat(media): read remote media ranges through the paired runtime

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(media): stream remote media through orca-media grants

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(editor): play video and audio from remote runtime workspaces

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(media): surface update-required for remote media and cancel in-flight chunks

Probe files.readChunk once per environment and pairing revision before minting a
grant so older servers show the update message. Closing a media stream now aborts
the in-flight remote chunk. Also validate the IPC address, fix extension-only
names, and tighten the preview return type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(files): one classifier decides which viewer opens a file

Local reads, files.readPreview on hosts, git blob previews and the desktop's
remote binary handling each kept their own image/PDF extension table, and
media had another. file-preview-kind.ts is now the one table: image, PDF,
audio or video, with the MIME type. AVIF, which only local reads knew,
previews on hosts and in git diffs too.

* perf(runtime): give byte-range reads their own small call lane

files.readChunk (media streams, paged files, downloads) now runs in a
two-call lane per environment, so a playing video or a download never holds
the slots terminal input and workspace calls use.

* feat(files): stream previews from paired runtimes by byte range

Generalises the remote media grant into one byte-range preview path: an
orca-media:// grant keyed by environment, worktree, path and pairing
revision, streamed over files.stat/readChunk. Audio and video use it now;
the shared classifier decides which viewers do.

- One files.stat at mint replaces the readChunk probe: it proves the host
  serves range reads and that the path is a file, so an old host or a
  missing file fails in the tab, not as a silent player error.
- A burst of range requests shares one stat (reused for 1s), so seeking
  costs one round trip less on a slow link; later requests re-stat, so a
  growing recording keeps playing.
- Removing a paired runtime revokes its grants; a re-paired one answers
  410 and drops the grant.
- Only readers that render bytes mint a stream; baseline checks don't.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

* fix(files): prove chunk reads at mint, and let a reload report the new size

Hosts from v1.4.5 answer files.stat but only v1.4.111 and later answer
files.readChunk, and no capability names it, so minting reads one byte,
cached per pairing, and an older host fails in the tab with the update
message. A re-mint seeds the grant with its fresh stat, so a reload never
reuses an older size. A renderer gone mid-mint mints nothing. Preview
chunks time out after 20s so a stalled one frees its lane sooner.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

* fix(files): resolve preview hosts in the pairing registry's own directory

The remote preview reader looked environments up under the canonical
user-data path, while the pairing IPC writes the registry under
app.getPath('userData'), which can differ in casing on case-sensitive
installs. Both now use one getter. A chunk-read verdict is cached only
when a pairing revision pins it, so it can't outlive a re-pair.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

* fix(files): read the pairing registry from one directory everywhere

Doc previews resolved paired servers under the canonical user-data path,
the same casing mismatch the media reader had. They and every other main
caller that names the registry directory inline now use
getRuntimeEnvironmentRegistryRoot(), so it has one source.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

* fix(files): pin an unpinned preview grant to the pairing it was minted on

A renderer that hadn't loaded the environment's pairing revision yet sent
none, and every later chunk read went unguarded, so a re-pair under the
same environment id would let the old URL read from the new host. Main now
pins the registered revision at mint.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

* refactor(files): route the remaining pairing-registry readers through the one getter

AI-vault host scans, the settings runtime-environment preference, skill
capability checks and the mobile relay host list named the registry
directory inline. They now use getRuntimeEnvironmentRegistryRoot() too;
callers that also use the directory for other storage keep their own path.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

* refactor(files): write new pairings through the same registry root

The ephemeral VM pairing writer named the registry directory inline; it now
uses getRuntimeEnvironmentRegistryRoot() like every reader.

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>

---------

Co-authored-by: Sahil Sharma <128581847+SahilSharma0810@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:08:55 -07:00
Neil f59bd43c46 fix(wsl-host): Remove keeps a WSL host's server that another Orca on this computer shares (R39) (#34699)
* fix(wsl-host): Remove keeps a WSL host's server that another Orca on this computer shares

A WSL host's server lives in the Linux login's ~/.orca, so two Orca installs on
one Windows account (stable and dev), or a desktop reaching the distro over SSH,
share one server. Remove used to close every terminal there and decommission
it, ending the other desktop's sessions and agents.

The server now answers host.peerDesktops (capability orcad.peer-desktops.v1):
the other desktops paired with it, whether each is connected now, and when each
was last seen. Before tearing anything down, Remove asks. If another desktop is
connected or was seen in the last 30 days, it only closes this desktop's bridge
and reports the server kept. If the server can't answer, the teardown is
refused. An older server without the capability gets today's teardown, plus a
warning.

* fix(wsl-host): a shared Remove releases this Orca from the host, and says the host keeps running

- On a shared server, Remove now revokes this desktop's own device (and its
  phones) on the host, through the new host.releaseDesktop, before letting go.
  Otherwise its recent row would make a peer's later Remove keep the server
  forever. Peers also skip rows of the caller's own installation (same caller
  fingerprint), such as an enrollment from before a re-add.
- The renderer says when a removal kept the host for another Orca, and the
  Remove confirmation no longer promises to end terminals unconditionally.
- Remove-path files now route CI to the fake transport e2e spec, which
  exercises Remove.

* i18n(wsl-host): Russian and Traditional Chinese for the shared-host Remove copy

* fix(wsl-host): tell desktops apart by device key, and release this Orca on every Remove

- Peers are no longer hidden by caller fingerprint. Every device-key fork of
  one pairing token row inherits that row's fingerprint, so two installations
  that paired through one code hid each other, and one's Remove could tear the
  server down under the other. Only a shared device public key now marks the
  same installation, as with a re-add under a new token.
- This desktop's own device is released as the last call over the bridge on
  every Remove that proceeds (no terminals left), not only a shared one. A
  later Add then Remove never meets a stale row of ours.
- When the release fails on a shared Remove, the toast says the host may keep
  running for a while. The Russian confirmation copy is corrected.

* fix(wsl-host): move undo refuses a removed host whose server another Orca kept

A Remove that kept a WSL host's server for another Orca (R39) now records
that. Before, undo of a move onto that host read the missing environment as
"its terminals and automations stopped with it", and would have restored the
project here while the host's copies kept running, duplicating its agents and
automations. Undo now reads such a host as unreachable and refuses, as it does
for any host it can't reach. A later Remove that proves the server stopped
clears the record. If the record can't be saved, the host stays listed as
pending instead.

* i18n(wsl-host): Turkish for the shared-host Remove copy

* fix(wsl-host): each WSL desktop splits off its own row from orcad's startup pairing

orcad publishes one startup pairing offer and never rotates it, so every
desktop that enrolled through it shared one token row and device id. Stable and
dev Orca on one distro were invisible to each other as peers, and releasing that
shared row broke the next re-add and pairing repair.

- New host.forkDesktop (same orcad.peer-desktops.v1 capability). A runtime
  device on a shared desktop token mints its own token row, forked from it and
  named by the caller. Forks share the device-key fork cap, and a released fork
  frees its slot.
- The WSL desktop forks right after enrolling through the startup offer, or
  after a repair lands back on it, and keeps the forked pairing (named after its
  userData folder, e.g. Orca or Orca Dev). On an older orcad, or if the fork
  fails, it keeps the shared pairing and warns.
- A token row that desktops forked from counts as a peer only while something
  is connected on it. One that nobody forked from keeps the last-seen rule.

* fix(wsl-host): keep host.forkDesktop to the startup offer's holder, over E2EE, with a clean label

host.forkDesktop mints a credential, so:
- only the holder of the desktop pairing offer this server advertised last
  (orcad's startup offer) may fork; an older offer, a fork, a phone or any
  other row is refused;
- the fork takes exactly the caller's grants and reach; params are just a label
  (strict, so a grant request is refused);
- it runs only on an authenticated E2EE socket; the token goes back only in
  that reply and is never logged;
- the label is display-only: control, zero-width and bidi characters stripped,
  whitespace collapsed, at most 64 characters; an empty result is refused.

* refactor(wsl-host): count only Orcas connected right now as WSL host peers; drop fork and release

Review found the token fork and self-release add more identity state than R39
warrants: orphan forks, a desktop left on the shared token releasing it, and a
changed caller fingerprint. So this drops host.forkDesktop, host.releaseDesktop
and the 30-day last-seen rule, and restores the restart test.

A peer is now another Orca installation with a control socket open on the
server right now. The desktop declares its installation (a hash of its userData
path) with host.declareClient on its shared-control socket after every ready,
including reconnects. The server tags that socket and drops the tag when it
closes. host.peerDesktops takes the caller's installation id, so its own other
sockets (multiplex, one-shot) never count, and two desktops sharing one startup
token stay distinct. Undeclared sockets, phones and browser sessions are
ignored. Peer names go through sanitizeDeviceLabel, and a startup-offer
"CLI <date>" name shows as "another Orca". An older orcad lacks the method:
nothing is declared, and Remove keeps today's teardown with a warning.

Limit: an Orca that's closed while its terminals keep running isn't protected.

* fix(wsl-host): keep a reconnecting Orca as a WSL host peer for 60 seconds

Lost contact isn't proof an Orca left. When an installation's last declared
control socket closes (a bridge exit, an orcad restart, resume from sleep,
reconnect backoff), the server keeps counting it as a peer for 60 seconds, or
until it re-declares. A Remove inside that gap no longer tears down its
terminals.

* fix(wsl-host): say an open Orca keeps the host, and name each peer once

- The Remove confirmation now reads "Unless another Orca that's open on this
  computer is using it", since only a connected Orca keeps the server.
  Updated in all 10 locales.
- The kept-host toast dedupes peer names, so several unnamed peers read
  "another Orca" once.

* test(wsl-host): pin the reconnect grace to the last socket's close, and to the server's own forget

- The unit test advances the clock between the two closes and checks that the
  peer still counts when the first close's grace would have run out.
- The integration test waits until the server has dropped the closed socket
  before reading peers, so it fails without the grace (checked by setting the
  grace to zero).

* fix(wsl-host): sweep expired reconnect graces on every close, on a monotonic clock

Expired graces are dropped at the start of forget() as well as peers(), so the
map stays bounded even when nobody asks for peers. The default clock is
performance.now(), so a wall-clock jump can't stretch or cut the grace.

* i18n(wsl-host): German for the shared-host Remove copy
2026-10-28 04:27:30 -07:00
0b6071aec4 fix(wsl-host): tell users how to fix a distro Orca can't host (D1c-1) (#33085)
* fix(wsl-host): tell users how to fix a distro Orca can't host

With the WSL bridge going away there is no fallback for a distro Orca's server can't
run in, so each refusal now names the step that fixes it:
- WSL 1: the wsl --set-version command, also shown in the Add host form, where the
  disabled option could never show it.
- An unregistered distro, at Add and at connect: how to list installed distros.
- NixOS's stub loader: turn on nix-ld.
- glibc below every runtime's floor: the minimum release to use.
- A WSL distro with an unknown platform no longer reads as "this SSH host".
Connect refusals other than WSL 1 report as 'unsupported-distro' instead of reusing
'unsupported-wsl1'.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(wsl-host): show only the plain message when a WSL host can't connect or be added

* test(wsl-host): fold the plain-message case into the existing readIpcErrorDetail tests

* fix(wsl-host): keep SSH's old-glibc and NixOS refusals as they were; add the fix only for a WSL distro

The glibc and nix-ld hints were appended in code SSH hosts share, so SSH users
saw new wording with the WSL hosts gate closed. The shared messages are back to
main's text; the WSL add flow adds the fix to its own refusal.

(cherry picked from commit c7a07b61c440e87652ca956570b8b91492c2a375)

* refactor(wsl): map a missing distro in the unsupported-distro module, keeping orcad deploy under its line cap

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 04:17:40 -07:00
NeilandClaude 9e1443ecf2 fix(wsl-move): undo connects the WSL host after a relaunch, and startup heals a crash mid-pairing save (#33279)
* fix(wsl-move): undo connects the WSL host after a relaunch, and startup heals a crash mid-pairing save

Undo is the user's act, so it connects the host the way Connect would before asking it anything;
without that, a relaunched host with no open work refused every undo as unreachable.

A crash between refreshWslHostPairing's two writes leaves the record's bridge port off the saved
pairing, and the overlay then drops the WSL link. Startup realigns the record to the pairing.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(wsl-hosts): startup realigns bridge ports before any bridge connects, and not while gated off

Co-Authored-By: Claude <noreply@anthropic.com>

* test(wsl-move): undo's terminal stop never connects a host whose Remove is pending

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-28 03:35:39 -07:00
OrcaWinandOrcaWin fe3eb45b85 feat(i18n): add German UI language (#34917)
* feat(i18n): add German UI language

Register German (de) as a built-in UI language alongside en, zh, zh-Hant,
ko, ja, es, fr, pt, ru and tr: language setting, system-locale detection
(de, de-DE, de-AT, de-CH, de_DE.UTF-8), renderer and main-process lazy
catalogs, Settings picker label in every catalog, the "Sprache" settings
search keyword, onboarding picker, docs and a German glossary and style
guide (config/i18n-german.md).

de.json covers every en.json key except the sparse-policy entries that must
fall back to English (status-bar credit balance copy and the incomplete goal
change notice). Tests that used de-DE as the unsupported system language now
use it-IT.

The catalog is machine-translated, reviewed by two independent passes with
arbitrated fixes, and swept for terminology consistency.

* feat(i18n): translate German strings added on main

Translate the 158 keys main added since the branch point (WSL project move,
diff size limits and others), reviewed by two passes like the rest of the
catalog, and drop the 5 keys main retired.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 03:21:12 -07:00
Neil 8377149070 Preserve terminal exit queries across snapshot restore (#34723)
* fix(terminal): preserve Codex exit queries across snapshot restore

* fix(terminal): replay owed queries from discarded output and superseded replays

- Mark yielded queries discarded when ACK overflow trimming, the recovery
  cutoff, or snapshot-buffer overflow drops their output unsent, and replay
  them after the covering snapshot (including the ACK recovery snapshot).
- Purge snapshot query replays and escape-tail frames still queued behind the
  ACK window when a newer snapshot is published, and put new replays at the
  head of the ACK queue so they follow their snapshot.
- Share isPrimaryDeviceAttributesQuerySequence across DA1 call sites.
- Move replayed-query settlement into settleReplayedYieldedQuery.

* fix(terminal): replay DA1 still batched when an ACK recovery snapshot covers it

* test(terminal): type the overflowed initial-snapshot stream fixture

* test(terminal): give the host-chain recovery stream fixture its required query and batcher state
2026-10-28 02:31:00 -07:00
Neil 0cca424b51 feat(wsl): admit no new WSL host or move in serve-only (kill switch 2/2) (#34694)
* feat(wsl): admit no new WSL host or move in serve-only

Main refuses wslHosts:add while the internal gate is closed and reports
acceptsNewWork in the WSL host capability; moves already need the open gate.
The renderer hides Add WSL distro and every Move entry point once main says
serve-only, while saved hosts keep their Connect and Remove.

* fix(wsl): offer no new WSL host or move until main answers the capability

The renderer's acceptsNewWork now starts false, so Add WSL distro and the Move
entries never flash in serve-only before the capability reply.

* fix(wsl): point the WSL-bridge chat refusal at Add WSL distro only when it is offered

In serve-only Add WSL distro is hidden, so the refusal keeps its terminal-only
words there.

* test(wsl): give the move status-line test's hosts mock the new-work hook

#34676's test mocks use-wsl-hosts-enabled; WslHostsSection now also reads
useWslHostsAcceptNewWork, which follows the same gate there.
2026-10-28 02:15:57 -07:00
Neil 21ad2d9dc5 fix(wsl-mover): settle an interrupted move automatically, and hold the host's server update during a move (#34823)
* fix(wsl-mover): settle an interrupted move automatically, and hold the host's server update during a move

An app update or crash between sending a move's copy and the host's commit leaves a
`blobs-transferred` record. Startup recovery has no host to ask, so the projects stay fenced, and
the automatic mover only previewed, which refuses an in-flight move, retrying forever. The
projects stayed read-only until the user started the move from the dialog. The automatic mover's
preview now settles that record first (through the host: back to zero, or forward when the host
committed), holding the distro's run while it does so it never races a move the user starts. The
dialog's preview stays read-only. The preview moves to its own module.

A Settings Update or Force, or any reconnect, could also redeploy the host's server in the middle
of a move, cutting it off (safe, but the move is lost). Once a running move has journaled, a
version-changing redeploy now keeps the running server and reports the update as waiting for the
move, mirroring the SSH path's skip while migrating. The move's own first connect, before it
journals, can still update an outdated server.

* fix(wsl-mover): an automatic settle never reloads the window, and never holds its own host update

Review of #34823:
- A settle that finds the host committed ran the desktop rewrite, which reloads the window past
  its unload veto. From the automatic mover it now records the commit only (the projects stay
  fenced), and the next start's rewrite pass finishes it before any window opens. A move the user
  starts still finishes at once.
- The server hold counted any unfinished record of the distro, so an automatic settle of an old
  record with an outdated server was held, refused for an update, and retried the same way. The
  hold now counts only the cutover the running move journaled itself.

* fix(wsl-mover): let the user finish a committed move in-session; park the automatic mover meanwhile

After an automatic settle records a commit for the next start, nothing could finish it in-session:
the user's Move refused as already in flight and the orchestrator retried for nothing.

- A settle that finds the head committed but not yet rewritten on this desktop finishes it now when
  the user asked (their Move, or the status line's Retry), returning the existing "finished an
  earlier move" answer; the automatic mover still leaves it for the next start.
- The automatic refusal carries detail `finish-pending`, which parks the distro (new reason
  `finish-pending`) instead of retrying; the next start, whose rewrite pass finishes the move before
  any window opens, puts it back to pending.
- The park rules move to wsl-move-orchestrator-park.ts.

* fix(wsl-mover): give the finish-pending park its own status line

Main's move status line (#34676) names every park reason; the finish-pending reason this PR adds
needs one: the earlier move is done on the host, and finishes here at restart or with Check again.
2026-10-28 02:10:24 -07:00
Brennan Benson fa5284aca2 feat: edit queued messages inline with the full chat composer (#31180)
* fix(native-chat): a refused queued message still blocks the queue when its card write fails

Every reader of a queued card goes through one derived view of the journal's dispatch facts,
so a card whose message was refused reads as returned even when its card-table update and heal
both failed. One decision and one writer store the derived state for every action.

* refactor(native-chat): keep each queued message in the chat's journal, not a second table

A queued message lived in its own SQL table, and a hook that may be skipped copied
each refusal from the journal into it. When that write failed, a refused card
vanished: the cards behind it sent, and its Send and Delete did nothing.

Each queued message is now a `queue` row in the chat's journal beside the
submission that hands it off and the dispatch row that answers it. The reducer
folds the card from those rows, so a refusal returns the card and holds the
cards behind it with nothing written, and replay reads the same. Every action
writes its own rows in one transaction. Cards an earlier build stored are read
through the same classification until they move: each action moves its own
card, and a bulk import runs once per open. A new epoch restates every card
still owed an answer in the transaction that deletes the old rows.

The settlement hook and its savepoint, the open-time heal, the drain's heal and
the derived-card view are gone. The published card list is unchanged.

* test(native-chat): adapt the queue tests to cards kept in the journal

Cards are written through the journal, so tests queue them with their body
alone and hand them off under their real fingerprint. Tests of the removed
settlement hook, heal and SQL store are deleted; the text-update spec now
covers the edit decision alone, and a test of an earlier build's stored holds
seeds the legacy table and reads the imported cards. Rolling over an epoch now
keeps a card whose hand-off was never handed over, so that test checks the
retried id never sends rather than that nothing sends.

* test(native-chat): a refused card holds the queue through rewind, a new epoch and a restart

One whole-flow test through the host: the refusal writes no card row, a rewind
that retires the refused hand-off and a rolled-over epoch keep the card
returned, a restart and Resume send nothing behind it, its own Send refused
again still holds the queue, and its Delete releases the next card in order.

* test(native-chat): an earlier build's queued cards move into the journal once, each read the same

One mixed-import test through the host. The first open's import fails and
moves nothing, yet every card reads as it will; Delete and Send each move only
their own card. The next open moves the rest in one transaction, writing a
refusal only where its hand-off is gone, nothing for answered cards, and leaving
an unreadable row on disk. A later command turn spends a moved command card, a
third open writes nothing, an old Delete replays from its receipt, and a chat
with only answered legacy rows clears them in a delete-only transaction.

* test(native-chat): adapt typechecked tests to cards kept in the journal

The row-kind table lists a queue row, the clear and legacy-import tests
match the current signatures, and the downgrade test keeps only the
leftover-send case: a queued card is now a newer-version journal row an
older build keeps read-only, and users don't downgrade.

* refactor(native-chat): drop the journal row writer's unused transaction hook

No caller passes one since the queue's savepoint bookkeeping left.

* test(native-chat): downgrade probes write rows at the version older builds read

The highest version this build reads is now 4, the queue row's. A newer
build's row in these probes is written at version 3 so the older build
meets its kind, not a newer version, and the Stop-event probe hands the
older build the rows as stored, since a read raises each row's version.

* test(native-chat): unit tests follow the queue row's version 4

The legacy-import test opens real SQLite, so it runs in the Node runtime
project. The newest version this build reads is now 4, and a read raises a
row's version to it, so the turn-lifecycle test no longer pins the
in-memory version (the written one stays pinned by journalRowSchemaVersion).

* fix(native-chat): one batch echoing two refused queued messages spends both

The per-row delivery check returned only the first waiting card an echo
proved delivered, so when one settlement carried echoes for two cards the
second stayed waiting and was sent again. It now returns every card the
row proves delivered, and the fold withdraws each; replay folds the same.

* fix(native-chat): a rewind moves an earlier build's cards into the journal first

While the open's import had failed, an earlier build's card that read as
refused through its hand-off lost that reading when an in-place rewind
retired the hand-off: it disappeared and the card behind it sent. The
rewind now adopts every legacy card ahead of its tombstones and deletes
their rows in its own transaction, as an epoch replacement already does,
so the fold latches each card's reading before its hand-off is gone.

* fix(native-chat): keep an earlier build's card whose hand-off is still unconfirmed

The import dropped a legacy card handed off as a send whose delivery was
unknown, though the fold keeps such a card linked and a later refusal
returns it. The import now keeps every card whose hand-off a later row can
still answer, through the same check the reducer folds dispatch rows by.

* fix(native-chat): one delivery echo withdraws at most one queued card

One provider echo proves one delivery, yet it withdrew every waiting card
with the same text whose hand-off was refused, so a second identical card
was lost. The fold now pairs each echo with one card: on its own row the
earliest-positioned match no other echo of that row took, and for a card
that waits again the earliest applied echo no card has taken. Paired echoes
live in the fold's state, so live and replay pair the same; readers of
cards not yet imported work on a copy.

* fix(native-chat): a queued message handed to the agent is sent again only on proof it never got it

Pressing Stop after Orca handed a queued or steered message to the agent,
before the agent confirmed it, put the card back in the queue, and Resume
or the next send sent it again. A check matching the agent's echo of the
text was the only guard against a double send, and it kept trading a
resend for a lost card. Whether the card waited again was decided by the
rejection's category, not by proof the agent never got the message.

A refused hand-off now waits again only where Orca proves the agent never
received it: the host still held it, or whoever settled it recorded that.
A failure still returns the card and holds the cards behind it. Anything
else may have reached the agent, so the card is spent and its message
reads as sent, derived on every fold from the stored rejection: with the
turn it was answered into, as that turn's unconfirmed message where it was
handed over; withdrawn before any turn opened, as a person's own send a
Stop took back. The reopen mark ignores a recovered doubt, and the restart
reconciler no longer judges a queued hand-off unsent from what history
lacks. The echo text matching and its pairing are deleted.

A close or quit while the agent starts settles sends with the unrun hold.
The host hands nothing to a child before its start answers
(structured-agent-session-delivery-loop.ts: a handover needs phase
ready), so such a send went to an earlier child and may have run: it now
reads as sent instead of waiting again.

* fix(native-chat): Claude's report that it removed a queued message unrun lets the card wait again

Claude says when it removed a send without running it: the interrupt
receipt's cancelled list, or a command_lifecycle cancelled, discarded or
refused frame for a command that never started. Every one settles through
rejectClaudeDispatchWaiters, which now marks the settlement unrun. For a
queued card's hand-off the host records that as proof the agent never got
it, so the card waits again under the queue's pause. A direct send keeps
the plain refusal: there the proof would owe it as stopped history.

* fix(native-chat): an earlier build's card stopped after hand-over follows the same rule on upgrade

Earlier builds put a card back to waiting after any refusal without a
verdict and cleared its link, so a card the agent may already have would
be sent again after the upgrade. The card's own hand-offs in the journal
decide now: unchanged and last sent as one that may have reached the
agent, it is spent on that hand-off and its message reads as sent; edited
or sent again since, it is held for its own Send under a host-only marker
that publishes as a plain hold; with no hand-off in doubt, including one
that never left Orca, it waits as before. The import writes a card spent
on a hand-off in doubt, so its message shows.

* fix(native-chat): read a queued row's hand-off before the lookup that uses it

The lookup's callback lost the narrowing of the row's change, which the
mobile test typecheck rejects.

* test(native-chat): a card steered into a turn a Stop ends stays sent in the Stop tests

Three tests pinned the old rule, that a card handed into a running turn
comes back to waiting when a Stop ends it. Under the new rule nothing
proves the agent never got it, so the card is spent: with the turn named,
its message reads as that turn's unconfirmed one; without, as a send the
Stop took back. Neither is ever sent again.

* fix(native-chat): a queued message stopped before Orca handed it to the agent waits again

A Stop that aborts a hand-off after its row is written but before the agent call now records
the withdrawal as never reaching the agent, for queued hand-offs only, so plan D's rule returns
the card to waiting under the Stop's pause instead of spending it. Direct sends keep the plain
withdrawal (the flag would owe them as stopped history). The agent's own refusal while aborted
is kept rather than overwritten, and an abort thrown during the call stays in doubt.

* fix(native-chat): a queued message stopped while the agent prepares it waits again

The abort signal reaches only an adapter's preparation (its options, the previous turn opening),
never its write, so the signal's own reason thrown back is the adapter saying nothing was
written. The handover now reads that, for queued hand-offs only, as never reaching the agent,
so the card waits again under the Stop's pause. Claude no longer wraps that abort as an option
refusal, which had returned a stopped card as a refused one blocking the queue. A real refusal
keeps its cause and its barrier; any other throw stays in doubt and is never resent.

* test(native-chat): the usage-limit Continue backlog case writes its card through the journal queue

Main's #30396 wrote its backlog card with the stored fingerprint and host-instance stamp the
journal queue derives instead; the card the Continue must yield to is the same waiting card.

* test(claude): the real-CLI queued Stop expects the unrun mark Claude's cancelled-unrun receipt carries

* feat(mobile): edit a queued message's images on the phone: remove or attach

* fix(mobile): gate queued image edits on the host reader verdict

* test(mobile): declare the upload fixture chat mode

* fix(native-chat): retain spent legacy messages when import fails

* fix(mobile): reveal the full queued image editor on open

* fix(mobile): invalidate queued editor geometry without a render ref write

* refactor(chat): prepare composer drafts and measured queue dragging

* feat(native-chat): add conditional queue removal and authoritative reordering

* feat(native-chat): move queued edits into the normal composer on desktop and phone

* fix(native-chat): own complete phone drafts before authoring

* fix: preserve submitted composer drafts across storage replacement

* fix composer validation and mobile CI diagnostics

* fix mobile composer test contracts and diagnostic output

* Capture mobile test compiler diagnostics in CI

* Fix queued message contract typing and English strings

* test: complete queued extraction transport fixtures

* refactor: separate composer skill binding from send mutations

* fix(mobile): grant draft storage within route limits

* fix(native-chat): settle each draft save on its own and publish complete composer drafts

Phone: a queued Edit copy no longer consumes the save that later typing
owns. Each ordinary save and the exact copy settle only their own result;
an explicit save waits for its own pending write, and a failed or retired
write never schedules another or revives the copy.

Desktop: a pasted-text leaf no longer shifts which selected file binds
where, and a hand-back held during an IME composition reaches the store
only merged with the visible editor document, including writes the editor
admits without an update and the composition settle.

* fix(native-chat): import the draft type once in the draft cache

* test(mobile): type the held-write state without an assertion

* test(native-chat): assert the handed-back file on Send without reading a typed transport as a mock

* test(mobile): pass main's agent-confirmed flag to the submitted-files harness

* fix: reveal queued reorder grip on touch screens

* fix: save reopened phone drafts and batch reorder measurements

* test: use an explicit React module type

* Preserve queued message sortable identities on unchanged renders

* Prevent stale queued drag completion after unmount

* fix(mobile): hand off navigation across session workspaces

* test(mobile): use resolved session identity in dictation fixture

* feat: edit queued messages inline with the chat composer

* fix: normalize queued edit skill ownership

* fix: retire phone skill selections with the sent draft

* fix: consolidate queued edit module imports

* fix: separate edit save action and stabilize image callbacks

* fix: complete phone inline composer integration

* fix: retain same-name queued skill selections

* fix: complete desktop queued composer integration

* test: use typed phone composer host lookups

* fix: include queued edit localization keys

* Preserve inline edit input across save and attachment completion

* Preserve complete queued edits across delayed saves

* Preserve queued edit input across delayed completion

* Complete queued edit lease loss and pending cancellation

* Use recognized queued edit test fixtures

* Consolidate queued edit imports and type host test spy

* Print mobile test compiler diagnostics on ratchet failure

* Correct queued edit integration fixture types

* fix: keep queued edit placeholder independent of readiness

* Fix queued edit compiler fixture boundaries

* Align upload completion test with ordinary insertion

* Combine mobile composer draft imports

* Load queued edit host fixture during test collection

* Size inline phone queue editor to available space

* Use typed scroll components in phone presentation contract

* Save typed text in a queued edit as written

The inline editor is the chat composer, so it keeps the composer's own identity model and no longer refuses Save for text that looks like a command. Command rows picked from the menu are still refused, Enter still means Save, and Edit never sends or runs anything.

Regression tests cover Undo after deleting an accepted command or skill pill, a scanned skill named like a command, two saved files behind one token, and typed command text saving literally. Also fixes two merge leftovers in tests: a missing import and a fixture that stored the whole menu row as a skill.

* Keep ordinary sends on the main composer contract

* Fix mobile composer imports and fixture loading

* Consolidate queued edit imports and duplicate regression

* Restore ordinary send catalog short circuit and repair test boundaries

* Preserve returned rich drafts through send acceptance

* Remove obsolete queued editor strings from new locales
2026-10-28 02:09:49 -07:00
Neil d6918c7a1d fix(remote-files): cap search replies, say when a diff is too large, and measure a 100 MB read on WSL (#34882)
Follow-ups from the large-file audit (E7):

- files.search had no byte budget: a result past the remote reply cap failed outright with
  reply_too_large. It now keeps the whole files that fit and marks the result truncated, as
  files.listAll and searchPaths already do. Local callers stay uncapped.
- A diff side past the 10 MiB read cap came back as binary, so the diff said "Binary file
  changed" for a large text file. Blob reads mark it `tooLarge`, the binary diff result carries an
  optional `tooLarge`, and both diff views say "File too large to diff". Older clients ignore the
  field and keep the binary view. buildDiffResult builds its binary sides without a cast.
- The real-WSL lane pages a 100 MB file over the plain distro's shared control socket in the
  editor's 384 KiB chunks and records the time, chunk count and orcad's VmHWM. Record-only: it
  asserts nothing, and a failure is recorded in the receipt.
2026-10-28 02:01:16 -07:00
780ab3d71d fix(bitbucket): canonicalize ssh.bitbucket.org remotes in review links and Gitea detection (#30808)
Fold every forge SSH host alias (ssh.github.com, altssh.gitlab.com,
ssh.bitbucket.org) through one shared normalizeForgeWebHost so main-process
provider detection and renderer manual review links agree. Remotes on
ssh.bitbucket.org now open bitbucket.org pull request forms instead of
ssh.bitbucket.org URLs, and Gitea no longer claims the host.

Co-authored-by: Eric Junker <4758+ejunker@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-28 01:57:10 -07:00
Mac kingsleyandNeil 1a0ce3a55c feat(editor): highlight Groovy files and Jenkinsfiles (#26792)
Register Groovy as a lazily loaded TextMate language using Shiki's bundled
grammar, and map .groovy/.gvy/.gy/.gsh/.gradle, Jenkinsfile, Jenkinsfile.*
and *.Jenkinsfile (plus the groovy shebang) to it. Token scopes are mapped
onto ids the stock Monaco themes already style.

Fixes #25635

Co-authored-by: Neil <neil@stably.ai>
2026-10-28 01:50:41 -07:00
Neil bd4702fbb5 fix(managed-server): let a forced update or rollback cross mixed daemon generations (#34664)
The terminal census collapsed sessions spread over more than one daemon
generation to a null protocol, so a host that kept a legacy daemon alive
across an earlier update refused every forced update and rollback while any
terminal ran. The census now reports every live protocol, and the crossing
check requires the incoming build to attach each one. The single-protocol
field stays for older desktops.
2026-10-28 01:40:40 -07:00
Brennan Benson 612febdadd Open existing agent chats and retained panes from their rows (#34650)
* fix(agents): open existing chat and retained pane destinations

* fix: keep agent navigation scoped to the current destination owner

* test: initialize dashboard tabs without a type assertion

* fix: complete navigation test and English catalog contracts
2026-10-28 01:39:00 -07:00
HopkinsandNeil cd1fe07182 feat(kimi): add K3 to the commit-message model picker (#11386)
Adds kimi-code/k3 (Kimi K3) to the static Kimi model list, which is also
the allowlist, so planning with K3 no longer fails with "not available".
Drops the test that only restated the catalog.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-28 01:38:47 -07:00
dojinekoandNeil 85ea1cc4c5 feat(browser): add Vivaldi as a Chromium cookie-import source (#11449)
Detect Vivaldi profiles on macOS, Windows, and Linux and import their
cookies like other Chromium browsers. On Linux, Vivaldi stores its
cookie key under Chrome's libsecret application name, so the browser
definition can override the keyring application used for the lookup.

Co-authored-by: Neil <neil@stably.ai>
2026-10-28 01:28:04 -07:00
NeilandManan Chataut 96cd21bbc4 Keep remote agent status in sync after completion and approvals (#34729)
* fix(remote): reconcile stale paired agent status and pane titles

Adapt the remote completion repair from #25205 while preserving sparse runtime pane slots through existing PTY bindings.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(remote): preserve pane identity when reconciling moved titles

Use mounted manager identities and retained PTY bindings ahead of replay order; keep fresh byte-stream ownership on the correct moved pane.

* fix(remote): release byte status fence after host approval

* fix(remote): tighten paired title removal and test the timing fence

- Share the byte-write timing decision (resolveRendererOwnedAgentStatusTiming) between pty-connection and the flap test so reverting it fails a test.
- Only delete a saved pane title when its durable leaf is absent from both the host layout and local panes; unresolved or surface-less slots are kept.
- Share the client-fence predicate (clientAgentStatusFencesHost) between status and title reconciliation.
- Cover removed-leaf spinner fallback in worktree status and a client split absent from a pre-split host snapshot.

* fix(remote): correct fence predicate and title test types

---------

Co-authored-by: Manan Chataut <mananchataut@gmail.com>
2026-10-28 01:19:22 -07:00
Agustin VarelaandNeil d20791e522 fix(session-search): read --since as an instant, whatever offset it carries (#28329)
Hosts filter with `updated_at >= ?`, comparing text against UTC
toISOString() values, so a `+08:00` cutoff was read as if UTC and a
whole-second `...00Z` cutoff dropped sessions in that same second
(`Z` sorts after `.`). The cursor key also hashed the raw spelling.

Normalize `since` to toISOString() in AiVaultSearchFiltersSchema. Every
client and host path parses this schema, so new clients send a cutoff
older hosts already compare correctly, and new hosts normalize whatever
older clients send before filtering or building the cursor key.

`Date` keeps milliseconds only, so a cutoff like `...00.0005Z` rounds up
to the next millisecond rather than letting in a session from before it.

Fixes #28253

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-28 01:16:02 -07:00