Commit Graph
337 Commits
Author SHA1 Message Date
Kelvin Amoaba ad2cce3af3 feat(mobile): show a voice meter and timer while dictating, with start and stop haptics (#35500)
* feat(mobile): show a voice meter and timer while dictating, with start and stop haptics

The phone meters the PCM it already streams with desktop's analyzer,
now shared from src/shared, so both shells get a level without a native
event crossing the page bridge. Recording start and stop tap the phone,
iOS now allows haptics during input, and "Dictation inserted" only
shows for live terminal input, where the text can be hidden.

* refactor(mobile): keep the dictation hooks under the line cap after #35502

Moves the start tap and stop tick into useMobileDictationHaptics and shares one current-finish guard in finishDictation.
2026-10-29 04:13:09 +00: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 dc4a990c4a test(e2e): background work never starts a stopped WSL host distro (E9 guard) (#35580)
* test(e2e): background work never starts a stopped WSL host distro (E9 guard)

A desktop on the fake WSL transport keeps a WSL-host project and a local one. With the distro
stopped, a window of background activity elsewhere (window focus/blur/reveal, workspace switches,
linked issue/PR cards, a streaming chat in the local workspace, Settings panes and Tasks, account
switches) must start nothing; opening a terminal in the WSL workspace then starts it exactly once.
createGitProject returns the real path so the helper also works where tmpdir is a symlink.

* test(e2e): actually render each Settings pane in the E9 background window

openSettingsTarget alone only set the navigation target, so no pane mounted and none of its reads
ran. Open Settings, wait for each pane's section to mount, then close it. A repo pane that boots
the distro on mount now fails the guard.

* test(e2e): fail the host-mode E9 guard early on a missing repo or connect log; state its scope
2026-10-28 19:24:16 -07:00
NeilandClaude c14c99bb49 feat(wsl-host): a paired phone reaches the desktop's WSL host and never starts its distro (E6) (#33911)
* feat(wsl-host): a paired phone reaches the desktop's WSL host and never starts its distro (E6)

Behind ORCA_INTERNAL_WSL_HOSTS and the Experimental switch. The desktop relay
lists its WSL hosts to phones. Every relayed call (resolve, call, watchChanges,
status.get) passes a never-start check, and a WSL host serves a phone only
while it already runs.

- WslHostPhoneGate holds the phone's view. The call check reuses E9's
  focusReachability, so with no bridge in this process it refuses without a
  running-list read. A host shows as "stopped" only on a confirmed stop
  (liveness's distro-stopped, or a running list read by the check); anything
  else gets a neutral "isn't available" message. Hosts are hidden from phones
  while the Experimental switch is off or a Remove is pending.
- The relay host list sends optional `stoppedOnDesktop` only for a confirmed
  stop. The phone badges those rows "Start on computer"; older peers ignore
  the field.
- The paired-server rows producer carries WSL hosts like any server, so the
  main-computed sidebar feed a phone reads has their rows.
- The paired-phone e2e drives a WSL host project end to end and asserts a
  stopped distro is never started.

* fix(wsl-host): keep the phone's no-start check off non-WSL servers, and a stale stop off a ready host

- status.get runs the no-start check only for a WSL host, through a
  check-only helper. SSH-managed and paired servers reach the status owner
  exactly as on main: no reconnect, no redeploy, and the manual-disconnect,
  coalescing and repair handling stay in place. The preparation option is
  stripped before the probe.
- WslHostPhoneGate drops a 'stopped' read when a ready status arrived while it
  ran (a per-host ready generation), and clears a recorded stop when a check
  finds the host answering.
- A phone asking about a host that is switched off or being removed gets copy
  that says so, not "open one of its projects".

* test(wsl-host): the real no-start helper leaves SSH-managed and paired servers untouched

* fix(wsl-host): tie a phone's confirmed WSL stop to that host's status, and label a host not connected

- A stop a phone's check confirmed is stored with that host's own status
  object and holds only while that status is current. Another host's change no
  longer clears it. Any status of its own (ready, a disable's closed, a failed
  Remove's closed) does. A read never records a stop over a ready status, so a
  read shared with one begun before ready can't. Removed hosts drop their
  entry. The generation map and status listener are gone.
- The relay sends optional `unavailableOnDesktop` for a WSL host this desktop
  isn't connected to and doesn't know is stopped. New phones badge it "Open on
  computer"; older ones still show Connecting.
- The relay list reads the WSL host records once per poll (reachAll).

* refactor(wsl-host): one records read per phone check; note the gate's status identity compare

* test(wsl-host): read the phone's host flags as booleans in the paired-phone e2e

toMatchObject({ stoppedOnDesktop: undefined }) requires the key to be
present, and the desktop omits an unset flag, so the ready poll could never
match. The poll now compares relay plus both flags read as booleans.

* test(wsl-host): expect the neutral refusal once main's fake drops a stopped distro's bridge as closed

Main's fake connector (#34573) now publishes 'closed' when a stopped distro's
bridge drops, not liveness's 'distro-stopped'. So the phone's check reads the
stop under a ready status and doesn't claim it. The phone gets the neutral
copy and unavailableOnDesktop; the confirmed-stop copy stays unit-tested.

* test(wsl-host): the paired-phone e2e expects the confirmed stop again, and checks nothing booted the distro

Main's fake now publishes 'distro-stopped' when it drops a stopped distro's
bridge (#34624), as liveness does, so the phone gets "isn't running" and
stoppedOnDesktop. The spec also checks distros.json still reads 'stopped' at
the end: the fake boots a distro by rewriting it.

* test(wsl-host): the startup reconcile suite stubs the phone gate built beside the service

The reconcile still runs before the connector exists; the suite only lacked the new
setWslHostPhoneGate export on its service-instance mock.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-28 18:23:02 -07:00
Neil b47f12abd5 test(e2e): keys typed into a new remote terminal before its first prompt reach the shell (#35312)
* test(e2e): keys typed into a new remote terminal before its first prompt reach the shell

Add a headless paired-host spec that types into each new remote terminal after
it binds while the host shell's rc file is still running, then requires the
whole command to run. Drop the managed-SSH helper's wait for a prompt before
typing, so those specs assert the same guarantee.

* test(e2e): prove the rc gate held, and that the early-typed command ran exactly once

The tty echo of the typed command must precede the rc file's own prompt, the
command's output must appear exactly once, and the rc wait outlasts the bind
wait. Throw if the host has no isolated HOME.

* test(e2e): drop the early-typed echo-position check; keep the gate and exactly-once proofs

The check that the typed line's tty echo precedes the rc file's prompt failed on
Linux CI 2 of 2 (run 40763469331): the pane began with the prompt and the typed
line followed it, while the command still ran exactly once. macOS echoes it during
the rc. Where the echo lands depends on when the host's line discipline sees the
bytes, which this test cannot pin; it is not evidence of loss. What the test keeps
proving: the keys were typed while the rc file still waited (release file absent),
the rc ran (its own PROMPT appears), and the command ran whole, exactly once.

* test(e2e): name the early-typing test for what it proves

Rename it to say every key typed before the first prompt is delivered exactly
once, and drop the header's claim about how the tty keeps type-ahead.
2026-10-28 14:43:36 -07:00
Neil f7a114af29 fix(wsl-host): a terminal on a stopped distro offers Start, and New Terminal starts it (#35168)
* fix(wsl-host): a terminal on a stopped distro offers Start, and New Terminal starts it

* fix(wsl-host): a Start whose reconnect fails toasts and returns false

* fix(wsl-host): hide a pane's error toast only where the Start card replaces it

* test(wsl-host): give the terminal Start e2e's timeout and no-boot window their reasons

* test(wsl-host): await the pre-stop New Terminal so its user-act boot can't land in the no-boot window

CI saw 2 extra fake connects right after the first stop. Instrumented runs traced
every boot after a stop to the Start flow's two calls (wslHosts:connect, then the
runtimeEnvironments connect, logged as a pair) and the renderer stack of the
second pair to openNewTerminalTabInActiveWorkspace. In CI the spec clicked New
Terminal, then waited only for the first .xterm. That was the auto-created
terminal, so it wrote the stop while its own New Terminal was still in flight.
That terminal then started the distro as the user act it is. No background path
booted anything.

The spec now runs the tab bar's New Terminal action and awaits it before the
stop. The no-boot assertion is unchanged.

* test(wsl-host): narrow the tab group id before opening the pre-stop terminal
2026-10-28 14:09:27 -07:00
Neil 448ef93938 test(e2e): retry the PTY write spy's main-process evaluates on transient CDP failures (#34912)
The spy already retried its install through retryTransientMainEvaluate, but its
reads, clear and delay setter did not, so a 'Resulting promise was garbage
collected' (playwright#33737) failed a passing Windows WSL paste test.
2026-10-28 03:06:24 -07:00
NeilandClaude 1eaaf85e9f Land the WSL project mover dark (merges only after verify-15b, the flag-off audit and green CI) (#34059)
* test(orcad-migration): pin the SSH manifest through the real dormant collectors

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

* refactor(orcad-migration): read the dormant-state export through a source-kind scope

A local-WSL scope lives on host local and owns only its projects, so host-wide
matches stay an SSH whole-host rule. SSH bytes are pinned by the collector golden.

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

* feat(wsl-mover): export a local WSL move's signed manifest with its dormant state (D1b-3c)

Collects the scoped projects, their groups and every dormant-state kind through the
local-WSL export scope, rewrites UNC paths to the distro's, and returns blockers for
anything the collectors can't carry instead of dropping it. Automations move disabled,
as the wire requires, and come back as a list to re-enable on the host after commit.

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

* fix(wsl-mover): reset an interrupted move to zero, or finish it once the host proves a commit

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

* fix(wsl-mover): abort a move by its identity when its manifest is too large for a request

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

* fix(wsl-mover): cap a new move's manifest below the request limit, and move forward on a by-reference commit only once the host flushed it

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

* fix(wsl-mover): remove only the newest move of a distro, so its chain stays readable

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

* feat(runtime): clients read a migrated pane's scrollback from its host (D1b-6d-1)

* fix(runtime): hold a remote pane only when its host is known to serve scrollback, and never read bytes in render (D1b-6d-1)

* fix(runtime): route the scrollback read by the tab's own host, never the focused one (D1b-6d-1)

* fix(runtime): resolve the scrollback host through resolveOwner and use the shared RPC error check, after main moved both (D1b-6d-1)

* feat(wsl-mover): a moved project's existing chats stay listed and resumable on this computer (D1b-7a)

* fix(wsl-mover): moved-project chats go through the layout owner instead of writing layout directly (D1b-7a)

* test(wsl-mover): run the moved-project chat runtime test in the Node runtime (D1b-7a)

* test(wsl-mover): read the hidden local row through main's per-host hidden-repo API (D1b-7a)

* feat(wsl-mover): rewrite the desktop's routing and seed the host's session after a move (D1b-7c)

Registers the main-side desktop rewrite stores ui-routing, session-globals and host-session-seed. Adds a +2 main layout-writer baseline line for wsl-move-main-rewrite-stores.ts: these are one-shot, ledgered move rewrites, and no LayoutCommand can set focus or seed a runtime host's partition.

* fix(wsl-mover): finish the main rewrite before the window loads, keep one copy of each moved tab, and copy instead of move (D1b-7c)

* feat(wsl-move): rewrite renderer stores for moved projects and keep their UI through catalog prunes (D1b-7d)

Registers the renderer-side desktop rewrite stores quick-open-history, file-view-preferences and notebook-environments. The renderer copies each moved workspace's entry to its WSL host key, marks it applied in the same storage, and records the ledger entry; main requests a storage flush first and accepts only the main window. Committed moves are re-listed for re-application at every startup. Catalog prunes keep filters, hook trust and setup-script dismissals of moved repos.

* fix(wsl-move): import the migration manifest types from main's orcad-migration folder (D1b-7d)

* fix(wsl-move): import wslUncPathToLinux from shared wsl-paths in the manifest exporter, where 7d moved it (D1b-7d)

* feat(wsl-mover): rewrite a moved project's orchestration history on the desktop (D1b-7e)

Settled local worker dispatches and their worker-terminal rows in a moved
project take the WSL host's worktree id, in one ledgered, idempotent store.
Chat workers, live rows, federated attachments and JSON receipts stay as
written; automations keep their unchanged local copy (the scheduler only
advances it). Per-table dispositions for orchestration.db.

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

* fix(wsl-mover): hold the orchestration store while a worker in scope is live; skip federated rows (D1b-7e review)

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

* feat(wsl-mover): copy in-app browser logins into the WSL host on a move (D1b-7f)

A committed WSL move now copies each in-app browser profile's cookies once into
the WSL host's stable route partition (owner decision 3a), merging without
overwriting what the host jar holds and never touching the source. Where that
is infeasible (no deployment link so no stable partition, the destination is
not a WSL host or is gone, or the partition name is held by another identity)
the host starts fresh and the move journal records why (3b). Registered as the
ledgered browser-cookies rewrite store; it runs only once browser sessions and
an app window exist. WSL_MOVE_DESKTOP_REWRITE_REGISTRY_COMPLETE stays false.

* feat(wsl-mover): retain moved source rows, reconcile them on start, and plan delta moves (D1b-8)

* fix(wsl-mover): keep the retained mark out of the WSL move manifest (D1b-8)

* feat(wsl-mover): the move driver runs a WSL move end to end, with production gate facts and journaled restore folders

One entry point (and preview/start/cancel/listUnfinished IPC for D1b-9) plans the move, gates
terminals on the hook server's agent status, the close-tab process probe, running automation runs
and live dispatches, pauses and proves exit, stages, sends blobs, commits only from
blobs-transferred (#29825), and finishes on the desktop. Any failure resets to zero or goes forward
when the host committed. Restore folders and the automations to turn back on are kept in the
journal; the host turns those automations back on after commit, retried until it confirms.
A real-PTY test pauses daemon shells, and runs on a real distro in the WSL orcad lane.

* feat(wsl-mover): move dialog with preview, blockers, progress and result behind the internal WSL hosts gate (D1b-9)

* fix(wsl-mover): move dialog hides instead of blocking, follows runs after a reload, and lets a start settle an unfinished move (D1b-9 review)

* fix(wsl-mover): word an unfinished earlier move as still finishing (D1b-9)

* test(wsl-mover): type the e2e's fake move state instead of reading it through Reflect (D1b-9)

* WIP(wsl-mover): 7f-2 registry complete, coverage proof, startup re-apply (D1b-7f-2)

* test(wsl-mover): registry-complete lifecycle, stale-focus restart, unload re-apply (D1b-7f-2)

* fix(wsl-mover): share the session focus write so the re-apply adds no layout writer (D1b-7f-2)

* fix(wsl-mover): redo additive rewrites once at a stale window's unload; behavioural coverage proof (D1b-7f-2 review)

* test(wsl-mover): the IPC record test now finishes the rewrite with the registry complete (D1b-7f-2)

* fix(wsl-mover): fence worktree-less spawns by cwd and re-prove terminal exit before commit (R46, WIP)

* test(wsl-mover): pin the commit re-proof, browser jar identity and repeated move generations (R45, R46, R47)

* fix(wsl-mover): fence any spawn by its folder and name the re-proof's reason (R46 review)

* fix(wsl-mover): a terminal in a known folder outside the move never blocks it (R46 follow-up)

* test(wsl-mover): kill the real move driver at every point against a real host store; abort a reset move's leftover stage on reconnect

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

* fix(wsl-mover): carry unsaved drafts into the moved project; round-trip every fixture through the real export, driver and host import

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

* fix(wsl-mover): reset a move waiting on a distro that is proven unregistered; test updates mid-move (R41), lost distros (R38) and two desktops (R39)

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

* test(wsl-mover): list the D1b-10 real-SQLite mover tests

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

* test(wsl-mover): type the fault test's journal hook without a cast

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

* fix(wsl-mover): reset for a lost distro even when its kept host fails to connect, never for a renamed one; compare filled-in repo keys only where the source lacked them

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

* fix(wsl-mover): carry markdown frontmatter visibility and closed-terminal-tab tombstones into the moved project

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

* test(wsl-mover): read the persisted stage through a typed record, not Reflect

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

* feat(telemetry): report how WSL moves end, by phase and reason code (R43)

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

* fix(telemetry): report a pause refusal's reason, an outcome for a thrown run, and only real transport errors as unreachable

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

* fix(agent-hooks): a WSL host's orcad never removes the hook entries the WSL bridge shares (R42)

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

* fix(agent-hooks): a WSL host's Codex account-home launch with hooks off skips too; mark the R42 guard to drop with the bridge

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

* feat(wsl-mover): "Use on this computer again" brings a moved project back as it was before the move

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

* fix(wsl-mover): judge a bridge WSL pane's foreground inside its distro

On Windows every live bridge pane's own process is wsl.exe, so the busy-terminal gate read every
open WSL terminal as running a program and refused the move. Read the pane's terminal foreground
process group inside the distro (processes carry ORCA_PANE_KEY): only shells means idle, anything
else blocks, and an unreadable guest stays unverifiable.

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

* fix(wsl-mover): only the selected projects' session rows can refuse a WSL move

The session eligibility check treated host local's whole partition as the move's, as the SSH
conversion does. So an agent session or unsaved draft in any other project on this desktop, or in a
project an earlier move retained, refused every later WSL move from that distro.

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

* fix(wsl-mover): sign the manifest as the journal and the host read it

The digest was computed over in-memory rows. A value with toJSON (a Date on a row added this
session) canonicalized to {} when signing but to its string once written to the journal or sent to
the host, so the first move after adding a project reset with orcad_migration_manifest_digest_mismatch
until a restart reloaded the row. Normalize through JSON before signing.

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

* fix(wsl-mover): carry a slept pane's scrollback from its daemon checkpoint

The move sleeps every pane first, and a slept pane's history lives only in the daemon's checkpoint,
never in a renderer snapshot. The export carried renderer snapshots only, so moved terminals opened
empty. After Sleep the driver reads each paused pane's checkpoint (as cold restore would replay it)
and the export carries it under the pane's own snapshot ref; the snapshot channel serves those bytes,
checked against the signed hash. SSH conversions pass no reader and are unchanged.

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

* fix(wsl-mover): say why when a move can't be prepared, instead of a bare reset

An export or journal failure after the pause (a digest mismatch, on awin) was rethrown and shown as
"everything was put back" with no reason. Nothing is journaled yet at that point, so refuse with the
error named instead.

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

* fix(wsl-mover): retry a renderer store main refused to record

The renderer applied a store and wrote its marker, but ignored a refused ledger record (gated-off,
failed, not-main-window), so a committed move stayed unfinished and refused every later move of the
distro until the next app start. Log the reason and retry with backoff until it lands.

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

* fix(wsl-mover): a WSL pane is idle only when its shell has nothing else

Review of #32343: reading only the terminal's foreground group let background jobs, nohup and
disowned work, setsid daemons and a script waiting at read pass as idle, and the move's Sleep would
end them. Scan the pane shell's subtree, its session and every process carrying its key; any
non-shell process, or a shell running a script, blocks.

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

* fix(wsl-mover): never leave behind an agent whose key may be the selected project

Review of #32343: scoping the session check to the selected projects let an unqualified key for a
repo id another host also registers fall out of both the transfer and the refusal. Such a key now
refuses. Adds tests that fail on the old code: another project's live agent with its tab open and
its draft don't refuse, the same kinds of state in the selected project still do.

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

* fix(wsl-mover): refuse an unreadable slept checkpoint, prefer it, and spool it to disk

Review of #32343:
- A checkpoint behind an unreadable log (or an unreadable one) refuses with the pane named instead
  of carrying stale history.
- For a pane the move just slept the checkpoint is the newest copy, so it wins over an older
  renderer buffer or snapshot.
- Buffers spool to a per-move folder and only their hashes and sizes stay in memory; the snapshot
  channel reads them by offset. The folder is removed whatever the outcome.
- A journal write that landed before it threw is reset, so no orphan record blocks the next move.

awin's checkpoints were 310-615 KB, well inside the 5 MB per-pane cap, so nothing there is trimmed.

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

* fix(wsl-mover): retry a failed listing, never a record another window was refused

Review of #32343: not-main-window can't clear by waiting, while a listing failure is transient.

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

* fix(wsl-mover): sign the manifest as the host parses it

The likelier cause of awin's digest mismatch: the signature covered the exported objects, but the
manifest parser keeps only the fields it knows, so a session field it drops (one a sleep can
write) left the journal and the host hashing something else. Sign the parsed form, and warn when the
parser dropped anything, since such a field does not move.

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

* chore(wsl-mover): split the journal step out of the driver, and format

The review fixes took wsl-move-driver.ts past the line limit; the export-and-journal step moves to
its own module unchanged.

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

* fix(wsl-mover): a slept pane passes the busy check; say what each move dropped or lacked

Re-run on awin (c5d263c77c):
- Every slept pane read unverifiable: the distro was asked about panes whose wsl.exe had already
  exited, and found no shell. Only a pane the host still sees running is asked now.
- A live pane read unverifiable when the WSL relay above its shell carried the pane key too; the
  pane's shell is now the topmost tagged shell.
- The parser-dropped-fields warning names the paths, and the slept-history reader logs how many
  panes it carried, could not read, or found no checkpoint for, so the next run says why a moved
  terminal opened empty.

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

* fix(wsl-mover): read the history of panes slept before the move too

verify-3 on awin: panes the user slept earlier in the session have no live terminal, so the move
listed 0 of 0 panes and moved them empty. Their layout still names the session whose checkpoint
holds the history; read those too, live panes first.

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

* fix(wsl-mover): moved panes paint their history, slept panes are found without a reload, and an older undo names the newer move

- A moved tab's first mount reads its history from the host even before this window has the host's status; it used to mount empty and never ask again.
- The window reports the panes it slept (main retires their binding on Sleep), checked against the moved workspaces before their history is read.
- A tab's Windows shell choice is dropped on purpose, not by the parser.
- "Use on this computer again" on an older move names the projects of the newer one to bring back first.

* fix(wsl-mover): moved panes read their history, an idle agent passes, refusals name tabs and show

- The manifest carries no desktop PTY binding: the host kept them, so the client took each moved
  pane for a live terminal, skipped the scrollback read and opened a fresh prompt.
- Progress counts the terminal history it sends while staging, not only the blobs.
- "Use on this computer again" shows a refusal in a dialog; the toast sat behind the update notice.
- Unreadable slept history names the tab by its title and is refused in the preview too.
- A bridge pane whose one job is an agent CLI lets the hook status decide: idle passes, working blocks.

* fix(wsl-mover): a split agent tab's shell panes restart as shells, not fresh agents

A moved (or any headless-restored) agent tab with a plain shell split beside the agent
pane brought every split back as a new agent: the start path used the tab-wide
launchAgent for each pane that had no recorded session. Tab-wide launch evidence now
applies only to a tab's sole pane, as the renderer already treats it.

Found in the D1b-10 real-host run on awin (report bug 6).

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

* fix(wsl-mover): restore folders of panes slept before the move; keep browser tabs

D1b-10 real-host run on awin (report bug 7, with bug 8 pinned), and verify-3:

- A pane slept before the move had no live terminal, so its last folder never reached
  the restore hints and it reopened at the project root (verify-3 lost `deep/`). The
  pause step now adds the panes the saved session or the window still binds, through
  the slept-pane listing that already feeds their history, so hints and history read
  the same panes.
- The move pauses projects through Sleep, which closes a workspace's browser tabs, so
  a moved project lost its in-app browser tab. A move's Sleep request now asks the
  renderer to keep them; an older renderer ignores the field.
- A moving project's unsaved draft carries (the host-session seed lays it back); a
  test pins that it never refuses the move.

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

* fix(wsl-mover): sweep journal writes that died before their rename

A killed journal write left <id>.json.<pid>.<ts>.tmp in the move journal forever
(D1b-10 real-host run, report bug 10). Once per directory and process, the journal now
removes another process's temporaries older than a minute; its own writes and a
write another instance may still be finishing are kept.

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

* fix(wsl-mover): move dialog and add-host fixes from the real-host run

D1b-10 real-host run on awin, report bug 9:
- A host that will not connect now refuses as host-unavailable, in the preview and
  at start, instead of "Orca could not check these projects" / an unexpected error.
- A run that ends without committing reports its result on the progress channel, so
  a window that only follows the run (reloaded mid-move, or started elsewhere) leaves
  "Moving… Preparing" for the result instead of sticking there until a reload.
- The preview counts terminal panes, as the terminal blockers do, not tabs.
- Small transfers read in B or KB, not "0.0 MB of 0.0 MB".
- A distro that stopped after the add-host list was read refreshes the list, so the
  button becomes Start and add, with a message that says so.
- The add-host copy no longer says bridge projects keep working "as they are" with
  no mention that they can move to the host.

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

* fix(wsl-mover): a sleeping WSL pane keeps its shell's folder

Bash on the bridge reports no folder, so a slept pane's checkpoint kept
its spawn folder and the move reopened every pane at the project root.
Before the final checkpoint of a Sleep, read the shell's folder inside
the distro (the busy check's probe, now with /proc/<pid>/cwd) and store
it. Internal WSL hosts gate and Windows only.

* fix(wsl-mover): moved browser tabs and drafts survive the moving window

The window open across a move saved its mirror of the host's tabs over
the host partition, and the unload's redo skipped any workspace that
mirror named, so the seed's browser tab and draft never came back. The
redo now adds the carried browser tabs and this desktop's drafts beside
that mirror, never replacing what it holds. A seeded draft is bound to
the host, as the SSH move's draft rename binds it.

* fix(wsl-mover): a pane whose only history is a stored snapshot cold-restores it for every client

A moved (or imported) pane carries its history only as a stored scrollback snapshot,
which no paired client's mirrored layout names, so the host started a fresh shell and
every desktop showed an empty prompt. Before starting such a pane, the host now seeds
a terminal session from the snapshot, so the start cold-restores it like any
interrupted session. Reproduced end to end in wsl-move-host-pane-history.spec.ts.

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

* fix(wsl-mover): seed history only for WSL-moved local panes, once per pane

Review of the first version: it seeded any pending pane with a saved snapshot,
including SSH panes (which would be asked to reattach a session the remote never
had) and ordinary panes. Now only panes of a project a committed WSL move brought
here (its import receipt names it) and never an SSH workspace. The session id is
the pane's own, so a later start reuses the folder the pane has since written,
or its clean exit, and never replays the stale snapshot; meta.json is written
last, by rename. Tests pin the gate, the reuse, and the legacy history restore
the seed relies on; the e2e adds the not-moved control.

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

* fix(wsl-mover): seed only panes the move carried; show history once; redo a cut-short seed

Second review: narrow the seed from per-repo to per-pane (a tab created after the
move's import is the host's own), redo a seed a crash left without meta.json, and
prove on a windowless host (as the distro's orcad is) that the desktop paints the
moved history exactly once - its mirrored layout never names the snapshot, so it
never reads it too - and that the moved browser tab is listed.

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

* fix(wsl-mover): refuse "Use on this computer again" while files are unsaved

An unsaved file's beforeunload silently vetoed main's reload after a release, so the
window kept the moved layout with panes bound to dead terminals (awin verify-5).
The renderer now asks for those files to be saved first; main logs a vetoed reload.

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

* chore(wsl-mover): log where an open pane's history, folder and busy verdict come from

awin verify-5 saw an open pane carry 140 B after 3,000 lines, reopen at its root, and
stay "cannot check" after its job was killed elsewhere. None reproduces off Windows,
since all three run through wsl.exe; these lines name the gap on the next run.

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

* fix(wsl-mover): an open pane keeps its shell folder through a move

Bash on the bridge reports no folder, so an open pane's checkpoint kept only its
spawn folder and `cd deep/er` reopened at the project root (awin verify-5). The
pause now reads each live pane's shell folder inside its distro before Sleep ends
the shell, and the restore hint prefers it.

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

* fix(wsl-mover): carry a slept pane's history once, not twice

Cold restore fills scrollbackAnsi from snapshotAnsi for a normal screen, so both
hold the whole buffer; the move concatenated them, carrying every line twice, and
the per-pane cap then kept only the newest ~1,040 of 2,000 lines (awin verify-6).
The move now carries the normal buffer once, as cold restore replays it.

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

* fix(wsl-mover): ask the distro when Windows can't observe a pane after a relaunch

After an app relaunch, Windows reads a reattached bridge pane's wsl.exe children as
unverifiable, so every live bridge pane stayed "cannot check" and blocked every
move until it was slept (awin verify-6). The gate now asks the distro about panes
Windows cannot observe too; only a pane whose shell the distro also can't find
stays unverifiable. Also formats the earlier commits' tests.

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

* fix(wsl-mover): name a terminal's pane after a relaunch before judging it

A terminal the runtime rediscovers after a relaunch carries no pane key until a
layout names it again, and the gate read every paneless terminal as unverifiable,
so no move could start after a restart (awin verify-6). The gate and the pause now
take the pane main's PTY bookkeeping binds the terminal to.

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

* fix(wsl-mover): read manifest diff paths through a typed record, not Reflect.get

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

* test(wsl-mover): type the slept-pane modes fixture instead of asserting it

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

* fix(wsl-mover): the move gate reads panes a relaunched window has not reopened

After an app relaunch, every live WSL bridge pane read "cannot check" and blocked every
move (awin verify-6, item 5). Root cause: the daemon adapter's hasPty only knows sessions
this app process spawned or reattached. A pane the relaunched window had not reopened is
still running in the daemon and listed by the runtime, but the inspection pre-check read
hasPty=false as terminal_gone, so the probe was unverifiable. Reproduced on macOS with a
two-launch e2e: an idle pane in an unopened workspace inspected as
{verdict:'unverifiable', reason:'terminal_gone'} after the relaunch. The same run showed the
runtime still holds the pane's original paneKey, so the paneKey theory is refuted.

The gate now passes includeUnattached, which skips that pre-check and asks the daemon,
which still refuses a session it no longer runs. Other callers keep the old behaviour.

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

* test(wsl-mover): an idle stub agent passes the move gate once its Stop arrives

awin verify-6 item 5d was inconclusive: p4's stub agent answered, but the preview stayed
"agent-working". Replaying the D1b-10 stub's hook payloads through the real hook server and
the gate's fact collection shows Stop settles the pane to done and the gate reads ready, so
the Stop path is not at fault. The run's screenshot (ev/p4-agent2.png) shows the likely cause:
the harness launched the stub twice in p4, and one instance sat at its prompt having fired
only SessionStart. The stub fires SessionStart at launch, which Orca's Codex normalizer treats
as a turn start, so a stub never given a prompt reads as working. No product change.

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

* test(wsl-mover): an unreachable daemon reads as unverifiable for the move gate

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

* test(wsl-mover): browserless host e2e and stale-window unit test for moved browser tabs and drafts

The distro's server has no browser of its own; an e2e-only switch (never in a packaged app)
starts the headless server the same way. With the desktop's per-host copy as a move leaves it,
the moved browser tab and pre-move draft show as tabs. The unload redo after a move rewritten
under an open window keeps both, whether the host published first or the rewrite seeded first.

* fix(wsl-mover): moved browser tabs and drafts survive the prepared reload after a move

"Reload now" backs up unsaved files by dispatching a synthetic unload first. That first unload
staged the window's mirror of the host (no browser tab, no draft) and drained the redo mark, so
the window, still open, saved its mirror again and the real reload had nothing to redo. The redo
now runs at every unload of the stale window and a last time when its document is replaced
(did-navigate), and only then is the mark drained.

* test(wsl-mover): the undo-path test's store answers the unsaved-file check

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

* fix(wsl-mover): read workspace records through the selectors

Main's new workspace-record read ratchet flags the mover's direct reads; the move dialog, the moved
projects section and its store helpers now read through workspace-record-selectors, and the scope
rows' catalog names its own fields. Prunes the layout-writer baseline to its actual count.

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

* test(wsl-mover): the dialog e2e expects the slept panes the start request now names

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

* fix(wsl-mover): a moved terminal's pane key comes from paneOfPty

Main's L80 census allows only paneOfPty and the switch-guarded writers to read a PTY record's pane
copies; the WSL move's live-terminal list now reads through it.

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

* style: format the imports the main merge repointed

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

* feat(wsl-mover): carry each moved pane's daemon checkpoint as a terminal-history blob the host restores once

Replaces the host-side stored-scrollback seed (#32789) with an installed interrupted session the pane's first start claims.

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

* feat(wsl-mover): the desktop sends each paused pane's checkpoint as its terminal-history blob

Drops the snapshot-ref spooling and the restore-hints blob for moved panes: one source (the pane's daemon checkpoint) carries history and folder.

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

* refactor(wsl-mover): register WSL move wiring from its own module

Keeps register-core-handlers under the max-lines limit.

* fix(wsl-mover): one import from ./ui in the move wiring

* refactor(wsl-mover): build the manifest's session before the payload

* style(wsl-mover): one import from stable-pane-id after the main merge

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

* test(wsl-mover): drop the e2e for the stored-snapshot seed this redesign deletes

The moved pane's history now comes from its own checkpoint; wsl-move-restore-lifecycle.spec.ts
covers it once, across reloads.

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

* chore(wsl-mover): keep #32668's two layout writers in the baseline until 2/3 deletes them

Main's layout-writer ratchet now counts main-process writers; wsl-move-echoed-browser-tabs.ts, the
file the hold-and-reload in 2/3 deletes, writes two. Restore redesign 2/3 removes the entry.

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

* fix(wsl-mover): move the project you have open into a host that has its own focus

The carried session named this desktop's focus, and a host that already focused another project
refused it (orcad_migration_dormant_focus_conflict:activeRepoId), so moving the project you had
open reset with "Nothing was moved" (awin verify-7 N1). The desktop's focus names nothing on the
host and the desktop rewrites its own focus after the move, so the export now carries none and
the host keeps its own. The reset result also says why the move stopped.

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

* fix(wsl-mover): startup removes the slept-history spools of moves the journal dropped

A run killed mid-pause is reset at the next startup without its process, so its export documents
(25 KB per pane on awin verify-7) stayed under wsl-move-slept-scrollback for good.

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

* fix(wsl-mover): a woken WSL pane keeps its folder unless the probe proves it gone

The cold restore checked a WSL pane's folder with stat over \\wsl.localhost, which waits for a
stopped distro and fails for reasons other than absence. After a rollback the pane stayed blank
about 15 s, then opened at the project root without its folder (awin verify-7, 5e). It now uses
the bounded wsl.exe probe every other restore uses, which keeps the folder unless proven missing.

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

* fix(wsl-mover): a moved pane's new shell prompt starts on its own line

The carried screen ends on the old shell's prompt with the cursor after it, so the host's new
shell printed its prompt beside it (`$ $`, awin verify-7). The exported checkpoint now ends the
normal screen with a line break; a TUI's screen and an unfinished escape are left as they are.

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

* test(wsl-mover): wait on conditions, a fixed clock and stated reasons instead of sleeps

Main's flaky-test ratchet flags the mover's tests:
- The browser-draft and pane-history e2e slept fixed seconds. They now wait on what they slept
  for: the host's tabs mirrored, the host terminal's last line, the moved tabs shown, the pane
  painted.
- The journal sweep test read the wall clock; it runs on a fixed fake time.
- Each longer per-test timeout says why it needs the time.

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

* refactor(wsl-mover): start the WSL mover from its own startup module

The mover's startup (rewrite stores, interrupted-move recovery, retention, retry) lived inline in
main-process-ready-foundation.ts. With main, the host reconnect and D1c branches each adding a
few lines on top, the file passed the max-lines limit in the combined build. It moves as one
unit to wsl-move-startup.ts; the foundation keeps the one call and its ordering.

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

* feat(wsl-mover): hold the window's saves from commit until main reloads it (restore redesign 2/3)

The window open across a move hydrated before the desktop rewrite, so every save it made
(session, UI, the unload checkpoint behind Reload now) wrote stale state back, and three files
re-applied the rewrite after it: echoed browser tabs, the seeded view state's redo and the
stale-window reapply. Now main holds that window's saves from the commit request until its
reloaded page has loaded, and reloads it itself once the rewrite is on disk. Use on this computer
again takes the same path, replacing its per-project release hold.

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

* fix(wsl-mover): only a settled commit reloads the window it held

A rewrite pass that ends while a commit request is out (another move's retry) must not reload
the window early: the reloaded page could save its view of projects the host is publishing.

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

* chore(wsl-mover): prune the deleted re-apply writers from the layout baseline

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

* fix(wsl-mover): the move's pause sleeps a workspace again before refusing

A workspace whose pane was still restoring, or had just spawned, could miss its final checkpoint
on the first Sleep and stay awake; the move then refused with "Orca could not pause the terminals"
although a second Sleep stopped it (awin verify-7, 6e). The pause now sleeps the failed workspaces
up to twice more, a second apart, and refuses only when they keep failing.

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

* fix(wsl-mover): a move retries a workspace whose pause failed instead of refusing at once

When one workspace of a move failed to pause, the move was refused right away with
host_terminal_stop_failed, and the retry added for awin verify-7 never ran (awin verify-8, 6e).
The window rolls a failed workspace's panes back and reports that rollback as an error. The
pause's persist step waited on every workspace and failed on that error, so the whole pause
call threw instead of returning the failed workspace. The retry only ran on a returned failure,
and the move's first pause had no retry at all.

The persist step now fails only on errors from workspaces main reported as slept; main already
reports the failed ones. Both of the move's pauses sleep a failed workspace up to twice more
before refusing. Each attempt is a fresh pause with its own id, so a re-sleep is not counted
as a duplicate.

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

* test(wsl-mover): e2e for the restart-style restore and undo (restore redesign 3/3, WIP)

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

* fix(wsl-mover): a moved pane's first subscriber gets its whole replayed history

The host replays a moved pane's checkpoint once, but every remote subscribe snapshot carried at
most 1000 rows, so the desktop's first view of a 2,000-line pane lost its first half. Only for
panes whose session is a moved one, the subscribe snapshot now tries 5000 rows first, then 2000,
then the usual ladder, within the same byte budget.

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

* test(wsl-mover): the restore lifecycle e2e waits for the printer instead of sleeping

Main's flaky-test ratchet flags it: it now waits until the host terminal shows the printer's
last line before the pane sleeps, and each longer timeout says why.

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

* test(wsl-mover): undo after reloads and a restart wakes split panes live, each with its history once

The undo e2e now follows a real move: main's own Sleep pauses a split tab, the committed move runs
its desktop rewrite and retention at startup, the window reloads three times and the app restarts,
then "Use on this computer again". The project comes back slept (no live terminal until opened),
and opening it wakes both panes from their checkpoints, history once, taking input.

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

* test(wsl-mover): carried and retained agents resume once across move, reloads and undo

The move e2e now carries the pane's idle Codex session to the host (its session file where the
distro keeps it): the agent resumes in the moved pane's folder on the first open and no reload
resumes it again. The undo e2e gives pane A an idle agent: after the undo it resumes there, once,
and pane B starts none.

Verify-6 item 5d (an idle agent read as working): the awin stub posts Codex's SessionStart at
launch, which Orca reads as a turn start; Codex posts it with the first prompt. The gate test now
pins that a resumed agent passes until its next prompt starts a turn.

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

* test(wsl-mover): read the undo e2e's agent record without a cast

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

* test(wsl-mover): parse the undo e2e's agent records instead of reflecting into them

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

* test(wsl-mover): the lifecycle e2e shows the moved pane when its agent does not start

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

* test(wsl-mover): start the carried agent by its recorded absolute command

A login shell may reset PATH (Ubuntu CI did), so the stub is named by the launch config the move
carries, as a recorded agent command is.

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

* test(wsl-mover): say where a resumed moved pane started when it is not its folder

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

* test(wsl-mover): print login profiles that move a shell when the resumed pane is outside its folder

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

* test(wsl-mover): keep the CI runner's login-profile cd out of the moved pane's folder check

Blacksmith's /etc/profile.d/blacksmith.sh ends with cd "$GITHUB_WORKSPACE", so every login
shell on CI, restored or new, plain or agent, starts there. Reproduced in Ubuntu 24.04 with the
same profile line: a plain restored pane lands in GITHUB_WORKSPACE too, and with it unset the
moved pane and its resumed agent start in the pane's folder. Orca spawns the shell in the right
folder; the runner's profile moves it.

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

* test(wsl-mover): judge the resumed pane's folder by where its shell started

Blacksmith's /etc/profile.d/blacksmith.sh sources the job env and cds into GITHUB_WORKSPACE, so on
CI every login shell, plain or agent, restored or new, moves there after Orca starts it in the
pane's folder (reproduced in Ubuntu 24.04 with that profile line; a plain restored pane moves too).
The check now accepts the pane's folder as PWD or as the OLDPWD a profile cd left behind.

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

* fix(wsl-mover): undo stops the host's copy of the project before its agents resume here

"Use on this computer again" resumed the project's agents here while the host's copies kept
running, so one agent session ran on both sides and two writers shared its transcript (awin
verify-7, 6c). Undo now sleeps the project's workspaces on the host and waits until none of their
terminals runs. A host that does not answer, or whose terminals do not exit, keeps the move and
says so; a host the user removed runs nothing, so the project comes back.

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

* test(wsl-mover): e2e: undo stops the host's running copy before the project comes back

A paired host holds the moved project under the move's ids with its terminal still running; after
"Use on this computer again" none of the host's terminals for it runs. Fails with the host stop
removed (the host's terminal stays at 1).

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

* fix(wsl-mover): "Use on this computer again" refuses only for the move's own unsaved files

An unsaved file in any project refused bringing a moved project back, so p6's draft README
blocked undoing p9 (awin verify-8). The window reload no longer stops on an unsaved file, and
only the move's own workspaces come back from their pre-move state. The window now tells main
which workspaces have unsaved files; main refuses only if the move holds one of them, before it
stops anything on the host, and the window names just those files.

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

* fix(wsl-mover): a refused "Use on this computer again" leaves the host's terminals running

Undo stopped the host's copies of the move's terminals before it checked whether the move could
be released, so an undo refused because a later move builds on it still killed the host's shells
and agents; the agents then resumed again (awin verify-9, X1). Undo now runs every refusal first:
unsaved files, a running move, a newer move in the chain, and whether the host answers for every
workspace. Only then does it stop anything, and a host that cannot answer for one workspace has
none of them stopped.

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

* fix(wsl-mover): the move gate finds a pane the window has not reopened since a relaunch

A pane the window has not shown since an app relaunch has no spawn-time pane key in main, so
the move gate's guest busy check could not find its shell. The gate now falls back to main's
own pane record. The Sleep half of the relaunch gap (awin verify-9, X2) moved to main with
#33398 and #33425.

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

* chore(wsl-mover): log who spawns each WSL pane and whether its cold restore came with it

After an undo reload a restored, inactive terminal tab showed only a prompt: something spawned
its session 2 s after the reload, before the window showed it, and its checkpoint never replayed
(awin verify-10). No log named the spawner. Each WSL pane spawn or reattach now logs its
session, spawn intent, whether it was new, whether a cold restore came with it, and the four
caller frames above the daemon client, for verify-11 to capture.

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

* test(wsl-mover): the undo host-copy e2e says why it needs its longer timeout

Main's flaky-test ratchet flags an unexplained per-test timeout.

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

* chore(wsl-mover): record the WSL spawn caller where it enters the daemon router

verify-11's `[wsl-spawn-origin]` lines named only the daemon client's own frames
(createOrAttachSpawn ← doSpawn ← withDaemonRetry): the async spawn path drops the caller.
The caller's frames are now captured synchronously at the router's `spawn` entry and logged
with the spawn, so verify-12 can name what starts a restored pane before its window shows it.

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

* chore(wsl-mover): capture the WSL spawn caller where every daemon spawn enters

verify-12 still logged "caller":[]. The router only wraps the daemon client when
legacy daemons exist; otherwise the bare client is the installed provider, so the
router capture never ran. Capture at the client's spawn entry (keyed by the
minted id too) and raise the stack limit so the async caller frames survive.

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

* chore(wsl-mover): log the seed and the daemon's screen for WSL cold restores

verify-13's second-cycle `$ $` checkpoint shows the daemon's own screen
without the fresh-shell blanking (old prompt still in the viewport,
scrollbackLines 0), which the macOS daemon harness never reproduces.
Log the seed's shape and tail with the spawn, and the daemon's screen
3 s after a WSL cold restore, so the next run shows where it is lost.

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

* fix(wsl-mover): keep an open host pane from restarting its agent during undo

"Use on this computer again" slept the host workspace and waited for its
terminals to go. A host pane open in the window saw its terminal exit and
resumed the agent on the host 1.6 s later, after the stop had already
counted zero, so the reloaded window resumed a second copy here (awin
verify-14, 6c). Refuse this window's terminal and agent starts on that host
from before the stop until the reloaded page has loaded (capped at 2 min),
and lift the fence when undo is refused.

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

* chore(wsl-mover): pre-landing cleanup for the dark landing

- Strip the verify-only spawn-origin and cold-restore-screen logging and the
  per-pane process warning.
- Read a pane's stored scrollback from its host only for WSL hosts; SSH and
  paired-server tabs no longer hold for a placeholder or ask their host.
- Limit the ambiguous-owner refusal to WSL moves; SSH conversions match as
  before.
- Drop the duplicate split-tab launch check (main fixed it in the projection,
  #32418).
- Reword comments that cited internal verify runs.

* fix(terminal): save a woken terminal's first history as a full snapshot

A wake that reused the Sleep-time cold-restore cache reopened the history
writer without asking for a full checkpoint, so the new process's output was
appended to the pre-wake checkpoint, which lacks the fresh-shell padding the
seed gave the daemon. The next save that replayed checkpoint + log (quit's
final save, or a relaunch's adoption) printed the new shell's prompt on the
restored one's row (`$ $`). Mark a full checkpoint for a new process in that
path, as the uncached cold-restore path already does.

* fix(terminal): keep ConPTY's viewport top through a width change, checked against a captured transcript

A captured ConPTY 1.23 resize (wsl.exe) shows it keeps the line at the top
of its viewport through every resize and repaints only that viewport. xterm
keeps the cursor's rows instead: a wider grid that rejoins wrapped rows pulls
rows out of scrollback (ConPTY's repaint then overwrites them), and a
narrower one that rewraps pushes rows into it (the repaint then repeats
them). The windowsPty row rule covered neither. ConPTY-backed emulators now
put the top line back on its row after a resize.

* feat(wsl-mover): D3a move orchestrator skeleton (dark) (#33802)

* feat(wsl-mover): D3a move orchestrator skeleton, dark behind the internal WSL hosts gate

* fix(wsl-mover): exhaustive orchestrator telemetry transition

* feat(wsl-mover): D3a T2 trigger, confirmed-running host readiness and T3 guard

* fix(wsl-mover): D3a review fixes (stuck moving, user release parks, A1 timer, deferred backoff, chain restore, GUID-only R38, uncounted window failures, backoff docs, restore settle, coalesced wakes)

* fix(wsl-mover): restore chain order without mutating

* fix(wsl-mover): D3a never touches a WSL host whose Remove is pending (E11 pendingStop)

* fix(wsl-mover): D3a survives an unreadable journal, honours a release mid-step, replays wakes after Restore, re-checks pendingStop before readying a host

* refactor(wsl-mover): orchestrator ports in their own module

* test(wsl-mover): warm the orchestrator wiring import before its first test

* fix(wsl-mover): a user Remove of a WSL host declines its move until the user adds it or retries

* test(wsl-mover): one checked cast in the host user-action test

* fix(wsl-mover): D3a review 3 — removal-pending clears, start re-checks after the window answers, non-blocking replies, replay in finally; split orchestrator modules under max-lines

* test(wsl-mover): index check style

* test(wsl-mover): core handler test mocks the move orchestrator wiring

* fix(wsl-mover): every user Remove of a WSL host declines its move; readying a host re-checks the decline before connect or add

* fix(wsl-mover): a failed sidecar save or step error never rejects a fire-and-forget trigger

* test(wsl-mover): split orchestrator tests under max-lines with shared fake ports

* fix(wsl-mover): report a host Remove before its cleanup, so a failed cleanup keeps the decline

* fix(wsl-mover): keep the moved-pane history wire surface off outside a WSL host

Register session.readTerminalScrollback, advertise its capability and the
terminal-history migration blob, and install that blob only on a WSL move's
destination (the WSL host's orcad, or an e2e host standing in for one).
Restoring a pane only probes for a moved pane's history there too. The
journal's orphaned-write sweep is limited to the WSL move journal, so SSH
migration journals behave as on main. Pin the advertised blob kinds again.

* fix(wsl-mover): user moves connect the host, automatic ones never boot; gate D3a's selector; tolerate a missing environment list

- The dialog's preview and start reach the host through service.connect (may boot, lifts a park);
  the orchestrator and its startup recovery keep the no-boot path (A1, E9). A host with a Remove
  pending is never connected either way.
- WslMoveDialogHost's active-distro selector does no worktree lookup with the flag off.
- hostScrollbackEnvironmentId returns null before the environment list loads; the mount test
  fixture supplies a WSL host.

* fix(wsl-mover): D3a waits out a move still being retained; orchestrator-over-driver tests (#34087)

* fix(wsl-mover): D3a waits out a move still being retained instead of failing attempts; orchestrator-over-driver integration tests

* fix(wsl-mover): D3a counts an unverifiable in-flight refusal, caps uncounted waits at a day (parks as stuck) and re-checks a done distro waiting on retention

* fix(wsl-mover): cap D3a's uncounted waits by checks, not wall clock; any in-flight detail counts

* fix(wsl-mover): a user's move leaves an already-ready host connected as is

service.connect bumps the liveness generation and re-ensures the server; the orchestrator readies
the host before the driver runs, so the driver connects only a host that is not ready.

* Revert "fix(terminal): keep ConPTY's viewport top through a width change, checked against a captured transcript"

This reverts commit ecd8f07396.

* test(wsl-mover): the orchestrator's automatic path never reconnects a serving host, and adds a bridge host once (E9)

* fix(wsl-mover): keep session.readTerminalScrollback in the RPC manifest; gate its registration

Gating the method out of ALL_RPC_METHODS dropped it from the generated params catalog. It is
static again, so the catalog types it, and the dispatcher leaves it unregistered on any host that
does not serve moved panes' history (method-not-found there, as before).

* fix(wsl-mover): undo returns focus from the host copy, so the reload does not reopen it

verify-15b 6c: undo stopped the host's copy of p9, but the window's focus still named the host
copy (the move pointed it there), so the reloaded window opened that workspace, the host
re-created its pane as a visible user-resume, and, since #34054 resumes a restored host pane's
agent from the hook store, Codex resumed on the host beside the one restored here. With writes
held and before the reload, undo now points focus at the project's copy here, reusing the move's
focus writer with the inverse patch.

* fix(wsl-mover): undo turns the host's copies of the move's automations off before releasing

After "Use on this computer again" the desktop's retained copy of a moved automation runs again,
while the host's copy stayed on: both ran against the same files. Before each move is released,
undo now turns off, on the host, that move's automations whose copy here is on (one off here keeps
the user's choice there). A host that cannot be reached refuses as host-unreachable and one that
refuses as failed, releasing nothing, like the terminal stop.

* fix(wsl-mover): undo turns host automations off before stopping terminals, and back on if it refuses

d3-gate-audit: with the automation step after the terminal stop, a refusal there left the host's
terminals and agents killed while undo refused, breaking X1. Per move, undo now turns the
automations off first (a step that undoes cleanly), then stops the terminals, then releases. A
terminal stop that fails turns back on what was turned off, and a disable that fails partway
turns back on what it had turned off before it throws.

* fix(wsl-mover): roll back undo's host automation step on any later refusal; review lows

- Any refusal after the automations went off (terminal stop, release failure or supersede) turns
  back on exactly what this attempt turned off. One it cannot is recorded on the move
  (hostAutomationsLeftOff), shown in the unfinished-move notice and retried by the rewrite retry.
- The spawn fence is released in a finally, and the port answers failed instead of throwing on an
  unreadable environment store.
- A host with a pending Remove (E11) is left alone with its own refusal copy; the
  host-automations refusal tells the user to connect the host.

* fix(wsl-mover): undo carries the host's automation states back and leaves each running in one place

Owner decision on d3a's item 5: undo reads every one of the move's automations on the host, turns
off each host copy that is on, and, with the release, gives the copy here the host's state (the
user's latest choice; one deleted there reads as off). An automation turned off on the host after
the move no longer comes back on here, and one turned on there no longer keeps running against a
project the host no longer owns. A refused undo restores the host's flags and changes nothing
here.

* fix(wsl-mover): the left-off automation retry re-reads the move after the host round trip

d3-gate-audit: the retry wrote back the snapshot it read before awaiting the host, so an undo that
released the move meanwhile could be undone, or a phase advance rolled back. It now re-reads the
record after the await, writes nothing for a move that is gone, and removes only the ids it
turned on, keeping everything else current.

* fix(wsl-mover): undo records the user's automation choice before turning any off, and takes turns with the retry

d3a re-review and d3-gate-audit:
- Undo reads every moved automation on the host, records those states on the move before turning
  anything off, and applies them here as part of the release step (undone if the release then
  refuses). An earlier attempt's record wins, so one left off by a failed rollback, or by a crash,
  keeps its choice (on) here instead of reading as off.
- A disable the host drops mid-loop carries what it could not turn back on on its error; those
  are recorded as left off, shown in the unfinished-move notice and retried.
- Undo's per-move steps and the left-off retry take turns, and the retry re-reads the move inside
  its turn, so a release during a retry neither resurrects the move nor leaves the host copy on.
- At startup, an undo a crash interrupted sends what it read as on back on through the retry.

* fix(wsl-mover): a left-off retry that finds the move released turns its host copies back off

If a move is released while the retry is turning a left-off automation back on, the host copy
would run for a project that is local again. After the round trip the retry re-reads the move;
when it is gone it turns what it just turned on back off (best effort, logged).

* fix(wsl-mover): a restart keeps a left-off automation's choice; undo's rollback never skips the host

d3a re-review:
- Startup settling keeps the recorded choice (on) for what it sends to the retry, and undo also
  reads any id on the left-off list as on, so a later undo after a restart and failed retries keeps
  it on here.
- A failed revert of the copies here no longer skips turning the host's copies back on; each
  revert write is tried on its own.
- Turning automations back on stops at a host that stops answering, leaving the rest as left off,
  instead of waiting on each in turn while undo's turn is held.

* fix(wsl-mover): undo always asks the host for the move's terminals, refusing a host that does not answer

verify-15c: with the host's orcad stopped, "Use on this computer again" for p7 proceeded, and after
a normal undo p6's host shell kept running. Undo's host-copy stop took the host workspaces to stop
from this computer's session, which after a move keeps only a moved project's chats: a project
with none named no workspace, so undo asked the host nothing (no reachability check, nothing
stopped) and released it. The stop now always lists the host's terminals first, adds every
workspace of the move's scope that runs one, and refuses as host-unreachable when the host does
not answer, answers with an error, or cuts the list short.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-28 00:02:37 -07:00
Brennan Benson 8d908764dc Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)" (#34662)
* Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)"

This reverts commit 29a5f89b8a.

On a machine with several clones of one repo, #32272 moved the oldest clone
into the shared cross-host project. Shared Windows QA machines have several
orca clones, so everyone's sidebar "orca" project suddenly gained that
machine's main checkout and its worktrees. Revert until the intended
grouping is decided.

Conflicts resolved against later main: Settings files moved into
settings/repository (imports adjusted), and two test files later deleted on
main stay deleted.

* Use checked types in restored test fixtures

* Combine restored fixture type imports
2026-10-27 20:38:35 -07:00
Younghwan ChaeandNeil 46c0a1f453 test(e2e): skip contributor git hooks and signing when seeding fixture repos (#31591)
Global setup and the worker-scoped fallback now share one seedTestRepository()
that runs its commit and worktree add with per-command
`-c core.hooksPath=/dev/null -c commit.gpgsign=false`, so a contributor's
global hooks or always-on signing no longer abort e2e before any spec runs,
and the test repo's own hook config stays untouched for specs that install hooks.

Fixes #31587

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 10:10:18 -07:00
Seiji KoharaandNeil 2327180d12 feat(quit): add Quit and Stop All Sessions to the app menu and tray (#32153)
* feat(quit): add Quit and Stop All Sessions to the app menu and tray

Quit keeps daemon sessions alive for warm reattach. The new item, placed
directly above Quit in the macOS app menu and menu bar icon, the Windows
File menu and tray, and the Linux File menu, asks for confirmation, runs
Settings' Kill all sessions in the renderer, and quits only when every
session stopped. Closing the tabs first means nothing reopens or resumes
on the next launch. Serve hosts do not offer the item.

* fix(quit): keep Orca open when Kill all cannot prove every session stopped

- Kill all no longer reads a daemon generation that failed to list as
  "no sessions": a failed poll keeps the last confirmed remaining set,
  and a failed initial listing sets listingIncomplete.
- Quit and Stop All Sessions stays open on an incomplete listing or a
  terminal tab opened during the sweep, logs why it stayed open, and
  reveals the window before the final quit.
- Share the main-window message box helper, stub the request on the web
  client, and add Portuguese strings.
- Cover the windowless path, request expiry, the in-flight guard, and
  failed listings in tests; bound the E2E exit wait.

* fix(terminal): count Kill all per daemon generation within a time window

- A poll updates only the daemon generations that answered, so one
  failing generation no longer hides the kills of the others.
- Poll for 6.5 s of wall-clock time instead of 65 attempts, because a
  wedged generation fails each listing only at the request timeout.
- Flag a sweep with no daemon provider as listingIncomplete.
- Log the leftover-tab exit, share the one-shot window intent with
  Open Settings, and cover the sender check, the reveal before the final
  quit, and retries in tests.

* fix(quit): stay open while a newer build's daemon is held

- Set listingIncomplete while this build holds a newer protocol's
  daemon, whose sessions it can neither list nor stop.
- Reuse getDaemonAdapters in Kill all, type the shared one-shot helper
  by its flag factory, and move the app-menu quit handlers next to the
  tray options so both serve/desktop splits are tested.
- Cover a quit that outlasts the request expiry and a failed request
  pull, and fix the docs lead-in for the new quit-time case.

* fix(quit): explain, without stopping anything, while a newer version runs terminals

- Check the newer-version daemon hold before the confirmation and show
  an explanation instead, so Kill all no longer closes every tab and
  then refuses. listingIncomplete stays as the backstop.
- Translate the explanation, describe listingIncomplete generically,
  and assert the single timed-out poll in the Kill all test.

* fix(quit): explain before asking whenever Kill all cannot list every session

- Show the explanation instead of the confirmation while the daemon
  provider is missing (still starting or failed) as well as while a
  newer version's daemon is held.
- Name only the steps that work (wait for terminals to open, reopen a
  terminal opened in a newer version), avoid stating a hold as fact, and
  label the dialog's button through the catalog.

* fix(quit): add the relaunch step to the explanation and align the ja wording

* test(pty): keep a confirmed stop when its generation fails a later poll

* test(quit): give the E2E timeout bump its reason

* fix(quit): stay open when the daemon still lists a session after Kill all

Kill all counts only the sessions of its first listing, so a session
started since then, for example from the CLI or an automation before its
tab reaches the renderer, would outlive the quit. Main now lists every
daemon generation again before quitting, and quits only after a listing
in which every generation answers and none lists a session. It polls
within Kill all's 6.5 s window so the tab sweep's PTY shutdowns can
finish; otherwise the renderer shows the existing error and Orca stays
open.

ui:quitAfterStoppingAllSessions becomes an invoke that answers whether
main quits.

* feat(quit): rename to Quit Completely with a macOS hover hint, and explain plain Quit in the confirmation

* fix(quit): show progress while Quit Completely runs, and refuse at once when the daemon can't be fully listed

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-27 03:52:06 -07:00
hwantageandNeil 483573f0b9 fix(browser): focus newly opened image tabs (#34127)
* fix(browser): focus newly opened image tabs

Focus the window before the webview so Command+W closes the new
image tab instead of the original tab after a plain image click.

Add tests for focus order, image loading, and close-shortcut forwarding.

Fixes #34114

* test(browser): verify image tab focus with native keyboard input

* refactor(ci): keep browser E2E routes within registry size limit

* fix(ci): enable background launch for native browser regression

* test(ci): include browser routes in isolated detector fixture

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-26 22:52:40 -07:00
Brennan Benson 29a5f89b8a Keep same-machine folders with matching Git remotes in separate projects (#32272)
* WIP: same-host same-remote checkouts keep their own project id

* WIP: claim the shared project id per checkout surface; drop the display-only clone split

* WIP: index derived ids by repo in the hydration normalizer

* Resolve workspace project affiliation from current checkout owners

* fix: preserve concrete project ownership across catalogs and operations

* fix: combine recent project selector imports

* fix: enforce canonical catalog reads and preserve search continuity

* fix: scope catalog lint exceptions to renderer files

* Resolve project search ownership before native candidate limits

* Combine duplicate session scope imports

* fix: derive returned history row ownership on the host

* Reconcile full session history replies by content

* Require authoritative project catalogs for terminal snapshots

* Preserve registered project claims and partial history results

* test: check orphaned setup cleanup state without assertions

* fix: align catalog callers and native search fixture contracts

* test: check full repo shapes in runtime routing fixtures

* Fix checked catalog inputs in compiler regression fixtures

* Keep checked PTY fixture repos mutable

* test: align catalog fixtures with canonical readers

* test: check catalog and runtime fixture inputs

* test: check catalog fixtures without outer assertions

* test: follow persistence harness relocation

* Adapt catalog test inputs to integrated main checks

* Test History hashing across cached depth revalidation

* test(settings): expect host-qualified setup removal

* test: capture host split listings and scope relay recovery

* ci: regenerate merge ref

* test: type host-split listing diagnostics
2026-10-26 19:07:06 -07:00
Neil 7a9abe48f3 refactor: group remote runtime client modules (#34133) 2026-10-26 17:47:25 -07:00
ad2156c358 test: remove 140 low-value cases and 49 suites (#34062)
* test: remove 140 low-value cases and redundant browser probes

* fix: remove unused upstream chat menu import

* test: pin the host-split layout fixture branch

---------

Co-authored-by: Orca Test Audit <test-audit@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-26 15:16:27 -07:00
NeilandBen Grynhaus d1f04e6042 fix(terminal): a pane moved to a new tab stays moved on paired servers (#33027)
* test(e2e): a pane moved to a new tab on a paired server workspace stays moved

* fix(terminal): keep a pane moved to a new tab on paired runtimes

Moving a split pane to a new tab in a paired-runtime worktree changed only
the window store. The host never learned about the move, so the next host
snapshot rebuilt the old tab and the pane snapped back.

Add a session.tabs.movePaneToNewTab RPC that commits the move in the host
session through planTerminalLeafMove and republishes the session tabs.
For a mirrored host tab, the renderer applies the move first under the
mirrored id the host snapshot will use, then asks the host to commit it.
An older host or a refused move falls back to the old local-only move.

Fixes #32939

* fix(terminal): commit a host pane move through the existing leaf-move writer

The layout-writer ratchet allows no new main-process layout writers. The
headless host now hands the move to commitLeafMoveAndRekey, the same
commit a local window uses, and keeps only the window check, validation,
rehydrate and notify.

* fix(terminal): gate the host pane move on a host capability

An older host keeps today's window-only move under a local tab id instead of
a mirrored one it never confirms. Adds a terminal.list check to the e2e.

* test: drop type assertions from the pane-move tests

* fix(terminal): the host keeps a moved pane's place, focus and live terminal

After the host commits the move, the window gives it the drop position and
focus through the existing tab move and activate calls; a refusal redraws the
host's tabs. The host moves the live PTY's surface with its pane and checks the
target tab id. The move lives in its own runtime module.

* fix(terminal): hold a moved pane's drop order until the host has it

The host's snapshot of the commit goes out before its reply and appends the
tab, so the window records the drawn order as a reorder intent at the drop and
sends that order, and the focus read then, once the host answers.

* fix(terminal): route the moved tab's reorder to its owning host

The follow-up names the worktree's host instead of reading the active-server
setting (focus-read ratchet); the e2e narrows host rows to terminal tabs.

* fix(terminal): the host places a moved pane's tab in the move itself; a refusal reverts

The move carries the drop's group and index (optional; an older host appends)
and the host places and selects the tab before its one publish, replacing the
follow-up reorder and the drop-time order hold. A refused move closes the tab
this window drew without stopping its terminal and redraws the host's tabs even
when unchanged. E2e covers a same-group drop and a refusal; host unit tests cover
same-group and cross-group placement.

* fix(terminal): a refused pane move keeps its pane records; drop index in host order

The revert hands the pane's status and launch records back to the source tab
before closing the drawn tab. The host turns a projected client's drop index
into its raw order with translateProjectedSessionTabMove, as session.tabs.move does.

* fix(terminal): only an unknown drop group appends a moved pane's tab

The host catches only target_group_not_found and invalid_tab_order from the index
translation and rethrows anything else.

* fix(terminal): settle a pane move by what the host holds when its reply is lost

A lost reply is not a refusal: the window asks the host whether it holds the
pane in the new tab before undoing anything, and waits for the next snapshot
when it cannot ask. A move the host committed but this window could not draw
takes the host's tabs.

* fix(terminal): a lost transport on a pane move is unknown, not a refusal

runtimeEnvironments.call answers a dropped or overloaded transport with an error
envelope; those codes (the shared recoverable set) now ask the host instead of
undoing a move it may have committed.

* test(e2e): pane-move spec waits on real signals, not delays

The source tab is created on the server and split once its shell is listed, so
the split no longer races a desktop-seeded tab's first spawn (the CI failure).
The stay check follows output from the moved pane; the refusal check waits for
the drawn tab to appear and go.

* test(e2e): read the refused move's tab counts without Reflect (anti-slop)

* fix(terminal): a moved pane's tab joins the order the user had, not a rebuilt one

The host inserts the new tab into the group order captured before the commit,
appending without a drop position, so editors and pages keep their places. The
shared group-move fallback is no longer used here.

* fix(terminal): a moved pane's groups hold the host's current tabs, in the user's order

Placement takes membership from the rebuild after the commit and only the order
from before it, so a tab opened while the commit was pending stays (after the
user's tabs) and no stale id is written.

* fix(terminal): a moved pane's placement keeps a reorder made while it committed

The order is read after the commit, from the host's current tabs, not before it.

* fix(terminal): a moved pane keeps the lone group the user merged into

A rebuild names a single group with the default id; placement keeps the user's
id for it, so the drop position still applies after a split was merged back.

* fix: import error classification from its new shared/remote-runtime path

* fix(terminal): a pane or divider drag survives focus moving inside the window

The drag's window blur listener was registered in the capture phase, so it also
heard every element blur. A terminal attach or host snapshot that moved focus
between panes mid-gesture ended the drag; in the paired pane-move e2e on CI it
cancelled the drag before the drop. Only the window losing focus ends it now.

* test: drive the focus-change drag test through a real pane manager, without casts

* fix(terminal): a headless host moves a pane with its live terminal and never empties its source

The host names the PTY it holds for the pane, so a session copy an SSH respawn
left with an old id no longer refuses the move as a mismatch. A move that would
leave its source tab with no pane (a sibling closed meanwhile) is refused, since
no headless cleanup retires the emptied tab and it would publish as a phantom.
The e2e fences its stays-moved check on a later server rename instead of
sampling on a timer.

---------

Co-authored-by: Ben Grynhaus <ben.grynhaus@wiz.io>
2026-10-26 02:01:08 -07:00
OrcaWinandOrcaWin cc3ca668d8 test: remove low-value predicates, inventories and helper echoes (#33778)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 22:57:11 -07:00
Jinwoo Hong 28de913868 test(connectors): drive a connector conversation end to end on a headless serve host (#33613)
Adds an end-to-end test that boots an isolated headless serve host and drives the orca connector CLI against a scripted Claude. Runs in the existing PR e2e job only when connector code changes.
2026-10-25 18:07:30 -04:00
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [comment audit] Align legacy failure-read counts with cached display

* [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci]

* [comment audit] Condense shared generation, path and preview comments [skip ci]

* [comment audit] Condense host, file and Git contract rationale [skip ci]

* [comment audit] Tighten Git lifecycle and provider contract comments [skip ci]

* [comment audit] Clarify shared host authority and launch isolation [skip ci]

* [comment audit] simplify local access mobile and native chat contracts [skip ci]

* [comment audit] simplify native chat presentation and state contracts [skip ci]

* [comment audit] simplify native runtime and provider contracts [skip ci]

* [comment audit] simplify migration and orchestration contracts [skip ci]

* [comment audit] simplify plugin trust and process capture rationale [skip ci]

* [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci]

* [comment audit] clarify remote recovery and launch wire rationale [skip ci]

* [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci]

* [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci]

* [comment audit] clarify sidebar, skill and SSH invariants [skip ci]

* [comment audit] condense structured chat delivery and timing rationale [skip ci]

* [comment audit] condense terminal replay and telemetry invariants [skip ci]

* [comment audit] clarify launch permission and terminal delivery rationale [skip ci]

* [comment audit] clarify workspace ownership and layout contracts [skip ci]

* [comment audit] wait for fitted and painted SSH terminal links

* [comment audit] clarify layout persistence and session ownership [skip ci]

* [comment audit] clarify host routing and workspace lifecycle contracts [skip ci]

* [comment audit] clarify startup persistence and app lifetime comments [skip ci]

* [comment audit] clarify recovery and activity boundaries [skip ci]

* [comment audit] condense activity and artifact invariants [skip ci]

* [comment audit] condense automation authority contracts [skip ci]

* [comment audit] simplify automation scheduling and history contracts [skip ci]

* [comment audit] clarify browser chrome and annotation lifecycles [skip ci]

* [comment audit] clarify browser routing and retained guest ownership [skip ci]

* [comment audit] clarify browser recovery and document ownership comments [skip ci]

* [comment audit] restore hosted runners required by native IME fixtures

* [comment audit] shorten preview, palette and dashboard invariants [skip ci]

* [comment audit] clarify terminal preview, dashboard and retention comments [skip ci]

* [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci]

* [comment audit] shorten rich-text and combined-diff ownership comments [skip ci]

* [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci]

* [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci]

* [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci]

* [comment audit] clarify rich Markdown source and selection safeguards [skip ci]

* [comment audit] clarify editor loading and emulator stream lifetimes [skip ci]

* [comment audit] trim emulator and feature tour narration [skip ci]

* [comment audit] clarify floating workspace and GitHub cache ownership [skip ci]

* [comment audit] trim provider controls and Linear filter explanations [skip ci]

* [comment audit] clarify pairing and chat resume lifetimes [skip ci]

* [comment audit] clarify chat delivery and transcript lifetimes [skip ci]

* [comment audit] clarify session options and host-owned status [skip ci]

* [comment audit] clarify draft durability and live-status precedence [skip ci]

* [comment audit] Trim chat send and model ownership explanations [skip ci]

* [comment audit] Shorten transcript ownership and send recovery comments [skip ci]

* [comment audit] Simplify native chat composer and transcript hook comments [skip ci]

* [comment audit] Shorten native chat scroll and structured session explanations [skip ci]

* [comment audit] Trim workspace picker and onboarding commentary [skip ci]

* [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci]

* [comment audit] Trim sidebar and session history explanations [skip ci]

* [comment audit] Condense session resume and review checks comments [skip ci]

* [comment audit] Shorten checks state and file explorer refresh comments [skip ci]

* [comment audit] Trim plugin bridge and source-control action rationale [skip ci]

* [comment audit] Trim source-control listing and review rationale [skip ci]

* [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci]

* [comment audit] Condense Settings account and appearance rationale [skip ci]

* [comment audit] Trim pairing and repository Settings explanations [skip ci]

* [comment audit] Condense Settings search SSH and terminal safeguards [skip ci]

* [comment audit] Shorten Settings voice and provider ownership comments [skip ci]

* [comment audit] Condense Settings draft persistence and host-state rationale [skip ci]

* [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci]

* [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci]

* [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci]

* [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci]

* [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci]

* [comment audit] Prune focus-read baseline after comment cleanup

* [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci]

* [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci]

* [comment audit] Clarify viewport geometry and skill update safeguards [skip ci]

* [comment audit] Tighten skill inventory, usage and host status comments [skip ci]

* [comment audit] Clarify usage ownership and resource kill safeguards [skip ci]

* [comment audit] Tighten tab selection, rename and search contracts [skip ci]

* [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci]

* [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci]

* [comment audit] Tighten provider loading and terminal recovery comments [skip ci]

* [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci]

* [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci]

* [comment audit] Tighten PTY startup and replay explanations [skip ci]

* [comment audit] Clarify PTY identity and delivery ownership [skip ci]

* [comment audit] Shorten remote reconnect and input replay rationale [skip ci]

* [comment audit] Condense terminal parking clipboard and file link comments [skip ci]

* [comment audit] Clarify IME ownership and trim trace history [skip ci]

* [comment audit] Shorten pane recovery and parked watcher explanations [skip ci]

* [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci]

* [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci]

* [comment audit] Trim renderer lifecycle and menu rationale [skip ci]

* [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci]

* [comment audit] Clarify renderer hook evidence and source ownership [skip ci]

* [comment audit] Tighten IPC ownership and reconnect explanations [skip ci]

* [comment audit] Trim renderer focus, paste and attention comments [skip ci]

* [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci]

* [comment audit] Trim agent launch, resume and ownership commentary [skip ci]

* [comment audit] Shorten account, browser and editor comments [skip ci]

* [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci]

* [comment audit] Condense launch recovery and editor tokenization comments [skip ci]

* [comment audit] Condense workspace selection and terminal pane rationale [skip ci]

* [comment audit] Condense terminal fitting and rendering recovery comments [skip ci]

* [comment audit] Condense terminal input and host routing explanations [skip ci]

* [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci]

* [comment audit] Condense structured launch and terminal link explanations [skip ci]

* [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci]

* [comment audit] Condense session persistence and workspace activation rationale [skip ci]

* [comment audit] Condense workspace routing and browser ownership rationale [skip ci]

* [comment audit] Condense mirror recovery and layout authority rationale [skip ci]

* [comment audit] Condense runtime transport and file safety rationale [skip ci]

* [comment audit] Condense runtime graph and structured session rationale [skip ci]

* [comment audit] Condense browser creation and remote tab recovery rationale [skip ci]

* [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci]

* [comment audit] Condense frame, startup and provider cache rationale [skip ci]

* [comment audit] Condense host twin and agent lifecycle rationale [skip ci]

* [comment audit] Condense browser authority and editor cleanup rationale [skip ci]

* [comment audit] Condense editor buffer identity and review cache rationale [skip ci]

* [comment audit] Condense provider lifetime and repository ownership rationale [skip ci]

* [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci]

* [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci]

* [comment audit] Condense cleanup ownership and listing admission rationale [skip ci]

* [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci]

* [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci]

* [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci]

* [comment audit] Condense cloud rollout and assignment safety rationale [skip ci]

* [comment audit] Condense relay session and database recovery rationale [skip ci]

* [comment audit] Condense relay placement and protocol compatibility rationale [skip ci]

* [comment audit] Condense build and packaging constraints [skip ci]

* [comment audit] Condense CI gates and validation isolation rationale [skip ci]

* [comment audit] Condense catalog identity and browser validation rationale [skip ci]

* [comment audit] Condense mobile browser and frame-budget rationale [skip ci]

* [comment audit] Shorten mobile route and browser test explanations [skip ci]

* [comment audit] Condense editor and session seam test rationale [skip ci]

* [comment audit] Trim session and terminal closure history [skip ci]

* [comment audit] Shorten terminal browser and census explanations [skip ci]

* [comment audit] Preserve root-only E2E diagnostic baseline

* [comment audit] Condense mobile seam and HTML preview test rationale [skip ci]

* [comment audit] Condense preview instrumentation and render harness rationale [skip ci]

* [comment audit] Condense bundle contracts and native-build constraints [skip ci]

* [comment audit] Shorten native packaging and CI routing rationale [skip ci]

* [comment audit] Condense release tooling and benchmark constraints [skip ci]

* [comment audit] Condense packaging and platform gate rationale [skip ci]

* [comment audit] Shorten mobile route and history rationale [skip ci]

* [comment audit] Condense mobile browser and component rationale [skip ci]

* [comment audit] Shorten drawer and workspace-form rationale [skip ci]

* [comment audit] Trim mobile review and editor-document explanations [skip ci]

* [comment audit] Condense editor lifecycle and Markdown explanations [skip ci]

* [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci]

* [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci]

* [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci]

* [comment audit] Condense bridge stream, native request and audio rationale [skip ci]

* [comment audit] Reduce bridge schema and page-client commentary [skip ci]

* [comment audit] Tighten mobile page bridge contracts [skip ci]

* [comment audit] Clarify mobile cache and session constraints [skip ci]

* [comment audit] Condense mobile route and lifecycle rationale [skip ci]

* [comment audit] Tighten mobile shell hook timing rationale [skip ci]

* [comment audit] Clarify route handoff and notification compatibility [skip ci]

* [comment audit] Clarify push registration and dictation cleanup constraints [skip ci]

* [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci]

* [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci]

* [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci]

* [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci]

* [comment audit] Tighten stream replay and queued session ownership rationale [skip ci]

* [comment audit] Condense mobile composer and reply-schema constraints [skip ci]

* [comment audit] Clarify mobile transcript recovery and session ownership [skip ci]

* [comment audit] Clarify mobile queue and source-control compatibility [skip ci]

* [comment audit] Condense storage and task launch constraints [skip ci]

* [comment audit] Clarify task schemas and terminal document ownership [skip ci]

* [comment audit] Tighten terminal input and viewport rationale [skip ci]

* [comment audit] Shorten terminal and RPC recorder contracts [skip ci]

* [comment audit] Condense recorder engine and connection contracts [skip ci]

* [comment audit] Shorten host credential and relay recovery comments [skip ci]

* [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci]

* [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci]

* [comment audit] Condense catalog and relay entry contracts [skip ci]

* [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci]

* [comment audit] Condense WSL relay and E2E regression rationale [skip ci]

* [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci]

* [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci]

* [comment audit] Shorten layout oracle and regression explanations [skip ci]

* [comment audit] Condense paired recovery and ownership regression contracts [skip ci]

* [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci]

* [comment audit] Condense terminal rendering and IME regression rationale [skip ci]

* [comment audit] Shorten terminal recovery and benchmark contracts [skip ci]

* [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci]

* [comment audit] Condense workflow routing and release rationale [skip ci]

* [comment audit] Condense mobile security and installer contracts [skip ci]

* [comment audit] Shorten native rationale and stylesheet workarounds [skip ci]

* [comment audit] Shorten Windows visibility probe rationale [skip ci]

* [comment audit] Condense embedded release and signing rationale [skip ci]

* [comment audit] Retain current main test behavior in final reconciliation [skip ci]

* [comment audit] Complete final comment review across current source inventory

* [comment audit] Handle consolidated audit reports before final CI

* [comment audit] Preserve credential storage census detail [skip ci]

* [comment audit] Scale broad changed E2E selections with verified timing shards

* [comment audit] Align E2E assertions with browser close and settled session metadata

* [comment audit] Match existing E2Es to current resource and windowing contracts

* [comment audit] Observe the shared server outage notice in reconnect coverage

* [comment audit] Make existing renderer probes measure settled tab and PTY work

* [comment audit] Align palette E2E selector with task search

* [comment audit] Refresh remaining palette search selectors

* [comment audit] Keep selector correction within selected audit specs

* [comment audit] Await emoji fixture settings inside renderer evaluation

* [comment audit] Cancel fixture fit retries between terminal tests

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-25 04:05:46 -07:00
Neil e1ee51dec4 fix(ssh): saved port forwards come back after a restart that reconnects through the host's Orca server (#32484) (#33388)
* test(e2e): a saved SSH forward on a managed host comes back after a restart (#32484)

* test(e2e): a restart that reaches the host only through its server's tunnel restores saved forwards (#32484)

* fix(ssh): restore saved port forwards whenever a host's server is up, not only after an SSH connect (#32484)

A host whose managed server the app reaches at startup through its tunnel
publishes its served status from the tunnel path, which never ran the
forward restore. The served status itself now triggers the restore, from
any publisher, and a host kept on the legacy relay bridge restores too.

* test(ssh): type the restore spy's target argument

* fix(ssh): user forward edits keep unrestored saved forwards; restore once the tunnel is up

Add, edit and remove now merge with saved forwards that aren't live, so a
restart that restored nothing no longer lets one re-added forward erase
the rest. A call that brings up the managed server's tunnel restores too,
so a failed update check or redeploy can't skip it, and a restore runs once
per transport instead of on every status note.

* fix(ssh): a forward that failed to bind is retried on the same transport

* fix(ssh): restore again when a restored forward closed; a reused local port replaces its old mapping

* fix(ssh): user forward edits and restores share one per-host queue

A restore that ran mid-edit could reopen the mapping the edit was
replacing and save both. Every add, edit, remove and restore for a host now
runs in order through the same queue.

* fix(ssh): pace forward restores and back off a forward that keeps failing

Each runtime call triggers a restore over the managed tunnel, and with
system SSH every attempt is a new login. Restores on the same transport
now run at most once per 30 s, a forward that keeps failing waits longer
each time, and teardown forgets both. A tunnel with no status yet counts
only outside a connect; a remove re-checks its forward inside the queue;
drop a circular listener test and the plain restart e2e case.

* fix(ssh): a start a teardown cancelled records no backoff

* fix(ssh): a reconnected transport retries failed forwards at once
2026-10-25 00:32:24 -07:00
NeilandMaple e13e5e8b7d fix(orchestration): submit mail pointers to Cursor CLI instead of leaving them in the composer (#33347)
Orchestration mailbox pointers now get their delayed Enter for Cursor too (the Cursor no-Enter skip from #3156 left mail delivered but never acted on). Verified with real cursor-agent. Fixes #19760. Supersedes #19761.

Co-authored-by: Maple <54388301+zleo-ai@users.noreply.github.com>
2026-10-25 00:07:42 -07:00
Neil fef00e4461 feat(dev): give each worktree its own pnpm dev profile and ports (#33416)
Linked worktrees now get a per-worktree userData profile under
orca-dev-wt/<hash>, a deterministic collision-checked renderer port
(15200+) and CDP port (9333+), so parallel `pnpm dev` runs no longer hit
the single-instance lock. The main checkout keeps the shared orca-dev
profile; --shared-profile / --worktree-profile / ORCA_DEV_PROFILE override.

The runner prints `ORCA_DEV_INSTANCE {json}` on stdout and mirrors it to
out/dev-instance.json so agents can attach over CDP. orca-dev.mjs and the
terminal wrappers follow the profile the worktree's dev app used.

Adds profile seeding (pnpm dev:seed-profile, pnpm dev --seed-profile,
pnpm dev:fresh --seed-from): an allowlisted slim copy of projects,
workspaces, settings, SSH targets, and the account session, written as
orca-data.json for Orca's own import; the source is only read.

Storage reclaim removes per-worktree profiles once their worktree is gone
and no app, runtime, or daemon still uses them. dev-fresh-profile.sh is
replaced by a cross-platform dev-fresh-profile.mjs.
2026-10-24 23:08:52 -07:00
NeilandClaude Opus 5.5 8e9f86b119 feat(managed-server): Update and Update anyway for a managed SSH server, keeping its running terminals (#33397)
* feat(managed-server): offer Update and Update anyway on a managed SSH server's row

A managed SSH server only updated on connect, and every connect deferred while
a terminal ran there. Its row now offers Update when this desktop bundles a
newer build than the Orca that runs the host. When running terminals defer it,
Update anyway restarts the server past them; the terminal daemon carries them
across the restart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(managed-server): Update anyway passes only counted live terminals and keeps Update's version admission

Update anyway now sends the incumbent build it confirmed over; under the host
fence it is refused if another desktop activated a build since, keeps the
newer-host downgrade refusal, and never passes an uncounted terminal census.
The offer also covers hosts activated before the Orca version was recorded,
for the build this desktop already deferred there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(managed-server): Update anyway also confirms a menu Update the row does not advertise

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(managed-server): the row shows one confirmation at a time

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci(e2e): route managed-server update sources to the Update anyway Docker spec

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 22:49:18 -07:00
OrcaWinandOrcaWin fdcf887bf0 chore(lint): fix suppressed lint violations instead of ignoring them (#33294)
* chore(lint): remove disable directives that suppress nothing

Each was reported unused by the oxlint config that owns its rule (root,
casting, react-doctor, anti-slop, native-plugins), including 17 file-level
react-doctor blanket disables whose rule no longer fires. An unused
react-hooks disable was masking a react/purity finding in
useAgentBucketCounts (compiler rules skip functions with hook
suppressions); suppress that one explicitly where the code explains it.

* test(tab-bar): use typed tab/file fixtures instead of @ts-expect-error partials

* chore(lint): fix misc suppressed lint violations instead of disabling

- consistent-type-imports: the "vi.importActual requires inline import()"
  rationale was wrong; use typed vi.mock(import(...), importOriginal) or a
  type-only namespace import.
- no-control-regex: source-control commit/push failure summaries, shell PATH
  hydration, and ipynb ANSI parsing reuse the shared ansi-escape-sequences
  stripper and control-character pattern instead of private copies.
- no-explicit-any / no-useless-spread / no-this-alias: typed `this` in
  bridge test mocks, drop blanket file-level disables, drop a this-alias.

* refactor(main): replace avoidable type assertions with checked narrowing

17 production cast suppressions in main/wsl-guest removed: untrusted IPC
and RPC payloads (Jira issue update, WSL guest plugin install, handshake
type) now narrow through isRecord/type guards and build typed results;
redundant casts dropped; hand-rolled object checks and errno casts use the
existing isRecord / hasErrorCode helpers.

* refactor(renderer,mobile): replace avoidable type assertions with checked types

13 production cast suppressions removed: RPC failures read through the
`ok` discriminant instead of casting to RpcFailure; react-native-web host
refs narrowed with instanceof HTMLElement; redundant reply/diff casts
become checked annotations; the TUI agent param uses z.custom(isTuiAgent)
like its sibling schemas; the agent-status slice cast that hid missing
actions is gone so the creator's return type checks the whole slice.

* chore(lint): drop dead tiptap wrapper; scope anti-slop mocking rule to test support

- safe-react-node-view-renderer patched handleSelectionUpdate, which
  @tiptap/react 3.31.3 no longer has, so it always returned early. Use the
  stock ReactNodeViewRenderer.
- The anti-slop test override only matched *.test.{ts,tsx}, forcing
  file-level no-module-mocking disables in test harnesses and fixtures.
  Match *-test-harness, *-test-fixture(s), .test-fixture and *.test.mjs,
  then drop those disables and the now-redundant per-file exemptions.

* test: replace repeated cast suppressions with typed fixtures (main/shared/e2e)

Net -119 cast directives. Typed fixtures replace recurring casts:
unusedSshConnection/unusedOrcadHostTransport placeholders, a typed
readPersistedState harness read (exposed a test treating optional
httpProxyUrl as always set), real Response objects in fetch spies, an
updater main-window fixture, a CDP webContents adapter, one shared
releaseModuleExport for cross-version tests, and requireOverlayDir in
place of `as string` on possibly-undefined overlay paths.

* fix(runtime): validate loaded device rows and host scope instead of casting

Two cast suppressions claimed checks that did not happen:
- device-registry-file-read: "normalizeLoadedDeviceRow re-validates every
  field" was false; deviceId, token, name and timestamps were never
  checked. Rows without an identity are now dropped and the file is set
  aside through the existing corrupt-file path (registry reads as
  doubtful, so no revocation verdicts); a non-string token is stripped
  so the row stays listed but cannot authenticate.
- client-ui: "only optionality differs" was false; any string reached
  persisted workspaceHostScope. Narrow it with isWorkspaceHostScope and
  ignore unknown scopes (wire schema unchanged for mixed versions). The
  remaining cast now states what it actually covers.

* test(runtime,ipc): drop redundant cast suppressions; typed runtime store fixture

-98 cast directives across runtime/ipc tests: 61 casts that typecheck
without the assertion; a typed vi.fn for scoped invalidation; a
partialRuntimeStore fixture in place of `store as never` for
OrcaRuntimeService; typed worker terminal/authority builders in the
incarnation-fallback suite (the old stub lacked runtimeId, ptyId,
worktreeId and launchTokenHash) plus removal of stacked duplicate
directives there.

* chore(lint): typed vi.mock imports and explicit listener snapshot in shared-bucket tests

Remaining files from the misc-rule pass that overlapped other buckets:
codex-accounts mocks use the typed vi.mock(import(...)) form instead of
a consistent-type-imports disable; the daemon late-exit fixture copies
its listener set with Array.from instead of a no-useless-spread disable.

* refactor(github,mobile): narrow routing settings type; drop remaining avoidable casts

- work-item-routing cast a one-field object to AppState['settings'] under
  a claim that readers only consume activeRuntimeEnvironmentId. Return the
  existing RuntimeFocusSettings pick instead and narrow the cache-key
  chain's parameters, so the compiler enforces that claim.
- mobile file preview reads refusals through the RpcResponse `ok`
  discriminant rather than a cast.
- terminal document host-notify accepts `unknown` errors, removing the
  message-bridge cast.
- native chat readability drops a redundant repo-list cast.

* test(renderer): replace repeated cast suppressions with typed fixtures

-140 cast directives (and ~150 unsuppressed casts of the same shapes):
- mockPanePtyArgs owns the connectPanePty fixture casts, so a wrong
  argument order or fixture is now a type error; drop binding casts for
  members PanePtyBinding already declares.
- Seed AppState tab rows with the typed makeTerminalTab; this exposed a
  split direction 'row' that is not a TerminalPaneSplitDirection.
- Partial GlobalSettings use getDefaultSettings overrides; Jira-only
  stores use createTestStore; runtime environment rows/statuses come from
  a typed paired-runtime-environment fixture.

* chore(lint): drop no-module-mocking disable now covered by the test-fixture glob

* test(e2e): fix all 221 grandfathered e2e type errors; baseline to zero

The e2e typecheck baseline grandfathered 221 errors in 119 files. About a
third were tests drifted from the current API, several of which would
fail or mislead at runtime:
- terminal-shortcuts called a nonexistent xterm buffer method;
- the daemon-lifecycle fake lacked terminateOwnedTree, hidden by importing
  its type from a module that does not export it;
- arguments in the wrong position (listProcesses(hostId, opts), a dropped
  pty.write inputKind, an extra toWebTerminalSurfaceTabId argument);
- removed enum values ('drawer', 'idle', 'editor',
  'session-insecure-origin') and a launch_source the telemetry schema
  rejects;
- missing required fields (turnScope, childWorkOpen, providerIdentity,
  reviewLookupOutcome, runtimeServiceAccount behind an `as RelayConfig`);
- dead reads (tab.customTitle, bucket.targets, changedPixels).
Also lifts variables that narrowed to `never` after poll-callback
assignment, and adds a typed xterm render-dimensions accessor. Includes
the cross-version suites' shared releaseModuleExport adoption.

* chore(typecheck): gate the now-clean e2e typecheck in local lint

The e2e baseline is empty, so the ratchet now rejects any tests/ type
error. CI already runs it via run-ratchets; add it to pnpm lint's
explicit ratchet list and replace the stale 'not yet clean' note.

* test(mobile): typecheck every mobile test; baseline 105 files -> 0

mobile/tests-typecheck-baseline.txt grandfathered 105 test files (531
errors) whose types CI never checked. Contract changes had landed without
these tests following, and type pins in them silently checked nothing.

- Root cause: salvagedOptional (src/shared/zod-salvage.ts) cast away
  zod's optional marker, so every reply type built on it claimed
  salvaged fields were always present. Return a real ZodOptional;
  runtime is unchanged (the outer optional short-circuits undefined).
- Production now takes the port it uses: RpcOperationSender instead of
  RpcClient for send-only callers, a narrow fetch signature, and only the
  request fields push-receive reads.
- findAllByHostType/findByHostType replace `'Text' as never` lookups;
  stubRpcClient gives tests a typed RpcClient.
- Real test bugs: settings-host-client-lifecycle "relay" hosts were
  direct hosts (relayHostId no longer exists); two toEqualTypeOf pins
  could never hold; a recovery hook ran with an undefined scope key;
  a create-PR test cast to a never-imported type; "no URL" assertions
  passed on null; plus stale required props, args and enum values.

* test(e2e): parse workerStart result instead of casting it

* test(runtime): typed partial runtime stores with production-shaped fixture data

Route every partial store passed to OrcaRuntimeService through
partialRuntimeStore with typed row builders, removing 60 cast directives
that hid fixture data production never produces:
- paired-client-navigation-test-harness keyed setWorktreeMeta writes by
  `[object Object]` (it took an id where the store takes a target), so
  reads never saw them;
- invalid worktree lineage origin/capture and incomplete workspace lineage;
- getWorkspaceSession returning null where the store returns the default
  session; getGitHubCache returning undefined;
- orcad migration states missing migrationId/manifest/receipt, with a
  toEqual pinned to that incomplete shape;
- partial repos, metadata, folders, project groups and notification rows.

* refactor(renderer): resolve suppressed React hook lint instead of disabling it

109 of 187 react-hooks/react-doctor suppressions fixed:
- ~95 exhaustive-deps disables only hid stable refs and setters passed
  through controller objects; list them (identity never changes, so
  nothing re-runs), plus a few real stale closures (tabId/worktreeId).
- Effects keyed on one value that read the latest props now use
  useEffectEvent (WebConnect, skill freshness, Bitbucket dialog seeding,
  Landing preflight, automation catalog, structured-session recovery,
  file explorer).
- The conditional layout-owner hook is selected once at module load from
  the build constant instead of a rules-of-hooks disable that also hid
  the whole controller from compiler and react-doctor rules.
- A hook disable was masking a react/purity Date.now() in the palette's
  recent-tabs memo; sample it when the snapshot is captured.
- Three identical debounced-search effects share
  useTaskSearchDebouncedApply; layout snapshot merge helpers move out to
  stay under max-lines.
Remaining suppressions are mount-once resources, invalidation epochs,
deliberately keyed fields, and test harnesses that mock the dispatcher.

* chore(ratchet): exempt test harnesses and fixtures from the focus-read ratchet

The ratchet skipped *.test-fixture.ts but scanned *-test-harness,
*-test-fixture(s) and *.test-support files. Those seed settings with
getDefaultSettings (which the ratchet discovers as a focus reader) and
never route host work, so 17 baseline entries were false positives, and
replacing a test's `settings: {...} as never` with typed defaults tripped
it. Match the repo's test-support names and prune (which also drops one
stale entry), and rename the frame-ordering harness to *-test-harness like
its siblings. A blanket "-harness" match would also exempt
e2e-terminal-pty-harness, which production imports, so the names stay
explicit.

* test: merge duplicate shared/constants imports

* revert(file-explorer): restore original tree load effects pending SSH e2e investigation

* test(runtime): drop RuntimeStore cast now that the fixture is typed

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-24 19:37:19 -07:00
Neil 7190b835f1 test(e2e): stop restarted serve hosts with SIGTERM so the PTY daemon survives (#33067)
On macOS, orca serve vetoes an unmarked app.quit() (#15537), so Playwright's
close never finished and its fallback force-killed the whole process tree,
including the detached PTY daemon's terminals. The restart spec's fixture
process got SIGTERM and the spec failed on every macOS run.

Restart now stops serve the way a supervisor does (SIGTERM, a marked quit),
and the restart spec no longer shows or focuses its client window.
2026-10-24 00:48:05 -07:00
Neil 82534d0183 refactor: colocate remote runtime passthrough socket (#33009) 2026-10-23 19:46:31 -07:00
OrcaWinandOrcaWin d1aef1eee9 refactor(worktrees): remove unreachable legacy worktree list IPC (#32942)
* refactor(worktrees): remove unreachable legacy worktree list IPC

The desktop and web preloads both always provide `worktrees.listDetected`,
so the renderer's fallback to `worktrees.list` never ran, and nothing calls
`worktrees.listAll`. Remove both IPC handlers, their preload/web wrappers,
the renderer fallback, and helpers only they used. Tests that observed
behaviour through the removed handlers now go through `worktrees:listDetected`.

* test(worktrees): drop removed list channels from SSH listing matrix

Address review: the canonical SSH listing test built channel names dynamically
and still invoked worktrees:list/listAll. Also fold the detected-result reader
into the shared worktree test fixtures, refresh stale comments, and assert the
backfill no-op test listed authoritatively.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 17:30:59 -07:00
Kelvin Amoaba 4c7991509f feat(browser): say why a page failed to load and reload it when it can (#31847) 2026-10-23 16:40:20 +00:00
Jinjing 8fc1698ebb Add Resource Manager PTY inventory and checked terminal termination (#31593)
* feat(resources): end local terminals with checked identity checks

- Add a PTYs view to the Resource Manager that lists local terminals and can end one. Before stopping, the host confirms the terminal's incarnation, agent ownership, and attachments. This prevents ending a same-ID replacement that started after the list was read.
- Bump the daemon protocol to v50 and gate the new fenced kill on it. Older daemons ignore the extra fields, so the client refuses End instead of sending an unchecked kill.
- Admit diagnostic reads (PTY listing and memory snapshot) only while the Resources view is visible, so a hidden window does no background scanning.
- Report incarnation and host on spawn and exit events so the renderer can match them to the terminal it showed.
- Replace the popover's terminal-session list with the new inventory and move memory polling into the Resources view.

* Show unrouted daemon PTYs in the Resource Manager

* Simplify PTY controls and clarify safe termination

* Format local build compatibility protocol lists

* Add PTY resource management localization strings

* Harden PTY resource manager test fixtures

* Use typed IPC event in PTY kill test fixture

* Refine local PTY resource view model typing

* Refine local PTY resource view model typing

* Include local host identity in PTY lifecycle event tests
2026-10-22 12:20:01 -07:00
Jinwoo Hong 4dd97f02d5 A paired desktop's tab moves, splits, resizes, props and renames in a server workspace go to the server's layout (switch-on builds only) (#31714)
* wip(h5c-1): server workspace layout intents go to the server's main; positions per stream

* wip(h5c-1): e2e without a cast

* fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over

* fix(h5c-1): layout-apply-send back under its line limit

* test(h5c-1): a routed answer reaches the handler with the server's stream key

* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did

* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)

* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)

* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)

* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab

* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)

* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252

* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup

* test(terminal): a Claude account-switch restart's tests draw the pane main holds

* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)

* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice

* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class

* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)

* chore(terminal): mark the phone-close row drop interim (ledger L260)

* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)

* test(worktree-create): record main's starts without a type assertion

* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)

* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)

* feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build)

- Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply;
  closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close,
  terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style
  closeTabs is never routed: main takes it as already stopped and stops nothing.
- A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the
  window's optimistic drop never outlives the answer; same for a refused pane close.
- A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or
  focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff.
  Its pane-geometry push is skipped (it goes as intents); pane renames still push.
- e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop),
  rename, split and new tab on a windowless switch-on serve.

* fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route

* refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap

* fix(h5c): a routed tab move anchors on the server's tabs only, and never sends an anchor the server does not hold (layout-owner build)

* fix(h5c): every routed tab move is placed among the server's tabs at the routing point, skipping desktop-only tabs (layout-owner build)
2026-10-22 08:00:09 -04:00
Jinwoo Hong ed13ca1e6f Layout groundwork (switched off): main starts and restores every pane, and the window only attaches and paints the host's history (#31567)
* feat(terminal): the window's store shows main's pane start status (layout-owner build)

A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.

* refactor(terminal): keep the frame sync within its line budget

* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)

* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)

Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.

* test(terminal): iterate store listeners directly

* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)

* test(terminal): a pane main started attaches to main's terminal at its own grid

* test(terminal): detach census tests assert main's move and frame with the switch on

* fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)

* fix(terminal): main releases a painted cold restore only for the pane's current terminal (layout-owner build only)

* test(terminal): typed ack args in the painted cold-restore test

* test(terminal): exited-pane tests assert main's close with the switch on

These 8 have failed switch-on since #31130 moved the exit close to main.

* feat(layout): main starts the panes a client's startPane or wake names, through its one restored-pane starter (layout-owner build only)

The model's start and stop effects reach the runtime once the command is written, never the
reply. startPane starts a pane whose binding has a start status, and a slept pane from its record.
A wake keeps a pane's resume record until its start binds; a failed start keeps it and shows why.
With the switch on, main owns a running agent's wake even with a window open.

* fix(layout): a window's wake of a pane it resumed itself is record-only, so main does not start it again (layout-owner build only)

* test(layout): a client's startPane starts the pane before any other trigger

* Revert "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"

This reverts commit a646d89b26.

* test(terminal): the cold-restore repaint ack names its pane in the layout-owner build

* fix(layout): a wake keeps the pane's resume record in the pane-owned form, so a host crash resumes the agent again (layout-owner build only)

* test(layout): a failing start in the pane-start runner test throws

* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)

The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.

* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path

* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused

Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.

* Reapply "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"

This reverts commit 420e605ad0.

* fix(layout): a trigger between a restored pane's start and its bind write joins that start, not a second one (layout-owner build only)

* fix(terminal): a window split main cannot start still shows its pane, failed, with why

* fix(terminal): keep the switch import the D1b-1 merge dropped from the layout restore

* feat(layout): main's starter shows a restored pane starting before any await, and a failed start with its reason; startPane starts a pane whose bound terminal ended (layout-owner build only)

* feat(terminal): a window's attach-only view of a pane attaches to its terminal or starts nothing, keeping the binding for main's start (layout-owner build only)

* feat(terminal): a restored pane with no live terminal asks main to start it and attaches once main binds it; the window's wake, hibernated wake, restart-on-show and gone-terminal fallbacks route through main (layout-owner build only)

* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status

Three test stores gain the pane-start field the window's store always has.

* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears

* test(terminal): with the switch on, the window's restores, wakes and restart replacements ask main to start the pane and attach to the terminal main binds, starting and resuming nothing themselves

* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module

* fix(terminal): a held pane connects as today so main's refusal holds it; main's start follows output; a capacity death after main's resume keeps the resume (layout-owner build only)

* fix(layout): main passes its start options only for commands that start panes; a windowed open resumes the agent under main (layout-owner build only)

* refactor(terminal): break the pane start module cycle; typed test fixtures

* test(terminal): a pane main wrote and the user closed mid-start drops its failed status

* fix(terminal): every waiter on main's start runs, a failed or refused start settles and is asked again on the next view, a hidden gone pane waits for its view, a twin main hands back stays on today's path, and lost contact never starts a pane (layout-owner build only)

* test(terminal): iterate the store listeners directly

* test(terminal): a cold-restore paint applies no window resume with the switch on

* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report

* test(terminal): name the main pane start test helper as a fixture

* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab

* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner

* fix(terminal): a pane main starts at relaunch keeps its full history and starts before the window's graph lists its tab (layout-owner build only)

* feat(terminal): main replaces the empty shell a hibernated agent left and resumes the agent when a view asks; a start main could not attach shows main's own terminal_not_started (layout-owner build only)

* test(terminal): main seeds its copy of a restored pane even when a window declared one, with the switch on

* fix(terminal): a pane main started hidden paints once when its view attaches (switch-on builds only)

The view's attach snapshot, taken after main unmarks the hidden gate, already holds the bytes main
dropped, so the reveal no longer asks a second restore from main's buffer.

* refactor(terminal): main's pane start reads the pane from its own layout, not the client projection (switch-on builds only)

* fix(terminal): a pane restart stops its terminal before writing the pane unbound, so a stop that fails keeps the pane (switch-on builds only)

* test(terminal): a start main could not bind shows terminal_not_started through the window's wait, from one copy site (switch-on builds only)

* fix(terminal): main's pane start resumes an agent recorded under the pane's number, as the window did; stale switch-on tests show main's start and the known banner loss (switch-on builds only)

* fix(terminal): review round: a visible view alone retires main's hidden-start drops, by episode; main reads a split pane's numbered record by its number; the window keeps a shell main kept (switch-on builds only)

* fix(terminal): main's start of a pane a window view is attaching to leaves it visible, so the view paints once (switch-on builds only)

* fix(terminal): a window view that attaches while main starts its pane takes main's start, with no second provider attach (switch-on builds only)

* fix(terminal): review round: a view takes main's start only when it carries a payload and still owns its terminal; attaching views are counted, keep main's pre-spawn mark off, and a failed attach hides only what it kept visible (switch-on builds only)

* test(terminal): checked test doubles in the view-join and numbered-record tests; reattach options fit the file budget

* test(e2e): with the switch on, the mount-authority spec keeps the window's bindings, which only main's frames write

Claude-Session: W5b

* test(e2e): the mount-authority spec fits its line budget; the layout-owner build check is one helper

Claude-Session: W5b

* test(layout): an unbound pane holding a resume record starts and keeps the record, since main's start resumes from it

Claude-Session: W5b

* fix(terminal): a pane main resumed holds a running agent's record, so it reads awake, not sleeping (switch-on builds only)

Claude-Session: W5b

* fix(terminal): in a split where no pane owns a numbered record, main resumes the tab's one conversation in pane 1 only (switch-on builds only)

Claude-Session: W5b

* fix(terminal): main marks only the record a resume started from live, never a sleep captured after it (switch-on builds only)

Claude-Session: W5b

* test(terminal): with the switch on, a windowless resume leaves the pane's record a running agent's

Claude-Session: W5b

* test(terminal): the resumed-record test double typechecks

Claude-Session: W5b

* refactor(terminal): with the switch off, main's pane start reads no resume record for the live mark

Claude-Session: W5b

* refactor(terminal): the merged files fit their line budget: the folder-fallback notice and a hidden pane's wait for its first view are helpers, and the answering-transport lookup moves to its one test caller

Claude-Session: W5b

* refactor(runtime): the window's layout apply fits the runtime's line budget after the main merge

Claude-Session: W5b
2026-10-21 22:13:57 -04:00
Jinwoo HongandClaude 98007cb5f5 refactor(sidebar): one shared feed client for the desktop window and the phone (#31524)
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)

The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.

- Main stops counting a closed tab's terminal once it was asked to stop
  and no pane shows it; a failed or refused stop takes the request back,
  and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
  so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
  read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
  feed stands in while a fresh read is out; it highlights its own
  last-opened workspace and a fold never scrolls; projects file under
  their project group with the desktop's indent; the collapse spinner
  shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.

* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises

- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.

* refactor(sidebar): read the sidebar feed through one shared client library on desktop and phone

The window and the phone each had their own feed reader. Both now use one reader under
src/shared/sidebar-feed-client: paging, notices, retries and the snapshot it installs. A
snapshot reuses each unchanged entity's parsed object, so one row's change re-parses one row
instead of its whole domain. Adds the gesture and view-report pieces later cuts wire up.
Lists are unchanged on both sides.

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

* refactor(sidebar): drop the feed client's unused pieces until their callers land

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

* test(sidebar): key the snapshot test's rows by host identity, as main's feed types now do

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 19:34:02 -04:00
Jinwoo Hong 92bbb380a6 Sidebar: land the switch-on fixes from the flip branch on main, switch still off (#31466)
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)

The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.

- Main stops counting a closed tab's terminal once it was asked to stop
  and no pane shows it; a failed or refused stop takes the request back,
  and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
  so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
  read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
  feed stands in while a fresh read is out; it highlights its own
  last-opened workspace and a fold never scrolls; projects file under
  their project group with the desktop's indent; the collapse spinner
  shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.

* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises

- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.
2026-10-21 12:24:27 -04:00
Neil c149395c20 fix(remote-terminal): repaint an idle full-screen program when a reconnect can't image its pane (#31482)
* fix(remote-terminal): repaint an idle program when a reconnect can't image its pane

After an SSH reconnect the pane is repainted from the host's screen
snapshot. When the host cannot give one (too large for the replay limit,
no buffer, unanswered, overflowed), the pane only printed a loss banner and
an idle full-screen program that writes diffs kept its pre-disconnect frame
until the user resized.

The two places a remote pane declares it has no host image now ask the
program to repaint through one choke point: a one-row viewport claim and
back, which every TUI honours (Node's tty ignores a same-size SIGWINCH).
It respects a phone or another desktop holding the size, skips hidden
panes, and puts the row back even if the pane unmounts mid-nudge.

Fixes #26887

* test(e2e): idle full-screen program frame after a managed SSH outage

* test(e2e): wait for the pane before typing in the idle-frame spec

* fix(remote-terminal): pick the redraw height on the host's clamped grid

* fix(remote-terminal): wait for the host's grid before putting the redraw row back

* test: give transport mocks a connection status for the redraw gate

* test(e2e): keep the orcad idle-frame proxy typed for the e2e ratchet

* fix(remote-terminal): let a new PTY's redraw supersede a swapped-out nudge

* test: keep new transport-mock lines free of type assertions

* ci(e2e): route the managed-SSH idle-frame spec to its sources
2026-10-21 02:48:21 -07:00
Neil b3e6705337 fix(orcad-migration): a sidebar view setting never blocks moving an SSH host to its server (#31374)
* fix(orcad-migration): a sidebar view preference never blocks moving an SSH host to its server

* fix(orcad-migration): keep repo filters on the desktop, keep later acknowledgements; e2e repro
2026-10-20 21:15:56 -07:00
Jinwoo Hong ce71535bbf Layout groundwork (switched off): main starts every split and new pane, the window only attaches (#31258)
* feat(terminal): the window's store shows main's pane start status (layout-owner build)

A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.

* refactor(terminal): keep the frame sync within its line budget

* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)

* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)

Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.

* test(terminal): iterate store listeners directly

* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)

* test(terminal): a pane main started attaches to main's terminal at its own grid

* test(terminal): detach census tests assert main's move and frame with the switch on

* test(terminal): exited-pane tests assert main's close with the switch on

These 8 have failed switch-on since #31130 moved the exit close to main.

* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)

The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.

* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path

* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused

Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.

* fix(terminal): a window split main cannot start still shows its pane, failed, with why

* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status

Three test stores gain the pane-start field the window's store always has.

* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears

* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module

* test(terminal): a pane main wrote and the user closed mid-start drops its failed status

* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report

* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab

* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner
2026-10-21 00:14:06 -04:00
Jinwoo HongandE2E Test b99552cabc fix(runtime): a phone reads and saves Markdown tabs on a headless server (#31101)
* fix(runtime): a phone reads and saves Markdown tabs on a headless server

With no window to relay through, markdown.readTab and markdown.saveTab answered
renderer_unavailable. Main now reads the tab's file (or its saved draft) from disk and
saves with a version compare plus the disk write precondition, in the shapes the
window's editor relay returns. With a window attached the relay is unchanged.

* Headless Markdown save: keep the draft's disk baseline, honour read-only tabs, re-find the record to clear

* Use the disk baseline only for a draft

* ci: run the phone Markdown spec's orcad case in the orca serve mode-switch job

* Headless Markdown: answer from disk only; a window's saved draft stays its view state

Clearing a saved draft wrote workspace layout from main, which no layout command carries.
Main now never shows or edits a draft; the desktop's restore check reports a disk change.

* Phone Markdown: one path through main and the disk, window or not

A paired server's workspace is read and saved on that server with files.read /
files.writeIfUnchanged, so it answers from its own disk and keeps the precondition.
The window relay (ui:mobileMarkdownRequest and the renderer bridge) is removed.

* e2e: a phone saves a Markdown tab the desktop holds unsaved edits in

The phone reads the disk with the tab marked dirty; its save lands on disk, and the
desktop keeps its draft and shows the changed-on-disk banner.

* Group the Markdown disk host's imports

* Drop an unneeded store cast from the dirty-readers test

* Phone Markdown: refuse a save on a server too old to check the disk; cover the untitled tab

A server before #30605 has no files.writeIfUnchanged, so the save is refused with the
existing update-required message rather than written unchecked.

* Phone Markdown: refuse a file an SSH host holds outside the workspace; pin the untitled answer

* Phone Markdown: untitled tabs stay read-only; older servers save like the desktop editor; a closed-source preview still reads

- The desktop lists an untitled document's tab with isUntitled (optional, additive); main answers
  it read-only with unsupported_untitled and refuses its save before touching the disk.
- A server without the files.write.disk-precondition capability gets the desktop editor's guarded
  save: compare from here, then a plain files.write, or a conflict. Every call of a save is pinned
  to the pairing it probed.
- A preview whose source tab was closed has no saved record; it reads its own file, read-only.

* e2e: read the untitled tab's answer without a cast

* Phone Markdown: pin a save's pairing before its first read

A save captures the server's pairing before it reads, and every call of the save, the read
included, is pinned to it, so a re-pair between the read and the write is refused.

* Test: with no window, a phone saves an existing untitled.md and never creates a deleted one

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 23:08:47 -04:00
Jinwoo Hong 065e6873f6 A paired desktop shows the held-terminal banner and Reopen for a server's held terminals (switch-on builds only) (#31275)
* A paired desktop shows a layout-owner server's held panes on the existing banner; Reopen asks the server (switch-on builds only)

The server's frames carry status 'held' on a pane a newer build's terminal service may still
run. The desktop now lists those panes on its existing newer-build banner, with the same wording
and states. Reopen calls the server's new `layout.reopenHeldPanes` under the server's ids: the
server stops its newer service, as the desktop's own Reopen does, and starts each pane still
held. The desktop starts nothing; the server's next frames clear the status and bind the pane.
A refusal or failure shows the banner's failed state.

Served and advertised (`layout.reopenHeldPanes.v1`) only by a layout-owner build, to paired
desktops (not phones). A server without the capability, or that publishes no status, shows no
banner, as today.

* test(layout): held server pane stays Reopening until the reattach frame, stays failed after a refusal, and shows nothing without the reopen capability

* fix(layout): a held pane's Reopen acts only on its own host and on main's published status (switch-on builds only)

- Reopen needs the clicked pane's key; a pane that already left the banner's store does nothing,
  so a server banner clicked as its frame drops the pane never stops this machine's newer service.
- Local versus server is decided by the pane's host, not by whether it has a release.
- The banner portal for a pane with no terminal handle exists only in a layout-owner build.
- The server reopens only panes its published layout shows held, the status the desktop saw.

* test(layout): Reopen ignores a pane rebound to a new terminal whose held entry is still stored

* test(layout): the dropped-pane Reopen test checks for the reopen method, since the view report shares the RPC client
2026-10-20 22:20:05 -04:00
Jinwoo HongandE2E Test 893c4e24a4 fix(ssh): an old SSH host moves to its managed server while a relay shell named only on its tab still runs (#31093)
* fix(ssh): an old SSH host converts while a relay shell named only on its tab still runs

The conversion check counted legacy pane-key aliases as terminals waiting to
reconnect. An alias only routes hook events to a pane whose terminal the lease
check, the bridge or the terminal gate already accounts for, so it no longer
blocks the move.

* Tighten the alias census comment

* Register the tab-only relay spec: typed deployment lookup, relay fence baseline, PR route

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 14:18:04 -04:00
Jinwoo HongandE2E Test b887483e55 fix(sidebar): the phone reads projects and project groups from main's sidebar feed, installed with the rows (#31102)
* fix(sidebar): the phone reads projects and project groups from main's sidebar feed, installed with the rows

The phone built its project sections from a catalog the desktop window published through a
side channel (mobileRelay.projectSections). That copy changed on a timer after the event that told
the phone to re-read it, and a re-read asked for during a read in flight was dropped, so the phone
could keep an old grouping (a project under the wrong group, a group missing) indefinitely.

Main now publishes the catalog (repos with their group and order, project groups, projects and
host setups, every host's including paired servers') as a projectCatalog domain of the sidebar
feed, from its own store and its own reads of each paired server. The phone reads it from the same
install as the rows. mobileRelay.projectSections stays, answered from the same builder, for pages
that predate the domain; a page on a desktop without the domain still reads it, now re-reading
when an invalidation lands mid-read. The phone e2e list helper runs the page's real refresh
lifecycle instead of re-reading on every assertion, and covers a group move while a read is held.

* test(mobile): stand AppState in for the screen hooks' react-native mocks, and tolerate a world with no project rows in the parity catalog

* fix(sidebar): main files a server's copy of a grouped project under its group, and reads a server's catalog only on catalog events

- The window gives an ungrouped paired-server repo the group of an existing copy of the same project
  (applyInheritedProjectGroups). That function now lives in src/shared and main's catalog builder
  folds each server's repos through it, so the phone files the copy where the desktop does.
- A paired server's change watch now says whether the event can carry a catalog change. Only
  ready, a replay and reposChanged mark the catalog stale; worktreesChanged (pins, unread, sort
  stamps) reads rows only.
- The parity fixture now builds the server side from raw server answers, folded through the
  window's mergeFetchedReposForHost and through main's own server read and builder, with a server
  copy of a grouped project and a partial saved manual order. It fails without the inheritance.

* fix(sidebar): parse a paired server's catalog rows with a schema instead of reading keys reflectively

* fix(mobile): sync the feed ref after render and re-read queued project sections without an await loop

* fix(mobile): spell the AppState shape the refresh reads, so the e2e helper pulls in no React Native types

* test(sidebar): give the merged builder test's feed domains a project catalog

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 09:34:02 -04:00
Neil 49a2203b0e Remote Orca Servers: one list instead of six sections; SSH Hosts: bulk remove that stays removed (#31082)
* feat(settings): one-list Remote Orca Servers page and bulk-manageable SSH Hosts

Remote Orca Servers: replace the three mode cards, Advanced fold, duplicate
managed/SSH-access lists and troubleshooting accordion with a single list
(one row per server: status, one action, overflow menu), an always-visible
'New projects open on' row, and Share / Cloud VM cards that open dialogs.

SSH Hosts: group by source, filter, checkbox + shift-range selection, one
confirm for bulk remove (skipping hosts with a managed server or
workspaces), bulk Test. 'Sync ~/.ssh/config' no longer clears removal
tombstones; 'Restore removed hosts (n)' is a separate confirmed action.

* fix(settings): address review findings on servers and SSH hosts redesign

- SSH filter stays clearable when the list shrinks under the threshold
- Cmd/Ctrl-A only selects hosts when pressed in the list itself
- group legacy no-source hosts with main's import predicate (now shared)
- capture the shift-range anchor before queuing the selection update
- blank server names de-duplicate instead of colliding
- Share dialog Done refreshes link count and resets name editing
- Install / SSH-route dialogs reload SSH hosts when opened
- default-host row shows the selector when the default is a WSL host
- managed 'Not running' no longer masks connecting/revoked/update states
- servers without a dropped transport can be re-checked

* test(e2e): WSL host spec matches repo.rm ending a removed project's shells

repo.rm now stops the project's terminals on the host (#29964), so the
'orphan left by older builds' setup can no longer be built; use a shell
only the host knows of instead. Resolve the project path so macOS
/private/var matches what the host reports.

* fix(settings): carry 'Orca on <host>' naming and paired-twice warning into the new server list

Ports #31099 and #31100 into the redesigned rows, install dialog and SSH
host dialogs.

* test(e2e): keep the WSL spec's project path as created
2026-10-20 05:22:39 -07:00
Jinwoo HongandE2E Test a2e0f20b1e Main sends one layout update per action, with only what changed (dark, layout-owner build only) (#31109)
* A respawned terminal's pane rebinds in one write, and the window shows a binding-only frame without a rebuild (dark, layout-owner build only)

Main: when the window's spawn for a restored pane finds its process gone, the spawn that holds
the pane's reservation keeps the dead binding and its fresh bind swaps it (the restart swap), so
the pane is never published unbound: a same-id respawn publishes no frame, a new id one. A failed
spawn still retires the dead binding. Other callers and window-owned builds retire early as before.

Window: a frame that differs from the shown one only in panes' terminal bindings updates the
store's pane trees as a rebuild would, falling back to a rebuild for holds and shared terminals.

* Dead-owner respawn tests expect the layout-owner build's one swapped bind instead of the early retire

* Main publishes one records frame per layout operation, and the window resubscribes on a missed frame (dark, layout-owner build only)

Main: a durable mutation (a layout.apply, the renameTabs batch, a binding write, a repair) is
one operation. It names the partitions it wrote, and at its end the layout stream publishes
once: one `records` frame holding only the changed records (tab rows, workspace groups, a pane
tree per tab, a binding record per pane, editor files, browser tabs, removed workspaces), each
keyed by main's own ids. Writes no operation scopes (window saves) still publish when their
task ends. The snapshot and every records frame carry a stream epoch. Each pane's binding
carries `ptyGeneration`, which rises on every binding write in this process, so a same-id
respawn is one frame with one binding record. Readers beside the stream get the end of each
operation and the stream's own per-partition loaded model (`workspaceLayoutReads`), so they
need no second Loader pass, with or without a window.

Window: the cache applies records frames to its full layouts, so the existing flush and the
binding-only path are unchanged. A records frame of another epoch or out of sequence, a
malformed frame, or one the cache cannot apply resubscribes at once for a fresh snapshot.

Tests install main's model check, which applies each frame to what subscribers held and
compares it with a fresh projection; dev builds log a mismatch, release builds run none.

* E1 review fixes: keep a swapped binding, bound the window's resyncs, guard operation listeners (dark, layout-owner build only)

- A spawn whose swap bind persisted no longer retires that pane's binding when a later commit
  step fails; a pane on an SSH host keeps the early retire, since its dead owner lives there.
- The window resubscribes at once at most three times before a frame applies cleanly, then takes
  the loss path's backoff. A skipped frame of a newer type still advances its position.
- A throwing operation listener is logged; it no longer fails a committed mutation.
- Tests freeze the shared partition model a reader gets; the dev-build model check skips frames
  that only change live titles. Generation and model-check comments say what is true.

* Keep the layout stream's backoff reset after a single resync; only a resync storm holds it

* A layout stream that recovers from a resync storm reopens on the normal delay again

* Move the replaced pane owner into its own module so the stable owner no longer imports its replacement (dark, layout-owner build only)

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 08:19:53 -04:00
Neil e278bc9a17 feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit (#30894)
* feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit

* fix(orcad-migration): refuse clashing drafts from the rename's own collection; stricter live spec

* fix(orcad-migration): stamp renamed drafts with the server, rename at startup after a cut-off commit, keep draft refusals

* fix(orcad-migration): retain only after a startup rename is durable; startup survives an unreadable journal

* fix(orcad-migration): a cut-off commit is renamed and retained at startup, so the rename runs once

* fix(orcad-migration): one retain per migration at a time; no rollback that could drop newer edits

* test(e2e): check the visible draft in whichever editor a large file opens in

* fix(orcad-migration): startup retains only migrations of hosts this profile owns

* ci(e2e): route the editor-drafts migration spec to the sources it covers
2026-10-19 22:44:43 -07:00
Jinwoo Hong b358328b7f test(e2e): pane-close specs close panes the way the user does and check main's copy (#30745)
The pane-close consistency spec closed panes with a bare PaneManager.closePane, the
drawing-layer call that never asks main. In a layout-owner build that close reached main
only because killing the pane's terminal made main retire the pane; right after a reveal
the terminal has not attached yet, so main kept the pane. Every user close path already
sends the close to main first.

The helpers now close through the pane close request the menu and shortcut use, and every
scenario also checks that main's stored panes match the window's.
2026-10-19 10:32:01 -04:00
Neil 9a46695f9f test(e2e): mount-authority spec polls through the host's "can't answer yet" terminal list (#30743)
* test(e2e): mount-authority polls retry the host's "can't answer yet" terminal list

First-mount adoption briefly makes terminal.list refuse with
terminal_surface_ownership_unavailable. Thrown out of an expect.poll
callback, that ended the poll instead of retrying it, so the spec failed
whenever a slow runner's probe landed in that window.

* test(e2e): say the refused read returns undefined
2026-10-19 07:29:03 -07:00
Jinwoo HongandClaude 36913fd4f5 test(e2e): a phone lists the desktop's managed SSH workspace as the desktop sidebar does (switch on) (#30718)
* test(e2e): a phone lists the desktop's managed SSH workspace as the desktop sidebar does (switch on)

* fix(sidebar): key folder workspace rows by host in the shared builder so the phone matches the desktop

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

* test(sidebar): pin the SSH folder row key encoding

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

* test(e2e): route the SSH phone feed list spec from its sources

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 09:19:39 -04:00
Kelvin Amoaba 162e07cf97 fix(browser): refuse agent clicks while the user is mid-IME composition (#30358) 2026-10-19 11:45:29 +00:00
Jinwoo HongandClaude 78bad24d33 test(e2e): nine sidebar specs create workspaces and links through main so they pass with the switch on (#30667)
* test(e2e): create reveal-spec workspaces through main so they pass with the sidebar switch on

The agent-send picker and host-section reveal specs injected fake repos and
workspaces into the window store. With ORCA_SIDEBAR_INPUTS=main the sidebar
lists main's rows, so those workspaces never appeared. The SSH host is now an
unreachable target seeded into the stopped profile, and the paired-server host
is a real headless orca serve host; pins, lineage and notes go through main.

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

* test(e2e): type the seeded hosts as execution host ids

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

* test(e2e): poll the reveal specs' page setup instead of awaiting it across CDP

An evaluate that awaits a preload promise sometimes fails with "Resulting
promise was garbage collected" in switch-on runs. evaluatePolled runs the work
in the page, polls its outcome, and still fails on a rejected or failed write.

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

* test(e2e): parent reorder spec creates its workspaces and links through main

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

* test(e2e): lineage specs create their workspaces and links through main

seedLineageScenario names and orders the two seeded workspaces and links them
with assignWorktreeParent. Extra families and surrounding rows come from a
fresh repo with real worktrees; pins and stale instances are main writes.

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

* test(e2e): default-branch and downward-drag specs create their workspaces through main

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

* test(e2e): reveal and active-delete specs create workspaces through main

The 60-row reveal, the folder-workspace reveal and both active-delete tests
now use real workspaces and folder workspaces added through main, ordered with
metadata writes. The deletes go through the real removal path, so the row
removal sampler waits by time rather than a frame count.

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

* test(e2e): address review on the switch-on spec follow-up

writeWorkspaceMeta now fails unless main lists the written values; the sleep
test's parent is a real worktree instead of a store-faked one; the legacy
inline-lineage case stays window-only and is skipped with the switch on;
the parent-reorder collapse key is built from the row's host.

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

* test(e2e): read-back reports an unlisted workspace and compares values structurally

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

* test(e2e): wait for the added repo before showing its worktrees

updateRepo returns false without writing when the store does not list the repo
yet. On CI the add's own refresh superseded the helper's fetch, so the visibility
write was skipped and every discovered worktree stayed hidden. The helper now
waits until the store lists the repo and fails if the write is refused.

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

* test(e2e): name the repo wait in its failure message

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

* test(e2e): size the setup poll budget to the number of main writes

evaluatePolled waited a fixed 10 s, sized for one write. Linking 53 children takes
about 8 s locally and 36 folder workspaces about 4.5 s, so CI ran past it ("page
work never settled"). Bulk setup now passes a budget of 10 s plus 500 ms per write.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 06:29:17 -04:00
Jinwoo HongandClaude 0c3807e73b test(e2e): reveal specs create workspaces through main so they pass with the sidebar switch on (#30642)
* test(e2e): create reveal-spec workspaces through main so they pass with the sidebar switch on

The agent-send picker and host-section reveal specs injected fake repos and
workspaces into the window store. With ORCA_SIDEBAR_INPUTS=main the sidebar
lists main's rows, so those workspaces never appeared. The SSH host is now an
unreachable target seeded into the stopped profile, and the paired-server host
is a real headless orca serve host; pins, lineage and notes go through main.

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

* test(e2e): type the seeded hosts as execution host ids

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

* test(e2e): poll the reveal specs' page setup instead of awaiting it across CDP

An evaluate that awaits a preload promise sometimes fails with "Resulting
promise was garbage collected" in switch-on runs. evaluatePolled runs the work
in the page, polls its outcome, and still fails on a rejected or failed write.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 05:10:40 -04:00
NeilandClaude 6328a104bd refactor(ssh): split ssh-connection-utils into concretely named modules (#30488)
* refactor(ssh): split ssh-connection-utils into concretely named modules

- classifiers -> ssh-reconnect-error-classification.ts
- retry/timeout constants and sleep -> connection/ssh-connection-timing.ts
- buildConnectConfig -> connection/ssh-connect-config.ts
- credential callbacks -> connection/ssh-connection-callbacks.ts
- exec options and abort error -> connection/ssh-exec-options.ts
- wrapRemoteCommandForPosixShell -> ssh-posix-command-wrapper.ts
- shellEscape and shared/wsl-login-shell-command quotePosixShell (identical) -> shared/shell-argument-quoting.ts quotePosixShell

No re-exports are left behind.

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

* fix: point the WSL git benchmark and the WSL lane route at shell-argument-quoting

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 20:48:05 -07:00