* feat(plugins): build-less TypeScript workers
A worker's main may be .ts/.mts. The plugin host installs module.registerHooks scoped to the
plugin folder: .ts files are transpiled with the frames' sucrase options (lines kept, inline
source map, source maps on), relative imports fall back to the frames' candidate files, and a
value import of the type-only 'orca/worker' fails with a clear error. Needs engines.orca
>=1.4.246.
* feat(plugins): components panels hot reload with their state
- In dev folders a save swaps only the hidden guest: keyed memory atoms are handed to the new
guest (ui.retain → ui.hydrate memory), dropped when an atom's version changes.
- A save that fails to load keeps the last good render and shows an inline file:line:col
error banner; the next good save clears it. A subtle Reloaded mark follows a reload.
- Transpile and import errors carry line and column; dev worker restarts keep storage.
* feat(plugins): orca plugin dev watch
One command for the inner loop: refreshes .orca/types, dev-loads the folder, follows its frame
and worker logs (plugins.getLogs gains an optional cursor), prints reloads (optional
devChangeCount on list entries) and status changes, and unloads on Ctrl-C. --json streams
NDJSON events for agents.
* feat(skills): orca-plugin-authoring bundled skill guide
A guide for agents to build Orca plugins end to end (decision table, build-less rules, the
dev watch loop, test/check, validate, release-entry, approvals, gotchas) with references
generated from the component catalog, host API table, event schemas, manifest schema and CLI
specs, checked for drift. Fixes the components template's Reset to set(count, 0).
* feat(plugins): orca plugin dev watch --check; test kit details in the authoring skill
- --check runs orca plugin check after the load and after each reload (one at a time; saves
made meanwhile fold into one rerun) and prints each panel's render or errors inline, as a
`check` event with --json. Local folders only, since it runs the plugin's code here.
- The orca-plugin-authoring guide's Test and check section now documents orca plugin test and
check from #36969 exactly (JSON results, exit codes, test placement, the orca/testing kit and
its common mistakes), and the loop uses dev watch --check.
* refactor(plugins): move the components renderer's timing and helper constants to their own module
* feat(plugins): orca/testing kit, `orca plugin test` and `orca plugin check`
- `orca/testing` mounts a components panel in plain Node (node:test, vitest, bun) with the real
runtime and Orca's own tree validator: find by text/role/label/testId, press/type/submit/toggle/
select that resolve once the panel settles, tree() snapshots, errors (with stacks) and warnings,
persisted state (with stored() for migrations), emit/setVisible/rerender, and unmount running
setup cleanup. It re-exports node:test and strict assert so tests type-check without @types/node.
- mockHost refuses what Orca refuses with the same codes (surface, capability from the manifest,
params, a gesture method without a press), checks configured results against the host API, and
records calls.
- `orca plugin test [dir]` discovers *.test.ts(x) and runs them with node:test through Node module
hooks that resolve `orca` like the guest's import map and transpile/resolve sources exactly as
Orca's build-less loader, or with the plugin's own vitest via a generated config.
- `orca plugin check [dir]` validates, then renders each components panel once and reports what it
threw (file:line), layouts Orca would refuse, and props it would drop.
- The kit, hooks and register entry ship minified beside orca-runtime.mjs and are unpacked from asar;
typings land in .orca/types/orca-testing. The components template and hello-tsx get sample tests.
- ui.error carries an optional stack; a new runtime starts every atom from its initial value; the
hydration fallback timer is cleared once hydrated.
* plugins: run plugin test/check on a system Node or Orca's own, and warn they are unsandboxed
* test(plugins): run the shipped example plugin tests from the unit suite
* fix(mobile): save iPhone library photos as JPEG named by their bytes
The iOS photo picker handed back the asset's HEIC bytes as-is, and the host
named every pasted image .png, so agents read a HEIC file as a PNG and failed.
Ask the picker for the compatible representation (JPEG), and name pasted
images by their signature so the extension always matches the bytes.
* fix(mobile): keep unknown pasted bytes on the .png name
A .heic name falls outside the shared image MIME table, so chat previews
and agent prompts would drop the image. The picker now sends JPEG, so
leave HEIC detection out.
* refactor(mobile): keep the pasted image extension list module-private
* fix(native-chat): stop async question Submit from queuing duplicate answers
When the agent is mid-turn, answering a Codex async question queues the
answer as an ordinary message. The card stayed up until a transcript row
retired it, which a queued message never posts until the turn ends, so
every Submit queued another copy. Hide the occurrence once its answer is
queued, on desktop and phone.
* fix(native-chat): steer async question answers into the running turn
An answer to the agent's own mid-turn question belongs to that turn.
Queuing it behind the follow-up setting held it until the turn ended,
after the agent had already proceeded without it. Send it with the
steer intent whenever the follow-up resolver would queue a plain send,
on desktop and phone. Stop and a waiting command still queue, and the
queued fallback still hides the card.
* fix(native-chat): hold async questions while a queued answer waits
Replace the mount-scoped hide on a queued answer with one derived from
the queue: a question stays hidden while a queued card quotes it, across
remounts, and returns if that card is deleted or edited away. Lock
Submit until the send settles, and keep a returned answer out of the
composer draft.
* fix(native-chat): keep structured session under the line cap
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(window): working background opacity and blur on macOS, Windows and Linux
One window material model (opaque / translucent / blurred): macOS vibrancy without transparent, Windows 11 22H2+ acrylic, Linux transparent windows. Terminals paint their translucent background once, the transparent WebGL frame clears before drawing (#6491), and Reduce Visual Effects drops modal/backdrop blur and honors prefers-reduced-transparency.
* perf(window): drop hidden macOS vibrancy while a theme image covers the window
* refactor(settings): move Reduce Visual Effects into its own setting and search files
* feat(plugins): build-less TS/TSX plugins, typings and authoring tools
- Plugin frames transpile .ts/.tsx/.mts/.jsx on load (sucrase on a shared worker thread with a
2 s budget, cached in main), resolve relative imports to exact files, and refuse bare
package imports by name. Components and html panel/editor entries may be .ts/.tsx.
- orca plugin types writes typings for the components orca module, window.orca, worker
activate(orca), host.call and events into .orca/types, from one host-typings generator.
- orca plugin create --template components; templates omit publisher and target 1.4.246.
- orca plugin release-entry, dev inspect and restart; validate:plugin checks named files.
- Guest console errors reach orca plugin logs; dev workers restart on edits; the dev watcher
ignores .git, .orca and node_modules; Git and folder installs cannot replace catalog installs.
- examples/plugins/hello-tsx, docs, and the plugin-buildless-tsx e2e spec.
* ci: run the Windows parity probes with one tsgo checker
tsconfig.node peaks near 22 GB with tsgo's default four checkers (each holds its own types)
and about 16 GB with one; the 16 GB Windows runner paged the native probe past its 900 s
timeout once this branch's package.json change triggered the job. One checker gives the
same diagnostics (verified locally with --parity-only); the timing benchmark is unchanged.
git.history gains optional branchOnly and skip params. A branch-only page
lists only commits the base lacks, with per-commit stats and whether the
upstream has each one, plus ahead/behind/unpushed counts. Older clients
send neither param and get the same reply as before; older hosts ignore
them, so clients detect support from the echoed branch and skip fields.
Mobile may now call git.remoteCommitUrl to open a commit on its host.
* Show child activity counts beside the existing children toggle
* Keep child activity counts inside the children control
* Reuse seeded settings in the child activity preview
* fix(ui): let toast actions work above a modal instead of looking clickable but dead
Radix modals dismiss/refocus on outside pointerdown, so the Toaster already swallowed
pointerdown inside toasts; it additionally swallowed click for every control except the
close button, making actions like 'Update skill' look live while doing nothing. Keep the
pointerdown shield (modal stays open, focus/input intact) and let the click run.
* test(e2e): keep a toast mounted and close Settings after the over-modal action
* fix(ui): default-deny toast actions above a modal, visibly disabled, with a modal-safe opt-in
Restores #32065's lock (close always works; actions that navigate or change settings
don't bypass the modal) and fixes the clickable-but-dead look: blocked controls get
aria-disabled, a 'Close the dialog to use this' title, and disabled styling, updated
live as modals open and close. Background/toast-local actions opt in via
modalSafeToastActionLabel or data-toast-modal-safe.
* fix(ui): return focus to the modal draft after a toast-opened dialog closes
A modal-safe action that opens a trigger-less dialog (Update skill) left focus on body
after close, minimize or Escape. captureDialogReturnFocus records the focused element
before opening and restores it via onCloseAutoFocus.
Also harden Dialog Escape: Radix arms a just-opened sibling dialog's Escape a render
late, so an early Escape closed the dialog beneath and dropped its draft. A dialog now
ignores Escape while a later-opened modal is open.
* fix(ui): hide toast actions above a modal instead of leaving them clickable-looking but dead
Replaces the modal-safe opt-in design. Main's close-only capture gate stays as is; a
single CSS rule hides every toast control except close while a modal overlay is open,
so the message and dismissal remain and actions return once the modal closes.
* fix(ui): type the sheet fixture's effect cleanup
* test(e2e): dismiss an older star-nag toast over the dialog instead of re-raising one id
* fix(ui): toasts sit behind a modal's backdrop until pressed or newly raised
Older toasts drop behind an open Dialog/Sheet backdrop, dimmed and inert, so nothing looks
clickable that isn't. A press on one, or a toast raised while the modal is open, brings the
stack forward; close and actions then work above the modal with its focus and draft intact.
Actions run that way are an escape hatch and emit toast_action_over_modal.
* test(e2e): read recorded telemetry without a type assertion
* test(e2e): read recorded telemetry through a typed global
* test(e2e): check the raised Later action once by mouse; keep #32065's per-pointer Dismiss as on main
* feat(themes): make Match Terminal a real app theme painted at :root
Match Terminal used to be an inline override on three left-column
elements, so the tab bar, right sidebar, status bar, titlebar, menus and
editors kept the built-in palette, and its fixed color mixes became
unreadable on low-contrast palettes.
It is now a light/dark app theme slot value synthesized from the active
terminal palette (read through resolveTerminalPalette, so each mode's
color overrides apply) and painted through the plugin theme pipeline
(derivation, contrast gate, startup cache, Monaco theme). The document
mode follows the terminal background, including onboarding's restore.
The legacy leftSidebarAppearanceMode='match-terminal' value fills the
empty slots on load and on writes (desktop and web), and it is kept
mirrored while a slot matches the terminal so older Orca still matches
its sidebar. The pickers mark the option with a muted "(dynamic)" label.
* fix(onboarding): sync the painted-theme ref in an effect, not during render
* feat(themes): use a lightning icon for Match Terminal's dynamic label
* perf(themes): reuse the Match Terminal theme when its content is unchanged
A switch over runtime RPC (the phone's Accounts screen) changed the
selection but never reached the window's running-session handling, which
only the status bar and Settings called after their own IPC switch. The
RPC path now reports the lane and outgoing/incoming account to the main
window, which runs the same handler. The web client handles its own
switches and opts out.
* feat(skills): auto-update unedited Orca skills; ask only for edited copies
Planner already hashes each installed SKILL.md against every shipped release
digest (legacy installs included). When a plan's only writes replace such
hash-verified copies, the freshness nudge now applies it silently. Edited
copies and repairs still go through the consent toast; edited copies now raise
it too instead of only showing attention in settings.
* feat(skills): decide silent skill updates per skill and per location
Add skills.autoUpdateBundledOrca (capability-gated): the host plans the named
installed skills under their locks and applies only hash-verified released-copy
updates, re-checking each location before writing. The nudge auto-updates each
unedited skill and names only edited, unknown or repair-needing skills; the
try-once guard is keyed per skill and observed state, so one failure falls back
to the toast for that skill alone.
* fix(skills): surface preserved edits, share run ownership, re-show retracted nudges
- A silent update that moved a racing edit to a backup now shows a warning
naming the skill and backup path instead of passing as success.
- Silent updates run under skill-update-run-store's per-target ownership, so
manual and automatic runs never interleave and the dialog shows busy.
- Programmatic toast retraction releases its session marker; only a user
dismissal suppresses the nudge.
* fix(skills): report stranded edits, isolate silent runs, bound attempt memory
- An old copy whose late edits cannot be backed up now fails the op with
strandedPath naming where the edits remain; the silent caller warns with that
path, and the next plan's backup cleanup is nudged, never deleted.
- Silent runs never start over a running or unacknowledged foreground result
and never surface in the status bar.
- Auto-update attempts are keyed per target and pruned to the states the
current scan observes; unchanged states stay suppressed.
* fix(skills): warn when a silent update's result is lost
Any rejected or timed-out auto-update request may follow a committed swap
whose racing edit went to a backup. Show a persistent warning naming the
target and its skill backup directory, and settle the attempt so the same
state is handed to the nudge instead of retried.
* test(skills): guard version skippers; ledger also walks release tags
Audit of every v* tag and HEAD history found no SKILL.md bytes missing from
released-digests.json. The generator now also walks release tags (falling back
to HEAD) so a release cut off-branch can never ship unrecognized bytes. Add a
real-disk test that installs the oldest shipped orchestration (v1.3.24-rc.2) in
the npx-skills and agent-folder layouts and auto-updates it silently.
* test(skills): type the nudge mock's error without an assertion
* chore(i18n): add the silent skill update warnings to the English catalog
* feat(telemetry): A/B holdout for the Chat UI launch tip
Freeze a 50/50 chat_ui_launch_tip_v1 cohort for profiles still due the tip on
official builds with telemetry not disabled; control never sees the tip. Emit
chat_ui_feature_tip_exposure in both arms and agent_default_surface_changed
from the tip and from Settings -> Agents.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(telemetry): hold the Chat UI launch tip until the telemetry banner resolves
Enroll only when consent is enabled. While the banner is pending, the tip is
skipped for that launch without being marked seen, so its exposure is not
spent unmeasured and the arm cannot influence the consent choice.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): document best-effort Chat UI tip exposure on rejected sends
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): pass the exposure test through a hoisted mock instead of in-test importActual
Co-Authored-By: Claude <noreply@anthropic.com>
* test(telemetry): justify the fake Store assertion in the telemetry harness
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(editor): choose a code editor theme independently of the app theme
* fix(editor): paint the diff overview ruler with the editor theme
* chore(i18n): add editor theme strings to the English catalog
* fix(editor): editor-theme settings search and Orca line tints in a separate editor theme
Searching 'editor theme' now opens the Interface section that holds the rows.
Diff comment-range bands and the conflict base-section tint use the editor
theme's line-number color, so they stay visible when the app's tokens don't
contrast with a differently based editor background.
* fix(editor): an editor-only theme paints the diff colors it would paint as the app theme
Main's diff tokens reach Monaco only where a theme painted them, and omitted tints
come from main.css fallbacks. An editor-only theme now reads its tokens through the
same fallbacks, so its diff colors match the ones it paints as the app theme.
The phone's liveness probe read a full status.get (~12 KB, ~2.3 KB gzipped)
and threw it away on every idle and foreground probe. It now sends
`status.get {probe: true}` and the desktop answers `{runtimeId}` alone,
skipping the machine-name wait. status.get keeps accepting any params, as
when it declared none; older desktops ignore the flag and answer the full
status, which still proves liveness.
terminal.list takes `includePreview: false`. The phone's session inventory
and automation pane lookup never read the 300-char output tail, so they ask
the desktop to drop it; older desktops strip the unknown key and keep
sending it.
Compressed terminal binary frames now use gzip level 1. On recorded-style
terminal output it keeps 93-99% of the level-6 saving (57-95% of bytes
saved) at 1.2-4x less main-thread CPU; text replies keep the default level,
where level 1 costs ~45% more bytes on the wire.
* fix(mobile): guard catalog snapshots against late replies and trim the snapshot paths
A slow unchanged or delta reply that predates a client switch (A→B→A)
could re-point the paired-server snapshot token at rows it no longer
matched; the next row delta then rebuilt onto the wrong base. Pending
reads now carry the token they asked with, and admit drops any non-full
reply whose token is no longer held.
Single-flight reads with one trailing join move into
WorktreeCatalogSnapshotClient.read(), so paired-server rows get the same
protection home had; home's own copy goes away. Server rows use one read
path for old and new desktops and drop snapshots for servers that leave.
The host screen asks for a first page only while nothing is shown and
the full read is not failing, and drops it on an invalid reply. The
shell no longer seeds the page with a status proven on a connection
that has since dropped.
Cleanup: drop exactActivity (unreleased; home only ranks by coarsened
activity and its exact rows defeated delta reuse), fold the legacy opt-in
check into the snapshot resolvers, remove unused worktree.ps result
types and a duplicate page subscription, and re-record the affected
RPC goldens.
* fix(mobile): keep server-row snapshots per client and skip first pages for loaded empty hosts
A shared per-host snapshot let a slow read on a replaced client reclaim
ownership from the new one, discarding the new client's reply. Snapshots
are now kept per client, and a delta rebuild retry runs only for the
current owner. An empty host that already loaded no longer asks for a
first page on every poll.
* fix(pi): preserve session titles after turns
* test(pi): preserve title batching coverage after synthesis opt-out
* test(pi): move title batching coverage to Hermes and drop duplicate headless test
Pi no longer synthesizes titles, so the renderer batching tests use Hermes,
which still does. The shared profile test and the runtime spec already pin
that Pi keeps its own title.
* test(pi): stop expecting a synthetic Pi ready title in typed-identity specs
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
* fix(wsl-host): a pane parked by a stopped host reopens once Start brings the host back
* fix(wsl-host): clear only the returned host's stopped refusal; test the strip and the bridge
The phone gave a direct socket 5 s from open to authenticated, while
the desktop keeps an unauthenticated socket for 10 s. On a slow link
(a handshake is two round trips, so ~2.5 s RTT is enough) the phone
dropped the socket the desktop would have accepted, then redialed into
the same deadline forever: the 'Can't connect' loop a test documented.
Both ends now share PAIRED_E2EE_HANDSHAKE_DEADLINE_MS (10 s). A link
that needs 6 s to authenticate now connects on the first dial; one that
never authenticates still escalates the same way, 5 s later. Relay
dials are unaffected: they keep their own stage budgets and race the
direct dial after 2.5 s.
The host screen re-read repo.list every minute and on every repo or SSH
event. Each reply carried every repo's uploaded icon inline as a data URL
(up to 400 KB each, barely compressible), although repos rarely change.
repo.list now takes an optional afterSnapshotId and answers unchanged
when the caller already holds the exact reply. The id is content-derived
(the hashing moves to content-snapshot-id.ts, shared with worktree.ps).
The phone keeps the last catalog per client, reuses it on unchanged
replies and still refreshes SSH host labels and health.
repo.list declared no params, so older hosts ignore the field and answer
the full catalog as before; callers that send nothing get the byte-exact
legacy reply. Icons still render from the same data, at the same size.
The host screen re-read every paired server's full worktree catalog on
each 3 s poll, through the desktop's mobileRelay.hosts.worktrees, even
when nothing changed: the same cost worktree.ps had before row deltas.
mobileRelay.hosts.worktrees now answers snapshot and row-delta reads with
the same resolver as worktree.ps (made generic over row type). The phone
keeps one WorktreeCatalogSnapshotClient per server, which now takes the
catalog request it snapshots, so server rows reuse the same token,
rebuild and copy-on-admit logic as desktop rows.
The params schema is strict, so the phone sends the snapshot fields only
to a desktop advertising mobile.relay-host-worktree-deltas.v1; older
desktops keep getting plain reads. Callers that send neither field, and
servers whose rows are not fetched yet, get the plain reply.
An app theme may declare one validated raster image (png/jpeg/webp, <=2 MB, >=640x360, inside the plugin) served through orca-plugin-image, painted as one static body layer under translucent terminals. Older Orca ignores the field; a bad image drops only the image.
* Agent message cards keep their sender's agent icon when the agent can't be found
A card names its sender by a snapshot written with the message, but drew the
icon only from a live lookup, so a sender the lookup missed (a worker on
another host, a terminal after restart, a closed agent) showed "?". Every
writer (worker start, mail, task, federated start) now saves the sender's
agent kind beside its name, and the card draws it when no live row names
the agent. Live rows stay the source of icon and status whenever they match.
* Preserve known worker agents on federated cards and mail
* Retain acquired worker launch identity for later sender snapshots
* Prune repeated worker sender contract checks
* Combine worker command contract projection imports
* Use the sender snapshot resolver in main's new receiving-account tests
* feat(themes): public tokens for diffs, git status, tabs, dividers, rows, links
Adds 29 public app theme tokens (diff, git decorations, tab bar, split
divider, current workspace row, host badge, chat bubble, text link), each
falling back to today's built-in color or CSS chain so existing themes and
built-ins render the same. Monaco's diff editor now paints from the diff
tokens, with sharper default diff tints (from #34698) and a readable dark
ignored-file color. The contrast gate learns one-sided drops, translucent
surfaces checked over their base, and main.css chains for half-set pairs.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(themes): check omitted diff tints, chat bubbles and dark host badges as painted
The contrast gate now derives an omitted diff tint from the theme's hue (and
drops the hue when its tint hides code), checks chat bubbles against chat's
mixed foreground on the chat canvas, and models the dark host badge at 80%
accent. The main.css fallback table moves to plugin-theme-css-fallbacks.ts.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(themes): blame a diff hue only for tints it painted, and check chat diff rows
A diff tint the theme set itself no longer drops the theme's hue when it
fails, and chat diff rows (hue-mixed text on the line tint over the chat
canvas) are now checked too.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(themes): new theme tokens only expose colors; unthemed surfaces paint exactly as before
Every new token now falls back to the exact value its surface used before:
- Diff hues default to the git decoration colors at the previous per-side
percentages, and chat diff text keeps its 70% mix.
- Monaco's diff view, overview ruler and links keep Monaco's stock colors
unless the theme painted a diff or link token. The colors go through the
derived Monaco theme instead of a global CSS override.
- --text-link has no :root default; each link rule keeps its old literal as
the fallback, including the markdown-light/-dark preview split and the
always-#58a6ff Monaco doc link.
- Tab bar, tab text and indicator fall back per element (bg-card,
text-foreground, ...) instead of through :root aliases.
- Dark --git-decoration-ignored goes back to #6e6e6e (#35910 ships separately).
The contrast gate skips checks that have no theme token to drop, so themes
that set only older tokens pay no extra probes.
* perf(themes): contrast check skips ring checks the theme didn't set and precomputes drop lists
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(editor): route editor typography through one resolver and add opt-in weight, line height, ligatures, size and macOS smoothing
Monaco sites built font options ad hoc and never received fontWeight,
fontLigatures or lineHeight; the editor size was tied to the terminal.
editor-typography.ts now feeds every Monaco and DOM code surface. New
optional settings default to today's rendering. A macOS-only Font
Smoothing setting rebuilds WebGL terminal renderers so the change
reaches their shared glyph atlases.
* fix: satisfy changed-code quality gates
* fix(terminal): cancel WebGL atlas warm-up on dispose so rebuilt renderers stop leaking canvases
The warm-up queue kept drawing into a disposed atlas, which re-appended its
scratch canvas to the terminal element. Every WebGL renderer rebuild (GPU
toggle, context-loss retry, font smoothing toggle) leaked one canvas per pane.
* feat(editor): read pixel and zero line heights like code editors and let markdown code follow an explicit editor font
* fix(tab-bar): new tab menu orders agents by default and agent order, not by Chat or CLI
Since the menu merged each agent's chat and CLI rows into one entry, entries
kept the rows' surface grouping, so an agent set to open as its CLI (Claude)
sank below every agent set to open as a chat.
* fix(tab-bar): refresh new tab menu order when the default agent changes
The picker inventory cache key left out the default agent, which now decides entry order, so changing the default with the same rows kept the old order.
* test(agent): expect the standard agent order in the shared-reads inventory test
The new tab menu now orders entries by default then standard agent order, so Claude is the first entry here.
* fix(native-chat): an open chat moves to the account you switch to before its next message
A running chat's agent read its account only when it started, and nothing compared it with the
account a start would use now, so open chats kept answering on the old account after a switch.
The agent a chat runs now records the account its launch used, and the host's replace-before-the-
next-send check replaces an idle one whose account is no longer what a start would use. A chat
mid-reply finishes on the old account first. Claude chats follow; agents whose chats keep the
account they were created on are never compared.
A chat's model-list reads and saves now use the account it runs on, so a moved chat no longer
writes its new account's list under its first account (STA-11281).
Tracked in STA-11211.
* fix(native-chat): declare the launch-account read for the runtime layer that wires the host
* fix(claude): find custom chat history before managed-account resume
* test(claude): assign the complete custom account home
* feat(native-chat): offer account switching and hand-off at usage limits
* fix: track the provider in account-list reload effects
* fix(native-chat): target recovery actions at the chat host and transcript
* fix(native-chat): verify account recovery for each chat on its host
* refactor(native-chat): retain the existing image-support result
* fix(native-chat): withdraw account recovery after failed eligibility reads
* fix(native-chat): leave unavailable recovery facts absent
* Align Japanese usage-limit recovery copy with catalog convention
* Preserve complete chat context through usage-limit recovery
* Keep Continue callback focus optional
* Keep recovery fixtures and account effects type safe
* Keep chat recovery on native sources and native accounts
* Validate native recovery files after path admission
* Use the optional cursor shape in recovery fixture
* fix(native-chat): exclude source CLI family from limit handoff
* Resolve recovery context in the owning workspace directory
* Preserve unknown account model facts and legacy option replay
* Clarify terminal account-switch feedback in translated locales
* Offer usage-limit Continue Now only once the limit resets
* Keep usage-limit reset timers current and bounded
* Keep option mutation plans checked against their contract
* Cover unknown model choices across chat consumers
* Keep consumer fixtures aligned with canonical types
* Match the mobile picker label in its consumer test
* fix: clarify terminal scope in translated Claude account settings
* Update integration tests for current session and remote client contracts
* Keep native worker replies on their receiving account
* Preserve receiving accounts across cold starts and nested reports
* Preserve the delivery receiver when reading chat commands
* Clarify native reply matching and consolidate imports
* Resolve catalog scope without re-entering conversation queues
Give owned option mutations and delivery settlement a resolved catalog input while external reads acquire scope once. Preserve account routing, catalog lifetime and current response obligations.
* Align catalog and mobile test fixtures with current contracts
Explain the existing timeout budgets and provide the idle turn identity and truthful model-only picker label in the mobile fixture.
* Align receiving-account readers and fixtures with typed contracts
* Repair account-switch fixtures and measure receiving work
* Use the checked account-home path in catalog tests
* Restore executed-option recorder context
* Control receiving catalog deadline test clock
* Align unknown-model consumer fixtures with send capture
* Consolidate session host type imports
* Align cross-version catalog fixtures with host read contracts
* test(native-chat): bind Runs through the current start authority in receiving fixtures
Main now resolves a worker start lazily through bindRun with objective and task references, and creating a Run no longer unbinds a coordinator's earlier Runs; run-use does. The receiving fixtures follow the same production owners so every original scenario, input and bound is exercised unchanged. The receiving runtime subclass moves verbatim into its own support module.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(native-chat): follow the selected account before its folder exists
A live Claude chat compared its account against the selected one only after that account's folder passed an availability check, so an account still being set up (or whose setup failed) left the chat silently answering on the previous account, and a resting chat refused its start before preparation could run. The retirement comparison and the start pre-read now ask the router for the selected route as identity, through the existing resolver purposes; catalog, create and probe reads keep checking availability. Preparation, typed launch failure and parking are unchanged.
Session History details show the account a chat was last launched under when the host recorded one, else its founding home.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(ai-vault): build the stale acquisition receipt without a type assertion
The History account case now reads the fixture receipt through the typed acquisition reader instead of spreading the record field through a cast; the three assertions and their inputs are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(native-chat): narrow the rig acquisition link before relinking it
The account-setup cases spread an optional link, which typed its id as possibly undefined; the rig always links a handle, so a guard that cannot fire narrows it. No assertion or input changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(native-chat): follow main's chat admission in fixtures and bound the History account projection
Chat admission no longer reads an experimental host setting, so the continuation-context test drops the spy main dropped in its own fixtures. The two History consumers that install a minimal chat host give their fake store the record accessor and host id the real store has, since the projection now reads the acquisition receipt. An operation-count contract holds the production History projection to one roster read and one record lookup per owned row at 10 and 100 rows.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(native-chat): give the declared-effort catalog fake its capture port
The declared-effort fixture builds a catalog service without the capture member the options reader asks every catalog for, which failed its desktop, catalog and phone consumers; it now supplies the inert test operation like the other fakes. The History projection contract also drops an unused map parameter.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Consume main's outcome change feed in the receiving-account index
Main's keyed journal records deliver each change after an epoch's frozen prefix as an
outcome sequence naming touched keys instead of typed rows. The receiving-account index
now re-reads those keys at their current values: queue cards by id, a deleted submission
retires its exact notice, and a deleted item drives the existing recovery rebuild, with
Stop marks still excluded. The in-process read row exposes the already-persisted
queue.cards keys and per-key deletion facts; itemIds and submissionIds keep listing every
touched key. The mobile unknown-consumer test mocks MobileSwitch as main's picker test does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test: validate historical usage-limit fixture before rendering
* fix: scope handoff choices and share account observation
* fix: align account menu lifetime and local projections
* test: preserve account observation contracts in quality checks
* fix: preserve typed account observation project coverage
* fix: restore handoff feedback for empty host inventory
* test: name exported handoff mocks with public types
* test: follow settings navigation consumption in feature tip adapter
* Keep usage-limit recovery actions beside their lead-in
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Every JSON reply and stream event reached the phone as base64 of sealed
UTF-8, a third larger than the JSON itself, and terminal snapshots (up to
512 KB per attach) went out uncompressed. Opening a host on a real desktop
moved ~2 MB, about 17 s on a 1 Mbps link.
Phones now advertise transport.compressed-frames.v1. For those sockets the
desktop gzips text frames of 1 KiB or more, and terminal frames of that
size, and seals them as binary frames tagged with a new kind byte, the
payload kind and the inflated length. The phone inflates them (native
gunzip when available) back into the same text or binary frame before
routing. Screencast frames are never compressed, and the relaying desktop
never offers the capability to paired servers, whose frames it rewrites.
On the same desktop, opening a host now moves ~180 KB.
Desktop CPU is unchanged: gzip then seal of the 1.5 MB catalog takes the
same time as seal then base64 of the plain one. Setting
ORCA_DISABLE_MOBILE_COMPRESSED_FRAMES=1 on a desktop turns compression off
without a release. Older phones never advertise the capability and older
desktops ignore it, so both keep plain frames.
* Open native chats on the phone in place
* Preserve desktop host and agent when opening chats
* Preserve chat opening and captured lifecycle contracts
* test: cover sender focus and unconfirmed chat close outcomes
* test: exercise structured worker CLI lifecycle through runtime socket
* test: use checked parked mail observation in CLI integration
* test: validate provider notification correlation identities
* test: construct checked lifecycle and navigation fixtures
* Keep cross-program session proofs in their test program
* Localize mobile chat opening feedback and complete runtime contracts
* Use the mocked text component identity in History feedback proof
* Temporarily expose grown E2E type diagnostics
* Keep E2E timer and fetch fixtures in their runtime domains
* Reconcile decreased E2E wait pattern counts
* Preserve live test clocks and expose remaining type diagnostics
* Keep mobile session data types independent of native views
* Complete structured session wire surface fixtures
* Expose existing grown E2E type diagnostics temporarily
* Preserve checked wire fixture tab mock signatures
* Preserve worker redrive when automatic close is refused
* Honor automatic close admission before settlement and retirement
* test: retain History reveal and close publication race
* Match automation reveal callback to activation result
* Align CLI integration fixture with current settings and output
* Prevent retained chats from reopening removed workspaces
* Import notification types directly from their owner
* Unify local removal fences for accepted worktree aliases
* Keep cross-surface authority tests in the combined test project
* Initialize coordinator mail test workspace ownership
* Register the retained automation chat test workspace
* Use checked inputs in worker start failure test
* Preserve the automation test repository host literal