* fix(terminal): carry kitty flags through Preview snapshots and pair rele
Preview was omitting the live kitty mirror from the IME bridge and dropping kitty flags from snapshots, so every commit was evaluated at flags 0. A TUI that negotiated bit-3 (report_all_keys_as_escape_codes) would receive the legacy raw text it declined.
Now the snapshot carries proven kitty flags beside their sequence boundary, the forwarder reads flags once per commit, and bit-1 (report_event_types) commits are paired with exactly one release regardless of keyup/insertText ordering. Snapshot authorities expose only the active screen's proven flags, so an old host's absent field stays unknown rather than downgraded to a manufactured zero.
* fix(terminal): sync kitty flags and IME releases across snapshots
* trim wordinesss
* fix(terminal): settle owed IME release before fresh same-key press
When a keyup is lost and the same key is pressed again, settle the stale
record's owed release instead of discarding it — this maintains correct
IME state during recovery. Also refine Kitty flag propagation to only
carry proven baselines across snapshots, and tighten related comments.
* fix(terminal): gate kitty flags on sequence boundaries
- Remote snapshots only include flags when seq is present
- Daemon uses parsed flags value when defined
- Ensures correct flag ordering in snapshot replay
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.
typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.
oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.
Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom
electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
The scanner service runs under ELECTRON_RUN_AS_NODE and cannot access
packed files in app.asar. Without unpacking, the worker entry fails to
spawn, causing all OpenCode sessions to disappear in packaged builds.
* Strengthen plain-node-entry-guard with entry name validation
- Add buildStart hook to validate guarded entry names exist in rollup
inputs, preventing stale names from silently stopping guards
- Extend electron require detection to subpaths (electron/main, etc)
- Improve smoke test signal/exit handling and use constants
- Add comprehensive tests for entry validation and new behaviors
* Add SIGKILL escalation to plain-node-entry-guard timeout
Switch from spawnSync to async spawn to properly handle daemons that trap
SIGTERM. spawnSync's timeout only sends the signal and waits, so a daemon
that ignores SIGTERM causes the build to hang. The new runDaemonEntry
function escalates to SIGKILL after a grace period to enforce the deadline.
Configurable timeouts and grace periods via SmokeTimings type; closeBundle
hook becomes async to support the change.
* perf(renderer): park hidden remote browser screencasts
* test(renderer): pin stale-visibility term and parked-unmount teardown
Restores the WHY behind the '|| isDocumentVisibilityProvenStale()' term the
emulator-module dedupe dropped, and pins it plus the close-tab-while-parked
path with tests. Extracts the lifecycle harness so the spec stays under
max-lines without a suppression.
Co-authored-by: Orca <help@stably.ai>
* fix(ci): exclude test-support modules from the localization audit
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): refresh existing shared hook scripts when the CLI is no longer detected
A CLI that falls off PATH (moved npm prefix, relocated shim) keeps its user-wide
config invoking Orca's launcher script under ~/.orca/agent-hooks, but the
presence gate skips install() with no removal — freezing the script at whatever
Orca generated last. Anyone in that state kept the pre-#11568 more.com-leaking
.cmd forever, because no launcher script is ever deleted and Windows startup
deliberately skips shell PATH hydration.
Reconcile before gating: every existing shared launcher/statusline script is
rewritten to the current template on each install pass. Creating scripts stays
behind the presence gate — an existing file is proof of a prior install; a
missing one means the gate did its job. Amp and Hermes are deliberately absent:
they write provider-native plugin code with its own install lifecycle, not
shared launchers.
- refreshManagedScriptIfPresent() in installer-utils (no-op unless the file exists)
- refreshManagedScripts() on the 11 launcher-writing services (openclaude via
the shared Claude class)
- reconcile pass in installManagedAgentHooks before presence detection,
filtered by the agents option, best-effort per agent
- coverage gate: a launcher written to ~/.orca/agent-hooks without a matching
refresher entry fails the suite, in both directions
* perf(agent-hooks): refresh launchers off the main thread
* test(agent-hooks): keep refresh mode assertion POSIX-only
* feat(ai-vault): isolate scanning in service processes
* fix(ai-vault): retire idle service processes
* fix(ai-vault): discard unverified cache processes
* fix(ai-vault): clear relay sidecar cancel watchdog on acknowledgement
A cancelled relay call is settled before its 2s cancel watchdog is armed, so the acknowledgement path bailed out of settle() before clearing the timer. The watchdog then faulted a healthy sidecar two seconds after every aborted scan, killing whatever request had since become active.
* fix(ai-vault): clear the pending restart before scheduling another
recordFault overwrote this.timer, stranding a restart that dispose() could no longer cancel.
* refactor(ai-vault): drop the orphaned first-prompt IPC wrapper
session-first-user-prompt-handler.ts now owns this entry point and routes through the service; the copy left in the read module had no callers.
* fix(ai-vault): retry a faulted cold start before surfacing it
A slow first start surfaced a raw 'did not become ready' error to the caller even though the supervisor was already respawning. Requeue an unsent call once onto the scheduled respawn instead.
Also stop arming the cancellation watchdog for a call the child never received: no acknowledgement is coming, so it killed a healthy service and stalled the lane.
Invalidation bookkeeping and ready-waiter construction move to the state module to stay under the max-lines cap.
* fix(ai-vault): give relay title reads their own lane
Before this branch the relay read title files directly, concurrently with scans. Routing both through one sidecar lane put title resolution behind a list scan that may run up to 130s, so SSH tab titles could lag minutes behind.
Split cache and interactive lanes in both the relay client and the sidecar entry, mirroring the desktop service.
Also: clear the ready deadline on fault, so a sidecar that dies before ready cannot fault its healthy replacement five seconds later; retry an unsent call once across a respawn; and skip the cancellation watchdog for a call the sidecar never received.
Restart/circuit bookkeeping moves to its own module, mirroring the desktop policy, to stay under the max-lines cap.
* fix(ai-vault): degrade relay title resolution on sidecar failure
listSessions already returns a host issue when the sidecar is unavailable; titles propagated the raw RPC error instead. Return no titles so callers fall back to preview text, and keep cancellation propagating.
* fix(ai-vault): scrub the service child environment
The children are forked with a 384 MiB heap cap and no loader, but both
spawn sites handed them the full parent environment, so an exported
NODE_OPTIONS silently raised the cap or --require'd code into them.
Allowlist both, following the plugin worker. The desktop child keeps the
eleven agent-root overrides it resolves its own roots from; the relay
sidecar takes remoteHome and hostPlatform from its init message and so
needs none of them. Both children share one priority module while they
share this one.
* fix(ai-vault): soft-disable relay vault when the service is missing
A missing service threw out of the constructor, so a Vault wiring bug
would abort relay startup and take every PTY on the host with it. The
unsupported-platform branch three lines above already treats a Vault
failure as a soft disable; do the same here.
Threading the service through the two handlers instead of a field also
retires the definite-assignment assertion the throw was propping up.
* fix(ai-vault): drain consumed cache invalidations
invalidatedPaths was re-applied in every request's finally and never
drained, so once N paths had been invalidated every later request paid N
evictions for the life of the process; the 4096 cap only bounded how bad
that got.
The re-apply exists to cover a read that overlapped the invalidation, so
drain once nothing is executing. Clearing unconditionally would drop the
re-apply for a request still running on the other lane.
* fix(ai-vault): keep a busy child through slow invalidation acks
invalidate() reused the 5s ready budget as its acknowledgement deadline
and killed the child on expiry, so a delete issued during a large scan
could kill a healthy process mid-scan and burn a slot toward the restart
circuit.
Fault only when nothing is executing. Fork IPC ordering already puts the
invalidation ahead of any later request, so a busy child owes no ack
here, and the 130s/15s request deadlines still catch a wedged one.
The start-retry predicate moves to the state module to stay under the
line cap, matching the shape the relay client already uses.
* fix(ai-vault): report a failed local scan as a host issue
A local-scope scan let its error escape to the renderer, which paints it
over the session list. Service supervision now produces those errors, so
"AI Vault service restart circuit is open." replaced the list.
Route local scope through the degradation the all-hosts leg and every SSH
leg already use, so it lands as a retryable host issue row instead. Same
result shape either way, so no IPC or wire contract changes.
* test(ai-vault): cover the relay restart circuit transitions
The relay policy shipped without tests. Pin both circuit edges, the
aging-out case, the forced-refresh reopen the relay has and the desktop
does not, and the backoff schedule.
* fix(ai-vault): keep the OpenCode roots in the service child env
The scrubbed allowlist dropped XDG_DATA_HOME and OPENCODE_DB, which the child
reads to locate the OpenCode store and database. The pre-PR worker thread
inherited them, so a user who sets either lost every OpenCode session.
* test(ai-vault): anchor the service spawn env assertion
* fix(pty): bound cooked reply queue
* fix(pty): bound cooked reply queue
Implement bounded storage for cooked-echo-safe replies: 64 pending
replies and 4096 UTF-16 code units. Shed oldest replies on overflow,
never ordinary input. Add drain failure containment with generation
fencing to prevent stale operations from clearing fresh input after
clear() reuse.
* fix(pty): report pty id in drain failures
When the async drain yields, the owner may rebind to a different PTY
before the failure surfaces. Pass the failing pty id so the transport can
ignore stale failures from a rebound owner. Also tighten the pending
reply queue size bound to prevent half-written entries.
* Add daily macOS dev build release channel
Publish once-daily signed macOS builds from main at a dedicated cadence,
separate from hourly (too noisy) and release branches (too infrequent).
Builds are notarized and installable via the updater, but unvetted —
published to stablyai/orca-daily rather than the main repo to avoid
evicting stable/RC entries from the releases feed.
* fix lint
* fix commit
* Add third token mint to daily macOS build workflow
The upload step's 2x45m retry budget can outlive the one-hour token, so a third
is minted after it for verify and cleanup operations. Release notes are moved to
a file to ensure consistency between draft creation and publish. Daily channel
description updated with specific UTC release time.
* Strip liveness gate from AI Vault session delete
Delete now requires only path validation + user confirmation — no process
roster, no liveness check, no quiescence, no ownership ledger.
Co-authored-by: Orca <help@stably.ai>
* Remove obsolete AI Vault liveness delete reliability gate
Session delete no longer checks process liveness, so drop the
manifest entry that still referenced the deleted test files.
* minor fix
---------
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): show a preedit the IME resumes without a compositionstart
Typing 2-Set Korean shows committed syllables but not the in-progress jamo, so
the user composes each syllable blind. Long-standing hole in the vendored
terminal library, not a regression: the same test fails identically against the
bundle this branch starts from.
The `.active` class that CSS keys `display: block` off is added only in
`compositionstart` and dropped in `_finalizeComposition`. Some IMEs (observed on
Windows/WSL Korean) resume a composition with a bare `compositionupdate` and no
second `compositionstart`, by which point `compositionend` has already hidden the
overlay, so the resumed preedit is written into a hidden element and never
positioned. `updateCompositionElements` also early-returned on `!_isComposing`,
so it would not lay the overlay out either.
Re-show the overlay on an update that carries data, and key the layout guard on
the shown overlay instead. `_isComposing` is deliberately left alone, so no
commit bookkeeping changes and `onData` stays byte-identical. The two guards are
equivalent on every pre-existing path: `compositionstart` sets both,
`_finalizeComposition` clears both.
The bundle hunks are the same two edits applied to the shipped minified output;
the sourcemaps are carried through unchanged.
* test(terminal): prove the resumed-preedit fix against a recorded Windows capture
The synthetic test pins the shape; this replays events a real Microsoft Korean
IME emitted on Windows/WSL. The capture holds three compositionupdates that
resume a composition with no second compositionstart — the exact ordering that
wrote the preedit into a hidden overlay.
Without the fix all three report shown:false; with it all three are visible.
Fixture derived from the sealed 11919-windows-wsl-current capture, which is
read-only and unmodified.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): stop the recorded Hangul fixture pinning a derivation artifact
The capture logs each event twice — a dispatch record and a batched next-frame
re-log. Deriving from both replayed every event twice, which made three
compositionupdates appear to land after a session had ended. Filtered to
dispatch records the capture holds zero resumes and 11 balanced sessions, so
the previous toHaveLength(3) was pinning an artifact of the derivation.
Re-scoped to what the capture does prove: the preedit stays visible across all
37 real updates. Verified by reverting the patch that this passes either way,
so it is coverage and the synthetic test remains the discriminator. Both facts
are now stated in the file.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore the preedit visibility patch onto its own branch
The previous commit accidentally reverted it: checking main's patch and lockfile
into the worktree to test whether a test discriminates also stages them, so the
commit that followed swept them up.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): claim printable keydowns structurally so committed text survives
Co-authored-by: Orca <help@stably.ai>
* chore(reliability-gates): retarget the IME forwarding gate after the allowlist removal
The gate listed terminal-ime-input-source.test.ts, which went with the
input-source allowlist. Points at the substituted-text commit test instead,
which covers what the gate is actually protecting: text committed outside a
composition session reaching the pty exactly once.
Co-authored-by: Orca <help@stably.ai>
* docs(terminal): record why withholding a claimed keydown needs no timer
The predicate withholds a keydown's byte until the commit arrives, so a key the
IME eats without committing would be dropped. Measured across the recorded
corpus that case does not occur, and the browser marks IME-owned presses on the
keydown itself. Both facts belong next to the predicate rather than only in a
handoff note, since the obvious fix for the imagined gap is a timer, and a timer
here once wrote a newline the user never typed.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): pin the kitty all-keys-as-escape-codes hole explicitly
Flag 8 asks for every printable key as an escape code; this path sends the
committed text raw instead. That is a deliberate trade, not an oversight, but it
was untested — the suite only covered the disambiguate flag. Pinning it makes
the choice visible and records the gate to use if it ever needs closing.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): keep the kitty key-release report for presses that reached the pty
Claiming the keyup unconditionally suppressed xterm's release report. That was
sized for the old design, which claimed only a short punctuation list; the
structural claim takes every printable keydown, so on macOS an app that
negotiated kitty report_event_types stopped seeing releases for ordinary typing
and would treat every printable key as held down.
Suppress the release only when the press put nothing on the wire — swallowed by
the input source, or owned by a composition transaction. xterm emits nothing
from keyup unless kitty report_event_types (or win32 input mode) is on, so
letting it through is inert everywhere else.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): pin the macOS key-binding substitution against #11170
An OS key-binding remap of the character a Korean layout puts on Backquote
is honoured everywhere on macOS except the terminal, which sent the raw
layout character to the PTY. The substitution is applied inside the system
text input path, so it exists only on keypress.charCode and the input
event's data; the keydown still carries the layout character. Nothing needs
to parse the binding file - Chromium has already applied it by the time
`input` fires.
The reported build sent the raw character. A later punctuation table
happened to list that one character, which closed the issue by enumeration
rather than by design, and the structural claim removes the table entirely.
Without a test the fix could regress silently on a change that never
mentions the issue.
Replays the reporter's captured event shape and pairs it with the same
physical key carrying no substitution, so a fix that rewrote the Backquote
position unconditionally would fail. Discrimination checked by mutation:
suppressing the structural claim, and separately removing the single table
character on a pre-rewrite tree, each make the replay send the raw layout
character while both negatives stay green.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): cover the other Korean layout on the remapped key
Korean layouts disagree about what the backquote position produces: two of
them give the currency sign the issue reports, one gives an asterisk. One
key-binding entry has to survive either, but honouring the substitution by
listing characters covers only the ones someone remembered to list - which
is why the reported character worked and this one did not.
This arm discriminates without a mutation: it fails on the pre-rewrite tree
and passes on the structural claim. The harness supplies no input-source
classification, modelling a source the older design did not recognise,
including the window before its async probe resolves. With the source
recognised the older design claimed all ASCII punctuation and covered this
too, so the gap was real but conditional; the header says so rather than
letting the failure read as unconditional.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): drop the Won-setting arm from the keybinding-dict replay
The Won-to-backquote feature was reverted, so the module this replay imported
no longer exists. The #11170 coverage is unaffected: the remaining arms pin the
substitution itself, which never depended on that setting.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): correct the fixture's provenance count
The header said two derived cases when there are four, and counted the
second layout arm as a negative when it is a positive. Each case already
carries its own recorded flag and note; this stops the summary contradicting
them, which matters in the one field whose whole job is provenance.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(i18n): restore the Japanese renderings the brand revert left in Latin
Before #12113 landed the canonical-rendering guard, the brand-mistranslation
revert treated ターミナル/エージェント/コミット/リポジトリ as machine-translation errors
and rewrote them back to English on every repair run. The guard stops new
damage, but ~700 values still carry it, so the Japanese UI reads
"この terminal を閉じると、agent の現在の作業が停止します。"
This heals what the catalog already holds and closes the defects the same
pipeline introduced elsewhere:
- Relocalize the four generic terms inside Japanese sentences. Both sides are
anchored on an adjacent Japanese character and reject a `-`, word character
or `.` neighbour, so `--agent`, agents.md and "Agent SDK" keep their spelling,
and the 和欧間スペース survives in front of an interpolation.
- Preserve style blocks and command/identifier values in English. MT had
rewritten a CSS selector to [データスラッシュメニュー], `background:` to `背景:` and a
keyframe name to ブラウザフラッシュ, and had turned `pnpm install` into
`pnpmインストール` and a toast dedup id into 陳腐なエージェント行. The same values are
restored in ko, zh and es.
- Drop the phrase fixes that stripped ~してください from validation messages,
which left 30 prompts ending in a bare noun.
- Settle terminology and typography: 紛争→競合, 資格情報→認証情報, 未知→不明,
プロフィール→プロファイル, the full-width ellipsis, no separator space inside
compound katakana, and one long-vowel form per word.
- Fix literal-translation errors (ナメクジ for slug, ミスター for MR, ランニング for
Running, 高い/中くらい for priority labels) and complete the truncated Kimi
status-bar description.
- Translate 570 values that were still English, 485 of them fragments the
catalog had left untouched and 85 newly added strings.
The catalog is regenerated with `repair-locale-catalog.mjs` and the run is
idempotent: a second pass reports 0 leaf updates.
* fix(i18n): keep the leading space in concatenated Japanese fragments
* fix(i18n): close the self-review findings in the Japanese repair rules
Seven defects the relocalization pass introduced or left behind:
- `git commit` and `orca terminal` are two-word commands, and the position
guards only looked at the character before the term, so the second word was
katakana-ized ("git コミットが失敗したとき"). A command-head lookbehind covers
git/gh/glab/orca/npm/pnpm/npx/yarn/docker/kubectl. A following Latin word now
also blocks the rule, so "Agent SDK" keeps its spelling as the comment claims.
- `on: 'オン'` matched every "on" in the catalog, including the preposition in
the external-automation delete confirmation, which rendered as
"外部ソース オン myhost". Moved to a new ja key-override module so the toggle
states and the preposition can differ; the module keeps
locale-key-overrides.mjs under max-lines.
- The phrase fixes write Cookie and fast-forward back in Latin, but neither was
in CJK_LATIN_SPACED_TERMS, so the 和欧間スペース was missing in five values.
- Three overrides spelled a half-width `...` that the ellipsis phrase fix
rewrites anyway, so the comment described the opposite of the behavior.
- Two descriptions render as [text] <code> [text]. The Japanese closed the
sentence with 。 and dropped the "such as" / "like" hand-off, leaving the code
span outside the sentence in the Git and quick-command panes.
- `' vs {{value0}}'` and `' · {{value0}} external'` were missed by the previous
leading-space fix, so "3 変更されたファイルvs main" rendered without a gap.
Adds three regression cases: two-word commands, the Latin-only label, and the
spacing for terms the phrase fixes reintroduce.
* fix(i18n): correct the Japanese an external review flagged
73 findings from a ChatGPT review of the full changed-value list, each
reproduced against the shipped catalog before being fixed:
- Two commands were translated into text that does not run: `pnpm playwright
test` became `pnpm プレイライトテスト` and `gh auth login` became `GH 認証ログイン`.
Both are pinned to English. A third value corrupted an identifier,
rendering `packages/web` as `package/web`.
- Two descriptions carried a stale translation with no relation to their
English source, and one had another row's text entirely.
- Syntax was misread in six values: `Command line Orca runs when…` read as
"the command line runs", `Let programs … copy` as "copy the programs",
`Dim files matched by .gitignore` kept `Dim` as a noun, and
`powers live quota reads` became 強化.
- Wording that changed the warning: `diffs may miss recent commits` read as
the commits being lost, `before merging is unblocked` as un-merging, and
`the newer disk content` as new content.
- `host` was rendered as サーバー in eight values even though it covers SSH
hosts, `worktrees` as ワークスペース, and `on this host` as リモート.
- Git vocabulary translated to its everyday sense: `upstream`→上流,
`staged changes`→段階的な変更; and identifiers `lan`/`deploy` were localized.
- Instructions to the user had lost their imperative (…をインストールします),
three validation messages still ended in a bare noun, and two completion
notices read as future tense.
- Assorted breakage: 窓 for a desktop window, 分割分割線, ターミナルパネル for
Terminal Panes, オプション for the macOS Option key, Herme for Hermes,
and a reversed noun phrase in the repo-icon import error.
The `Open` action needed a key override: bare "Open" is the PR/issue state in
16 of 18 places, and only the browser download row and the checks panel use it
as the verb, matching what ko/zh/es already do.
Three fixes had to be reworded rather than written literally: the existing
新しい→新規 and 実験的→実験的機能 phrase fixes run after value overrides and
turned 新しい名前 into 新規名前 and 実験的な into 実験的機能な.
Not fixed: `{{value0}} site{{value1}} connected` still shows the plural-suffix
placeholder, which needs the code change already listed in the PR notes.
* fix(i18n): sweep the whole catalog for the defect classes the reviews found
The external review covered a sample. This runs each of its finding classes as
a detector over all 11,857 values and fixes what they turn up, as durable
pipeline rules rather than one-off value edits where the class recurs:
- host は サーバー ではない. Orca's "host" covers SSH hosts and this computer,
so a phrase fix rewrites サーバー to ホスト whenever the English says host and
does not also say server, where the two are deliberately distinct (15 values).
- worktree joins the guarded generic-term list, so the seven values still
reading "worktree を削除" match the 205 that already say ワークツリー.
- Git vocabulary and brands restored from their everyday sense: 上流→upstream,
起源→origin, 段階的な変更→ステージ済みの変更, エルメス/ヘルメス→Hermes,
パワーシェル→PowerShell, アヒルアヒル→DuckDuckGo. All five terms are added to
CJK_LATIN_SPACED_TERMS so the restored Latin keeps its 和欧間スペース.
- 14 more code values pinned to English: Tailwind class strings
(size-4 text-muted-foreground → サイズ 4 テキストミュート前景), git refs
(origin/main → 原点/メイン), sample hosts (example.com → 例.com) and spec
fixtures (dashboard.spec.ts → ダッシュボードの仕様).
- 12 instructions regained their predicate (…を選択。 → …を選択してください。),
and 窓のぼかし, macOSのオプションキー, 中くらいのセクション見出し are corrected.
Checked and deliberately not changed: toast notifications that end in 〜しました
(237) read correctly for a completion notice, and setting descriptions ending in
〜します (200) describe what the setting does rather than instructing the user.
Sound preset names stay katakana, matching the rest of that list.
Adds three regression cases covering the host rule and its server exception,
the Git/brand restorations, and the newly pinned class strings and refs.
* fix(i18n): keep Agent in Latin in the Japanese catalog
Japanese developer UIs conventionally leave Agent unlocalized — it names Orca's
own concept rather than the everyday word — so the ja catalog now writes it in
Latin and only normalizes the case, so no sentence mixes "agent" and "Agent".
The 和欧間スペース comes from the existing spaced-term list.
This is the one term where ja diverges from locale-generic-ui-terms.mjs, which
lists エージェント as the expected rendering; ターミナル, コミット and リポジトリ
follow it exactly. Three test expectations are updated to match, and the PR
description flags the divergence so a maintainer can ask for it to be reverted
— it is a single rule in locale-ja-phrase-fixes.mjs.
The guards are unchanged, so `--agent`, agents.md, `orca agent` and "Agent SDK"
keep their spelling.
* fix(i18n): preserve selectors with no declaration block, and sharpen progress labels
CodeRabbit was right that `STYLE_BLOCK` only matched a selector when it carried
a declaration block or an attribute selector. `div.pricing-grid >
div.card.starter:nth-of-type(1) > a.cta` was unprotected and only survived
because MT happened to leave it alone.
A value now also counts as style when every whitespace token is selector-shaped
and at least two carry a class, id, pseudo or attribute. The first attempt at
that threshold counted a sentence-final period as a selector join and froze 259
ordinary two-sentence strings in English; the marker must now be followed by a
letter, so `Show live workspace ports. Click it for …` stays translated. Both
directions are pinned by tests.
The predicate moved to locale-style-values.mjs to keep locale-translation-policy
under max-lines.
A DeepL cross-check of the whole changed-value list surfaced six more:
- Progress labels had lost their 〜中: `Creating...` read 作成…, `Reopening...`
read 再開…, `Thinking…` read 考え…
- `Hide from sidebar` / `Show in sidebar` carried a stale 左サイドバーから Orca
Mobileを削除 in one of six places, with the 和欧間スペース missing too
- `Recent or tab strip.` was 最近のまたはタブストリップ。, which is not Japanese
Everything else DeepL flagged was this PR's settled terminology (Agent, Issue,
ホスト, 競合, fast-forward) or a fragment where DeepL had no surrounding context.
* fix(i18n): treat proper nouns as brands, and cut the comment noise
Review feedback, all verified against the catalog:
- Hermes, PowerShell, Mermaid, Claude Code and VS Code are proper nouns, so
they belong in BRAND_MISTRANSLATIONS with the other product names, not in the
ja phrase fixes. Claude コードセッション, マーメイドダイアグラム and VS コードで開く
were unfixed until now because nothing covered them.
- Sweeping every proper noun against the catalog found more the same way:
Orca IDE rendered as OrcaIDE, and Git had no 和欧間スペース in eight values,
because neither term was in CJK_LATIN_SPACED_TERMS.
- Settings-search keywords are lowercase, so the brand revert (case-sensitive)
cannot reach them. windows read 窓, gitignore read ギティ無視, component read
成分 (the chemistry sense), compose read 作曲する, and neovim/hermes/powershell
were transliterated. Pinned by value.
- The override sources still spelled エージェント in 34 places even though the
shipped value is Agent, so the file no longer said what it produced. The
代理人 rule also ran after the Agent rule, so a future MT 代理人 would have
stayed katakana; it now maps straight to Agent.
- Two of my own overrides had no matching English source left and were dead.
Comment volume is cut from 80 added lines to 25. The locale modules carry 2-8
comment lines each, and this PR was running an order of magnitude over that;
what is left is one line per genuinely non-obvious constraint.
BRAND_MISTRANSLATIONS moves to its own module to keep locale-translation-policy
under max-lines.
* fix(i18n): give every English string one Japanese rendering
215 English sources had two or more Japanese forms in the catalog, so the same
button read 削除 in one place and 削除する in another. Several of the variants
were also wrong outright: Hide read 隠れる, Sort read 選別, Run read 走る, and
"Don't ask again" read 二度と聞かないでください.
Picks follow the catalog majority — action labels drop する, completion notices
use 〜しました, status labels are 体言, and デフォルト / フィルター / スコープ /
ディストリビューション / 並べ替え win their pairs. A value override is keyed on the
English string, so one entry makes every occurrence agree.
Three key overrides contradicted the value they now share and were realigned;
seven pairs remain and are deliberate, where the same English is a different
thing per call site (Cursor the product vs the caret, Open the PR state vs the
action, Forward the port vs the browser button).
Grab mode picks a page element and hands it to the AI, but read 掴む as a button
and グラブモード in the web-client notice while the rest of the feature said 取得.
Also from CodeRabbit: a single dotted, colon or bracketed token — button.primary,
a:hover, wsl.exe, localhost:3000 — is code whether it names a selector, a file
or a host, so it is preserved too. That caught localhost:3000 reading
ローカルホスト:3000. And an override still spelled `Agent 、` with a space before
the Japanese comma.
The unified map lives in locale-ja-unified-values.mjs to keep the override file
under max-lines.
* fix(i18n): scope the catalog change to ja, and drop the zh-only Terminal form
The ja brand list carried 端子 as a Terminal mistranslation, but 端子 is the
zh rendering and never appears in ja — the Japanese one is 端末, which the
phrase fixes already rewrite to ターミナル. Listing 端末 here instead would be
wrong in the other direction, because this list reverts to Latin. The round-5
expectation moves to 端末 → ターミナル, which is behaviour that can actually occur.
The ko/zh/es identifier restorations are pulled back out; they are real bugs
(pr-view read PR视图, pnpm install read pnpm 설치) but they belong in their own
PR rather than a Japanese one. One zh line has to stay: without it,
verify-localization-catalog refuses the new stale-agent-row-{{value0}} entry
because repair would rewrite the Chinese text to English. That value is a toast
dedup id, not copy.
* chore(i18n): regenerate the Japanese catalog on the current base
The branch point moved forward 68 commits, which added 45 keys to en.json. The
catalog is rebuilt from that base so the repair run stays idempotent, and the
one string the new keys left in English is translated.
* fix(i18n): preserve the code strings rendered inside <code> and font-mono
@smwbev scanned by call-site context rather than value shape — a translate()
that renders inside <code> or a font-mono element is code — and found values
the shape-based list missed. Reproduced against the catalog and fixed here:
- {prompt} read {プロンプト}. It is the substitution token for the commit-message
prompt template, so a translated one never substitutes.
- /goal read /ゴール, which is not a slash command.
- npm run dev read npm 実行開発, in the same font-mono placeholder role as
pnpm install.
- nbformat read nbフォーマット.
upstream and upstream/main were already covered. The remaining values in the
scan break in zh rather than ja, but the entries are locale-agnostic, so
orca.yaml, LIN-329, GH #1799 and orca · zsh are pinned here too and #13124
restores the Chinese catalog.
* fix(i18n): repair Japanese translations of code, CLI, and URLs
Code samples, CLI arguments, URLs, and template variables must remain
executable. Adds to NEVER_TRANSLATE_VALUES to prevent future mistakes,
fixes ja.json mistranslations, and adds regression tests.
---------
Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
* fix(terminal): show a preedit the IME resumes without a compositionstart
Typing 2-Set Korean shows committed syllables but not the in-progress jamo, so
the user composes each syllable blind. Long-standing hole in the vendored
terminal library, not a regression: the same test fails identically against the
bundle this branch starts from.
The `.active` class that CSS keys `display: block` off is added only in
`compositionstart` and dropped in `_finalizeComposition`. Some IMEs (observed on
Windows/WSL Korean) resume a composition with a bare `compositionupdate` and no
second `compositionstart`, by which point `compositionend` has already hidden the
overlay, so the resumed preedit is written into a hidden element and never
positioned. `updateCompositionElements` also early-returned on `!_isComposing`,
so it would not lay the overlay out either.
Re-show the overlay on an update that carries data, and key the layout guard on
the shown overlay instead. `_isComposing` is deliberately left alone, so no
commit bookkeeping changes and `onData` stays byte-identical. The two guards are
equivalent on every pre-existing path: `compositionstart` sets both,
`_finalizeComposition` clears both.
The bundle hunks are the same two edits applied to the shipped minified output;
the sourcemaps are carried through unchanged.
* test(terminal): prove the resumed-preedit fix against a recorded Windows capture
The synthetic test pins the shape; this replays events a real Microsoft Korean
IME emitted on Windows/WSL. The capture holds three compositionupdates that
resume a composition with no second compositionstart — the exact ordering that
wrote the preedit into a hidden overlay.
Without the fix all three report shown:false; with it all three are visible.
Fixture derived from the sealed 11919-windows-wsl-current capture, which is
read-only and unmodified.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): stop the recorded Hangul fixture pinning a derivation artifact
The capture logs each event twice — a dispatch record and a batched next-frame
re-log. Deriving from both replayed every event twice, which made three
compositionupdates appear to land after a session had ended. Filtered to
dispatch records the capture holds zero resumes and 11 balanced sessions, so
the previous toHaveLength(3) was pinning an artifact of the derivation.
Re-scoped to what the capture does prove: the preedit stays visible across all
37 real updates. Verified by reverting the patch that this passes either way,
so it is coverage and the synthetic test remains the discriminator. Both facts
are now stated in the file.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore the preedit visibility patch onto its own branch
The previous commit accidentally reverted it: checking main's patch and lockfile
into the worktree to test whether a test discriminates also stages them, so the
commit that followed swept them up.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): claim printable keydowns structurally so committed text survives
Co-authored-by: Orca <help@stably.ai>
* chore(reliability-gates): retarget the IME forwarding gate after the allowlist removal
The gate listed terminal-ime-input-source.test.ts, which went with the
input-source allowlist. Points at the substituted-text commit test instead,
which covers what the gate is actually protecting: text committed outside a
composition session reaching the pty exactly once.
Co-authored-by: Orca <help@stably.ai>
* docs(terminal): record why withholding a claimed keydown needs no timer
The predicate withholds a keydown's byte until the commit arrives, so a key the
IME eats without committing would be dropped. Measured across the recorded
corpus that case does not occur, and the browser marks IME-owned presses on the
keydown itself. Both facts belong next to the predicate rather than only in a
handoff note, since the obvious fix for the imagined gap is a timer, and a timer
here once wrote a newline the user never typed.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): pin the kitty all-keys-as-escape-codes hole explicitly
Flag 8 asks for every printable key as an escape code; this path sends the
committed text raw instead. That is a deliberate trade, not an oversight, but it
was untested — the suite only covered the disambiguate flag. Pinning it makes
the choice visible and records the gate to use if it ever needs closing.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): keep the kitty key-release report for presses that reached the pty
Claiming the keyup unconditionally suppressed xterm's release report. That was
sized for the old design, which claimed only a short punctuation list; the
structural claim takes every printable keydown, so on macOS an app that
negotiated kitty report_event_types stopped seeing releases for ordinary typing
and would treat every printable key as held down.
Suppress the release only when the press put nothing on the wire — swallowed by
the input source, or owned by a composition transaction. xterm emits nothing
from keyup unless kitty report_event_types (or win32 input mode) is on, so
letting it through is inert everywhere else.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): show a preedit the IME resumes without a compositionstart
Typing 2-Set Korean shows committed syllables but not the in-progress jamo, so
the user composes each syllable blind. Long-standing hole in the vendored
terminal library, not a regression: the same test fails identically against the
bundle this branch starts from.
The `.active` class that CSS keys `display: block` off is added only in
`compositionstart` and dropped in `_finalizeComposition`. Some IMEs (observed on
Windows/WSL Korean) resume a composition with a bare `compositionupdate` and no
second `compositionstart`, by which point `compositionend` has already hidden the
overlay, so the resumed preedit is written into a hidden element and never
positioned. `updateCompositionElements` also early-returned on `!_isComposing`,
so it would not lay the overlay out either.
Re-show the overlay on an update that carries data, and key the layout guard on
the shown overlay instead. `_isComposing` is deliberately left alone, so no
commit bookkeeping changes and `onData` stays byte-identical. The two guards are
equivalent on every pre-existing path: `compositionstart` sets both,
`_finalizeComposition` clears both.
The bundle hunks are the same two edits applied to the shipped minified output;
the sourcemaps are carried through unchanged.
* test(terminal): prove the resumed-preedit fix against a recorded Windows capture
The synthetic test pins the shape; this replays events a real Microsoft Korean
IME emitted on Windows/WSL. The capture holds three compositionupdates that
resume a composition with no second compositionstart — the exact ordering that
wrote the preedit into a hidden overlay.
Without the fix all three report shown:false; with it all three are visible.
Fixture derived from the sealed 11919-windows-wsl-current capture, which is
read-only and unmodified.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): stop the recorded Hangul fixture pinning a derivation artifact
The capture logs each event twice — a dispatch record and a batched next-frame
re-log. Deriving from both replayed every event twice, which made three
compositionupdates appear to land after a session had ended. Filtered to
dispatch records the capture holds zero resumes and 11 balanced sessions, so
the previous toHaveLength(3) was pinning an artifact of the derivation.
Re-scoped to what the capture does prove: the preedit stays visible across all
37 real updates. Verified by reverting the patch that this passes either way,
so it is coverage and the synthetic test remains the discriminator. Both facts
are now stated in the file.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore the preedit visibility patch onto its own branch
The previous commit accidentally reverted it: checking main's patch and lockfile
into the worktree to test whether a test discriminates also stages them, so the
commit that followed swept them up.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): extend the shell-ready startup barrier to fish (STA-3417)
Fish never emitted the OSC 777 shell-ready marker, so agent launch
commands were written into the PTY while fish/Starship were still
initializing: the daemon path wrote them synchronously at session
create and the local path blind-wrote ~30ms after the first output
byte. The command was echoed by the kernel but never executed.
- shell-templates: shared fish --init-command that emits the marker
once on the first fish_prompt event (the earliest point fish's own
reader owns the PTY, mirroring zsh's zle-line-init marker)
- daemon shell-ready: fish joins the startup barrier so the launch
command queues until the marker (timeout fallback unchanged)
- local-pty-shell-ready: fish launch config gains the marker wrapper
- codex-startup-delivery/tui-agent-startup: omp/pi/opencode plans now
request shell-ready delivery (codex parity) so the SSH renderer path
also waits for the prompt; plain payload-free codex stays on the
markerless fast path
* fix(terminal): answer DA1 past the shell-ready barrier
The barrier queues all inbound input until the ready marker, including the
renderer's DA1 reply. A shell that withholds its first prompt until DA1 is
answered — fish waits 10s — therefore never emits the marker that would
release the reply it is waiting for. Measured: 10.37s to launch an agent,
versus 0.35s once the reply lands.
Answer DA1 from the daemon while the barrier holds, writing straight to the
subprocess so the reply bypasses the queue, and consume the query so the
renderer's xterm cannot also reply. Released on ready, timeout, or dispose,
handing DA1 back to the renderer for steady state.
Consolidates the identical DA1 handler the ConPTY override already used.
* fix(terminal): prevent duplicate startup DA1 replies
* fix(ci): run the root-directory guard on stock macOS bash 3.2
The guard script builds its base-tree lookup with `declare -A`, which
needs bash 4+. Its test spawns plain `bash` from PATH, and stock macOS
has shipped /bin/bash 3.2 since 2007, so on any Mac without a Homebrew
bash the script exits 2 before asserting anything and the default
`pnpm test` suite fails 3 of the guard's 4 cases. Machines with a
Homebrew bash on PATH never see it, which is why it went unnoticed.
Replace the associative array with a plain-array linear scan. Root
directories number in the dozens, so the O(n^2) membership check is
negligible, and the NUL-delimited reads that protect unusual filenames
stay as they were. The empty-array expansion is guarded for
`set -u` under bash 3.2.
All four guard tests now pass with /bin/bash 3.2; behavior under CI's
bash 5 is unchanged.
* fix(ci): run the root-directory guard under node instead of bash
The guard is the only check in the repo written in shell, and it used
`declare -A`, which stock macOS `/bin/bash` 3.2 does not have — so the
guard's own test suite failed 3 of 4 cases on any Mac without a Homebrew
bash. CI never noticed because runners ship bash 5.
Porting it to node removes the interpreter-version variable instead of
working around one construct: node is what the sibling script in this
directory already uses, it is the runtime that runs the test, and the
NUL-delimited read is the same shape as check-changed-code-quality.mjs.
It also drops a latent false pass — a failing `git ls-tree` inside the
shell's `< <(...)` was not caught by `pipefail`, so the read loop saw
nothing and the guard reported success. `execFileSync` throws instead,
which is why the two `git rev-parse --verify` probes are no longer
needed.
Output and exit codes are otherwise unchanged; the usage line now prints
node's script path where the shell printed `$0`.
Tests pin each guarantee and fail when it is reverted: NUL-delimited
reads so odd paths are reported unmangled, exit 2 on bad usage, and
git's own 128 with no node stack trace when a sha does not resolve.
* fix(ci): keep root entry bytes intact and fence guard output
git pathnames are arbitrary bytes, but the guard read ls-tree with
encoding 'utf8', so every invalid sequence collapsed to U+FFFD. That
mangled the reported name and, because the replacement is not
injective, let two different entries compare equal — a genuinely new
root entry could be waved through as pre-existing. Read the bytes as
latin1 and write them back unchanged.
The blocked-entry list is also attacker-controlled and went straight to
stdout. The runner trims leading whitespace before matching '::', so an
indented entry name still parses as a workflow command, and a pathname
may embed a newline. Wrap the list in ::stop-commands:: with a random
resume token so only the guard's own annotation is acted on.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(artifacts): gate agent artifact publishing behind an off-by-default capability
Public artifact sharing was reachable by any agent through `orca artifacts
share`: the Artifacts settings toggle only controlled sidebar visibility, and
nothing in the main process checked a capability before minting a public URL.
Add `artifactSharingEnabled` (default off) and enforce it in
ArtifactCloudService.share/update — before auth, network, or the share-record
write — so the CLI, relay-forwarded remote CLI, and IPC paths are all denied.
The denial carries a stable `artifact_sharing_disabled` code plus next steps
through the RPC error allowlist, so the CLI prints actionable guidance.
list, unshare, and delete stay ungated: turning publishing off must not strand
already-published links. The capability is absent from the `settings.update`
RPC schema, so an agent cannot grant it to itself — only the desktop UI can.
Co-authored-by: Orca <help@stably.ai>
* fix(artifacts): gate agent artifact publishing behind an off-by-default
Publishing is blocked until enabled in Settings → Artifacts. CLI preflights the capability before reading files to avoid unnecessary uploads. RPC surface rejects capability grants so callers cannot self-grant. UI shows opt-in workflow and recovery path when publishing is off. Web clients mirror the host's setting read-only.
---------
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): gate WSL relay reattach on agentStatusHooksEnabled
Spawn only ensures the guest relay distro when agent status hooks are
enabled, but reattach called ensureForDistro unconditionally — so a
disabled setting reinstalled guest hooks on every local WSL reattach.
Pass the same isAgentStatusHooksEnabled gate through all three reattach
call sites as a required argument so a new site cannot skip it.
Co-authored-by: Orca <help@stably.ai>
* Gate WSL relay at manager level for live toggle support
- Move agentStatusHooksEnabled check from reattach call sites to centralized isWslHookRelayAllowed gate
- Add non-permanent dispose mode so manager can revive relays when setting is re-enabled
- Watch setting changes and dispose live relays when agent status hooks are disabled mid-session
* Restore WSL relays when re-enabling agent status hooks
Extract guest install logic to `wsl-hook-relay-guest-install.ts` for modularity
and add `resumeStoppedRelays()` to restart relays when hooks are re-enabled.
Track distros stopped during a hooks-off teardown, but skip resuming those the
user has shut down (which would unwantedly boot a stopped distro). Strengthen
the disposed check with state identity to prevent respawning untracked relays.
Abandon in-flight launches when hooks are switched off so no relay exists after
opting out.
---------
Co-authored-by: Orca <help@stably.ai>
* Revert "test(ime): restore coverage the composition-ownership change removed (#13168)"
This reverts commit 25a8c517e1.
* Revert "refactor(terminal): return IME composition ownership to xterm (#13128)"
This reverts commit 17b3dff3c4.
* test(ime): keep the architecture-neutral Korean trace coverage
The recorded IBus/fcitx5 and Windows MS-Korean traces from #13168 assert PTY
byte order, not composition ownership, so they still hold once the terminal
composition layer is restored. The mobile accessory-order test pinned the new
handleLiveInputChange signature and does not.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): keep the macOS Backslash bypass through the revert
The restored native-text forwarder only claims keys for input sources in its
hardcoded CJK allowlist, so third-party IMEs off that list (Qingg, #10896) still
get a raw backslash. #13128 added this bypass as a partial replacement; keep it
rather than trade the open issue back.
Scoped to the bare backslash key. The rest of shouldBypassXtermForMacNativeText
bypassed all unmodified non-ASCII text, which would race the restored forwarder.
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): move the mirror-step ref write out of render
The restored hook assigned runMirrorStepRef during render, which is not
replay-safe — React can discard render work, so the mutation can leak from UI
that never commits. Its only read is inside the held-commit timer, which fires
long after commit, and the ref has a safe default, so an effect is soon enough.
Surfaced by the changed-lines React Doctor gate: the rule postdates this code,
so restoring the file re-introduced it as a new violation.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* test(wsl): drive hook-relay reattach bench through real PTY spawn
Follow-up to #13139: stop calling ensureWslHookRelayForReattach from the
benchmark and instead reattach a surviving WSL PTY via main's
registerPtyHandlers path, so a missing or misplaced integration in pty.ts
fails the bench.
* refactor(bench): isolate reattach relay refresh measurement and verify s
- Wrap benchmark in try-finally for reliable cleanup
- Add jiti module graph duplication detection to catch missing pty.ts integration
- Track relay refreshes only during reattach phase to avoid false positives from earlier phases
- Disable agent-status hooks during PTY spawn (reattach path doesn't gate on them)
- Improve error messages and make cleanup safe with optional chaining