* fix(plugins): keep binary editor edits across renames, reloads and twin tabs
- Renaming a binary file with unsaved plugin-editor edits gave its tab a new
id, and the bytes store took the old id's disappearance as a close and
dropped the edit. The bytes now move with the tab as the rename lands.
- Tabs of one file share one dirty flag, so they now share one bytes draft;
before, saving one tab marked both clean and dropped the other's edit.
- A reload that reads the same bytes keeps the document, so the editor is not
sent a new version that turns an edit in flight into a conflict.
* fix(plugins): finish a twin-tab bytes save through any tab still open
A tab that closed mid-save left its twin with the old hash, and a twin
adopting a shared draft no longer lets its own older read replace it.
* fix(plugins): hand a closed tab's bytes draft to its open twin; drop stale reads
A twin that had not shown the file yet lost the draft when the tab holding it
closed, and a read that started before an edit and save could replace the
newer saved bytes. Boxes now count edits and saves; a read only lands if none
happened since it started.
Cwd-less gh/glab calls whose host CLI is missing fell back to the default
WSL distro with no running check, so background work (rate limits, auth
status, Settings, task-source probes) booted a stopped distro. The
fallback now asks the background exec gate first: stopped or
unverifiable refuses with the distro-stopped error, running and user
acts fall back as before.
* fix(i18n): format renderer dates and numbers in the UI language
About 80 renderer sites formatted with the OS locale: Intl constructors
given undefined (or nothing) and argless toLocale*String calls, while
nearby text followed the UI language.
- toLocale*String calls pass getIntlLocale().
- Module-level Intl formatters and collators use new ui-intl-formatters
helpers, cached per options object and rebuilt on language switches;
per-call constructors pass getIntlLocale().
- ReleaseBuildPicker's memoized labels re-run on language changes.
- An ast-grep rule (renderer-intl-ui-locale) rejects undefined/omitted
locales in src/renderer; machine-facing code passes a fixed locale.
* fix(i18n): collators check their own locale support; re-sort memoized lists on language changes; widen the Intl guard
* fix(i18n): name the formatter options type
* fix(i18n): reuse the CSV viewer's existing language subscription
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
A launch made while the Codex history source is a \\wsl path records no
rollout dates, so after switching back to a plain path the next migration
pass only scanned the old pending dates and missed those sessions. Such a
launch now sets the local backfill marker's full-scan flag (Orca user data,
no 9P access), and the next eligible pass walks all history.
Also checks the resolved source home (setting, else the system home) when
deciding whether the history source is in WSL.
* refactor(plugins): one liveness check and list hook for plugin contributions
Panels, commands, custom editors and new-file types each repeated the same
running/restarting/idle filter, the same plugin tagging, and the same
load-then-memo hook. They now share isLivePlugin, fromPlugin,
keyedPluginContribution and usePluginListDerived.
* perf(plugins): keep the panel-error map's identity when health is unchanged
Every panel load reported its health, and each report rebuilt the map the
sidebar badges subscribe to. Also drops an unchecked cast.
* 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
* refactor(i18n): share one i18next setup between main and renderer
- createOrcaI18n (src/shared/orca-i18n-instance.ts) owns the backend,
init options, plugin-pack registration, resource-language resolution
and the translate wrapper; main and renderer inject only how a
built-in catalog loads (dynamic import vs. fetch) and extra modules.
- Remove setRendererUiLanguage (tests only); tests load then switch.
- resolveUiLocale maps built-ins by lookup instead of an if-chain, and
catalog URLs/loaders are direct map lookups.
- Drop <I18nextProvider>: initReactI18next already binds the instance.
* refactor(i18n): native i18next plurals at call sites, plural-aware extraction
Call sites branched on count === 1 and passed literal _one/_other keys,
which resolvePluralFamilyKey stripped again so CLDR could pick the form.
They now call translate('x', otherDefault, { count, defaultValue_one })
and i18next selects the form natively; catalog keys stay x_one/x_other.
- Converted 284 families with an AST codemod plus a few by hand; added
defaultValue_one to 21 base-key families that relied on en.json.
- Extraction runs with plurals enabled, so both English defaults are
checked for drift. A key that only fills {{count}} (no defaultValue_one)
stays singular in en.json.
- verify/sync:localization-catalog expand a native plural call into its
x_one/x_other English entries.
- Removed 22 base keys that a sibling plural family shadowed in every
catalog.
* fix(i18n): pick English plural defaults by English rules; require count for plural refs
* refactor(i18n): convert plural sites merged from main; treat key-as-default plural extraction as dynamic
* fix(i18n): keep an undefined pass-through count absent; only dotted key-as-default is dynamic
* refactor(i18n): native plurals for exact-1 families; drop desktop suffix stripping
Twelve families kept special exact-1 copy ("Used the browser") beside an
_other-only family, so CLDR one (ru 21, 31) reused _other wording. They
now call the base key with an English _one translator source, the
exact-1 branch stays a separate key, and ru one forms get the right
noun ("21 раз"). With no suffixed call sites left, desktop translate
no longer strips plural suffixes; mobile keeps resolvePluralFamilyKey.
* test(i18n): update plural call expectations in two renderer tests
* refactor(i18n): convert a plural site merged from main
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): code AI text-generation failures for commit, PR, and Jira title generation
* fix(i18n): merge duplicate type import
* fix(i18n): code Source Control AI settings and generation plan failures
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(orcad): keep orcad's terminal models at the daemon's live depth
orcad's per-PTY headless terminal model kept 5,000 rows of scrollback, but
orcad has no renderer to reveal from: requested snapshots read the daemon
first and subscribe snapshots read at most 1,000 rows. The model now
mirrors the daemon's live window, from the same resolver the daemon uses.
The execution-context reseed also awaited its provider snapshot with no
bound, so a provider that never answered held every live chunk queued
behind it. It now uses the authoritative snapshot timeout.
Refs #34410
Refs #33870
* fix(orcad): keep provider preference when the reseed snapshot times out
Refs #34410
Refs #33870
* fix(runtime): bound every provider snapshot wait in one place
serializeProviderTerminalBuffer now applies the authoritative snapshot
timeout when a caller passes none, so no reader can wait on a provider that
never answers. The execution-context reseed relies on that bound; when its
capture times out it keeps the PTY provider-preferred and reseeds again once
the capture lands, instead of staying on the provider path.
Refs #34410
Refs #33870
* fix(runtime): reseed once after a late provider snapshot, trust the model on none
awaitProviderTerminalBuffer reports the capture still running when the
bounded wait gives up, instead of callers inferring it from the acquisition
map. The execution-context reseed retries once when that capture lands with
a snapshot, and stops preferring the provider when it lands with none, so a
slow provider can neither loop reseeds nor pin the PTY to the provider path.
Refs #34410
Refs #33870
* fix(runtime): never trust a late-reseed model that found no snapshot
The once-only retry disposes a model that had parsed the live bytes since
the timeout. If its own capture then fails, the replacement holds less than
its predecessor, so the PTY keeps preferring the provider instead of
treating that model as complete.
Refs #34410
Refs #33870
* 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
* refactor(i18n): share one i18next setup between main and renderer
- createOrcaI18n (src/shared/orca-i18n-instance.ts) owns the backend,
init options, plugin-pack registration, resource-language resolution
and the translate wrapper; main and renderer inject only how a
built-in catalog loads (dynamic import vs. fetch) and extra modules.
- Remove setRendererUiLanguage (tests only); tests load then switch.
- resolveUiLocale maps built-ins by lookup instead of an if-chain, and
catalog URLs/loaders are direct map lookups.
- Drop <I18nextProvider>: initReactI18next already binds the instance.
* refactor(i18n): native i18next plurals at call sites, plural-aware extraction
Call sites branched on count === 1 and passed literal _one/_other keys,
which resolvePluralFamilyKey stripped again so CLDR could pick the form.
They now call translate('x', otherDefault, { count, defaultValue_one })
and i18next selects the form natively; catalog keys stay x_one/x_other.
- Converted 284 families with an AST codemod plus a few by hand; added
defaultValue_one to 21 base-key families that relied on en.json.
- Extraction runs with plurals enabled, so both English defaults are
checked for drift. A key that only fills {{count}} (no defaultValue_one)
stays singular in en.json.
- verify/sync:localization-catalog expand a native plural call into its
x_one/x_other English entries.
- Removed 22 base keys that a sibling plural family shadowed in every
catalog.
* fix(i18n): pick English plural defaults by English rules; require count for plural refs
* refactor(i18n): convert plural sites merged from main; treat key-as-default plural extraction as dynamic
* fix(i18n): keep an undefined pass-through count absent; only dotted key-as-default is dynamic
* test(i18n): update plural call expectations in two renderer tests
* refactor(i18n): convert a plural site merged from main
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(i18n): code AI text-generation failures for commit, PR, and Jira title generation
* fix(i18n): merge duplicate type import
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(codex): never read a WSL distro's history over 9P in the per-account and shared-mirror lanes
When codexSessionSourceHome points inside a distro (\\wsl.localhost\…), the
per-account lane used it as a history source and the shared-mirror lane scanned
it, both through 9P from the Windows side, which boots a stopped distro (E9) and
yields links codex resume ignores. Both now leave such a source out; the WSL
lane, which runs only at launch, owns those homes. Ordinary custom sources are
unchanged.
* fix(codex): no system-default history migration scan from a WSL source home
The host system-default session migration (backfill and index heal) also read
resolveHostCodexSessionSourceHome as its sessions root; a \\wsl.localhost
source would be scanned over 9P and boot a stopped distro (E9). Such a source
now makes the migration ineligible, sharing one check with the mirror lane.
* fix(codex): one WSL-source predicate for every history lane, and no backfill prep from it
Every lane that reads the host history source now uses one protected predicate,
isHostHistorySourceInWsl(): the per-account, shared-mirror and ~/.codex lanes,
migration eligibility, backfill invalidation after a managed system-default
launch, and preparing a migration pass, which now returns false without marking
the backfill pending.
#34632 let Share read every stopped WSL distro's skill folders. The Share
dialog now sends the chosen skills' folders, and discovery reads only the
stopped roots (and workspace plugin folder) that hold them; every other
stopped root stays unread and unavailable. The skills.share RPC names skills,
not folders, so it scans gated first and reads stopped roots only when a
selector is missing and a folder went unread.
Panels and custom editors each had their own copy of the read-entry,
newest-read-wins, reload-on-change loop. The panel copy also cleared its
failure dot on the next plugin-list change while it still showed the
suspended message. One hook now loads both; a suspended frame stays suspended
(and reported failed) until its HTML actually changes, and panel health is
derived from the entry state.
* feat(wsl): bridge sh/dash/ash panes report their folder (OSC 7) at every prompt
bash, zsh, fish and nushell panes already tell the terminal daemon their
folder; a bridge pane whose login shell is sh, dash or busybox ash did
not, so restores fell back to the starting folder.
dash has no rcfile flag or prompt hook, but an interactive sh reads $ENV
after ~/.profile, and dash 0.5.8+ (plus ash and bash-as-sh) expand $(...)
in PS1. The bridge now points ENV at a new sh/env file in the published
wrapper tree, saving the user's own ENV. That file hands ENV back to the
user's value (so children see theirs), sources it, and inside WSL only
prefixes PS1 with the report. A root containing $, ` or \ is skipped,
since the shell expands ENV's value.
A non-interactive sh -c never reads ENV, and a missing file (Orca
updated away or uninstalled) is silently ignored by dash, ash and bash.
* fix(wsl): sh ENV hook survives quotes in the user's ENV and keeps prompt width
- The user's ENV is expanded through a here-document, so a value with a
quote, $ or space no longer breaks startup with a syntax error.
- bash-as-sh and busybox ash (fancy prompt) get the report inside \[ \]
so its invisible bytes don't count toward the prompt width; dash has no
line editor to miscount and would print \[ literally, so it stays bare.
- Tests: a user ENV of `$HOME/u "q" sp/env`, a user PS1 with $, a quote
and a backslash, and reports read through the daemon's WSL parser.
- CI: edits to the sh hook and the bridge launch trigger the shell
contracts job (its prefix list moves to its own module, keeping the
scope script under its line limit), and the sh and fish WSL pty tests
leave the unit shards.
* ci: check out the shell-contract prefix module in the change detector
The detector job sparse-checks-out a fixed file list; the new
pr-shell-contract-prefixes.mjs that pr-code-change-scope.mjs imports was
missing from it, so the job failed with ERR_MODULE_NOT_FOUND. A contract
test now walks the scope script's local imports and requires each in
that list.
* ci: list the shell-contract prefix module wherever the scope script's modules are listed
Two config-script suites copy pr-code-change-scope.mjs and its sibling
modules into a fixture repo; without the new pr-shell-contract-prefixes.mjs
the copied script failed to load, and the detector fell back to running
everything. Also lists it with the scope script's other modules in the
mobile workflow trigger and the mobile non-test file set.
* test(terminal): run the renderer's OSC 7 agreement cases through xterm
The renderer's OSC 7 parser only ever sees what xterm's OSC parser hands
its handler, and xterm drops raw C0 bytes first: a raw tab, LF or CR
becomes /tmp/ab, and a raw NUL is dropped rather than refused. The
agreement table now records those rows as a known renderer gap (viaXterm)
and the renderer side runs every case through @xterm/headless.
* test(terminal): pin raw ESC, CAN and SUB, and run the daemon side through its PTY scanner
- A raw ESC ends xterm's OSC at /tmp/a, and the daemon's scanner rejects
the report (ESC not followed by \), so the pane keeps its folder.
- CAN and SUB cancel the sequence in xterm, so the renderer's handler
never runs (no-report); the daemon reads the byte as part of the path.
- The daemon side now also runs every case through
TerminalOscCwdTitleScanner after a prior good report, as PTY traffic
reaches it, alongside the direct-parse check.
The restart baseline only had a non-PTY wsl.exe exec as the old bridge's lower bound. The lane
now also runs the bridge as the app does: wsl.exe in a ConPTY with the bridge's WSL terminal
setup command and the probe typed ahead, recording first paint and first command output, median
of 3, alongside the host and exec runs. Test-only.
A snapshot command that rejects now falls back to the per-file reads
instead of failing the connect. A snapshot whose record section was
unreadable gives no hints even when the fallback read answered. Pins the
held-fence and pending-journal cases at the connect level, and notes that
snapshot raws drop trailing newlines.
The uninstaller's other-install check treated an Orca registration whose
InstallLocation reads empty as "none", so it would remove the agent hooks.
That registration could still be another install, so it now reports "unknown",
which keeps the hooks; every doubtful case leaves them in place.
* fix(plugins): stop an in-flight auto-update pass once the user opts out
A pass already waiting on the catalog or kill list could still install after
the user turned automatic updates off. Recheck the setting just before the
install starts.
* fix(plugins): recheck the auto-update setting inside the install step
The approval step awaits the old version's recipe files before installing, so
the check now runs at the install call itself.
* fix(plugins): compare self-hosted repository paths case-sensitively
A self-hosted Git server can serve team/Plugin and team/plugin as two
repositories, so treating them as one let a catalog switch repositories
without the moved-repository review. Case is still ignored on GitHub,
GitLab.com and Bitbucket, which route paths case-insensitively.
* refactor(plugins): keep the marketplace module under the line limit
* Send ordinary PR reviewer updates directly to the API
* test(github): trim redundant cases
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(codex): link managed-account history into the user's own ~/.codex on system default
When system default launches Codex on the real ~/.codex, bridge every trusted
managed host account home into it so sessions started on a managed account
stay resumable after switching back (#25000).
Split from #33578 (host half). Needs an owner product decision (#10770 vs #24511).
* feat(codex): a per-host opt-out for sharing Codex history across accounts (#24511)
Adds codexShareHistoryAcrossAccounts (absent = on, today's #10770 behaviour).
Off, the host that launches Codex starts none of its history bridges: system
default into the shared runtime home, accounts and ~/.codex into a per-account
home, managed accounts into the real ~/.codex, and the WSL distro and sibling
accounts into a WSL account home. Turning it off removes nothing already linked.
The setting lives in each host's own store, since that host writes the links.
Settings → Agents shows it for the selected host: local writes update this app,
a paired SSH or WSL host is read and written over settings.get/settings.update.
New hosts always publish the key; a host that doesn't is shown as needing an
update and is never sent it, because older hosts' strict update schema would
reject the call. Strings are localized for all 11 locales.
The system-default lane moves into the launch class, keeping managed-home under
the line cap.
* fix(codex): ~/.codex gets hardlinks or nothing, and a WSL history source is left to the WSL lane
The system-default lane wrote into codexSessionSourceHome when set, which can be
a \\wsl.localhost path: the background scan would boot a stopped distro over 9P
and its cross-volume symlinks are ignored by codex resume. That lane now returns
early for a WSL UNC target; the WSL lane owns those homes.
Into the user's own ~/.codex the bridge now links with hardlinks only: a failed
hardlink (EXDEV across volumes) skips the rollout, counted as skippedFiles,
removes any folder it just created, and never falls back to a symlink that
codex resume ignores and that would dangle once an account is removed. Bridges
between Orca-managed homes keep the symlink fallback.
Also: the setting's JSDoc sits above its own field, and Settings search finds
the switch (codex, history, share, shared, account; existing localized keys).
* fix(windows): remove Orca's agent hooks and PATH entry when uninstalling
On Windows most agents' managed hooks, and Claude's status line, call Orca's
.cmd scripts by bare path (to avoid starting PowerShell per event), and the
uninstaller removed neither those entries nor the `orca` command's user PATH
entry. Once %USERPROFILE%\.orca went, every agent failed a hook on each event.
A real uninstall (not an update) now runs the installed exe with
--orca-uninstall-remove-agent-hooks, mirroring the WSL holder release: no window,
lock or IPC, file edits only, a 30 s self-exit, and an exit code the uninstaller
ignores. It then drops $INSTDIR\resources\bin from the user PATH with native
registry calls, keeping every other entry and the value's type, and skipping the
edit if the read could have been truncated.
* fix(windows): keep agent hooks while another Orca install remains; skip PATH edits of unknown type
The hook scripts live in the shared ~/.orca\agent-hooks and reference nothing in
$INSTDIR (they call system curl/more and a copy of orca-agent-hook.exe there), so
they outlive an uninstall; the entries only break once that folder is removed.
The last Orca install for this user now takes them out: the uninstaller reads
the other per-user/per-machine registration (every channel shares one app GUID)
with native registry calls, and skips the removal if another install still has
the app or a read fails. PATH cleanup still runs, as it is per $INSTDIR.
PATH cleanup now writes only with a value type it actually read (REG_SZ or
REG_EXPAND_SZ); a failed open or query skips the write instead of assuming REG_SZ.
* fix(windows): install the app environment before the uninstall hook removers run
The uninstall mode skips the app's own preflight, so setAppEnvironment was
never called. Claude's remover then threw at getAppEnvironment() right after
writing settings.json: its agent-view keybinding stayed in every profile home
and its status-line marker stayed too. Install ElectronAppEnvironment right
after the AppData path, as main-process-preflight does. A new test runs every
agent's real remover in that mode with no environment preset; none warns, the
mode exits 0, and Claude's agent-view binding and status-line marker are gone.
* test(windows): register the uninstall preflight's host port and keep its real-remover test off real homes
The uninstall hook-removal preflight installs the app environment, so it joins
HOST_PORT_SOURCES. Its real-remover test now points HOME, USERPROFILE,
ORCA_USER_DATA_PATH, XDG_CONFIG_HOME, APPDATA, CODEX_HOME and CLAUDE_CONFIG_DIR
at the fixture: on Linux CI the Codex runtime home resolved from the runner's
XDG_CONFIG_HOME, which the real-agent-home write guard rejected.
Track in-flight resolve/unresolve requests per comments cacheKey + threadId in the store. A second request on the same thread no-ops until the first settles, and the Checks panel ResolveButton shows pending from the store so the remounted button after the optimistic section move cannot fire the opposite request.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Reuse work-item page totals for matching count queries
* Verify page totals avoid later count requests
* Read numbered PR pages with reusable cursors
* Combine short cursor prefixes with the requested page
* Use advanced search for grouped PR cursor queries
* Validate combined quota changes against the final PR base
* Verify forced and credential-scoped PR pages bypass saved cursors
* Batch uncached PR reads and reuse equivalent PR totals
* Match Boolean PR totals to the advanced list predicate
* Keep malformed PR count groups on the scoped legacy search
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(tasks): preserve navigation during created issue hydration
* refactor(tasks): check created issue hydration fixtures
* test(tasks): trim created issue hydration tests
Drop the activeView guard: hydrating while Tasks is parked is harmless.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* perf(mobile): avoid image marker whitespace backtracking
* test(mobile): drop image marker case that passes with the old regex
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): retire closed-tab composer state
* Require confirmed tab retirement before composer cleanup
* fix(mobile): keep drafts of tabs the host has not listed yet
Optimistically created tabs can precede their host listing, so an accepted inventory without them must not retire their composer state.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(mobile): show a voice meter and timer while dictating, with start and stop haptics
The phone meters the PCM it already streams with desktop's analyzer,
now shared from src/shared, so both shells get a level without a native
event crossing the page bridge. Recording start and stop tap the phone,
iOS now allows haptics during input, and "Dictation inserted" only
shows for live terminal input, where the text can be hidden.
* refactor(mobile): keep the dictation hooks under the line cap after #35502
Moves the start tap and stop tick into useMobileDictationHaptics and shares one current-finish guard in finishDictation.