mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fe060f9ba0a4e7e4791c71a0da03460e87ee4bdf
14049
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe060f9ba0 | refactor(runtime): check workspace removal and recovery ownership (#27624) | ||
|
|
d3b17ca9f4 |
fix(runtime): save a windowless host's chat tabs in its layout (#28354)
* fix(runtime): save a windowless host's chat tabs in its layout Claude-Session: p2-headless * fix(runtime): remove a chat's saved tab on the index's hidden notice, which every close reaches Claude-Session: p2-headless * fix(runtime): a chat whose record is gone loses its saved tab at startup Claude-Session: p2-headless * test: run the windowless chat-layout host test in the Node runtime project Claude-Session: p2-headless * fix(runtime): remove a chat's saved tab from whichever partition still holds it Claude-Session: p2-headless * refactor(runtime): list saved chat tabs across partitions in the layout module Claude-Session: p2-headless * refactor(runtime): read saved chat tabs straight from the store's partitions Claude-Session: p2-headless * fix(chat-tabs): save a windowless chat tab under its own tab id Review pass: the saved tab reuses the id status and attention address, the close checks for a window before scanning partitions, and the host tests seed readable records with unreadable, other-host and no-index cases. Claude-Session: p2-headless * test(chat-tabs): a window opening a windowless-saved chat reuses its tab Claude-Session: p2-headless * test(chat-tabs): check the saved tab with the lookup a window reuses The window's existing-tab lookup moves to the shared projection so the windowless-layout test exercises it without importing the renderer. Claude-Session: p2-headless * test(chat-tabs): assert the reused tab's identity directly Drops the shared lookup added for the test; two functions already carry that name. Claude-Session: p2-headless * fix(chat-tabs): leave an unstamped chat tab in an SSH partition to that host Claude-Session: p2-headless * fix(chat-tabs): count an unstamped chat tab as this host's only in the local partition A desktop mirrors a remote server's chats into that server's runtime partition without a host stamp; the startup sweep must not delete them. Claude-Session: p2-headless * refactor(chat-tabs): one chat tab writer, one startup match, local partition only Chats run only on local hosts, so their saved tabs live in the local partition: the cross-partition listing and its writer go. Open and close share one layout load, the startup fill and sweep become one function beside the index seed, and the tab filter is the existing savedSessionId. Claude-Session: p2-headless * fix(chat-tabs): restore and sweep saved Claude chat tabs like any structured chat The Claude skip predates Claude structured chats (#18519, #18560); an id the host has no record of is already dropped by the readable restorer. Claude-Session: p2-headless |
||
|
|
5592d98c3d | refactor: check terminal layout methods and organize modules (#27614) | ||
|
|
078f8f314f | refactor: check terminal output methods and organize modules (#27606) | ||
|
|
f894e257e9 | ci: stop daily and post-merge headless-server checks (#28664) | ||
|
|
8d88c44484 | refactor(runtime): type terminal waits and delivery evidence (#27602) | ||
|
|
207b220d9c |
fix(browser): agent keys and text go straight to the page, never into the user's focus (#28453)
Main-session CDP key and text input on a <webview> guest is routed by the window's focus, so it landed in the user's terminal (#20595). Embedded guests now get widget key/char events; child-frame focus and untypeable text keep the CDP path after focusing the guest. |
||
|
|
16c499933b | refactor(runtime): check terminal identity modules (#27595) | ||
|
|
b20d8ac507 | refactor(runtime): remove redundant runtime typecheck suppressions (#27590) | ||
|
|
aadc1d489e | refactor(runtime): type workspace queries in domain modules (#27588) | ||
|
|
9c72b3c30f | refactor: check headless session tab methods (#27584) | ||
|
|
663cab34d6 | refactor(runtime): give file operations checked domain owners (#27582) | ||
|
|
b84d371a9f | refactor(runtime): compose typed browser command owners (#27576) | ||
|
|
230b39c279 | refactor(runtime): organize file search and terminal artifacts by domain (#27571) | ||
|
|
bbbeb96f4b |
feat(wsl-mover): terminal gate, last-folder capture and exit proof for a WSL move (D1b-5b) (#28606)
* feat(wsl-mover): the terminal gate a WSL move passes before it pauses anything (D1b-5b) * feat(wsl-mover): capture each paused pane's last folder for the host's restore (D1b-5b) * feat(wsl-mover): prove a moved workspace has no live terminal before commit (D1b-5b) * fix(wsl-mover): a permission prompt blocks the move; the exit proof also checks recorded pane sessions (D1b-5b) * fix(wsl-mover): any agent waiting on a person blocks; the exit proof refuses an incomplete census (D1b-5b) |
||
|
|
39e70c11e0 | refactor(runtime): typecheck terminal artifact grants (#27420) | ||
|
|
93aeda3c8c |
fix(terminal): don't report kitty releases for key presses the app never received (#28621)
With kitty event reporting (flag 2) on, every keyup xterm saw became a release report. IME-consumed keys produce keyups whose press xterm never encoded: fcitx4 Sogou letters arrive as keyups only, Hangul presses are Process/229 with real-key keyups, and a key held across a focus change releases without a press. Track the physical codes whose keydown actually reached xterm's encoder and keep any other keyup away from it while report-event-types is active. |
||
|
|
47903077db |
fix(keybindings): match modifier chords pressed during IME composition by physical key (#28620)
With a Hangul syllable still composing, macOS reports Ctrl+2 as key 'Process', so tab.selectByIndex (and every other chord) matched nothing and the press only committed the syllable. A Cmd/Ctrl chord (or Alt off macOS, minus AltGr) is never IME text, so fall back to the physical code for 'Process' the way '' and 'Unidentified' already do. Fixes #28159 |
||
|
|
ae8842306b |
chore(lint): flag Enter handling on raw text fields that skips the IME guard (#28609)
Adds an oxlint plugin rule that reports Enter-key handlers on raw input, textarea, and contentEditable elements (and keydown listeners on text-field receivers) unless the handler reaches the shared IME guard. Also routes the automation run history window listener through isImeOwnedKeyboardEvent, whose previous nativeEvent check never fired for native events. |
||
|
|
498818fc63 |
Native chat: a message stopped before the agent got it reaches the agent with your next send (#27623)
* Carry a person's stopped messages to the agent with their next send A message a person sent and then stopped before the agent started on it was kept on screen as "Stopped manually" but never reached the agent. The delivery loop now derives the stopped messages the agent is still owed, names them on the next ordinary send's handover row, and each agent receives them in that same write as framed context (Claude and Pi: framed text and images in the prompt; ACP: leading blocks; Codex: turn/start additionalContext, or framed input items for images and steers). Codex withdrawals that may have reached the agent are marked so they are never carried. * Let a queued card's hand-off carry stopped history; test the exactly-once outcomes A card's hand-off records no sender, so carrier eligibility also accepts a queued card whose body names no other agent. Tests cover every row of the exactly-once table against the real host, each agent's rendering, the journal fold, and that nothing a client parses changes. * Keep a send that carried stopped history out of fingerprint reconciliation After a host crash, restart reconciliation matches an unsettled send to the agent's transcript by fingerprint. A carrier's transcript copy leads with its stopped history, so it could never match and would read as never delivered, which would also release its history again. Like a send with an attachment, it now stays unconfirmed. * Owe a stopped message only on proof it never reached the agent; carry Codex history as input - Codex: every stopped message rides as leading framed input on turn/start and turn/steer (an older Codex silently ignores additionalContext and would read as delivered); add a Codex fit check against its 1,048,576-character input limit. - Flip the marker: a withdrawal is owed only with `neverReachedAgent`, written where Orca proves it (host-held Stop, ACP held steer, Claude queue clear, a start the Stop ended). Old journals and Codex withdrawals read as not owed. - A carrier releases its history only for Orca's own pre-write refusals or a marked withdrawal; provider refusals and not-signed-in are doubt. - If the history does not fit, try once without its images, then send alone. - Re-read the turn scope after selection; owed excludes typed slash commands; dropped-history fallbacks log through the host logger. * Owe stopped messages only where Orca held them; list only the history actually written - Claude's queue clear (lifecycle and interrupt sweep) and a start ended by Stop write plain `cancelled` again, as on main: Claude may already hold a swept send, and the child-exit site proved nothing for leftover rows. The proof sites are the host-held Stop withdrawal and ACP held steers. - An adapter that leaves the history out after it fit calls the host's new `historyLeftOut` before writing; the host rewrites the pending row without `carries`, so those messages stay owed. - History is chosen oldest first: each message with its images if it fits, else without them, else skipped this time without holding back later ones. - Session History titles and previews skip `<stopped-message>` blocks through the existing hidden-block list. * Build the swept-send test's marked fact with the shared builder * Keep a person's own words when a rewind rebuilds their message A rewind rebuilt each kept message from the provider's copy. Codex's copy of a send that carried stopped history starts with the framed history, so that bubble showed it. Keep Orca's body for a person's own message, as the live fold already does; other senders keep the provider's body with their sender. * Require the logger on the handover context * Write a plain Claude send as soon as before when no stopped history rides Wrapping the content build in an async function added microtask ticks before every Claude write, even with no history, which a turn-timing test caught. Return the same promise when nothing rides. * Keep stopped messages and their carriers' coverage through a rewind A rewind rebuilt history from the provider's items, which hold none of the host's send facts, so a stopped message the agent never received lost its bubble and its submission, and a surviving send that carried it lost its coverage. The rebuild now restates those sends with the submission and dispatch rows that first recorded them, in both the epoch and the post-clear paths. A carrier the rewind removed is simply absent, so what it carried is owed again with the next send. * Test that a rewind keeps stopped messages owed or covered Drives the real host, store and journal through a Codex-shaped rewind: before and after the carrier, after a /clear, across a host restart, for a stopped message nothing carried, and for a rewind that crashed after its history committed. Each checks what the desktop and the phone draw and what the next send carries. * Owe stopped history again when a carrier provably never reached the agent A Pi prompt refused for sign-in after its retries, and an ACP prompt line the encoder refused before writing a byte, now settle with neverReachedAgent. The owed derivation treats any carrier rejection carrying that proof as refused before write, so its history rides with the next send instead of being lost. Pi checks sign-in before it records a prompt from 0.84.0 (Orca's floor). * Share the Pi adapter test harness with other Pi tests Moves the scripted connection and setup out of the adapter test, unchanged, so a stopped-history test can drive the real adapter without growing that file. * Prepare each send once with the stopped history that fits, in one batch frame Stopped messages now reach the agent in one frame ahead of the next send: a header saying they are earlier, stopped history, one tagged element each with its images numbered in the order sent, then a 'User message:' label. Only a '<' that would open or close the frame's own tags is escaped. Each adapter prepares the send once (prepareDispatch): images are read once, each message's cost is weighed once against that agent's real limits, and the result is the exact payload plus the ordered ids inside it. The host lists those ids on the pending row and dispatch writes that same payload, so what a row says it carried is what the agent got. A message that does not fit goes without its images, then shortened with a marker; only when not even that fits does it wait, and the frame says how many were left out. A plain send with nothing owed is prepared and written exactly as before. ACP measures every prompt's whole line, text included, and refuses a text-only message too long for one line before writing it. Titles, previews and copied first prompts hide the whole frame and its label, however long. * Keep a stopped or restarted queue paused through a rewind after a /clear After a /clear the rewind rebuilds in place, so a restated accepted carrier reads as a turn after the person's Stop (or a restart's pause) and released the cards it held. Restate those pauses above it, as a new epoch does. * Restate a rewind's stopped messages in the order they were sent Owed history follows the restated rows' order, so write them in their old send order within each anchor rather than the order they were refused. * Fit the rewind tests to the prepared-dispatch frame * Cap carried history at 64k characters and degrade only the oldest stopped message * Owe stopped history again when ACP or Claude refused its carrier before writing it * Strip a carried-history frame before capping a Grok preview * Leave a title that opens like a frame but never closes it unchanged * Drop the image labels Codex writes for carried history images from titles and copied prompts * Read a stopped message's images one by one so a refused image leaves the readable ones riding * Settle a Pi prompt still waiting out a sign-in refusal as never reaching Pi when the session ends * Name the journal rewind module after the rows it builds * Type Pi's rejection helper with the facts that can reject a message * Hide everything ahead of a carried-history frame within a bounded lookahead, and cap carried images at 20 * Fit Pi's stopped history beside the line it is written, and settle an encoder-refused prompt as never reaching Pi Preparation measured the prompt without the streamingBehavior field PiRpcTurns adds, so a nearly full carrier could exceed the line limit; the encoder then refused it and the write settled as unknown, covering the carried history forever. Both now measure the frame from the same builder, and an encoder refusal settles as writeFailed with neverReachedAgent, leaving the history owed. * Record stopped messages appended to the agent's own log in the journal A host-only mark on an id no item takes, written before the append and cleared by a later proven refusal, so an older host reads past it and clients never see it. * Derive owed stopped messages from append records as well as ACP and Pi carriers * Append owed stopped messages to the agent's own log at the next handover The record is written first and covers them; only a proven refusal owes them again. A failed or unknown append is logged and never holds the send. Agents that cannot append keep carrying them in the send. * Write Claude's stopped messages as shouldQuery:false frames that run no turn Each message is its content and then Claude's own block-form "[Request interrupted by user]". The append settles when Claude names every frame in a result, and its zero-turn commands are claimed by uuid so they open no turn, show no Working and write no row. The prepared carrier send and the startup-failure never-reached mark are gone. * Inject Codex's stopped messages with its own turn_aborted record while idle One thread/inject_items holds each message and the default-mode user-role marker. Only "method not found" proves a refusal; any other error may have recorded them, so they are never injected again. The prepared carrier send is gone. * Carry stopped messages for ACP and Pi as plain text, whole messages only Each rides as its words and Claude's interruption line ahead of the send's own words, oldest first, while it fits the agent's own limits; the first that does not fit waits with the rest. The frame, numbering, labels, shortening and history caps are gone. * Drop the stopped-history frame stripping from Session History scanners Nothing writes the frame any more. Claude's and Codex's own interruption records were already never read as a title or first prompt. * Keep stopped messages and their append records through a Codex rewind Only stopped sends are restated, each with the record that covers it, so a kept message is never injected twice. Carrier coverage on rewind is gone: Claude and Codex no longer carry, and ACP and Pi never rewind. * Keep an append record through a Codex rewind only where Codex keeps its pair A turn can come between a Stop and the send that appends the stopped message (another agent's mail, a slash command, a busy Codex). A rewind to before that turn kept the bubble and its append record, while Codex dropped the injected pair, so the message read as covered when the agent no longer had it. The reducer now keeps each record's row position. A rewind restates a record only when that row precedes the rewound turn, and writes it back after the row it followed, so a later rewind compares the same way; the same-epoch rebuild after a /clear takes back every record after the floor first. A stopped send is restated with its submission and rejection rows only: the queued withdrawal never wrote a handover row. * Check, bound and settle each stopped-message append once Each session now gives one answer for how its agent takes stopped messages (append, carry or none), instead of a per-session check beside a router that always offered the append. Right before writing, both agents check again that the child, connection and fence are the same and that no turn runs, by the agent's account or Orca's journal; a failed check is refused, so the message stays owed. Reading a stopped message's images has its own 2 s limit and the write and its settlement another 2 s, so a send or Stop queued behind a stalled append waits about 4 s at most; work abandoned on a timeout never writes later. A message with nothing left to read is logged as such, not as unknown. Claude: the append keeps only a uuid observer. Its own lifecycle, echo and result settle it and stay out of turn tracking, even late; its running, init and idle frames are left alone. It holds the child from rest only while unsettled, and a child that ends settles it from what the writes prove, so frames it never took are refused. Codex: every inject error is unknown. A Codex without the method answers an error Orca cannot tell from a failed write, so it is never asked again, as before appends existed. Remote image URLs are left out: Codex refuses them before recording anything. * Give ACP's carried stopped messages a text block of their own The carried words were folded into the send's first text block. With an image-only send its own images came before the interrupted text and read as part of the stopped request. The prompt is now the carried images, one text block with the carried words, then the send's own blocks unchanged. Pi keeps text and images apart, so it cannot interleave them, and stays as it was. * Hide Codex's own <turn_aborted> record in Session History Codex writes a user-role <turn_aborted> message for its own Stop. After a first message holding only an image, that marker became the session's title, its copyable first prompt and a preview row, in natively interrupted sessions as in appended ones. It is now hidden like the other harness blocks in titles and previews, and never seeds the first prompt. * Give carried stopped-message preparation one time limit, so a stalled image never holds the send or Stop * Read Codex's stopped images within Orca's attachment limit, not Claude's * Append only while the agent is idle by its own account, and record nothing while it is busy * Find a Codex rewind's cut in one place for kept rows and kept append records * Drop the rewind send-order sort, share the interruption marker, and return untouched files to main * Test the append end to end through the router, and a late append echo with the next send's words * Leave out a stopped image whose read stalls, and never read that file again while it is stuck * Inject Codex's stopped images only into a Codex that resizes injected images, read from its handshake * Skip and log a kept append record no rebuilt row precedes, instead of anchoring it at the end * Restate a rewound stopped send's rejection through the one dispatch-row builder * Keep a stopped image unread until every abandoned read of it settles, not just the first * Read stopped-history transcripts through the one projection every client now shares Main removed the rejectedInPlace option: the desktop and the phone now draw a recorded-then-rejected send the same way, so the tests read one transcript instead of comparing two. * Deliver stopped messages with the next send: Codex by injection, every other agent inline Claude no longer writes stopped messages as separate shouldQuery:false appends with their own frame-claiming, idle gate and settlement. Like ACP agents and Pi, it now gets them at the front of the person's next words-only send, followed by the "[Request interrupted by user]" record. Codex keeps thread/inject_items right before the send, idle only, at most once; a Codex without the method is refused and its sends carry them instead. One selection serves both: words only, the newest that fit a 1 MiB encoded bound, in the order sent, older ones left out and covered. A send with attachments carries nothing, so history can never push a send past an agent's limits; the per-agent prepare and fit modules, stopped-image reads and Codex version plumbing go. The injection record is renamed for what it is. Rewind restatement is split out: a rewind drops stopped messages with their bubbles, as before, and nothing is injected twice. * Bound stopped history by one send's size beyond the newest, counting its marker, and keep scanning older messages * Keep a whole stopped-history delivery within one send, counting either transport's framing * Budget stopped history by the agent's remaining context beside the send, and carry none when none fits * Bound stopped history by one send again, not the agent's context: each would have reached the agent unstopped * Bound stopped history together with the send it rides with The delivered stopped messages and the send's own words never exceed one send's size, so the agent is never handed more than a send the person could make. If the newest doesn't fit, they all stay owed for a later send. * Never owe a stopped message too large to deliver beside any send Such a message could never fit, so it held every older one back for good. * Owe a stopped message only if it fits beside a one-letter send The smallest send that carries history has one letter, so the owed bound reserves that, not an empty send. * Deliver Codex's stopped messages inline, like every other agent Codex no longer gets stopped messages through thread/inject_items. Its next turn-opening send carries them at the front, the same text every other agent gets, and the carrier's own pending row records what it carries. That removes the injection module, its host-only journal row and reducer handling, the adapter and router hooks, the unsupported-method fallback flag, and the <turn_aborted> hiding in Session History that only injected items needed. A Codex rewind rebuilds kept messages from Codex's copy, which now leads with the carried words, so a person's own message keeps the words Orca recorded, as the live fold already does. The per-message size allowance is now the inline framing itself. Measured with real Codex 0.159.0 before building: inline acted on "go ahead" 8/8, never on "ignore", and 0/21 on unrelated follow-ups, matching the injected pair and a real Stop. * Owe a Codex send's stopped messages again when Codex declines its turn/start A request Codex answered with an error recorded nothing, so the rejection now carries the never-reached proof and any stopped messages that send carried are owed again instead of covered forever. A timeout or transport failure still proves nothing. |
||
|
|
80f038ed80 |
feat(wsl-mover): a journal and fence for local WSL moves, on one shared journal store (D1b-5a) (#28536)
* refactor(migration): one durable journal-directory store for every source kind * feat(wsl-mover): a WSL move journal in its own directory, one move in flight per distro (D1b-5a) * feat(wsl-mover): the WSL move journal fences and hides the rows it names * fix(wsl-mover): journal records only move forward; a new journal directory is durable * test(migration): pin the SSH journal's path and bytes across the shared store * fix(wsl-mover): journal records are detached, unique per id, rooted, kept while in flight, and digest-checked |
||
|
|
b428def8f1 |
feat(cli): open files at a requested line and column (#28240)
Accept positive safe-integer line and column flags through the existing file-open path. Reject invalid or incomplete positions before changing the editor. Bind deferred reveals to the requested workspace and tab, including floating panels, and clamp oversized positions without moving another editor or stealing late focus. Co-authored-by: Nikita Maketryuk <n.maketryuk@lince.studio> |
||
|
|
9473e73d87 |
test(ci): let the Windows relock test reach its own 60 s wait (#28566)
vitest's 30 s default cut the test's 60 s relock waitFor short; it failed package (windows) 3 times at 30-33 s in ~1,000 runs. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
76d1c68ab5 |
Resume in New Native Chat for OpenCode (#28072)
* feat(chat): Resume in New Native Chat for OpenCode terminal conversations An OpenCode conversation started in the terminal now offers "Resume in New Native Chat" in its Session History row menu and its tab's right-click menu, as Claude, Codex, Grok and OMP do: a new OpenCode chat takes it over, showing its history and continuing it. OpenCode 2.x rows (`opencode2`) and 1.x rows both open an OpenCode chat. Two pieces kept OpenCode off the shared adoption path. Its account is a locator, not a folder, so the transcript import now works over the account the chat pins, whatever its kind; the folder agents wrap their lookups in directoryTranscriptImport, unchanged. Its conversations live in a database, so the journal import reads the found database with the reader the OpenCode transcript view already uses. OpenCode refuses to reopen a session from another folder, so the workspace rule covers it too. * test(chat): Pi is now the agent with no transcript import * fix(chat): find an OpenCode 2.x terminal tab's Session History row The pane reports `opencode` while Session History lists the conversation as `opencode2`, so the pane header's and tab menu's Resume in New Native Chat found no row. Match by the chat agent that takes the conversation over. * Fix OpenCode chat adoption environment and pane eligibility * test(chat): adapt parent adoption fixtures to account identities * test: keep only the tests that pin user-visible behavior * refactor(chat): map a Session History row to its chat agent where the chat starts Both Resume in New Native Chat callers repeated the row-to-chat agent lookup. The launch now takes the row agent and maps it once, so the Session History actions file stays unchanged. |
||
|
|
b5c568e9fb |
fix(browser): agent bringToFront and activateTarget no longer take the user's focus (#28452)
Adds docs/reference/agent-browser-focus-isolation.md: measured input routing for <webview> guests and the plan for #20595. |
||
|
|
1ccb2897f2 |
feat(acp): permission requests a dialect claims are presented as questions, never auto-answered (#28449)
A dialect's request() now presents session/request_permission when it claims it, and full access no longer auto-allows a claimed request. No dialect claims permission requests yet, so nothing changes today. |
||
|
|
83ddc89596 | docs: update WeChat group 12 QR code [skip ci] (#28639) | ||
|
|
f2df6a706d |
fix(orcad): stamp the build commit so orca skills install works on SSH and headless hosts (#28558)
* fix(orcad): stamp the build commit so orca skills install works on SSH and headless hosts #27603 pins 'orca skills install|update' to the commit the CLI was built from, read from the CLI's out/package.json. The desktop CLI build stamps it, but the server bundle (the 'orca' in SSH-host and 'orca serve' terminals) wrote that file without a commit, so the command always failed there with 'Could not read the commit this Orca CLI was built from'. Stamp it the same way. * test: verify built orcad CLI skill source in artifact preflight |
||
|
|
e49ae24863 |
perf(ai-vault): share concurrent Codex title-index reads (#28497)
* perf(ai-vault): share concurrent Codex title-index reads * bench(ai-vault): reproduce Codex title-index reads * chore: drop one-off benchmark script; order imports |
||
|
|
b83a6f74be |
Finish agent-selection cleanup and restore merge validation (#28607)
* Retire newly translated None option labels * Reserve required verify check for the complete PR gate * Preserve headless default tab styling and stabilize import ordering test * Restore the option mutation clock after crossing fixes * Check both supported workflow extensions |
||
|
|
1c274cc178 |
fix(i18n,test): drop setup-only keys and fix the default-tab test #28543 left red (#28603)
* fix(i18n): drop the setup-only keys #28543 removed from en.json Main's localization catalog check fails on these three stale keys in es/fr/ja/ko/zh. * test(runtime): give the default-tab agent fixture a store #28543 reads settings before creating default tabs (setup-only skips them), so a storeless runtime now fails provisioning with runtime_unavailable. * fix(native-chat): restore the option write's clock both crossing fixes removed #28543 and #28599 each dropped one of the duplicate now keys, leaving none. |
||
|
|
d8458f14f5 |
Fix browser login popups and menu actions in native windows (#28474)
* fix(browser): preserve unnamed OAuth login popups * fix(browser): preserve form and blank popup sessions * Preserve native request context for Shift popup navigation * Validate route popups with production policy attachment order * Fix browser background modifier request continuation * Route opener-less OAuth popups to Orca tabs and fix menu targets in nati - Plain links and Cmd/Ctrl+Shift-clicks open OAuth URLs as Orca tabs instead of native windows. Those popups have no opener, so they lose nothing by reloading the URL. - Scripted popups that have an opener keep the existing origin-bar window, so their window.opener link still works. - Discard pending popup contents with a navigation veto before closing, so a link's first request cannot reach the server. - A POST with no page to hand over is still blocked, because reloading it by URL would drop its body. - Edit, Help, and paste menu actions now target the main Orca window when a native browser popup has focus. The popup has no Orca renderer to receive those IPC messages. * Cover opener-less OAuth popups and popup request provenance in tests - Add Electron tests showing opener-less OAuth links open as local tabs without loading the discarded page, and that modifier-opened POSTs are never replayed. - Add a test that a remote foreground modifier click keeps the native cross-site request context (cookies, referrer, fetch site). - Add a test that the first adopted link request is blocked when route preparation fails during popup creation. - Launch the CDP fixture through resolveElectronProbeLaunch so Linux runs use the display-aware launcher instead of a hardcoded xvfb-run. - Replace `as` casts in the popup tests with typed declarations. * Route Edit menu actions to native browser popups Copy, Paste, and Select All from the app menu did nothing when a native browser popup was focused. The popup has no Orca renderer to receive the IPC, so the action was dropped. Those actions now go to the focused web contents, falling back to the popup's own contents, so they reach the page the user is in. Also removes the popup request-provenance Electron test, which is no longer part of this change. * Fix static analysis failures in browser popup handling * Handle unavailable permission probe in browser popup fixture * Admit script-opened native popups and enforce minimum size Modifier-clicked `window.open` children from page scripts reach Electron after the opener's first task, so the one-shot admission lapsed and they were left unprepared. Their first request then lacked popup context, and the origin title could be hidden by a hostile page title. - Keep a per-opener pending admission that answers the next native child the opener reports. Unadmitted native children are discarded rather than left as bare windows. - Route popup children through the contents whose window-open handler answered, since Electron reports script-opened children to that contents. - Create native popups content-sized with a 360x200 minimum, matching origin-bar popups, so `resizeTo` cannot shrink the origin title out of view. The limits move to `popup-window-minimum-size.ts` so both paths share them. - Tests: cover scripted admission timing, unadmitted discard, minimum size, and route popup caps. Fixture waits for hidden iframe loads and polls with a longer timeout for CI. * Prime hidden Linux OOPIF hit testing for all opaque popups The popup compatibility test clicked a frame gesture target only for the "route" case when the input was opaque. Hidden Linux OOPIFs need that priming click before hit testing works, and the route's root gesture gate depends on it too. Other opaque cases were skipping it and could fail for the same reason. Apply the priming click to every opaque case and correct the comment to describe the real reason. |
||
|
|
294927e097 |
fix(native-chat): drop the duplicate now key left by crossing fixes (#28599)
#28571 and #28577 both added the clock; oxlint fails on the duplicate key. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
eb7f427426 |
Clear agent selections in workspace creation and settings (#28543)
* Replace workspace None agent option with a clear action * Limit agent clearing to new workspace creation * Let selected default agents toggle off in settings * Keep default tab templates out of setup-only creation * Remove duplicate clock property blocking CI |
||
|
|
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 |
||
|
|
5eda0a0dbd |
fix(source-control): keep status refresh for worktrees whose admin dirs differ only by case (#28586)
On case-sensitive filesystems Git can register two linked worktrees whose admin dirs are `Foo` and `foo`. The status-attribution memo lookup folds case, so it kept only one checkout per key and a `git add` in the other was attributed to its sibling, skipping that worktree's Source Control refresh. Name every checkout that shares the folded key instead. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5e1c117a86 |
fix(agent-detection): trust a \\wsl$ path's distro over the cached distro list (#28233)
The agent list for a \\wsl$ workspace with no saved runtime no longer checks the path's distro against Orca's cached list, which is read once and can be stale; the path proves the distro exists, as it already does for launches. |
||
|
|
d14b7ca2b5 |
fix(session-history): refuse desktop resumes of chat-owned conversations (#28229)
* fix(session-history): check host ownership before desktop resume * fix(session-history): preserve resume behavior across hosts * test: keep only tests that pin user-visible behavior |
||
|
|
a0de43aa1f |
AI-button and OpenCode-model tabs start their agent in their own terminal (#28210)
* fix(agent-launch): an AI button plans its agent inside its own pane's spawn, with the host's planner and no launch record The window queues the launch on the tab; the pane's reserved spawn plans it with the planner every host launch uses and starts it at the pane's size, with its launch token. Every way the spawn ends finishes the launch, and the window pastes the prompt only once this pane says the agent started. An ordinary spawn reaches its provider without waiting on a plan. An OpenCode tab with a model keeps main's host hop untouched (TEMPORARY, until a local producer of model options exists). * fix(agent-launch): an AI button's prompt waits for its pane to start before the spawn budget runs, so a pane that settles slowly still gets its prompt; stale comments * test(agent-launch): the late pane-start test's store double is typed without an assertion * test(agent-launch): the late pane-start test pins the prompt written once and Enter once, and nothing when the pane didn't start * fix(agent-launch): an AI button's prompt goes only to the PTY its pane started, never a split's shell that bound first; the pane's start answer names that PTY * fix(agent-launch): an AI button's launch watches each split's readiness as it binds, so the launch pane's own ready output is seen even when a split bound first and it drained at once * fix(agent-launch): an AI button's pane plans with the host's current planner and runs its runtime fact's shell, so its command is quoted for the shell it is typed into, as a host launch is; the tab's inferred shell is not sent as a request |
||
|
|
ea7942e638 |
Give Windows taskkill a 5s budget to reduce flaky tree cleanup (#28577)
* Give Windows taskkill a 5s budget before tree cleanup is reported unveri On busy Windows machines, `taskkill` could take longer than the 2s budget used for other subprocesses. Tree termination then gave up before the process tree was confirmed gone. The taskkill timeout is now 5s, matching the other Windows tree-cleanup paths. Tests cover a slow taskkill that still verifies termination, and a hung taskkill that is bounded and falls back to SIGKILL. The batch-shim recovery test gets a longer hang and a 30s timeout so it has room on slow runners. * Pass the context clock into structured session option writes - Expose the injected `now` clock to the structured agent session option writer, so timestamps come from the same source as the rest of the context. - Update the main and renderer layout-writer baselines to match the reduced line counts in `src/main/ipc/session.ts` and the removed `set-active-folder-workspace.ts` entry. |
||
|
|
069bbaa4f9 |
fix(native-chat): pass the clock to a recorded option pick (#28571)
#27826 and #28268 crossed: the option-intent writer now needs `now`, and the message-options context didn't supply it, breaking main's typecheck. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
6cdb4b1006 |
test(mobile): prove server browsers reach a paired phone through the desktop (#26521)
* 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 * test(mobile): prove a server browser page streams to a phone through its desktop Server-placed browser tabs already reach a desktop-paired phone: browser.* and status.get route to the execution host, and the relay passes screencast binary frames through untouched. This pins it end to end: the tab lists, and real JPEG frames arrive on the phone socket. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): stable request ids and checked replies in the server browser e2e Date.now() ids could repeat within a millisecond and match a stale reply; a counter cannot. The reply helper now checks its frame instead of asserting it non-null. 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 |
||
|
|
58d0de6bb6 |
fix(terminal): keep the text of an IME key pressed before a commit finalizes (#28441)
* fix(terminal): keep the text of an IME key pressed before a commit finalizes A Process (229) keydown that lands after compositionend but before xterm's deferred finalizer runs took the composing early return and never recorded that it owed its own commit. When the finalizer ran first, the key's insertText found no pending transaction and no owed commit, so xterm dropped it: `你` then `,` reached the PTY as `你`. Record the debt for such a keydown when the ended session's commit is already in the textarea, and clear it when the pending transaction settles the text itself. Sessions still waiting for their post-composition echo keep their old path, so the echo cannot be sent twice. Fixes the intermittent terminal-linux-direct-ime-commit e2e failure. * fix(terminal): keep a commit when Escape or a keypress follows compositionend A plain Escape after compositionend cancelled the accepted commit, dropping both the text and the Escape; it now settles the commit and then sends Escape. The Escape that itself ends a session (still composing, reported after compositionend) still cancels. A keypress for the Process key that owes a commit now clears that debt, so a later input cannot send the key's text a second time. |
||
|
|
9b1e83a7d8 |
refactor(native-chat): rename the chat visuals folder to the chat scratch folder (#28389)
* fix(files): never write through a dangling symlink A path that doesn't exist yet was authorized by walking up to the nearest existing folder. A dangling symlink fails realpath with ENOENT, so the walk stepped over it and approved the link's own path; the write then followed the link and created its target outside the allowed folders. The walk now continues from the link's target, and returns that real destination. * refactor(native-chat): rename the chat visuals folder to the chat scratch folder The per-chat folder agents write into is the chat's scratch space, not only a visuals folder. Its root becomes <state>/chat-scratch/<hash> (the hash is unchanged) and the agent variable becomes ORCA_CHAT_SCRATCH_DIR. The old ORCA_CHAT_VISUALS_DIR is never set and is still stripped from what every agent child inherits, Pi included. The visuals skill names the new variable and says the folder is temporary. * test(native-chat): keep the delivery test to the rename The launch parity snapshot already pins that every agent drops the pre-rename variable. * fix(files): refuse any dangling symlink in the missing-path walk Following a dangling link by hand resolved relative targets against the lexical parent, not the link's real folder: through a folder link the walk could loop forever or authorize the wrong file. Refuse instead: realpath can't say where a dangling link leads, but a write would follow it. * test(files): drop the loop case that duplicated the dangling-link refusal |
||
|
|
0504670961 |
Fix CLI delivery and fresh launch environments for managed SSH agents (#26464)
* fix: refresh host environments before structured agent command resolution * fix: ship the owning server CLI for managed remote agents * test: include the shipped CLI in remote install fixtures * Bind packaged agent CLI calls to their execution host * Keep owning-server CLI binding scoped to managed agents * Serve the last shell environment to chat starts and refresh it in the background Only the first chat start waits for the login shell; later starts get the last snapshot and refresh it in the background once it is 10 s old. On Windows hosts without the registry addon (managed server slots), the saved PATH is reread with reg.exe. The Windows server CLI launcher stager now checks the target architecture, and the CLI help says ambient selectors are ignored inside a managed server. * Keep the last good shell environment when a refresh fails, and load the Windows profile once A login-shell capture now says whether the shell produced the env or Orca fell back to its own, and only a real capture replaces the snapshot. Windows loads the PowerShell profile once per process and refreshes only the saved PATH. The reg.exe fallback queries the whole key so a missing user Path no longer drops the saved PATH. The first capture starts when the resolvers are created. * Keep the last good login-shell env for other readers while a forced refresh runs * Mark the Claude launch resolver as a reviewed router-aware module It only types the chat command resolver it injects; main's launch-routing guard (#26801) matched the name. * Keep a settled login-shell env for readers during a refresh and share the first run - Any settled cached entry, a fallback included, now serves other readers while a forced capture runs; only a real capture replaces it. - Native chat's first capture shares a run a startup reader already began, so the login shell runs once per process at startup on macOS/Linux. - Type the injected Claude resolver without naming resolveClaudeCommand. - Cover the runtime's Claude command options, the owning-host --host check, and the PTY flag drop. * Drop an inherited owning-host flag from daemon terminal panes The persistent terminal daemon merges its own environment into every pane, so an inherited ORCA_CLI_OWNING_HOST reached desktop panes even though main strips it from the spawn env. Drop it with the other per-launch CLI variables. * Route agents to their own server through ordinary launch defaults; start the first chat after an install The native-chat agent environment now sets the owning server's ORCA_USER_DATA_PATH and empty ORCA_ENVIRONMENT / ORCA_PAIRING_CODE / ORCA_REMOTE_PAIRING, which the CLI already reads as unset; empty rather than deleted, because the spawn merges the launch overlay over the server's own env. The ORCA_CLI_OWNING_HOST flag, the CLI's skip of ambient selectors, the launchers' export of it, the terminal clean-up, the help-text exception and the WSL launcher guard are gone, so a person's terminal follows their own settings as on main. A launch that finds no program in the shell snapshot waits for one shared refresh (at most one shell timeout) and resolves again; create support rereads the shell once before refusing. On Windows a failed PowerShell profile load is retried on the next refresh instead of being kept until restart. * One shared shell reread when an agent's program is missing The pre-create check for OpenCode, OMP and Pi forced its own login-shell capture on every refusal: not shared with launches, one per concurrent check, triggered by an installed-but-too-old version too, and an older capture finishing last could overwrite a newer cached environment. On Windows, every launch miss also reran a failed PowerShell profile load, bypassing the 10-second limit. - One saved shell environment per host: chat launches, their misses, the pre-create check and AI commit messages read it and share one refresh. - The pre-create check resolves the agent's program the way a launch does, so only a missing program waits for the refresh; registrations declare how to resolve their launch and check its version. - A forced login-shell capture joins one already running for the same shell, so captures cannot land out of order. - On Windows, a miss within 10 s of a failed PowerShell load rereads only the saved PATH. * One owner for the cached shell environment Review found two more races in the reread-on-miss machinery, both from the host wrapper keeping its own snapshot beside the login-shell cache: - A launch that found no program could join a reread that started before the agent was installed, and spend its one retry on the old PATH. - On Windows, saved-PATH rereads renewed the snapshot's age, so a failed PowerShell profile was never retried while launches kept missing. The login-shell cache is now the only owner of the captured environment, the run in progress and which result wins (the newest run started; a fallback never replaces a captured env). It takes three options instead of `force`: - startedAfter: a lookup that found nothing waits for a run that started no earlier than the lookup did, joining one if it has, else starting one. - refreshAfterMs: serve the last result and rerun in the background once the last attempt is older. - keepCaptured: a captured env is kept for good; only a fallback is rerun (Windows' PowerShell profile). The host wrapper keeps only policy: the 10 s interval, on Windows the saved PATH laid over the profile env on every read (a miss forces a fresh registry read), and the settings overlays. Its snapshot, pending promise, staleness and Windows "loaded" bookkeeping are gone. A lookup's start time travels with the miss (resolveAgainAfterRefreshOnMissingProgram passes it). * Read an injected shell environment once per runtime, as main does The previous commit read an injected environment on every acquisition, which broke main's 'caches shell exports but re-reads configured overrides for a resume' contract. It is read once when the resolvers are made again. * Model lists reread the environment on a missing program, as launches do Live QA: the first new chat after installing Claude showed "Claude wasn't found on the computer running this chat". The chat launch itself reread the login shell and started the new binary, but Claude's model-list check ran first, with the environment read before the install, and never retried: catalog probes were deliberately built without the miss refresh. The retry now lives in each agent's one shared lookup of its binary and environment, which a launch and a model listing both call: resolveCodexStructuredInvocation, resolveAcpLaunchInvocation, a new shared resolvePiRpcInvocation, and resolveClaudeChildEnvSources (which already had it). Each catalog probe is given the runtime's refreshAfterMissingProgram, and the catalog context no longer strips it, so a listing and a launch reread the same single-owner environment the same way. * Pass optional launch lookups through directly instead of conditional spreads * Read the host shell env through resolveLoginShellEnvironment; drop the extra wait cap CI on the merge with main failed main's new codex-workspace-relative-path test (#26684), which mocks the login-shell module with only resolveLoginShellEnvironment: the wrapper called captureLoginShellEnvironment and imported the spawn-timeout constant. The wrapper only needs the env, so it reads through resolveLoginShellEnvironment again, as on main. The miss wait no longer races its own 5 s timer: the shell run it waits for is already killed at the spawn timeout, so the second cap was a guard over the same bound. The constant goes back to private, as on main. * Test that a hung login shell holds a launch miss for at most one shell timeout The bound lives in the one owner: the shell run is killed at the spawn timeout (5 s) and settles to a fallback, which never replaces a captured snapshot. A mutation raising that timeout fails this test. * A program found through a relative PATH entry is not missing Main's #26684 runs Codex through a workspace-relative PATH entry (`bin`, `node_modules/.bin`). The lookup searches such an entry against Orca's own directory and returns the bare name; the spawn then finds the program in the chat's directory. Counting that bare name as missing made every such launch wait for a login-shell reread. The retry now fires only when the lookup truly found nothing: a bare name back from a PATH whose entries are all absolute. Each agent's one lookup tells it which PATH it searched. * Count a relative PATH entry as found only when it holds the program; Windows miss waits for a saved PATH read started after it looked A bare name with a relative PATH entry is checked from the launch's working directory, so an agent in neither entry still gets its one reread. On Windows the miss no longer joins a registry read that began before the lookup. |
||
|
|
7760a0435e |
feat(wsl-mover): bind the host to its distro registration and preflight every moved path (D1b-4) (#28542)
* feat(wsl-mover): tell a WSL host's orcad which distro registration it runs in (D1b-4) * wip(wsl-mover): destination preflight comparator * feat(wsl-mover): prove each moved path is the same repository on the host (D1b-4) * fix(wsl-mover): a folder outside git needs a marker to prove it is one folder * fix(wsl-mover): the launch shares the deploy's one registry read; no extra reg.exe |
||
|
|
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> |
||
|
|
b91795ac33 |
fix(native-chat): store a chat's permission mode on its own, so a model pick can't reset it (#28268)
* fix(native-chat): picking a Codex model or effort keeps the chat's permission mode A model or effort pick on a running chat replaced the record's options with the provider's answer, which names only the provider's own options. The saved permission mode was dropped, and the next start read it as Ask. The pick now keeps the record's permission mode beside the provider's answer; everything the provider reset still resets. * chore: re-run CI against current main * chore: re-run CI against current main * test(native-chat): keep one permission-mode-survives-pick test * refactor(native-chat): store a chat's permission mode as its own record field A model or effort pick replaces the record's options with the provider's answer, which dropped the permission mode saved among them. The mode now lives beside the options, so no provider answer can touch it. Clients still pick it with setOption and read it from the same published fields. * fix(native-chat): keep a permission mode an earlier dev build saved among options Hourly builds before the separate field saved the mode in options.permissionMode. The startup pass now moves it into the field instead of re-deriving it from the agent's arguments. Temporary; removed after 1.4.226 ships. * chore: re-run CI against current main |
||
|
|
126009d4c5 |
Skip Windows security addon steps when the builder is missing (#28546)
Older requested refs predate the security addon builder, so the desktop template CI job failed trying to run a script that isn't there. Gate the build, keep, and collect steps on that script existing in the checkout. Add a test that pins the gating for both cases. Also drop an unused getAppEnvironment import in orcad-entry.ts. |