mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
d2d61ba0c01d5addf051316dedc2b038cb2881e7
62
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
c0df84f6b7 |
fix(extensions): bound outstanding native-host writes (#35362)
* fix(extensions): bound outstanding native-host writes * fix(extensions): evict the largest stalled native host and allow two queued frames per host --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
0eb18d9775 | fix(preflight): isolate directory enumeration | ||
|
|
1510f46258 | test: run the WebSocket payload-lifetime test on Node's precise GC (#35388) | ||
|
|
6e2b245ac6 |
Recover brief Linear read failures and retain network causes (#35074)
* Recover brief Linear read transport failures * Preserve safe diagnostics across runtime Linear reads * Match existing attachment metadata in read contract * Use a checked runtime in Linear CLI integration tests * Keep Linear CLI integration coverage in the backend test graph * Use integration test ownership for Linear CLI recovery coverage * Observe empty Linear recovery traces before the first sink write |
||
|
|
a21f79dee4 |
Fix copying from Codex started manually in remote shells (#34715)
* fix(terminal): forward copies from Codex started in remote shells * test(terminal): capture PowerShell copy probe exit status * fix(terminal): preserve configured shell arguments for remote profiles * test(terminal): isolate profile result from system startup output * test(terminal): run native daemon shell probes with Node * fix(terminal): keep an inherited remote-viewer marker out of local panes An Orca started from a remote viewer's pane inherits ORCA_REMOTE_VIEWER_CLIPBOARD, which would make every local Codex fake an SSH session. Treat the marker as per-spawn identity so only explicit launch env carries it. |
||
|
|
0323d28498 |
test: remove 49 low-value test cases (#34678)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
7a9abe48f3 | refactor: group remote runtime client modules (#34133) | ||
|
|
cc3ca668d8 |
test: remove low-value predicates, inventories and helper echoes (#33778)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
883b768bc7 |
A chat never starts writing a conversation a terminal is still writing, and restored panes respect open chats (#30930)
* fix(native-chat): one ownership index with link roles, and a chat refuses to start while a terminal writes its conversation Each chat link now carries its role (head or earlier) and key; Session History names the chat continuing a conversation first, duplicate adoption checks only head links in both the pre-commit check and the store transaction, and terminal resumes stay refused exactly as before. The terminal-writer check now also runs inside acquireOwner before the spawn, refusing with the typed pre-spawn reason conversationOpenInTerminal. * fix(native-chat): hold a terminal start Orca issues until its terminal reports it An in-memory registry of managed terminal starts per conversation, registered in the same step as the resume admission (terminal.send with a resume command, createTerminal with a command), bound to the PTY execution that runs it, and ended only when that terminal's hooks report the conversation or the execution is gone. A chat's agent start refuses while an entry may hold its conversation. * fix(terminal): an automatic pane restore never resumes a conversation an open chat continues Resume recipes built from a pane's saved facts (cold restore, deferred and late reattach, sleeping wake, host mobile materialization) carry resumeOrigin 'saved'. The execution host decides before any command is issued: while an open chat continues the conversation, or a chat may be writing it, the pane starts its plain shell with the started-fresh notice and its saved claim is forgotten; with unreadable records only a saved restore is refused and its record kept. An admitted resume registers its managed start in the same step, so the chat's agent start refuses until the terminal reports it or exits. Clearing the last saved record now reaches disk. * Fix saved terminal resume ownership and effective launch results * Preserve conversation case and clear refused cold restore identity * fix: publish effective terminal launch results on attachment * fix: preserve terminal launch callback and candidate ownership * fix: keep existing terminal attachments observational * fix: consolidate terminal launch imports * test: document snapshot runtime fixture boundary * fix: preserve terminal launch identity types * fix: accept reattach payload policy sessions * fix: preserve terminal result validation contracts * refactor: separate terminal create contract * test: document partial terminal runtime fixtures * test: align sleeping resume store fixture * Fix terminal create type contracts * fix: return the terminal main started to its waiting view * fix: register reattached terminals before publishing their handles * Check unavailable-resume test fixture types * Complete sidebar resume test store fixture * test: bound saved resume ownership and refusal work * Settle refused saved resume claims from actual launch results * Fix saved resume test type contracts * test(e2e): capture exact terminal parking diagnostics * test: verify parked terminal with fresh host inventory * Consolidate agent session imports in remote terminal transport * Clarify terminal conversation refusal guidance * Admit note-only saved conversations without hiding live writers * Retain terminal writer proofs through final admission |
||
|
|
cb82f94267 |
OpenCode and Grok chats show the turns made outside them (#31616)
* feat(native-chat): OpenCode and Grok chats show the turns made outside them When an OpenCode or Grok chat reopens its conversation, the agent replays its saved history before it answers. Orca used to drop all of it. Now the turns in it the chat never saw (made in the agent's own CLI, or anywhere else) are added once, in the agent's order, after the chat's rows and behind the existing "Continued outside this chat." divider. Turns are matched by the agent's own ids: Grok's prompt id (Orca's own turns carry the id Orca sent) and OpenCode's saved message ids, plus the rows of a transcript the chat was adopted from. A send of the chat's own that failed before any reply is matched by its text between the turns proven around it, because OpenCode never tells a client the id it gives a person's message. The import is translated by the same translator and assembler as a live turn into an in-memory overlay, then written as one settlement: a stale or failed import writes nothing, and it never fails the open. A turn imported part-way gets the rest of what the agent saved on a later open. * fix(native-chat): type the ACP catch-up staging and origin reads * refactor(native-chat): share the catch-up open runner across agents The Codex catch-up's agent-neutral parts move to one module: the open-time envelope (its own deadline and cancellation, never gating the open), the bounded staged batch, the single settlement commit that re-checks the journal before writing, and the "Continued outside this chat." divider. Codex keeps only how it reads its saved turns and what the chat already holds. No behavior change; the other agents' catch-up builds on this instead of copying it. * fix(acp): bound final catch-up journal barrier * chore: remove unrelated pnpm executable metadata * refactor(native-chat): the catch-up runner reads the chat's view only when the agent asks * refactor(acp): catch up through the shared history runner The open envelope, budget, divider and one checked commit now come from the shared catch-up runner; ACP keeps only how it reads the reopen replay and what the chat already holds. The final-barrier deadline test moves with the barrier to the runner. * chore: remove regenerated pnpm executable metadata * fix(native-chat): the catch-up deadline also bounds waiting for its commit to be written A journal write barrier that never settled after the import was admitted held the provider's open past the catch-up deadline. The wait now ends with the deadline or the open's cancellation, like every other step. * test: type the history catch-up sink fixture without assertions * fix(acp): reconcile imported history snapshots without claiming live work * fix(native-chat): bound and revoke history preparation * feat(native-chat): validate expected saved items during history catch-up * fix(acp): preserve user boundaries and visible catch-up dividers * fix(acp): replay ordered messages within provider executions * fix(acp): report replay overflow and refresh journal test reads * test(rpc): explain partial runtime fixture boundary * Bound ACP history reconciliation and rejected-copy projection work * test: use typed calls for history scaling instrumentation * test: account for history views and exit provenance work * fix: type history scaling test fixtures explicitly * test: route history probes to Node and bind replay sink before open * test(acp): explain journal scaling timeouts |
||
|
|
4dcd6af5df | test: run released-phone compatibility checks under Node (#33480) | ||
|
|
d81958f0e8 |
refactor: group local and WSL filesystem watchers (#33407)
* refactor: group local and WSL filesystem watchers * test: isolate startup ordering from watcher initialization * test: collect the startup preflight subject once |
||
|
|
e4bbbe045f |
perf(test): faster local test loop and cold-worktree changed runs (#33421)
- test:changed exits before starting Vitest when Git reports no changed files. - Unrecorded tests beyond 100 in scope use Vitest's static import graph instead of all running, so a fresh worktree's first changed run no longer runs the whole suite (patched RecordedModulesResolver; CI does not record, unchanged). - Bun's slow config load was the SQLite import scan statting every unit test; filtered runs now classify only the tests their filters can select, and the scan walks directories instead of fs.globSync. - Silence the native config loader notice on every run. - Zod's test bundle is content-addressed and reused, so a killed run leaks nothing; leaked per-run directories older than 6h are swept. - build:packages skips compiling when package inputs, compiler, and dist are unchanged; test scripts no longer nest pnpm invocations. |
||
|
|
9c5d789a42 | refactor: group cookie import orchestration and cleanup (#33314) | ||
|
|
5ee74f4f83 | refactor: group browser cookie source modules (#33250) | ||
|
|
4c2a2ac3d9 |
A Claude chat shows the turns made outside it (#31615)
* A Claude chat shows the turns made outside it When a Claude chat's agent next opens, turns written to the same conversation elsewhere (claude --resume in a terminal) are appended once, in order, under the existing "Continued outside this chat." divider. Streamed reply and thinking rows are now keyed by their source block (message id and block index), which Claude also saves on each transcript row, so the chat can tell which saved rows it already shows. The read goes back from the end of the transcript only to the chat's saved turn-end leaf, replays the rows through Claude's own translator in an isolated sink, and commits them in one settlement that writes nothing if the chat moved. Imported prompts are display-only and never settle a send; any failure opens the chat normally. * test(claude): name the transcript row shapes the catch-up test builds * fix(claude): read a catch-up row's uuid once as a string * fix(claude): parse transcript tail rows without type assertions * refactor(native-chat): share the catch-up open runner across agents The Codex catch-up's agent-neutral parts move to one module: the open-time envelope (its own deadline and cancellation, never gating the open), the bounded staged batch, the single settlement commit that re-checks the journal before writing, and the "Continued outside this chat." divider. Codex keeps only how it reads its saved turns and what the chat already holds. No behavior change; the other agents' catch-up builds on this instead of copying it. * refactor(claude): catch up through the shared history catch-up runner * chore: remove unrelated pnpm executable metadata * refactor(native-chat): the catch-up runner reads the chat's view only when the agent asks * chore: remove regenerated pnpm executable metadata * fix(native-chat): the catch-up deadline also bounds waiting for its commit to be written A journal write barrier that never settled after the import was admitted held the provider's open past the catch-up deadline. The wait now ends with the deadline or the open's cancellation, like every other step. * test: type the history catch-up sink fixture without assertions * fix(native-chat): bound and revoke history preparation * test(claude): expect source block identity in stream cadence * fix(claude): preserve final output and history coverage * feat(native-chat): validate expected saved items during history catch-up * fix(claude): integrate conditional history settlement * fix(claude): use new content and provider prompt identities for catch-up * fix(claude): retain qualified completed leaves through session exit * fix(claude): preserve unresolved membership for recovered follow-ups * test(claude): use fork epoch reason for rewind preservation * fix(claude): consolidate translator sink type imports * fix(claude): preserve source message times during catch-up * fix(claude): narrow staged history mutations to items * test: bound Claude retained-leaf catch-up work * fix(claude): settle imported work at source boundaries * test(claude): keep shared journal harness portable * test(mobile): await editor listener cleanup before snapshot * test: isolate retained slice measurements and accept closed socket EPIPE |
||
|
|
9c262ffe36 | refactor: colocate remote runtime connect bound (#33004) | ||
|
|
1cc2a2afd8 | refactor(shared): group remote runtime shared-control lifecycle (#32977) | ||
|
|
9a5da64fdd | refactor: group remote runtime requests and subscriptions (#32952) | ||
|
|
d10626876e |
Watch only the files Orca needs in its settings folder and .git, instead of the whole folders (#32410)
* perf(watch): stop watching all of userData and .git for a handful of files On macOS a directory fs.watch is a recursive FSEvents stream. Two watches used that to notice a few files: - The Active Server preference watched the whole userData directory for orca-environments.json, so Orca's own writes there (agent-hooks status, terminal history, browser caches; ~10 events/s measured) were routed back to it. - The shallow git metadata watch ran fs.watch on each repo's whole common .git dir (and .git/logs), receiving objects/, refs/, lfs/ and every linked worktree's churn to see six files. Both now watch the individual files (kqueue on macOS, no fseventsd) through a new rebinding file watch: events and a 30s sweep re-stat the path, rebind when a rename-replace or deletion moved it, and pick up files created later. Events are confirmed by a stat (size/mtime/ctime/inode) because macOS raises an attribute event for every mmap read of the index and packed-refs; the watch is one-shot with a backing-off window so a file every git process maps costs about one rebind a second instead of a wakeup per read. The registry path is unchanged, so CLI/serve readers and older peers are unaffected. Linux and Windows keep the non-recursive directory watch for shallow subscriptions. Co-Authored-By: Claude <noreply@anthropic.com> * fix(watch): don't hang unsubscribing an errored watcher; find late metadata files sooner - Node closes an errored fs.watch handle without emitting 'close', and a later close() is a no-op, so awaiting 'close' could hang a watcher-child unsubscribe and every subscribe queued behind it. Errored watchers are now dropped without a close, and closeFileSystemWatcher also settles on 'error' or after 1s. Applies to both the macOS file watch and the Linux/Windows directory watch. - Missing metadata files (packed-refs from pack-refs/gc) are re-statted every 2s instead of waiting for the 30s rebind sweep. - On macOS the delivery probe watches a file, matching the kqueue path the shallow subscriptions now use. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
c32d46850b |
fix: offload macOS deletion spawns and reduce branch cleanup (#32227)
* fix: reduce worktree deletion branch cleanup overhead Refresh only the remote base branches needed to prove a branch merged, with automatic maintenance disabled. Skip merge simulation when no common ancestor is available and preserve the branch when its remote base cannot be refreshed. * fix: preserve fetch mappings and live branch cleanup fallbacks * fix: spawn macOS worktree deletion commands off the main thread |
||
|
|
47484b13d6 |
fix(native-chat): keep queued messages in the chat's own record, so a refused one can't be skipped (#30418)
* fix(native-chat): a refused queued message still blocks the queue when its card write fails Every reader of a queued card goes through one derived view of the journal's dispatch facts, so a card whose message was refused reads as returned even when its card-table update and heal both failed. One decision and one writer store the derived state for every action. * refactor(native-chat): keep each queued message in the chat's journal, not a second table A queued message lived in its own SQL table, and a hook that may be skipped copied each refusal from the journal into it. When that write failed, a refused card vanished: the cards behind it sent, and its Send and Delete did nothing. Each queued message is now a `queue` row in the chat's journal beside the submission that hands it off and the dispatch row that answers it. The reducer folds the card from those rows, so a refusal returns the card and holds the cards behind it with nothing written, and replay reads the same. Every action writes its own rows in one transaction. Cards an earlier build stored are read through the same classification until they move: each action moves its own card, and a bulk import runs once per open. A new epoch restates every card still owed an answer in the transaction that deletes the old rows. The settlement hook and its savepoint, the open-time heal, the drain's heal and the derived-card view are gone. The published card list is unchanged. * test(native-chat): adapt the queue tests to cards kept in the journal Cards are written through the journal, so tests queue them with their body alone and hand them off under their real fingerprint. Tests of the removed settlement hook, heal and SQL store are deleted; the text-update spec now covers the edit decision alone, and a test of an earlier build's stored holds seeds the legacy table and reads the imported cards. Rolling over an epoch now keeps a card whose hand-off was never handed over, so that test checks the retried id never sends rather than that nothing sends. * test(native-chat): a refused card holds the queue through rewind, a new epoch and a restart One whole-flow test through the host: the refusal writes no card row, a rewind that retires the refused hand-off and a rolled-over epoch keep the card returned, a restart and Resume send nothing behind it, its own Send refused again still holds the queue, and its Delete releases the next card in order. * test(native-chat): an earlier build's queued cards move into the journal once, each read the same One mixed-import test through the host. The first open's import fails and moves nothing, yet every card reads as it will; Delete and Send each move only their own card. The next open moves the rest in one transaction, writing a refusal only where its hand-off is gone, nothing for answered cards, and leaving an unreadable row on disk. A later command turn spends a moved command card, a third open writes nothing, an old Delete replays from its receipt, and a chat with only answered legacy rows clears them in a delete-only transaction. * test(native-chat): adapt typechecked tests to cards kept in the journal The row-kind table lists a queue row, the clear and legacy-import tests match the current signatures, and the downgrade test keeps only the leftover-send case: a queued card is now a newer-version journal row an older build keeps read-only, and users don't downgrade. * refactor(native-chat): drop the journal row writer's unused transaction hook No caller passes one since the queue's savepoint bookkeeping left. * test(native-chat): downgrade probes write rows at the version older builds read The highest version this build reads is now 4, the queue row's. A newer build's row in these probes is written at version 3 so the older build meets its kind, not a newer version, and the Stop-event probe hands the older build the rows as stored, since a read raises each row's version. * test(native-chat): unit tests follow the queue row's version 4 The legacy-import test opens real SQLite, so it runs in the Node runtime project. The newest version this build reads is now 4, and a read raises a row's version to it, so the turn-lifecycle test no longer pins the in-memory version (the written one stays pinned by journalRowSchemaVersion). * fix(native-chat): one batch echoing two refused queued messages spends both The per-row delivery check returned only the first waiting card an echo proved delivered, so when one settlement carried echoes for two cards the second stayed waiting and was sent again. It now returns every card the row proves delivered, and the fold withdraws each; replay folds the same. * fix(native-chat): a rewind moves an earlier build's cards into the journal first While the open's import had failed, an earlier build's card that read as refused through its hand-off lost that reading when an in-place rewind retired the hand-off: it disappeared and the card behind it sent. The rewind now adopts every legacy card ahead of its tombstones and deletes their rows in its own transaction, as an epoch replacement already does, so the fold latches each card's reading before its hand-off is gone. * fix(native-chat): keep an earlier build's card whose hand-off is still unconfirmed The import dropped a legacy card handed off as a send whose delivery was unknown, though the fold keeps such a card linked and a later refusal returns it. The import now keeps every card whose hand-off a later row can still answer, through the same check the reducer folds dispatch rows by. * fix(native-chat): one delivery echo withdraws at most one queued card One provider echo proves one delivery, yet it withdrew every waiting card with the same text whose hand-off was refused, so a second identical card was lost. The fold now pairs each echo with one card: on its own row the earliest-positioned match no other echo of that row took, and for a card that waits again the earliest applied echo no card has taken. Paired echoes live in the fold's state, so live and replay pair the same; readers of cards not yet imported work on a copy. * fix(native-chat): a queued message handed to the agent is sent again only on proof it never got it Pressing Stop after Orca handed a queued or steered message to the agent, before the agent confirmed it, put the card back in the queue, and Resume or the next send sent it again. A check matching the agent's echo of the text was the only guard against a double send, and it kept trading a resend for a lost card. Whether the card waited again was decided by the rejection's category, not by proof the agent never got the message. A refused hand-off now waits again only where Orca proves the agent never received it: the host still held it, or whoever settled it recorded that. A failure still returns the card and holds the cards behind it. Anything else may have reached the agent, so the card is spent and its message reads as sent, derived on every fold from the stored rejection: with the turn it was answered into, as that turn's unconfirmed message where it was handed over; withdrawn before any turn opened, as a person's own send a Stop took back. The reopen mark ignores a recovered doubt, and the restart reconciler no longer judges a queued hand-off unsent from what history lacks. The echo text matching and its pairing are deleted. A close or quit while the agent starts settles sends with the unrun hold. The host hands nothing to a child before its start answers (structured-agent-session-delivery-loop.ts: a handover needs phase ready), so such a send went to an earlier child and may have run: it now reads as sent instead of waiting again. * fix(native-chat): Claude's report that it removed a queued message unrun lets the card wait again Claude says when it removed a send without running it: the interrupt receipt's cancelled list, or a command_lifecycle cancelled, discarded or refused frame for a command that never started. Every one settles through rejectClaudeDispatchWaiters, which now marks the settlement unrun. For a queued card's hand-off the host records that as proof the agent never got it, so the card waits again under the queue's pause. A direct send keeps the plain refusal: there the proof would owe it as stopped history. * fix(native-chat): an earlier build's card stopped after hand-over follows the same rule on upgrade Earlier builds put a card back to waiting after any refusal without a verdict and cleared its link, so a card the agent may already have would be sent again after the upgrade. The card's own hand-offs in the journal decide now: unchanged and last sent as one that may have reached the agent, it is spent on that hand-off and its message reads as sent; edited or sent again since, it is held for its own Send under a host-only marker that publishes as a plain hold; with no hand-off in doubt, including one that never left Orca, it waits as before. The import writes a card spent on a hand-off in doubt, so its message shows. * fix(native-chat): read a queued row's hand-off before the lookup that uses it The lookup's callback lost the narrowing of the row's change, which the mobile test typecheck rejects. * test(native-chat): a card steered into a turn a Stop ends stays sent in the Stop tests Three tests pinned the old rule, that a card handed into a running turn comes back to waiting when a Stop ends it. Under the new rule nothing proves the agent never got it, so the card is spent: with the turn named, its message reads as that turn's unconfirmed one; without, as a send the Stop took back. Neither is ever sent again. * fix(native-chat): a queued message stopped before Orca handed it to the agent waits again A Stop that aborts a hand-off after its row is written but before the agent call now records the withdrawal as never reaching the agent, for queued hand-offs only, so plan D's rule returns the card to waiting under the Stop's pause instead of spending it. Direct sends keep the plain withdrawal (the flag would owe them as stopped history). The agent's own refusal while aborted is kept rather than overwritten, and an abort thrown during the call stays in doubt. * fix(native-chat): a queued message stopped while the agent prepares it waits again The abort signal reaches only an adapter's preparation (its options, the previous turn opening), never its write, so the signal's own reason thrown back is the adapter saying nothing was written. The handover now reads that, for queued hand-offs only, as never reaching the agent, so the card waits again under the Stop's pause. Claude no longer wraps that abort as an option refusal, which had returned a stopped card as a refused one blocking the queue. A real refusal keeps its cause and its barrier; any other throw stays in doubt and is never resent. * test(native-chat): the usage-limit Continue backlog case writes its card through the journal queue Main's #30396 wrote its backlog card with the stored fingerprint and host-instance stamp the journal queue derives instead; the card the Continue must yield to is the same waiting card. * test(claude): the real-CLI queued Stop expects the unrun mark Claude's cancelled-unrun receipt carries * fix(native-chat): retain spent legacy messages when import fails |
||
|
|
2366072dc8 | refactor: colocate browser cookie validation (#32062) | ||
|
|
eba0124de9 |
[mem] Backpressure worker subprocess output without dropping bytes (#30698)
* fix(memory): backpressure worker subprocess output without dropping bytes * style(wsl): clear formatting gates without increasing line limits * test: cover Codex deadlines and shutdown under worker backpressure * test: synchronize backpressured Codex deadline fixture --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
818a25d430 |
test(vitest): derive the Node SQLite test list from imports (#30647)
* test(vitest): derive the Node SQLite test list from imports instead of a hand-kept list The two SQLite list files held ~511 test paths that every PR adding a SQLite test had to edit, which caused frequent merge conflicts. The vitest config now finds tests that directly import a SQLite module or harness when it loads, using the classifier the boundary test already had, with a modification-time cache so unchanged files skip parsing. The 42 tests that reach SQLite only through a shared helper or a dynamic import stay listed in the Node runtime list. * test(vitest): skip unused SQLite derivation and reject malformed cache entries * test(vitest): use typed test convention for cache contract |
||
|
|
f14475caa6 |
fix(browser): label popups from committed document origins (#30540)
* fix(browser): label popup chrome from committed documents * fix(browser): keep opaque popup documents neutral * test(browser): prepare real popup identity validation * test(browser): apply background policy in popup probes * test(browser): fix popup title and view-lifetime checks * fix(browser): ignore subframe failures in popup chrome * rm draft docs * fix(browser): finish popup probe teardown after signal exit * test(browser): use typed popup probe window access * fix(browser): prevent automatic popup title updates * commit * fix test |
||
|
|
53191250e8 |
Accept messages while any agent starts, and bound each startup step (#28046)
* Finish structured agent readiness before delivering held work * Publish the Codex process before protocol startup * Publish ACP agent processes before session startup * Publish Pi at spawn and settle signed-out starts once * Adapt permission fixtures to startup lifecycle * Keep model discovery for chats before their first start * Use one host readiness promise for provider operations * Await provider opening in the execution host * Await Claude initialization in host-owned open * Observe Claude readiness through the host status * Keep optional readback behind host readiness * Recheck launch permission after host readiness * Preserve the awaited child during permission revalidation * Await Codex protocol open under host ownership * Await ACP protocol open in the host * Derive ACP opening from its protocol session facts * Exercise unanswered ACP options with an explicit silent route * Await Pi open and fail signed-out construction * Read Pi catalog facts from awaited opening * Keep the Pi fixture exit observer * Record failed owned opening before the first message * Abort a matching provider open when its child exits * Cover provider exit during an unanswered host open * Cover explicit failure facts on a host-stopped child * Capture message choices per accepted send and preserve picker state * Apply accepted Claude message choices at prompt handover * Apply captured message choices at provider dispatch * Bind native chat choices to accepted message dispatch * Reset Codex turn choices for each captured message * Resolve Codex message defaults from native facts * Align message choice and Pi default fixtures with provider dispatch * Align option fixtures with saved message choices * Match Codex message choices to the active turn origin * fix: let pending commands reach handover refusal * Abort pre-prompt message choice writes with host-owned wait * Verify message choices through provider input boundaries * Keep legacy mobile test hosts explicit about message choices * Keep startup waits cancellable and preserve operation order * Derive Codex startup exits from host phase * Open Codex protocol in background-task test fixtures * Keep ACP startup ownership until handshake completes * Run ACP opening catalog fixtures with Node SQLite * Verify saved ACP picks without live option writes * Keep ACP catalog bookkeeping from failing an options read * Restore signed-out restart and fail Pi startup faults * Run Pi opening catalog fixtures with Node SQLite * Preserve message choices across cold clients and kept cards * Remove the unused provider option restore ledger * Clarify launch choices and legacy option hydration * Remove remaining restore-ledger forwarding and assertions * Remove unused ACP and Pi restore bookkeeping * Keep live choices and delivery order while agents start * Drop retired startup revision from Codex opening * Keep live-option cancellation until message choices and clarify catalog reads * Hold ACP option replies in live-write cancellation regressions * Remove retired Pi startup revision and match saved-pick behavior * Check running choices and retained rewind messages after delivery integration * Apply startup picks and let parked messages release operations [skip ci] * Remove unused Codex opening report field [skip ci] * Keep ACP restoration facts local to the provider [skip ci] * Apply Pi startup picks after readiness and remove unused open facts [skip ci] * Cover goal changes during the held Codex message wait [skip ci] * Save a waiting startup pick after Stop ends its opening [skip ci] * Save choices when startup ends or a replacement is still opening * Check saved choices separately from Claude startup reports * Release scripted Codex startup before awaiting queued picks * Check the saved picker selection in per-message choice fixtures * Exercise current choices and eager client capabilities in runtime fixtures * Reuse runtime fixture reads and type the fake connection directly * Align startup fixtures and preserve observed Claude exits * Honor queued send authorization in Claude runtime fixtures * Open Codex protocol sessions before Stop fixture dispatch * Wait for the opened Codex child before runtime notifications * Keep repeated permission picks usable and align Codex fixtures * Validate pending picks against the live option state * Order startup picks before later messages and retain flow regressions * Keep reviewed lifecycle contracts while pruning duplicate tests * Save picks after a proven exit without requiring a child * Type the startup reply fixture against the Claude control contract * Keep Codex startup flow contracts and remove overlapping adapter tests * Restore the committed dependency lockfile after test tooling * Keep startup recovery flows and remove duplicate Pi handshake cases * Cover both Pi startup choices in the delivery flow * Match the Pi prompt entry recorded by the scripted wire * Honor the before-input callback in the startup reply fixture * Keep message choice flows and remove overlapping selector tests * Keep startup option menus independent and show the saved choice * Exercise startup picks across real menus and the runtime * Match startup picker fixture to saved message choices * Keep saved effort visible when picking a model during startup * Include the running-turn scope in repeated Stop fixture * Use the current attach contract in import fixtures * Keep the Codex journal translator within its line limit * Match ACP startup fixtures to saved picks and late Stop behavior * Check startup effort after saved picker choices are acknowledged * Complete Stop target types and keep the effort regression concise * Use requested picker values in the effort regression acknowledgement * Require owned provider spawn and one host-awaited open (from #28055) * Restore the live checks the startup cleanup's deleted tests pinned One assertion each, in the surviving suites: a stale-generation exit is ignored, a failed create lets its sink go, a readback before open is saved, an idle stop that fails is retried, a no-pick Pi start teaches its default, an older client's pre-spawn failure and packed answer, an undrivable chat stays readable, the resting slash menu stays on its own host, the main agent reads apart from subagents, restore publishes status, naming runs off the host's status feed, and a throwing logger never reaches the operation. Also corrects the reliability gate note that claimed the send-idempotency suite replaced the refusal-retry tests. * Read a rewind in doubt outside the chat's serialize and apply it in the commit The started settle and the attach's ready-child path read the provider's history first, then apply what it proves in a short commit that re-checks the rewind it read. * Accept messages while the agent starts: run the start outside the chat's serialize The chat's serialize was both where a send was admitted and where its agent started, so every slow step of a start (closing-child join, lease reconcile, recovery probes, resolvers, history read, spawn, sink drain, provider dispatch) held every later send. A start now registers in one short step before its first wait: its abort controller (which Stop, close and quit already reach outside the lock), its startup clock (the 10-minute ceiling now counts from here) and quit's drain. Every wait after runs outside the lock, raced with that signal, and every write is a commit that first re-checks the start is still the session's. A start that loses the check is late: its spawn is closed through the post-acquisition failure path and nothing is handed to it. - The delivery loop's prepare returns a start handle; the loop awaits the start outside, then re-prepares. Handover writes the pending row and begins the dispatch abort handle in one commit, calls the provider outside after re-checking the child, and resolves in a second commit. - Ready operations claim a start instead of running one in their step. - A client attach runs the same steps under one serialize, as before. - An exit reported between spawn and publish is held for the publish commit, which ends the child it indexes. - An unproven close still fails only what was queued when its start was asked for. * Test that a send is accepted at every wait an agent start makes One whole-flow test holds the start at each wait in turn (closing-child join, reconcile probe, recovery re-probe, resolver, spawn, sink drain, open, started recovery, pending row, provider call): a second send is accepted at once, a Stop is recorded without waiting and nothing reaches the provider, and what was sent goes to the published child once, in order. Also quit before spawn, concurrent sends, a lease left recovering, and an idle sweep racing a send. * Bound each startup step on its own and drop the startup silence rule A start no longer ends after 60 s without output: output is not protocol progress. Each protocol step now has its own bound where its handshake lives, and one 10-minute ceiling from registration stays as the outer bound. - Codex: initialize 15 s and thread start/resume on the connection's 30 s default again, instead of the startup ceiling. - ACP: initialize 60 s, authenticate 120 s, session new/load/resume/fork 300 s; turns stay unbounded and option writes keep their caller's bound. - Claude: the initialize answer has a 60 s bound; past it the open fails with the ordinary startup-failure words and the host ends the child. - The onOutput plumbing that only fed the silence rule is removed. * Count Pi ready once it reports its state Pi's start no longer waits for its model listing and slash commands. The open returns after get_state and the unknown-provider check; the listing then arrives as an options report (with the catalog listing the start resolved) and the commands as a later publish. An empty model list no longer fails the start as signed out: a signed-out Pi runs the unknown provider, which still fails it, and a prompt it cannot run is refused with the sign-in words by the existing prompt path. get_state keeps the connection's 30 s request bound instead of the startup ceiling. * Test that model and effort picked while a chat is created ride its first message A slow host's create reply arrives after the user picked a model and effort. The picker's view layers the launch's held picks over its seed and the host's saved selection, and every send carries what the picker shows, so the first message carries the picks whether the host's status names its selection or not, and while the last pick is still on its way to the host. * Start a phone's new empty chat on its first message The phone's new chat is an agent.launch with no prompt, which always created the chat with its agent started, while desktop chats start on the first message. The phone now advertises the create-without-start capability, and a launch with no prompt from a caller advertising it creates the chat at rest, keyed in the ledger by its create intent as a client's create is. Launches with a prompt, the CLI (which advertises nothing), orchestration workers and ensure still start at once; an older host ignores the capability and stays eager. * Take main's lockfile; the branch changes no dependencies * Restore main's exact text in files the merge only reformatted The pre-commit formatter rewrote 37 of main's files staged by the merge, though this branch never changed them, pushing two past the line limit. Committed without the hook because the hook would reformat main's text again; CI lints the result. * Fit the startup tests to the owned-spawn, host-awaited-open contract Test doubles gain the mandatory open and an acquisition generation, the removed started event gives way to the rig's proveStart, and two literal types are tightened for CI's typecheck. A Stop during a held start writes no host note on this branch: the taken-back send draws its own stopped-before-start row, which the startup-attempt test now asserts. * Fit the remaining CI checks to the startup limits and current releases - Run agent-launch-at-rest.test.ts in the SQLite Node runtime project. - Pin the send-options cross-version case to v1.4.228, the first published release with agent-session.send-options.v1; v1.4.226 was never pushed. - Load the baseline's isStructuredTab from src/shared (moved in v1.4.228), falling back to the renderer path for older baselines. - End the never-proven start with a short startup ceiling: the silence limit it relied on is gone, and the ceiling counts from registration. * Read the option stamp from the chat's one host-execution selection * fix: preserve startup authority and accepted agent choices * Retain dispatch authority and reconcile accepted options * fix(native-chat): preserve accepted choices and cancellation authority * Restore established client selection flow * Preserve provider output observation and typed startup fixtures * fix: preserve initial Claude launch choices and effort readback * test: complete Claude catalog session fixture * Preserve incoming main files after merge formatting * Preserve compact handover through startup merge * Type instant startup fixtures before their open is supplied * Keep browser sign-in under its existing expiry owner * Finish the main merge: retired-model display, start rewind cut, new tests A model the Claude CLI retired is reported again, so the picker shows what the child runs and the next send moves the chat off it. The start reads a rewind in doubt against the handle it answered with, the record its commit then applies to. Main's tests follow the start's open: the admission fixture holds a real turn, and a sign-in close during setup fails the open. * Type the start's rewind record as the store returns it * Type the Sleep test's held open as the provider open it replaces * Sleep counts a chat stopped once the start it aborted leaves it at rest A start runs outside the chat's lane, so Sleep's step can run before the start it aborted has settled; it then re-reads the chat once that start is done, instead of reporting it unverified. * Keep Sleep's start-abort bookkeeping beside the rest of Sleep's stop |
||
|
|
da58ee0875 |
Session History: the Workspace tab stops re-reading every transcript to find its sessions (#30469)
* perf(session-history): remember each transcripts folder so Workspace scans stop re-reading files * refactor(session-history): keep the scope cwd table in its own module * test(session-history): type the hoisted read log without an assertion * test(session-history): type the collected session folders * chore(wsl-move): list the parse cache scope_cwds table * Keep scoped session selection current with cache authority * Consolidate scope cache imports in regression test * Run scope cache SQLite regression tests under Node --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai> Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
2db7d5aac4 |
Refuse chat resume until terminal writers have exited (#29242)
* fix(native-chat): identify the owner when resume create is refused * test(native-chat): consolidate transcript refusal cases * test(native-chat): keep ownership coverage at the launch boundary * test(native-chat): update adoption fixture for early ownership admission * test(native-chat): align resume admission assertions with host ownership * test(native-chat): keep RPC coverage focused on ownership proof * test(native-chat): assert blank request presence before optional source * fix(native-chat): admit the actual CLI source during chat creation * fix(native-chat): satisfy source admission lint checks * test(native-chat): provide the waiting hook prompt * test(native-chat): isolate source admission fixtures from host homes * test(native-chat): canonicalize adoption fixture directories * test(native-chat): preserve early ownership admission in source stack * Resume CLI conversations directly in native chat * fix(native-chat): preserve resume refusal behavior and honest transcript feedback * fix(native-chat): retain import read evidence in typed result * Use the header test fixture record type * Consolidate launch owner imports and correct feedback assertion * Keep existing resume translation in runtime catalog * fix(native-chat): preserve admitted source and prepared execution home * fix(chat): retain source evidence for dispatched adoption * fix(chat): keep source capture within module limits * Keep deferred chat publication in the shared launch owner * test(chat): allow host evidence in create routing * Satisfy launch publication checks and scope slot cleanup * Preserve canonical tab membership on projected reorders * Use recorded launch ownership for surface recovery * Reserve deferred chat order through existing group actions * Pass the active layout owner into launch cancellation * Stop terminal writer before adopting conversation in chat * Guard chat adoption completion and satisfy static checks * Switch completed terminal turns without confirmation and test handoff * Recheck terminal writers on reserved adoption retry * Split session entry delegates and fix linted test types * Keep chat opening send below create module line limit * Require explicit terminal stop consent before chat adoption * Use direct process-table test mock * Fix chat adoption types and runtime localization * Localize terminal takeover refusal guidance * Keep CLI stop admission scoped to active source panes * Preserve ordinary history resume through attach * fix(native-chat): admit an unsaved transcript under a linked account folder A missing transcript stayed unresolved while its account root resolved through symlinks, so containment failed and a not-yet-saved conversation showed a load error instead of the unsaved notice. * Update adoption replay test for at-rest creation * refactor(native-chat): move the newest-item import window into its own module Keeps journal-legacy-import.ts under the line limit after the source identity observer landed on top of the streaming import. * fix(native-chat): restore the refusal error import lost in the main merge * Remove duplicate account resolver and condense terminal admission * Refuse conversation adoption until terminal writers exit * Keep conversation inspection within lint limits * Preserve conversation owner in launch feedback * Expect unavailable writer scan to refuse fresh adoption * Keep terminal conversation evidence tied to its execution * Correct writer continuity test refusal and event shapes * Prove original terminal writers exited before chat adoption * Scope saved conversation holds to their execution host * Keep child-inspection fixture verdict literal * Recheck awake saved conversation holds against their owned pane * fix: inspect owned Windows jobs independently of foreground naming * test: use braces in Windows inspection fixture * test: complete ordinary Windows terminal inspection fixture * test: explain partial terminal inspection fixture * fix: inspect the terminal a conversation is switched out of * test: separate OpenCode writer observation coverage * fix: tie terminal conversation links to the PTY execution The conversations a terminal reported now live on its PTY record for that incarnation, so moving the pane or running many other terminals can no longer drop them, and a link whose terminal can't be inspected refuses. - Each link keeps the agent process its hooks last proved; a live or suspended process refuses even when the terminal's tree is shell-only (detached tmux, nohup). - tmux-published events are never pane-owned, and an unmarked binding copy no longer revokes an earlier pane-owned proof. - A restored row or saved checkpoint is censused against the pane's current local PTY instead of requiring this runtime's observed identity. - The refusal now says Orca can't confirm the agent has exited and asks the user to stop what's running there or close the terminal. * test: drop the stub-host admission calls from OpenCode observation coverage * test: census suspended children through the real terminal host * test: drop writer checks the real-terminal flow already covers * fix: let prompt workers outside the terminal's session pass the exit check - The POSIX shell-alone proof skips a session leader below the shell and everything under it: a prompt's zpty worker has left the terminal, while suspended, background, nohup and disowned jobs still refuse. - Windows steady-state polls read the cached process table; every read that decides adoption or close stays fresh. - The pane header and tab menu point back to Session History, since their action is gone once the agent exits. * fix(terminal): count only Windows job members still linked to the shell A pane's job keeps every process it ever started, including daemons whose parent exited (an rc-started ssh-agent), so such panes never read as an idle shell and the chat switch refused forever. Walk descendants of the shell along live parent links within the job, for every shell; the Git Bash launcher keeps its bash-chain allowance and a child older than its parent still refuses. * Revert "fix(terminal): count only Windows job members still linked to the shell" MSYS exec and fork leave intermediate Win32 parents exited, so a live Claude started through a script (and 'sleep 60 &') became unreachable and the chat switch admitted while it ran. Back to the job census until a console-based design is measured. * fix(terminal): set aside detached daemons on a Windows shell-alone decision A pane's job keeps every process ever started in it, so an rc-started ssh-agent made a Git Bash pane read as busy forever and the chat switch refused. MSYS fork/exec leaves live work under exited Win32 parents, so parent links alone can't tell the daemon apart; it is the one that also left the console. On a scanChildProcesses decision read (switch admission, run-terminal close, close guards), a member neither linked to the shell nor attached to its console no longer counts. Polls and shell confirmation keep the job census. The console-list helper now forks through the process-host chokepoint. * refactor(daemon): name the process inspection options once Keeps the daemon router and terminal host within their line budget now that the options carry scanChildProcesses. * fix(terminal): set aside detached daemons only for adoption, never for close Closing a Windows pane kills its whole job, so a close check that ignored a detached process (gitk &, cmd /c start ...) would kill it without asking. Adoption asks whether anything is still in the terminal; close asks whether closing destroys something. Only the adoption read now sends ignoreDetached; close guards and the run-terminal sweep keep the job census. The win32 test also stops the ssh-agent it starts. * fix(native-chat): check the terminal being switched out of before opening its chat When the conversation already had a native chat, the switch hit the ownership conflict first and the header opened that chat, even while the terminal's agent (a later claude --resume) was still running. The switched-out terminal is now checked before the conflict, so a live one refuses as usual and one at rest still opens the owning chat. * test(terminal): assert close reads never call an ssh-agent pane idle A plain read can answer unverifiable while the eval's short-lived processes exit; either way it must not read as no-children. * fix(native-chat): check terminal writers before naming an owning chat for any switch Session History's Resume in New Native Chat carries no source pane, so with an earlier chat still holding the conversation it skipped the terminal check and the owning chat's first send would start a second writer beside a live terminal Claude. Every adoption request now runs the writer check before the owner conflict, as the no-owner path already does. * test(reliability): run the chat-switch writer oracles in the provider-ownership gate |
||
|
|
d2bb60735b |
test(browser): document blocked popup compatibility gates (#30537)
* test(browser): expose native popup admission blockers with faithful app events * test(browser): probe Electron popup identity and inherited sandbox API limits * test(browser): handle requests without associated contents in popup probe * docs(browser): record blocked Electron popup compatibility gates and test routing * docs(browser): keep popup evidence in PR |
||
|
|
1938bd055a |
refactor(ipc): group SSH and runtime-environment IPC handlers into ipc/ssh/ and ipc/runtime-environments/ (#30362)
git mv plus import, vi.mock and path-literal rewrites; e2e route regexes and the Windows host lane filter follow the moved handlers. |
||
|
|
2d1ec53533 |
refactor(ssh): fence the legacy relay in ssh/legacy-relay/ and drop the relay name from live deploy primitives (#30292)
Moves legacy-relay-bridge/ plus the relay-only census and endpoint files into ssh/legacy-relay/, so the 2027-01-15 retirement deletes one folder. Live install, exec and framing modules that only carried the relay name become remote-*. git mv plus import, path-literal, routing and workflow path-filter rewrites only; no remote file name or path changes. |
||
|
|
fc24ac3715 |
Window saves stop carrying tab and pane layout; main keeps its own (dark, layout-owner build only) (#30282)
* fix(layout): an SSH pane's terminal binds in its own session partition (dark, layout-owner build only) Main keeps its own layout over a window save, so a binding only a save carries is lost. A window or runtime spawn of an SSH pane bound in the local partition; it now binds where the pane's layout lives. (cherry picked from commit |
||
|
|
2c12c1edf1 |
refactor(runtime): move runtime/relay to runtime/mobile-cloud-relay (#30274)
The folder is the cloud relay that reaches phones; under its old name it read as the legacy SSH relay. git mv plus import and path-literal rewrites only. |
||
|
|
027eb0ec83 |
Session History: save the parse cache one row per transcript, so restarts re-read nothing (#29401)
* Persist session parse results beyond the memory cache limit * Expire Session History parse rows by idle time instead of a count cap The parse cache dropped its oldest row once it held 4,096, so a scan over more transcripts evicted its own upcoming hits and re-parsed every file on every scan. Rows now stay until no reader has used them for 24 hours, swept after each completed History scan and each search-index pass. Only the live parser state keeps a 4,096 count bound; dropping it costs one whole re-read of a file that changed, never a re-parse of one that did not. Removes the overflow directory and the orcad persistence wiring from the earlier revision of this change. * Expire only rows History lists, and keep the newest transcripts under bounds The search pass ran the idle sweep every 20 s but never re-stores History's unchanged rows, so with search on a day away from History dropped them all and reopening it re-parsed everything. Only a completed History scan sweeps now, and it expires only rows History listed (on a monotonic clock). Rows only the search index or a title lookup created keep main's 4,096 count bound, so an index build can't grow the scanner process without limit. Scans store newest transcripts first, so every store-order bound dropped the newest, most active files: the saved snapshot kept the oldest ~11k rows and the resume bound stripped the newest files' parser state. The snapshot is now ordered by transcript mtime, and both count bounds drop the tenth of their rows with the oldest transcripts. * Drop the 24 h idle sweep; History rows live for the process The sweep let whichever listing ran last decide when a row died, and listings differ in scope: a smaller one (the phone's Agent History, a lower desktop limit) expired rows only a larger one used, so the next Unlimited scan re-parsed them. Rows History lists now live as long as the scanner process; the snapshot's newest-transcripts cap bounds what survives a reload, and rows other readers made keep the 4,096 bound. Also clears the resume bookkeeping for a row the unlisted bound drops in the same store. * Skip resume bookkeeping for a row the unlisted bound just dropped Running the resume trim first let a row the unlisted trim then deleted push the resume set over its bound and cost 409 live resume points. * perf(ai-vault): store the Session History parse cache one row per transcript The parse cache was persisted as one JSON file, rewritten whole after any change and capped at 64 MB / 1M tokens (about 11k rows). Past that cap, a restart re-read the oldest unchanged transcripts, and an agent writing while History was open rewrote 10-27 MB every few seconds. The scanner child now keeps the rows in their own SQLite file. A miss in the in-memory map reads the row by path; changes are written per row, debounced and chunked; deletes run immediately. A used_at column, refreshed at most daily, expires rows nothing has used for 30 days, so rows for transcripts deleted outside Orca have a way to die. The old JSON file is imported once and removed. Electron main only records the path for the child. The rebuild-on-corruption open moves out of the search index into a shared versioned cache database helper used by both. * fix(ai-vault): import the old parse cache at open and expire rows only for missing transcripts Review round 1 on the per-row parse cache store: - The old JSON was imported only by a History scan or search pass, and only into an empty database, but deleted either way. Tab-title sync writes a row first, so the import was skipped and the first History open after updating re-read everything. The import now runs when the database opens, before any lookup, write or delete can reach it, keeping rows already present. - A 30-day last-use expiry also dropped rows for transcripts that still exist, so opening History less than monthly re-read them. Rows unparsed for 30 days are now checked on disk and dropped only when the transcript is gone, then the freed pages are reclaimed. The daily last-use rewrite goes away. - 8 KB pages (a ~2.3 KB row filled a 4 KB page), busy_timeout before the WAL switch, and lookups served from rows still waiting to be written. - Revert accidental pnpm-lock.yaml churn. * chore: revert accidental pnpm-lock.yaml churn * fix(ai-vault): never stat WSL transcripts from the parse cache recheck A stat on a \\wsl.localhost path outside the scanner's running-distro filter and gated WSL lane could boot a stopped distro or hang on a stuck one. WSL rows now skip the existence check and age out after 180 days unparsed. SQLite-backed session keys use the shared OpenCode path splitter. * fix(ai-vault): register the parse cache database with the WSL mover census Each schema keeps its own meta table again, so the shared open helper creates none and the search schema's SQL is unchanged. The parse cache's tables are recorded as not moved: a cache rebuilt from transcripts. * fix(ai-vault): a forced whole read never reuses a row a failed database delete left behind |
||
|
|
be033c2ab2 |
Session History: stop re-reading unchanged transcripts past 4,096 files (#28821)
* Persist session parse results beyond the memory cache limit * Expire Session History parse rows by idle time instead of a count cap The parse cache dropped its oldest row once it held 4,096, so a scan over more transcripts evicted its own upcoming hits and re-parsed every file on every scan. Rows now stay until no reader has used them for 24 hours, swept after each completed History scan and each search-index pass. Only the live parser state keeps a 4,096 count bound; dropping it costs one whole re-read of a file that changed, never a re-parse of one that did not. Removes the overflow directory and the orcad persistence wiring from the earlier revision of this change. * Expire only rows History lists, and keep the newest transcripts under bounds The search pass ran the idle sweep every 20 s but never re-stores History's unchanged rows, so with search on a day away from History dropped them all and reopening it re-parsed everything. Only a completed History scan sweeps now, and it expires only rows History listed (on a monotonic clock). Rows only the search index or a title lookup created keep main's 4,096 count bound, so an index build can't grow the scanner process without limit. Scans store newest transcripts first, so every store-order bound dropped the newest, most active files: the saved snapshot kept the oldest ~11k rows and the resume bound stripped the newest files' parser state. The snapshot is now ordered by transcript mtime, and both count bounds drop the tenth of their rows with the oldest transcripts. * Drop the 24 h idle sweep; History rows live for the process The sweep let whichever listing ran last decide when a row died, and listings differ in scope: a smaller one (the phone's Agent History, a lower desktop limit) expired rows only a larger one used, so the next Unlimited scan re-parsed them. Rows History lists now live as long as the scanner process; the snapshot's newest-transcripts cap bounds what survives a reload, and rows other readers made keep the 4,096 bound. Also clears the resume bookkeeping for a row the unlisted bound drops in the same store. * Skip resume bookkeeping for a row the unlisted bound just dropped Running the resume trim first let a row the unlisted trim then deleted push the resume set over its bound and cost 409 live resume points. |
||
|
|
b16a71c7fb |
ci: reuse Windows hook builds and shorten test setup and shutdown (#29439)
* ci: remove Windows build and persistence test overhead * ci: collect unit phase diagnostics during PR checks * test(ci): seed hook reuse fixture from the canonical prebuild The hook reuse regression first built into a missing private --output, so it always reached Cargo (74s warm on Windows). Build through the default output instead, which reuses a warm canonical prebuild, then copy its executable and fingerprint into the private fixture root. * fix(lint): clear inherited max-lines and broad object tab parameter after main merge Combine the duplicate shared/execution-host imports in runtime-host-services (301 -> 300 lines) and type the overlap test's tab mapper with the RuntimeMobileSessionClientTab owner type instead of object. * fix(ci): align inherited runtime and workflow contracts * test: establish changed runtime probe signatures deterministically * test: await asynchronous daemon profile cleanup * test: shut down owned daemons before deleting profiles * test: clean up oracle daemons during final app close * test: recognize verified Linux zombie exit during daemon teardown * fix: preserve folder SSH ownership when project groups conflict |
||
|
|
dcdf3860e1 |
ci: share runtime test imports and Node 18 preparation (#29300)
* ci: share runtime test imports and Node 18 preparation * ci: run native plugin reconciliation on Node |
||
|
|
1a19671ec5 | test: remove low-value coverage and shorten expensive fixtures (#29135) | ||
|
|
81ed8c0feb |
feat(runtime-files): the host serves a chat's scratch folder as a file root (#28397)
* fix(files): never write through a dangling symlink A path that doesn't exist yet was authorized by walking up to the nearest existing folder. A dangling symlink fails realpath with ENOENT, so the walk stepped over it and approved the link's own path; the write then followed the link and created its target outside the allowed folders. The walk now continues from the link's target, and returns that real destination. * refactor(native-chat): rename the chat visuals folder to the chat scratch folder The per-chat folder agents write into is the chat's scratch space, not only a visuals folder. Its root becomes <state>/chat-scratch/<hash> (the hash is unchanged) and the agent variable becomes ORCA_CHAT_SCRATCH_DIR. The old ORCA_CHAT_VISUALS_DIR is never set and is still stripped from what every agent child inherits, Pi included. The visuals skill names the new variable and says the folder is temporary. * test(native-chat): keep the delivery test to the rename The launch parity snapshot already pins that every agent drops the pre-rename variable. * fix(files): refuse any dangling symlink in the missing-path walk Following a dangling link by hand resolved relative targets against the lexical parent, not the link's real folder: through a folder link the walk could loop forever or authorize the wrong file. Refuse instead: realpath can't say where a dangling link leads, but a write would follow it. * feat(runtime-files): the host serves a chat's scratch folder as a file root File commands accept `path:<this host's chat-scratch root>/<32 hex>` and resolve it only when it names this host's own folder, at the file-commands seam so the selector never reaches chat placement. They authorize against the project roots plus the chat-scratch root, refuse files.open/openDiff for the folder, and agentSession.readVisual's ok results carry the file's absolute path so a client can build the selector. * test(runtime-files): name the readVisual helper's parameter type * test(files): drop the loop case that duplicated the dangling-link refusal * test(runtime-files): run the readVisual RPC test, which opens a real SQLite store, under Node * test(runtime-files): put the same-suffix folder inside the authorized root so only the selector refuses it * test(runtime-files): wait for the stores to close before removing the state folder |
||
|
|
d3b17ca9f4 |
fix(runtime): save a windowless host's chat tabs in its layout (#28354)
* fix(runtime): save a windowless host's chat tabs in its layout Claude-Session: p2-headless * fix(runtime): remove a chat's saved tab on the index's hidden notice, which every close reaches Claude-Session: p2-headless * fix(runtime): a chat whose record is gone loses its saved tab at startup Claude-Session: p2-headless * test: run the windowless chat-layout host test in the Node runtime project Claude-Session: p2-headless * fix(runtime): remove a chat's saved tab from whichever partition still holds it Claude-Session: p2-headless * refactor(runtime): list saved chat tabs across partitions in the layout module Claude-Session: p2-headless * refactor(runtime): read saved chat tabs straight from the store's partitions Claude-Session: p2-headless * fix(chat-tabs): save a windowless chat tab under its own tab id Review pass: the saved tab reuses the id status and attention address, the close checks for a window before scanning partitions, and the host tests seed readable records with unreadable, other-host and no-index cases. Claude-Session: p2-headless * test(chat-tabs): a window opening a windowless-saved chat reuses its tab Claude-Session: p2-headless * test(chat-tabs): check the saved tab with the lookup a window reuses The window's existing-tab lookup moves to the shared projection so the windowless-layout test exercises it without importing the renderer. Claude-Session: p2-headless * test(chat-tabs): assert the reused tab's identity directly Drops the shared lookup added for the test; two functions already carry that name. Claude-Session: p2-headless * fix(chat-tabs): leave an unstamped chat tab in an SSH partition to that host Claude-Session: p2-headless * fix(chat-tabs): count an unstamped chat tab as this host's only in the local partition A desktop mirrors a remote server's chats into that server's runtime partition without a host stamp; the startup sweep must not delete them. Claude-Session: p2-headless * refactor(chat-tabs): one chat tab writer, one startup match, local partition only Chats run only on local hosts, so their saved tabs live in the local partition: the cross-partition listing and its writer go. Open and close share one layout load, the startup fill and sweep become one function beside the index seed, and the tab filter is the existing savedSessionId. Claude-Session: p2-headless * fix(chat-tabs): restore and sweep saved Claude chat tabs like any structured chat The Claude skip predates Claude structured chats (#18519, #18560); an id the host has no record of is already dropped by the readable restorer. Claude-Session: p2-headless |
||
|
|
d8458f14f5 |
Fix browser login popups and menu actions in native windows (#28474)
* fix(browser): preserve unnamed OAuth login popups * fix(browser): preserve form and blank popup sessions * Preserve native request context for Shift popup navigation * Validate route popups with production policy attachment order * Fix browser background modifier request continuation * Route opener-less OAuth popups to Orca tabs and fix menu targets in nati - Plain links and Cmd/Ctrl+Shift-clicks open OAuth URLs as Orca tabs instead of native windows. Those popups have no opener, so they lose nothing by reloading the URL. - Scripted popups that have an opener keep the existing origin-bar window, so their window.opener link still works. - Discard pending popup contents with a navigation veto before closing, so a link's first request cannot reach the server. - A POST with no page to hand over is still blocked, because reloading it by URL would drop its body. - Edit, Help, and paste menu actions now target the main Orca window when a native browser popup has focus. The popup has no Orca renderer to receive those IPC messages. * Cover opener-less OAuth popups and popup request provenance in tests - Add Electron tests showing opener-less OAuth links open as local tabs without loading the discarded page, and that modifier-opened POSTs are never replayed. - Add a test that a remote foreground modifier click keeps the native cross-site request context (cookies, referrer, fetch site). - Add a test that the first adopted link request is blocked when route preparation fails during popup creation. - Launch the CDP fixture through resolveElectronProbeLaunch so Linux runs use the display-aware launcher instead of a hardcoded xvfb-run. - Replace `as` casts in the popup tests with typed declarations. * Route Edit menu actions to native browser popups Copy, Paste, and Select All from the app menu did nothing when a native browser popup was focused. The popup has no Orca renderer to receive the IPC, so the action was dropped. Those actions now go to the focused web contents, falling back to the popup's own contents, so they reach the page the user is in. Also removes the popup request-provenance Electron test, which is no longer part of this change. * Fix static analysis failures in browser popup handling * Handle unavailable permission probe in browser popup fixture * Admit script-opened native popups and enforce minimum size Modifier-clicked `window.open` children from page scripts reach Electron after the opener's first task, so the one-shot admission lapsed and they were left unprepared. Their first request then lacked popup context, and the origin title could be hidden by a hostile page title. - Keep a per-opener pending admission that answers the next native child the opener reports. Unadmitted native children are discarded rather than left as bare windows. - Route popup children through the contents whose window-open handler answered, since Electron reports script-opened children to that contents. - Create native popups content-sized with a 360x200 minimum, matching origin-bar popups, so `resizeTo` cannot shrink the origin title out of view. The limits move to `popup-window-minimum-size.ts` so both paths share them. - Tests: cover scripted admission timing, unadmitted discard, minimum size, and route popup caps. Fixture waits for hidden iframe loads and polls with a longer timeout for CI. * Prime hidden Linux OOPIF hit testing for all opaque popups The popup compatibility test clicked a frame gesture target only for the "route" case when the input was opaque. Hidden Linux OOPIFs need that priming click before hit testing works, and the route's root gesture gate depends on it too. Other opaque cases were skipping it and could fail for the same reason. Apply the priming click to every opaque case and correct the comment to describe the real reason. |
||
|
|
efbe3210d6 |
perf(session-history): keep a timed-out scan's finished transcripts (#27937)
* perf(session-history): save finished transcripts while a scan runs, not only after it succeeds * test(session-history): check the mid-scan save without relying on a cancel race * fix: avoid cache checkpoints for unchanged scan batches * fix: checkpoint committed session cache changes * fix(ai-vault): retry failed in-flight cache save on flush |
||
|
|
2233fd3669 |
feat(native-chat): each Claude/Codex chat gets its own permission mode (#26116)
Each native Claude and Codex chat now has its own permission mode (Ask for approval, Accept edits for Claude, Approve for me, Full access), shown and changed from a button right after "+" in the composer. A change takes effect before the next message: the agent restarts quietly and resumes the same conversation, never interrupting a reply or background work. New chats start from Settings → Chat → Permissions, which is seeded once from Agent Permissions. The mode is stored as an ordinary chat option, and the restart reuses the existing "replace the agent before the next message" step. |
||
|
|
de5470a900 |
fix(rate-limits): start the Codex usage probe off the main thread so focus doesn't freeze the UI (#27707)
* fix(rate-limits): spawn the Codex usage probe off the main thread On Windows, creating a cold standalone codex.exe can spend seconds in code integrity checks on the calling thread. The usage probe runs on window focus, so that froze the UI. The probe's child is now created and piped on a worker thread and proxied back to the main thread. Fixes #27362 * refactor(rate-limits): type the worker's children through @orca/process-host * fix(rate-limits): kill an orphaned Codex probe by pid when its worker dies Once the worker thread that owns the probe's child is gone, kill() can no longer reach the child through it. The proxy now falls back to a pid-addressed kill; the Codex probe uses the app-server tree kill so a node launcher's codex.exe child goes too, while SIGTERM stays a graceful root signal. * refactor(build): move the main build's entry map out of electron.vite.config.ts The new worker entry pushed electron.vite.config.ts past the 300-line limit. The Rollup input map for the main build now lives in its own module. * refactor(rate-limits): drop the orphan pid kill and trim the worker child-process stand-in A worker thread's death closes its children's pipes, so an abandoned Codex probe already gets stdin EOF, its graceful stop. The pid-addressed orphan kill was redundant with that and with terminateCodexProbeChild's own gated Windows tree kill. Its hard kill also bypassed the tree-kill gate's recycled-pid refusal by turning the handle-addressed fallback into a pid kill. - Remove orphan-pid-kill, killOrphanedCodexProbe, the injected killOrphan, and the orphaned flag. kill() now reports false once a child is no longer tracked. - Process-creation failure is one 'spawn-error' event that the stand-in expands into error/exit/close, replacing the duplicated triple and the exported exit code. Stdin errors arrive as child errors, as the probe already treated them. - Fold both suites into one over a real MessageChannel and real children. Add a real-worker test showing that terminating or crashing the worker lets the child exit on stdin EOF. * test(rate-limits): run the worker child-process suite under Node The suite asserts Node behavior that Electron main relies on: a failed spawn closes with a null signal, and a dead worker thread closes its children's pipes. Bun reports an undefined signal and, on Linux, leaves the pipes open, so the Bun lane failed on runtime differences, not on the product code. * test(rate-limits): count a reaped-by-nobody zombie as an exited child On Linux and macOS a child whose worker thread died gets stdin EOF and exits, but libuv reaps a child only through the handle on the loop that spawned it. Once that loop is gone, nothing waits on the pid, so it stays a zombie and kill(pid, 0) still succeeds. The real-worker tests now treat a zombie as exited, and still require the child's EOF marker. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
d4d12120c6 |
[CI] Advance test clocks and preserve cleanup checks (#27791)
* [CI] Remove wall-clock waits and stabilize GC fairness check * [CI] Include the final GC timer gap in the fairness check * [CI] Advance test clocks and preserve cleanup checks |
||
|
|
57452c681f |
Fix cold proxy authentication in the desktop HTTP client (#26957)
* fix(network): answer authenticated proxy challenges on cold desktop requests * fix(network): destroy a stalled proxy-fetch body on cancel --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
ff5c284cc1 |
feat(mobile): relay a paired phone's server-workspace requests through the desktop (hidden) (#26435)
* feat(runtime): let a paired desktop register its phones as delegated mobile devices A desktop paired to this runtime can now call pairing.delegatedMobileDevice.sync to make each of its phones an ordinary mobile-scope device it owns, so the phone mirrors that host through the desktop. Children are never pending, inherit the parent's reach, and die with the parent's grant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(runtime): move the mobile device revoke primitive below dispatch Dispatch binds the caller-bound delegated-device context, so the revoke path it needs now lives in a layer under it as a pure move; this drops the abstract method seam. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): keep a runtime grant until its delegated phones are revoked A failed child cleanup now leaves the parent in place so a retry can still find the children, and revokeRuntimeAccess lives beside the revoke it cascades to. Adds restart, rename and bounds coverage, shares credential minting, and drops the timing-dependent census. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(runtime): fail a delegated sync whose dropped phone cannot be revoked Matches the parent revoke: a child whose cleanup could not be saved is kept and the sync errors before minting anything, so the desktop retries instead of believing the server matches its list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): relay a paired phone's server-workspace requests through the desktop A phone mirrors its desktop, including workspaces on the desktop's configured servers. The desktop now forwards a request that names `executionHost: runtime:<env>` to that server over one socket per (phone, server), signed in as the phone's delegated device; replies pass through untouched except stream ids. Hidden until the phone client ships: the relay capability is not advertised yet. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the desktop relay contract in its entry module main dropped docs/reference, so the relay's contract lives beside the code. Also pins that a server whose delegated sync fails (e.g. delegated_device_revoke_failed) is unavailable until a later request syncs it again. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin the relay's configured-server adapter The production hosts adapter was only typechecked; this pins that it resolves configured servers by exact id only (never a name or a removed server), fences by pairing revision and runtime identity, and stops reporting retirements once unsubscribed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): tighten the desktop relay after elegance review Reuses the shared E2EE socket opener, folds relay state into fewer fields, never sends a request whose link ended while opening, passes failure codes through, relays only this desktop's own phones, and drops the unused host-state reader until S2 needs one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): bound relay frames and stop sticky relay verdicts Restores the 8 MiB frame cap on every remote runtime socket, re-checks an update-needed server on each new link so an in-place upgrade relays, answers a phone the server just declined without syncing again, and ends a link whose mirrored capability update cannot be queued. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): run targeted desktop-owned calls on the desktop The phone tags every call on a workspace screen with its execution host, so a paired-desktop method carrying `runtime:<env>` now runs locally instead of failing 'forbidden'. The Mac stays the only reader of the routing table. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(ci): run the remote-runtime WebSocket opener test on Node Under the bun vitest project, `ws` resolves to Bun's built-in client, which ignores maxPayload, so the frame-cap test hung. The opener only ships on Node/Electron, so run its test in the Node runtime project like its siblings. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-relay): derive the phone allowlist from the routing table Adding a phone method now takes one edit (its route). A server-targeted call on a legacy non-E2EE socket is refused instead of running locally, and only execution-host methods enter the relay. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
2b4b60a87e |
refactor(relay): delete the SSH relay server, its build assets and tests (#27215)
* wip: delete the SSH relay server and its build, assets and tests * wip: drop main/relay plugin conformance; WSL reader lookup test * fix: drop the emptied relay bench; prune baselines * test: keep the native halves of tests that also covered the relay * chore(ci): drop shard timings for the deleted relay server tests * fix: keep the directory listing budget that darwin terminal names now import --------- Co-authored-by: m4air <m4air@Mac.localdomain> |