* feat(dictation): optional AI cleanup of dictated text, with a correction word
Off by default. When on, a dictation's text goes through the user's agent
CLI (the AI text actions' runner, Haiku by default for Claude) before it
lands: the pill says "Cleaning up…" and the text is inserted once. Failure
or a 10 s timeout inserts it as heard with a warning; the pill's X skips
cleanup. A consent dialog asks before text first goes to an agent. The
correction word ("scratch that") survives filler removal for cleanup to act
on. The microphone test gets "Try cleanup", and phone dictation the computer
transcribes is cleaned on the host.
* fix(dictation): keep finals that arrive during cleanup and tighten cleanup edges
* fix(native-chat): let a chat continue on the account you switch to after a usage limit
A usage-limit reset belongs to the account that hit it. After a switch the
host kept holding the chat until the old account's reset, and desktop and
phone hid Continue until then, so Switch Account on the card did nothing.
The host now releases the hold once the chat would start on another
account, letting only that account's own reading hold it, and publishes the
earlier continue time that both clients read.
* fix(native-chat): save the account a usage limit hit, per cut
The fallback to the chat's founding account could report a switch that
never happened for a chat that had already moved, releasing it before the
new account's reset. Save the cut's account while the child that ran it is
live, use only that, and key the switch time by the cut it belongs to.
* fix(native-chat): record the account each chat runtime launched on
record.accountHome names only the founding account, so after a switch
nothing said which account a usage limit hit. Owner proof now records the
account each runtime fence launched under, every host cut carries its
turn's fence, and the hold reads the cut's account from that pair. This
replaces the sweep's saved copy, which a fast child exit could miss.
Spending the new account showed before a switch now holds the chat until
its own reset; only a reading taken after the switch releases it.
* fix(native-chat): restart the switch time when the chat moves to yet another account
A second switch for the same cut kept the first switch's time, so a reading
of the newer account taken before it was chosen could release the chat.
* Keep model picks independent of answer metadata
* Remove unused answer model extraction
* Verify model selection stays separate from answer metadata
* Keep the agent-chosen model as a display default
* Keep agent-chosen model text out of input selections
A reorder of equal-height rows moves them by transform and lays none of
them out, so the failure scroll's settle path kept the order from the last
layout and could scroll to the wrong row. The hook now takes the queue's
current order every render.
Follow-up to #41611 (Neil post-merge C1, F41611-C1-1).
* feat(mobile): floating answer card, queued messages above it
Port of stablyai/orca-private#724 (e1a794e6), plus:
- keep orca #40820's filled Steer pill on the lit head row
- let the open queue list shrink and scroll under a height limit so
the Show less row stays whole at 320pt on a short screen
* test(mobile): pin the short-screen queue shrink at narrow width and on the box
* fix(mobile): scroll a failed queued message into view and keep the lit Steer pill's press
- A failure that opens the queue scrolls its row into view, so on a short
screen the reason the list opened is visible above Show less.
- The pressed opacity comes after the lit fill on the Steer pill.
- Test the lit pill (orca #40820) and the failure scroll.
* fix(mobile): place a failed queued row only from its fresh height and let stale requests lapse
- The failure scroll waits for the failed row to lay out again, then settles
on the known heights after 300ms, so a stale height or an unresolved
request never moves the list later.
- A deferred show expires after 1s and on a queue change; the inline
editor's reveal scrolls at once again.
* refactor(mobile): keep the failure-scroll request as the queue list's own state
React Doctor reads a hook-returned setter called during render as a prop
callback; the list now owns the request with useState and hands it to the hook.
* fix(mobile): address review threads on the queued list
- Fold again for a later prompt: an open choice made in one placement no
longer comes back in the next.
- Scroll a message the person queues into a folded list into view through
the same deferred show as a failure.
- Lift from the row only in the plain presentation; a settings row keeps
its grip gesture.
* fix(mobile): let a choice made in the render a prompt arrives win over the placement reset
* fix(mobile): count an open inline editor's height when placing a later queued row
* test(mobile): note why the inline-editor test stub is cast
* fix(mobile): let the chat transcript scroll by touch on the phone's web pages
Gesture handler 3 (Expo 58 upgrade, #37935) gives a GestureDetector's view
touch-action: none on web by default. The chat transcript's pinch-to-zoom
detector sits on its scroller, so in the phone's WebView pages the browser
refused every touch pan and no chat could scroll. Gesture handler 2 left it
at auto.
The row list now wraps its own scroller in MobileNativeChatPinchZoom, which
keeps touch-action: auto. A browser render check pins the scroller's
touch-action under the detector.
* fix(mobile): keep the transcript pinch on its text size, not page zoom
touch-action: auto let a two-finger pinch on the transcript zoom the whole
page in the phone's web pages, so the transcript's text-size pinch never ran
and a zoomed page stopped the composer lifting over the keyboard.
pan-x pan-y keeps one-finger scrolling (and sideways code blocks) with the
browser and hands the pinch to the detector.
The render check now pans the transcript with one finger and pinches it
with two, asserting it scrolls, the text scale changes and the page stays
at scale 1.
* fix(agent-picker): stop listing Claude short-name rows beside the versioned model they run
* fix(agent-picker): hide a short name only on reported proof; keep hidden rows searchable
* fix(native-chat): keep a pending hidden model pick listed
* Hide projects from the sidebar without stopping them
Adds Repo.hiddenAt, a per-host, view-only flag. A hidden project's
workspaces, empty header and project-filter entry leave the desktop
sidebar, the paired phone's list, the new-workspace picker and the
palette's project filter. Its terminals, agents and automations keep
running, and repo.list / worktree.ps still list it.
- Hide Project in the project menu (toast with Undo), a collapsed
"N hidden projects" list under the workspace list with Show buttons,
and a Hide from sidebar switch in Settings > Projects.
- repo.setHidden RPC ({repo, repoHostId?, hidden} -> {repo, hostId,
hidden}) and `orca repo hide|unhide [--host]`; `repo list` marks
hidden rows.
- Opening a hidden project's workspace (notification, CLI, deep link)
focuses it without unhiding it, lifting sidebar filters, or leaving a
pending sidebar reveal.
- repo.update echo check: a host that drops hiddenAt reports failure
instead of reading as hidden.
* Address hidden-projects review: multi-host hide, picker scope, mute, stale filters, shared predicate, restore list styling
* Hidden projects re-review: keep saved filters, Agents filter, composer seed, per-toast Undo, shared default host
* Hidden projects: address review threads (pre-feed, palette search, notices, dialog default, seed remap, row keys, a11y)
* Hidden projects: no implicit dialog default when every project is hidden
* fix(mobile): exclude overlay permission and clear removed-host diagnostics
* fix(mobile): ignore late diagnostics for removed hosts
A closing client or a re-acquired open that fails with host-not-found could
append after removal and re-persist diagnostics for the removed host.
* fix(sidebar): keep the session name first on a monitoring agent row
Leading with "Monitoring background tasks" made every monitoring row in a
narrow sidebar truncate to the same text. The row's dot already draws the
monitoring glyph, so the status can trail the name like every other state.
* fix(agents): keep a monitoring child row's name first too
Sub-agent rows (desktop sidebar, chat strip and mobile) share one lead/trail
rule that also put "Monitoring background tasks" before the name.
* feat(agent-picker): fold legacy models the same way in every model picker
Claude aliases whose versioned model is listed, and Codex models superseded
in their family or by a newer major line, fold under Legacy models in the
agent launch picker, text-generation pickers, and native chat on desktop and
mobile. The default and current model always stay listed.
* feat(agent-picker): expand legacy models in place instead of a separate list
* feat(source-control): fold legacy models in the action model menu
* fix(agent-picker): keep model rows keyed across sections; let launch search find legacy models
* fix(agent-picker): keep rows in place while picking; classify each model catalog once
Legacy membership no longer depends on the selected model; a picker opens the
legacy section when the current model is in it. Classification is one pass and
cached per catalog, so streamed chat renders don't reclassify an unchanged list.
Typing on a closed Select no longer swallows its next close.
* fix(agent-picker): reopen menus with the current legacy model shown; PTY chats classify their discovered list
* test(agent-picker): wait on the menu instead of sleeping
* fix(agent-picker): keep the Legacy models row last, expanding older models above it
* fix(agent-picker): label the folded section More models, matching mobile
The close actions swallowed refusals and transport errors, so a tap on
close did nothing when the host refused (for example while the desktop
window is still loading). Show a short toast instead, with a specific
message for runtime_unavailable.
Holding Cmd/Ctrl swapped the round Send/Stop button for a wide labeled pill,
pushing the model picker, context ring and mic left. The preview now keeps
Send's round button and only swaps its icon (CornerDownRight for steer,
ListEnd for queue); the label stays as the aria-label. With Steer queued,
the head queued card's Steer button also fills as the primary.
The phone composer had the same pill and now follows the same rule.
The site now lives at orca.dev; the old onorca.dev and www.onorca.dev hosts
redirect there. Linking to orca.dev directly skips that redirect.
Only page links change. Service hosts, sign-in and relay endpoints stay as they are.
* chore(endpoints): call orca.dev for feedback, plugin lists and update notes
The site moved to orca.dev. Feedback, connection diagnostics, plugin install
stats, the plugin kill list, update nudges and the changelog feed now use
orca.dev, as do the diagnostics token URL baked into builds and the release
download mirror (downloads.orca.dev). The old hosts serve the same paths,
so released builds keep working.
Skill share links on share.orca.dev are accepted, and the mobile scanner
recognizes the orca.dev Android download code. The old hosts stay accepted.
* chore(ci): leave the release JavaScript benchmark workflow unchanged
Editing it triggers the benchmark, whose parity check currently fails on
arm64 because max-quality brotli output for the mobile page differs by
architecture. The benchmark only needs some diagnostics URL, and the old
host still serves it.
* fix(orchestration): label follow-up worker cards, gate remote-owned start cards on ready
A send that continues a finished or closed worker starts a follow-up Dispatch,
and the coordinator's chat showed it as "Started worker". The agent-start item
now carries the Dispatch it followed and whether the conversation reopened, and
the card reads "Reopened worker" or "Sent follow-up to worker".
worker-start --on a server that owns its workers wrote the card for any parsed
answer, with no agent kind. It now passes the started agent and writes the
card only once the server readied the worker, as local and attached starts do.
* test(native-chat): assert follow-up heading kind, not its copy
* feat(mobile): show only the open tab, with a tab count and a sheet of every tab
The session bar shows the open tab at full width, then a tab count cell and a + cell. The count opens a sheet of every tab grouped Needs you / Working / Everything else; tapping the tab opens its actions; swiping it steps between tabs; + opens a new tab sheet with agents as tiles. The header subtitle shows the branch. Brings back #40592 on current main.
* fix(mobile): don't switch tabs when a bar swipe is interrupted
* fix(native-chat): hold an automatic continue until the chat's account has usage again
The automatic continue after a usage limit sent at the cut's reported reset
without looking at the account, so it could send into a still-spent weekly
window. Each sweep pass now attributes the chat's launch account (the host's
launchAccountHome) to Orca's polled reading taken in that same home, and holds
the send until the latest spent window's reset (+15 s). The hold is derived
each pass, never saved, only grows for one cut and account, and waits up to two
minutes for a reading taken since that reset before falling back to the reset
it has. A spent window with no known reset stops the automatic continue for that
cut; on System Default, where Orca can't tell which login a reading saw, a fresh
reading with room releases both. Rows on desktop and phone show the held time
through an optional continueAt; older clients keep the cut's time.
* fix(native-chat): attribute usage holds once per account and retire them with their scope
- C1/X7: launch home resolved once per provider and reading once per (provider, home) per pass; narrow usageReading accessor replaces getState().
- X8: forget() drops the hold; hidden-chat caches are pruned while the setting is off; readState stores nothing for an unattributed chat.
- X3/C3: launch and reading homes compared in canonical form (~, dot segments, async realpath, bounded cache).
- X4/C2: a hold belongs to its scope (canonical home + billing kind) and retires when it changes.
- X2: a launch whose provider has no reading yet asks once and waits within the two-minute window.
- X5: attribution runs before the overdue check, so a restart keeps the account's later reset.
- X6: the launch account is re-attributed right before each send; beforeRun refuses a changed attribution.
- C5: cold chats publish when their continue time moves.
- X1/C4: WSL launches keep the cut's own reset (no hold).
* fix(native-chat): gate held continues on an account scope token and stop reviving stale cuts
- C6: a hold extends only with a spent window that had begun by the cut's reset.
- X6-R2, X10, P1-1: a synchronous account scope token (selection, launch env, reading home and
provenance) is captured at attribution; send() re-resolves only when it moved, memoized per
token, and beforeRun compares live and captured tokens. Opening a chat drops only cold-cut caches.
- X11: production tick() test at N=300 and 10N=3000 counts resolver and reader calls.
- X2-R2: the bootstrap wait starts when a pass first sees the due send without a reading.
- X9: canonical homes are resolved once per account per pass; the unbounded-lifetime cache is gone.
- C5-R2: unloaded chats have no status push path, so the cold continueAt publication is removed.
* fix(native-chat): separate continue eligibility from current spending and bind sends to their attribution's sources
- X12: a cut's eligible anchor (its reset, an established hold, or a covering window) takes the
24 h overdue test; once eligible, every currently spent window holds the send.
- X13/P1-2, X6-R3: the account owner returns attributions with a synchronous source witness
(settings snapshot identity, cached login-shell capture revision, reading-source identity);
Claude billing comes from the launch's own env composition. A result whose sources moved while
it resolved is discarded; send() and beforeRun bind to the attribution's own witness and
supersede without spending an attempt.
- CI-1: the scope-token type assertion is gone with the token.
* fix(native-chat): keep a covering hold beside spent windows of unknown reset
- X12-R4 / C7: rebuilding the covering anchor skips spent windows whose reset, and so start, is
unknown; the eligible branch still refuses on them.
- E1-R4: the preparation-change and suspended-attribution race tests run on a Claude chat
through the real host send, mutation and accepted settlement.
* refactor(native-chat): move the usage-limit continue status into its own wire module
The merge with main put agent-session-wire.ts one line over the 300-line limit.
* fix(native-chat): read the shell capture revision lazily so module mocks without it still load
Tests that main added mock login-shell-environment without the revision accessor, so binding it at module load failed.
* fix(native-chat): release a usage hold on fresh room for any account without stranding the cut
- QA-1: room in a fresh attributed reading newer than the last spent evidence (spentSeenAt)
releases the hold and clears an unknown reset for every account; the System Default-only
branch and identityKnown are gone.
- QA-1b: the release pulls heldUntil down to that reading instead of clearing it, and an
unknown-reset hold anchors on its last evidence, so a release over a day after the cut sends.
* fix(native-chat): release an expired usage hold from its room reading and ask for readings while a reset is unknown
- R6-1 / QA-1b-R6: a release sets heldUntil to the room reading itself, so an expired finite
hold followed by an unknown reset no longer strands the cut.
- R6-2: the usage poller idles while Orca is unfocused with no viewer, so an unknown reset asks
for a non-forced reading at most every 10 minutes through the existing refresh.
* fix(native-chat): bound unknown-reset readings per account and honor the failure backoff
- R7-1 / X-R7-1: the rate-limit service owns the automatic-read cadence: a read waits while the
provider's last reading or admitted automatic read is within the asked interval, so held chats
on one account cause at most one unknown-reset read per 10 minutes.
- R7-2: automatic reads wait out the poller's failure backoff (shared failureRetryDue) and start
its clock; forced refreshes are unchanged.
* fix(rate-limits): scope automatic usage-read admission to the selected account
- R8-1 / X-R8-1: an admitted automatic read counts toward the cadence only for the account whose
reading source the service held then, so a switch never lets one account's read delay another's.
- Prunes the flaky-test-pattern baseline: service-refresh-orchestration no longer reads the wall clock.
* fix(native-chat): keep a usage-reading request retryable until the service starts it, and show the last known reset while one is unknown
* fix(native-chat): show no automatic continue while a chat waits on an unknown reset, since fresh room still releases it
* fix(native-chat): publish no continue time for a cancelled or stopped cut while its account's reset is unknown
* fix(native-chat): refuse a stale startup-dialog card choice and add a card for the multi-server MCP dialog
* fix(native-chat): drop casts from the startup-dialog guard
* fix(native-chat): refuse a startup card choice until the screen model has applied all received output
* fix(native-chat): refuse a clipped MCP server list and guard restored startup cards
* fix(native-chat): allow one startup card choice per screen so a second client's keys can't hit the next dialog