* perf(i18n): load built-in catalogs once, without copies or a runtime plural fill
Each catalog load parsed the JSON, rebuilt it to fill CLDR plural forms
from _other, then deep-copied and deep-merged it into the store (about
26 ms after parsing per 1.3 MB catalog), and concurrent loads of one
locale fetched it twice.
- sync:localization-catalog now commits the plural forms a locale's
CLDR rules need beside each _other (fr/es/pt many, tr one, a few
de/ru), and verify:localization-catalog fails when one is missing.
Plural forms may include {{count}} where English omits it.
- Renderer and main load built-in catalogs as-is; plugin packs keep the
runtime fill.
- Renderer catalog requests are shared per locale; a timed-out request
is dropped so a retry fetches again. Bundles are added with skipCopy.
* fix(i18n): require {{count}} in plural forms when English shows it
* chore(i18n): sync plural forms for catalogs merged from main
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): show live input caret and preserve selection edits
Display the existing input directly so its native caret shows. Accessory
Backspace/Delete edit at the field's caret, read from the input when pressed,
so edits that keep the caret (IME composition) still delete, and the caret is
restored for subsequent typing.
Port and adapt community PR #28294 to the current command dock.
Co-authored-by: Kyino <kyino1025@gmail.com>
* fix(mobile): keep live input caret keys local and the dock height fixed
Hardware Left/Right/Home/End moved both the visible field caret and the
PTY cursor, so the mirror erased at the wrong place. With field text they
now move only the field caret, like Backspace and Delete; Right/End at the
field end still reach the PTY so shells can accept an autosuggestion. On
Android the key binding lets plain caret keys through to that decision
instead of sending them to the PTY. The dictation and upload status covers
the field instead of adding a row, so starting or stopping either no longer
resizes the terminal.
* test(mobile): annotate the DOM field cast in the accessory input test
* chore(mobile): allowlist the live input field strings for localization coverage
---------
Co-authored-by: Kyino <kyino1025@gmail.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix: allow dismissing workspace auto-name failures
* fix: retain auto-name failures until dismissal is saved
* refactor: use workspace selectors for metadata state updates
* fix(extensions): bound outstanding native-host writes
* fix(extensions): evict the largest stalled native host and allow two queued frames per host
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Load the Hermes plural-rules polyfill from the phone app entry instead of
mobile-i18n, and transform mobile-i18n and its catalog loader without the
Expo tsconfig in root vitest.
Moves ~600 strings in the shared components (modals, drawers, host cards, dictation,
accounts), the pull request sidebar and diff review to the translation API, with all
ten translations.
- Invalid account snapshots are detected by error type, not by matching English text
- Credit expiry wording uses separate keys for one and several credits instead of
plural rules, which would pick the wrong sentence in Japanese or Russian
- The PR chip memo recomputes on a language change
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Moves ~600 strings in the root screens, settings, pairing, onboarding, accounts,
agent history, diagnostics, connection status, home and host workspace lists to the
translation API, with all ten translations.
- Connection verdicts carry an explicit `dialing` flag instead of matching the
"…" ending of an English label
- Update-failure copy and connection-path accessibility labels are built from data,
not by editing translated text
- Workspace status names stay the host's labels, as on desktop; transport errors
stay English
- The stale-key check keeps a plural family while any of its forms is referenced
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Output stays buffered per task, so a long unit-test step looked hung for
30+ minutes. Print each task's completion, a 60s heartbeat naming running
tasks, and kill any step after 30 min (--timeout=<minutes> overrides).
Co-authored-by: Claude <noreply@anthropic.com>
* feat(mobile): add UI localization infrastructure
Mobile follows the phone's language (or an in-app choice under Settings > Appearance)
across the same 11 languages as desktop, switching live like the theme does.
- i18next with desktop's translate(key, fallback, options) call shape; catalogs in
mobile/src/i18n/locales load lazily, English fallbacks stay inline
- Hermes lacks Intl.PluralRules: load the FormatJS polyfill with the 11 languages' rules
- OTA pages get the shell's language through an optional bridge init field
- iOS permission prompts localized; iOS and Android 13+ per-app language pickers
- verify:mobile-localization and a coverage ratchet run in lint, check:changed and CI
* fix(mobile): mount pages after the newest language request; tighten localization gates
- Wait for the latest page language activation, not a superseded one
- Keep literal fallbacks when a key also has a dynamic one
- Count headerTitle as visible copy; select the mobile check when the runtime catalog generator changes
* feat(mobile): repaint themed components on language changes
Components that read the theme already subscribe for repaint; a language switch
repaints them too, so labels built by helpers during render follow it. RPC
recordings load the real i18next and JSON catalogs.
* fix(mobile): scope the translate() gate to render bodies and memos; prune stale keys on sync
Handlers and effects may call translate() when they run. sync:mobile-localization now
removes keys no code references from every catalog.
* fix(mobile): use node: imports in the locale config plugin; pass uiLocale in bridge hook test
* chore(mobile): refresh the localization coverage baseline
* fix(mobile): follow render callbacks in the translate() gate; keep used plural siblings on prune
Also let anonymous default-exported screens count as components, flag any plural
family a locale started, and mark JSON modules as ES modules in the recording loader.
* fix(mobile): treat async and named local callbacks as handlers in the translate() gate
* chore(mobile): refresh the localization coverage baseline
* chore(mobile): refresh the localization coverage baseline
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): localize plural counts, sentence fragments and hardcoded copy in editor, chat, skills, stats, onboarding and tips
Refs #32207
* chore(i18n): resync runtime English catalog after merging main
* test(i18n): let mocks interpolate counts and keep real i18n exports
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): localize sidebar, status bar, tab bar and workspace cleanup copy
Replace fragment-concatenated sentences, separate singular/plural keys and
raw English in the S2 renderer areas (sidebar, status-bar, tab-bar,
tab-group, workspace-cleanup, new-workspace, activity, dashboard,
terminal-pane, cmd-j) with whole-sentence keys and CLDR plural families,
and add translations for all built-in locales.
Refs #32207
* fix(i18n): subscribe DashboardAgentRow to UI language changes
* test: keep real i18n exports in SmartWorkspaceNameField mocks for Trans rows
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Replace sentence fragments, manual non-family plurals, item(s) copy and
hard-coded English in the right sidebar, GitHub item dialog, PR page,
GitLab item dialog and diff-comment surfaces with whole-sentence keys and
CLDR plural families, and add translations for all built-in locales.
Refs #32207
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Nest chat-UI orchestration workers under the agent that started them
Main's lineage projection only walked terminal panes, so workers running
in the chat UI never got a parent link and listed flat in the sidebar.
It now also projects chat workers, keyed by their visible chat row, and
resolves a chat creator or coordinator to that chat's visible row
instead of its handle or private mailbox pane key.
* Fix public worker lineage across assignment and row identities
* Complete terminal lineage provenance and bound active session selection
* Repair lineage test runtime boundaries and SQLite counters
* Batch public lineage verification after exact recency selection
* Use checked runtime values in lineage integration tests
* Document the trusted Dispatch SQL row boundary
* Keep lineage integration imports within their test programs
* Load native lineage fixtures before test execution
* test: complete lineage inputs in runtime and sidebar fixtures
* test: seed benchmark facts from active dispatches
* test: replace partial orchestration DB casts with typed fixtures
* test: complete stale-leaf mailbox fixture session input
* fix(test): render worker lineage fixture with production list
* fix(mobile): restore lifecycle-safe terminal accessory repeat
The selection hook replaced the established repeat controller with unbounded timers, allowing held keys to cross terminal/client changes. Reintegrate the existing pinned sender, lifecycle cancellation and backpressure.
* test(mobile): stop held terminal input after refusal or disconnect
* test(mobile): give accessory-repeat harnesses the PTY modes and focus hook main now reads
* fix(mobile): stop a held key when its strip unmounts and repeat through the current live-input mode
* fix(mobile): type the accessory repeat sender's input
* refactor(mobile): read app state live in the accessory repeat gate
* test(mobile): pin the key bar's held press to the repeat controller's first send
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* 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>
* fix(i18n): re-render memoized translated UI on language and catalog changes
Memoized components and useMemo/module caches that call translate() kept the
previous language after a switch or a plugin language-pack reload, because
react-i18next only re-renders components that subscribe themselves.
Add useUiLanguageRevision() (language + catalog revision via
useSyncExternalStore), call it in the 34 memoized translating components, key
translated useMemo results and module caches on it, and add a ratchet so new
memoized translating components must subscribe.
Fixes the composer pickers reported in #34783.
Co-authored-by: Vyacheslav Scherbinin <14369958+gentslava@users.noreply.github.com>
* fix(i18n): drive the language revision from the resource version
Tests that mock @/i18n/i18n have no i18n instance, so subscribe through
translation-resource-version (now also bumped on languageChanged) and give
useSyncExternalStore a server snapshot for renderToString tests.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Vyacheslav Scherbinin <14369958+gentslava@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
* Recover brief Linear read transport failures
* Preserve safe diagnostics across runtime Linear reads
* Match existing attachment metadata in read contract
* Use a checked runtime in Linear CLI integration tests
* Keep Linear CLI integration coverage in the backend test graph
* Use integration test ownership for Linear CLI recovery coverage
* Observe empty Linear recovery traces before the first sink write
* 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
* fix(browser): free the page queue when an eval's caller disconnects
A local orca eval whose caller gave up (CLI timeout, Ctrl-C) kept the
page's serial command queue until its script settled, so a never-settling
script blocked every later browser command on that page. The unix socket
now forwards its disconnect signal to browser.eval, and the eval stops
waiting on abort; the abandoned script keeps running.
* test(runtime): pin local disconnect forwarding for browser.eval
* refactor(runtime): name the methods that get the local disconnect signal
* fix(ssh): offer Remove from Orca for SSH workspaces this app cannot delete
Refs #34677
* fix(ssh): retire delete-remote copy for unserved SSH workspaces
Refs #34677
* fix(ssh): retire delete-remote copy in the German catalog
Refs #34677
* fix(ssh): drop a stale host-removal comment
Refs #34677
* test(ssh): pin that an unserved SSH twin is only forgotten
Refs #34677
* test(ssh): build the unserved-twin store without a cast
Refs #34677
* test(ssh): type the unserved-twin store state
Refs #34677
* 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.
* perf(i18n): cache option-free translations per language and resource version
A workspace switch re-renders chrome that resolves hundreds of static strings
through i18next (~3 ms per switch). Option-free lookups only change with the
language or the loaded resources, so cache them keyed on both.
* perf(file-search): skip file search state writes that change nothing
Every workspace switch cancels the outgoing workspace's idle search, which wrote
an identical state object and re-notified every store subscriber.
* perf(sidebar): keep unchanged worktree cards and project headers memoized across switches
A plain press replaces the sidebar selection, and the selection-keyed gesture
callbacks plus the shared selectedWorktrees array were passed to every card, so
every card and its menus re-rendered on each workspace switch. Gesture callbacks
now read the latest selection from a ref, only selected rows receive the
selection, and project header actions are memoized.
* test(terminal): bound toast translations from above now that static strings are cached
* perf(terminal): stop re-rendering hidden panes and retained tab strips on workspace switches
Hidden panes re-measured their title-overlay rects on every switch, forcing a
layout and clearing the rects, which re-rendered the pane once on hide and again
on show. Hidden overlays are display:none, so they keep their last rects until
shown. Retained tab strips subscribed to the active repo and worktree for a
Windows-only shell menu, re-rendering every strip; those reads are gated like
the project maps beside them.
* perf(app-shell): let root overlays skip the App render a workspace switch causes
App re-renders on every switch (its chrome layout tracks the active worktree),
and it handed fresh floating-workspace and onboarding objects to the root
overlays, re-rendering every overlay and dialog. Both objects are memoized,
AppRootSurfaces and the floating toggle button are memo components, and the
toggle callback is stable.
* perf(worktrees): switch the active repo and worktree in one store write
Activating a worktree in another project wrote activeRepoId and then the
active worktree separately, so every store subscriber re-ran its selector twice
(~1 ms per switch). The repo change now rides along as a state transition of the
same setActiveWorktree write, as the SSH server-copy activation already does.
* perf(app-shell): stop App re-rendering on workspace switches
App subscribed to the raw active worktree and tab ids through the chrome layout
and three sync hooks, so every switch re-rendered the whole app shell. The
layout now exposes booleans, shortcut dispatch and the titlebar expand button
read the active ids when they fire, and the git-status polling, web session tab
sync and review refresh reporting hooks run in a null gate component under
AppBackgroundServices.
* perf(worktrees): fold visit recency and nav history into the activation write
A switch wrote the active worktree, then the leave/enter recency stamps and the
nav-history entry as separate store writes, each re-running every subscriber's
selector. The stamps (resolved against the post-activation selection, as
markVisited did) and the history entry now ride in the same setActiveWorktree
state transition as the repo follow.
* perf(terminal): present instead of clearing the shared glyph atlas on worktree reveal
The settled reveal reset predates xterm's per-renderer page-layout versioning
(renderers now rebuild their model when a shared atlas page changes), so a
healthy atlas no longer needs re-rasterizing on every worktree switch. DPR
repair and system wake keep the full reset.
* fix(worktrees): keep fresh visit stamps when the activation's history entry is de-duplicated
* fix(app-shell): repaint memoized shell controls on a language change
* test(floating-terminal): stub the language subscription in the toggle button's hook harness
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
When the pinned Bun binary is installed but its version probe times out,
required typecheck callers (repo typecheck, CLI build, E2E and mobile
typecheck) now report the timeout with the executable path and budget
instead of claiming Bun is missing. Each caller suggests its own compiler
fallback. Optional Bun consumers keep returning null and falling back.
Fixes#33707
Co-authored-by: Neil <neil@stably.ai>
Pass --literal-pathspecs to the per-file diff in collectAddedLineRanges so
filenames containing glob characters (e.g. `[ab].tsx`) match only
themselves instead of also pulling in other files' hunks (e.g. `a.tsx`).
Covers both edits and renames with real-Git regression tests.
Fixes#34784
Co-authored-by: Neil <neil@stably.ai>
Only srcset is a comma-separated candidate list; src and href hold one URL.
Splitting them invented missing targets for paths containing commas or
spaces and could pass a missing target whose fragments were tracked.
Fixes#34815
mobile/ is a separate pnpm project, so the root pnpm install leaves
mobile/node_modules empty and pnpm build failed in build:mobile-web with
hundreds of unexplained esbuild "Could not resolve" errors.
build-mobile-web-app-bundle now checks for mobile/node_modules first and
exits with the one command that fixes it. The probe path is shared with
the bundle tests' dependency check. CONTRIBUTING documents the install in
Local Setup and points "Before Opening a PR" at it.
Fixes#24324
Co-authored-by: Neil <neil@stably.ai>
* 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
Daemon log lines used appendFileSync per event and the trace sink used
writeSync, so a disk or writeback stall froze the daemon and orcad together.
Both now go through one bounded writer: lines queue in memory, one async drain
writes a batch at a time and is the only code that rotates, a full queue drops
its oldest lines behind a count marker, and flush() waits only for its own
lines. Crash breadcrumbs and exits append the remaining queue synchronously.
Orderly quit waits up to 500 ms for the async flush.
Refs #34410
Refs #33870
* 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(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
* fix(terminal): forward copies from Codex started in remote shells
* test(terminal): capture PowerShell copy probe exit status
* fix(terminal): preserve configured shell arguments for remote profiles
* test(terminal): isolate profile result from system startup output
* test(terminal): run native daemon shell probes with Node
* fix(terminal): keep an inherited remote-viewer marker out of local panes
An Orca started from a remote viewer's pane inherits ORCA_REMOTE_VIEWER_CLIPBOARD,
which would make every local Codex fake an SSH session. Treat the marker as
per-spawn identity so only explicit launch env carries it.