mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 18:47:40 +00:00
3a18a7c67904320510126c4fef154d2347331ecd
391
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6ae1386f98 |
fix(native-chat): never run a chat on an older host with more access than Ask for approval allows (#29442)
* fix(native-chat): refuse to start or send on a host that ignores Ask for approval * fix(native-chat): hide Continue on a host held for its permission mode * fix(native-chat): hold queued cards and goal turns on a held host; re-check on reconnect * fix(native-chat): hold goal objectives and start retries on a held host; re-ask while held * test(native-chat): give the remote Continue test a host that applies permission modes |
||
|
|
ec9a0af3e8 |
Show phone subagents and open the correct parent chat (#28686)
The phone's worktree list now shows the running subagents the desktop sidebar shows, under their parent agent with their own ages, read from the host's existing child records. Tapping one opens its parent's existing chat or terminal. The feature was reviewed in #26423 and merged into this branch. Links from the phone to native chats whose saved tab ID differs from the session-derived one (restored, reserved and headless chats) now resolve to the chat's real tab instead of missing it. |
||
|
|
f152101010 |
ci: reuse prepared Windows security addon in PR packages (#29352)
* ci: reuse prepared Windows security addon in PR packages * ci: prepare bounded Windows security reuse qualification * ci: ignore owned Windows benchmark receipts during source checks * test: inject Windows security qualification dependencies * test(mobile): register chat view mocks in test roots |
||
|
|
330fd55376 |
test(mobile): split MobileNativeChatView.test.ts under the max-lines limit (#29368)
Pure move: the 36 test cases and their assertions are unchanged, now in six feature files. Shared mocks and render helpers live in MobileNativeChatView.harness.test-fixture.ts. The only non-move edits are typing and formatting: the harness adds getComposerEditGeneration to its default props (a required prop the fixture never passed), tests use hostType(node) for tag comparisons, and the typecheck baseline and reliability gate paths drop the removed file. |
||
|
|
7c23f79622 |
fix(quick-commands): save agent prompts whole, bounded by what a chat message carries (#24488)
* fix(quick-commands): save agent prompts whole, bounded by what a chat message carries Agent quick-command prompts were silently cut to 6,000 characters on save: the editor accepted a long paste, settings normalization trimmed it, and reopening showed the shortened copy. The 6,000 was a Windows launch speed threshold, not a limit on prompts. - Storage no longer trims. A save is refused, never trimmed, when a prompt's chat text block is over 256 KiB of JSON (measured on its real escaped size, the measure the chat send schema applies) or the list is too large for one paired reply. Host and desktop store both check before writing. - The editor shows the size against the limit and blocks Save past it, and stays open with the text when the host refuses the save. - terminal.quick-commands.long-prompts.v1: hosts withhold prompts over 6,000 characters from clients that do not advertise it (older clients reject a whole list holding one); editors keep an older host's 6,000 cap. - The phone editor no longer drops pasted text past 6,000 via maxLength. Launch delivery of long prompts is left to the launch-prompt series (#24257, #23962, #24080); this branch's earlier paste-after-ready delivery duplicated it and is removed. * fix(quick-commands): drop a stale duplicate doc comment * fix(quick-commands): advertise long prompts to this build's own host too * fix(quick-commands): keep submitted editor immutable until save settles * test(quick-commands): keep storage and save lifecycle coverage focused * fix(quick-commands): keep portalled choices within pending form ownership * Preserve retained credentials when settings validation refuses a save --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai> |
||
|
|
e9c77360f5 |
fix(mobile): open each chat at its newest message when switching chats (#29099)
* fix(mobile): open each chat at its newest message on a chat switch The chat view stays mounted across chat tabs, so a reader's scroll in one chat carried into the next: it opened mid-transcript and not following. Reset follow intent and the page's history head when the chat changes, before paint, and keep view-wide state (Tools toggle, text size). * fix(mobile): pin a switched-to chat to its own end on the page The page pin scrolled to the last reported content height, which after a chat switch is still the previous chat's, so a taller chat could paint one frame mid-history before its own height arrived. Pin to the scroller's live height instead. |
||
|
|
d6f9df9b74 |
fix(native-chat): settle acknowledged messages after a paged reconnect (#28993)
* fix(native-chat): retire acknowledged send echoes after catch-up * test: follow phone lint rules in reconnect regression * test: format reconnect message expectation |
||
|
|
e2f2277d51 |
feat(native-chat): one queue header that becomes the pause row; drop the composer Resume and the send dialog (#28654)
* feat(native-chat): one queue header that becomes the pause row; no composer Resume or send dialog The queued cards now sit under one header row on desktop and phone: "Queued" and the count normally, and "Paused" with the count, Clear all and Resume while the host holds the queue after a Stop. A chevron folds only the cards; the choice is local to the view and resets when the queue empties. The composer's Send no longer turns into a second Resume, and a message sent into a paused queue no longer asks "Send message?": it sends, its accepted turn lifts the pause, and the cards follow it. Clear all deletes every card through the existing per-card delete. No wire or host change. * test(mobile): find the background-tasks strip's toggle inside the strip, not the queue header's * Make queued Clear all atomic across clients * Show queued message failures in collapsed headers * Use per-card delete for paused queue Clear all * Join repeated paused queue Clear actions |
||
|
|
0d4eb242d7 |
Recover failed-job log retrieval onto main (#29180)
* feat(checks): add optional job log outcome * fix(checks): preserve host capture lifetimes and safe outcomes * fix(checks): retrieve and retry bounded failed-job logs * test(checks): cover queued snapshots and captured error safety * test(checks): verify sanitized host excerpts and paired failures * test(checks): identify measurements and preserve paired cleanup * Use the generated check-log fixture in host retrieval tests * Keep real paired-server shutdown outside the injected stall deadline |
||
|
|
507d0a9122 |
fix(mobile): show answered approvals and questions as receipts, and a waiting question as "Awaiting user input" (#28906)
* fix(mobile): draw answered approvals and questions as receipts, and a pending question as its ask row
The phone projected a structured chat's journal with the host's plain
projection: an answered approval read "Allow Bash? {json} resolved", an
answered question listed its choices without the answer, and every
AskUserQuestion call stayed a raw tool line, also while its card was open
above the composer.
Desktop's question projection and receipt rules move to src/shared
(structured-agent-question-projection, native-chat-resolution-receipt,
with the receipt subject and answer heading and their words). The phone's
structured session now projects through them and hands each row its
receipt; new MobileNativeChatResolutionReceipt and
MobileNativeChatAwaitingInputRow draw them as desktop does. In a terminal
chat, a question call draws as "Awaiting user input:" until answered and
"Asked:" after.
* fix(mobile): say a question was asked, not awaited, once its agent has ended
A structured chat's question stays pending in the journal after the agent
that asked it ends (a restart, a crash, a new generation). The host stops
listing it as actionable, so the answer card goes away, but the phone's
row still read "Awaiting user input" and announced itself as live.
The session hook now returns the host's actionable prompt ids next to the
receipts, and each row asks the same shared
isActionableStructuredAgentSessionPrompt desktop's transcript row calls.
A question nobody waits on reads "Asked: …", as on desktop. The row
callback moves to its own hook so the chat view stays within its size.
* fix(mobile): decide a native chat's open question with desktop's selector
The phone decided whether a question call was still awaited with its own
rule (no answer yet and the turn working). Desktop asks the shared
selectActiveToolCall, which also reads the call's state, so a call marked
completed with no output reads "Asked" there and "Awaiting" on the phone.
Native chats now call the same selector. Terminal chats keep their rule.
* fix(mobile): zoom a receipt and its question row with the chat
Before receipts, an answered question or approval was a faint system line
that grew with the chat's pinch zoom. The receipt and the question row drew
at fixed sizes, so zooming no longer enlarged them. Both now take the
chat's zoom, through the same scaledType the notice row uses, which moves
next to the other row styles.
* fix(mobile): list a group's questions once nobody can answer them, and zoom the row's icon
A grouped prompt's row names only a count ("Asked: 2 questions"). Once it
is cancelled, or the agent that asked has ended, the answer card is gone
and the receipt lists no answers, so the phone showed the questions
nowhere. The row now lists them under its line, following desktop's
listsQuestions rule: the receipt asks for the list only when it shows no
answers, and the transcript's own question rows keep it off, as desktop's
tool run does. While the host waits, the card shows them.
The row's icon also grows with the chat's zoom, as the notice row's icon
does, so it stays level with its line.
* refactor(mobile): trim comments and simplify the question row and receipt
Comments that restated the code go. The answer heading takes the subject
its caller already has, dropping two conditions that never changed the
result, on desktop and the phone. The question row lists a group's
questions whenever its caller asks, as desktop's does, and the receipt
asks only while nobody waits. fontScale is required on both new rows.
|
||
|
|
f3551dba57 |
fix(mobile): show skill commands you ran and draw warnings, errors and status lines as notices (#28899)
* fix(mobile): show skill commands you ran in the chat Claude records a skill run (/simplify, /review-pr 123) as a command envelope, which the noise filter hides. Desktop surfaces it as the typed `/name args` before folding; the phone only normalised image markers, so the turn vanished and the reply answered nothing. Both apps now prepare a terminal agent's transcript through one shared prepareNativeChatMessages: skill envelopes, reporting-command output and image marker turns. * fix(native-chat): keep a streamed sentence from coming back after a tool or thought row Both chats hide the live preview once the transcript shows it, but each only compared the preview with the last row. When a tool call or thought landed below the sentence, the last row had no words and the earlier sentence came back at the bottom as a live message. latestNativeChatReplyText reads back past those rows to the newest assistant words in the turn; desktop's preview check and the phone's streaming gate both use it. * fix(mobile): draw errors, warnings and status notices as notices, not agent replies The phone drew every system row through the agent's markdown, so an API retry warning, a sign-in failure, "Context compacted" or "Stopped manually" read as the agent's own words. Only the clear boundary and two host notices were special-cased. Desktop's notice rules move to shared native-chat-notice: which block of a system row is its notice, and the look it gets (divider, command output, host status, plan, aside, or a toned line). Desktop's notice row switches on it with no change in what it draws; the phone's new notice row draws the same looks, with words in faint type when a row has no notice. * fix(native-chat): show a new reply that follows a tool call on desktop The earlier fix read back past a tool call or thought to the newest assistant words, then kept desktop's length rule. Commentary written before the tool is often longer than the reply after it, so a new, shorter reply stayed hidden until the transcript caught up. The last-row rule is back as it was. Reading back now only hides the preview when the earlier words already contain it, which is the sentence still on screen while the tool runs. The phone's gate reads only rows that landed after the current reply began, so it was not affected. * fix(mobile): open the actions sheet from a long press on a plan's link A plan card's Markdown was drawn without the row's long-press handler, so on Android holding a link or file path inside a plan no longer opened Copy message / Select text, as it did before plans were drawn as notices. The notice row now passes the handler to the plan's Markdown. |
||
|
|
d9de1fad1d |
Native chat: desktop and mobile read the host's working answer; plain refusal for a stopped agent's prompt (3/5, rebuild of #26867) (#28104)
* feat(native-chat): record which agent run wrote each journal item Each journal item row, subagent roster entry and background-task entry now records ownerFence: the lease fence of the agent run whose output produced it. Optional, persisted, no row version bump and no migration. The journal's itemFence() returns it, so the turn-verdict rules and per-run provider activity read the producing run rather than the writer's fence. * feat(native-chat): one current-work projection for every host reader; record death first The host decides what work is current in one place (structured-agent-session-current-work.ts) from the lease, the exit this host saw, the runtimes it replaced, its live child and each item's provenance. Every host reader (queue, Stop, prompt answers, commands, status feed, idle sweep, teardown snapshot, history pages and frames, orchestration mailbox) reads it, so a dead agent's leftovers stop reading Working or holding the queue even when their cleanup write failed. The journal's activeTurnId()/liveTurnScope() become the raw runningTurn(). On exit the host records the child's end first and signals generationEnded, which republishes frames, the status feed and settlement waits and wakes the queue drain with no journal row. Pages and frames carry optional working and actionablePromptIds fields. * feat(native-chat): clients read the host's working answer; refuse answers to a stopped agent plainly Desktop and mobile prefer the host's working and actionablePromptIds fields when present (merged across batches like latestTurn) and fall back to today's row derivation against an older host. A prompt an ended agent raised stops looking active; answering it is refused before any provider sees it, with "<Agent> has stopped, so your answer was not sent. Send a message to continue." Cancelling it only dismisses it in the journal: it no longer triggers the chat's Stop, pauses the queue or withdraws queued cards. * refactor(native-chat): keep structuredAgentSessionHandsOver file-local until a caller needs it Nothing outside the projection calls it in this part; Part 5 exports it for the start-failure row. * test(native-chat): fewer provenance tests: fold reopen and unstated-owner checks into the main case Drops the epoch-replacement case (Part 2 rewind test reads the same values through the same rebuild) and the corrupt-value case (no version writes one). * test(native-chat): drop current-work mirrors: the viewKey unit case and the still-live rewind case The flow cases keep every outcome: dead leftovers read as nothing, Stop has nothing to stop, a waiting card drains, a live owner publishes as before, and a rewind never promotes dead work. * test(native-chat): fewer client tests: one flow per client, one fallback each Keeps desktop and phone Working from the host (with the older-host fallback), the reducer merge contract, the ended question Cancel that only dismisses, the agent-named refusal and the resolution receipt. Drops per-helper prompt tests, the transcript-placement test, the stop-note client reduction, the Steer cards that match main, and per-reason refusal rows. * test(native-chat): an unstamped roster entry from an older build reads as its row provenance Moved here from Part 4 so it sits beside stampJournalEntryProvenance. * test(native-chat): build the older-build roster row inline, after main removed its body helper Main #27947 writes one row per subagent and deleted journal-subagent-group-body; a roster row from an older build is still what this case reads. * refactor(native-chat): provenance is per item only; drop per-entry stamping Main's #27947 writes one saved row per subagent, and each background task already has its own row, so an item's own ownerFence names the run of every entry a producer still writes. Per-entry stamps inside a row mattered only for the shared roster rows saved before #27947, and no run writes into those any more, so the row's own provenance settles them correctly. Removes the entry stamping, the optional ownerFence on roster entries and background tasks, and their tests. * fix(native-chat): an item's provenance is set when it is created; revisions keep it A newer run's provider can revise an item an older run created: Pi's start and an ACP session/load both report context usage onto the newest saved turn. Restamping that turn with the newer run let the newer run's exit settle the older run's unverifiable turn as interrupted. Only creation states an owner now, as main's itemFences rule did. * test(native-chat): keep the provenance test below the production lines it covers * fix(native-chat): an ended agent's card Cancel commits with its receipt, through dismissPrompt The ended-generation dismissal wrote the cancelled resolution without the Cancel's operation receipt and without the dismissal's failure handling, so a receipt write failing after it would answer "outcome unknown" over a card already closed. It now goes through dismissPrompt, which carries the uncommitted receipt in the same append and answers dismissalNotSaved on failure; a null `answer` skips the adapter, since no live provider holds an ended generation's card (Pi's adapter refuses a fence with no live session outright). |
||
|
|
095399aa4d |
fix(mobile): render markdown with markdown-it instead of a hand-written reader (#28858)
* fix(mobile): size markdown table columns to their content * fix(mobile): always show the copy button under chat messages Revealing it on tap shifted the transcript. * fix(mobile): read markdown with a CommonMark parser markdown-it rather than desktop's micromark: about 8x faster on the phone's JavaScript engine. |
||
|
|
6d519f7835 |
fix(mobile): keep content clear of the side insets in landscape (#28749)
In landscape the iPhone sensor housing and Android's side nav bar are left/right insets, which no screen or over-the-air page padded for. Fixes #21190 |
||
|
|
6607c54984 |
perf(ci): cut redundant tests and reuse expensive setup (#28669)
* perf(ci): reduce test overhead and reuse expensive build work * test(ci): keep worker startup outside failure-case deadlines * test: isolate push fixture advisory locks * test(ci): preserve fixed drain retry allowance * Align CI contracts with current triggers and guide content * Stabilize consolidated reveal scenarios and retain behavioral CI contracts * Fix upstream lint blockers while retaining behavior and assertions * Classify global Jira preferences in the WSL migration inventory * Align Jira test fixtures with startup and build safety checks * Include package-test routing dependency in CI checkouts and fixtures * Keep native stream classes intact in transcript dependency tests |
||
|
|
cd1ff506e1 |
feat(mobile): list the desktop's server projects on the phone's Tasks screen (#27741)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve restart and today's gap: a desktop's phone does not list the desktop's server workspaces. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): show SSH host health on the workspace row host badge Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): compose the phone-mirror topology and check the cold snapshot Slices need one composer for host, desktop and phone with a single teardown, so specs stop copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold start, which is what the phone builds its tab strip from. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name an unhealthy SSH host even in a single-host list The desktop card shows SSH status on any host count, so the phone now reads target summaries whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one target-to-host keying, and a hook-level test pins the reply-to-row wiring. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): keep phone-mirror teardown errors from masking test failures Teardown failures become a test attachment instead of replacing the real failure, and the cold-snapshot poll reports stream errors together with the PTY list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): clear stale SSH host badges and announce them without the dot A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps, so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox, Disconnected") instead of reading the middle dot. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * 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 * test(mobile): type the single-host health fixtures inline so the test tsc passes Fixtures hoisted into consts widened their health maps to string, which the now-required hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union. 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 * test(mobile): re-record the repo-metadata goldens for SSH host health The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts. 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 * feat(mobile): list the desktop's configured servers and their last workspace rows for the phone The phone needs the same host list the desktop shows, including servers that are offline or too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): list server rows through one desktop fetch, with no side effect on listing The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the desktop's own fetch is both the live source and the cache, and stamping stays in main. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows when a server hangs, drops a server's rows when it is removed or re-paired, and checks the server's identity after the fetch so a reply from before a re-pair is never served. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name a server workspace's host on the phone's calls A phone reaches a server's workspace through its desktop only if each call names the host; the direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete A server Orca manages on an SSH host now lists once under that host's name, or not at all while the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is deleting are left out at reply time, as a phone asking the server itself would see. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied the phone's list; rows are now pruned only once a server is no longer configured. Fetches are shared per server identity, so a poll after a re-pair asks the new server right away. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): list the paired desktop's server workspaces on the phone Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin that a desktop failing the hosts list leaves only its own rows The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave today's list with no server rows and no catalog error. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): mirror the desktop's shown hosts exactly, one host included Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a one-host list narrows the phone too instead of being read as a single-host focus. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): scope a server workspace at its route, never the desktop Every screen under a server workspace's route now reads one context and gets that server's client and a router that keeps pushes on it, refusing to fall back to the desktop; pins write to the row's owner like the Mac. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): never open an unreachable server workspace, and prove the relay in e2e A server workspace the phone can't reach now gets one explicit screen at its route and no row tap or action-sheet entry opens it; native routes read the route's host, a census pins every workspace route, and the e2e reads files and git on the server. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): gate a server workspace on that server's own features A targeted status.get now relays to the server, so a server workspace's route provides that server's status to every screen under it and its action sheet, while whether the phone can reach the server stays the desktop's answer. 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 * fix(mobile): run a server workspace's SSH calls on that server A phone in a server workspace asks for SSH state before a file mutation, but the Mac answered about its own targets, so server-owned SSH repos failed. The desktop asks the server, so the ssh.* family now routes by target; an e2e pins files, uploads, commits and review on the server. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): keep the server files/git e2e offline and baseline-git safe Review eligibility reached real gh/glab over the network and proved nothing beyond repoSlug; setup git now ignores the runner's config and avoids `init -b` (Git 2.25). The relay integration test is the routing oracle, so the census re-listing goes. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): sleep, wake and new workspaces on a desktop's servers from the phone The desktop renderer alone sleeps a server's workspace and holds its slept agents, so the phone now asks the desktop (two paired-desktop methods) instead of the headless server; the new-workspace sheet lists and creates on reachable servers the way the desktop's composer does. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): sleep and wake only a server the desktop has configured A phone's sleep or wake names a server by host id; the desktop now refuses one that is not in its own configured servers, read from its settings rather than any fetched workspace list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep server workspaces' repos, picks and sleeps on their own host A hung server no longer holds back the desktop's repos, a repo id shared by two hosts no longer swaps the pick, and a server row's optimistic sleep waits for the server's own list to confirm it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): give the server-workspaces test rows every Worktree field The tests-typecheck ratchet checks mobile test files; the fixture row was missing required Worktree members, so the file no longer typechecked. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): give the file preview the workspace router, back to 128 page assets The preview was the one workspace screen on the plain router, so the scoped router was reached by a route set of its own and esbuild split it into an extra chunk (129 assets); every workspace screen now uses it and it shares the route's chunk. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * revert(mobile): drop server sleep and wake from the phone for now The workspace-layout refactor deletes the notifier path these used; sleep and wake on a server come back later as server-side layout.sleep / layout.wake. A server row's Sleep behaves exactly as on feat/mobile-remote-workspaces. 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 * refactor(mobile-relay): drop unread freshness fields from server workspace lists The phone reads only the rows, so stale, fetchedAt, totalCount and truncated were dead wire weight. The desktop still serves a server's last rows while the server is unreachable. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): read only the rows of a server's workspace list The desktop no longer sends a freshness flag, so the phone's schema drops it. The relay capability list names the delegated-devices constant instead of repeating its literal, which also retires a now-circular type check. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): open server-workspace diffs and file taps on the phone's own screens A headless orca serve has no renderer, so files.openDiff / files.open answer renderer_unavailable and the Changes tap showed that raw code. On that refusal the phone now opens its review screen (git.diff) or file preview (files.read) instead. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): show a server workspace as disconnected while its server is down The phone read the desktop's connection state for a server workspace, so a stopped server kept a green dot and "Loading tabs" spun forever. The workspace route now narrows that state by the server's health from mobileRelay.hosts.list, reusing the existing disconnected session UI and refetching tabs when the server returns. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): name run targets and the desktop's own row as the host list does The Run on picker showed a server's raw environment id behind "Remote ·" and always a monitor glyph; it now takes the host list's badge labels and icon, as the desktop's picker does. Server rows also made the desktop row read "This computer" because its platform was only read for a mixed desktop catalog. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): drop Open in Session and preview new notes in server workspaces A server workspace's host has no editor tabs, so the review screen no longer offers Open in Session there, and a new markdown note whose files.open answers renderer_unavailable opens in the file preview instead of a raw error toast. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): share one desktop server-list feed between the host list and workspaces A server workspace polled mobileRelay.hosts.list beside the host list's own read, so the phone could ask the desktop twice per tick and the row badge and the screen could disagree. One feed per desktop client now owns that read; both readers join it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): read a server's reachability from the hosts list, not a dropped stale flag |
||
|
|
75513668fb |
fix(mobile): check a server workspace's SSH state on that server (#26519)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve restart and today's gap: a desktop's phone does not list the desktop's server workspaces. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): show SSH host health on the workspace row host badge Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): compose the phone-mirror topology and check the cold snapshot Slices need one composer for host, desktop and phone with a single teardown, so specs stop copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold start, which is what the phone builds its tab strip from. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name an unhealthy SSH host even in a single-host list The desktop card shows SSH status on any host count, so the phone now reads target summaries whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one target-to-host keying, and a hook-level test pins the reply-to-row wiring. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): keep phone-mirror teardown errors from masking test failures Teardown failures become a test attachment instead of replacing the real failure, and the cold-snapshot poll reports stream errors together with the PTY list. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): clear stale SSH host badges and announce them without the dot A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps, so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox, Disconnected") instead of reading the middle dot. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * 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 * test(mobile): type the single-host health fixtures inline so the test tsc passes Fixtures hoisted into consts widened their health maps to string, which the now-required hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union. 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 * test(mobile): re-record the repo-metadata goldens for SSH host health The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts. 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 * feat(mobile): list the desktop's configured servers and their last workspace rows for the phone The phone needs the same host list the desktop shows, including servers that are offline or too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): list server rows through one desktop fetch, with no side effect on listing The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the desktop's own fetch is both the live source and the cache, and stamping stays in main. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows when a server hangs, drops a server's rows when it is removed or re-paired, and checks the server's identity after the fetch so a reply from before a re-pair is never served. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): name a server workspace's host on the phone's calls A phone reaches a server's workspace through its desktop only if each call names the host; the direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete A server Orca manages on an SSH host now lists once under that host's name, or not at all while the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is deleting are left out at reply time, as a phone asking the server itself would see. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied the phone's list; rows are now pruned only once a server is no longer configured. Fetches are shared per server identity, so a poll after a re-pair asks the new server right away. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): list the paired desktop's server workspaces on the phone Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): pin that a desktop failing the hosts list leaves only its own rows The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave today's list with no server rows and no catalog error. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): mirror the desktop's shown hosts exactly, one host included Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a one-host list narrows the phone too instead of being read as a single-host focus. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): scope a server workspace at its route, never the desktop Every screen under a server workspace's route now reads one context and gets that server's client and a router that keeps pushes on it, refusing to fall back to the desktop; pins write to the row's owner like the Mac. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): never open an unreachable server workspace, and prove the relay in e2e A server workspace the phone can't reach now gets one explicit screen at its route and no row tap or action-sheet entry opens it; native routes read the route's host, a census pins every workspace route, and the e2e reads files and git on the server. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): gate a server workspace on that server's own features A targeted status.get now relays to the server, so a server workspace's route provides that server's status to every screen under it and its action sheet, while whether the phone can reach the server stays the desktop's answer. 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 * fix(mobile): run a server workspace's SSH calls on that server A phone in a server workspace asks for SSH state before a file mutation, but the Mac answered about its own targets, so server-owned SSH repos failed. The desktop asks the server, so the ssh.* family now routes by target; an e2e pins files, uploads, commits and review on the server. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): keep the server files/git e2e offline and baseline-git safe Review eligibility reached real gh/glab over the network and proved nothing beyond repoSlug; setup git now ignores the runner's config and avoids `init -b` (Git 2.25). The relay integration test is the routing oracle, so the census re-listing goes. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): give the file preview the workspace router, back to 128 page assets The preview was the one workspace screen on the plain router, so the scoped router was reached by a route set of its own and esbuild split it into an extra chunk (129 assets); every workspace screen now uses it and it shares the route's chunk. 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 * refactor(mobile-relay): drop unread freshness fields from server workspace lists The phone reads only the rows, so stale, fetchedAt, totalCount and truncated were dead wire weight. The desktop still serves a server's last rows while the server is unreachable. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): read only the rows of a server's workspace list The desktop no longer sends a freshness flag, so the phone's schema drops it. The relay capability list names the delegated-devices constant instead of repeating its literal, which also retires a now-circular type check. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): open server-workspace diffs and file taps on the phone's own screens A headless orca serve has no renderer, so files.openDiff / files.open answer renderer_unavailable and the Changes tap showed that raw code. On that refusal the phone now opens its review screen (git.diff) or file preview (files.read) instead. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): show a server workspace as disconnected while its server is down The phone read the desktop's connection state for a server workspace, so a stopped server kept a green dot and "Loading tabs" spun forever. The workspace route now narrows that state by the server's health from mobileRelay.hosts.list, reusing the existing disconnected session UI and refetching tabs when the server returns. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): drop Open in Session and preview new notes in server workspaces A server workspace's host has no editor tabs, so the review screen no longer offers Open in Session there, and a new markdown note whose files.open answers renderer_unavailable opens in the file preview instead of a raw error toast. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): share one desktop server-list feed between the host list and workspaces A server workspace polled mobileRelay.hosts.list beside the host list's own read, so the phone could ask the desktop twice per tick and the row badge and the screen could disagree. One feed per desktop client now owns that read; both readers join it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(e2e): read a server's reachability from the hosts list, not a dropped stale flag |
||
|
|
12ba3bc594 |
fix(mobile): keep the chat where the reader scrolled to (#28413)
* fix(mobile): keep the chat where the reader scrolled to RN Web reports no drag events, so the page never stopped following a live turn. Native lost its place when older history loaded. * fix(mobile): keep chat history the phone already shows A busy turn outran the 40-message window and dropped the row being read. * fix(mobile): keep the page's place when history loads RN Web's list draws rows by index, so rows added at the front replaced the ones on screen. * fix(mobile): hold the page's place by a row on screen Total height also grows when live output lands below the reader in the same update. --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
04b4e962a7 |
fix: a Claude model that's gone switches to the list's default when you send (#27826)
* fix: start a new Claude chat on the listed default when its remembered model is gone The remembered new-chat model was launched as-is even after Claude stopped offering it, so every new chat failed its first message. The host create and the desktop new-chat picker now derive the same replacement from the host's saved model list: the listed default, effort moved to the nearest level it offers. Explicit models (--model, held picks) run as given; with no saved list the remembered model is kept. * fix: read the host's model list only for a Claude create that needs it The check read host.deps before ruling out other agents and explicit models, so creates whose host carries no deps (and every non-Claude create) paid for a lookup they never use. * fix: picker derives the fallback only where the host would A held model runs as picked on the host, so the picker no longer derives an effort beside it. A cached list whose default marker was dropped for this workspace keeps the placeholder instead of naming its first row. * fix: the new-chat picker picks the replacement for a gone Claude model The host-side check read the saved list separately from the picker, so the two disagreed and a cold start never fired it. The desktop new-chat picker now picks the replacement it shows, once, through the existing pick path, so the first send runs it and the remembered selection heals. The host is back to main. * fix: decide a gone Claude model's replacement at send, from the list in hand The open-time auto-pick raced the chat's create and the list's arrival, so the first send could still launch the gone model. Now, like the common pattern, the picker shows the replacement for a model the host's loaded list lacks, and the send step applies it through the existing pick call, awaited, right before the message goes out. A failed pick never holds the send back. * fix: one verified list decides a gone model's replacement The picker, the send step and the option setter now all read the list the picker shows, less the row a session adds for its own unlisted model, so what is shown is what applies and an explicit pick from that list stays. A listing that is still running, failed, or absent keeps the model. The send step re-derives from the chat's state when it runs, so a second send behind one that already moved the model applies nothing. * fix: only rows a finished host listing names can replace a gone model A single verified flag outlived the catalog it described: live options built from the built-in fallback list, or a malformed or empty host answer, still authorized a replacement the host would refuse. The state now keeps the ids a finished, well-formed host listing names, and a replacement must be both shown in the picker and named there. * fix(native-chat): the send carries the composer's Claude model and effort Every composer send (desktop and phone) carries the model and effort the picker shows, as optional agentSession.send options, only to a host that advertises agent-session.send-options.v1. The host writes a carried value that differs from the chat's own through the existing pick path before accepting the message, so the turn starts on it and the chat records it. A queued card keeps its options and applies them when it goes out; a launch's create call carries the replacement for its first message. Replaces the separate awaited pick before each send. * chore: drop an unrelated lockfile change * refactor(native-chat): one helper names a send's control fields for client and host * ci: rerun against current main * fix(native-chat): a message runs on the model and effort it was sent with The carried selection is now stored with the message (its submission row, and a queued card's row) and applied through the shared option writer as the delivery loop hands the message over, or before the start it needs. A pick made after the message was accepted no longer changes the model it runs on. Direct sends, queue drains and Send now share that one apply point; the early apply before acceptance and the queue-only apply are gone. The picker and the send read one effective selection: the replacement for a model the host's finished listing lacks shows only for a host that takes a send's selection, the phone validates picks against what it shows, a send carries the default the composer shows, a resumed launch's first message gets the same resolution, and only the listing proves a row (no label heuristic). * docs(native-chat): a submission's options are the host's alone * fix(native-chat): an opening message keeps its model; a pending pick resolves like the picker The opening message a create sends now stores the model and effort the chat was created with, so a pick made before its handover no longer changes what it runs on, and a card kept after a restart keeps them. A pick on its way to the host resolves through the same option view the picker shows before a send carries it, so a gone model it names is replaced too; the send selection reads only the shown snapshot. The option writer builds its own record-and-publish context, so the message path no longer duplicates it, and the client and host share one function for a send's fingerprint fields. * ci: rerun against current main |
||
|
|
5f134d8f01 |
feat(mobile): show and use the desktop's server workspaces on the phone (#26501)
* test(e2e): add a paired phone client helper and phone-mirror baseline specs
Later phone-mirror slices need one oracle that drives a real mobile-scoped device with the
phone's own capabilities against a serve host or a paired desktop. Pins #26022 across a serve
restart and today's gap: a desktop's phone does not list the desktop's server workspaces.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): show SSH host health on the workspace row host badge
Rows on an SSH host the desktop cannot reach looked healthy on the phone. The badge now
adds the desktop sidebar's own word ("devbox · Disconnected"), derived by the same shared
function from the connection status ssh.listTargetSummaries already sends; old desktops keep today's badge.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): compose the phone-mirror topology and check the cold snapshot
Slices need one composer for host, desktop and phone with a single teardown, so specs stop
copying setup. The serve-restart spec now reads the first tab-stream snapshot after the cold
start, which is what the phone builds its tab strip from.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name an unhealthy SSH host even in a single-host list
The desktop card shows SSH status on any host count, so the phone now reads target summaries
whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one
target-to-host keying, and a hook-level test pins the reply-to-row wiring.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): keep phone-mirror teardown errors from masking test failures
Teardown failures become a test attachment instead of replacing the real failure, and the
cold-snapshot poll reports stream errors together with the PTY list.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): clear stale SSH host badges and announce them without the dot
A catalog that no longer spans hosts or holds an SSH repo now clears the health and label maps,
so a host that left cannot keep badging rows. The badge gets a screen-reader label ("devbox,
Disconnected") instead of reading the middle dot.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* 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
* test(mobile): type the single-host health fixtures inline so the test tsc passes
Fixtures hoisted into consts widened their health maps to string, which the now-required
hostHealthById rejects under the tests-typecheck ratchet; passing them inline keeps the union.
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
* test(mobile): re-record the repo-metadata goldens for SSH host health
The settled metadata state now carries hostHealthById, and an SSH-only catalog now reads the
target summaries so a lone unhealthy host can be badged. Scoped re-record via rpc-recording.mts.
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
* feat(mobile): list the desktop's configured servers and their last workspace rows for the phone
The phone needs the same host list the desktop shows, including servers that are offline or
too old to relay to. mobileRelay.hosts.list/worktrees answer from the desktop's own status and
its own worktree.ps fetches, cached per server and fenced by pairing, never from relayed replies.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): list server rows through one desktop fetch, with no side effect on listing
The phone now always lists a server's rows through mobileRelay.hosts.worktrees, so the
desktop's own fetch is both the live source and the cache, and stamping stays in main.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): fetch a server's full workspace list within a bound, and forget removed servers
The desktop now asks for the phone's full-catalog limit, answers within 5s with the last rows
when a server hangs, drops a server's rows when it is removed or re-paired, and checks the
server's identity after the fetch so a reply from before a re-pair is never served.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): name a server workspace's host on the phone's calls
A phone reaches a server's workspace through its desktop only if each call names the host; the
direct, cloud-relay and shell-bridge transports now carry `executionHost`, streams keep it for
replay and unsubscribe, and an older shell (no `executionHost` grant) refuses rather than strips it.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): list managed servers as the desktop sidebar does, and hide rows mid-delete
A server Orca manages on an SSH host now lists once under that host's name, or not at all while
the host is reached over the SSH relay, using the sidebar's own folding. Rows the server is
deleting are left out at reply time, as a phone asking the server itself would see.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): keep a disconnected server's last rows, and never join a re-paired server's fetch
Retirement also fires on a manual disconnect or managed-server update, which wrongly emptied
the phone's list; rows are now pruned only once a server is no longer configured. Fetches are
shared per server identity, so a poll after a re-pair asks the new server right away.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): list the paired desktop's server workspaces on the phone
Fixes the phone missing a desktop's `orca serve` workspaces: rows come from the desktop's
mobileRelay.hosts.* with each server's health ("Update needed", "Disconnected"), open through the
relay, follow the desktop's hidden hosts, and the desktop now advertises the relay capability.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(mobile): pin that a desktop failing the hosts list leaves only its own rows
The page asks a relaying desktop for its servers; a refusal such as method_not_found must leave
today's list with no server rows and no catalog error.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): mirror the desktop's shown hosts exactly, one host included
Jinwoo's call: the phone follows visibleWorkspaceHostIds as the desktop sidebar does, so a
one-host list narrows the phone too instead of being read as a single-host focus.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): scope a server workspace at its route, never the desktop
Every screen under a server workspace's route now reads one context and
gets that server's client and a router that keeps pushes on it, refusing
to fall back to the desktop; pins write to the row's owner like the Mac.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): never open an unreachable server workspace, and prove the relay in e2e
A server workspace the phone can't reach now gets one explicit screen at its route
and no row tap or action-sheet entry opens it; native routes read the route's host,
a census pins every workspace route, and the e2e reads files and git on the server.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* feat(mobile): gate a server workspace on that server's own features
A targeted status.get now relays to the server, so a server workspace's route
provides that server's status to every screen under it and its action sheet,
while whether the phone can reach the server stays the desktop's answer.
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
* fix(mobile): give the file preview the workspace router, back to 128 page assets
The preview was the one workspace screen on the plain router, so the scoped router
was reached by a route set of its own and esbuild split it into an extra chunk
(129 assets); every workspace screen now uses it and it shares the route's chunk.
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
* refactor(mobile-relay): drop unread freshness fields from server workspace lists
The phone reads only the rows, so stale, fetchedAt, totalCount and truncated
were dead wire weight. The desktop still serves a server's last rows while
the server is unreachable.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): read only the rows of a server's workspace list
The desktop no longer sends a freshness flag, so the phone's schema drops it.
The relay capability list names the delegated-devices constant instead of
repeating its literal, which also retires a now-circular type check.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(mobile): show a server workspace as disconnected while its server is down
The phone read the desktop's connection state for a server workspace, so a stopped
server kept a green dot and "Loading tabs" spun forever. The workspace route now
narrows that state by the server's health from mobileRelay.hosts.list, reusing the
existing disconnected session UI and refetching tabs when the server returns.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(mobile): share one desktop server-list feed between the host list and workspaces
A server workspace polled mobileRelay.hosts.list beside the host list's own read, so
the phone could ask the desktop twice per tick and the row badge and the screen could
disagree. One feed per desktop client now owns that read; both readers join it.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(e2e): read a server's reachability from the hosts list, not a dropped stale flag
|
||
|
|
13a75d3939 |
[CI] Reduce redundant test work and import existing helpers directly (#28365)
* [CI] Reduce redundant test work and import existing helpers directly * [CI] Preserve provider guards and pin the mobile browser test core * [CI] Fix CI test setup and verify retained contracts * [CI] Compare native media chunks without boxed byte arrays * [CI] Detect shifted native media reads with position-sensitive bytes |
||
|
|
a622bb0f4b |
fix(native-chat): phone Stop reaches a message the agent hasn't answered; unreadable task list reports unconfirmed (#28243)
* fix(native-chat): phone Stop reaches unanswered sends; unreadable task list is unconfirmed The phone named the running turn on every Stop, so a Stop pressed before the agent echoed the message said "Stop not sent" and the agent ran on. Against a host advertising agent-session.conversation-stop.v1 the phone now sends the Stop that names no turn, as the desktop already does; the host stops whatever the chat has in flight. A card's Cancel still names its turn, and older hosts keep the turn-named Stop. A background-task Stop whose host could not read the session's task list read the failure as "no tasks" and answered cancelled:false. It now refuses as outcome unknown, the refusal a timed-out background stop already returns. Adds host tests pinning the Stop-before-echo races on the unnamed Stop: Codex turns admitted or started before the echo, a later send just before the Stop, a retried Stop, and a Stop after a failed one. All pass on main unchanged. * fix(mobile): show Stop before the turn opens; a card's Cancel keeps its turn The phone offered Stop only once a turn existed, so the Stop that names no turn was unreachable while a send waited for the agent's echo. Against a host that advertises conversation stop, a working chat now offers Stop. A card's Cancel chose the untargeted form from the prompt after the prompt-cancel capability filtered it out, so against a host with conversation stop but no prompt cancel it sent a Stop naming nothing. It now always names the card's turn. The race test for an admitted turn that cannot take an interrupt accepts success only once the host has let go of the agent, and otherwise requires the unconfirmed refusal. * ci: rerun against current main * ci: rerun against current main |
||
|
|
8be17ddf3a |
feat(chat): host writes one saved row per subagent (#27947)
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * feat(chat): host writes one saved row per subagent Claude, Codex and Grok now only report child-work evidence. The host writes each subagent's row from its child record, so the chat row, strip and sidebar read one lifecycle. Deletes the shared subagent tracker, the per-agent rosters and their caps. An accepted Stop settles only the run it was sent to, provisionally, until that run reports its own ending. Work the host store has no room to record keeps the running-tasks strip and its Stop instead of reading as idle. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * fix(chat): subagent row writer reads the live turn once, stop acks survive a failed batch - the row writer reads the journal's live turn at most once per change and only for a subagent with no saved, queued or in-flight row; a revision keeps its first scope - a background stop names each accepted task as its answer lands, so a stop-all batch that times out or fails part way still ends the runs it was answered for (Claude and Grok) - the strip's untargeted Stop rule lives in the shared view (offersStopAll); both strips inline it again; incomplete tracking no longer holds commands or rewind - clients admit a frame whose only change is incomplete tracking - drop the Claude stop's dead publish; the real-CLI stop test asserts the run's own ending - list the writer test with the real SQLite tests * fix(chat): a late Grok report never revives an ended helper; Codex keeps announced helpers - Grok: once a helper's run ended, a later progress or repeated spawn sends no live evidence, so its record and saved row stay ended after the next send retires the record; the test rig now retires finished records on each turn open, as the host does - Codex: past the bound, only a child that ran and ended is evicted, so an announced helper that has not started keeps its record, row, Stop and parent - cut: Codex parentTurnId (written, never read), the Claude identities' turn-end forwarding (call the corrections directly), the duplicate Grok outcome map - update stale comments that described the deleted producers * test(chat): Claude frames to saved row, kept Codex path and usage rules, agent ingestion exemption - a captured background Claude helper keeps its saved row working past its spawn result and the parent turn's end, through the real adapter, host and child records, until its own end - restore focused tests for the Codex root path, label trim and placeholder, usage before announcement and parent usage - the per-session live limit test uses tasks: subagents are exempt from it * fix(chat): incomplete tracking follows the refused children, and the strip's Stop reaches them - the host remembers which children the store refused for room, by handle; each leaves when a later report of it is admitted or it reports its own end, so incomplete clears on a truthful signal; the provider session's end still clears all, and past 512 a flag holds until then - a parent row the store refused counts every live child it reports as refused - with work no record lists, an untargeted strip Stop while no turn runs ends the agent through the idle sweep's stop; under a running turn it leaves the agent alone - the background Stop moves into its own module * fix(chat): the incomplete Stop is offered for every agent and decides inside its step - the strip offers the untargeted Stop for work no row lists whenever no turn runs, whatever untargeted stop the provider has (ACP agents, Codex before it proves terminal stops), since the host reaches it by ending the agent; never under a running turn - the Stop reads whether work is unlisted and whether a turn runs inside its own step, after the listed stops land, so a wake-up turn opened meanwhile is never ended - Codex executions past the bound evict the oldest ended child, else the oldest never started - test the writer's unnamed-subagent label; qualify the writer's retry comment * refactor(chat): the subagent row writer joins the views with their records Drops the shared host-only row projection and its hook-server read: the writer's wiring reads the views every surface reads and the records they come from, and takes residency and the ending's basis from the record. * refactor(chat): Grok subagent evidence reads its ending through the one outcome map * refactor(chat): a subagent's group entry as one object * fix(chat): a background Stop with nothing listed asks the agent for no task stops So an agent with no stop route (an ACP agent the probe found none for) never refuses before the Stop ends the work no row lists. Tests: the unlisted case is untargeted and reads unlisted work inside the step; the repeat-press rig lists the task its Stops reach; reflow the writer header. * test(chat): a nested helper's saved parent; describe how incomplete tracking clears * fix(chat): a closed session claims no Stop; a helper's row follows its own running work - a session close Orca proves is loss of contact, not a Stop the CLI accepted: Claude no longer ends what still ran as stopped, so the host settles it unknown and its row reads unverifiable, as before this stack; stop-acknowledged comes only from a Stop the CLI accepted - a helper that finished while work it started still runs is written working, by the strip's own rule; once that work ends, or the session ends, it reads its own ending again - the CLI starting a completed helper again under the same spawn call is the helper waking when its work ends: the decoder reports it as a restart, so its one row revises in place and ends with its real duration; after any other ending such a start is still read as late - tests from a captured 2.1.280 frame order; item 4 Stops pin their stop-acknowledged basis * refactor(chat): drop the unused Stop basis on a child's own ending Only the acknowledged-Stop edge writes that basis now; reflow the decoder header. * fix(chat): an older history page keeps a turn's subagent ordinals and row A page now reaches back to the first sibling of each subagent group it holds part of, at most one more page of conversation, so loading the older page no longer renumbers same-named helpers or moves (and closes) their folded row. |
||
|
|
26cd1c22da |
fix(mobile): load markdown tabs from any desktop split group; center load-error Retry (#28132)
* fix(mobile): load markdown tabs from any desktop split group and center load-error Retry The desktop answered markdown.readTab only for tabs in its focused split group, so a phone opening a markdown tab from another group got tab_not_found and showed "Couldn't load markdown". Resolve the tab across all of the worktree's groups. Load-error states now share one component that centers the message and its Retry button; the markdown reader's button was pinned to the left. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(renderer): mobile markdown read picks the requested tab across groups Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(renderer): resolve mobile markdown tab to one file id Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
ebfae6a5e9 |
Speed up Mobile Checks by overlapping typechecks and tests (#28127)
Overlap mobile typechecks with tests and avoid repeated filesystem probes and unused device substitutes in recording replays. Keep state isolated between loaders, give exhaustive snapshot cases an explicit execution deadline, and retire obsolete locale keys that blocked repository validation. |
||
|
|
94d2462379 |
fix(mobile): copy complete chat replies and loaded markdown (#26152)
* fix(mobile): make message and markdown source copy reachable * fix(mobile): keep copy failure feedback during streaming * fix(mobile): complete source copy on paired web pages * Align mobile source Copy controls with desktop * Update chat status test for icon-only Copy * Preserve literal source when copying own messages * fix(mobile): show a message's copy button only after tapping it Like desktop's hover-only copy, the chat message copy button stays hidden until the message is tapped; another tap hides it. The tap target is not a single accessible element, so screen readers still reach the text inside. * fix(mobile): a held press on a message selects text without toggling Copy On iPad the release of a long-press selection, or of a selection-handle drag, also counted as a tap and toggled the message's copy button. Claiming the long press keeps only quick taps as toggles; native selection is unaffected. * test(mobile): load the web clipboard in the copy-button test the way other tests do vi.importActual with a relative path resolved from the mocker under CI's runner and failed to find ../platform/clipboard.web; a plain import() in the mock factory resolves from the test file. |
||
|
|
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. |
||
|
|
6440d3c520 |
fix(mobile): keep a not-sent message in place on the phone, using the desktop's logic (#27936)
* fix(native-chat): list not-sent rows at their journal places The shared projection appended rows the host recorded but never delivered after every other row, leaving the reader to sort them. A reader that draws the list as given showed an old not-sent message under newer turns. Merge them in at their journal places, after the stop rows are built, so the list is already in order. The desktop sorts by the same order, so what it draws is unchanged. * refactor(native-chat): move the recorded-rejection line into shared words The desktop's words for a message the host recorded and then rejected now live beside the other rejection words, returning notice parts each client renders its own way, so the phone can say the same thing. The desktop calls it with the same inputs; its words are unchanged. * refactor(native-chat): one projection behaviour for rejected rows Every reader now draws a message the host recorded and then rejected where it was sent, marked not sent, so the rejectedInPlace option goes. The phone was its last off caller apart from the host's conversation outline, which now leaves not-sent rows out itself; a test proves the outline is the same as if the rejected message had never been sent. * fix(mobile): show a not-sent message in place with the reason under it The phone now draws a message the host recorded and then rejected where it was sent, as the desktop does, with one small line under the bubble in the desktop's words. The line is built from the journal the view already receives; when a start-failure or sign-in row already gives the reason, it says only that the message was not sent. The load-earlier header moves to its own file to keep the view under its line limit. * fix(mobile): a send the host recorded is never handed back When the send's own reply said the host recorded the message and then rejected it, the phone put the text back in the composer with a banner, though the host and the desktop keep it in the chat. A recorded send now settles like any other: its row draws it in place as not sent (or its kept card holds it), and the composer stays empty. The banner's failure-fact plumbing, fed only by that branch, goes with it; the transcript states the sign-in guidance once. * fix(mobile): keep each structured send on the shared optimistic list Once a not-sent row stays in the chat, matching a pending bubble by its text breaks: resending the same words after a not-sent message never reached the count the bubble waited for, so a stray copy stayed, and an acknowledgement lost on that resend showed a false "Delivery unconfirmed". A structured send is now one record, kept by the structured session under the id it is sent with. Three parts match the desktop's sender: the id, the drawing (the shared projection draws it at the tail until the host's row under that id lands, in the same pass) and the settle rule (the host's own record of it, a submission, a card, or a card's hand-off, drops it by the one rule the desktop now shares). The rest stays the phone's own: the bubble shows once the host accepts the send, not from the tap; a send whose answer was lost is held undrawn and says "Delivery unconfirmed" only if its deadline passes first, instead of going back to the composer; records are kept whether or not a chat view is open; and one send in flight is the composer's rule, not the sender's. Its photo is kept under the row id it shares with the host's row, so it shows on the bubble, the landed row and a not-sent row. The drafts' text matching, with its older queued-card matching, now serves only terminal-backed chats, whose transcripts carry no ids. * fix(native-chat): merge not-sent rows by the transcript order alone The merge kept a rule for rows with no journal place that no caller reaches and that the desktop's sort does not have; drop it, and say what merging after the stop rows actually keeps. A test pins that the list the phone draws as given is already in the desktop's sorted order, with a moved stop row, a retry run, a cut-turn notice, a held card and two not-sent rows. * fix(mobile): draw the not-sent line as the desktop does Red and capped to the bubble's width, like the desktop's line and the phone's own returned-card caption, and only when there is a notice: the row had a second, unreachable source of words. A send a Stop took back is no failure, so it no longer rebuilds the notices on every streaming frame. |
||
|
|
6af34a4d99 |
fix(chat): name the agent when it refuses a message (#27946)
* refactor: move provider-retry pieces into a sibling words file Pure move of the six provider-retry translation pieces out of agent-session-failure-words-text.ts, which sits at the 300-line limit. No key or behaviour change. Co-Authored-By: Claude <noreply@anthropic.com> * fix(chat): name the agent when it refuses a message "The provider did not accept this message" becomes "{{agent}} didn't accept this message" ("The agent" when the name is unknown), in English and every locale. The providerRejected failure kind is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
fe1f0223af |
Native chat: read one saved row per subagent (part 1 of 2) (#27914)
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * refactor(chat): a subagent's group entry as one object |
||
|
|
7cb30b7f8f |
feat(mobile): let the OTA page send extra top-level request fields (#27815)
* feat(mobile): let the OTA page send extra top-level request fields A page request could only carry method, params and three fixed options, so any new top-level wire field needed a new app binary. Requests and subscribes now carry an optional verbatim `envelope`, gated by a new `rpcEnvelope` grant; nothing sets it yet. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): one envelope merge seam per transport Review follow-up: the direct tracker and registry now hand frames to a single sendFrame that adds the credential and envelope, like the relay; page refusals use one fixed code per error class and an envelope-only options bag posts no options. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): refuse a non-object envelope before treating it as empty null threw synchronously and [] or a Date read as empty and was dropped; the schema now runs first. The tracker tests settle their requests instead of leaving timers. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): type the direct socket's request as the wire request The typecheck now proves every direct request carries the transport fields, and the native-verb fence says why it drops an envelope. No behaviour change. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
50b3d2e34c |
fix(mobile): accept a fresh terminal snapshot after a stream ends (#27539)
* fix(mobile): accept a fresh terminal snapshot after a stream ends Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop the native chat resume's duplicate snapshot-mark clear Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop the native chat stream's snapshot-mark wiring Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): drop the refit adapter's dead snapshot-mark argument Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
28d07a6b63 |
fix(mobile): open Grok, OpenCode, OMP and Pi chats on the phone (#27212)
* fix(mobile): open Grok, OpenCode, OMP and Pi chats on the phone The phone advertised only the Claude chat lane, so the host retitled every other agent's chat tab 'Update to view' and the phone's resolver refused non-Claude/Codex chat tabs anyway. Mobile now reads any agent the host lists, passes the chat's real agent to its send and slash-command paths, advertises the registered-agents and Pi dialog capabilities, hides image attach for agents the host says take none (as desktop does), and draws Pi's input dialog as one line. * test(mobile): drive the agents read through the shared fake client * fix(mobile): read options reports with no model Grok, OpenCode and OMP can report no current model, and a chat at rest may have none saved. The host refuses those reports to clients that don't advertise agent-session.options.optional-model.v1, which left the phone's model picker on its seed list and dropped /clear and /compact. * fix(mobile): reserve the chat session for every launched agent The phone matches a launched chat tab only by its session id, which it minted only for Claude and Codex. A Grok, OpenCode, OMP or Pi chat started from + landed only once the host replied, and a lost reply read as "Couldn't confirm the agent started" even with the chat listed. Hosts accept an agent-named id for any agent and ignore it for a terminal launch. * fix(mobile): read the host's agents once per connection, retry after a failure Every session-screen mount asked agentSession.agents again, so the attach button popped in late for OpenCode and Pi, and a read that failed while connected was never retried. Share one read per client, drop it on disconnect, and forget a failed read so the next chat asks again. * test(mobile): pin the chat's own agent on the send path and attach gating The send-bridge test passed with the claude/codex narrowing reverted. Drive an OpenCode chat through the controller and the structured session's send, where /review is the agent's own command but Codex's host claims it, and check the overlay offers attach only when the agent takes images. * fix(mobile): offer a card's X only while a turn is running The X on a structured prompt card stops its turn. Pi opens dialogs outside a turn too, and tapping X there could only say "Stop not sent". Claude and Codex cards arrive inside a running turn, so they keep their X. * fix(runtime): let a phone read the host's registered agents The phone now asks agentSession.agents to decide whether a chat's agent takes images, but the mobile allowlist refused the method, so OpenCode and Pi never got an attach button. It returns only each agent's id and its fixed capability record. * fix(mobile): keep a host's refusal of the agents list for the connection Released hosts advertise registered agents but refuse agentSession.agents to phones (forbidden, not available to mobile clients). Treat that answer as no list until the next connection instead of re-asking on every chat; keep retrying transient failures. * fix(mobile): shrink the model pill before it pushes Send off the composer row On a narrow phone, attach + a long model label + mic left no room for Send (seen with Pi's resting label). The pill's slot is now the row's only shrinkable item, so its label truncates and the buttons keep their size. Rows that already fit look the same. * fix(mobile): keep /clear and /compact for a chat whose agent lists no models The phone dropped a structured chat's option controller when its picker had nothing to show, and the / menu reads the chat's conversation commands from that controller. OMP lists no models, so its menu lost /clear and /compact. Keep the controller for every structured chat; the picker still renders nothing without a model. * feat(runtime): say in a chat's options report whether it takes images The host's options read now carries acceptsImages for every session: the agent definition's imagePrompts, narrowed by a running ACP child to what its initialize advertised (the same rule its sends use). Absent means an older host. * refactor(mobile): read whether a chat takes images from its options report The phone no longer reads the host's agent list for the attach button. It takes acceptsImages from the agentSession.options read it already makes, keyed to the chat, and kept through a pick made during that read. Before the read answers, and from an older host, Claude and Codex keep attach and other agents don't, as before. Drops the agent-list hook with its cache and refusal latch, the host-support flag only it used, and agentSession.agents from the host's mobile allowlist. * refactor(native-chat): gate desktop chat images on the session's options report The composer's image gate takes the session's acceptsImages answer first, falling back to the host's agent list before the options read lands (a new chat's start) and for an older host that sends no answer. With the phone no longer using it, structuredAgentAcceptsImages moves back to the renderer, where it was. * test(native-chat): prove the session's image answer reaches the composer gate |
||
|
|
0ba67e1181 |
fix(mobile): agents for workspaces another runtime owns show why (#27196)
* fix(mobile): agents for workspaces another runtime owns show why The paired host no longer lists its own agents for a workspace another Orca server owns: preflight.detectAgents refuses with workspace_on_other_runtime, and the phone shows 'Agents on Another Server - Pair that server directly'. The phone also recognises an unambiguous runtime-owned repo row itself, so an older host is never asked. * fix(mobile): a shared repo id with a runtime-owned row defers to a refusing host Rows on several hosts can share a repo id, and the first row's SSH connection could name the paired host's own target. When any row is runtime-owned the phone now asks a host that advertises preflight.other-runtime-refusal.v1 to decide by worktree, and shows the other-server row on an older host. * fix(mobile): keep the agent-loader change inside the web bundle budget Move the refusal code beside its capability in protocol-version, read the capability in the loader, and test the runtime: prefix directly: importing execution-host into the loader split a web bundle chunk (129 > 128 assets). --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
d91921a9c6 |
Let a Grok chat list and stop the commands it left running in the background (#27022)
* Let a Grok chat list and stop the commands it left running in the background A command Grok backgrounded (a dev server, a watcher, a monitor) showed only as a transcript line: it never reached the background-tasks card above the composer, so it could not be seen as running or stopped from Orca. Grok's subagents and Claude's and Codex's background commands already list and stop there. The ACP lane that publishes Grok's subagents as child records now also publishes its background-task rows: a running task as a live background command or monitor, an ended one as removed, derived from the rows the journal admitted. A Stop for one of them goes through the same adapter route to Grok's own per-task kill (x.ai/task/kill); Grok's completion notice ends the row, and a task Grok no longer holds is removed at once. A refusal reports the task as not stopped; no answer or an unreadable one leaves the outcome unknown. Each Grok process is checked once at start with a no-target kill, beside the existing subagent check. Only Grok naming the method unknown hides Stop. * Settle a Grok task's transcript row when Stop finds it already gone When Grok answered a Stop with already_exited or not_found, the card row was removed out of band while the transcript row kept saying it was running, and a later restated running notice could bring the card row back with Stop. The answer now settles the transcript row through the translator, like the subagent Stop, so the card row leaves because its row settled, the existing no-relight rule keeps it gone, and a completion Grok still sends after already_exited writes the real result. Also covers /clear being refused while a Grok background command runs. |
||
|
|
8452fc3315 |
Keep tool results with the right command and reveal all results on mobile (#26968)
* Pair native chat tool results in linear time with provider identity * Preserve orphan tool output and independent disclosure identity * Keep mobile tool detail attached to its command after row changes * Keep named mobile results open across copied history refreshes |
||
|
|
cbc565d804 |
Let a Codex chat stop a sub-agent from the background-tasks strip (#27026)
* Let a Codex chat stop a sub-agent from the background-tasks strip A Codex sub-agent showed in the background-tasks strip with no Stop, and the chat's own Stop interrupts only the main turn, so a helper ran on until it finished. Codex's app-server takes `turn/interrupt` for any loaded thread, sub-agent threads included, so a sub-agent row's Stop now interrupts the turn Orca saw that sub-agent run, through the same background Stop route Codex commands use. Codex's -32600 refusal (no active turn, another turn, thread not found) means the run the row showed is over, so the Stop is quiet; any other refusal says the task wasn't stopped; a timeout or lost contact is unconfirmed. A live run showed Codex holds an interrupt that lands as the turn ends until the sub-agent's next turn ends, so a timeout on a run the session has since seen end is quiet too. Older clients' `codex-agent:` rows name no host record and stay unstoppable. * Stop a Codex sub-agent's own sub-agents and commands with it, and hold Stopping per run A sub-agent Stop interrupted only that helper's turn, so the commands it was running (Codex keeps a turn's processes when the turn is interrupted) and the helpers it had spawned kept going, and its row stayed in the strip. The Stop now interrupts the helper's run and every descendant's run, then terminates the processes those threads hold where the app-server has background terminals. The interrupt goes through interruptCodexTurn, and a small codex-background-task-stops module composes the sub-agent and terminal stops, so the terminals module is terminals only again. A Codex helper keeps one row id (its thread id) across runs, so a confirmed Stop's held button carried over to the helper's next run. Both clients now hold it for the run it was pressed on, read from the row's invocation. * Test that a sub-agent Stop leaves siblings alone and skips terminates after a timeout Adds direct tests of the per-run Stop hold (including an older host's roster), matches the desktop hold comment to mobile's, and reuses the one 10 s bound. |
||
|
|
c8b8997762 |
feat(native-chat): offer Codex Ultrafast as a speed choice (#26962)
* feat(native-chat): offer Codex Ultrafast as a speed choice Codex lists Ultrafast as a third service tier for eligible models and accounts (GPT-6.1 Sol). Codex chats saved Fast as on/off, which cannot hold a third tier. The Codex speed is now a Standard/Fast/Ultrafast choice built from the tiers the model lists; the tier id is still resolved from the catalog at each send. Older clients keep their Fast toggle: the host still reports Fast on/off and accepts it as a speed. * refactor(native-chat): save the Codex tier id instead of a speed name The chat now keeps the tier id Codex lists (serviceTier: default, priority, ultrafast) and sends it as-is on turn/start; Codex drops a tier the model no longer lists with its own warning. This removes the saved per-model tier map, the send-time lookup and the reconcile code. The unused fastModeTierByModel catalog field is deleted. Older clients still get Fast on/off, derived from the model's tier named Fast. Also drops the pnpm-lock.yaml churn the first commit picked up. * test(pi): drop the removed fastModeTierByModel catalog field * fix(native-chat): review round 1 for Codex service tiers - Accept an older client's fastMode write through the Codex adapter; it was refused before reaching the tier alias. - Bump the saved model catalog schema so catalogs from before serviceTiers re-list instead of offering a Fast toggle with no tier. - Keep only the reported tier on the record, not the Fast it implies. - Drop a reported tier, not only a picked one, when the next model does not list it. - Localize Standard inside the speed choice only. - Return the options answer directly instead of a one-field wrapper. * fix(native-chat): review round 2 for Codex service tiers Keep a tier Codex reports for the thread (it accepts unlisted ones such as flex); read the legacy Fast tier beside an empty serviceTiers list. * fix(native-chat): review round 3 for Codex service tiers A reported tier the old model listed and the new one does not resets to Standard on a model switch, and the reported tier itself stays pickable. * fix(native-chat): keep a re-picked reported Codex tier The thread's reported tier is never reset by the unlisted-tier drop, so a re-picked Flex survives the next read, a model switch, and the turn. * refactor(native-chat): one rule for which Codex tier a model offers Standard, a tier the model lists, or the thread's reported tier that no model lists. The setter and the unlisted-tier reset share it, replacing the separate reported-tier and model-switch guards. * test(native-chat): drop the removed fastModeTierByModel field from main's new catalog test |
||
|
|
d3e3b173e6 |
fix(agents): one host-routed agent-detection entry point (#27054)
* fix(agents): one host-routed agent-detection entry point
preflight.detectAgents / refreshAgents take an optional { worktreeId } and the
host resolves that workspace's project runtime (WSL included) or SSH target.
Gated by preflight.workspace-scoped.v1; old peers keep the host default.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): route detection by the worktree's own host record
A repo id shared by local and SSH rows made the first row pick the detection
host. Resolve the worktree from the host's list and apply the shared
execution-host routing rule; ambiguous rows refuse. Dashboard falls back to the
host-default list until a workspace's own list loads.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): refuse an ambiguous workspace instead of probing the host
Only selector_not_found keeps the host default; selector_ambiguous now fails
closed. Adds runtime-level coverage of the lookup.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(agents): refuse an ambiguously owned folder workspace
Co-Authored-By: Claude <noreply@anthropic.com>
* test(agents): quick-launch runtime target carries the workspace
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
8b3de5bbfb |
fix(native-chat): drop the redundant 'Waiting for your answer' line from queued cards (#27073)
The open question below a queued card already says the agent is waiting, so the card's own caption said the same thing twice. A card behind an open question is now plainly queued (the 'turn' hold): same Steer, Delete and More actions, same sending rules, no caption. The 'awaiting-answer' hold and its pending-prompt input fed only that caption, so both go, on desktop and in the phone app, along with the now-unused locale key. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9764dc8b97 |
fix(mobile): keep chat drafts and the sidebar reachable on iPad (#26096)
* fix(mobile): keep chat drafts and the sidebar reachable on iPad Chat composer drafts now live in a module-level store keyed by host, workspace and tab, so leaving the session screen no longer drops them. On iPadOS 26 windowed mode the window buttons covered the top-left header controls; a native launch hook adds their height to the root safe area. A hidden sidebar now shows a Show sidebar button in the session header. * fix(mobile): preserve draft edits across chat remounts * test(mobile): type sidebar header fixture as a partial controller |
||
|
|
e2234a37ef |
Let a Codex chat stop a command it left running in the background (#26780)
* Let a Codex chat stop a command it left running in the background A command Codex started and left running (a dev server, a watcher) showed in the background-tasks strip above the composer with no Stop, because Codex reported no stop it could honour. Codex 0.140+ has one: the app-server's experimental thread/backgroundTerminals/terminate names the process by the processId on the command's item, and thread/backgroundTerminals/list shows what still runs. The command tracker now keeps each command's processId. The first time a running command carries one, the session probes its own app-server once with a one-row list; a listing marks the app-server's backgrounded commands stoppable and the session as offering per-row Stop and Stop all, through the fields the strip already reads. Any refusal (an older Codex answers "unknown variant"), timeout or malformed answer leaves it as today: no Stop. A Stop terminates each named process, then confirms against the thread's own list. A thread Codex says it unloaded counts as stopped; a dropped connection does not, since lost contact is no proof a process ended. An app-server that times out is not asked again. The killed process's own item/completed removes its row, as before. * Ask Codex again after a probe gets no answer, and say when a background Stop fails A background-terminal probe that timed out or lost its request used to hide Stop for the rest of the app-server's life. Only a refusal by name (unknown variant, method not found) or a malformed listing now settles it as unsupported; anything else is asked again on the next update. A background Stop whose agent failed (a process still listed after its stop, a timeout) used to answer "nothing stopped" and the button simply came back. The host now refuses it, so both clients show their usual notice: "The background task wasn't stopped." This covers Claude's background Stop too. * Say a background Stop wasn't stopped only when the agent shows it still running A timeout or lost contact with the agent leaves the outcome unknown, so it now answers with the existing outcome-unknown refusal instead of "The background task wasn't stopped." * Keep a background task's Stop in its stopping state until the row leaves A confirmed Stop re-enabled its button for the moment between the host's answer and the roster dropping the row. The button now holds while the host says it stopped the task and the strip still lists it, on desktop and phone; a failed, unconfirmed or nothing-stopped answer, or the row coming back, offers Stop again. * Keep Grok's own subagent Stop refusal quiet, and a reply about another subagent unconfirmed * Report Grok's own refusal of a subagent Stop as not stopped Grok answers an ended or unknown subagent with a normal reply, so a JSON-RPC refusal means it did nothing and the subagent still runs. |
||
|
|
573ff0bca3 |
Save a new chat's first message during create (#26332)
* fix(native-chat): the host owns the send queue; the window keeps no saved outbox The desktop kept each structured chat's unsent messages in localStorage and sent them in order, so one message whose fate was unknown froze every later send, Retry dropped it silently, failed sends could not be discarded, and an offline chat could send hours later. The host already records every message and owns the queue; the window now only sends. - One in-memory sender for composer, launch prompts and messages sent from outside the chat. One send in flight per chat; a transport failure resends the same id for up to 30 s; a refusal that proves nothing was recorded puts the text back in the composer with the reason; a send that went out and was never answered shows an in-doubt line with Send again and holds nothing up. - The host's "unknown" rows get the same in-doubt line and Send again, from the journal, in every window. - A message an older build left in localStorage is never sent: the host's conversation outline decides what goes back to the composer, and the copy is deleted once that is saved. * fix(native-chat): send through the structured chat RPC wrapper, with its timeouts The sender called the runtime RPC directly, skipping the per-method timeouts every other structured chat call gets. Only a remote host's request skips the compatibility check the sender already ran. * fix(native-chat): an unconfirmed send goes back to the composer, with no new row line The common pattern draws nothing extra on a message whose delivery is in doubt once its turn is over, and puts a failed send's text back in the composer with the reason. So a send nobody answered in time, or one a host answers in a way that proves nothing, goes back to the composer worded as unconfirmed, and a host "unknown" row shows nothing extra. Removes the in-doubt phase, Send again, and its strings. A host's made-up row for an id its journal lost now reads as unconfirmed, not as recorded, so that message comes back instead of vanishing. * test(native-chat): pin that nothing resends a send given back as unconfirmed * fix(native-chat): sends survive a tab close, never resend after a Stop, and keep remote images - A normal tab close lets sends on their way settle; what the host never took goes back to the conversation's draft. Only a cancelled launch or a worktree purge drops them. - After any Stop, a send already on its way is never sent again under its id: a doubtful answer, or a resend that was due, hands its text back worded as unconfirmed. - A returned image keeps the SSH connection it lives on; the connection never goes to the host. - An older build's saved message the host recorded and then rejected is left to the host's own row, never handed back. * fix(native-chat): a Stop or a tab close never resends a send already out, and a kept card is the card's A Stop that landed while a same-id resend was being readied (its timer fired, its request not out yet) let that resend go out after the Stop. The sender now tracks whether a request is awaiting its answer: a Stop hands back every send between attempts as unconfirmed, lets one whose request is out settle from its answer, and never issues a request after it. A normal tab close withdraws the same way instead of doing nothing, so nothing more goes out and nothing is dropped. A send the host rejected but kept as a card (keptAsQueuedMessageId) is the card's, from its reply or the journal, and never goes back to the composer. Adds the freeze tests: a send whose fate is unknown holds later sends no longer than its deadline, and one the host can neither confirm nor deny releases the next at once. * fix(native-chat): read a resend's turned-away call or reused id as unproven Ports the send-answer proof contract. A call the host turned away before running it (method_not_found, invalid_argument, unauthorized) proves only that this request wrote nothing, so it reads as never sent on a first attempt only; on a resend an earlier attempt may have landed, and it goes again under the same id. A resent id the host says was already used for other content (messageIdReused) proves nothing either, like an expired or conflicting id. Pins the rest of the contract: any row the host returns is its own, a thrown error is no answer whatever its code, and an older build's refused, held or outlived-Stop copy is handed back once and never sent. * refactor(native-chat): type the structured composer's send with its attachment type Keeps NativeChatStructuredSession.tsx within the file length limit. * refactor(native-chat): drop the kept-card guards the sender never needed A kept send's row is a rejection that was never a Stop's, so the sender already reads it as recorded, from its reply or the journal. The test that pins it stays; the two extra checks only covered a kept row that is also a withdrawal, which the host never writes. * test(native-chat): route launch tests' sends through the client wrapper the sender calls The sender sends through callStructuredAgentSession, but these launch tests replaced that module with a factory that answered nothing (and still named a probe that no longer exists), so every launch prompt resent until its 30 s deadline and the tests timed out. Each factory now hands sends to the runtime RPC mock the tests already answer, and expectations of a local send no longer ask for the remote-only compatibility option. * fix(native-chat): hand a message back without importing the composer's attachment hook The worktree purge reaches the launch prompt, which hands text back, and the attachment hook's imports reach the store. A test that builds the real store behind a mocked one then waited on itself and hung. Handing back now writes images to the draft store directly, as the hook's helper did, and a test pins that each returned image keeps its SSH connection. * fix(native-chat): one send per chat, with no line of sends behind it A chat with a send out took further messages into an in-memory line and sent them one by one. A message typed behind one in doubt then hit its own 30 s deadline and came back as not sent without ever going out. Now, as the common pattern does, a chat takes one send at a time: while it is out, Send is disabled and Enter leaves the text in the box. The sender refuses a second send instead of lining it up, so the 30 s deadline always runs from the send itself. A Stop or a tab close stops the one send: settled from its answer if its request is out, handed back as unconfirmed between attempts, or silently if it never went out. Notes sent from outside the chat while its send is out stay with their sender (not ready); a launch prompt that meets the person's own first message waits in the composer instead of being lost. * fix(native-chat): give a Stop-withdrawn note back to its chat once its notes were cleared Notes sent from outside a chat clear once their message is recorded. A message the host recorded as pending and a Stop then withdrew came back only to its sender, which had already let go of it, so the text was lost. The chat's draft now takes it, as an earlier build's outbox did. * test(native-chat): type the composer-actions probe without a cast * chore(native-chat): drop outbox wording left in comments and an empty locale group * fix(native-chat): pace failed checks, keep the host's reason, and hold the chat for its launch prompt - A send whose checks failed before its request went out (an unreachable or incompatible host, an unreadable history) was tried again at once, about a thousand times a second for 30 s. Attempts are now paced by the attempts made, whether or not their request went out. - A host that refuses every resend by throwing (native chat turned off, a journal that won't open) gave back only "couldn't confirm". The host's reason now comes first, still without claiming not sent. - A launch's prompt now holds the chat's one send from the click, drawn as sending, so a message typed while the chat starts can't overtake it; it goes out once the chat exists, and a cancelled launch frees it. - Notes whose send nobody could confirm say so instead of "did not accept", and notes launched into a new chat let go of their text once that chat's composer holds it. - An open chat keeps drawing a recorded send until its row arrives, so a reply that beats the history no longer makes the message flicker out. * fix(native-chat): hold a chat's sends until it has started, and send a failed chat's message with its restart A message sent to a chat still starting went out at once to a host that had no record of the chat yet, read for a fence it could not get, and came back as not sent. A message sent to a chat whose start failed restarted it but no longer went with the restart. While a chat starts, Send stays off and Enter leaves the text in the box, as with a send already out. A text message sent to a chat whose start failed restarts it and goes as the restart's first message, through the same staged-prompt path a launch prompt takes: it holds the chat's one send slot, drawn as sending, until the chat exists, and comes back to the composer if the restart fails again. Notes wait while a chat starts and ride a failed chat's restart, keeping their text until it is sent. * chore(native-chat): test the queue request, rejection words and card hand-offs; drop an unused clear - Pin which sends ask the host to queue, the moved rejection wording, and that a queue send whose card was handed off and then refused or withdrawn, or whose replay names a withdrawn card, is never handed back. - The send-at-most-once gate now says what the desktop promises after a reload: it never sends the id again, and hands the text back. - The legacy read names when it goes, and drops the notice clear nothing called. * fix(native-chat): keep a sent launch prompt's entry, and give back at once what can't go out - Cleaning up a launch prompt released its send slot even after the prompt had gone out, which deleted the entry the sender keeps once the host records it. A launch prompt recorded as pending and then withdrawn by a Stop was lost from both the chat and the box, and an open chat dropped the new chat's first message before its row arrived. The slot now gives back only a reservation that was never sent. - A send stopped before its request went out by something trying again won't clear (this client and the server can't talk, or the host refused the history read) kept Send off for 30 s and then said Orca couldn't reach the agent. It now comes back at once with its own cause: not sent, since nothing went out, or unconfirmed if an earlier attempt did. Transport errors keep the paced retries. * fix(native-chat): notes keep their own text through a new agent's launch Notes sent to a New agent whose start failed came back to the notes and also sat in the new chat's composer, so sending both delivered the text twice. The notes keep their text until it goes out (they hold it from the click), so the launch now says so and no composer gets a copy, on a failed start or a refused prompt alike. The tests that asserted the copy in the composer pinned the old double ownership and now assert the notes are its only owner. Notes that rode a failed chat's restart and ended unconfirmed now say Orca couldn't confirm them, as notes sent directly do, instead of that the agent did not accept them. * chore(native-chat): the send-once gate says what the desktop keeps across a reload The desktop keeps a send's id in memory only: a send still unsettled at a reload or crash is not resent and not handed back. The coverage notes no longer credit the renderer tests with durable identity across a remount. * fix(native-chat): say once why notes sent to a new agent did not go Since notes keep their own text through a new agent's launch, a prompt the host refused, nobody could confirm, or that found the chat's send taken came back to the notes with nothing said anywhere: the chat shows no notice for text its caller keeps. The notes menu now reports it once, with the toast a send to an existing chat already uses: not accepted, couldn't confirm, or not ready. A start that failed still says so in the new chat instead. * fix(native-chat): retry a history refusal the host says clears, and name it at the deadline A send stopped before its request went out came back at once for any refusal of the history read, including ones the host names as clearing (its journal briefly unavailable, a chat detached while the host quits), so the automatic retry was lost. Only a version mismatch and refusals that won't clear come back at once now; the rest go again on the paced schedule, and if the deadline still finds nothing sent, the words are the host's refusal rather than Orca couldn't reach the agent. * feat(native-chat): a send makes one request, and nothing ever sends it again The desktop resent a message under its own id for up to 30 s when the answer was lost. A send now makes exactly one request, as the common pattern's clients do: - An answer that is lost, dropped or proves nothing hands the text back at once with "couldn't confirm… check the chat". - A failure before the request goes out (the environment check, the history read for the fence, a refused connection) means nothing went out: the text comes back at once with its own reason, or as not sent. - The 30 s cap stays on the one request, so a host that never answers can't hold Send. Nothing ever resends, so nothing can go out after a Stop: a Stop only takes back a send still in its pre-send checks. The resend state goes with it (tries, generation, awaiting, stopped, the resend timer, the send-answers-proof probe, and the first-attempt/resend split in the evidence), and the send-once gate says the desktop never resends. * fix(native-chat): notes keep the chat's line, and a send held behind a rewind says it was not sent - Only a send whose text belongs to the chat's composer clears the chat's line. Notes sent from outside the chat no longer wipe a "couldn't confirm... Check the chat" line that explains text already back in the box. - A send the host turns away behind a rewind it could not confirm went back as "not sent" but said "couldn't confirm what happened. Check the chat". It now gives the rewind's reason and says the message was not sent. - Reliability gate names the single-request test and records a fresh evidence run; the sender test drops its leftover resend mocks and a duplicate Stop test. * fix(native-chat): a send ends even when putting its text back fails If writing the returned text into the chat's draft threw, the send never settled: it stayed "sending", the chat refused every later send until a reload. The send now always ends after a hand-back, the failure is logged, and the chat's line adds "Couldn't save your message." * fix(native-chat): a message typed during /clear stays in the box and follows the chat A /clear moves the chat to a new conversation, and its host refuses any send while it runs. A message sent then went out, came back refused into the old conversation's draft with its line, and vanished when the chat moved on: the box and the line now belong to the new conversation. - A /clear holds the chat's one send slot while it runs: Enter does nothing, Send shows busy and the text stays in the box, as for a send that is out. Notes sent from outside get "busy". - Once it moves the chat, the old conversation keeps taking no send until the view leaves it, and what is left of its draft moves into the new conversation's draft, after anything there: when the composer's /clear settles, and again when the view moves. * fix(native-chat): no "Send message?" or queue clear while the chat's send is out While a send is out (or a /clear runs) the chat takes no message, yet Enter over a held queue still opened "Send message?", and Clear queue deleted every card before its message was refused. Enter now does nothing there and the text stays; Clear queue re-checks and deletes nothing if a send went out after the question opened. * refactor(native-chat): take the /clear draft carry out of this change Moving the old conversation's draft into the one a /clear replaces it with fixes a bug main has too (text left in the box during a /clear stays with the old conversation), so it goes in its own change. Kept here: a /clear holds the chat's send slot while it runs, and the conversation it moved away from takes no send until the view leaves it. * fix(native-chat): a /clear's hold on sends always ends - A view that unmounted while a /clear that moves the chat was out left the old conversation holding its sends until a reload: the late reply kept the hold for a view that was gone. The reply now releases it. - The local call for a conversation command has no deadline of its own, so a /clear that never answers kept Send off for good. The hold now also ends at the command's deadline (195 s, the one the remote call already uses), whichever comes first. * test(native-chat): opening a chat an older build left stuck; hand back its copy in send order Pins, through the real chat hook, sends, legacy recovery and draft store, what opening such a chat does: the queued messages behind a message the host recorded in doubt come back to the composer once, in order, with the "not sent" or "couldn't confirm" wording; the chat is free to send under its read's fence; nothing happens while the chat has no fence here. The legacy reader now hands entries back in the order they were sent (queuedAt), as the older build's reader did, instead of array order. * fix(native-chat): a send this window turned away for a re-paired server comes back as not sent A managed server's update rotates its pairing, and this window's main process then answers the next call itself, before forwarding anything, with runtime_environment_changed. The send read that thrown answer as proving nothing, so the person was told Orca couldn't confirm a message that never left. It is now handed back as not sent, with the reason. Every other thrown answer still reads as unconfirmed once the request may have gone out. * Save a new chat's first message during create * Align opening-message types with the user-message wire * Keep chat publication and cleanup outside store initialization * Complete the create refusal fixture ledger * test(native-chat): prove a created first message waits for the agent's start and is handed over once * refactor(native-chat): keep the host and session hook under the line limit after the main sync * test(native-chat): a chat created with its first message and no pick still teaches its agent's default After the main sync, a created chat's agent starts from the delivery loop rather than the create call. Grok (through the real ACP adapter and model catalog) and Claude (through the real adapter) each still report the model their own config resolved to once the chat reads its options. |
||
|
|
1f8dce85d4 |
refactor(runtime-rpc-terminal-io): clean up Phase 3-3.8 code (#26895)
* refactor(runtime-rpc-terminal-io): clean up Phase 3-3.8 code - One admission prologue and one unary invocation shared by both RPC dispatchers - Delete rpc schema re-export shims; import from the shared params catalog - Delete the unregistered duplicate markdown tab method module - Collapse multiplex installer stage types; add isMultiplexStreamAttached - One source-identity guard in the ACK recovery snapshot - Single stream teardown in the renderer multiplexer - De-duplicate RPC method declarations; derive dispatch options from RpcContext - Share viewport-frame decoding and one head-trim loop for pending queues - Single sendTerminal call in sendTerminalStreamInput * test: point cross-version and scope checks at the moved browser tab-create schema; run transport parity test under Node --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
7b91405547 |
Let in-page links switch sections inside a chat visual (#26729)
* Let in-page links switch sections inside a chat visual * Make in-page links work in chat visuals on mobile and in WebKit The first fix (an about:srcdoc base in the visual) only reached desktop Chromium. On mobile, the page hosting the visual carried a policy with base-uri 'none', and a srcdoc visual inherits its host's policy before its own, so the base was refused and an in-page link still took the visual off its page and showed it as failed. The mobile web shell sends the same rule as a header. Separately, WebKit (iOS, and the web client in Safari) fires the frame's load event for an in-page link inside a sandboxed frame, and both hosts treated any second load as the visual navigating away and retired it. - The mobile host page and the mobile web shell header now allow only an about: base, so the shell's own base applies; the visual's own policy still refuses any base it declares. - Instead of retiring a visual on its second load, the host now asks the visual's shell after every load. A later load must be answered by the same run of the shell within the plugin panel watchdog's 5 s deadline; no answer, or an answer from a new run (a reload), still retires it. - Tests: unit tests for the policy, the answer reader and the frame's keep/retire rules, and a Chromium + WebKit test of both mobile surfaces that runs in the mobile web app CI job. * Keep any answered later load, and say plainly what the check is for The previous commit tagged each run of a visual's wrapper with a random id so a reload would still retire it. The visual shares a window with its wrapper, so it could read or set that id itself, and a reload only reruns the same Orca-written document, which is not an escape. Drop it: after any load past the first, the host asks once, and any answer on this visual's channel within the wait keeps it; silence retires it. - Comments now say the check is a tripwire for a frame gone blank or to another page; the frame guard (desktop), the host page's policy plus the app's load filter (mobile) and the sandbox are the boundaries. - Visuals get their own 5 s wait instead of borrowing the plugin panel watchdog's, because a 1 s wait retired a busy visual in WebKit. - Tests: a reload is now kept; going blank, or dropping the base and following a link, still retires. The CI routing for the shared wrapper is now pinned in the path-classification test. * Load the visual links check's mobile modules only where they can build The unit shards run every config/scripts test with only the root dependencies installed. This check imported a mobile/ module at load, and transforming it reads mobile/tsconfig.json, which extends a package those shards do not have. The transform error could not be sent back to the test worker under Bun, so the whole shard stopped with "DataCloneError: The object can not be cloned". Import the visual shell and the mobile host page inside the dependency gate, as the session-dictation check already does, so the shards skip the file without loading them. The mobile web app job, which installs mobile dependencies, still runs it in full. |
||
|
|
7b1242ba94 |
[CI] Reduce repeated test work and use pinned Bun for localization extraction (#26806)
* [CI] Reduce repeated test setup and retire source inventories * [CI] Give consolidated floating panel suites distinct titles |
||
|
|
6ba86414bf |
Show model and effort choices for every agent before its session starts (#26407)
* feat(native-chat): one saved model catalog for every structured agent Every agent registration now declares how its models are listed without a session (a probe, or explicitly none). Probes share one supervised runner (15 s budget, bounded output, teardown on every path). Grok lists from initialize (or x.ai/models/list), OpenCode from models --verbose, Pi from --list-models; Claude moves onto the shared runner. ACP option reads no longer copy the running model's effort menu or selection onto every model. Desktop and phone pickers read the same catalog for every agent, with a non-pickable provider-default model pill before any list exists. * fix(native-chat): host names a listing's configured model; live listings saved for every agent - The catalog answer carries an optional listingNamesConfiguredModel field, set per registration (Codex, Grok) and per workspace; pickers name the listed default from it instead of a Codex-only client flag (older hosts fall back). - Grok, OpenCode and Pi live option reads save their model list through one host step, keeping the account's known default and per-model efforts. - Phone reads the host catalog like the desktop and seeds every agent. - Saved picks accept any known agent id (OpenCode, Pi). - ACP agents with no session-free listing register as unavailable; Grok's live effort menu falls back to the session's when the advertised one is empty. * fix(native-chat): discovery and live listings kept apart; every agent's live listing saved by the host; phone-created chats name their model * fix(native-chat): phone new-chat mark clears on the chat's first live answer; Claude's startup listing reaches the host catalog * fix(native-chat): live-listing type lives beside the catalog entry so wire types don't pull the store's persistence into the phone * fix(native-chat): the model picker never waits on a listing; a first listing lands in place * feat(native-chat): the host lists every agent's catalog ahead of use and traces each listing * feat(native-chat): a new Claude chat names the model and effort its config resolves to * test(native-chat): catalog service fakes gain prewarm * test(native-chat): prewarm span recorder without a type assertion * fix(native-chat): project agent config overrides the account default only when it sets a model or effort A committed .claude/settings.json for permissions or hooks, or the settings.local.json Claude writes on always-allow, no longer hides the configured Claude model in that project. Unreadable config still counts; Codex config.toml follows the same rule. * test(native-chat): the Codex picker read and the launch it starts resolve one account key, through the real resolvers Runs resolveHostCodexHomePathForLaunchReadOnly and prepareCodexRuntimeHomeForLaunch as the runtime service wires them, for the system account on the real home, on the shared runtime home, and a managed account. Also scopes the prewarm suite to agents in use. * fix(native-chat): Claude's configured model and effort are saved together behind the project check; prewarm lists only agents in use and stops with its host A live Claude chat no longer stamps its effort onto the account catalog; the effort travels with the configured default, which a no-pick chat whose config resolves to no listed model now forgets. Prewarm lists agents with a saved catalog or a chat, and host teardown (quit included) starts no queued listing and stops running ones through an abort signal the listing runners honor. * test(native-chat): new catalog tests use typed hoisted state and type-only namespace imports * test(native-chat): a prewarm probe sets and clears the sign-in verdict like a read's, and re-checks an aged one beside a fresh catalog * fix(native-chat): an aged sign-in verdict re-probes only from probe paths, not a Codex chat's picker The main merge put the held-verdict check into store.shouldRefresh, which also gates the live Codex picker. A chat's own listing never clears the verdict, so a signed-out Codex whose chat works re-listed model/list on every picker read. shouldRefresh is back to main's rule; the catalog read's probe and prewarm ask probeDue instead. * fix(native-chat): Claude project settings with an effort cap, a main-thread agent or an Anthropic cloud provider count as an override maxEffortLevel clamps every effort including the model default, and agent applies that agent's model (both from the bundled SDK settings type). CLAUDE_CODE_USE_ANTHROPIC_AWS and CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD sit in the SDK's provider-switch list beside Bedrock and Vertex. A project that sets only these no longer shows, or saves as the account default, the account's model or effort. * refactor(native-chat): the catalog store's picker waiters live in their own module No behavior change: pendingListing and the waiter notifications move as they were, which keeps the store under its line limit for the fixes that follow. * fix(native-chat): changing an agent's command or environment re-lists its catalogs and re-checks its notice The settings change called prewarm, but prewarm skipped any catalog still fresh (the catalog key is agent + account, not the command), and a held "not installed" verdict kept its 30-second window. The change now marks that agent's catalogs due and ages its held verdicts, so the prewarm it triggers re-lists them and re-checks the notice. Only agents whose command or env actually changed are touched. * test(native-chat): the sign-in notice tests check the model picker is really usable They checked only that a model entry existed, which a disabled or empty pill also passes. They now check it is settable, carries no disabled reason, and offers the catalog's or the seed's model. * fix(native-chat): a probe the host stops keeps the held notice reason, and a stopped prewarm reads nothing A probe aborted because the host is going away says nothing about the account, so it no longer replaces a held sign-in or not-installed reason with a plain failure. A prewarm after stop returns before reading chat records. * test(codex): the catalog account-key test uses the runtime's real Codex launch-home resolvers The resolvers move to codex-launch-preparation as one export the runtime service spreads, so the test runs exactly what the runtime is handed instead of a copy. * feat(native-chat): keep each host's saved model lists in the renderer A chat picker could only learn the host's saved model list by reading it after it mounted, so its first frame was a guess. The renderer now keeps the host's answers per host and agent: this machine's lists are read once its runtime answers, for the agents its chats were used with; a paired host's again when this client regains contact; and an account or launch setting change drops this machine's lists. Every entry is a host answer. * fix(native-chat): render the model pills once, from the host's list A new chat's composer painted its first frame from the built-in model list: a saved pick that list lacks (Claude's `opus[1m]`) showed as a raw id with no effort pill, then jumped to "Opus (1M context) · High" when the host's list landed. Every new chat painted a guess and then corrected it. Now the first frame reads the host's saved list the renderer already holds, so it is the final frame. Without one, the quiet "Model" placeholder stands until the host answers; a host with nothing saved makes the built-in list usable, naming nothing, until its listing lands. A saved pick is named only from a host or session list that names it. The phone follows the same rule and keeps the host's list across a new fence. * feat(native-chat): answer a saved-only model catalog read The app could preload a host's saved model lists only for agents with a saved pick, because the catalog read starts a listing (spawns the agent's CLI) when nothing is saved. Grok, OpenCode and OMP chats with no saved pick were never preloaded, so their pickers showed "Model" before the host's list. `agentSession.modelCatalog` now takes `savedOnly`: the host answers from what it saved (the entry, configured default and held reason) and starts, joins or re-checks no listing. Its params are strict, so the host advertises `agent-session.model-catalog.saved-only.v1` and clients send it only to hosts that do. An answer also says when its named default holds in every workspace (Grok reads no project config for its model), so a new chat in any workspace can use it on its first frame. OMP stays without a session-free listing: `omp models --json` lists every model without the `enabledModels` filter its chats apply. * fix(native-chat): preload every registered agent's saved model list Grok, OpenCode and OMP chats painted the "Model" placeholder, then the host's pills, because the app preloaded only agents with a saved pick. Now the app reads every agent a host registered through the saved-only read: this machine once its runtime and agents are known, a paired host at connect and on each reconnect, and this machine again after an account or agent-setting change. A host that does not advertise the saved-only read keeps the earlier rule (saved picks only; a paired host's answered agents). An agent the host saved nothing for stays unknown. An answer sent before the host's lists were dropped is not recorded. * test(native-chat): drop a type assertion from the snapshot sync test * fix(native-chat): learn an ACP agent's default from a chat started with no pick Grok's session-free listing named the model `initialize` computed (grok-4.7), but chats ran another (grok-4.6), so a new chat painted 4.7 and then jumped. The listing no longer names a default; a new ACP session with no model pick reports what it runs, and the host saves that as the account's configured default behind the workspace check. A catalog read waits for a recording still in flight, so the chat's own follow-up read names it. * feat(native-chat): check OpenCode and OMP project config before naming their default OpenCode reads opencode.json(c) and .opencode/ in every ancestor of the workspace; OMP merges its .omp settings and other agents' project settings. A workspace whose config sets a model, effort or provider key (or can't be parsed) names no default; any other lets a no-pick chat's model become the account's default, so the next new chat names it on its first frame. * fix(native-chat): re-read the host's list once a no-pick chat reports its model The renderer kept the answer it read before the chat started, so the next new chat's first frame still named no model. A new chat that named no model reads the host's list again once it reports what it runs; a default no workspace can replace also serves chats in other worktrees. |
||
|
|
56cdd678fc |
fix(native-chat): read a queued message in full (#26004)
* fix(native-chat): read a queued card's whole text, and keep Edit from hiding it behind a prompt A queued card showed one clipped line, so a long message (often another agent's, never typed by the reader) could not be read before Steer or Delete. A clipped card now opens in place to its whole text; the phone's two-line card opens on tap. Edit moved the card's text into the composer and deleted the card even while a question or approval card stood in the composer's slot, so the text vanished from the page. Edit now waits, saying why, until the prompt is answered; the pane and the queue read one check for whether a prompt holds the composer's slot. * fix(native-chat): keep the full-text hover title on a folded queued card * fix(native-chat): phone card opens only when it clips; test the composer-slot check The phone card measured nothing and made every card a button; it now lays the text out unseen at its width and offers Show more only past two lines. The Edit reason is no longer dimmed twice, and the shared composer-slot check and the desktop toggle's keyboard reach have their own tests. * fix(mobile): the unseen measuring copy of a queued card takes no touches * fix(native-chat): open a queued card's full text below its row, at the card's width In a narrow chat the opened text sat beside the card's actions and wrapped a few characters per line. It now opens under the row, indented to the text, the way the transcript's awaiting-input row opens its question. * fix(native-chat): the queued card's toggle names the opened text only while it exists * refactor(native-chat): the queue reads pending prompts itself, keeping the session hook under its line cap * refactor(native-chat): keep this PR to reading a queued message in full; Edit moves to in-place editing * test(native-chat): queue list rig passes the pending-prompt flag main's controller takes |
||
|
|
bb44f74d00 |
Keep native chat /clear in the same conversation (#26579)
* Keep clear in the same chat and reset its provider context * Derive clear pauses only from journal membership and cover older rewind clients * Point queued command rewind test at the structured hook * Group queue consume errors and rewind replay assertions |
||
|
|
ff4a51c872 |
Show sign-in guidance for all native chat agents (#26544)
* Recognize native chat sign-in failures across structured agents * Fix authentication failures and chat sign-in guidance * Preserve sign-in diagnostics and command retry context * fix(native-chat): keep visible sign-in guidance and localize startup rows * fix(native-chat): separate auth diagnostics from retry guidance * test(native-chat): align startup guidance expectations * fix(native-chat): start a new agent for the send after it reports not signed in * fix(native-chat): scope the signed-out check by fence and read without a snapshot * fix(native-chat): keep a signed-out agent while it owes work, and read only its own rows * fix(native-chat): keep a signed-out agent that may owe a wake-up turn, and skip the check during a Stop |