* 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
* 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>
* 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
* 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
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
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.
* 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
* 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
* 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
* 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
* 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
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
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.
* 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.
* 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>
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>
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>
* 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
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.
* 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
* 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>
* 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
* 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>
* 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>
* 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>
* 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
* 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.
* 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.
* 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
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.
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>
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>
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.
* 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
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>
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>
* 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>
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>