Repository navigation
Conversation
…a.yaml default tab, as the desktop does (stablyai#26106) * fix(worktrees): the host's create puts its agent in the first default tab, as the window does With orca.yaml default tabs, the window's create starts the agent in the first default tab: that tab takes the template's title and color, and the template's command does not run beside the agent. The host's create started the agent in its own untitled tab and then created every default tab, the first one running its command too, so the workspace had one tab more than the window's create. Now the agent's tab takes the first template's place. * fix(worktrees): title the agent's default tab as a tab, so the phone keeps the agent's status The host's create titled the agent's tab by renaming the terminal, which writes the pane's own title stamped to outrank every title the agent sets later. On a headless host the phone reads status from those titles, so the agent tab showed the template title forever and lost its working/idle status. The window's create only sets the tab's custom title. The host now titles the tab the same way: the window gets the tab's custom title (not counted as a user rename), and a headless host saves it on the tab and shows it on the phone until the agent titles itself. The pane's title is left to the agent. The host also finds a terminal's tab itself from its handle, so the create over SSH, which never passed the tab, colors the agent's tab too. Every default tab is dressed by the same step, so the other tabs' titles are now kept on a headless host as well, and a failed title no longer skips the color. * fix(worktrees): find a default tab through the window's leaf once the window adopts its handle The host found a provisioned terminal's tab only through the runtime's own pty record. Once the window's graph sync adopts the handle, that lookup misses, and the default tab's title and color would be dropped with only a log line. No create reaches it today (the tab is dressed before the window's first graph sync), but any wait added before provisioning would. It now falls back to the window's leaf, as renaming a terminal already does. Tests: the restart case now saves the title to a real session and rebuilds the phone's tabs from it; a create through createManagedWorktree with an agent and default tabs spawns the agent and the second template only and titles the agent's tab "Dev" without counting it as a user rename. * test(worktrees): drop type assertions from the default-tab tests Use a runtime subclass for the protected launch scope and provisioning host, a typed store and notifier, and typed mocks in place of casts, so the changed-code quality gate passes.
… its folders match (stablyai#26104) * fix(worktrees): the host's create keeps a sparse preset only when its directories match The window's create records a sparse preset on the new worktree only when the checked-out directories are exactly that preset's, so an edited selection is not shown as the preset it began as. The host's create wrote whatever preset id it was sent. Both now share one check (moved out of the two copies in the window's create path). * fix(worktrees): read sparse presets only when a preset was chosen, and never fail the create on them The shared preset check took the preset list up front, so every create read the presets even with no preset chosen (the window's create used to skip the read), and a read that threw failed the create - on the host after `git worktree add` had already run. The check now takes a reader, returns early when no preset was sent, and reads inside its try so unreadable presets record no preset instead of blocking the create. Repeated checked-out directories no longer match a preset with a different set of directories.
… and follows the chat's next turn (stablyai#25960) * fix(native-chat): drop the queue-paused header and Resume button A Stop, a restart or /clear holds the queued cards. The hold stays; only the header row naming why, and its Resume button, go. A held card shows no caption, and its own Steer, or any new message, releases the queue. * test(native-chat): type the unknown hold reason a newer host may publish * fix(native-chat): a held card offers Send, not Steer, when no turn runs Steer vs Send now follows whether a turn is running, not the card's hold, so a card held after a Stop, a restart or /clear reads Send. * fix(native-chat): the queue sends past held cards instead of stalling behind them A card queued after a Stop (or written after a restart or /clear) sent only once the cards held before it were released; with no header to explain or release the hold, it sat silently. The next sendable card now skips held cards; a returned card still blocks what is behind it. * fix(native-chat): the queue's send of a card is the person's turn, so held cards follow it After a Stop, a card queued later sent past the held cards, but the queue recorded that send as Orca's own turn. It never ended the Stop's pause, so the held cards then waited forever with nothing on the card saying why. A queued card is always something the person wrote: only the client send RPC may now create one. The queue's send of it is therefore recorded as the person's turn, which ends the Stop's pause once the agent takes it, and the held cards then drain in order. * fix(native-chat): a queued card carries its author, so the queue's send of it is that author's turn Main now lets Orca's own sends ask to queue (sendAgentTurn's 'queue' delivery), so "every card is a person's" no longer holds by refusing host sends. Each card records who wrote it (the submission's client/host vocabulary) in a new nullable column; the drain records that origin, so a person's card ends a Stop's pause and Orca's does not. /clear carries the author. Rows from before the column read as a person's. The userSend-only admission gate is removed. * docs(native-chat): state why an unrecorded card author reads as a person's * fix(native-chat): a restart holds only cards written before it, and an idle held queue offers Resume A restart's pause held every waiting card, including one a person typed after the restart while Orca's own continuation ran, and nothing released it except a per-card Send. It now holds only cards another host process wrote, the same way a Stop holds only cards queued before it. The composer's primary button becomes Resume (Play) while nothing is typed, no turn runs and the host holds a card Resume would send, whatever held it (Stop, restart or /clear). It calls the existing agentSession.queuedMessagesResume, guarded against a second press in flight. A card nothing holds keeps the run going between a turn's end and the queue's send of it, so its Steer no longer flips to Send for the frame in between. * fix(native-chat): the host publishes which pause holds each queued card The host published one pause for the whole queue, so a client held every waiting card while it was set. Between a turn's end and the queue's send of a card queued after a Stop or restart, the composer could flash Resume and the cards Send, and a card queued after a Stop lost its "Waiting for your answer" caption. Each published card now carries an optional `heldBy`: the pause holding it, or null, derived from the same rule the drain reads. A client holds only those cards; against a host without the field it falls back to the queue-level pause. * test(native-chat): Resume needs the queue capability and is disabled whenever Send is * docs(native-chat): describe per-card holds in the queue contract and table comments * fix(native-chat): the composer goes from Resume straight to Stop, and Resume returns focus After Resume, the host lifts the hold in one update and sends the first card in a later one. In between nothing was running, so the composer's button flashed a disabled Send. A card nothing holds now keeps the queue's run going for the button too: an empty composer shows Stop, disabled until the turn starts. Not when the host refuses every send (a rewind whose outcome is unknown, read from its status), where nothing is coming. The same fix removes the Stop, Send, Stop flip between queued turns. Resume disables the button, which dropped keyboard focus; focus now returns to the composer. * fix(native-chat): the host names the card its queue sends next, so the chat stays working across the gap A turn's end, or a Resume, and the queue's send of the next card commit as two host updates. In between nothing was running, so the working status, timer, pickers and composer button flipped for one update. The client guessed the drain from its own copy of the host's gates, which missed a /clear-replaced source and covered only the button. The queue publication now carries `nextQueuedMessageId`: the drain's own next card through the drain's own gate (`nextStructuredQueuedMessage`, which the drain step now calls), null whenever the host would refuse the send. The client derives one fact, the queue is about to send, and every working reader follows it; Stop stays disabled until a turn can be stopped. The client-side copy of the gates and the status-feed rewind read are removed. * test(native-chat): the queue's next card survives the coalescer, the reducer and a history page * test(native-chat): build the snapshot that names the next card through its helper * feat(native-chat): a held queue keeps its header row, and a new message asks before passing it The queue's header row ("Queue paused because you interrupted", or Orca restarted, or you cleared the conversation) comes back above the cards it holds, with Resume; it names the oldest held card's pause, as the host publishes it per card, and hides over cards held only on their own or returned. The header's Resume and the composer's share one in-flight guard. A held card reads Steer again whether or not a turn runs; a card held on its own or returned keeps Send. Sending a message while the header shows (Enter or the button) first asks "Send message?": Clear queue deletes every card and then sends (a failed delete sends nothing), Send message sends and keeps the cards, which follow the new turn, and dismissing sends nothing and keeps the draft. Host commands send as they are. * fix(native-chat): the paused row goes while your own message is on its way to lift it After "Send message" over a held queue, the row kept saying "Queue paused…" until the agent accepted the new turn. The chat now reads that gap from the outbox: while this composer's direct send is recorded by the host and not yet accepted, the controller shows no paused row (and so no Resume or confirmation). A refusal settles the entry and the row comes back, since the hold did not lift. Orca's own sends never enter this outbox, and the queue's send of a card goes under a fresh id, so neither hides it. Nothing is stored. * fix(native-chat): a "Send message?" choice is taken once, and a failed Clear queue is one toast The closing dialog stays mounted and clickable through its exit animation, and a double-click or a held Enter lands twice before any re-render, so Send message (or Clear queue) could send the captured message twice. The pending send now lives in a ref that the first choice takes; a second one finds nothing. Clear queue deletes one card at a time and stops at the first failure, so a failed press shows one toast instead of one per card. The dialog keeps its compact width at desktop sizes and the primitive's narrow-window gutter (`max-w-sm sm:max-w-sm`, as the other compact confirmations). * fix(native-chat): Clear queue's message goes out once, and keeps text typed while it waits After Clear queue, the message waited in the composer while the cards were deleted one by one. A second Enter in that window sent it again, and text typed meanwhile was wiped when the chained send was accepted. From the Clear queue choice until its message has gone out, the composer's structured send does nothing. The chained send (and Send message's) now carries the composition it was taken from, and the composer is cleared on acceptance only if it still holds exactly that, as host commands already do. Also: the v1 contract comment names `nextQueuedMessageId` and its absent- means-null fallback, and the own-send check returns at once on an empty outbox. * fix(native-chat): the queue carries on after any turn, in order, and a restart sends nothing by itself - Any accepted turn ends a Stop's or a /clear's pause, whoever sent it (a person, Orca's own messages, or the queue), and so does Resume. The card and submission author fields that only fed the old person-only rule are gone. - The queue sends strictly in order: a card never overtakes a held one. - After a restart nothing sends by itself and no paused row shows: the chat's next turn (the carry-on, or the person's own message) runs first, then the cards. - Resume and "Send message?" are offered only while nothing runs and no prompt waits. * fix(native-chat): after a restart no queue pause shows, and a card written before the next turn waits for it too * fix(native-chat): a quit hands no queued card off, and the paused row goes while any turn that will lift it is on its way - The queue stops handing cards off when the host tears down. A card sent during a quit was refused at close, and that refused send withdrew the chat's restart offer, so resuming after the relaunch sent nothing. - The host publishes no pause while a turn sent after it (your message, Steer, or Orca's own) waits for the agent; a refusal shows it again. This replaces the client's own-send check. - A card written after a restart is an ordinary card again: it waits while any card from before the restart still waits. * refactor(native-chat): the host's paused-row-while-a-turn-is-on-its-way check in one expression * refactor(native-chat): the composer's queue Resume rides the structured transport beside the held queue * fix(native-chat): the "Send message?" choice ends with the pause it asked about; tests follow main's draft props - The open dialog closes when the queue's pause lifts under it (Orca's mail, another client's Resume, any accepted turn): nothing is sent, the draft stays, and the next Enter sends as usual. The pending choice records the hold it was asked under; nothing new is stored. - The composer-field Resume test passes main's dropScopeKey/draftScopeKey. - The dialog test expects main's rule: only the sent text leaves the composer. * fix(native-chat): a message kept after a quit or close waits like every other card, and follows the chat's next turn A message Orca accepted but never handed to the agent before a quit, crash or close came back as a card held on its own ("Not sent yet — press Send"): the queue skipped past it, so later cards sent first, and only the person's own Send released it. It is now an ordinary card at the head of the queue that waits, with every card the chat closed with, for the chat's next accepted turn. One rule for a chat that was not running, derived from the journal: when this host first opens a chat (after a restart or crash), or a person closes it, and cards are waiting, a reopen mark is written (a tombstone carrier key, like the Stop and Resume marks). The cards queued before it wait until a turn is accepted or Resume comes after it; nothing sends by itself, and no paused row shows, a Stop's included. The idle sweep's own eviction writes nothing and changes nothing the person sees. A mark that cannot be written leaves the open working and holds from the open itself until the next turn; the next open marks again. A rewind restates the mark. /clear's carried cards also wait unshown. This replaces the host-instance comparison and its adoption write, and the 'kept' hold (stored 'kept' and legacy 'stopped' holds now read as none). * fix(native-chat): mark the reopen in the one open path, and keep an idle chat with waiting cards open Review round 1: the startup restore opened chats past the per-host first-open mark, so their cards could send by themselves after a quit or crash; a card mid-hand-off at the open got no mark; a close that left the chat open re-marked after every new send. - Every open marks when a card waits, or is mid-hand-off with its send unanswered. - The idle sweep keeps a chat's handle while cards wait, so its eviction never reopens one and stays invisible; it drops it on the next sweep once they leave. - A person's close marks once; its delivery re-check marks only when it settled a send. - A failed mark holds from where the mark would have gone. - A Stop made after a reopen shows its row. - The rig's restart is a real quit and relaunch. * fix(native-chat): review round 2: restore the dropped Resume and failed-Stop tests; a late mark starts where the chat stopped - Restores eight tests the previous commit dropped by mistake. - A mark the delivery loop or a close's re-check writes after a later send starts where the chat stopped, so that send still lifts it; a later mark never narrows an earlier, wider one. - Only the idle sweep's own close keeps a chat with a card waiting (or mid-hand-off) open, and it still releases an ended child's lease first; a person's close drops it as before. * test(mobile): a host-kept card's test stands in a Stop's pause, as this host publishes no restart pause Main's stablyai#24660 test published queuePause 'restarted', which this branch's wire type no longer lists, so the mobile tests typecheck ratchet failed. * refactor(native-chat): settle a restart's leftovers and mark them in one host-lifetime step Keeps the delivery loop under its line limit after main's Stopping change; no behaviour change. * test(native-chat): a kept card's Resume and failed-mark tests quit through the held start's release Main's stablyai#25152 holds the start these tests send into; quitting without releasing it left the quit waiting.
…ats (stablyai#26103) * feat(native-chat): visual directive grammar and host read for a chat's visuals folder A shared grammar for the ::orca-visual{file="..." title="..."} reply line, the per-chat visuals folder location on the owning host, and the agentSession.readVisual runtime method that reads one visual with lexical and canonical containment, a 512 KiB bounded read and UTF-8 refusal. * feat(native-chat): render chat visuals inline and in the right sidebar Native-chat assistant replies render a ::orca-visual{...} line as the chat's HTML visual in an opaque, scripts-only sandboxed frame: CSP first, the host frame navigation guard registered before content runs, live theme without a reload, fitted height, links opened in the viewer's browser only from a real gesture, lazy mount, and one muted line when the visual cannot be shown. Open in sidebar shows the same frame in the right sidebar, widened while it is open and restored after. * fix(native-chat): visual CI fixes, shared height governor, live-turn streaming hold Registers agentSession.readVisual from the methods index so the structured method file stays under its line budget, replaces reflective reads with checked narrowing, moves the pure height governor to src/shared for mobile, and holds a half-written directive tail while the turn works (structured text rows carry no running state). * fix(native-chat): harden the visual read and link opening Re-checks after the open that the chat's visuals folder is still the real directory at Orca's path, reports unexpected filesystem faults by code without host paths, and lets one click in a visual open at most one page. * fix(native-chat): keep visual lines out of plain-text reply surfaces; review fixes One shared helper drops visual lines (outside fenced code) from reply text where it becomes plain text: the structured status summary that feeds the sidebar row, dashboard, notifications, phone rows and handoffs, and AI Vault reply previews. Review fixes: height also counts a pinned body's overflow, only the live frontier row holds a half-written visual line, the runaway-height stop needs the same step repeated, and any host refusal evicts the cached revision. * fix(native-chat): resolve the visuals folder without the removed journal-paths helper Main removed the per-chat journal paths and the journal database's state directory; the visuals folder keeps the same sha256 layout on its own and the read method uses the profile state directory the chat host is opened in. * fix(native-chat): review round 2 fixes; copy a reply without its visual lines Reply previews in Agent Session History drop visual lines per text part before lines are folded; the frame adds a body's overflow only when the body really overflows; fence tracking follows CommonMark closers and openers; the copy button copies a reply without visual lines; a coded read fault keeps its cause. * fix(native-chat): update the frame's theme ref after render; read the visuals folder pair at once * test(native-chat): declare agentSession.readVisual on the cross-version agent-session surface * fix(native-chat): copying a reply keeps its code blocks and indentation Removing visual lines now closes only the gap each removal leaves, instead of collapsing blank lines across the whole reply and trimming its indentation; the visuals folder is checked parent first again so a broken path answers the same way every time.
…e) (stablyai#26078) * refactor(terminal): renderer senders attach placement to pty spawn, report-only The window now says where each fresh terminal goes: the first pane of a tab sends new-tab with the tab's creation fields, a split sends the parent leaf, direction, ratio and the tree after the split, background launches send the same, and a Codex restart that rebuilds a rootless layout sends root. A reattach sends nothing. Main still only records whether placement names the tab its binding write picks; its 'absent' value now counts only new leaves without placement, so it reads the fallback mint and graft directly. Extends the shared placement type with optional row, ratio and proposedRoot, each dropped alone if malformed, so older and newer peers keep working. * fix(terminal): background agent placement row claims no launchAgent the tab lacks The adopted tab is created without launchAgent, so a row naming one would diverge from the renderer's tab once main applies placement. * fix(terminal): a before-split publishes its pane with the final tree order wrapInSplit now places the new pane first itself, so the pane-created handler's proposedRoot sees the real post-split tree instead of the order before the subtree split moved it. * test(terminal): type the background-terminal tab patch precisely
…tition (stablyai#26088) * fix(ssh): bind reattached SSH panes into the target's own session partition A relay reattach persisted the pane binding into the local partition, minting a minimal copy of every reattached SSH tab there. When nothing saved over it (a reconnect with no window open), the next startup kept that copy and skipped the SSH partition's rows for the workspace: its open editor tabs were missing for a launch and its agent-resume records were dropped (STA-9544). Bind into ssh:<target>, where the spawn bound the same pane. * test(e2e): run the reattach home-partition spec only in the Docker SSH lane * test(e2e): keep Electron running after its last window closes on Linux in the reattach spec * test(e2e): sync the SSH reattach spec on state, not sleeps Waits for the SSH partition to persist the open file and for main's SSH state to report the reattached connection, instead of fixed 3s/30s sleeps that could pass vacuously on a slow reconnect.
…er (stablyai#25146) * feat(native-chat): a paired server keeps a store for chat attachments A structured chat on a paired Orca server had nowhere to put a file the client attached. The server now keeps a per-chat attachment store under its userData, filled through agentSessionAttachment.uploadStart/Append/Commit/Abort and read back for previews through agentSessionAttachment.read, behind the structured session gate and advertised as agent-session.attachments.v1. Nothing records cleanup as owed: a sweep re-derives what may go from the host's chat records and journal (unfinished uploads after an hour, uploads for a chat that never existed or that its journal never mentions after a day). The clipboard RPC's in-flight bookkeeping moves into a shared ChunkedUploadRegistry both use. * feat(native-chat): upload attached files into a paired server's chat store Main streams dropped or picked files (fs:uploadPathsToAgentSessionAttachments) and pasted images (clipboard:saveImageAsTempFile with agentSessionAttachment) into the chat's store on its paired server, reusing the file-import slice streamer and pinning every call to the pairing revision and server process. The browser client's paste takes the same route. * feat(native-chat): attach files to a structured chat on a paired server Pasted images, files dropped from Finder/Explorer and the file picker no longer refuse with "Local attachments are not available for remote sessions." in a structured chat on a paired server: they upload into that server's chat store and the chat gets only the stored path. Dropped files show as pending chips at once so Send waits for them; every attached item carries the server, pairing and chat it was stored for, and a send to anywhere else drops it with the existing "changed hosts" notice. Chips and transcript images read stored files back through the server, never from this machine's disk. An older server gets an update notice instead of an upload. The terminal-backed chat keeps refusing. * test(native-chat): type the attachment test doubles without bare casts * refactor(native-chat): keep the clipboard RPC on its own upload bookkeeping The chunked upload registry stays for the chat attachment store only, beside it. * feat(native-chat): claim chat attachments when the host admits a message A message's references into the host's attachment store are claimed in the same journal transaction that makes it durable (a direct send, a queued draft, a /clear carry). The sweep deletes an upload only after marking it in that database while no claim exists, so a send and a sweep can never both win: a client's send that names an expired or foreign upload is refused whole with the new attachmentExpired reason, before anything is recorded. This replaces scanning chat transcripts for paths. The store is now <root>/<upload id>/<name>, refuses uploads for chats the host does not hold, caps stored names in UTF-8 bytes and avoids Windows device names. The preview read goes through the protected bounded read and the request's reply budget, so an image too large for one reply is refused instead of closing the connection. * feat(native-chat): let structured Claude read the host's chat attachment store Attached non-image files live outside the workspace; the store root is passed as an added directory so the agent reads them without asking, the common pattern. * fix(native-chat): skip a dropped folder before staging walks into it * fix(native-chat): pin the browser client's chat paste to its server Every upload call goes to the environment the paste was meant for and checks its pairing and server process, as the desktop upload does; a re-pair ends the upload instead of storing the image on another server. * refactor(native-chat): let the host's claim be the only attachment check The composer no longer records which server and pairing each attachment came from, and Send no longer strips attachments it judges foreign: the host refuses an expired or unknown stored path when it admits the message, which also covers retries, Stop-restores and queued edits the client check missed. Previews of stored files route through the chat's own server by path. Removing a file's chip while it uploads now keeps its @path out of the draft. A dropped or picked file shows its name and kind on its chip while it uploads, with no separate progress toast, and files that did not attach are named in one notice. A rich-text paste into a chat on an older server no longer shows the update notice beside the pasted text. * chore(native-chat): keep the claim hook beside the draft consume and fit the line budgets The submission's claim runs in the same append hook as a draft's consume, owned by the queued-message collaborator, so the journal store stays within its size limit. Formats the new files and regenerates the runtime-required English catalog. * test(native-chat): pin the attachment store grant in the Claude launch, restore upload result types * fix(native-chat): create the attachment store at install and claim only exact store paths Claude drops an added directory that does not exist when it starts, so the store root is created when the host installs it, best effort. A commit keeps its upload in flight until the rename lands, so a sweep never takes a slow upload's part file. Only a path under this host's exact store root is claimed and required; any other mention of a store path is plain text and never refuses the message. * refactor(native-chat): reuse the newer-Orca notice and drop leftover attach options An older server's refusal to store attachments uses the notice every other write it refuses already shows, so one string fewer in every catalog; the attach callbacks lose an options parameter no caller passes since the provenance check went. * fix(native-chat): give a message refused for an expired attachment back to the composer Sending the same message again can never bring the attachment back, so instead of a Retry that always fails the message's text and images return to the composer, as a Stop's withdrawn message does, and the notice says what to remove. * fix(native-chat): keep uploads across a prompt, say why a file did not attach An upload that finishes while a prompt card has replaced the composer lands in the scope's attachment and draft caches, which the composer reads back when it returns, instead of the unmounted composer. The single failure notice names the cause the files share, such as the size limit. Rich text pasted into a chat on an older server no longer flashes an image chip: the chip waits for the server to take the image. A picked command waits, as Send does, while an attachment is still uploading. * fix(native-chat): keep a paste's upload across a prompt; pin the Claude grant hand-off A paste uploading into a paired server's store keeps its chip live when a prompt card unmounts the composer, and its result is filed for the composer's return, as a drop's is. A failure cause ending in full-width punctuation gets no extra full stop. A runtime test pins that the store root reaches Claude's launch resolver through the adapter. * test(native-chat): type the grant hand-off captures without casts * test(native-chat): pin that the composer files a paste's upload across a prompt * fix(native-chat): retry a held rename at commit and keep cut names Windows-safe A commit renames the part file with the Windows retry the app uses elsewhere, so an antivirus or indexer holding it briefly no longer loses the upload. A name cut to the byte limit is stripped of trailing dots and spaces again. The upload pump's result cast states why it holds. * fix(native-chat): keep attachments still on their way in the pane's attachment cache A prompt card unmounts the composer, and an attachment still saving or uploading lived only in that composer: one that came back showed no pending chip, so Send went out without the file and the file then landed in the next draft. Pending chips now live in the pane's attachment cache beside settled ones and settle there whichever composer is showing, so Send waits for them after a remount too. A stored file's @path goes into the pane's draft cache, which keeps it through an input-method composition and a remount. A rich-text paste's image is registered as a hidden pending chip before the server is asked, so Send waits for it from the start, and it shows only once the server takes it. * test(native-chat): a dropped file still uploading stays pending across a remount * fix(native-chat): reveal a rich-text image in a composer that came back; keep caret inserts A rich-text paste's held image is revealed through the pane's attachment cache, so a composer a prompt card remounted during the server's answer shows it rather than holding Send on an invisible chip. A stored file's @path goes in at the caret while the composer is showing and not composing, as every other attach does; only mid-composition or after a remount does it go to the pane's draft. * fix(native-chat): never evict a pane's attachments while a composer shows them or one is on its way The pane attachment cache is now where chips live, so its 128-scope bound passes over a scope a mounted composer subscribes to or that still holds a pending attachment, and evicts the oldest scope nobody uses instead. Protected scopes are bounded by mounted composers and attachments in flight. * fix(native-chat): never evict the scope just written when every older one is in use * fix(native-chat): keep the attachment store out of the RPC method table's imports The method table is imported far and wide (the SSH relay's CLI included), and the attachment RPCs pulled in the store, whose preview read loads modules that read fs constants at load: any test that mocks fs/promises without them failed to import. The installed store now lives in a small registry module; only the host wiring loads the store itself. * refactor(native-chat): the submission hook gets its own module Merging main added the operation receipt to the queued-message insert, which put journal-queued-messages.ts over the 300-line limit. The submission hook only uses the queued-messages object's public methods, so it moves out. * refactor(native-chat): fit the attachment sweep and paste upload into main's line budgets After merging main, the record store and the clipboard handlers each ran a few lines over the 300-line budget. The sweep now asks the record store whether a chat is recorded through the two lookups it already has (readable or unreadable) instead of a new id-listing method, and the paired-server paste upload lives beside the other attachment uploads. No behavior changes. * fix(native-chat): pending chips sit beside main's saved draft, which keeps server-stored images Main now saves a composer's draft (text and settled images) in one store that survives a reload or quit. This branch kept every chip, settled or not, in its own pane cache. After the merge the saved draft owns settled images and the pane cache holds only chips still on their way: they come back pending in a composer a prompt card remounted, hold Send, settle into the saved draft whichever composer is showing, and are never saved themselves, so a restored draft cannot bring back an upload as if it were attached. An image a paired server stored for the chat is saved with the draft as the real image (a pasted one is no longer turned into a "Not kept" placeholder), and the restore check leaves it to that server, whose claim at Send refuses one it no longer holds, instead of asking this machine's disk for a path that only exists on the server. Also: previews and the pending subscription move into their own small hooks to fit the line budget, the "local attachments" notice moves to the composer-target module so the attachments hook no longer pulls in the upload module's imports, and tests follow main's new mocks. * test(native-chat): give the claims test main's provider handle and message source * fix(native-chat): a paste still uploading dies with its tab or workspace A pasted image still uploading to a paired server sits in the pending chip cache, which by design outlives the composer and its tab. Removing the workspace deleted its saved drafts but left those chips, so the upload finishing afterwards recreated and saved the deleted draft. With the workspace's tabs gone it had no owner either, so no later workspace cleanup could ever remove it. A pending chip now records the owner its draft would have, resolved the way the draft store resolves one when the chip is added. Workspace removal and a user's tab close drop pending chips by the same owner, conversation and tab matches they delete drafts by, and a chip that settles after its chat's tab closed writes its draft under the owner it was begun in. * fix(native-chat): the claim type uses the journal's own SQLite type * refactor(native-chat): read the pending-image handlers off the composer's attachments Main's multi-file picker (stablyai#23956) and this branch together took NativeChatComposer over the 400-line limit; reading the two pending-image handlers the way the neighbouring ones already are keeps it at the limit. * test(native-chat): pass the launch-args resolver main now requires in the attachment grant test * fix(native-chat): grant the attachment store beside folders the saved Claude Arguments add Main (stablyai#25721) now builds additionalDirectories from the user's --add-dir arguments; this branch's attachment-store grant replaced that list instead of adding to it. Both are granted now. Moves the Claude session-id derivation to its own module to keep the resolver under the line limit. * test(native-chat): run the attachment store's SQLite tests in the Node runtime project * test(native-chat): follow main's attach ownership recheck (stablyai#25749) in the attachment tests * test(native-chat): a named upload chip still shows when the agent takes no images * fix(native-chat): a paired-server image paste failure keeps its error apart, as main's notice card does * fix(native-chat): the attachment sweep reads the chat list directly now that records have no import still owed
…er (stablyai#26044) * fix(native-chat): recall every sent prompt with Up/Down in the composer Recall read a list kept in the composer, so it forgot everything on reload or remount. It now reads the prompts the chat shows. * fix(native-chat): put the caret at the end of a recalled prompt It stayed where the last prompt had it, so Up skipped past a recalled multi-line prompt. * test(native-chat): read the editor in the recall spec through a type guard
…ly (stablyai#26207) Copy was always listed, greyed out or copying text selected earlier. Paste was offered on messages, and did nothing in a structured question's answer field.
* ci: run full pull-request unit tests across ten shards * Bound required Linux package tooling setup
…tablyai#26315) * test: keep native filesystem watcher contract under Node * test: reset canonical repository keys with runtime mocks
…orkspace's own copy (stablyai#26098) * fix(session): at startup a local copy never overrides an SSH-owned workspace's own copy Rows the local partition holds for a workspace whose repo the catalog places on an SSH target are residue (pre-stablyai#19572 builds, relay reattach). Startup used to keep them whenever they held a tab and skip the SSH partition for that workspace, dropping live tabs, restoring closed ones and erasing agent-resume records on the first save. Boot hydration now drops those local rows (keeping open files and visit recency) so adoption takes the SSH partition whole. * refactor(session): let adoption take a catalog-owned SSH workspace instead of pre-filtering local Replaces the sentinel-partition split with one rule in adoption: the base's terminal tabs no longer keep out the partition the repo catalog places the workspace on (contested ids keep today's behavior). * fix(session): an SSH-owned workspace's records win under shared keys; unsaved local drafts survive Addresses review: a stale local tab sharing an id with the SSH tab kept its layout and resume records (fill-only), and a replaced open-files row dropped local-only unsaved drafts. * fix(session): an SSH copy with no tabs never replaces local tabs Addresses review: an owned workspace the SSH partition holds no tabs for keeps today's rule, so its empty tab row cannot wipe the local tabs. * refactor(session): drop a superseded local copy before adoption; publish path passes owned ids Simplifies the rule: for a workspace the catalog places on this SSH host, uncontested and with host tabs, the base's rows are dropped and the existing gap-fill adoption runs unchanged; unsaved local drafts survive. One resolver says which workspace each scoped entry belongs to, shared by the census, the drop and adoption. The upload path (persistedSessionForTarget) now passes owned ids too, so main cannot publish the stale local copy to the host. * fix(session): match host tab rows by workspace id; a local draft beats a clean host entry Addresses review: a host tab row under a workspace key now counts toward superseding the local copy, and a local unsaved draft for a path the host holds clean is kept instead of dropped. * fix(session): scope catalog-owned ids to the ssh partition the catalog names A workspace homed on one SSH target with leftover rows in another target's partition no longer has the owner's adopted rows removed by the leftover's pass.
…r tab (stablyai#26284) While the desktop was in the screen saver or minimized, a paired phone opening a browser tab spun forever: the throttled main window stopped compositing, so the guest's captures hung. Each page stream now holds the existing renderer-throttle lease for its lifetime, released by the stream's own cleanup on every exit. A 10 s no-frame deadline reports the existing timeout error so a viewer never spins forever.
… and the host owns what it accepted (stablyai#25959) * fix(native-chat): the host owns the send queue; the window keeps no saved outbox The desktop kept each structured chat's unsent messages in localStorage and sent them in order, so one message whose fate was unknown froze every later send, Retry dropped it silently, failed sends could not be discarded, and an offline chat could send hours later. The host already records every message and owns the queue; the window now only sends. - One in-memory sender for composer, launch prompts and messages sent from outside the chat. One send in flight per chat; a transport failure resends the same id for up to 30 s; a refusal that proves nothing was recorded puts the text back in the composer with the reason; a send that went out and was never answered shows an in-doubt line with Send again and holds nothing up. - The host's "unknown" rows get the same in-doubt line and Send again, from the journal, in every window. - A message an older build left in localStorage is never sent: the host's conversation outline decides what goes back to the composer, and the copy is deleted once that is saved. * fix(native-chat): send through the structured chat RPC wrapper, with its timeouts The sender called the runtime RPC directly, skipping the per-method timeouts every other structured chat call gets. Only a remote host's request skips the compatibility check the sender already ran. * fix(native-chat): an unconfirmed send goes back to the composer, with no new row line The common pattern draws nothing extra on a message whose delivery is in doubt once its turn is over, and puts a failed send's text back in the composer with the reason. So a send nobody answered in time, or one a host answers in a way that proves nothing, goes back to the composer worded as unconfirmed, and a host "unknown" row shows nothing extra. Removes the in-doubt phase, Send again, and its strings. A host's made-up row for an id its journal lost now reads as unconfirmed, not as recorded, so that message comes back instead of vanishing. * test(native-chat): pin that nothing resends a send given back as unconfirmed * fix(native-chat): sends survive a tab close, never resend after a Stop, and keep remote images - A normal tab close lets sends on their way settle; what the host never took goes back to the conversation's draft. Only a cancelled launch or a worktree purge drops them. - After any Stop, a send already on its way is never sent again under its id: a doubtful answer, or a resend that was due, hands its text back worded as unconfirmed. - A returned image keeps the SSH connection it lives on; the connection never goes to the host. - An older build's saved message the host recorded and then rejected is left to the host's own row, never handed back. * fix(native-chat): a Stop or a tab close never resends a send already out, and a kept card is the card's A Stop that landed while a same-id resend was being readied (its timer fired, its request not out yet) let that resend go out after the Stop. The sender now tracks whether a request is awaiting its answer: a Stop hands back every send between attempts as unconfirmed, lets one whose request is out settle from its answer, and never issues a request after it. A normal tab close withdraws the same way instead of doing nothing, so nothing more goes out and nothing is dropped. A send the host rejected but kept as a card (keptAsQueuedMessageId) is the card's, from its reply or the journal, and never goes back to the composer. Adds the freeze tests: a send whose fate is unknown holds later sends no longer than its deadline, and one the host can neither confirm nor deny releases the next at once. * fix(native-chat): read a resend's turned-away call or reused id as unproven Ports the send-answer proof contract. A call the host turned away before running it (method_not_found, invalid_argument, unauthorized) proves only that this request wrote nothing, so it reads as never sent on a first attempt only; on a resend an earlier attempt may have landed, and it goes again under the same id. A resent id the host says was already used for other content (messageIdReused) proves nothing either, like an expired or conflicting id. Pins the rest of the contract: any row the host returns is its own, a thrown error is no answer whatever its code, and an older build's refused, held or outlived-Stop copy is handed back once and never sent. * refactor(native-chat): type the structured composer's send with its attachment type Keeps NativeChatStructuredSession.tsx within the file length limit. * refactor(native-chat): drop the kept-card guards the sender never needed A kept send's row is a rejection that was never a Stop's, so the sender already reads it as recorded, from its reply or the journal. The test that pins it stays; the two extra checks only covered a kept row that is also a withdrawal, which the host never writes. * test(native-chat): route launch tests' sends through the client wrapper the sender calls The sender sends through callStructuredAgentSession, but these launch tests replaced that module with a factory that answered nothing (and still named a probe that no longer exists), so every launch prompt resent until its 30 s deadline and the tests timed out. Each factory now hands sends to the runtime RPC mock the tests already answer, and expectations of a local send no longer ask for the remote-only compatibility option. * fix(native-chat): hand a message back without importing the composer's attachment hook The worktree purge reaches the launch prompt, which hands text back, and the attachment hook's imports reach the store. A test that builds the real store behind a mocked one then waited on itself and hung. Handing back now writes images to the draft store directly, as the hook's helper did, and a test pins that each returned image keeps its SSH connection. * fix(native-chat): one send per chat, with no line of sends behind it A chat with a send out took further messages into an in-memory line and sent them one by one. A message typed behind one in doubt then hit its own 30 s deadline and came back as not sent without ever going out. Now, as the common pattern does, a chat takes one send at a time: while it is out, Send is disabled and Enter leaves the text in the box. The sender refuses a second send instead of lining it up, so the 30 s deadline always runs from the send itself. A Stop or a tab close stops the one send: settled from its answer if its request is out, handed back as unconfirmed between attempts, or silently if it never went out. Notes sent from outside the chat while its send is out stay with their sender (not ready); a launch prompt that meets the person's own first message waits in the composer instead of being lost. * fix(native-chat): give a Stop-withdrawn note back to its chat once its notes were cleared Notes sent from outside a chat clear once their message is recorded. A message the host recorded as pending and a Stop then withdrew came back only to its sender, which had already let go of it, so the text was lost. The chat's draft now takes it, as an earlier build's outbox did. * test(native-chat): type the composer-actions probe without a cast * chore(native-chat): drop outbox wording left in comments and an empty locale group * fix(native-chat): pace failed checks, keep the host's reason, and hold the chat for its launch prompt - A send whose checks failed before its request went out (an unreachable or incompatible host, an unreadable history) was tried again at once, about a thousand times a second for 30 s. Attempts are now paced by the attempts made, whether or not their request went out. - A host that refuses every resend by throwing (native chat turned off, a journal that won't open) gave back only "couldn't confirm". The host's reason now comes first, still without claiming not sent. - A launch's prompt now holds the chat's one send from the click, drawn as sending, so a message typed while the chat starts can't overtake it; it goes out once the chat exists, and a cancelled launch frees it. - Notes whose send nobody could confirm say so instead of "did not accept", and notes launched into a new chat let go of their text once that chat's composer holds it. - An open chat keeps drawing a recorded send until its row arrives, so a reply that beats the history no longer makes the message flicker out. * fix(native-chat): hold a chat's sends until it has started, and send a failed chat's message with its restart A message sent to a chat still starting went out at once to a host that had no record of the chat yet, read for a fence it could not get, and came back as not sent. A message sent to a chat whose start failed restarted it but no longer went with the restart. While a chat starts, Send stays off and Enter leaves the text in the box, as with a send already out. A text message sent to a chat whose start failed restarts it and goes as the restart's first message, through the same staged-prompt path a launch prompt takes: it holds the chat's one send slot, drawn as sending, until the chat exists, and comes back to the composer if the restart fails again. Notes wait while a chat starts and ride a failed chat's restart, keeping their text until it is sent. * chore(native-chat): test the queue request, rejection words and card hand-offs; drop an unused clear - Pin which sends ask the host to queue, the moved rejection wording, and that a queue send whose card was handed off and then refused or withdrawn, or whose replay names a withdrawn card, is never handed back. - The send-at-most-once gate now says what the desktop promises after a reload: it never sends the id again, and hands the text back. - The legacy read names when it goes, and drops the notice clear nothing called. * fix(native-chat): keep a sent launch prompt's entry, and give back at once what can't go out - Cleaning up a launch prompt released its send slot even after the prompt had gone out, which deleted the entry the sender keeps once the host records it. A launch prompt recorded as pending and then withdrawn by a Stop was lost from both the chat and the box, and an open chat dropped the new chat's first message before its row arrived. The slot now gives back only a reservation that was never sent. - A send stopped before its request went out by something trying again won't clear (this client and the server can't talk, or the host refused the history read) kept Send off for 30 s and then said Orca couldn't reach the agent. It now comes back at once with its own cause: not sent, since nothing went out, or unconfirmed if an earlier attempt did. Transport errors keep the paced retries. * fix(native-chat): notes keep their own text through a new agent's launch Notes sent to a New agent whose start failed came back to the notes and also sat in the new chat's composer, so sending both delivered the text twice. The notes keep their text until it goes out (they hold it from the click), so the launch now says so and no composer gets a copy, on a failed start or a refused prompt alike. The tests that asserted the copy in the composer pinned the old double ownership and now assert the notes are its only owner. Notes that rode a failed chat's restart and ended unconfirmed now say Orca couldn't confirm them, as notes sent directly do, instead of that the agent did not accept them. * chore(native-chat): the send-once gate says what the desktop keeps across a reload The desktop keeps a send's id in memory only: a send still unsettled at a reload or crash is not resent and not handed back. The coverage notes no longer credit the renderer tests with durable identity across a remount. * fix(native-chat): say once why notes sent to a new agent did not go Since notes keep their own text through a new agent's launch, a prompt the host refused, nobody could confirm, or that found the chat's send taken came back to the notes with nothing said anywhere: the chat shows no notice for text its caller keeps. The notes menu now reports it once, with the toast a send to an existing chat already uses: not accepted, couldn't confirm, or not ready. A start that failed still says so in the new chat instead. * fix(native-chat): retry a history refusal the host says clears, and name it at the deadline A send stopped before its request went out came back at once for any refusal of the history read, including ones the host names as clearing (its journal briefly unavailable, a chat detached while the host quits), so the automatic retry was lost. Only a version mismatch and refusals that won't clear come back at once now; the rest go again on the paced schedule, and if the deadline still finds nothing sent, the words are the host's refusal rather than Orca couldn't reach the agent. * feat(native-chat): a send makes one request, and nothing ever sends it again The desktop resent a message under its own id for up to 30 s when the answer was lost. A send now makes exactly one request, as the common pattern's clients do: - An answer that is lost, dropped or proves nothing hands the text back at once with "couldn't confirm… check the chat". - A failure before the request goes out (the environment check, the history read for the fence, a refused connection) means nothing went out: the text comes back at once with its own reason, or as not sent. - The 30 s cap stays on the one request, so a host that never answers can't hold Send. Nothing ever resends, so nothing can go out after a Stop: a Stop only takes back a send still in its pre-send checks. The resend state goes with it (tries, generation, awaiting, stopped, the resend timer, the send-answers-proof probe, and the first-attempt/resend split in the evidence), and the send-once gate says the desktop never resends. * fix(native-chat): notes keep the chat's line, and a send held behind a rewind says it was not sent - Only a send whose text belongs to the chat's composer clears the chat's line. Notes sent from outside the chat no longer wipe a "couldn't confirm... Check the chat" line that explains text already back in the box. - A send the host turns away behind a rewind it could not confirm went back as "not sent" but said "couldn't confirm what happened. Check the chat". It now gives the rewind's reason and says the message was not sent. - Reliability gate names the single-request test and records a fresh evidence run; the sender test drops its leftover resend mocks and a duplicate Stop test. * fix(native-chat): a send ends even when putting its text back fails If writing the returned text into the chat's draft threw, the send never settled: it stayed "sending", the chat refused every later send until a reload. The send now always ends after a hand-back, the failure is logged, and the chat's line adds "Couldn't save your message." * fix(native-chat): a message typed during /clear stays in the box and follows the chat A /clear moves the chat to a new conversation, and its host refuses any send while it runs. A message sent then went out, came back refused into the old conversation's draft with its line, and vanished when the chat moved on: the box and the line now belong to the new conversation. - A /clear holds the chat's one send slot while it runs: Enter does nothing, Send shows busy and the text stays in the box, as for a send that is out. Notes sent from outside get "busy". - Once it moves the chat, the old conversation keeps taking no send until the view leaves it, and what is left of its draft moves into the new conversation's draft, after anything there: when the composer's /clear settles, and again when the view moves. * fix(native-chat): no "Send message?" or queue clear while the chat's send is out While a send is out (or a /clear runs) the chat takes no message, yet Enter over a held queue still opened "Send message?", and Clear queue deleted every card before its message was refused. Enter now does nothing there and the text stays; Clear queue re-checks and deletes nothing if a send went out after the question opened. * refactor(native-chat): take the /clear draft carry out of this change Moving the old conversation's draft into the one a /clear replaces it with fixes a bug main has too (text left in the box during a /clear stays with the old conversation), so it goes in its own change. Kept here: a /clear holds the chat's send slot while it runs, and the conversation it moved away from takes no send until the view leaves it. * fix(native-chat): a /clear's hold on sends always ends - A view that unmounted while a /clear that moves the chat was out left the old conversation holding its sends until a reload: the late reply kept the hold for a view that was gone. The reply now releases it. - The local call for a conversation command has no deadline of its own, so a /clear that never answers kept Send off for good. The hold now also ends at the command's deadline (195 s, the one the remote call already uses), whichever comes first. * test(native-chat): opening a chat an older build left stuck; hand back its copy in send order Pins, through the real chat hook, sends, legacy recovery and draft store, what opening such a chat does: the queued messages behind a message the host recorded in doubt come back to the composer once, in order, with the "not sent" or "couldn't confirm" wording; the chat is free to send under its read's fence; nothing happens while the chat has no fence here. The legacy reader now hands entries back in the order they were sent (queuedAt), as the older build's reader did, instead of array order. * fix(native-chat): a send this window turned away for a re-paired server comes back as not sent A managed server's update rotates its pairing, and this window's main process then answers the next call itself, before forwarding anything, with runtime_environment_changed. The send read that thrown answer as proving nothing, so the person was told Orca couldn't confirm a message that never left. It is now handed back as not sent, with the reason. Every other thrown answer still reads as unconfirmed once the request may have gone out. * test(native-chat): a message refused for an expired attachment comes back with its file and why A paired server checks every stored file a message names when it admits it, and refuses the whole message before recording it when one has expired. The sender hands such a message back to the chat's draft, file included, with the refusal's words, whether or not a view shows the chat, so it can be removed and attached again. Ported from the saved-outbox test that came with attaching files to a structured chat on a paired server.
…ablyai#26071) * feat(native-chat): visual directive grammar and host read for a chat's visuals folder A shared grammar for the ::orca-visual{file="..." title="..."} reply line, the per-chat visuals folder location on the owning host, and the agentSession.readVisual runtime method that reads one visual with lexical and canonical containment, a 512 KiB bounded read and UTF-8 refusal. * feat(native-chat): shared frame document for chat visuals One string builder every client wraps a visual's HTML with: the policy (CDN assets only, no fetch, frames, workers, forms or base rewrites), the theme variables, and a prelude that reports height, routes links to the parent and refuses navigation. Also the validated frame-to-parent message reader and the live theme message. * feat(mobile): render native-chat visuals inline in the phone's chat A finished assistant reply's ::orca-visual line now shows the visual inline, read from the chat's owning host through agentSession.readVisual. The visual runs in an opaque sandboxed child of a trusted host document inside the WebView; the app accepts only a token-checked height and an http(s) link opened under user activation. Navigation away from the visual's own document is refused, a dead web process reloads once, and the visual opens full screen. The hybrid shell's page renders it sealed. * feat(native-chat): render chat visuals inline and in the right sidebar Native-chat assistant replies render a ::orca-visual{...} line as the chat's HTML visual in an opaque, scripts-only sandboxed frame: CSP first, the host frame navigation guard registered before content runs, live theme without a reload, fitted height, links opened in the viewer's browser only from a real gesture, lazy mount, and one muted line when the visual cannot be shown. Open in sidebar shows the same frame in the right sidebar, widened while it is open and restored after. * fix(mobile): chat visuals use the shared shell; only the host page may message the app Review round 1: - use PR 1's shared visual shell and height governor instead of a second builder; the app decides heights and pushes them to the host page - react-native-webview patch: the message channel accepts only string messages from the main frame on iOS and Android, so a visual in its sandboxed child cannot reach it (or crash Android with a non-string) - structured replies grow in place: while a turn works, a row holds back a directive still being typed at its tail; finished lines mount - links need child focus + activation, one per activation window - a refused read takes the visual down and drops its cached bytes - in-page anchors load; text spelling a placeholder renders no visuals * refactor(native-chat): move the visual height governor to src/shared The phone bundles only src/shared, so the governor the desktop frame uses moves there unchanged and the mobile frame shares it. * fix(native-chat): point the desktop and mobile frames at the moved governor * fix(mobile): review round 2 for chat visuals - host page relays only messages on the visual's own channel, as its own copy, so a visual cannot push oversized fields through it; relay rate halved; a link is validated before it uses up the link window - the frame denies camera, microphone, geolocation, clipboard and display capture; iOS media capture requests are denied - react-native-webview patch: the iOS history-shim handler also accepts only main-frame string messages - only the newest assistant row of a working turn holds back an unfinished directive; earlier finished rows show their visuals - an error reply or older host is not a verdict: it keeps a visual on screen and its cache, and retries; only a host refusal takes it down * fix(mobile): a drag that starts on an inline visual scrolls the chat iOS gives a scrollable frame its own scroll view, which took the drag; the inline frame is sized to its content, so it no longer scrolls. Full screen still does. * fix(mobile): review round 3 for chat visuals - hold back only the last block of the newest assistant row, and not while a question or approval is open, so a visual followed by a tool call or a pending question shows at once - an older host (method_not_found) reads as unavailable without retries - drop a stray @pnpm/exe lockfile block; only the patch hash changes * test(mobile): list the visual frame's web sibling; mock it in the prompt-controller harness * fix(mobile): an older desktop's mobile allowlist refusal reads as unavailable at once * fix(native-chat): visual CI fixes, shared height governor, live-turn streaming hold Registers agentSession.readVisual from the methods index so the structured method file stays under its line budget, replaces reflective reads with checked narrowing, moves the pure height governor to src/shared for mobile, and holds a half-written directive tail while the turn works (structured text rows carry no running state). * fix(native-chat): harden the visual read and link opening Re-checks after the open that the chat's visuals folder is still the real directory at Orca's path, reports unexpected filesystem faults by code without host paths, and lets one click in a visual open at most one page. * fix(native-chat): keep visual lines out of plain-text reply surfaces; review fixes One shared helper drops visual lines (outside fenced code) from reply text where it becomes plain text: the structured status summary that feeds the sidebar row, dashboard, notifications, phone rows and handoffs, and AI Vault reply previews. Review fixes: height also counts a pinned body's overflow, only the live frontier row holds a half-written visual line, the runaway-height stop needs the same step repeated, and any host refusal evicts the cached revision. * fix(native-chat): resolve the visuals folder without the removed journal-paths helper Main removed the per-chat journal paths and the journal database's state directory; the visuals folder keeps the same sha256 layout on its own and the read method uses the profile state directory the chat host is opened in. * fix(mobile): keep visual lines out of the worktree row and copied message text A reply's ::orca-visual line renders only in the transcript. The worktree list's agent row and the message actions sheet's copy text now drop it with the shared helper; a reply that is only a visual falls back to the prompt, as an empty one does. * fix(native-chat): review round 2 fixes; copy a reply without its visual lines Reply previews in Agent Session History drop visual lines per text part before lines are folded; the frame adds a body's overflow only when the body really overflows; fence tracking follows CommonMark closers and openers; the copy button copies a reply without visual lines; a coded read fault keeps its cause. * fix(native-chat): update the frame's theme ref after render; read the visuals folder pair at once * test(native-chat): declare agentSession.readVisual on the cross-version agent-session surface * fix(native-chat): copying a reply keeps its code blocks and indentation Removing visual lines now closes only the gap each removal leaves, instead of collapsing blank lines across the whole reply and trimming its indentation; the visuals folder is checked parent first again so a broken path answers the same way every time.
…t in a terminal (stablyai#25947) * fix(native-chat): a local chat the host declines opens the agent in a terminal A local structured launch opened its chat tab before this machine said whether it could create the chat, so a "no" (a Claude account bound to WSL, for example) left a failed chat whose Retry failed the same way and no terminal. Local launches now ask first through the same admission a paired server's launch already uses: nothing of the chat exists until the host answers, and a "no" runs the caller's own launch as a terminal. * fix(native-chat): no stray shell while a local chat waits on its host; bound that wait Round 1 review of the ask-first local launch: - A launch waiting on its host now counts as a pending chat create, so the first-terminal watcher no longer seeds "Terminal 1" beside a local chat (watched creates and the empty-workspace default chat). - This machine's answer is waited on for at most 3 s; past it, or while a new workspace is not resolvable yet, the chat opens and its own create reports, as before. Only a real "no" opens a terminal. - The empty-workspace default chat seeds its plain shell on a "no" instead of starting the agent in a terminal nobody asked for, through a narrow launcher option. * test(native-chat): type the declined-create worktree fixture's owner fields * fix(native-chat): one first-surface claim for a launch waiting on its host The wait was held in a separate record only the passive watcher read, so re-activating the still-empty workspace during the wait seeded "Terminal 1" through the activation's reseed. The wait now lives in the empty-workspace default-surface claims both seeders already read, and the separate record is gone.
…i#26425) * Give Windows Electron writer probes a larger test budget * Drain queued writer replies across two poll turns before timing out * Check writer shutdown after both reply-drain turns * Add inline always-force-delete preference to workspace recovery toast * Use split-button menu for saved force-delete preference * Await durable force-delete preference writes before deleting * Restore absent settings keys after failed durable writes * Place deletion translations beside worktree copy
…blyai#26358) * Native chat: let users turn off inline visuals for new chats * Native chat: keep inline visuals guidance light * Move inline visuals setting to Chat settings
* Allow another phone send after delivery is unconfirmed * Update phone image recordings after journal removal
# Conflicts: # .gitignore # mobile/pnpm-lock.yaml # src/renderer/src/components/NativeChatResumeOnRestartAgentRow.tsx # src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts # src/renderer/src/i18n/en-runtime-required.json # src/renderer/src/lib/github-work-item-workspace-attachment.ts
* perf: stop retrying relay bulk frames after admission * fix: prevent relay bulk retries during admission * test: prove relay bulk admission races remain exercised * test: limit teardown watchdog to rejected admissions
… disclosure after upstream sync
…n-app folder picker in golden e2e
…PaneHeaderOverlay test)
Owner
Author
|
superseded by #15 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
fork-mainhad fallen 125 commits behindstablyai/orcamain. This PR merges upstream in by hand (a--no-ffmerge, the same approach asconfig/scripts/fork-sync-fork-main.mjs) and keeps every fork customization. The automatic sync allowlist is unchanged.What Changed
Before:
fork-mainat6433503260, 125 behind upstream.After:
fork-maincan take two--no-ffmerges of upstreammain. The first mergesb1cd85820band the second catches up to8ae5264c63, the upstream tip at push time. After this PR,fork-mainis 0 behind upstream.Conflicts (first merge only; the catch-up merge was clean):
.gitignore(allowlisted): applied the workflow's own union rule (unionIgnoreFile): keep ours and append the missing theirs lines. The fork's!docs/fork-entry-points.mdand!docs/reference/...whitelist entries stay.mobile/pnpm-lock.yaml: kept the fork lockfile, with its extra patched deps@react-native/codegenand@xterm/addon-webgl. Only thereact-native-webview@13.16.2patch hash changed, to upstream's55b7bce3…, which matches sha256 of the merged patch file.src/renderer/src/components/NativeChatResumeOnRestartAgentRow.tsx: took upstream's resume-tree row (useContext+ResumeTreeRow) and kept the fork'sAgentIconimport from@/lib/agent-catalog-icon.src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts: kept the fork'suseTerminalDictationEventshook (fork dictation: backspace cap / replace). Took upstream's removal of the legacy per-terminalonFileDropeffect, because upstream moved drop ownership elsewhere (sta-6940).src/renderer/src/i18n/en-runtime-required.json: kept the fork keys, then regenerated withsync:localization-runtime-catalog. The verifier passes.src/renderer/src/lib/github-work-item-workspace-attachment.ts: kept the fork re-export ofsrc/shared/github-work-item-workspace-attachment.ts, which the CLItasks reviewuses. Ported upstream's new attachment-aware matching (sourceUrl,worktreeMatchesGitHubNumber) into that shared module with shared-relative imports.Follow-up fix:
config/scripts/mobile-web-bundle-packaging-workflow-contract.test.mjsis a fork-kept contract. Its census now also findswin-orcad-serve-switch-e2e.yml serve-switchas a packaging job after upstream's script changes, so that job is added toEXPECTED_PACKAGING_JOBS. Its per-job bundle assertions already pass.Upstream deletions taken as-is: about 200 upstream-only files (Claude managed-account modules, structured-session outbox, legacy drop owners, many
docs/reference/*.md). None of them were modified onfork-main, so the merge reported no modify/delete conflicts. The fork'sdocs/reference/own-mobile-relay-key-expiry.mdstays.Fork customs kept: pet voice /
pet.speak.*, grok--agent minimal, Herdr, trusted-machine relaykeyExpiryDisabled, and the fork packaging contracts (fork-*workflows, packaging contract test, sequentialbuild:speech-macos). Grep counts forpet.speak,herdrandkeyExpiryDisabledare identical before and after.Why
Expanding
AUTO_RESOLVABLE_SYNC_PATHSwould teach the daily job to auto-resolve product files, so these conflicts are resolved in a normal PR instead. Taking either side wholesale would drop fork dictation, the shared CLI attachment helper, or upstream's new behavior.Linked Issue
Fork ops: maintainer-approved sync merge PR (Karl, 2026-10-08). No upstream issue.
Visual Proof
N/A. This is a merge-conflict resolution with no fork UI restyle.
Testing
AUTO_RESOLVABLE_SYNC_PATHSunchangedverify:localization-runtime-catalogpassespnpm typecheck: not run locally because it ran out of memory on the shared box; CI covers itAgent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
No tag, release or deploy. Coolify and production stay on HOLD.
Checklist
N/Awith reason