Commit Graph
309 Commits
Author SHA1 Message Date
Neil a82468b023 chore(deps): safely update vetted dependencies across workspaces (#41291)
* chore(deps): safely update vetted dependencies across workspaces

* fix(deps): deduplicate rolldown to 1.2.16 for electron-vite tsc parity
2026-11-11 17:58:44 -08:00
Neil 48f9eb037d chore(deps): bump tsrs to 0.22.0-ts7.1.0-beta (#40572)
0.23.0 is inside the 3-day minimumReleaseAge window, so 0.22.0 is the
newest version installable without an exclusion.
2026-11-09 17:43:03 -08:00
Kelvin Amoaba d291670a01 refactor(speech): remove sherpa-onnx (#39473)
transcribe-cpp is now the only on-device engine on every platform, so
the sherpa-onnx packages, their packaging (extraResources, the darwin
libonnxruntime prune, the native-variant assert and the Linux libstdc++
exemption), the sherpa recognizer in the speech process and the
per-model engine choice all go. Hotwords are written as plain terms.
2026-11-09 18:59:10 +00:00
Kelvin Amoaba fd46321d85 feat(speech): move the speech models to transcribe-cpp and offer an update for old downloads (#39468)
* feat(speech): move the speech catalog to transcribe-cpp models

Parakeet v2/v3, Whisper tiny and SenseVoice now download their GGUF
builds, which run on transcribe-cpp everywhere, Intel Macs included (on
the CPU). A model still holding its old sherpa-onnx download shows
"Update to the faster version (<size>)"; updating replaces the old
files, and dictating before then says the model needs that update.

The five models with no GGUF build are retired and their users moved
over: the Japanese Parakeet, the Chinese-English Zipformer and the
Paraformer to SenseVoice, and the Korean and Chinese streaming
Zipformers to the new Nemotron 3.5 (28 languages). Nemotron's locale
codes (ko-KR) are matched from the ISO language setting.

* feat(speech): offer the speech model update once after the engine change

A card in the notification corner tells people whose selected on-device
model needs the new download, or whose retired model was replaced, and
starts the same download as Settings > Voice. After a replacement it
offers to delete the retired model's files.

* feat(speech): move retired Korean and Chinese models to SenseVoice and offer Whisper Large v3 Turbo

On transcribe-cpp 0.4.1 with Metal, SenseVoice beat Nemotron 3.5 on Korean
and Chinese FLEURS clips, so the retired Korean and Chinese streaming
Zipformers now move to SenseVoice. Whisper Large v3 Turbo (Q8_0, 886 MB)
joins the catalog as the most accurate choice for Japanese, Korean and
Chinese. Nemotron 3.5 stays, described by the languages it does best.
2026-11-09 18:00:13 +00:00
Kelvin Amoaba 0b8e94aa5c feat(speech): run GGUF models on transcribe-cpp with a CPU fallback (#39326)
* feat(speech): run GGUF models on transcribe-cpp, falling back to the CPU

The speech host child can now load a second engine. A model whose files
are GGUF runs on transcribe-cpp (Metal on Apple silicon, Vulkan on
Windows and Linux, CPU otherwise); everything else stays on sherpa-onnx,
which remains the only engine on Intel Macs.

A GPU that fails to load, errors, stalls for 25 s or crashes the process
moves the dictation to a CPU-only process (TRANSCRIBE_BACKENDS=cpu) that
replays the audio the GPU had not decoded yet, so no words are lost or
repeated. A GPU that crashed or would not load is remembered per
transcribe-cpp version and OS release, and voice.speechGpu = 'off' keeps
it on the CPU.

Packaging copies transcribe-cpp, koffi and the target's native package
into resources/node_modules outside asar, fails when a target's package
is missing, and ships the MSVC runtime beside transcribe.dll on Windows.

No catalog model is GGUF yet, so nothing runs on transcribe-cpp until
the catalog change that follows.

* fix(speech): keep the speech queue running after a failed task, and drop audio frames after a discard

* fix(speech): keep the engine's file reads off the main thread's hot paths

The engine lookup and the GPU failure marker are read once per process
instead of on every dictation start and mobile catalog request, the marker
is written asynchronously, and the child reads the hotwords file with
fs/promises. The baseline moves the existing engine presence checks from
stt-worker-paths to the shared package table and adds the once-per-process
marker reads.
2026-11-09 16:37:03 +00:00
Brennan Benson c192f04dba chore(claude): update the Claude Agent SDK to 0.3.318 (#39940)
* chore(claude): update the Claude Agent SDK to 0.3.318

Includes the upstream fix for omitted permissionMode (claude-agent-sdk-typescript#519), so the 'use my Claude settings' permission choice honors settings defaultMode.

* test(orcad): expect the SDK's joined --permission-prompt-tool=stdio argument

Claude Agent SDK 0.3.295+ passes named option values in the same argument as their flag.

* refactor(claude): drop the permission flag SDK 0.3.318 no longer reads

The SDK leaves an omitted permissionMode to the CLI, so the 'own' choice needs no opt-in option.
2026-11-08 17:44:46 -08:00
Neilandm4air dddc1260e5 chore(lint): oxlint 1.91, cheap perf/correctness rules, return-await fixes (#39730)
* chore(lint): bump oxlint to 1.91, add perf lint rules and perf audits

* fix(lint): await promises returned inside try/catch/finally for return-await

Prepare typescript/return-await with error-handling-correctness-only: await
returns whose catch or finally was meant to cover them, and suppress the eight
sites where the catch must not re-handle the returned promise.

* chore(lint): enable return-await (error-handling-correctness-only); exempt secondary-scan directives in the changed gate

* refactor(mobile): move file preview discard modal into its own module

Keeps MobileFilePreviewScreen under the 400-line limit after merging main.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
2026-11-08 06:21:54 -08:00
NeilandFlamingoPg 6a68dac421 fix(terminal): bound WebGL atlas pages and release removed page textures (#39715)
Cap merged atlas pages at FORCED_MAX_TEXTURE_SIZE instead of the device
MAX_TEXTURE_SIZE (16384 on Apple Silicon), stop the link render layer
from acquiring the shared atlas with a mismatched 2048 texture size, and
reset GL texture slots of removed atlas pages to the 1x1 placeholder.
Mirrors xtermjs/xterm.js#6187, #6156 and #6306 in the addon-webgl source
patch and regenerates the runtime patch and lock hash.

Co-authored-by: FlamingoPg <1106310035@qq.com>
2026-11-07 21:30:43 -08:00
NeilandCopilot 0b22e904e3 perf(terminal): avoid reparsing xterm inline color styles (#39482)
Set RGB and contrast-adjusted colors through CSS properties instead of serializing and reparsing the complete inline style. Regenerate the pinned xterm runtime patches and lock hash without changing terminal scheduling or recovery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-07 12:53:11 -08:00
OrcaWinandOrcaWin 75910ba631 build(renderer): gate React Compiler compatibility with a ratchet (#39324)
Adds oxc-transform-react as a devDependency and a ratchet that runs React
Compiler in analysis mode over the shipped renderer sources. Files can't gain
bailouts (new files must compile fully), and new code the compiler would run
differently is banned: hooks it memoizes into a cached block, bare `void x;`
reads, member postfix updates used as values, and "use no memo" opt-outs.
Both baselines are generated by --update / --prune.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-07 00:42:01 -08:00
Neil a59d47b1eb fix(terminal): let default-matching cell backgrounds follow background opacity (#39372)
Explicit SGR backgrounds equal to the translucent default background, and
style-only cells (dim, italic, extended underline) in WebGL, painted an
opaque fill over the theme wallpaper. Treat them as the default background
in both the WebGL and DOM renderers; the buffer keeps the original color.

Closes #37671
2026-11-07 00:41:53 -08:00
Brennan Benson 3a6bb347b5 Apply supported read-only controls to hidden text helpers (#38558)
* Keep background text generation read-only

* Align runtime MIME dependency in frozen lockfile

* Bundle MIME lookup data for background text tasks

* Preserve text task cancellation and process ownership during policy probes

* Update streamed generation fixture for permission probe

* fix: preserve naming recipes with compatible restrictions

* test: align naming contracts with single-attempt behavior

* fix: preserve older OpenCode helper restrictions

* test: complete OpenCode capability fixtures

* Separate supported read-only controls from naming retries
2026-11-06 04:15:29 -08:00
Brennan BensonandAJIADb9 239a70040c Terminal tabs show the agent session's name instead of the first-prompt title (#38518)
* fix(tabs): show the agent session's name on terminal tabs

The tab strip rebuilt each terminal tab without its session title and then re-ranked it, so the first-prompt title won over the session's own name. Carry the session title through the projection, and give the tab strip, unified tab label and sidebar agent row one shared name order so they cannot drift.

Co-authored-by: AJIADb9 <ajiadb9@bgd.team>

* fix(tabs): paired desktops take the host's session name for mirrored terminal tabs

The host projection now forwards the window's session title to paired clients, and the client's rebuild adopts it (older hosts omit it, so the client keeps its own). Without it a mirrored tab re-ranked to the client's first-prompt title.

* fix(tabs): one writer per session for a mirrored tab's session name

The paired client's own title sync keeps a session it already tracks current; the host's name only fills a session the client doesn't know, so a stale host snapshot can't revert a rename the client already read.

* fix(tabs): a paired host is the only writer of a session name it publishes

* fix(tabs): only a host window that resolves session names speaks for them

* fix(tabs): derive session title authority from live publishers

* fix(tabs): keep the session title publisher check concrete for runtime subclasses

* fix(tabs): one publication when a closing window drops browsers and title authority

* test(tabs): follow main's runtime test helper layout

* fix(tabs): paired desktops keep a quick-command name ahead of the session name

* fix(tabs): a name the host cleared doesn't come back as the idle pane's title

* test(tabs): build the idle-pane fixture as its own surface variant

* fix(tabs): point the moved session-title projection at its new shared path

---------

Co-authored-by: AJIADb9 <ajiadb9@bgd.team>
2026-11-05 11:04:58 -08:00
Neil 9c7fe9793e fix(deps): backport minimatch, glob, and deepmerge security fixes (#37981)
Backport upstream matching and abort fixes plus safe deepmerge property handling across the independent workspaces. Includes installed-package regression tests and audit-tool fixture repair. Agent-reviewed at 83ca381d519e6247d7c1b3f0a52ce2907a9f9a19; CI aggregate passed. Version-based scanner alerts remain open pending documented disposition or compatible upstream releases.
2026-11-03 17:13:11 -08:00
Neil f3c65cd556 fix(deps): update Electron to 43.8.6 and resolve open security advisories (#37916)
- Electron 43.8.4 -> 43.8.6: upstream Chromium/V8/ANGLE/PDFium/Skia/WebRTC
  security backports. Drops the now-unneeded minimumReleaseAge exclusion.
- @hono/node-server 2.1.0 -> 2.1.5 (GHSA-rmxm-3fg6-px4f), lockfile only.
- mobile node-forge 1.4.0 -> 1.4.2: fixes 10 advisories (5 high) and the
  previously patched GHSA-86w9-cpqp-85rv, so the local patch and its audit
  exception are removed. The regression test still passes on 1.4.2.
2026-11-03 13:52:12 -08:00
Brennan Benson 86a2fe9684 Load Linear drawer fields in one issue query (#37663)
* Read Linear drawer details in one issue query

* Keep readable Linear details after optional field errors

* Select only Linear fields needed by each issue reader

* Explain the typed Linear partial response boundary

* Typecheck Linear integration in the mixed-process project

* Update Linear integration fixtures for raw issue reads
2026-11-02 22:25:42 -08:00
Neil b69963356d chore(terminal): bump xterm to 6.1.0-beta.336 and regenerate patches (#37266)
Port the xterm, webgl, search, serialize and image source patches to upstream
08829c7. Where upstream fixed the same bug (#6204 mouse NaN guard, #6226 warm-up
cancel on dispose, #6235/#6237 serialize width clamp) take upstream and keep only
Orca's remaining delta (domContainer warm-up gate, clipped wide-cell blanking).
Bump mobile's webgl subset patch alongside. Drop serialize fuzz seed normal:5,
which now round-trips.
2026-11-01 18:05:58 -08:00
Brennan Benson 07b437c9e9 fix(terminal): detect foreground from the physical PTY owner (#36342)
* fix(terminal): observe foreground at the physical PTY owner

* fix(terminal): keep existing inventory, inspection and display semantics around master-group foreground

Review round 1 fixes for the physical-owner foreground observation:

- Stop and liveness verification keep complete-inventory proof: the router's
  scoped listProcesses({ptyIds}) is owner resolution again (absent = []).
  Only the launch guard's old-owner fallback asks for an established
  target's owner via an internal targetOwnerOnly option.
- Degraded daemon mode routes foreground observations and target reads to
  the established owner, so an unreachable unrelated daemon cannot refuse a
  known terminal there either.
- Display and name refreshes read the master's foreground group but take
  member rows from the shared single-flight process-table snapshot; no
  per-terminal ps per refresh. Recognized agent names skip the confirm scan
  again, and builds without the native accessor keep the existing reader.
- Ordinary inspections keep their first-match routing; strict owner and
  incarnation checks apply only to foreground decisions. The unused
  foregroundOnly preload/IPC/RPC surface is removed.
- Local inventory forwards only the target scope, never caller deadlines.
- An interpreter running an agent script counts as that agent, as before;
  a blank argv row no longer voids a terminal capture outside the
  foreground group.
- Bump the packaged compatibility contract to daemon protocol 55 and move
  the node-pty accessor above the process getter's doc comment.

* fix(terminal): keep the node-pty patch hunk well-formed and drop redundant foreground tests

Remove two stray context lines from the src/unixTerminal.ts hunk so its body
matches its header (strict patch parsers reject the mismatch), and update the
lockfile patch hash. Prune foreground tests whose rows repeat branches the
parity table already covers.

* fix(terminal): index the shared snapshot by TTY once and keep observation age nonnegative

Display refreshes filtered the whole shared process table per pane, so
work grew with panes times processes. Group each snapshot by normalized
TTY once, memoized by array identity, and look each pane up directly;
the scaling contract now counts row TTY reads at N and 10N panes.

Both foreground observation producers clamp capturedAgeMs at zero, so a
wall-clock step back during the read no longer yields an envelope the
validator rejects and the launch guard refuses.

* test: move foreground-observation compat tests into the protocol compatibility suite

Main removed daemon-pty-adapter-steady-state-compat.test.ts; the merge restored it with only this branch's block. Reuse the suite's existing inspection adapter helper so no new type assertion is added.
2026-11-01 15:32:17 -08:00
Brennan Benson 9eb930173e Keep rich Markdown in place when focus returns from Find (#36781)
* Preserve rich Markdown viewport through bare focus returns

* Retire viewport ownership on editor remount

* Complete no-op navigation and body Tab coverage

* Keep patch metadata keys separate from dependency snapshots

* Retire the viewport before root detach callbacks

* Give the engine writer fixture its native selection

* Keep native focus navigation current in Markdown

* fix: leave smooth Markdown scrolling to native focus cancellation

* Cover focus wrapper removal and routed Markdown writers

* Type the viewport scroll spy and mark the focus-recovery wait as deliberate

* Scope Markdown viewport wheel and touch guards to the editor scroller
2026-11-01 11:46:13 -08:00
Neil 4ee788a725 Build-less TS/TSX plugins, typings and authoring tools (#36918)
* feat(plugins): build-less TS/TSX plugins, typings and authoring tools

- Plugin frames transpile .ts/.tsx/.mts/.jsx on load (sucrase on a shared worker thread with a
  2 s budget, cached in main), resolve relative imports to exact files, and refuse bare
  package imports by name. Components and html panel/editor entries may be .ts/.tsx.
- orca plugin types writes typings for the components orca module, window.orca, worker
  activate(orca), host.call and events into .orca/types, from one host-typings generator.
- orca plugin create --template components; templates omit publisher and target 1.4.246.
- orca plugin release-entry, dev inspect and restart; validate:plugin checks named files.
- Guest console errors reach orca plugin logs; dev workers restart on edits; the dev watcher
  ignores .git, .orca and node_modules; Git and folder installs cannot replace catalog installs.
- examples/plugins/hello-tsx, docs, and the plugin-buildless-tsx e2e spec.

* ci: run the Windows parity probes with one tsgo checker

tsconfig.node peaks near 22 GB with tsgo's default four checkers (each holds its own types)
and about 16 GB with one; the 16 GB Windows runner paged the native probe past its 900 s
timeout once this branch's package.json change triggered the job. One checker gives the
same diagnostics (verified locally with --parity-only); the timing benchmark is unchanged.
2026-11-01 01:28:35 -08:00
Neil 14d29b8d24 feat(themes): background images for app themes (#36589)
An app theme may declare one validated raster image (png/jpeg/webp, <=2 MB, >=640x360, inside the plugin) served through orca-plugin-image, painted as one static body layer under translucent terminals. Older Orca ignores the field; a bad image drops only the image.
2026-10-31 19:45:04 -07:00
Neil 82ac952ad7 feat(editor): one typography resolver for every Monaco surface + opt-in weight, line height, ligatures, size and macOS smoothing (#36577)
* feat(editor): route editor typography through one resolver and add opt-in weight, line height, ligatures, size and macOS smoothing

Monaco sites built font options ad hoc and never received fontWeight,
fontLigatures or lineHeight; the editor size was tied to the terminal.
editor-typography.ts now feeds every Monaco and DOM code surface. New
optional settings default to today's rendering. A macOS-only Font
Smoothing setting rebuilds WebGL terminal renderers so the change
reaches their shared glyph atlases.

* fix: satisfy changed-code quality gates

* fix(terminal): cancel WebGL atlas warm-up on dispose so rebuilt renderers stop leaking canvases

The warm-up queue kept drawing into a disposed atlas, which re-appended its
scratch canvas to the terminal element. Every WebGL renderer rebuild (GPU
toggle, context-loss retry, font smoothing toggle) leaked one canvas per pane.

* feat(editor): read pixel and zero line heights like code editors and let markdown code follow an explicit editor font
2026-10-31 19:20:48 -07:00
Neil 0cb87957e0 fix(terminal): stop terminal text turning bold after a restart (STA-4042) (#36463)
xterm's WebGL atlas warm-up pre-rasterized default-color ASCII without a DOM
container. When it ran before the first glyph was drawn (a blank restored
pane), the glyph canvas was still detached, so Chromium ignored the page's
-webkit-font-smoothing: antialiased and baked heavier glyphs into the cache
until something cleared the atlas (e.g. an image paste).

Warm up through the terminal element and skip it while that element is not
connected; skipped glyphs are drawn on demand.
2026-10-30 15:08:42 -07:00
Brennan Benson eb3379ff99 Fix macOS daemon PTY kqueue descriptor leak (#16870)
* fix(daemon): release node-pty kqueue descriptors

* test: remove redundant wait from kqueue regression
2026-10-30 11:42:31 -07:00
Neil f586149b3d chore(deps): update Electron to 43.8.4 (#36323)
Picks up File System Access blocklist and asar hardening fixes. 43.8.4 is
excluded from minimumReleaseAge since it was published under 3 days ago.
2026-10-30 02:55:32 -07:00
Neil 756d623955 perf(terminal): bound the in-process macOS process table's blocking calls and ps fallbacks (#31783) (#36282)
* fix(terminal): bound the in-process macOS process table's blocking calls and ps fallbacks (#31783)

Follow-up to #35987 from its post-merge reviews.

- cwd and executable path: node-pty's readProcessCwd/readProcessPath now run
  PROC_PIDVNODEPATHINFO and proc_pidpath on a libuv worker (the synchronous
  exports are gone, so an older pty.node falls back to ps/lsof). JS caps them at
  two outstanding lookups and 250ms; a wedged mount or a full cap answers from
  lsof -b, which never stats, so a dead NFS/SMB/FUSE mount cannot freeze the
  daemon's event loop or take the whole pool.
- Unreadable argv prints "(comm)", as ps does, instead of a proc_pidpath lookup
  under Orca's credentials on the table worker.
- The table exports each row's euid. Argv recovery runs setuid ps only for
  another user's processes, never for our own mid-exec or exiting ones (or
  SIDL/exiting rows), which ps cannot read either: a pane looping short
  commands forked ps on ~15% of full reads, now none.
- Recovery is coalesced per process across concurrent readers and a failed
  recovery waits 5s before costing another ps. The cache keys on comm and start
  time.
- Full-table and shell-foreground captures, which feed strict evidence with
  the capture's age, take only argv ps read after their request; display
  readers keep the 30s cache.
- While the reader is down (stalled-pool breaker open, or a rejecting addon,
  which now backs off 1s..30s), runPsInProcess runs /bin/ps itself through one
  shared budget: identical invocations share a run, a burst of 4 forks then one
  a second, and a run spawned before a request never answers it. Descendant
  teardown and foreground-group signalling keep their own full-timeout ps
  (callerRunsPs). 13 pollers at 750ms: 1,040 ps/min before, 63 after.

No daemon protocol bump: the wire format is unchanged, and each daemon loads
JS and pty.node from the same runtime copy.

* fix(terminal): make the libproc reader survive worker teardown, fail closed and share one fair fork budget (#31783)

Review follow-ups to the previous commit.

- Native: every libproc AsyncWorker derives from OrcaPromiseWorker, which skips
  JS completion once an env cleanup hook marks the env closing and swallows a
  Napi::Error thrown mid-teardown. Terminating a worker_thread with cwd or table
  reads in flight aborted the process; 200 terminates each now survive.
- (comm) in a terminal's foreground group (argv neither reader recovered, e.g.
  a root `su`) judges 'unknown', never 'agent', so the launch line is not typed
  into it.
- Fresh argv only for getFreshSnapshot captures; the 500ms TTL readers keep the
  30s cache. Argv recovery runs through its own fork budget (2, then one per
  2s). Root `su` pane, 13 pollers at 750ms: TTL 79 -> 2 ps/min, fresh 158 -> 31.
- The fork budget serves invocations in FIFO turns, grouping each one's waiters
  onto one run. 13 streams: served 80,42,1,1,2,0x8 -> 10-12 each.
- Forced subprocess captures (a held naming pass) go through the fallback
  budget: 5 readers, 400 direct ps/min -> 63, 395 of 400 captures served.
- A failed recovery is retried 5s after ps gave up, not after it started.
- Tests pin Orca's euid instead of reading the runner's and assuming root is 0.
- process-cwd keeps one lsof per pid until it exits, so a dead mount no longer
  leaks one per poll; shell readiness reads the exe path through runProcess,
  which gives up on an lsof that ignores SIGTERM.
- A #35987 pty.node (table reader, no async path reads) keeps the in-process
  table; cwd/path use lsof, with a one-time warning.
- captureProcessTable moves to process-table-ps-capture.ts.

* fix(terminal): make withheld argv unverifiable and reuse recovered argv for at most 2s (#31783)

Round-2 review follow-ups.

- Worker teardown: drop OrcaPromiseWorker and its env flag (Node drains the
  pool before cleanup hooks, so it never fired). node-pty's mac pty target now
  builds with NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS, which covers both the
  call-in and completion paths. The smoke test now calls the reads from a
  timer while terminating: the previous build aborted 2 of 3 runs, this one
  survives 3/3 x 100. pty.cc is back to the first follow-up's exact content.
- A foreground process whose argv ps withheld (`(comm)`: another user's,
  exiting, or a recovery the budget skipped) makes the batch resolver, and so
  host-stamped evidence, `argv_unreadable`/unverifiable rather than live with no
  agent; agent-foreground resolution reports it unavailable.
- judgeTerminalForeground fails closed only for an unreadable shell (`(bash)`
  from a root `su`), not our own exiting `(git)`.
- Recovered argv is reused for at most 2s per identity (pid, start, comm) for
  every reader (the next commit ages evidence by it), and
  fresh reads no longer queue behind the recovery budget. Root `su` pane, 13
  fresh pollers: p50 2,989ms -> 53ms, 31 -> 26 ps/min (TTL reader 1 -> 26).
- The fork budgets serve interactive and fresh readers before display polls
  (cheap and TTL snapshot captures), FIFO within each. 20 display streams plus
  a 3s launch-paste read every 5s: 0/12 -> 12/12 served, polls 4-7 each.
- On a selected terminal, the foreground group's argv is recovered even with no
  process of ours left (`exec su`); the root login is not.
- Budgeted captures keep the 32 MiB output ceiling.

* fix(terminal): age strict evidence by its oldest recovered argv (#31783)

Round-3 review follow-ups.

- Recovered argv carried no read time, so a capture reusing it (cached, or a
  recovery still in flight) stamped its own capture time over argv up to 2s
  older, and the remote write guard could take a same-comm re-exec for the
  agent. Rows now carry when ps read their recovered argv; runPsInProcess
  reports the oldest through onArgvReadAt, and the snapshot reader stamps
  capturedAtMs at that input, so capturedAgeMs is the age of the oldest input
  and consumers' own freshness checks (the write guard's 500ms) decide. A
  recovery begun more than 2s before a reader asked no longer answers it, and
  an older recovery finishing late cannot replace a newer one.
- resolveSpawnFileForegroundFromRows reports a withheld `(node)` foreground
  unavailable.
- One budget queue entry per invocation: an interactive request promotes a
  queued poll's, a poll joins an interactive one.
- parseShellForegroundRows gets its JSDoc back.

* fix(terminal): keep a cached name from authorizing a refused write; integer, under-2s argv ages (#31783)

Round-4 review follow-ups.

- When the relay's evidence declines a write (stale, unverifiable, another
  agent), its foreground name may still prove the shell but no longer proves
  the agent: that name can be the tracker's 1s display cache, e.g. `claude`
  cached just before Claude exited to a root shell under su. Relays that send
  no evidence keep the name as before.
- Snapshot ages round up to whole milliseconds; the wire rejects fractions
  from the monotonic-to-wall conversion.
- Recovered argv is reused for at most 1.5s, so reuse plus the 500ms snapshot
  cache stays under the 2s freshness gate; reuse ages from when that ps
  spawned, not from the wait for its fork token, and only an answer is
  remembered (a timed-out or withheld run proves nothing). Fork budget
  unchanged. Root su pane, 13 pollers: 30 ps/min; fresh p50 51ms; strict
  evidence age p50 752ms, max 1,508ms.

* fix(terminal): keep the relay's agent name behind structurally unverifiable evidence (#31783)

The previous commit refused the name fallback whenever the relay sent any
evidence, so `unverifiable` evidence for structural reasons also blocked
writes: npm Codex (node codex.js spawning native codex) and Gemini (relaunching
itself) put two agent rows in the foreground group (ambiguous_foreground_group),
and remote tmux reads multiplexer_boundary. On SSH POSIX hosts that dropped
launch prompts and failed worker briefs, conversation send/cancel and
automations with agent_not_in_foreground.

The name now cannot authorize only when the evidence saw the group (`live`:
stale, or no agent in it, as in the cached-`claude`-over-root-shell repro) or
withheld a foreground argv (`argv_unreadable`, possibly a root `su` shell).
Other unverifiable reasons keep the name fallback, as before this branch.

* fix(terminal): let a relay name complete only fresh agent-positive evidence (#31783)

The SSH name fallback still let a stale or cached name authorize a write under
any unverifiable reason but argv_unreadable: ps EAGAIN (process_table_unreadable)
after the tracker cached `claude` over a root bash, anchor_missing,
tty_boundary (`sudo -i` with use_pty, a root shell on a new pty), or a 1.5s-old
ambiguous capture.

- The name now completes evidence only when it is fresh (within the guard's
  500ms bound), its reason is one that saw an agent row
  (ambiguous_foreground_group, multiplexer_boundary,
  candidate_start_time_missing), and the name is the launched agent. Every other
  reason, including ones this build does not know, reads unknown. Relays that
  send no evidence keep the name, for mixed versions.
- The relay's evidence names one agent that holds two rows of the group (npm
  Codex: node codex.js spawning native codex; Gemini relaunching itself), so a
  new relay proves them live even when its name is `node`. Two different agents
  stay ambiguous. No wire change.

* fix(terminal): judge a relay name by the host's capture age; keep old relays' npm Codex (#31783)

- Name completion compared the capture's start with the read's start, so a
  slow response made old evidence look fresh: a capture 1,500ms old arriving
  after 1,100ms passed. It now uses the host-reported capturedAgeMs against
  the 500ms bound, which latency cannot shrink. Live evidence keeps its
  capture-start rule.
- Relays before same-agent dedupe report npm Codex (node wrapper plus native
  binary) as ambiguous_foreground_group and name it `node`. For that reason
  only, `node` now completes the evidence when the launched agent is
  npm-installed per the node entrypoint tables; other agents and reasons stay
  fail-closed.

* fix(terminal): bound relay-name completion by twice the snapshot TTL (#31783)

The 500ms bound refused ~9.5% of tmux name completions on a host with 13
pollers (cached snapshot up to one 500ms TTL old plus ~30ms capture, max
528ms), and a refusal drops the launch prompt or throws with no retry. The
bound is now 2 x PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS (1s), still on the
host-reported age: 0 of 767 reads exceed it, the delayed 1,500ms repro still
refuses, and the cached-name hazard (the tracker's ~1s display cache) is no
wider.

* fix(terminal): complete relay evidence with a name read now, not the display cache (#31783)

With the 1s bound, a capture at 0-528ms, `codex` cached at 100ms, Codex
exiting at 101ms and the guard at 999ms still let the cached name authorize a
write over the bash that replaced it.

- Name completion (structural reasons, host age within 2x the snapshot TTL)
  now asks confirmForegroundProcess, which reads the foreground now (a fresh
  scan, or node-pty's raw process name) rather than the tracker's 1s display
  cache; a null answer reads unknown. No new wire surface: the local daemon answers
  confirmForegroundProcess, paired runtimes evaluate guards locally, and the
  production controller returns null (read as unknown, failing safe) when a
  provider lacks it. Controllers without the method, and relays sending no
  evidence, keep the cached name as the legacy path.
- Live evidence also accepts a host-reported age within 2x the TTL, so a real
  agent behind a 500-528ms cached snapshot on a fast link is no longer
  refused; a capture begun after the read was asked still counts, and
  5,000ms-old evidence still refuses.

* fix(terminal): confirm live evidence older than one snapshot TTL with a fresh read (#31783)

Accepting live evidence up to 2x the TTL reopened the window the previous
commit closed for name completion: a capture at 0-528ms, the agent exiting at
~529ms, and at 999ms `live claude` still read as agent over a bash.

Live evidence begun no more than one TTL before the read was asked (so any
host age within one TTL, however long ps took) still answers alone. Between
that and 2x the TTL, confirmForegroundProcess reads the foreground now: the
launched agent proves it, a shell reads shell, anything else unknown.
Controllers without that read fall through and refuse, as before.

Reachability: the remote (host.remote) write-guard path has no production
caller since the SSH provider's removal (#27499); the registry dispatches only
local providers and paired runtimes evaluate guards locally. This is defence
in depth for any future remote provider.
2026-10-30 01:05:34 -07:00
Brennan Benson fd976a47be Keep zoomed PDF detail text sharp (#36173)
* Keep zoomed PDF detail text at device resolution

* Test PDF detail update dimension-read bounds

* Account for browser layout units in PDF geometry test
2026-10-29 21:27:45 -07:00
OrcaWinandOrcaWin 5951dbbf92 fix(editor): task-list parsing fixes (ordered parents, wrapped markup, tight continuations, bare markers) (#35187)
* fix(editor): preserve sibling tasks beneath ordered list items

* fix(editor): preserve wrapped inline markup in task items

* fix(editor): keep nested task continuations valid and editable

* fix(editor): keep literal task markers out of mixed checklists

* fix(editor): apply review fixes to task-list parsing stack

- Trim synthetic test matrices to the behavioral cases
- Regenerate pnpm-lock (drops a stale github-slugger importer entry)
- Assert the plain bullet's node type under an ordered parent

* fix(editor): keep one-column-deeper ordered tasks as sibling checkboxes

Dedent an ordered item's task block by the first task's column instead of
keeping its leading whitespace. A task one column deeper stays a sibling
checkbox, and text at or within the first task's column stays nested as
before. Strip leftover paragraph indent from task children.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 17:38:19 -07:00
OrcaWinandOrcaWin 15023ab339 fix(editor): match Unicode heading anchors across rich and preview modes (#35077)
* fix(editor): match Unicode heading anchors across rich and preview modes

* test(editor): drop slugger tests that only re-test github-slugger

The preview-parity test in markdown-anchor-scroll.test.ts covers the behavior that matters.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 17:14:52 -07:00
Kelvin Amoaba 5834a3dcfd fix(browser): agent clicks keep their target when a page changes, by upgrading agent-browser to 0.39.4 (#36007)
* chore(browser): upgrade bundled agent-browser to 0.39.4

0.39.4 keeps refs stable across snapshots and refuses stale or hidden
click targets. Its replies now carry daemon lifecycle bookkeeping, which
is dropped where the JSON is parsed, and its tarball binaries lose the
executable bit pnpm's skipped postinstall used to restore, so the orcad
dev resolver now restores it like the desktop one.

* fix(browser): keep orcad's Chromium unbounded and read 0.39's stale-ref error

* fix(browser): import the shared stale-ref rule in orcad and keep the parser typed

* fix(browser): keep workers and frames running after the agent-browser helper leaves

agent-browser 0.39 asks auto-attached targets to wait for it before
starting. The proxy forwards that onto the guest's shared debugger,
which outlives the helper, so after the helper's idle exit nothing
resumed new workers or frames.
2026-10-29 21:15:04 +00:00
Neil 525719789f perf(terminal): read the macOS process table in process instead of forking setuid ps (#35987)
* perf(terminal): read the macOS process table in process instead of forking setuid ps (#31783)

/bin/ps is setuid root, so every exec costs an uncached syspolicyd
assessment and an EDR inspection. The terminal host forked ~7-10 ps/lsof
per second with 4 agent panes, and each stale daemon multiplied that.

node-pty's patched pty.node now exports processTable (sysctl
KERN_PROC_ALL/KERN_PROCARGS2 on a worker thread), processCwd
(PROC_PIDVNODEPATHINFO) and processPath (proc_pidpath). runPsInProcess
renders the same ps column text for the invocations Orca issues, so the
existing parsers and callers are unchanged; anything it cannot reproduce,
non-macOS hosts, and builds without the exports keep running ps/lsof.

Measured with 4 anchored panes polled every 750 ms for 60 s: ~440
ps/lsof children before, 0 after.

* fix(daemon): roll the in-process macOS process table into a fresh v54 daemon (#31783)

* fix(terminal): bound, escape and complete the in-process macOS process table like ps (#31783)

Review follow-ups for the libproc reader:

- Timeouts: every caller passes its ps timeout; a read stalled behind a
  saturated libuv pool resolves timed out (code null, timedOut) at that
  budget, exactly as the killed ps would. Identical native reads share one
  worker, each caller racing its own deadline; past 8 distinct reads in
  flight callers run ps. execFile-shaped callers reject like a failed child.
- tty naming shares one slot with captureDarwinProcessTable's guard, so a
  draining naming pass sends both paths to ps instead of naming again.
- Commands: the addon renders argv with adv_cmds' getproclline and strnvis
  (VIS_TAB|VIS_NL|VIS_NOSLASH) under the caller's LC_CTYPE via a
  thread-scoped uselocale, so newlines print as \012 instead of splitting
  tokens; zombies print <defunct>.
- Other users' processes: unreadable argv is marked commandIncomplete. Only
  -p selections and processes on a terminal under the user's own readable
  process there (sudo in a pane) get a targeted `ps -p ... -o pid=,command=`,
  cached by (locale, pid, start time); root daemons and a pane's root login
  never spawn ps.
- -t selects with KERN_PROC_TTY after ps's addelem_tty path resolution, so
  per-terminal reads skip argv for every other process (~0.3 ms vs 4 ms ps).
- -p deduplicates; rows sort by terminal then pid as pscomp(); stat adds
  ps's < N X V L modifiers. The export is renamed readProcessTable so an
  older pty.node falls back to ps rather than misreading the new query.

* test(terminal): diff the real libproc addon against /bin/ps on macOS (#31783)

Spawns a pty holder whose argv carries a newline, tab, invalid UTF-8 and é,
then compares Orca's ps invocations column by column: pid/ppid/pgid/tpgid/
lstart/tdev/tty exactly, commands for the user's own processes, and stat
modulo running/idle (thread state needs setuid ps). Rows are compared only
when two in-process reads around the ps run agree, so churn cannot flake it.

* fix(terminal): never serve a native process table read begun before the request (#31783)

A timed-out read stayed shareable, so a later descendant capture could join
an earlier poll's abandoned worker and take its rows as fresh; a descendant
that exited meanwhile could have its reused pid SIGTERMed. The addon now
reports when its worker actually began reading (startedAtMs), and
runPsInProcess refuses a shared read that began before its own request and
reads again, so every consumer that signals from rows (descendant
teardown, foreground-group signalling, identity probes) only sees rows
scanned after its stamp. Regression: pty-descendant-termination-libproc.

Also from review:
- argv recovered with setuid ps is keyed by pid, start time to the
  microsecond, comm and executable path (exec changes the latter two) with
  a 10 min TTL and 512-entry bound.
- Recovery covers a sudo use_pty tree: the unreadable chain must start on
  the terminal of the user's own readable process, so the root shell and
  agent on sudo's own pty qualify while root daemons, GUI helpers and a
  pane's root login (own pty, under a terminal-less or tty-inheriting host)
  do not.
- Concurrent tty= readers wait out the single naming pass, and readers past
  the 8-read cap queue behind one, within their deadlines, instead of
  bursting /bin/ps at startup restore (24 panes polled at once: 0 ps).
- darwin-terminal-naming-slot.ts is now darwin-terminal-naming-single-flight.ts.

* test(terminal): require /bin/ps row order in the libproc parity test (#31783)

Parsers that take the first matching row depend on ps's terminal-then-pid
order, so compare the order of the rows both captures share, not just their
contents.

* test(terminal): pin libproc read start times instead of reading the wall clock (#31783)

* fix(terminal): match native process table reads to requests by sequence, not wall clock (#31783)

Freshness compared the worker's gettimeofday with Date.now(), so a clock
step back let an abandoned read's rows through (wrong-pid SIGTERM) and a
step forward could reject fresh reads until the deadline. Each read now
takes a JS-side sequence number when created and is submitted a microtask
later; a caller joins only a read not yet submitted or created after it
registered, so callers in one turn share a worker and nothing submitted
before a request serves it. The addon drops startedAtMs again. Deadlines,
timeouts and the argv cache use performance.now().

Callers past the 8-read cap wait on one slot notifier and each joined read
has one settle notifier; both unsubscribe on timeout, so a stalled pool
retains no per-caller reactions. If the pool is still at the cap when an
overflow caller's deadline passes, the reader is marked stalled for 30s
and callers run ps, as before this branch.

Recovered argv is trusted for 30s: exec can keep pid, start time, comm and
executable (node -> node with another script) and no cheap field shows it,
so a sudo pane costs one targeted ps per process per 30s.

* fix(terminal): keep ps fallbacks off a stuck terminal naming pass (#31783)

- captureDarwinProcessTable's canonical fallback, taken while the naming
  slot is busy, now runs /bin/ps directly instead of re-entering the
  in-process reader and waiting on that same slot (a held lstat timed out
  every strict capture without one subprocess fallback).
- In-process tty= readers wait out a short pass (startup restore) but hand
  a pass older than 500ms to ps, which names terminals out of process.
- The naming single flight keeps one settle notifier per pass; waiters sit
  in a Set and drop off on timeout, so timed-out callers leave no reactions
  on a stuck pass (was one per call). The notifier moves to
  deadline-settle-notifier.ts, shared with the native read scheduler.
- Once the native read breaker has tripped, a caller finding the cap still
  held after the cool-down returns to ps at once instead of waiting out its
  timeout.
- Descendant teardown and foreground-group signalling give the native read
  half their budget and fall back to a fresh ps on a native timeout, so a
  partly stalled pool cannot cost a kill.

* fix(terminal): give signalling ps fallbacks their full baseline timeout (#31783)

Splitting the budget in half shrank ps's window behind a stalled native
read: a 750ms ps lost descendant SIGTERMs and an 80ms foreground ps lost
group SIGWINCH. Descendant teardown and foreground-group signalling now try
the native reader for min(150ms, timeout/4) (inProcessSliceMs), then give
ps the whole original timeout; readProcessTableBeforeDeadline extends the
production reader's outer deadline by that slice, so ps succeeds exactly
when it did on main. The stale-read regression test now also asserts the
fresh rows' real descendant is signalled.

* fix(terminal): carry the in-process deadline allowance through every descendant reader (#31783)

readProcessTableBeforeDeadline granted the native slice only when the
reader was readProcessTable by identity, so the daemon shutdown sweep's
sharing wrapper kept a bare 250ms deadline: a stalled native read plus a
200ms ps missed it on every retry and descendants survived shutdown.
ProcessTableReader now declares deadlineAllowanceMs; the fresh reader sets
it (0 without the addon or with a stalled pool, so Linux and ps-only hosts
keep their exact deadlines), createProcessTableSnapshotReader and the
shutdown wrapper inherit it, and injected readers default to none. The
fresh reader moves to pty-descendant-fresh-table.ts. The real-ps parity
test gets a 30s per-case timeout, since whole-table /bin/ps runs slow
under suite load.

* chore(terminal): move the descendant reader's child_process baseline entry with it (#31783)

* test(terminal): pin the libproc test locale so lstart doesn't follow the runner's LANG (#31783)
2026-10-29 09:51:53 -07:00
edc0373fcd plugins: Plugin Alpha (#35470)
* i18n(plugins): drop retired plugin strings from the Portuguese catalog

* i18n: keep the empty project group in the Spanish and French catalogs

* i18n: retire translated entries for plugin strings this change rewords or removes

* plugins: syntax contributions

A plugin can contribute up to 16 syntaxes: a TextMate grammar, optional
language configuration, and the file extensions and names it claims.
Main validates every grammar when the plugin is approved (shape, scope,
size, nesting) and lists only the file associations. A language's
grammars cross IPC only when a matching file opens, and includes must
resolve inside the same plugin. Catalog releases can declare syntax.

* plugins: highlight files with plugin syntaxes

Files Orca shows as plain text use a matching plugin syntax. Its grammar
compiles once in a worker that stays warm between edits and runs under
core's per-line limits; a worker that stops reporting progress or crashes
is terminated, the language stays plain, and the plugin card says so.
Tokens reach Monaco as semantic tokens, so Monaco asks only for models
shown in an editor, keeps one request per model, and keeps painting the
previous colors, shifted by edits, until the new pass lands. Plugin
languages borrow ids from a pool of fixed Monaco languages, counted by
the editors using them; a language no editor uses moves its models back
to plain text, and the least recently used idle slots are reused first.

* plugins: Gleam syntax example pack

An official-style data plugin with the upstream Gleam TextMate grammar
(Apache-2.0, provenance and license alongside) and language
configuration. A test installs it through the production inspection and
loads it through the syntax registry.

* plugins: list themes, icon themes and syntaxes in the marketplace preview

The preview's Includes line named only language packs, commands,
shortcuts, VM recipes, panels and events, so a theme or syntax pack read
as "Manifest metadata only".

* fix(plugins): a syntax failure belongs to one revision, not the syntax id

A grammar that failed in revision A kept the plugin card flagged after revision B
replaced it. Failures are now keyed by id@revision, matching the runtime slots,
and dropped when the installed list no longer contains that revision.

* chore(plugins): format the Gleam example grammar

* plugins: import the Monaco editor API, not the full bundle, from the syntax runtime

* plugins: type the syntax engine against the Monaco API it uses

* plugins: suggest a catalog plugin for files Orca can't open

When a file shows as plain text or as a binary Orca can't display, and a
cached catalog lists a release whose fileTypes include its extension, the
editor offers it: View plugin opens the marketplace preview (never an
install), Not now hides it for the session, and Don't suggest remembers
the extension. Suggestions come only from cached, verified catalogs, skip
kill-listed releases and anything an installed (even disabled) plugin
already handles, and can be turned off in Plugins settings. A release's
fileTypes must match its manifest's syntax extensions.

* fix(plugins): scope the file-type suggestion's Not now to the file type

The editor stays mounted when its file changes, so a single hidden flag
suppressed the suggestion for the next file type too. Track which file
type was hidden instead.

* plugins: type the plugin-owned settings normalizer inputs

* plugins: update the preview-request callback ref after render, not during it

* test(plugins): smoke specs for themes, syntaxes and file-type suggestions

Three end-to-end specs join the demo and marketplace journeys:
- an app theme and icon theme apply from Settings and fall back when the
  plugin is disabled;
- a plugin syntax highlights an already-open file, re-highlights it after a
  grammar update, and lets go when disabled;
- a v2 catalog release with fileTypes is suggested for an unknown file,
  View plugin opens its preview without installing, and Don't suggest is
  remembered.
The marketplace spec's Git fixture helpers move to a shared helper.

* test(plugins): say why the file-type suggestion e2e needs a longer timeout

* test(editor): install Monaco's cancellation guard in the owner view-state test

The test mocks monaco-setup, which skips the guard production installs, so
a disposed editor's pending word highlight surfaced as two unhandled
"Canceled" rejections once plugin syntaxes added a render. The run
passed but reported errors.

* plugins: plugin app themes recolor the code editor

When a plugin app theme is active, every Monaco surface (editor, diffs,
notebook cells, code excerpts, automation prompt) uses a theme derived
from the colors the document is actually painted with: editor background,
text, line numbers, cursor, selection, current line and widgets. Syntax
colors stay the built-in light or dark ones; if text would fall below
4.5:1 or a syntax color below 3:1 on the plugin's editor background, the
editor keeps its built-in theme. Colors are resolved to hex through the
browser's own parser, so color-mix() and other CSS color forms work. The
theme is redefined when the plugin updates or the mode changes, and the
editor returns to the built-in theme when the plugin theme is deselected,
disabled or removed.

* refactor(plugins): one module per contribution point and a single points table

The manifest's contributes object, the execution-profile derivation and the
catalog type list now read one PLUGIN_CONTRIBUTION_POINTS table instead of
three hand-kept lists.

* refactor(plugins): express id and path rules as patterns

Plugin ids and relative paths now validate with a regex first, so a generated
JSON Schema can carry them as `pattern`. The predicate only adds what a
pattern cannot (reserved names, Windows device names), and a bad value
reports one issue instead of two.

* plugins: document every manifest and catalog field in the schema

Each field now carries a description, examples and the Orca release that
first reads it, and each contribution item has a named fragment id. Manifest
v2 accepts an optional $schema key for editor completion, and the catalog
gains an authoring schema built from the same field definitions.

* plugins: generate published JSON Schemas and author references from zod

pnpm generate:plugin-schemas writes schemas/plugins/orca-plugin.schema.json,
schemas/plugins/orca-marketplace.v2.schema.json and the matching reference
pages under docs/reference from the runtime schemas. A test fails when the
committed files drift, when a field lacks a description or since-version,
when a documented example does not validate, and when the schema and the
runtime disagree on bundled manifests or known-bad inputs.

* plugins: report every manifest and catalog mistake with its path

Manifest and catalog validation now lists up to eight distinct problems as
`contributes.themes[0].label: must not be empty` instead of zod's first raw
message, and rewrites parser wording (required, unknown keys, allowed
values, limits) into what the author should change.

* plugins: say which Orca a newer plugin or catalog release needs

A manifest the strict schema rejects is pre-read for engines.orca and
manifestVersion first, so a plugin built for a newer Orca reports
"requires Orca >=X (this is Y)" instead of unknown-key noise. A catalog
release whose minimumOrca is above this build stays listed but is refused
before anything is downloaded.

* plugins: version the published schemas and pin v2 fields to a real release

Schemas move under schemas/plugins/v2/ so a later breaking format can
publish beside them without changing what a pinned URL means, and the
example plugin authors copy carries its own copy for offline completion.
Fields new with manifest v2 cite one constant, PLUGIN_MANIFEST_V2_SINCE
(1.4.232, the release after the latest tag), and a test fails once a tag at
or above it ships without manifest v2. The reference documents format 1 as
legacy.

* plugins: open review when enabling a plugin that was never approved

Dismissing the review dialog records keep-disabled without consent. Turning
the plugin's switch back on then only cleared the disable, so it returned
to "Needs review" with no dialog. Enabling now opens the review whenever
the result is still pending.

* plugins: release details for the plugin detail page

Catalog releases may carry a license, repository and homepage (https only),
an updatedAt date and a short plain-text changelog; an invalid value is
dropped so it never hides the plugin. Manifests may declare license and
homepage. Marketplace listings flatten name, version and those details, and
installed plugins report license, homepage, repository and the version a
rollback would restore.

The JSON lint-staged step no longer fails a commit whose only staged JSON
files are generated schemas the formatter ignores.

* plugins: fold in the schema review

- Parity runs both ways: documented examples also pass their zod field,
  bundled manifests pass both the schema and the runtime, and manifest and
  catalog negatives (traversal, oversize, wrong types, duplicates) fail in
  both. A table lists every rule only the runtime can enforce, and each is
  named in the description of the field it constrains.
- The published files carry the format in their names
  (orca-plugin.v2.schema.json, orca-marketplace.v2.schema.json); $id stays
  on main until the v2 release, and a test demands the release tag once it
  exists.
- The v1 since-value is 1.4.160, the first release that read plugins, and a
  test pins it to the tags. engines.orca documents that format 2 needs at
  least the release that ships it.
- v2 catalog sources strip unknown keys like the rest of the entry, and the
  v2 runtime schema is derived from the one authoring definition, whose
  plugins field documents the duplicate-id rule. Contribution types and
  categories declare uniqueItems; fileTypes items come from the file-type
  schema itself.
- Issue messages no longer depend on zod's English: a missing field is told
  from the input, and every pattern carries its own message. Unknown-key
  lists show five keys plus a count, and each line is capped at 200
  characters.
- A prerelease host (1.5.0-rc.1) now ranks below its release, as in semver.

* plugins: read the rollback version on demand, not while listing

The plugin list is rebuilt at startup and on every plugin change, and each
installed plugin cost a directory read to find the version a rollback would
restore. That moves to plugins:getRollbackVersion, which the detail page
calls when it shows the action, and a test pins the list build to zero
filesystem calls.

* plugins: v1.4.232 shipped without manifest v2, so v2 fields cite 1.4.233

The release-tag check caught it: v1.4.232 was cut from main before this
stack landed.

* plugins: list installed plugins while the plugin system is off

With the system off the service discovers nothing, so plugins:list returned
an empty list and the settings page could not show what is installed. The
client list now reads the install tree on demand in that state and projects
every plugin as disabled; nothing is activated or started, and startup with
the system off still does no plugin I/O. Older clients that hid the list
while off are unaffected.

Also for the plugin page: plugins:getInstalledManifest returns one installed
plugin's parsed manifest on demand, marketplace installs report the catalog
ref beside its URL, and the listing type declares the release record it
already carried.

* plugins: final schema decisions for catalogs and versioning

Catalog
- The catalog is orca-catalog.json with a required catalogVersion: 2; a
  client that sees a higher version refuses with "update Orca to browse
  this catalog". New clients read it first and fall back to the frozen
  orca-marketplace.json, which export-legacy-plugin-catalog.mjs generates.
- Parsing is tolerant at every level: unknown keys are stripped, unreadable
  entries and releases are skipped, over-limit lists are trimmed, a repeated
  id keeps the first copy, invalid display-only fields are dropped.
  pnpm validate:plugin reports each of those as an error for authors and CI.
- Entries list releases newest first; Orca offers the first whose
  minimumOrca it meets, and shows "Needs Orca X" only when none fits.

Versioning
- engines.orca is the only gate and must cover the newest since-version of
  every field and enum value used (capabilities, events and built-in actions
  now carry per-value since tables). The check is part of the v2 schema, so
  validation, catalog checks and installs all enforce it.
- pluginApi is optional in format 2 (defaults to 1) and refused on data and
  instructional plugins. Format 2 rejects unknown keys at every level,
  including engines, author and every contribution item.
- contributes.agents is reserved for coding-agent definitions and must be
  empty in format 2 until their format exists.
- Plugins are gated against the newer of the app version and the newest
  since this build defines, so development and local builds, whose package
  version only moves at release, read the fields they introduce.

The policy is written into the generated references and pinned by tests.

* plugins: one-click install for content-only plugins, and the verified profile on the list

installMarketplacePlugin takes an optional enableIfData. Main then turns the
plugin on in the same step only when the installed, verified manifest is
execution: data, approving exactly the fingerprint it just installed through
the normal consent path; anything else comes back needsReview: true, whatever
the renderer sends. Older hosts ignore the field and leave the plugin pending.

Installed plugins now report their verified execution profile, so the plugin
page never infers "content only" from the absence of a worker.

* plugins: images and contribution summaries for plugin pages

Manifest format 2 gains cover and screenshots (alt text required), and its
icon must be a square PNG or WebP. Images are checked at install from their
headers only: static PNG, JPEG or WebP, per-kind pixel and byte limits, at
most 4096 pixels a side and 8 MB in total, so nothing is ever decoded to
decide trust.

Catalog releases may carry images stored in the catalog repository and
pinned by SHA-256, a summary of what they add, and the capabilities they ask
for, so a listing can show the plugin completely before anything is
downloaded. All three are dropped when unreadable; the summary and
capabilities must match the manifest exactly at install. pnpm validate:plugin
checks each image's hash, size and format against the repository files.

* plugins: serve plugin and catalog images from verified bytes

Images reach the renderer only as orca-plugin-image URLs, which main serves
with nosniff and the type it read from the header:
- catalog images are read from the catalog repository at the fetched
  commit, kept only when their hash, size and format match, and cached by
  SHA-256; a listing references only those;
- preview images are cached by hash before the staging tree is deleted;
- installed plugins' images are named by plugin, install hash and slot and
  re-checked on every read, so listing plugins still reads no files.

Listings, previews and installed plugins carry an images field the plugin
page can render without fetching anything else.

* plugins: catalog theme palettes, and one-click never approves a dev override

Catalog releases may list up to four theme palettes (hex colors only) so a
listing can draw theme cards before install; they are display-only and
dropped when invalid. One-click enable now also refuses when a development
override shadows the installed plugin, since those are not the bytes the
user just chose.

* plugins: one-click approves only the fingerprint the user saw

installMarketplacePlugin takes an optional expectedFingerprint, the
preview's consentFingerprint. With enableIfData, main now approves only
when the installed fingerprint equals it, so bytes that changed between
preview and install stay pending for review instead of being enabled. The
release commit is already pinned by resolvedCommit. Older hosts ignore the
field.

* plugins: gate manifest v2 on 1.4.242 and publish schemas under docs/schemas

v1.4.240 shipped without manifest v2, so the first release that reads it is 1.4.242.
The root-directory guard refuses a new top-level schemas/ folder.

* plugins: read unvalidated schema input through a typed object guard and break the image validation import cycle

* plugins: one place decides an installed plugin's status word

Pull the status ranking, label and enabled/needs-review checks out of the settings row so the
coming list rows and detail page read the same words. No visual change.

* plugins: one list entry shape for installed and catalog plugins

PluginListEntry joins an installed plugin to its catalog listing (or stands for a listing alone),
and matchesPluginSearch is the one search both lists use. No visual change.

* plugins: usePluginCatalog owns catalog loading, refresh and preview

Sources, listings, refresh, the install/update preview and the outside preview request move out
of PluginMarketplaceBrowser into one hook the coming list and detail page share. The browser
keeps only its view state. No visual change.

* plugins: one plugin list with fixed slots, a plugin page, and inline states

- Installed plugins first, then one group per catalog (verified/unverified · owner or URL), capped
  at 6 rows with Show N more; every plugin appears once
- Rows: the name opens the plugin page and covers the row; status, action, switch and ⋯ are
  fixed-width sibling slots in Tab order. Update {version} is the action when a catalog has a newer
  release; Stopped offers Restart with a red mark on the switch; Blocked offers Remove…
- Plugin page in the same pane: what it adds (commands with platform shortcut chips, panels,
  recipes, contribution summary), what it can access, logs, and info. Back link, Alt+← or ⌘[
  return focus to the row
- States inside the list: skeletons, a catalog that fails only affects its own group, no results
  with Clear search, nothing installed. Plugins off keeps the installed list visible, dimmed
- Add plugin ▾ (folder, Git URL) replaces Install plugin; Plugin system stays at the top
- Catalog listings now expose their release (name, version, minimum Orca, types) to the renderer

* plugins: filter by category and catalog, one review for Update all, Needs newer Orca

- Filter popover (category, catalog) next to search; search and filters show every match
- Update all opens one review: updates that ask for new access are expanded with what's new, the
  rest are one line, any can be unticked; installs run one at a time
- A catalog release that needs a newer Orca says so and disables Install, in the row and the page
- Installing from a plugin page shows a review with only access and source, since the page just
  showed what it adds

* plugins: plugin page uses the manifest name; Off status word; official isn't a category

* plugins: catalog sources live in the Add plugin menu

* plugins: search takes its own row when the list is narrow

* plugins: release notes, license and links on the plugin page; roll back names its version

* plugins: fix list review findings

Plugin page owns the pane (back link and plugin header only); Adds to Orca names
every contribution; access never reads as 'nothing' when the check failed; filter
kinds have readable names; Update all is reachable, fetches once per open and
routes updates needing review; installed plugins join their own catalog;
attention-first order; paused list while plugins are off.

* plugins: readable category names on the plugin page

* plugins: Update all keeps its set; reviews queue; updates only from the plugin's own catalog

* plugins: name what an off or unreviewed plugin adds from its manifest; join catalogs by url and ref

* plugins: Needs Orca checks use the version plugins are gated against

Main gates plugins on pluginHostVersion, the newer of the app version and the
newest since-version the build reads. The plugin page compared releases to the
raw app version, so a development or local build showed Needs Orca for plugins
it would install.

* test(plugins): e2e journeys follow the redesigned plugin list

The page offers "Turn on plugins" while the system is off, catalog rows share
the installed rows' data-plugin-key, the review action is "Review", and an
enabled plugin reads "On".

* plugins: describe access from the verified execution profile, never from "no worker"

A commands-and-shortcuts plugin's page said "Content only. It has no code that runs."
because the page treated "no worker" as "no code". A worker plugin without capabilities
read "runs in a sandbox". The page now classifies from the catalog release's execution
profile (install refuses bytes whose manifest differs), the fetched manifest, or the
installed manifest's profile from the plugin list's `execution` field. Unknown stays
"Reading…" or "Couldn't read", never content only. The review dialog uses the same
profile, and an older host that omits it is reviewed as instructional.

* fix(plugins): read revision-scoped syntax failures on detail pages

* plugins: use the Blocks glyph for icon themes in the plugin page

* plugins: update latest-value refs after render in the update dialog and page navigation

* i18n: retire translated entries for plugin strings this change rewords or removes

* plugins: one action to install, inline consent, and a picture-first catalog

Settings > Plugins no longer opens a confirm dialog and then a review dialog.
- Content-only catalog plugins install from their card or page in one click; main
  turns them on only when the verified manifest is data. Undo stays in place for 10s.
- Plugins that run code or instructions show "If you install <Name>, it can:" on the
  host security surface above one Install & enable, which stays off until the access
  rows are read, and approves exactly the fingerprint shown. Review of an installed
  plugin happens on its page too (Enable / Keep off); Remove stays in its menu.
- The catalog is a grid of cards drawn from what each plugin is (its theme colours once
  installed, a code sample, keycaps), with the name as a stretched link and one action.
- The page pins the listing it opened, says "Newer version available" instead of
  swapping rows, and previews are prefetched only on hover intent, capped per visit.
- The plugin system switch, file suggestions, catalog sources and development folders
  move into a header menu; amber text uses a contrast-safe token.

* plugins: give the reworded no-access line its own key so English shows the new copy

* plugins: pin installs to the release shown, warn by what a plugin carries, disclose replaced shortcuts

- The page pins the listing it opened; a preview of any other release (commit, catalog
  commit or execution profile) leaves Install & enable off and shows "Newer version
  available". One-click installs from a card refuse the same mismatch.
- Warnings are the union of the verified profile and the contributions: a panel that
  also adds commands gets the instructions warning, never "bounded" or "own files only";
  unknown contributions over-warn. Access rows follow the same rule.
- Shortcuts that replace one of yours are named in the consent before install, from the
  manifest or the catalog summary; the shortcut list itself stays in Adds to Orca.
- Pages paint from the catalog summaries, capabilities and theme palettes, and show the
  cover (or the card's art) as a hero, with screenshots in a strip and lightbox.
- Installing fills a reserved status line and keeps its action row (Undo, Use now), so
  the page doesn't move; the settings scroller keeps a stable gutter.
- Row art names the plugin type instead of initials, rows drop the "Needs review" word,
  the content hash moves into the menu, and card art is tinted and coloured.

* plugins: catalog covers on cards

* plugins: Review reopens its page after Back; hero art and stable status line follow what an installed plugin carries

* test(plugins): e2e journeys review and install on the plugin page, with no dialogs

* plugins: install exactly the release shown, classify from the full manifest, polish the page

Code review:
- A page or card arms Install only when the preview matches everything the listing showed
  (catalog commit, release commit, content hash, execution); any change, including a
  catalog-only refresh, reads "Newer version available". One-click installs send the
  preview's fingerprint as expectedFingerprint, so main turns on only those bytes.
- Install stays off while a catalog refresh is in flight.
- Trust uses the full verified preview manifest when present; a capped catalog summary
  counts as unknown and gets the stronger warning.
- The installed-manifest read is keyed by plugin and installed revision, so a same-key
  update never pairs old contents with the new fingerprint.
- A failed Undo shows its error on the card or page and keeps Undo to retry.
- The security-surface test now covers the inline consent instead of the removed dialog.

Visual:
- Lightbox: close button clear of the image, "1 of N" with previous/next and arrow keys,
  content scrolls on short windows, focus returns to the opening thumbnail.
- Keycap art shows the plugin's own first shortcut, larger, with the platform's keys.
- The hero fills the content column; a screenshot identical to the cover isn't repeated.
- "Change in Shortcuts" is a link; the chip says "Adds commands" (or "Runs a worker",
  "Adds a panel", "Content only"); the disabled button says "Reading what it can access…".
- A content-only plugin's action row keeps fixed slots, so its note doesn't move on install.

* plugins: keep the decision above the fold, route suggestions to the page, final review fixes

- The page shows the decision (Install, the consent, Enable) before the picture, and the
  picture is capped and cropped from its centre, so the button is in view at 1280x800 and
  900x700. Nothing grows after first paint.
- The consent gives no access verdict while the plugin is still being read.
- A file-type suggestion opens the plugin's page instead of the old install dialog; that
  dialog now serves updates only.
- A theme plugin reserves its "Use now" line from first paint; the action slot and its
  button share one width.
- The file-suggestion toggle reports a failed save; a failed Undo is reported once, on
  the card or page that offered it.
- Cards show a plain trust chip ("Content only", "Adds commands"); the local and bundled
  group reads "Bundled & local".
- Install error copy moves into the protected plugin namespace, and the new consent
  components join the language-pack security pins.
- Dead code goes: the keybinding consent preview (its conflict helper stays), its strings,
  trustRunsNothing and the access-only preview plumbing.

* test(plugins): the file-type suggestion opens the plugin page, not a dialog

* plugins: the install decision sits top-right beside the name; no plugin on/off switch in the UI

- Plugin page: Install (content only), Install & enable (disabled until its access is read),
  Enable / Keep off (installed, waiting for review) and the installed switch share one slot
  top-right of the title. The consent sits directly under the header with no button of its
  own; the "Can't reach anything outside its own files" note sits on the line under the
  publisher, which every state reserves, so nothing moves between states.
- Removed "Turn off plugins" from the ⋯ menu and the "Turn on plugins" button from the off
  state. The setting and all gating stay: off now comes only from policy, and the page says
  so without a switch.
- e2e journeys turn plugins on through the setting and use the page's one button.

* plugins: restore Turn on plugins, drop Undo, add Remove to installed card menu

Plugins stay opt-in, so the off state keeps its Turn on button; only the
menu's Turn off item stays removed. Installed catalog cards get a sibling
more-actions menu with Remove (immediate, inline error) and Turn off/on,
inside a fixed-width action slot so the footer doesn't shift.

* test(plugins): installing a theme plugin never changes the active theme

* fix(plugins): confirm card removal before deleting data

* fix(plugins): pause review queue when leaving plugin page

* fix(plugins): disclose execution changes before updating

* refactor(plugins): share single and bulk update review

* refactor(plugins): share installed card and row actions

* test(plugins): cover confirmed removal and shared update review

* feat(plugins): offer icon and terminal themes after install

* test(plugins): capture settled review dialog screenshots

* fix(plugins): make installed contributions discoverable and actionable

* fix(plugins): label card-install toast Try it and mark terminal-theme Use now buttons

The toast action opens the plugin page rather than applying a theme, so it
always says Try it. Terminal themes often share their app themes' names, so
their Use now buttons carry a terminal icon. Update the update-review e2e for
the Use now accessible names.

* fix(plugins): apply a theme plugin's themes when the user installs or enables it

Installing (card or page) or enabling a plugin that adds themes now applies its
app theme (matching the current light/dark mode), icon theme and terminal theme
at once, with a 'Now using' toast whose Switch back restores the previous
selections. Updates, startup discovery and catalog refreshes never apply.

Co-Authored-By: Claude <noreply@anthropic.com>

* plugins: call toast actions through a typed button in tests; key card art by stable names

* i18n(plugins): drop retired plugin strings from the Portuguese catalog

* test(plugins): say why the update review e2e needs a longer timeout

* i18n: retire translated entries for plugin strings this change rewords or removes

* refactor(plugins): organize main plugin files by responsibility

* refactor(plugins): group shared plugin schemas and contracts

* refactor(plugins): organize renderer plugin surfaces

* plugins: keep the plugin service under the line limit after the folder moves

* refactor(plugins): move the JSON object guard beside the manifest schemas

* test(plugins): move the flaky-test baseline entries with the worker tests they count

* test(plugins): point the panel lifetime test at the reorganized plugin folders

* plugins: reserve skills, mcpServers, taskSources, connections and settings contribution keys

Format 2 accepts each key only as an empty list, so the points can ship later as additive
minors without a new manifest format.

* plugins: freeze host API v0 with a generated reference and refuse unknown pluginApi majors

The method table now carries a summary and an optional deprecatedSince, and renders
docs/reference/plugin-host-api.md with the stability and deprecation policy. A manifest
naming a newer host API major fails with "update Orca" instead of "must be 1".

* plugins: name the bad release in catalog validation and re-ask on any execution profile change

validate:plugin now reports each unreadable release with its reason (for example a tag
instead of a full commit SHA) rather than a count. The consent fingerprint includes a
declared non-data execution profile, so a data pack that becomes instructional no longer
keeps its one-click approval. Tests pin that catalogs drop non-SHA releases, that a
repository cannot load or approve a plugin it carries, and that added capabilities or a
changed profile always need re-approval.

* plugins: add the author guide, theme token list, v2 examples and a printed catalog release entry

docs/reference/plugin-authoring.md walks write, load a development folder, validate and
publish. The theme token table is generated from the public token set and its fallbacks.
hello-orca and hostile-panel move to manifest format 2. validate:plugin on a plugin
repository root materializes HEAD the way install does and prints the release entry to
paste into orca-catalog.json.

* plugins: name the host API type renderer and reserved contribution fields for their role

* plugins: gate the bundled example plugins on 1.4.240

* plugins: reserve orca, orca-*, official, anthropic, openai and github publishers

* plugins: rename storage/secrets to storage:own/secrets:own and gate events by domain capability

events:subscribe and the events.subscribe host method are gone; workspace events need
workspace:read and agent status needs the new agents:read. Format 1 manifests keep their old
names, rewritten on parse.

* plugins: move samples and fixtures off the now-reserved orca-samples publisher

* plugins: name the public surface workspace, not worktree

Events become workspace.created/removed with workspaceId and a kind field, agent status
carries workspaceId, and command context/keybinding when use workspace. Format 1 names are
rewritten on parse, v1 workers still receive worktree.* handlers, and clients treat any
non-global context from older hosts as workspace-scoped.

* plugins: rename the instructional execution profile to declarative

* plugins: move agents into the reserved contribution keys

Format 2 keeps contributes.agents empty like every reserved point; format 1 entries, which
Orca never read, are dropped on parse so they stay inert and bind no content.

* plugins: skip unknown theme tokens and icon slots at runtime, refuse them in validate:plugin

A token or slot added in a later release no longer fails the whole theme on older Orca.
validate:plugin now parses each theme, terminal theme and icon theme strictly. App themes
accept an optional schemaVersion 1.

* plugins: inject panel design tokens as --orca-* custom properties

* plugins: accept a closed, documented list of panel icons

Format 1 panels keep any icon name: a known one in either Lucide spelling is canonicalized,
an unknown one is dropped and shows plug, as before.

* plugins: make catalog release types optional and derive them

Every required release field is permanent for old clients, and types repeat what contributes
and execution already say. Printed release entries no longer include it.

* plugins: strip unknown kill-list and consent fields instead of refusing them

A strict kill list froze old clients on their cached list as soon as a later list added a
field, silently stopping revocation; a strict consent request would refuse a newer client's
optional review field.

* plugins: check only the shape of a panel action in the renderer

The host's capability gate already refuses methods panels may not call. Filtering against the
client's own method list blocked every panel method a newer host added on older clients,
including remote and SSH ones.

* plugins: read a leading >=x.y.z from engines.orca leniently in the compatibility pre-read

* plugins: derive content-bound approval from the contribution policy table

Each point now declares bindsContent, replacing the hand-kept list in the consent
fingerprint, so a new point such as mcpServers cannot swap bytes under an old approval by
being forgotten.

* plugins: name the host API "Host API 1" and add a stability section to the author guide

The guide now lists what is stable and what is experimental: the agent status payload, the
worker process model, language-pack keys, validate:plugin output beyond its exit codes,
settings:own and manifest format 1. Secrets copy now says encrypted file, not OS keychain.

* plugins: make the hello-orca sample speak in plain words

The worker command returns a sentence instead of a JSON object, and the panel labels
terminals by position instead of their opaque ids.

* plugins: give plugins short branch names, not refs/heads/...

* plugins: panels can no longer type into terminals; workers only (D3)

terminal.sendText with Enter runs shell commands locally or over SSH, which
the sandboxed-panel tier's "limited" consent copy did not convey. The
capability copy now says so, and the sample panel no longer types.

* plugins: put full-access worker plugins behind a second setting, off by default (D2)

Trusted workers are unsandboxed Node, so the plugin system switch alone no
longer lets them run. With "Allow plugins that run code with full access"
off, main refuses to install a full-access catalog release, approve or enable
one, and keeps installed workers stopped with an error naming the setting.
The catalog card says "Full access · turned off in settings" and links to
the switch; the worker chip now reads "Full access".

* plugins: bind dev-folder approval to the folder it was given for

A dev plugin's consent fingerprint now includes the folder's canonical path,
so approving one dev folder no longer approves another folder whose
manifest claims the same key and capabilities. The development dialog now
says edits inside an approved folder run without another review.

* plugins: close WebRTC as an outbound channel from panels

CSP has no WebRTC directive, so a panel could reach a STUN/TURN server it
controls despite connect-src 'none'. The panel shell now locks
RTCPeerConnection to undefined before plugin code runs, and the main window
that hosts panels gets the same WebRTC IP-handling policy as doc preview
guests. DNS-prefetch hints remain the residual doc preview already accepts.

* plugins: stop language packs relabelling trust copy outside plugin settings

Protection covered only plugin copy under settings, so a content-only pack
could rename the catalog's Official badge, the file-type suggestion banner,
or swap Allow and Deny on agent approvals. Those components are now
protected whole, the same way the plugin security dialogs are.

* plugins: refuse Git URLs with a dash-led user or host, or a '..' host

ssh reads a leading '-' as an option, and the scp form is not normalized, so
'stablyai/../evil' read as the official owner. The shared URL check used by
the catalog schema, validate:plugin and every install path now refuses these,
and provenance parsing refuses '..' path segments.

* plugins: flag an update whose catalog moved it to another repository

An update whose catalog now points at a different repository showed the new
URL without saying it changed. The update review now names the old and new
repository and leaves that update unticked, so it installs only once the
user opts in.

* plugins: sample worker returns a readable toast; sample panel shows no internal ids

Hello Orca's ping returned raw JSON, which the command toast printed as-is,
and its panel showed the full ref name. It now returns a sentence and shows
the plain branch name.

* plugins: move runtime policy checks and catalog types out of files over the line limit

* plugins: refresh the plugin safety list every 6 hours while plugins are on

Revocations previously reached a session only at launch or when plugins were
turned on, so a long-running Orca kept running a revoked plugin.

* plugins: main sends an update from a moved repository to review

The update dialog left such updates unticked, but main still accepted a
one-click approval for them. Main now compares the installed and new
repository itself and always answers needsReview.

* plugins: label the plugin system Alpha instead of Experimental

Settings badge, nav entry badge, search/description copy in all locales,
and plugin docs' stability sections. Setting keys are unchanged.

* plugins: trim the Plugin settings menu to titles and a short full-access hint

* plugins: reserve statusBarItems and sidebarCards and list planned contribution points

Both keys are rejected when non-empty, like the other reserved points. The
manifest and authoring references gain a Planned contribution points list
built from one table. Also moves the Alpha note on the host API page into its
generator so the schema/doc parity test passes again.

* plugins: an update that asks for nothing new keeps its approval

Before, any catalog update to a declarative, sandboxed-ui or trusted-worker
plugin changed its consent fingerprint (it binds the package bytes), so the
plugin turned off and waited for review after every Update. Only content-only
(data) plugins kept running.

Main now reads the approved version before installing and, when the update
comes from the same repository and adds no capability, no higher execution
profile, and no new command, shortcut, panel, event or VM recipe, records the
new fingerprint so the plugin stays on. Anything that grows access, a moved
repository, a development override or a plugin that was never approved still
waits for review, and the full-access switch still gates trusted workers.
The update preview now carries what grew, so the Update dialog lists new
shortcuts, commands, panels and events as well as capabilities. The consent
copy says updates that don't ask for more access install without asking.

* plugins: match the full-access test to the shorter setting name

* plugins: update catalog plugins automatically when an update asks for nothing new

Adds "Update plugins automatically" to Plugin settings, on by default. The
setting is pluginUpdatePolicy ('auto' | 'manual') so later policies can be
added without a migration.

At startup (after a short delay) and every 12 hours, packaged Orca refreshes
the catalogs and, for each enabled, approved catalog plugin with a newer
release, installs it in place when it comes from the same repository and
grows no access; the approval carries over and the worker reloads. A release
from a non-official catalog installs only after it has been listed for 12
hours, timed from when this Orca first saw it (stored locally because
catalogs carry no listed-at time), so takedowns can land first. An update
that grows access or moved repository is not installed: the current version
keeps running and the plugin shows "Update needs review", whose Review
update button opens the existing update review with what is new. Plugins
updated this way show a quiet "Updated to vX" chip, and each pass shows at
most one toast. Manual Update and Update all are unchanged.

* plugins: clear code-quality findings in the update approval and auto-update code

* plugins: give an app theme that leaves --sidebar unset a visible sidebar edge

Orca draws no line between the left sidebar and the main area; the sidebar's
slightly different surface is the edge. A theme that omitted --sidebar (like
the Dusk sample) had it filled from --background, so the sidebar matched the
main area and the edge vanished. An unset --sidebar is now mixed 8% toward
the theme's foreground. The in-repo sample theme sets --sidebar explicitly
and the theme smoke test checks it reaches the sidebar.

* plugins: treat an unknown update policy as manual, not auto

* plugins: auto-update only pinned releases, and only the release that was held

* plugins: start the auto-update hold clock before the catalog refresh, and check every 6 hours

* plugins: refresh the kill list right before auto-installing, and skip the install if that fails

* plugins: refuse an unpinned full-access install while full access is off

* plugins: an automatic update re-checks inside the install lock that the plugin is still installed

* plugins: compare the full repository path, so a GitLab subgroup move counts as moved

* plugins: time out the kill-list fetch and retry 15 minutes after a failure

* plugins: drop one unreadable kill-list entry instead of the whole list

* plugins: keep language packs off the orca.yaml and notebook trust prompts, certificate warning and site permission prompt

* plugins: compare whole capability declarations on update, and cover swapped command actions and shortcuts

* plugins: log automatic updates so headless and remote hosts record them

* plugins: say that automatic updates can change what a full-access plugin does

* plugins: pin and document the nested-frame WebRTC residual in plugin panels

* plugins: delete the unused PluginReviewBody

* plugins: drop the dead repositoryMoved parameter and the predecessor wrapper

* plugins: declare the plugin policy settings type once

* plugins: show what main found an update changes instead of recomputing it in the renderer

* plugins: move update approval and update policy into the catalog folders

* test(plugins): expect the workspace.removed event name in the lifecycle test

* plugins: rename structural helpers for their role, type the update identity read, and give plugin system settings their own group

* i18n(plugins): drop retired plugin strings from the Portuguese catalog

* plugins: build the hello-orca status line with a template string

* plugins: install catalog plugins from a directory of a shared repository

A catalog source may name a 'path' inside its repository. Orca downloads the
pinned commit as before but materializes only that subtree, so contentHash
covers the plugin's own files and sibling commits never change it. A changed
path counts as a moved repository and needs review. validate:plugin accepts a
subdirectory and prints its source path; the frozen v1 export skips such
entries.

* plugins: offer the full access setting on a full-access plugin's page

The page offered Install & enable even with full access off, then failed with a generic error.
It now shows the card's 'Turn on in settings' action, names the real reason when main refuses,
and drops a stale install error once the full access setting changes.

* plugins: say a content-only plugin couldn't load its files, not that it crashed

Language packs and other data plugins have no code or logs, so a load failure now reads
"Couldn't load its files" on the page and the row instead of "Its code kept crashing".

* plugins: leave Settings before running a built-in action from a plugin page

Built-in actions such as Open Board and Go Back are disabled while Settings covers the app,
so Run on the plugin page always failed. Run now closes Settings, commits that synchronously
so the dispatcher sees the app view, then runs the action.

* plugins: refuse terminal:send on panel plugins and never list it on panel approvals

Panels get no host method that types into a terminal, yet a panel's approval listed
"Type and run commands in your terminals". Format 2 manifests now fail validation when a
profile below trusted-worker asks for terminal:send, and approvals, the access section and
update review drop that line for format 1 panels that still declare it.

* plugins: say "catalog" in user-facing copy, not "marketplace"

The menu said "Catalog sources…" but the dialog it opens said "Marketplace sources" and
asked for orca-marketplace.json. Plugin copy now says catalog in every locale, names
orca-catalog.json (still accepting the older file), and the author guide matches the UI.
Internal identifiers and settings keys are unchanged.

* test(plugins): run a built-in action from its plugin page end to end

* plugins: concise copy pass across plugin UI

Update review shows one line per plugin and only what's new for updates
that need approval, with the rest behind a See all access disclosure.
Consent, Can access, dialogs, settings menu and empty states drop restated
titles and filler; safety facts (new access, full access = files/network)
stay visible where the user decides.

* plugins: keep See all access on Button and Collapsible primitives' own spacing

* plugins: separate light and dark theme slots so packs pair and mix

Plugin app themes now fill a Light theme or Dark theme slot by their base. Orca paints the
slot for its current mode, so under System it swaps live with the OS. Appearance shows both
pickers grouped by plugin; install fills the matching slot(s) and Switch back restores them.
A legacy single selection migrates into its base's slot.

* plugins: installed carousel and card grid, category badges, In use state; load language packs that cover protected copy

* fix(theme): let plugin themes colour menus and dialogs

Menus, popovers, selects, hover cards, sheets and dialogs painted a fixed
glass tint (dialogs rgb 23,23,23 in dark mode), so a plugin theme's
--popover was ignored and a true-black theme still showed grey dialogs.
The tint now lives in --floating-surface / --dialog-surface, which a
plugin theme replaces with its --popover.

* plugins: name the app-theme slot a pack fills, not its terminal colours

* plugins: page the installed row by whole cards; e2e follows the card menu

* plugins: hide the installed row's scrollbar and draw kind art instead of initials

The installed carousel pages with its arrows and snap points; the native
scrollbar under it only added a stray bar. Format 1 catalog listings (the
official catalog until catalog v2 publishes) carry no release types or
images, so their cards fell back to a two-letter monogram. Art now reads
the kind from catalog categories, and the last fallback is a plugin glyph.

* plugins: reserve official publishers to Orca's catalog and pin installs to their catalog

Reserved publishers (stablyai, official, anthropic, openai, github, orca*; case-insensitive)
and orca-* ids now install only from Orca's official catalog: a Git URL, local folder or any
other catalog is refused, and a third-party catalog listing one is refused whole. The rule lives
in one import-free module the directory indexer can vendor; validate:plugin uses it too and
warns when publisher differs from the origin remote's owner.

A marketplace install never replaces one recorded from a different catalog; switching means
remove and reinstall. Docs state the publisher == repository owner policy (enforced by the
official and community catalog CI, not a new runtime field).

* plugins: label trust by catalog source and note other catalogs listing an install

The chip beside a plugin now says where trust comes from: the Official mark for Orca's catalog,
otherwise the catalog name and the owner parsed from its Git URL (github.com/acme,
gitlab.com/acme/tools), never manifest text; Dev · unverified for development folders and
Unverified for Git URL installs. An installed plugin's page says which other catalogs list it.

* test(plugins): give update review's Git-backed steps a loaded-runner timeout

Install and update review each fetch a plugin over Git. On a loaded machine that
took 25-30s with or without the publisher checks, past the 10s expect default,
so the test read as stalled on Installing…

* test(plugins): give the row-art fixture the entry's catalog source fields

* test(plugins): say why the panel WebRTC test needs a longer timeout

* feat(plugins): dismissible Alpha notice on the Plugins settings page

Shows "Alpha: The plugin interface may change. Expect nuances." under the
Plugins title until dismissed; dismissal persists in UI state
(pluginsAlphaNoticeDismissed). Localized in all locales, with unit and e2e
coverage for show-by-default and dismiss-across-reload.

Co-Authored-By: Claude <noreply@anthropic.com>

* plugins: stop shipping Orca Navigation Shortcuts and retire stale bundled installs

Removes the bundled copy and its fallback-catalog listing. On startup, bundled
bootstrap now uninstalls any previously bundled plugin this release no longer
ships, so existing users don't keep a protected entry they cannot remove.
Removal code moves to plugin-removal.ts to keep plugin-install.ts under the
line limit.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(plugins): concurrent saves of one image no longer fail the update review

Each save now writes its own uniquely named temp file and treats a rename
failure as success when a concurrent writer already produced the same
content-addressed file. A cache failure now only drops the image from the
preview or catalog refresh instead of failing it.

* fix(plugins): an update keeps unchanged machine recipes off the review's New list

Recipes always counted as new because their commands live in files the
manifest diff cannot see. Main now reads what each recipe can run (id,
checkout mode, commands) from the installed tree and the preview's staging
tree, and only a recipe that runs something new asks for review. An
unreadable file still counts as new.

* plugins: app themes color code in the editor via VS Code-shaped tokenColors

Theme files gain optional tokenColors (TextMate scope -> foreground/fontStyle).
The derived Monaco theme maps them onto Monaco's token names, the plugin-syntax
legend and raw TextMate scopes; unreadable colors are dropped.

* test(plugins): theme smoke checks tokenColors color the editor and revert

* i18n: retire translated entries for plugin strings this change rewords or removes

* plugins: add the custom editors contribution point

A plugin can now declare contributes.customEditors to open files of given
types in the editor tab, beside Source. The editor runs in the same
sandboxed, no-network panel frame and only exchanges the document's text
over a scoped postMessage channel; Orca keeps the buffer, dirty state,
saving, external-change banner, read-only tabs and SSH files.

The Editor | Source toggle and the 'first scene after load is the
baseline' approach follow community PR #27413.

Co-authored-by: Roughriver74 <115543510+Roughriver74@users.noreply.github.com>

* plugins: list custom-editor plugins with panels in the plugin list

* plugins: name an editor-only plugin's access 'Adds an editor'

* plugins: keep the Editors category label for custom-editor plugins

* plugins: restyle plugin frames in place and keep file paths out of editor frame names

A theme change used to remount panel and custom editor frames, which reloaded
the plugin and lost its state (Excalidraw undo, unsent edits). The host now posts
orca-panel-theme and the shell updates the --orca-* tokens and the html class in
place. Custom editor frames are named with a random id: window.name is readable
inside the frame, and the old name carried the file's absolute path.

* plugins: let custom editors open binary files as bytes (contentKind)

A custom editor can now declare "contentKind": "bytes" (with "readonly": true)
to receive the file's raw bytes instead of its text, for formats such as
spreadsheets. Orca pages the bytes from the host that loaded the tab (local,
SSH or a paired runtime, through the existing stat + readChunk calls, so no
wire change), refuses files over 16 MiB with a "too large to open" message,
re-sends on outside changes, and never applies a bytes editor's edits. The frame
gets a transferred ArrayBuffer and the matched suffix, never a path.

Documents are now versioned: { version, readOnly, content: { kind, ... } }.
Text edits may name a baseVersion; an edit against a replaced document is
refused with an orca-editor-edit-rejected conflict. Text documents keep the
legacy text field and unversioned edits still apply, so existing editors work.

"readonly": true on any editor makes the install review say "Opens .x files in
Orca" instead of "Opens and edits".

* plugins: suggest catalog custom editors for files Orca already opens

The file-type suggestion only offered a plugin when a file opened as plain text
or as the binary placeholder. It now also offers a catalog custom editor for a
file Orca shows with a language (an .excalidraw file opens as JSON): the editor
asks main with the `editor` purpose, which matches only release file types the
listing's contributions name as custom editors, and only when no installed
plugin's editor handles the type. Binary files such as .xlsx keep the existing
path, since release fileTypes already include custom-editor types. The banner
reads "A plugin adds an editor for .x files: Name"; Not now and Don't suggest
work per type as before.

* fix(plugins): an update that lets a custom editor edit, take over opening, or change content kind asks again

* fix(plugins): one unreadable catalog summary item no longer hides the whole summary

* fix(plugins): plugin editors never open Orca-core file types by default, and join Markdown, CSV and notebook view toggles as options

* fix(plugins): catalog releases list editor file types as data instead of parsing the display summary

* fix(plugins): custom editor edits must name their base version; drop the pre-release text field

* fix(plugins): a custom editor edit Orca refuses keeps the tab dirty and blocks the save instead of vanishing

Rate-limited edits are held and applied before the next save; oversized ones are reported to
the frame and the user. Every save, from any trigger, first flushes plugin editor frames with a
bounded wait and refuses to write while a frame holds edits the buffer lacks.

* fix(plugins): run custom editors as network-isolated guests and save only on real key presses

Editors see file contents, so they now run as <webview> guests in their own in-memory partition
instead of iframes of Orca's renderer. The session routes everything through a dead SOCKS proxy
(loopback included), refuses every non-editor request and permission, and each guest gets
disable_non_proxied_udp, no popups and no navigation. An Electron test proves fetch, WebSocket,
images and TURN-over-TCP all fail from the editor and from its srcdoc and javascript: child
frames, while the same page in a plain session reaches every listener. Electron 43 still resolves
<link rel=dns-prefetch> names outside the session; the test pins that residual and the docs say so.

Save chords now come from main's before-input-event on the guest, which page code cannot
synthesize; the shell's keydown forwarder and the orca-editor-keydown message are gone. Guests
also run in their own renderer process.

* fix(plugins): enforce a read-only custom editor in main, not just the tab

Main reads readonly from the installed manifest (bytes editors always count) and drops every
edit such a guest posts before it reaches the renderer. The tab host also refuses them and tells
the editor its document is read-only, even on a writable tab.

* test(plugins): the editor-suggestion catalog fixture lists editorFileTypes

* plugins: custom editors edit and save binary files; drop readonly and the isolated guest

Bytes editors now post `{ baseVersion, content: { kind: 'bytes', bytes } }` full
replacements (a transferable ArrayBuffer). The tab keeps the unsaved bytes beside
the text drafts, marks itself dirty, and the normal save pipeline (frame flush,
Cmd+S, close prompt) writes them: locally through a new `fs:writeFileBytes`
atomic replace guarded by the loaded bytes' SHA-256, and on SSH-served or paired
hosts by uploading chunks with `files.writeBase64Chunk` and swapping them in with
the new capability-negotiated `files.replaceWithUpload`. Older hosts are refused
before anything is written. A stale baseline is a conflict: the edit stays
unsaved and the external-change banner offers Reload or Overwrite. Source stays
the binary placeholder.

Per the product decision, every custom editor may edit: the `readonly` manifest
field, its main-side enforcement and re-approval rule are gone, and an editor
that only views simply never posts edits. Editors run in the normal sandboxed
plugin frame again instead of a network-isolated webview guest; the frame's save
chord is honored only while it carries real user activation. Consent now reads
"Opens and edits .ext files in Orca. It can read the files it opens."

* plugins: reserve agentHarnesses and hooks instead of agents; rename agents:read to agent:read

* plugins: syntaxes and custom editors share one file matcher (extensions with the dot, plus filenames)

* plugins: title for themes and syntaxes, when for commands (format 1 names rewritten on parse)

* plugins: add the editor catalog type for custom editors

* plugins: official means listed by the official catalog, not an orca- id

* plugins: one envelope protocol and window.orca for workers, panels and editors

Workers, panels and custom editors now share one internal envelope
({orca: 1, kind: req|res|evt|cancel}) instead of three message sets.
Frames get a frozen window.orca from the shell (api, host.call with
AbortSignal cancel, host.has, on, and document helpers in editors);
ready/hello carries the api, the surface's methods and the theme.
Editors gain host calls through their own session.

The host API table is keyed by method with typed params/results (no
handler casts), per-method surfaces and a numeric since minor; every
params schema is strict. Errors are {code, message, retryable,
retryAfterMs?} with cancelled/timeout/conflict/not_permitted/too_large
reserved and unknown codes read as action_failed. Event payloads are
typed with a closed agent-status enum, and the list entry type is
shared by main and preload.

* plugins: orca.document.onDidChange hands a late subscriber the current document

Editors that subscribe after their bundle loads (Excalidraw) missed the
document that arrived first and stayed blank.

* plugins: custom editor documents up to 64 MiB, with a large-file note past 16 MiB

The cap covers text and bytes on every route; reads and SSH or paired-host
writes are already chunked, so only the shared cap moves. Past 16 MiB the
tab shows a small, non-blocking note that the editor may take a moment.

* plugins: delete dead props and branches left by removed rows, logs toggle and access retry

* plugins: one status word per state (a crashed plugin reads Stopped everywhere) and drop unused plugin locale keys

* plugins: contribution rows use the manifest point names, so the policy table is the one source of catalog types

* test(plugins): assert the Use now line and page behavior, not Tailwind classes

* i18n: add the custom editor large-file note to the English catalog

* plugins: fold the loose shared plugin modules into contributions, frames, images, capabilities, events, languages, installation and catalog folders

* plugins: custom editor tab icons and contributes.newFiles templates

A custom editor may declare an icon; the tab and its hover card show it and the
editor title instead of "Editor", falling back to the plugin icon. contributes.newFiles
declares untitled file types (title, extension, optional UTF-8 template and icon) offered
in the tab bar + menu, its search, the command palette and the plugin page, created at the
workspace root through the Markdown untitled-file flow. The install toast's Try it creates
one for plugins that declare them.

* test(plugins): hidden e2e for a plugin new file from the + menu and its editor tab icon

* plugins: turn the plugin system on by default with a one-time migration

Saved false from the old default flips on once (pluginSystemDefaultedOn guard);
a later deliberate false is respected. The Turn on plugins off-state is gone,
and the official catalog seed is deferred off the startup path (and skipped
offline), seeding on demand when the marketplace is opened.

* plugins: move the card menu Disable/Enable directly above Remove, without an icon

* plugins: show the Alpha notice with the warning banner tokens

* plugins: show each category's icon in the filter dropdown

* plugins: seed drawn fallback art per plugin so two listings never look identical

* plugins: show manifest covers for catalog releases published without images

The official catalog's newest Midnight, Nord, OLED and Dusk & Ember releases
were regenerated without an images block (release-entry only kept images the
author pasted), so their cards fell back to drawn art. A catalog that keeps its
plugins in its own repository now borrows the manifest's images from the
catalog commit, checked like declared images, and the validator's printed
release entry includes them so regenerated catalogs keep their covers.

* plugins: drop the unused Turn on plugins strings

* settings: find the Plugins page by plugin, mod and extension searches

* plugins: remove the Allow full-access plugins setting

Full-access (trusted-worker) plugins now need only their own per-plugin
approval: main no longer gates install, consent, enablement or runtime on a
global switch, and the settings menu item, the Turn on in settings card and
page states, and the stored key are gone. The consent now says plainly what
full access means. Kill list, official review and publisher checks are kept;
docs note an enterprise block policy may come later.

* i18n: retire translated entries for plugin strings this change rewords or removes

* test(plugins): prune low-value plugin tests

Drop tests that asserted CSS classes, DOM structure, internal call counts,
copy, or duplicated coverage; merge overlapping e2e file-type suggestion
specs. Keep security, consent, data-integrity, compatibility and main
journey coverage.

* test(plugins): stop git auto-maintenance racing marketplace fetch teardown

* test(plugins): import the panel session token schema from the frame bridge

* feat(plugins): open orca://plugins install links at the plugin's page

orca://plugins/<publisher.id>?catalog=<official|community|git URL> opens
Settings -> Plugins at that plugin; it never installs. Plugins off shows
Turn on plugins first, an unknown catalog asks before it is fetched, and a
malformed link only toasts. Reuses the skill-share deep-link path (open-url,
second-instance argv, sidecar handoff) with repeat/burst dedupe, and
registers orca:// on packaged Windows, which NSIS leaves unregistered.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(plugins): show the linked catalog URL on its own line

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(plugins): follow main's moved plugin files and language-pack coverage field

* fix(plugins): keep a plugin disabled through its update and give format 1 event handlers worktreeId

* fix(plugins): keep held editor edits across view switches, ignore stale editor entry reads, share bytes across split panes, and meet the low-evidence lint

* refactor(editor): keep the file content loader under the line limit

* fix(plugins): keep a bytes draft's baseline across reloads, cover plugin new-file menu rows, and move render-time ref writes into layout effects

* refactor(tab-bar): keep the create-menu controller under the line limit

* refactor(tab-bar): keep the create-menu controller under the line limit with the plugin new-file case

* test(plugins): turn plugins back on through settings in the install-link journey

* fix(wsl): import the atomic plugin file writer from its new folder

* fix(plugins): save bytes custom-editor edits from Save-and-close and the overwrite banner

* test(plugins): park the Linux-only bytes editor e2e failure as a follow-up

* fix(plugins): follow main's new language-pack tests to the moved artifact module

* test(i18n): give main's new plugin language-pack fixture its coverage

* plugins: manifest v2 ships in 1.4.245, since 1.4.244 released without it

* fix(i18n): import the plugin language-pack artifact from its folder in the shared i18n instance

* fix(plugins): drop syntax loads that land after dispose and restore terminal overrides on Switch back

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Roughriver74 <115543510+Roughriver74@users.noreply.github.com>
2026-10-28 20:58:25 -07:00
Neil f12e12f212 fix(test): report a failing test whose stack names a Bun builtin frame (#35384) 2026-10-28 15:15:29 -07:00
OrcaWinandOrcaWin f80a3ccefb fix(i18n): upgrade i18next to 26.5.11 and react-i18next to 17.0.19 (#35335)
Picks up the GHSA-5jr9-983r-c8x4 nesting-regex ReDoS fix (26.4.4), the
placeholder-injection fix for interpolated values (26.5.4), and
react-i18next's useTranslation resubscribe/stale resolvedLanguage fixes.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 14:27:14 -07:00
Brennan Benson 7c57ad6a55 Reuse equations while chat replies stream (#34819)
* perf(chat): typeset each unchanged formula once instead of on every streamed update

A streaming paragraph or list re-parses on each revealed prefix, and every
complete formula in it went through KaTeX again, as did every formula on a
virtual-row remount. Keep a bounded cache of typeset results keyed by what
the typesetter reads (tag, math classes, formula text), hand each tree its
own copy, and remember failures so invalid formulas still show their source.

* test(chat): import the typeset module type at the top of the math test

* fix(chat): evict the oldest cached formula without a type assertion

* fix(chat): type the readable math fallback without an assertion

* Cache immutable math results with bounded retention
2026-10-28 13:27:53 -07:00
OrcaWinandOrcaWin a891e3612c chore: remove remaining Vitest patch (#34878)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 01:10:32 -07:00
OrcaWinandOrcaWin 7cdfebdc1b Revert iteration checks and custom Vitest selection (#34834)
* revert: remove iteration checks and custom Vitest selection

* chore: use upstream affected-test selection

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 00:11:04 -07:00
OrcaWinandOrcaWin 0866cb1b08 fix: bound local iteration checks and show live progress (#34700)
* fix: bound local iteration checks and show live progress

* fix: harden check lifecycles and cached test evidence

* fix: preserve process instrumentation and fixture lifecycles

* fix: give fixture unref spy a portable function type

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-27 23:04:23 -07:00
Kelvin Amoaba 917ef9c737 feat(browser): install Chrome extensions from the Web Store into Orca's browser (#30940) 2026-10-27 22:04:12 +00:00
Takuya AkiyaandNeil 176852ad5a fix(terminal): highlight the active IME conversion range (#27903)
Japanese IMEs convert one clause at a time, but the terminal's preedit overlay
painted the whole composition uniformly, so the clause the candidate window
targets was invisible. Read the IME's selection from xterm's textarea on the
insertCompositionText input event (Chromium only updates it there) and paint
that range of the preedit in the theme's selection colors. Cell-grid advance
corrections are unchanged and nothing reaches the PTY; a collapsed caret or a
range outside the preedit highlights nothing.

Fixes #27873

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 04:51:06 -07:00
OrcaWinandOrcaWin becf899273 fix(test): stop Vitest failing green unit shards with EISDIR on directory stack frames (#34335)
Vitest's reporter checks existsSync(file) and then reads the file to find a
source map. When an error stack names a directory, readFileSync throws EISDIR
as an unhandled error, so the shard fails with every test passing. Read only
regular files.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-27 01:54:51 -07:00
MeapriandNeil 47567c6ac7 fix(terminal): refuse non-finite mouse coordinates so scroll never sends NaN to the PTY (#23330)
xterm's touch inertia gesture events carry no clientX/clientY, so with a
TUI requesting wheel reports a flick sent `ESC[<65;NaN;NaNM` to the PTY
(#20983). NaN slipped past `col < 0 || col >= cols`; the patched range
check in MouseService._triggerMouseEvent is negated so it fails closed.
Regenerate the beta.304 bundle and lock hash.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-26 23:36:01 -07:00
Kelvin Amoaba 577af08b12 fix(browser): scroll off-screen elements into view before agent clicks (#34262)
Bump agent-browser to 0.27.3. Its 0.27.2 release scrolls the target node into
view (nested scroll containers included) before computing the click point and
errors when another element covers that point, instead of reporting a click
that never reached the page.
2026-10-27 05:21:38 +00:00
fcf5e7b418 fix(native): pin node-pty cfset*speed with legacy baud codes for glibc 2.42+ (#33803)
* fix(native): pin node-pty cfset*speed with legacy baud codes for glibc 2.42+

glibc 2.42 versioned cfsetispeed/cfsetospeed (GLIBC_2.42) to take literal
rates and redefined B38400 as 38400U, so node-pty built on 2.42+ fails the
Ubuntu 20.04 floor gate. Pin both to the compat version alongside the
existing openpty/forkpty/pthread_sigmask pins, and pass the legacy CBAUD
code (__B38400 on 2.42+ headers, B38400 otherwise): the compat setters
reject literal rates with EINVAL, which left every PTY at speed 0.

Fixes #19420. Supersedes #19421.

Co-authored-by: MK <mkjung.github@gmail.com>

* fix(native): fail the build if pinned cfset*speed would get a non-legacy baud code

A 2.42+ header set without __B38400 would otherwise silently pass 38400U to the
compat setters and open every terminal at B0.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: MK <mkjung.github@gmail.com>
2026-10-26 00:29:36 -07:00
OrcaWinandOrcaWin 35af10dded build(deps): bump vite 8.3.5, oxlint 1.89.0, oxlint-tsgolint 7.0.2005 (#33794)
oxlint 1.89 adds eslint(no-setter-return), which flagged one test setter.

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 23:19:26 -07:00
Neil 5447a26fd1 perf(lint): keep JS plugins out of default oxlint and adopt ast-grep (#33430)
* perf(lint): keep JS plugins out of default oxlint and adopt ast-grep

Any jsPlugin makes Oxlint hand every file's AST to Node, so the seven custom
plugins cost the default `oxlint` run several times its native time.

- Move app-store-performance, quadratic-buffer-concat, renderer-scrollbar-style
  and ime-enter-guard (scope- or dataflow-aware) to config/oxlint-js-plugins.json,
  run by `pnpm lint:js-plugins`, CI, lint-staged and the changed-lines gate.
- Port sort-comparator-performance, mobile-pairing and native-chat-design-tokens
  to ast-grep rules with rule tests, and delete the plugins.
- Add @ast-grep/cli, config/sgconfig.yml, `pnpm lint:ast-grep` / `test:ast-grep`,
  a CI step, and an ast-grep pass in check-changed-code-quality.
- Add flaky-test rules (wall-clock reads, real-time waits, polling loops,
  unquarantined skips, unexplained timeout bumps), baselined by a count ratchet.

* Enforce flaky-test ratchet on changed files; refresh baseline

* docs(agents): keep custom-rule details out of AGENTS.md

* Resolve ast-grep from the repo so gate scans of other roots work
2026-10-25 00:23:38 -07:00
Neil e4bbbe045f perf(test): faster local test loop and cold-worktree changed runs (#33421)
- test:changed exits before starting Vitest when Git reports no changed files.
- Unrecorded tests beyond 100 in scope use Vitest's static import graph instead
  of all running, so a fresh worktree's first changed run no longer runs the
  whole suite (patched RecordedModulesResolver; CI does not record, unchanged).
- Bun's slow config load was the SQLite import scan statting every unit test;
  filtered runs now classify only the tests their filters can select, and the
  scan walks directories instead of fs.globSync.
- Silence the native config loader notice on every run.
- Zod's test bundle is content-addressed and reused, so a killed run leaks
  nothing; leaked per-run directories older than 6h are swept.
- build:packages skips compiling when package inputs, compiler, and dist are
  unchanged; test scripts no longer nest pnpm invocations.
2026-10-24 23:15:51 -07:00
Neil f3104c80bf fix(daemon): a Windows terminal's output-relay write error no longer kills the PTY daemon (#33391)
* fix(daemon): contain ConPTY output-relay write errors to the one terminal instead of killing the daemon

* fix(daemon): end a Windows terminal whose output relay dies before connecting

* fix(daemon): kill a Windows terminal whose output relay fails before its first output
2026-10-24 21:45:06 -07:00
Neil 18d25591f9 fix(deps): resolve body-parser and happy-dom security findings (#32983)
* fix(deps): resolve body-parser and happy-dom security findings

* test(mobile): await rich markdown effect cleanup
2026-10-23 17:35:12 -07:00
Kelvin Amoaba 4c6cfda0ac fix(editor): keep paragraphs starting with "Dr.", "A." or "iv." out of ordered lists (#32694) 2026-10-23 22:19:53 +00:00