* perf(build): minify desktop JavaScript bundles
* perf(build): minify with rolldown's oxc and emit hidden main source maps
'esbuild' made rolldown disable its own minifier and re-print every chunk
through esbuild, which is not a declared dependency and resolves only via
pnpm's shamefullyHoist from electron-vite's tree (0.25.12 against a declared
peer of ^0.27.0). Switching to rolldown's in-process 'oxc' minifier drops
that second pass: main+renderer build falls 23.2s -> 11.9s and ships ~2.7MB
less JavaScript.
keepNames is dropped with it — it cost ~1.5MB and only recovered function
names. main now builds with sourcemap:'hidden', which restores names *and*
locations without emitting a sourceMappingURL. Packaging excludes
out/**/*.map so app.asar is unaffected; release CI publishes the maps.
Track scroll-restore generation to invalidate stale callbacks that were
resetting the scroll position to 0 when reopening a detail page. Prevent
restoration while a detail page is open.
Update automation test to use runtime.call RPC instead of removed preload
CRUD method. Hide browser import hint in E2E profile to prevent overlay from
intercepting test setup clicks.
Validate scheduled values match current state before executing idle
timeout callbacks. Use useLayoutEffect to synchronously update refs,
preventing outdated searches when rapid keystrokes overwrite timers.
* refactor(agent-hooks): drop the unused per-agent hook status IPC surface
No renderer, CLI, or mobile caller invoked window.api.agentHooks.*Status; main
already reads install status through MANAGED_AGENT_HOOK_STATUS_READERS. The
14 handlers had also drifted (kimiStatus existed in main/preload but not in
AgentHooksApi or the web stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(tui-agent-config): default launchCmd and expectedProcess to detectCmd
32 of 36 entries repeated the binary name three times. Entries are now
authored in a source form where both default to detectCmd and resolved once
at module load, so TUI_AGENT_CONFIG keeps its exact shape for consumers
(verified equal to the previous table).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(mobile): derive the agent order, labels, and picker from src/shared
The mobile mirror (and its regex-over-desktop-source parity test) predates
mobile importing runtime values from src/shared, which it now does in a dozen
modules. Only the favicon-domain map stays mobile-local because desktop's lives
in the renderer catalog next to bundled ?url imports. The parity test now
imports the real registries and also checks label parity.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(web): align preload surface after hook IPC removal
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Review follow-ups on the create-field shaping:
- Seed only `reporter`. isVisibleJiraCreateField matches every required
non-system field, so the previous filter also pre-filled required custom
user pickers (Reviewer, Requested by) that Jira never defaults.
- Skip the seed when the target project is on another site. The viewer
comes from the active site, and the host shapes against the target's
client, so a foreign accountId is rejected on Cloud and can silently
resolve to a different person by username on Server/DC.
- Render JiraUserPicker only for scalar user fields. It holds one user, so
an array-of-user field collapsed to a single member; those keep the
existing comma-separated text path, which still reaches toUserFieldValue's
array branch.
Adds docstrings across the touched Jira functions to satisfy the
docstring-coverage pre-merge check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdMzQN6T3jRVahCK2sNyur
- Jira rejects a bare string for reporter/user-picker fields on issue
create, so shape customFields values into {accountId}/{name} objects
for keys the caller flags via userFieldKeys.
- Seed required user fields with the authenticated viewer by default
and add a searchable user picker (jira.searchUsers) so users aren't
forced into free text for reporter/custom user fields.
* fix(agents): keep OMP identity and forward ask/approval events (STA-4130)
A live OMP pane was re-owned as Pi because the generic pi-compatible
fallback always won, ask events blocked without a question payload, and
OMP suppressed tool_approval_* unless an extension registered handlers.
Mark Pi as the title-group fallback so a specific OMP identity is not
downgraded, publish OMP ask input as the existing questions envelope, and
forward tool_approval_requested/resolved onto blocked/working.
STA-4130
Related to #14278
Co-authored-by: devatnull <59279509+devatnull@users.noreply.github.com>
* fix(agents): keep launch Pi ownership over OMP wrapper frames (STA-4130)
The pi-compatible fallback treated every generic Pi owner as inferred, so an
explicit launch-Pi pane (and a launchless Pi pane with an OMP-shaped title)
was re-owned as OMP. Launch provenance now stays authoritative; only an
inferred status-frame owner yields to a specific sibling, and same-group
titles no longer count as reuse.
STA-4130
* fix(agents): drop Pi wrapper idle titles while OMP hook is active (STA-4130)
Title-completion suppression compared pick-a-winner ownership, so a Pi ready
frame looked like a different agent than a live OMP hook and fired a spurious
task-complete notification. Reuse checks now use the title-identity group.
STA-4130
* fix(agents): restore OMP approval forwarding after merge
* fix(agents): restore title-owner API after merge
* test(agents): update identity inventory ratchet
---------
Co-authored-by: devatnull <59279509+devatnull@users.noreply.github.com>
Co-authored-by: Merge Sim <sim@local>
* fix(omp): read Pi/OMP static state-title markers and retire stale spinners
OMP 17.2.12 replaced its animated braille title frames with static markers
on WSL/ConPTY (`π : working`, `π > idle`, `π ! needs input`). Orca read all
three as idle, so a working OMP pane lost its status, and a synthetic title
spinner started by an earlier hook kept rotating after its status row was
gone.
Classify the markers from one shared table so a later upstream punctuation
change is a row, not a reparse, and stop the spinner when the hook row it
stands in for is cleared or dismissed.
Fixes#13890
* test(omp): preserve static state titles during normalization
---------
Co-authored-by: Merge Sim <sim@local>
* feat(browser): edit a page annotation's comment and intent inline in the tray
Fixing a typo in a browser annotation required deleting it and re-picking
the element in the page. Each tray row now has a hover-revealed edit
button that swaps the row to an inline editor (comment textarea seeded
with the current text, intent toggle, Save/Cancel), reusing the compose
card's submit shortcut and length budget. Edits route through a new
updateBrowserPageAnnotation store action that no-ops on a missing id and
sanitizes the merged annotation exactly like the add path.
Escape inside the editor cancels only the row edit: the annotate mode's
window-level capture listener now exempts the edit container, since
stopPropagation from a descendant cannot reach a capture listener.
Editing is deliberately limited to comment and intent: element targets,
cross-navigation persistence, and the prompt builder are untouched (the
builder reads live off the array, so edited text flows through).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017rEaTnSaSneEMEJvebzM53
* test(browser): pin the annotate-mode Escape exemption for annotation edits
The data-slot annotation-edit clause in useGrabMode's capture keydown
listener had no test that dispatched a keydown through it, so deleting
the clause left the suite green. These tests invoke the real registered
capture handler with an Escape whose target sits inside (and outside) an
annotation-edit container; the inside case uses a button, which
isEditableKeyboardTarget does not cover, so only the exemption clause
can keep grab mode alive. Removing the clause now fails the suite.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Show remote host failure details in submenu
* chore(i18n): sync catalog for remote host submenu strings
* fix(status-bar): keep remote host actions accessible
* fix(status-bar): preserve submenu keyboard navigation
* fix(status-bar): drop the submenu chevron on remote host rows
The panel sits at the screen edge, so Radix collision-flips the submenu to the
left. A right-pointing chevron then points away from where the menu opens.
hideChevron is opt-in, so the 21 other SubTrigger consumers are unchanged.
---------
Co-authored-by: Merge Sim <sim@local>
* Fix orchestration CLI recovery, settled-Dispatch mail, and guide defects
Five reported orchestration CLI defects, verified individually before fixing.
Two were real code defects, one was a docs error, one was correct as-is, and
one was correct on both ends except for its recovery wording.
- Mail addressed to a settled `dispatch:<id>` was accepted and silently dropped.
Local sends bypassed the settlement check the federated branch already had, so
the caller was told success for a delivery no worker would ever read. Reject
with `dispatch_inactive` and name the Run mailbox to use instead.
- A lost mutation response offered no read-only way to ask whether it took
effect. `--retry-request` does dedupe correctly, but the recovery guidance
emitted a query command only when the payload carried a dispatch id, which is
exactly what a lost response lacks. Add read-only
`orca orchestration request-show --request <id>` over the durable receipt
ledger, and always emit a read-only step before the keyed retry.
- The bundled `orca-cli` guide documented `check --unread --inject`, a flag the
parser rejects. Correct it to `--format` and add a ratchet that runs every
orchestration invocation in the bundled guides through the real CLI parser.
- `check --json` is one stdout document and its keepalives are stderr-only; the
reported `Extra data: line 2` came from merging the streams. Document the
contract rather than changing the wire.
- A rejected lifecycle message is loud on both ends already, but the rejection
never named the flag that supplies the missing capability. Name it.
* Harden orchestration mutation recovery guidance
* fix(agent-hooks): route reminted pane keys to canonical identity (STA-3993)
Spawn was stripping $$<base32>:L$$ ORCA_PANE_KEY values (and the launch
token) instead of rewriting them to the metadata-proven tab:leaf key, so
OMP hooks never entered last-status.json and sleeping rows stayed working.
Alias that exact remint form onto the canonical pane so later posts still
route, and keep unmatched tokens from stamping another pane.
* fix(agent-hooks): keep reminted pane-key aliases first-pane-wins
Remint tokens have no embedded tab identity, so a later spawn that reused
the same $$ token with a different tab/leaf was overwriting the alias and
routing leftover hook posts onto the new pane. Refuse destination changes
for that form while still allowing same-pane pty id updates.
* fix(agent-hooks): keep pane alias limit import valid after refactor
* fix(agent-hooks): bound pane alias destination keys
* fix(ssh): keep pane identity env stripped when hooks disabled
---------
Co-authored-by: Merge Sim <sim@local>
* fix(mobile): dismiss the keyboard after sending to an agent
Sending a message left the software keyboard up, covering the reply the
user was waiting on. Drop it once the send is accepted, on all three send
paths: the terminal live input, the buffered command input, and the chat
composer.
Gated on the tab being an agent session. A plain shell keeps the keyboard
so back-to-back commands stay typeable, a rejected send keeps it so the
handed-back draft stays editable, and the accessory shortcut row is
untouched because dismissing would pull away the row being tapped.
* fix(mobile): gate keyboard dismissal on accepted sends
* fix(mobile): fence keyboard dismissal completions
* fix(mobile): fence stale send completions
* test(mobile): update terminal guard expectations
* fix(mobile): restore rejected buffered drafts by origin
* fix(mobile): preserve intentional buffered draft clears
* fix(mobile): harden send dismissal authority
* test(mobile): preserve Strict Mode send dismissal
* fix(mobile): preserve drafts across terminal remints
* fix(mobile): preserve draft ownership through terminal races
* fix(mobile): harden draft recovery and send freshness
* fix(mobile): fence route reuse and native draft clears
* fix(mobile): preserve native draft edits before clear
* test(mobile): pin the terminal-list sweep that bounds buffered drafts
`bufferedTerminalDraftState.pruneDrafts(retainedHandles)` is the only bound on
two structures that live as long as the session screen — the buffered-draft
record and the pending-restoration map — and nothing failed when it was deleted
or when it was pointed at the raw `terminal.list` handles instead of the
retained set. Both mutations reddened 0 of 3,949 mobile tests.
Adds the wiring pin (both mutations now redden it) plus two behavioural tests
showing why the argument matters: `terminal.list` omits a chat-covered handle
while the desktop graph reloads, so the raw list drops a draft the user is
still holding while the retained set keeps it.
---------
Co-authored-by: Merge Sim <merge@sim.local>
Co-authored-by: Merge Sim <sim@local>
* Reorganize combined-diff components into feature-organized structure
Splits flat combined-diff files into feature-focused subdirectories
(browse-files, load-sections, resolve-changes, review-controls,
scroll-viewport) to improve code organization and reduce clutter in
the editor directory. Groups related logic by concern for easier
navigation and maintenance.
* Split up combined-diff viewer into feature-organized modules
Decompose the 221-line monolithic CombinedDiffViewer into smaller, focused modules organized by feature: entry resolution, section loading, view state memory, file tree navigation, review controls, and scroll viewport handling. Main component now composes these hooks to orchestrate the combined-diff view.
* fix(combined-diff): prevent replayed preference writes
Move preference write outside state updater callback since React may
replay state updaters, causing multiple writes. Add sideBySide to
dependency array.
* fix(combined-diff): re-resolve sections by key to handle list rebuilds
The section list can rebuild while a write is pending (due to rebase, file changes, etc.); re-resolve by key instead of stale index to apply updates to the correct section.
- Convert skipped conflicts message to structured i18n plural forms
- Add oldPath field to git status signature for rename tracking
* Suppress react-doctor diagnostics in combined-diff feature
Add suppressions for react-doctor diagnostics that are necessary patterns
for the combined-diff implementation, configured in both the quality check
script and package.json.
* fix(pi): settle OMP status from the agent_end contract
OMP exposes no agent_settled hook and ctx.isIdle() can stay false after a
finished turn, so Orca's idle-recheck loop backed off and spun forever and
the pane stayed "working" indefinitely. OMP instead marks non-terminal
agent_end events with willContinue; honor that for configured and
runtime-routed OMP and treat an absent flag as terminal.
Extracted from #15658, which bundles this with a launch-authority change
that conflicts with in-flight #17077. Only the settle half lands here.
STA-4130
Co-authored-by: Bing.Z <zzb@gxsmjx.com>
* fix(pi): preserve non-terminal continuation guards
Keep the base Pi and Prime willContinue guard while settling terminal OMP events directly. Add regression coverage so sibling runtimes cannot publish a false completion after conflict resolution.
---------
Co-authored-by: Bing.Z <zzb@gxsmjx.com>
Co-authored-by: Merge Sim <sim@local>
`adds no tab when the host workspace snapshot stalls across a relaunch` replays
the bytes it reads off the relay, but only ever waited for the snapshot FILE to
exist -- never for it to carry the tabs the test had just seeded. A capture that
missed the baseline produced a failure that reads as a product regression and is
not one: an empty `session.tabsByWorktreePath` places nothing, so it reports
nothing unplaced, so `remote-workspace-snapshot-apply.ts` marks the target
hydrated and `hydrateTabsSession` replaces the worktree's tabs with none. That is
exactly the observed `baseline=3 duringStall=3 afterHydration=0`, and it is
correct behaviour for a host snapshot that genuinely holds no tabs.
Assert the precondition where it belongs -- on the capture, before the relaunch
that consumes it -- so an empty or unparseable fixture names itself instead of
surfacing later as a tab count the product appears to have lost.
No assertion is weakened: `afterHydration` still has to equal the baseline, and
no retry, sleep, or timeout was added.
When a remote browser pane opens a link with an explicit placementPreference,
honor that override rather than applying the generic browser client policy.
Links opened from remote panes may require specific host placement to respect
execution boundaries.
* Upgrade xterm to 6.1.0-beta.303 and generate the addon patches
Takes the current xterm beta line: xterm 287 -> 303, addon-webgl 286 -> 299,
addon-serialize 287 -> 300, headless 302, the remaining addons -> 300, and the
same set on mobile. All four packages stamp upstream commit d3e32b3.
The reasons are upstream #6042/#6043/#6055 (a shared glyph atlas no longer
garbles sibling panes on a page merge, clear, or sampler-budget overflow) and
Note that core 303 is not image-addon-only over 302: it carries the buffer perf
work, including the new BufferLineStringCache.
addon-webgl and addon-serialize move into the patch generator
--------------------------------------------------------------
Both were hand-edited minified bundles, which is what the Known Gaps section of
docs/reference/xterm-patch-regeneration.md described. Both reproduce byte for
byte from the pinned commit, so they are now manifest entries generated from a
source patch like @xterm/xterm already was. Their sourcemaps now move with their
bundles; before this they shipped maps whose offsets did not match the code
beside them.
The webgl patch shrinks from a 1.06 MB hand-edited bundle to a 6.6 KB source
patch, because upstream took the invalidation half Orca had backported. What is
left is only what upstream still lacks: the fragment-shader else branch for a
v_texpage past the sampler budget, the clearTexture guard that no-ops once a
merged page holds index 0, spending the merge retry budget before beginFrame
latches the version it saw, and Orca's font-weight probe.
The serialize source patch is byte-for-byte the same fixes as before; upstream
changed nothing in that addon between 287 and 300.
Generator fixes, each of which failed silently
----------------------------------------------
- `--relative` was appended after the `--` separator in CHECKOUT_DIFF_FLAGS, so
git read it as a pathspec and kept repo-root-relative paths, dropping every
source hunk from an addon's patch.
- `git apply` run from a package subdirectory still resolves patch paths from
the repo root, skips every hunk and exits 0. It now runs from the root with
`--directory=<packageDir>`, and a source patch that leaves the checkout
unchanged is a hard failure rather than an empty patch.
- An addon's own `tsgo -p .` has empty files/include and only project
references, so it emits nothing and the addon webpack then fails on a missing
./out/. The root build now runs first.
- versionStampFile is optional; publish.js stamps an addon's package.json, which
overlayBuildOutput never patches.
- On a version bump the lockfile has no entry under the new key yet, so --write
reports the gap instead of aborting mid-run. --check still fails on it.
Adding the two addons pushed the generator and the Electron packaging contract
test over max-lines, so the patch-text helpers move to xterm-patch-text.mjs
(pure text: no checkout, no build) and the vendored-xterm assertions move out of
the packaging contract into xterm-webgl-runtime-contract.test.mjs.
Tests
-----
Four tests asserted upstream bugs that are now fixed, not Orca behaviour:
- xterm-user-scrolling-contract pinned headless and core by version string.
Upstream bumps each package only when its own output changes, so headless 302
and core 303 are the same source. It now asserts they share a commit.
- Five CSI 3 J assertions expected a reader stranded at the top after an erase.
Upstream #6081 clears isUserScrolling there, so the erase releases them to the
bottom instead. Orca's pin still lands them correctly, because its parser
handler observes the erase before xterm's own handler runs.
- The IME transaction test hard-coded the xterm version; it now reads the
installed package, since the point is that bundle, map and version agree.
- The Electron runtime contract asserted Orca's old clearModelGeneration. Shared
atlas invalidation is upstream's now, so it asserts pageLayoutVersion on the
resolved dependency, plus the Orca-only hunks on the patch.
Verified: 66,008 unit tests, mobile's 3,863, the four WebGL atlas e2e specs, and
`regenerate-xterm-patches.mjs --check` in sync on all three packages.
Left alone deliberately: resetAllTerminalWebglAtlases still fans out globally
even though clearTexture now self-heals siblings, and upstream #6068
(WebglAddon.dispose leaks the GL context) is still open.
* Drop the two unused WebGL atlas fan-out exports
resetAllTerminalWebglAtlases and presentAllTerminalPanesWithoutAtlasClear have
no callers, and had none at cadfc55102 either — the last call site went in
#6949, which routed reveal recovery through
resetAndRefreshAllTerminalWebglAtlases instead. Only a comment in
pane-manager.ts still named the first one; it now points at the live entry
point. scheduleRevealPresent leaves the registry's structural type with them,
though the manager method stays: terminal-visibility-resume.ts calls it
directly.
This is dead-code removal, not a consequence of the xterm bump. The live
recovery path is unchanged.
resetAndRefreshAllTerminalWebglAtlases stays, and so does the reveal-time
escalation in pane-reveal-repaint.ts. Upstream 299 does make a pane-local
clearTexture bump pageLayoutVersion so siblings rebuild on their next frame,
which is the bug the escalation was written for, but I could not demonstrate
that removing it is safe: with the escalation removed,
floating-workspace-shared-glyph-atlas.spec.ts still passed headful, and it also
passed with upstream's mechanism deliberately disabled (pageLayoutVersion
pinned to 0 in the installed bundle, verified present in the built renderer).
A guard that passes with the fix disabled cannot license removing the
workaround, so the escalation stays until that spec can reproduce the garbling.
Verified: pane-manager and terminal-pane suites (4,713 tests), typecheck, the
headful shared-atlas spec, and the three headless WebGL specs.
* Give the shared glyph atlas spec a trigger that can fail
floating-workspace-shared-glyph-atlas.spec.ts guards the corruption where one
terminal wiping the module-global atlas leaves sibling terminals drawing from
stale texture coordinates. Both of its tests drive that through a floating
panel reveal, and Orca's reveal paths escalate to a registry-wide atlas reset
that repaints every pane — so the recovery under test heals the damage before
the assertion runs, and the tests pass whether or not xterm propagates the
invalidation at all.
The new test clears the shared atlas straight through the floating manager with
the panel closed, so nothing else repaints the workspace terminal, then repaints
it with terminal.refresh(). That is the load-bearing detail: _updateModel skips
cells whose content is unchanged, so the refresh reuses vertices baked against
the pages that were just wiped, which is exactly the state the fix has to
recover from.
Verified as a discriminator rather than assumed. Pinning ITextureAtlas's
pageLayoutVersion getter to 0 in the installed bundle, which disables the
per-renderer invalidation upstream added in addon-webgl 0.20.0-beta.299, and
confirming that reached the built renderer:
fix intact: siblingClearIntact=true 1 passed
fix disabled: siblingClearIntact=false 1 failed
The failure renders the workspace terminal completely blank — stale coordinates
into a wiped atlas sample nothing. The two reveal tests pass unchanged in both
configurations, which is the gap this closes.
* Compare shared-atlas screenshots with tolerance instead of byte equality
Byte equality fails on sub-pixel antialiasing noise that leaves every glyph
legible, so the headful spec flaked under xterm 303. Reuse the existing
compareTerminalScreenshots helper: real stale-model corruption blanks the
terminal at ~3% of pixels, twice the helper's 1.5% threshold, so the looser
oracle keeps its teeth. Log the ratio so failures are diagnosable.
* fix(xterm): cancel empty deferred IME compositions
* test(xterm): strengthen runtime patch contracts
* fix(relay): scope PTY ids to mint epochs
* test(relay): treat minted PTY ids as opaque
* test(relay): pin mint-epoch id shape and restore spawn-sequence assertions
The epoch escaping had no test: dropping encodeURIComponent left the whole
relay suite green. Pin the three-field id shape against an epoch that carries
both separators, and cover a colon-bearing relay id through the unchanged
app-side SSH id wrapper.
subprocess.test.ts had traded `pty-1`/`pty-2` for `expect.any(String)`, which
discarded the invariant those two cases exist to prove: an early node-pty load
failure burns no sequence, a late spawn failure burns one.
* test(relay): mirror production epoch escaping in testPtyId
The harness built the expected id without the encodeURIComponent production
applies at the mint site. A test epoch carrying a reserved character would
diverge silently across ~40 assertions in 11 files.
Edit > Paste, context-menu Paste, Paste as plain text, Select All and Ctrl+V
were all no-ops in the Agent Dashboard terminal preview on Windows/Linux, while
the same commands worked in a real terminal pane. Three independent defects:
- The preview subscribed to the raw ui:appMenuPaste / ui:appMenuSelectionAction
IPC instead of claiming the renderer ownership events a pane claims, so
handleAppMenuPasteRequest fell through to the focused text control — which for
a focused terminal is xterm's hidden .xterm-helper-textarea. Now it claims
APP_MENU_PASTE_EVENT / APP_MENU_SELECTION_ACTION_EVENT with preventDefault()
and leaves text controls unclaimed for the native fallback.
- The pop-out window has no App shell, so nothing translated the menu IPC into
those ownership events. DashboardPopoutRoot now mounts useAppMenuPaste() and
useAppMenuSelectionActions().
- Plain Ctrl+V was deferred to an Edit-menu accelerator that does not exist on
Windows/Linux, where Orca draws its own titlebar. The isMenuPasteChord
carve-out is now darwin-only, matching TerminalPane.onKeyPaste.
Also honors terminalRightClickToPaste in the preview (selection copies, no
selection pastes, Ctrl+right-click falls through), and extracts the box-fit
transform into preview-terminal-box-fit.ts to keep the component under the
max-lines cap.
Fixes#15757
On a Windows host the runtime stores a WSL worktree as the UNC path Windows
sees, but a user inside the distro types the Linux spelling, so every `path:`
selector missed: `worktree show`, `terminal list --worktree` and
`worktree rm --worktree` all reported selector_not_found for a directory Orca
manages.
Translate once in the CLI, which is the only side that can prove which distro
the typed path belongs to — from its own UNC cwd, never from WSL_DISTRO_NAME,
which a Linux-native CLI also sets. The runtime's `path:` branch stays
exact-spelling-only for the same reason: this resolver feeds delete, so a
tail-only match would remove another distro's copy.