mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
de3da487fa6bbfde6f9e4b53fba7672fada5322c
16002
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e273ec3119 |
refactor(ipc): route filesystem git handlers through the host router (#27059)
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
d068d852ba |
fix(native-chat): hide agent message controls while the turn runs (#26404)
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
b44a5796c1 |
fix(native-chat): settle a reply cut off by a server restart (#26460)
* fix: preserve streamed turn duration after host restart * test: run restart duration regression in Node * fix: preserve provider receipt time in journal updates * test: verify provider receipt in Claude ownership assertion * fix(native-chat): time interrupted turns from real provider output, not flushes Shared-timeline checkpoints stamped the coalescer flush or retry time as the provider's latest proof of life, so a turn whose provider died before a delayed flush settled with an inflated duration after a host restart. Carry each delta's receipt time through the coalescer and timeline lane. * fix(native-chat): derive interrupted-turn duration from saved output A reply cut off by a server restart could show "Worked for 0s" because recovery ended the turn at the last heartbeat or the reply's first timestamp. End it at the latest time this owner's output was saved to chat history instead, derived in the existing journal fold from the row save time. Drops the per-frame receipt plumbing through every provider and buffer. * fix(chat): exclude host recovery revisions from provider activity Mark journal-open and connection-cleanup revisions with the existing recovery annotation so an interrupted turn does not include host downtime. Preserve buffered provider output and advance restart fixtures to the restart clock. * fix(native-chat): interrupt turns a replaced server left running When the managed server died mid-reply and a new one started on the same host, the cut-off turn settled `unverifiable` with no end whenever the old provider's identity could not be proven gone, and nothing ever revised it: the chat showed no turn status and the sidebar said "unconfirmed". The new server opens the chat records holding the host's instance lock, so every runtime that wrote them before is gone, and every provider it held lost its pipes with it. Record that at load (the fences those runtimes granted, the loaded owner's last renewal, how its runtime ended) and let the stale settle end those runtimes' running or unverifiable turns as interrupted at the owner's last saved output or renewal, with the existing Orca-stop row. Lease adjudication and the live / unverifiable / exited owner verdicts are unchanged. * fix(native-chat): require the record store for any journal open that settles Only an acquisition's open may skip it; a reader open without the store would leave a replaced server's turns unverifiable. * fix(native-chat): say a remote host's Orca restarted, not that it was closed A reply cut off when a remote host's Orca server stopped read "Orca on <host> was closed while this response was in progress." Nobody closed anything there: the server stopped and Orca started it again before the row could show. For a chat on a remote environment, cause quit now reads "Orca on <host> restarted while this response was in progress." The desktop's own quit keeps "was closed". Renderer-only; no wire change. * fix(native-chat): say a remote host's Orca stopped, true for a shared desktop too * test(native-chat): register Grok without compaction in the recovery-output rig Main's #26502 made Grok declare compaction; the rest-test rig adapter has no compact, so the registry refused it before any recovery case ran. * chore(native-chat): say a replaced runtime also revises an unverifiable turn |
||
|
|
59631b8b26 |
fix(native-chat): edit a queued message in place, even while a question is open (#26886)
* feat(native-chat): edit a queued message in place on the host A queued card's Edit used to copy its text into the composer and delete the card, so the message lost its place, an agent-mail card lost its sender, and while a question or approval replaced the composer the text vanished. The execution host now edits the card itself: - agentSession.queuedMessageUpdate replaces only the card's text, as a compare-and-set on the body fingerprint the client last saw. It keeps the id, position, sender, kind, send mode and attachments; a command card is not editable. A resent Save answers from the card as it stands (unchanged or changed), so a lost answer never overwrites newer text. - agentSession.queuedMessageEditHold acquires, renews and releases an in-memory edit lease per caller and edit. While one is live, automatic delivery stops at the card (earlier cards still send, later ones wait); an explicit Send still overrides. Leases expire after two minutes on their own, re-derive validity from the card on every read, and die with the conversation. Release uses cleanup admission. - The published card carries an optional editHeld marker, never `paused`. - Both methods sit behind the new agent-session.queued-message-edit.v1 capability, independent of the dark mid-turn queue capability, and are on the mobile allowlist for the phone follow-up. * feat(native-chat): edit a queued card inline on desktop Edit message now opens an editor inside the queued card instead of moving the text into the composer. The card keeps its place and sender, and the editor stays usable while a question or approval holds the composer's slot (it does not lose focus to the prompt card). - Enter or Cmd/Ctrl+Enter saves, Shift+Enter is a newline, Escape cancels; IME composition keys never save or cancel. Save and Cancel replace the card's own actions while it is edited; one editor per pane. - The editor holds the host's edit lease, renewing every 30 seconds and taking it again after a lapse. Lease failures are only logged: they never block typing, Save or Cancel. - If the card is sent or removed elsewhere mid-edit, a clean edit just closes; changed text goes into the chat box after any existing draft, with one toast. A Save that lands and then sends counts as success. - Against a host without agent-session.queued-message-edit.v1 the card offers no Edit (no copy-and-delete fallback); an empty overflow menu is hidden. * fix(native-chat): name every queued-edit notice in its switch * fix(native-chat): keep queued-edit typing safe and stop disabling Send elsewhere - Save with an unreadable answer (dropped for a moved runtime fence, or lost) now reads the published card: saved text shown -> close; card gone -> the sent-as-saved / chat-box path; otherwise keep the editor quietly. - Send and Steer stay enabled on a card edited on another device; the published editHeld marker is gone (no reader left). Automatic delivery still stops at an edited card on the host. - Save answering changed moves the typing to the chat box and reopens the editor on the newer text. Notices say when the chat box is hidden by a question or approval. - Closing the pane or switching chats with unsaved typing appends it to that chat's box quietly. - Lease renewal no longer publishes, so an open editor is not idle-sweep activity. - The consume compare-and-set also matches the body fingerprint. - Saving unchanged text keeps the original blocks. * fix(native-chat): keep Agent in the Japanese queued-edit notices, as the catalog does * fix(native-chat): close a queued edit its own Save already landed, wake the queue for a pruned lease, keep typing across a reload A Save whose answer was lost now closes once the card shows the saved text, and a card that went out with exactly the typed text closes quietly instead of copying it to the chat box. A renew that prunes another editor's lapsed lease wakes the queue in place of the timer it cancels. A reload, which unmounts nothing, hands unsaved typing to the chat box like closing the pane. * refactor(native-chat): move the queued-edit notices to their own module * fix(native-chat): unref the edit-lease timer in a way the phone's typecheck accepts The lease module reaches the mobile test program through journal-store, where setTimeout answers a number, so `timer.unref?.()` failed mobile's tests typecheck ratchet. * test(cross-version-wire): call the queued-edit methods in both skew directions The manifest must name every agentSession method; the two edit methods reach their host methods on a new build and are only called by clients on a host advertising agent-session.queued-message-edit.v1. * test(rpc): count the two queued-edit methods on the structured surface * fix(native-chat): lay out the inline queued-message editor as a header, field and Save footer While a card is edited, its row shows a pencil, "Editing message", the sender and an X that cancels; the field at full width; and a footer with the key hint and Save. The card's queue icon and its status line are hidden until editing ends. * fix(native-chat): show the Enter key on the queued-message editor's Save button The editor footer's muted key hint is gone. Save now carries the same inline key badge as the new-workspace composer's Create button, extracted into a shared ButtonKeyHint so both buttons render one badge. |
||
|
|
6cf1aa00f4 |
refactor(ssh): register each SSH host's providers as one set (#27044)
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
280ef3c6f3 |
fix(native-chat): accept sends through their own receipt (#26672)
* feat(native-chat): add a chat-owned command receipt store Adds the agent_session_command_receipts table and an insert-if-absent receipt write that runs inside a command's own journal transaction. A receipt records the command's identity, canonical hash, status and a pointer to the row holding its result; it dies with its chat record. Nothing uses it yet: the existing operation ledger stays authoritative. * fix(native-chat): key command receipts like today's ledger ids Rows are keyed by caller and operation id; a send looks across callers and other methods look up only their caller's row, matching the existing ledger. Bad rows and newer table shapes read as unreadable instead of throwing. * fix(native-chat): order receipt lookups by insertion and key by operation id Global lookups now take rows in insertion order, so a read and an insert agree on which receipt answers an id regardless of the host clock. The primary key leads with the operation id, so its own index serves both lookups. * fix(native-chat): accept sends through their own receipt Send (direct, queued, steer) and /compact no longer commit a separate ledger reservation before the message is saved. Their command receipt is inserted-if-absent in the same transaction as the submission or queued draft; a repeated id replays from it, a changed request conflicts. Operation ids are checked for syntax only, so age, clock skew, pruning and the reservation's own storage can no longer refuse a send. The ledger success row they already co-committed stays as a temporary insert-if-absent write so families not yet switched keep seeing these ids; it is never read on this path. * fix(native-chat): answer a send's receipt one way, share /compact's id space with Send, wake delivery once - After its commit, a send whose publication fails answers through the same receipt replay a retry takes, so one id gets one answer; the in-memory copy and journal reload are gone. - /compact ids are global like Send's: a retry under a new connection replays instead of needing a second acknowledgment write. - The receipt route calls its runner directly; the runner wakes delivery when a new message row commits. - The compatibility ledger row expires from host time; each temporary site is labelled. - Receipt read errors and post-commit failures are logged. * test(native-chat): expect receipt answers where tests assumed the send ledger Restart-continuation tests now model the case Send cannot vouch for (saved but not loaded into memory) and keep it unconfirmed; a later throw after the message is in the chat answers continued, as a resend would. Drop /compact's unreachable legacy replay branch and fix stale comments. * test: run the queued tests that open the journal database in the Node runtime project |
||
|
|
09aff1b94d |
perf(usage): keep per-source usage caches off the main thread (#26980)
* perf(usage): keep per-source usage caches off the main thread The usage stores kept every provider's per-source scan cache (processedFiles / processedDatabases) inside the one JSON file main loads at startup. On a large Claude history that file is 56 MB, 43 MB of it per-source records only a scan reads, so main paid a sync read+parse at launch, a structured clone of the whole cache to and from the scan worker on every scan, and a pretty-printed rewrite after every scan. The usage scan worker now owns those records in a sidecar (`<cache>-sources.json`): it reads it before a scan, writes it after (compact, durable rename), and returns only sessions and daily aggregates. Main keeps and writes only that report, compact. A cache still carrying inline records is split on the worker the first time it is loaded (above 8 MB); smaller ones parse on main and rescan cold. Sync IPC getters and automation lookups wait for the load. Synthetic 56 MB cache, same machine, interleaved runs: - first launch: 108 ms sync parse -> 18 ms main busy, 4 ms longest block - later launches: ~69 ms -> ~7 ms - forced scan, longest main-thread block: 89 ms -> 11 ms (real built app, headless: 84-94 ms -> 11-13 ms) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(usage): preserve cache attribution and legacy warm scans * fix(usage): reclaim cache temps after worker retirement --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
618dc62889 |
perf(daemon): send PTY output to main as raw bytes, not NDJSON (#27006)
* perf(daemon): send PTY output to main as raw bytes, not NDJSON The stream socket's hello can now negotiate binary-v1 framing: length-prefixed frames carrying PTY data as raw UTF-8 next to a small JSON header, so neither the daemon nor main escapes or parses the payload. Older daemons ignore the hello field and stay on NDJSON; older mains never ask. The hello reader now hands the stream reader the exact bytes that followed its line, which an event sharing the hello's read used to lose. ORCA_DAEMON_BINARY_STREAM=0 keeps NDJSON. Fixes #27003 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(daemon): preserve negotiated output and encode ordinary frames once --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
9b0117480a |
Add the inert layout client for local and remote runtimes (#27000)
* feat(layout): add the inert one layout client for local and remote runtimes The renderer's RuntimeLayoutClient sends layout commands and reads the layout.subscribe stream through one transport interface, with the same client code over the local runtime and a paired server. Frames go to a read-only cache whose only writer is applyFromRuntime: a snapshot replaces every workspace, a workspace frame replaces one, removed drops one. A lost stream resubscribes and the new snapshot replaces the cache; nothing is merged or replayed. Nothing in the app uses it yet. - The stream's frame types move to shared so the runtime and the client share them. - subscribeRuntimeRpc is the streaming twin of callRuntimeRpc, taken unchanged from the structured session client, which now uses it. * fix(layout): stop on a refused layout stream and keep equal cached layouts - A host that answers method_not_found or forbidden is not retried: the subscription stops and reports a refused status the caller can read; only other failures back off and resubscribe. - applyFromRuntime keeps the cached layout when the runtime's is equal by value, so a reconnect snapshot with unchanged data replaces nothing. - A snapshot frame's subscriptionId is optional to the reader. * fix(layout): resubscribe on a desynced layout stream and slim the layout client A malformed frame of a known type, or a change before the snapshot, now resubscribes for a fresh snapshot instead of leaving the cache stale. The retry backoff resets only after a change proves the stream stayed up, adds jitter, and stops for a revoked pairing or a removed or replaced server. The client is one function over subscribeRuntimeRpc, the cache has one writer, and the change-frame type lives in the shared frames module. * fix(layout): drop the defensive layout-stream handling with no user flow Unreadable frames are ignored again, the retry delay has no jitter, and the client no longer tracks server removal or the unauthorized code; the switch closes a removed server's subscription. The backoff still resets only after a change frame, with no new state. |
||
|
|
3dd332ec0d |
fix(chat): keep select-all and copy off the terminal under a chat (#26831)
Cmd+A in the transcript selected the hidden terminal, and every later Cmd+C copied that instead of the highlighted message. |
||
|
|
094f589977 |
Floating-workspace chats of every native-chat agent keep links in the folder they started in (#26981)
* fix(native-chat): resolve every registered agent's floating chat to its pinned folder Drop the unused showStructuredAgentSessionChoice and say what the built-in agent check means. * fix(native-chat): launch floating OpenCode, Grok, OMP and Pi chats in their pinned folder The renderer now resolves every floating chat's links, images and skills against the folder the chat was pinned to. The ACP (OpenCode/Grok/OMP) and Pi launchers still started the agent in the current floating folder setting, so after the setting moved and the agent relaunched, the agent ran in one folder while links resolved against another (and Pi forked its conversation into the new folder). Both launchers now use resolveAgentSessionLaunchDirectory, the helper Claude and Codex already use: launch in the pin, refuse with the existing "folder this chat ran in no longer exists" message when it is gone, and pin an unpinned floating record on first launch. Worktree and folder chats are unchanged. The tab-directory reader now calls the status bridge's own isStructuredTab instead of restating its predicate inline. |
||
|
|
7634bbfffe |
Start plain new-tab agent launches through the host's agent.launch (#26489)
* Start plain new-tab agent launches through the host's agent.launch The tab bar's + menu, Quick Launch, the floating panel and its shortcut, the new-agent shortcut and dashboard spawn now start a no-prompt agent through the host, the way main's AI-button launches already do, wherever that is main's own launch: chat default off, enabled agent, not an SSH workspace. Everything else keeps the window's startup queue. * Type the new-tab host route tests without assertions * Keep a new tab the window launches on another platform on main's path On a Windows client a WSL-path workspace launches as Linux from the window, which the host does not infer, so it keeps the window's own launch. |
||
|
|
7450f40662 |
Every agent reports its learned default model once, when a chat starts (#26982)
* fix(native-chat): a chat says what its config resolved once, at its start
Every options read re-reported a no-pick chat's configured default: ACP
re-derived it from the session's current state, and Pi re-sent its start
snapshot. A chat left open kept writing its start model back over a newer
default another chat had learned since (for example after the user changed the
agent's own default), so new chats named the old model and then jumped.
A ready child's acquisition now carries its start listing, with the default its
start resolved after restoring the chat's saved picks, and the host saves it
once when the owner is proven (a starting child already hands it on its
`started` event). Options reads carry the model list only. Grok, OpenCode, OMP
and Pi share this path and the one no-pick rule.
* fix(native-chat): only a chat's own effort menu outranks a newer one
The configured-effort fallback took the older listing's menu whenever it had
the effort, so an older background listing could bring back an effort the
agent's own newer menu no longer offers. It now falls back only to the chat's
own listing.
* fix(native-chat): an empty Pi settings file or dotfile-only extensions folder picks nothing
Pi reads an empty settings file as no settings, and a .pi/extensions folder
holding only .DS_Store or .gitkeep loads no extension, so neither keeps the
"Model" placeholder any more. The BOM strip is written as an escape. Tests
cover the packages key and an extensions folder that can't be read.
* fix(native-chat): Claude says what its config resolved once, with its start's settings readback
Claude's configured default rode on every options read, so an open Claude chat
with nothing picked kept writing back the model its settings applied when it
started, over a newer default another chat had learned since. Its default is
known only once the settings readback after `started` lands, so that readback's
`options-reported` event now carries the start listing with the resolved
default, and the host saves it once for the child that reported it. Options
reads carry the model list only, as for every other agent.
* fix(native-chat): a start's learned default is saved behind the attach, and never fails the start
Claude's settings readback, its one report of what the config resolved, was
checked against the host's child index as it arrived. A readback that beat a
slow attach found no child yet and was dropped for good. It now waits in the
session's serialized step behind that attach, as `started` does.
Handing a ready child's start listing to the catalog ran inside the acquire's
cleanup scope, so a throw there would have torn down a proven child. It is
bookkeeping: a failure is logged and the start proceeds.
* refactor(native-chat): name the Claude listing's configured-default option; Pi's start listing is read-only
No behavior change: claudeCatalogListing takes { withConfiguredDefault } instead
of a trailing boolean, and PiRpcSession exposes its start listing through a
getter over a private field.
|
||
|
|
8e4716aa65 |
refactor(pty): PTY controller inventory and startup waits take a host id (#26998)
The runtime PTY controller's listProcesses / supportsForegroundProcessEvidence and the local daemon-startup waits took a connectionId where null meant this machine and undefined meant every host. They now take an ExecutionHostId (omitted = every host for inventory, this machine for startup waits); id-routed callers pass the PTY's resolved host instead of re-deriving a raw SSH connection id. Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
9d94a20f84 |
fix(browser): stop a cancelled drag from leaving every browser tab unclickable (#26986)
The webview registry made every webview click-through on dragstart and only restored it on dragend or drop. A dragstart that something cancels (for example pressing and moving an agent logo in the tab strip, which the tab drag sensor cancels) never starts a drag, so neither event fires and every existing browser tab ignores clicks until a later full drag or a reload. A live native drag delivers no pointer events to the page, so any pointerdown or pointermove now ends the native-drag passthrough. Renderer pointer drags hold their own passthrough tokens and are unaffected. |
||
|
|
807e93baac |
Open links and window.open in Orca tabs the way Chrome does (#26976)
* fix(browser): route links and window.open to tabs by Chromium's own disposition window.open(url, '_blank', 'noopener,noreferrer') opened a floating popup window because the new-tab check required an empty features string. Chromium already reports Chrome's tab-vs-window decision as the disposition, so the one rule is now: a tab disposition opens an Orca tab unless the page keeps a named handle it may use (OAuth); size/position features and Shift-click stay popups. The injected click-routing script that relabeled link clicks is gone: native dispositions route target=_blank, Cmd/Ctrl-click, middle-click and iframe links identically. Real clicks still bypass the page-initiated tab budget, now via the browser-process input gesture tracker the route guests already use. Co-authored-by: Lasha Kvantaliani <lasha@sweenk.com> * refactor(browser): spend the click on every window open and drop the noopener parser Review follow-up: a click that opened a popup window stayed unspent and could fund a later scripted tab; every open now consumes it, as Chrome does. The noopener parser only changed named noopener opens, which stay working child windows, so the rule is just an unnamed open with a tab disposition. * test(browser): table-test the new-tab rule and correct stale comments --------- Co-authored-by: Lasha Kvantaliani <lasha@sweenk.com> |
||
|
|
9a175d6800 |
fix(native-chat): match model picker type to the options menu (#26349)
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
0f7c06aedb |
fix(native-chat): identify a paired device by its device id, never its credential (#26777)
A paired device's socket passes its bearer credential as clientId, and native chat used it as the caller key it stores in ledger rows, receipts, chat records and prompt answers (shown to other clients). Name a paired device by the device id its socket proved instead. |
||
|
|
47a46c1b92 |
Show the usage unit once in the status bar (#26869)
* Show the usage unit once in the status bar Add formatBareUsagePercentage and a leading Used/Remaining label so each status-bar usage number renders as a bare percentage. The label stays mounted while every chip is folded into +N, and measureUsageRow counts it so density levels don't oscillate. * Derive status bar usage sections in one place The status bar chips and the shared used/remaining unit label each had their own copy of the rules for which percentages are shown. The two copies could disagree, so the unit label could stay visible when no percentage was showing. Both now read from getStatusBarUsageSections. - Collapsing the last visible percentage chip also frees the unit label's width, so the density calculation no longer leaves a gap. - Verbose bucket chips take a labelFirst flag so the name still renders before the percentage, as before. - Compact and verbose modes share one section builder, and the verbose-only mini bar is kept as before. |
||
|
|
0a1d09d237 |
fix(layout): an absent editor or browser map is not a round-trip change (#27007)
Main's row mint stores an SSH partition without openFilesByWorktree /
browserTabsByWorktree, and the Serializer always writes them as {}. The
load report compared absent with {} and flagged both as unknown changes,
failing the SSH layout-oracle e2e scenarios on every PR that runs them.
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
|
||
|
|
a9458b5d14 |
fix(native-chat): every new-chat request opens its own chat (#26782)
* fix(native-chat): a second new chat opens even when the first is still empty
* fix(native-chat): a new chat joins another launch only when it re-delivers the same request
A request with text ("Send notes > New agent", browser annotations) used to take over a blank
chat that was still starting in the same tab group, so the same action landed in the just-opened
blank tab or in a new tab depending on how fast the first chat started. Every start now opens its
own chat unless it re-delivers the same request id (or resumes the same conversation).
Removes what only that claim read: the claim branch in the launch join, the attempt's blank flag,
the request's text/group fields, the launch options' targetGroupId and the launch target's
groupId (the tab still opens in the caller's group), the empty-chat and chat-tab-group modules,
and the draft-load-pending probe only the empty-chat check used.
|
||
|
|
000cfe5193 |
Add "Copy sign-in link instead" for Claude accounts (STA-9912) (#26990)
* Catch Claude's sign-in link through BROWSER and wait 10 minutes for sign-in A hidden claude auth login now runs with BROWSER pointed at a small script in the account folder that hands the localhost-callback link back to Orca. Orca opens it in the default browser as before, or hands it to Settings to copy when asked. Windows host sign-ins keep Claude opening the browser itself. * Add Copy sign-in link instead to Claude Add Account and Sign in again * Only catch the Claude sign-in link when copying it Plain Add Account and Sign in again no longer set BROWSER, so Claude opens the browser itself exactly as before. A copy-link sign-in that cannot prepare its BROWSER stand-in now fails instead of opening the default browser, a failed chmod in WSL no longer stops the login, and a failed clipboard write shows the existing copy-failed toast. * End a copy-link Claude sign-in that gets no sign-in link, and simplify its plumbing A missing or unexpected link now fails the sign-in instead of leaving a 10-minute spinner. The link check moves to main and only tells the localhost link from the pasted-code one; the poll is a plain loop, the pending link exists only for copy-link sign-ins, and copyLink travels as a plain boolean. * Pass the started Claude sign-in to the copy-link helper and give its copy-failed toast its own key * Remove a partly written Claude sign-in script when setup fails |
||
|
|
e2234a37ef |
Let a Codex chat stop a command it left running in the background (#26780)
* Let a Codex chat stop a command it left running in the background A command Codex started and left running (a dev server, a watcher) showed in the background-tasks strip above the composer with no Stop, because Codex reported no stop it could honour. Codex 0.140+ has one: the app-server's experimental thread/backgroundTerminals/terminate names the process by the processId on the command's item, and thread/backgroundTerminals/list shows what still runs. The command tracker now keeps each command's processId. The first time a running command carries one, the session probes its own app-server once with a one-row list; a listing marks the app-server's backgrounded commands stoppable and the session as offering per-row Stop and Stop all, through the fields the strip already reads. Any refusal (an older Codex answers "unknown variant"), timeout or malformed answer leaves it as today: no Stop. A Stop terminates each named process, then confirms against the thread's own list. A thread Codex says it unloaded counts as stopped; a dropped connection does not, since lost contact is no proof a process ended. An app-server that times out is not asked again. The killed process's own item/completed removes its row, as before. * Ask Codex again after a probe gets no answer, and say when a background Stop fails A background-terminal probe that timed out or lost its request used to hide Stop for the rest of the app-server's life. Only a refusal by name (unknown variant, method not found) or a malformed listing now settles it as unsupported; anything else is asked again on the next update. A background Stop whose agent failed (a process still listed after its stop, a timeout) used to answer "nothing stopped" and the button simply came back. The host now refuses it, so both clients show their usual notice: "The background task wasn't stopped." This covers Claude's background Stop too. * Say a background Stop wasn't stopped only when the agent shows it still running A timeout or lost contact with the agent leaves the outcome unknown, so it now answers with the existing outcome-unknown refusal instead of "The background task wasn't stopped." * Keep a background task's Stop in its stopping state until the row leaves A confirmed Stop re-enabled its button for the moment between the host's answer and the roster dropping the row. The button now holds while the host says it stopped the task and the strip still lists it, on desktop and phone; a failed, unconfirmed or nothing-stopped answer, or the row coming back, offers Stop again. * Keep Grok's own subagent Stop refusal quiet, and a reply about another subagent unconfirmed * Report Grok's own refusal of a subagent Stop as not stopped Grok answers an ended or unknown subagent with a normal reply, so a JSON-RPC refusal means it did nothing and the subagent still runs. |
||
|
|
7012f8455c |
[CI] Advance debounce clocks and simplify test fixtures (#26996)
* [CI] Advance test debounce clocks and remove workflow text checks * test: use a representative PowerShell command fixture |
||
|
|
1a2ccb856e |
Start server-run automations' agents through the launch executor (#26820)
* Start headless automations' agents through the launch executor Server-run automations, both the new-workspace and existing-workspace runs, now start their agent through executeAgentLaunch as a terminal-only launch with automation-owned factories. argv agents keep the prompt folded into their command; agents that read the prompt after start get it from the executor's own terminal writer once their composer is ready. The run is recorded as soon as the terminal exists, as before. * Deliver headless post-start prompts on Windows hosts and keep session starts out of run completion - Headless automations pass the write guard's write-unless-shell policy, as worker briefs do, so a host that cannot find the agent in front (Windows) still gets the prompt; only a proven shell refuses. agent.launch keeps refusing by default. - judgeIdleRun ignores session-boundary status rows: a SessionStart row lands before the prompt and must not settle the run completed. - A launch failure after the terminal was published is logged instead of dropped. * Deliver headless post-start prompts through the create's follow-up writer QA found the executor's composer-readiness writer drops the prompt of a slow-starting post-start agent (aider): it watches the input-box signal only ~8 s, then tui-idle, which those agents never satisfy. Main's follow-up writer (wait for the agent process, type the prompt) delivered it. - Extract the follow-up writer into an awaitable deliverWorktreeStartupFollowup (same waits, write and log lines); sendWorktreeStartupFollowupWhenReady wraps it. The runtime exposes it as deliverStartupFollowup, and the headless launch's deliverTerminalPrompt uses it, so the executor still sequences the launch with main's delivery. - Revert the round-1 write-guard option and the judgeIdleRun session-boundary rule, which existed only because of the executor's writer. |
||
|
|
8455c97183 |
refactor(pty): key the PTY registry by execution host (#26995)
The PTY ownership map and provider registry now speak ExecutionHostId (`local` / `ssh:<target>`), with this machine as an ordinary host instead of `null`. An unowned PTY id resolves through getPtyExecutionHost: a bare id stays local by explicit rule (daemon restores arrive before ownership is recorded), while a paired-runtime `remote:` id or a malformed `ssh:` id now fails closed instead of being driven on this machine's provider. Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
bf3f9f410e |
refactor(runtime): runtime file commands use a provider for every host (#26994)
Local file work now has a provider object: createLocalFilesystemProvider binds this machine's runtime file operations to the filesystem contract, with path authorization injected from the caller's store. The host router's local filesystem route carries the factory (as the git route does), and requireRuntimeFileProvider returns a provider for local too, so the runtime file commands drop their "SSH provider, else node:fs" branches. Behaviour is the runtime handlers' own, moved unchanged; desktop IPC handlers are untouched. Preview, chunk reads, mobile reads and upload commit keep their local arms: their local limits, result shapes or symlink handling differ from the contract. Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
e88c8c76fd |
chore(providers): neutral router rationale and typed provider generation (#26925)
Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
0703073b61 |
Add an inert read-only layout stream and the layout round-trip self-check (#26964)
* feat(layout): read-only layout.subscribe stream and the round-trip self-check (inert) - layout.subscribe streams each workspace's layout as an explicit published projection (tabs, groups, panes and their terminals), never the model itself: no runtime records, closed-tab records, spawn inputs or legacy persistence values. It is re-projected from the Store's sessions after each write, only while someone subscribes, and only from the partition that owns each workspace. Not advertised as a capability and not on the mobile allowlist, so nothing calls it yet. - Replaces the unused terminal-topology publisher (#25682) it generalizes. - The round-trip self-check: a partition must load and save to a fixed point, changing only the kinds of value the Loader's rules change. It runs on Store writes in dev and e2e (logged, never thrown) and as the oracle's new roundTrip check. * fix(layout): know the spawn handoff main's minimal row mint stores * fix(layout): name-based invented ids, no partition on the wire, isolated listeners - Every id the Loader invents is derived from what it stands in for (a workspace's group from its key, a pane from its row or tab and position, a repeated tab id from its terminal), so removing an earlier workspace or tab no longer re-ids later ones. - The published layout no longer names its partition: the workspace key is the address and routing stays with the host; each tab still names the host that runs it. - One throwing stream listener no longer costs the others the event. - The self-check says plainly it is an idempotence check, and logs each new kind of first-load change with its count. |
||
|
|
5ffc8d7972 |
Keep Chat UI users with a Terminal default on the terminal, with a chat mode switch in the upgrade tip (#26915)
* Keep Chat UI users who opened new tabs in the terminal on the terminal Before the chat upgrade, Experimental Chat UI had a separate Default view choice, and it defaulted to Terminal. The upgrade retired that choice so Chat UI on now means new agent tabs open as chat, which moved people who had kept the terminal default into chat with no obvious way back. On load, a profile saved by a pre-upgrade build with Chat UI on and Default view not set to Chat now gets Chat UI turned off, and is left out of the one-time native chat upgrade tip. The old key is stripped on the first save, so the migration runs once and a later opt-in sticks. * Update the saved-settings test for Chat UI on with Default view Terminal * Show the upgrade tip to terminal-default users, with a switch to turn on chat mode People who had Chat UI on with new tabs opening in the terminal now keep the terminal on upgrade, but they still need the tip's directions for moving between chat and CLI. They now get the same upgrade tip, plus a note that new agent tabs still open in the terminal and a "Turn on chat mode" switch. The switch shows when Chat UI is off as the tip opens and stays put while it is flipped. The tip is taller in that case so nothing scrolls. * Decide the chat mode switch from the saved tip audience, not the live setting The upgrade tip's 'Turn on chat mode' switch showed whenever Chat UI was off when the tip opened, so a chat-view member who had turned Chat UI off also got it. Main now answers with one tip variant (none, standard, keep-terminal) from the saved audience, and only keep-terminal shows the note and switch. Also moves the feature tip panel styling into a DialogContent surface variant and drops the Label restyle, and tests that the switch stays put after flipping. * Let the upgrade tip decide the chat mode switch from its variant The modal now passes the saved tip variant to the dialog, which decides the switch itself, so the dialog tests cover that decision. |
||
|
|
62878c3b0c |
fix(folders): folder workspaces can answer "paired server" (#26958)
* fix(folders): folder workspaces can answer "paired server" * fix(folders): name a server folder's nested target only when its id is unambiguous * fix(folders): read the folder group only when the answer needs it --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
4a964633f4 |
fix(automations): stop offering Blank Terminal where it cannot be chosen (#26961)
Blank Terminal is now opt-in on AgentCombobox. Only the new-workspace composer opts in; automations, source-control agent actions, and chat continuation offer agents only. The props are a discriminated union so agent-only callers' onValueChange never receives null. Fixes STA-9868 |
||
|
|
b8a0554357 |
refactor(runtime): runtime git commands use a provider for every host (#26965)
requireRuntimeGitProvider now returns a provider for the local host too (the local git provider, built per call from the worktree's WSL options and shared link paths), so the runtime git commands drop their "SSH provider, else local free function" branches. Push-target materialization stays host-specific and moves into one helper. Branch-diff keeps its two arms because the local git function returns one pinned diff, not the contract's list. Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> |
||
|
|
2356baa181 |
Open a chat visual in its own tab instead of the sidebar (#26728)
* Open a chat visual in its own tab instead of the sidebar * Keep chat visual tabs alive across tab switches and finish editor-tab consolidation - Render chat visual tabs from a worktree-level retained overlay so switching tabs or moving the tab to another split never reloads the page. - Reopening a visual focuses it in whichever split holds it, takes its newest title, and opening from the floating panel no longer takes the main window's editor selection (check-details opener uses the same helper). - Visual tabs return to the reopen-closed stack and show in the tab palette. - One editor-family list and one virtual-tab predicate: no Copy Path, Copy Relative Path, Reveal or git status decoration for check-details and chat visual tabs. * Give chat visuals one open path and re-read them when shown - Reopen-closed (Cmd/Ctrl+Shift+T) of a chat visual goes through the visual opener, so it focuses the visual in whichever split holds it and keeps its id when a runtime environment is active. - A visual tab re-asks its host each time it is shown or reopened; an unchanged revision keeps the page as is, a changed one shows the new page, and an unavailable tab recovers. - The tab palette no longer shows or matches a virtual tab's internal id; the new-tab search shows the chart icon for visuals. - Cmd/Ctrl +/- uses app zoom and Alt+Z leaves word wrap alone while a virtual tab (CI details, chat visual) is active. - A tab dragged over a visible visual passes through the frame, using the existing drag passthrough. * Slim the chat visual tab back to the minimum Remove the retained overlay, the visibility re-read, the zoom and word-wrap rerouting, palette inclusion, reopen-closed routing and the check-details opener change. The visual tab renders through the editor area again, stays off the reopen-closed stack, and keeps: one tab per visual across splits, no main-window selection change from the floating panel, the newest title on reopen, and no Reveal or git status letter on virtual tabs. * Re-read an open chat visual when it is opened again; no watcher for virtual tabs - "Open in new tab" on a visual whose tab is already on screen in another split now re-asks the host, so a rewritten visual shows its new page. - CI details and chat visual tabs no longer start a workspace file watcher. - Drop the unreachable chat-visual case from the restore-time transient check, use the shared virtual-tab check in the header path, and say why visuals stay off the reopen-closed stack. * Let tab drags pass over chat visuals; hide path copies on virtual tabs - A chat visual's frame joins the existing tab-drag pointer pass-through, so a tab dragged over a visual (in its tab or inline in a chat) can drop or split there instead of the frame swallowing the drag. - The tab menu no longer offers Copy Path / Copy Relative Path for CI details and chat visual tabs, whose path is an internal id; no stray separator. * Reuse the hover icon for editor tab icons, so a chat visual shows its icon in both |
||
|
|
2cb2bef67e |
feat(native-chat): highlight code blocks and shell commands (#26343)
* feat(native-chat): highlight code blocks and shell commands Uses Shiki rather than the highlight.js already in the preview: it can resume mid-block while a reply streams. * fix(native-chat): leave the tool row command unhighlighted * refactor(native-chat): highlight code through a shared TextMate interface Chat code now goes through one renderer highlighting interface built on Shiki core and the Oniguruma WASM the editor already loads for Nim and Typst, with VS Code Light+/Dark+. Lines that are too long or too slow leave the rest of the block plain, failed grammar loads retry from a mounted block, every block shares one time slice, finished-block caching counts retained text, each theme keeps its own formatting, Vue template expressions are colored, and tool-row commands take the grammar of the shell they ran in. * chore(deps): drop pnpm self-install entries from the lockfile Keeps the lockfile change to Shiki alone; the earlier commit picked up the @pnpm/exe entries pnpm 12 writes when it runs in a fresh checkout. * test(native-chat): give cold grammar loads time in highlighting tests The first colored block in a test file loads its grammar and the regex engine, which took over a second on a busy CI runner. --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
470f685b80 |
refactor(providers): local git provider behind the host router (#26955)
* refactor(providers): local git provider behind the host router Adds createLocalGitProvider, which binds this machine's git free functions to the IGitProvider contract with the admission tiers and WSL options the local call sites use today. The router's local git route now carries it as a per-call factory. No caller changes yet. * refactor(providers): read shared link paths lazily in the local git provider --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
dfffb302f8 |
Start orchestration workers as native chats for every agent that has native chat (#26776)
* fix(orchestration): start native-chat workers for every registered native-chat agent * chore(orchestration): correct the restored worker agent fallback comment |
||
|
|
8defd660c7 |
A new Pi chat names the model it will run on its first frame (#26821)
* refactor(native-chat): one rule for what a no-pick session says of its configured default ACP's rule (only a new session that restored no model pick and was sent none runs its config's default; an effort it picked says nothing) moves beside the catalog entry types so other agents' adapters report through the same step. An empty listing (a signed-out agent) now says nothing instead of "no listed model", so it can never retire a saved default. * feat(native-chat): check Pi's project config before naming its default model Pi reads `.pi/settings.json` in the chat's folder and loads project extensions from `.pi/extensions` (and the `extensions`/`packages` lists). A settings file that sets defaultProvider, defaultModel, defaultThinkingLevel, modelThinkingLevels or enabledModels, loads extension code, or can't be parsed counts as the project picking the model; extension code may register providers or set the model itself. Key names come from Pi 1.0.4's own settings manager and docs, read without running Pi. * feat(native-chat): a new Pi chat names the model and thinking level Pi starts on Pi's model listing marks no model as the one it runs, so a new Pi chat with no saved pick showed "Model" and then changed to the model its session reported. A new Pi session (not one resumed or forked from a saved conversation) that restored no model pick and was sent none now reports the model and, unless it picked one, the thinking level it started on, through the same host step as Grok, OpenCode and OMP. The host saves them as the account's default behind the project-config check, so the next new Pi chat names them on its first frame. A signed-out Pi, which lists no model, saves nothing. * fix(native-chat): a newer, coarser effort menu keeps the effort a chat ran The saved list takes each model's effort menu from the newer listing. Pi's `--list-models` menu stops at high, while a chat's own menu adds xhigh or max for models whose thinking map allows them. After a background re-list, a learned xhigh was no longer in the menu, so the first frame dropped it. When the configured effort is only in the other listing's menu, that menu is kept. * fix(native-chat): Pi teaches only the model and thinking level it started on A Pi chat re-derived its configured default from Pi's current state on every options read, so a model an extension switched to mid-chat (extensions can call setModel) was saved as the account default, and the next new chat named the wrong model. The choice is now computed once, after start-up restores the chat's saved picks, and every later read reports that snapshot. A pick made in the chat no longer matters to it. Tests cover an extension switch, a pick made in the chat, a forked chat, and a learned xhigh surviving a newer re-list. |
||
|
|
573ff0bca3 |
Save a new chat's first message during create (#26332)
* fix(native-chat): the host owns the send queue; the window keeps no saved outbox The desktop kept each structured chat's unsent messages in localStorage and sent them in order, so one message whose fate was unknown froze every later send, Retry dropped it silently, failed sends could not be discarded, and an offline chat could send hours later. The host already records every message and owns the queue; the window now only sends. - One in-memory sender for composer, launch prompts and messages sent from outside the chat. One send in flight per chat; a transport failure resends the same id for up to 30 s; a refusal that proves nothing was recorded puts the text back in the composer with the reason; a send that went out and was never answered shows an in-doubt line with Send again and holds nothing up. - The host's "unknown" rows get the same in-doubt line and Send again, from the journal, in every window. - A message an older build left in localStorage is never sent: the host's conversation outline decides what goes back to the composer, and the copy is deleted once that is saved. * fix(native-chat): send through the structured chat RPC wrapper, with its timeouts The sender called the runtime RPC directly, skipping the per-method timeouts every other structured chat call gets. Only a remote host's request skips the compatibility check the sender already ran. * fix(native-chat): an unconfirmed send goes back to the composer, with no new row line The common pattern draws nothing extra on a message whose delivery is in doubt once its turn is over, and puts a failed send's text back in the composer with the reason. So a send nobody answered in time, or one a host answers in a way that proves nothing, goes back to the composer worded as unconfirmed, and a host "unknown" row shows nothing extra. Removes the in-doubt phase, Send again, and its strings. A host's made-up row for an id its journal lost now reads as unconfirmed, not as recorded, so that message comes back instead of vanishing. * test(native-chat): pin that nothing resends a send given back as unconfirmed * fix(native-chat): sends survive a tab close, never resend after a Stop, and keep remote images - A normal tab close lets sends on their way settle; what the host never took goes back to the conversation's draft. Only a cancelled launch or a worktree purge drops them. - After any Stop, a send already on its way is never sent again under its id: a doubtful answer, or a resend that was due, hands its text back worded as unconfirmed. - A returned image keeps the SSH connection it lives on; the connection never goes to the host. - An older build's saved message the host recorded and then rejected is left to the host's own row, never handed back. * fix(native-chat): a Stop or a tab close never resends a send already out, and a kept card is the card's A Stop that landed while a same-id resend was being readied (its timer fired, its request not out yet) let that resend go out after the Stop. The sender now tracks whether a request is awaiting its answer: a Stop hands back every send between attempts as unconfirmed, lets one whose request is out settle from its answer, and never issues a request after it. A normal tab close withdraws the same way instead of doing nothing, so nothing more goes out and nothing is dropped. A send the host rejected but kept as a card (keptAsQueuedMessageId) is the card's, from its reply or the journal, and never goes back to the composer. Adds the freeze tests: a send whose fate is unknown holds later sends no longer than its deadline, and one the host can neither confirm nor deny releases the next at once. * fix(native-chat): read a resend's turned-away call or reused id as unproven Ports the send-answer proof contract. A call the host turned away before running it (method_not_found, invalid_argument, unauthorized) proves only that this request wrote nothing, so it reads as never sent on a first attempt only; on a resend an earlier attempt may have landed, and it goes again under the same id. A resent id the host says was already used for other content (messageIdReused) proves nothing either, like an expired or conflicting id. Pins the rest of the contract: any row the host returns is its own, a thrown error is no answer whatever its code, and an older build's refused, held or outlived-Stop copy is handed back once and never sent. * refactor(native-chat): type the structured composer's send with its attachment type Keeps NativeChatStructuredSession.tsx within the file length limit. * refactor(native-chat): drop the kept-card guards the sender never needed A kept send's row is a rejection that was never a Stop's, so the sender already reads it as recorded, from its reply or the journal. The test that pins it stays; the two extra checks only covered a kept row that is also a withdrawal, which the host never writes. * test(native-chat): route launch tests' sends through the client wrapper the sender calls The sender sends through callStructuredAgentSession, but these launch tests replaced that module with a factory that answered nothing (and still named a probe that no longer exists), so every launch prompt resent until its 30 s deadline and the tests timed out. Each factory now hands sends to the runtime RPC mock the tests already answer, and expectations of a local send no longer ask for the remote-only compatibility option. * fix(native-chat): hand a message back without importing the composer's attachment hook The worktree purge reaches the launch prompt, which hands text back, and the attachment hook's imports reach the store. A test that builds the real store behind a mocked one then waited on itself and hung. Handing back now writes images to the draft store directly, as the hook's helper did, and a test pins that each returned image keeps its SSH connection. * fix(native-chat): one send per chat, with no line of sends behind it A chat with a send out took further messages into an in-memory line and sent them one by one. A message typed behind one in doubt then hit its own 30 s deadline and came back as not sent without ever going out. Now, as the common pattern does, a chat takes one send at a time: while it is out, Send is disabled and Enter leaves the text in the box. The sender refuses a second send instead of lining it up, so the 30 s deadline always runs from the send itself. A Stop or a tab close stops the one send: settled from its answer if its request is out, handed back as unconfirmed between attempts, or silently if it never went out. Notes sent from outside the chat while its send is out stay with their sender (not ready); a launch prompt that meets the person's own first message waits in the composer instead of being lost. * fix(native-chat): give a Stop-withdrawn note back to its chat once its notes were cleared Notes sent from outside a chat clear once their message is recorded. A message the host recorded as pending and a Stop then withdrew came back only to its sender, which had already let go of it, so the text was lost. The chat's draft now takes it, as an earlier build's outbox did. * test(native-chat): type the composer-actions probe without a cast * chore(native-chat): drop outbox wording left in comments and an empty locale group * fix(native-chat): pace failed checks, keep the host's reason, and hold the chat for its launch prompt - A send whose checks failed before its request went out (an unreachable or incompatible host, an unreadable history) was tried again at once, about a thousand times a second for 30 s. Attempts are now paced by the attempts made, whether or not their request went out. - A host that refuses every resend by throwing (native chat turned off, a journal that won't open) gave back only "couldn't confirm". The host's reason now comes first, still without claiming not sent. - A launch's prompt now holds the chat's one send from the click, drawn as sending, so a message typed while the chat starts can't overtake it; it goes out once the chat exists, and a cancelled launch frees it. - Notes whose send nobody could confirm say so instead of "did not accept", and notes launched into a new chat let go of their text once that chat's composer holds it. - An open chat keeps drawing a recorded send until its row arrives, so a reply that beats the history no longer makes the message flicker out. * fix(native-chat): hold a chat's sends until it has started, and send a failed chat's message with its restart A message sent to a chat still starting went out at once to a host that had no record of the chat yet, read for a fence it could not get, and came back as not sent. A message sent to a chat whose start failed restarted it but no longer went with the restart. While a chat starts, Send stays off and Enter leaves the text in the box, as with a send already out. A text message sent to a chat whose start failed restarts it and goes as the restart's first message, through the same staged-prompt path a launch prompt takes: it holds the chat's one send slot, drawn as sending, until the chat exists, and comes back to the composer if the restart fails again. Notes wait while a chat starts and ride a failed chat's restart, keeping their text until it is sent. * chore(native-chat): test the queue request, rejection words and card hand-offs; drop an unused clear - Pin which sends ask the host to queue, the moved rejection wording, and that a queue send whose card was handed off and then refused or withdrawn, or whose replay names a withdrawn card, is never handed back. - The send-at-most-once gate now says what the desktop promises after a reload: it never sends the id again, and hands the text back. - The legacy read names when it goes, and drops the notice clear nothing called. * fix(native-chat): keep a sent launch prompt's entry, and give back at once what can't go out - Cleaning up a launch prompt released its send slot even after the prompt had gone out, which deleted the entry the sender keeps once the host records it. A launch prompt recorded as pending and then withdrawn by a Stop was lost from both the chat and the box, and an open chat dropped the new chat's first message before its row arrived. The slot now gives back only a reservation that was never sent. - A send stopped before its request went out by something trying again won't clear (this client and the server can't talk, or the host refused the history read) kept Send off for 30 s and then said Orca couldn't reach the agent. It now comes back at once with its own cause: not sent, since nothing went out, or unconfirmed if an earlier attempt did. Transport errors keep the paced retries. * fix(native-chat): notes keep their own text through a new agent's launch Notes sent to a New agent whose start failed came back to the notes and also sat in the new chat's composer, so sending both delivered the text twice. The notes keep their text until it goes out (they hold it from the click), so the launch now says so and no composer gets a copy, on a failed start or a refused prompt alike. The tests that asserted the copy in the composer pinned the old double ownership and now assert the notes are its only owner. Notes that rode a failed chat's restart and ended unconfirmed now say Orca couldn't confirm them, as notes sent directly do, instead of that the agent did not accept them. * chore(native-chat): the send-once gate says what the desktop keeps across a reload The desktop keeps a send's id in memory only: a send still unsettled at a reload or crash is not resent and not handed back. The coverage notes no longer credit the renderer tests with durable identity across a remount. * fix(native-chat): say once why notes sent to a new agent did not go Since notes keep their own text through a new agent's launch, a prompt the host refused, nobody could confirm, or that found the chat's send taken came back to the notes with nothing said anywhere: the chat shows no notice for text its caller keeps. The notes menu now reports it once, with the toast a send to an existing chat already uses: not accepted, couldn't confirm, or not ready. A start that failed still says so in the new chat instead. * fix(native-chat): retry a history refusal the host says clears, and name it at the deadline A send stopped before its request went out came back at once for any refusal of the history read, including ones the host names as clearing (its journal briefly unavailable, a chat detached while the host quits), so the automatic retry was lost. Only a version mismatch and refusals that won't clear come back at once now; the rest go again on the paced schedule, and if the deadline still finds nothing sent, the words are the host's refusal rather than Orca couldn't reach the agent. * feat(native-chat): a send makes one request, and nothing ever sends it again The desktop resent a message under its own id for up to 30 s when the answer was lost. A send now makes exactly one request, as the common pattern's clients do: - An answer that is lost, dropped or proves nothing hands the text back at once with "couldn't confirm… check the chat". - A failure before the request goes out (the environment check, the history read for the fence, a refused connection) means nothing went out: the text comes back at once with its own reason, or as not sent. - The 30 s cap stays on the one request, so a host that never answers can't hold Send. Nothing ever resends, so nothing can go out after a Stop: a Stop only takes back a send still in its pre-send checks. The resend state goes with it (tries, generation, awaiting, stopped, the resend timer, the send-answers-proof probe, and the first-attempt/resend split in the evidence), and the send-once gate says the desktop never resends. * fix(native-chat): notes keep the chat's line, and a send held behind a rewind says it was not sent - Only a send whose text belongs to the chat's composer clears the chat's line. Notes sent from outside the chat no longer wipe a "couldn't confirm... Check the chat" line that explains text already back in the box. - A send the host turns away behind a rewind it could not confirm went back as "not sent" but said "couldn't confirm what happened. Check the chat". It now gives the rewind's reason and says the message was not sent. - Reliability gate names the single-request test and records a fresh evidence run; the sender test drops its leftover resend mocks and a duplicate Stop test. * fix(native-chat): a send ends even when putting its text back fails If writing the returned text into the chat's draft threw, the send never settled: it stayed "sending", the chat refused every later send until a reload. The send now always ends after a hand-back, the failure is logged, and the chat's line adds "Couldn't save your message." * fix(native-chat): a message typed during /clear stays in the box and follows the chat A /clear moves the chat to a new conversation, and its host refuses any send while it runs. A message sent then went out, came back refused into the old conversation's draft with its line, and vanished when the chat moved on: the box and the line now belong to the new conversation. - A /clear holds the chat's one send slot while it runs: Enter does nothing, Send shows busy and the text stays in the box, as for a send that is out. Notes sent from outside get "busy". - Once it moves the chat, the old conversation keeps taking no send until the view leaves it, and what is left of its draft moves into the new conversation's draft, after anything there: when the composer's /clear settles, and again when the view moves. * fix(native-chat): no "Send message?" or queue clear while the chat's send is out While a send is out (or a /clear runs) the chat takes no message, yet Enter over a held queue still opened "Send message?", and Clear queue deleted every card before its message was refused. Enter now does nothing there and the text stays; Clear queue re-checks and deletes nothing if a send went out after the question opened. * refactor(native-chat): take the /clear draft carry out of this change Moving the old conversation's draft into the one a /clear replaces it with fixes a bug main has too (text left in the box during a /clear stays with the old conversation), so it goes in its own change. Kept here: a /clear holds the chat's send slot while it runs, and the conversation it moved away from takes no send until the view leaves it. * fix(native-chat): a /clear's hold on sends always ends - A view that unmounted while a /clear that moves the chat was out left the old conversation holding its sends until a reload: the late reply kept the hold for a view that was gone. The reply now releases it. - The local call for a conversation command has no deadline of its own, so a /clear that never answers kept Send off for good. The hold now also ends at the command's deadline (195 s, the one the remote call already uses), whichever comes first. * test(native-chat): opening a chat an older build left stuck; hand back its copy in send order Pins, through the real chat hook, sends, legacy recovery and draft store, what opening such a chat does: the queued messages behind a message the host recorded in doubt come back to the composer once, in order, with the "not sent" or "couldn't confirm" wording; the chat is free to send under its read's fence; nothing happens while the chat has no fence here. The legacy reader now hands entries back in the order they were sent (queuedAt), as the older build's reader did, instead of array order. * fix(native-chat): a send this window turned away for a re-paired server comes back as not sent A managed server's update rotates its pairing, and this window's main process then answers the next call itself, before forwarding anything, with runtime_environment_changed. The send read that thrown answer as proving nothing, so the person was told Orca couldn't confirm a message that never left. It is now handed back as not sent, with the reason. Every other thrown answer still reads as unconfirmed once the request may have gone out. * Save a new chat's first message during create * Align opening-message types with the user-message wire * Keep chat publication and cleanup outside store initialization * Complete the create refusal fixture ledger * test(native-chat): prove a created first message waits for the agent's start and is handed over once * refactor(native-chat): keep the host and session hook under the line limit after the main sync * test(native-chat): a chat created with its first message and no pick still teaches its agent's default After the main sync, a created chat's agent starts from the delivery loop rather than the create call. Grok (through the real ACP adapter and model catalog) and Claude (through the real adapter) each still report the model their own config resolved to once the chat reads its options. |
||
|
|
35b145dbc8 | [CI] Run Activity tests together and replace the real idle wait (#26949) | ||
|
|
6a929b2023 |
fix(hosts): host registry stops minting client SSH hosts from a server's nested target (#26952)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
8b2a5e45f1 |
Re-land Claude per-account folders (#24434, #26405) with the upgrade and proxy fixes (#26801)
* Revert "Revert Claude per-account folders (#24434, #26405) before release (#26638)"
This reverts commit
|
||
|
|
ce9eb662bb |
refactor(renderer-ui-store): clean up Phase 3-3.8 code (#26903)
Share one bounded generation map across SSH, runtime SSH, slug resolution and session-tab tracking; reuse the SSH target generation helpers; replace the removed-flag chain in SSH target cleanup with a patch accumulator; derive purge recency targets from the rows the drop already removed; share the add-repo stale guard and runtime target; share the host-setup availability rule; drop the dead sync session persister; reuse host-id constructors in the file explorer; trim remote browser stream comments. Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
65b2fcf89a |
Start AI commit messages from the same shell environment chats use (#26808)
* Start AI commit messages from the login-shell environment chats use A Dock-launched Orca only takes PATH from the user's shell, so variables exported in the shell rc (proxy base URL, auth token) never reached the commit-message agent even though chats and terminals saw them. Local host generation now starts from the same cached login-shell base env as chats, with account overrides applied on top; WSL and SSH paths are unchanged. * Prefer the login shell's config directory over Orca's inherited one for commit messages |
||
|
|
6dab0cb7fe |
refactor(ipc-persistence-shared): clean up Phase 3-3.8 code (#26902)
- Install store domain contexts with one generic symbol copy - Share one import-source classifier and one copy-name deconflicter across local, SSH and runtime-upload imports - Collapse kill.ts synthetic-exit and stop-failure repetition into helpers - Use Promise.allSettled in SSH transport teardown - Use the shared errorMessage() instead of inline copies - Fold isLiveSshPtyLease into ssh-types and drop the duplicate predicate Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
a8ce574c77 |
Add inert layout commands and runtime transitions (#26749)
* feat(layout): add the inert workspace layout model, Loader and Serializer One model of a workspace's tab layout (all tab kinds, groups, split trees, pane bindings, titles/colors/pin, sleeping records) in src/shared/workspace-layout, with a Loader from today's stored session partition and a Serializer back to it. The structural rules check and the pure pane/group tree helpers move into the same folder so the module can run them; their callers import the new paths. Nothing in the app calls the new module yet. * feat(layout): add inert layout commands and runtime transitions applyLayoutCommand applies every layout command a client can send (create, split, close, move, reorder, rename, recolor, pin, open editor/browser/agent tabs, start/restart, sleep/wake) to the layout model, and applyLayoutTransition applies the facts the runtime sees (process started or exited, SSH lease ended, orphan adopted, owner removed or renamed, agent launch verdict, sleeping records). Pure tab-order, group-move and pane-split helpers move into the shared module so the renderer, today's headless host and the new commands share one implementation. Nothing in the app calls the new commands. * fix(layout): avoid toSorted in shared code and drop an unused helper The mobile bundle includes src/shared and Hermes lacks Array#toSorted. * refactor(layout): name tree comparisons for what they ignore * fix(layout): one stored home per layout fact; Loader reports every rule it applies The row's terminal id, preview flag, worktree id and execution host are now derived on save instead of kept a second time. Groups never stay empty. The Loader reports row/tab-bar disagreements, re-minted ids, worktree id and host disagreements, keeps a row stored in two workspaces where the tab bar names it, and gives a pane id repeated in a second tab a new unbound pane. The rules check runs on what the Serializer writes. Fixtures become test fixtures, and a test lists every model field with the one fact it holds. * fix(layout): commands keep one home per fact and refuse instead of throwing createGroup is gone (a group only exists while it holds a tab); a lone tab split beside another group moves its group leaf and keeps the id; moveTab refuses when the move cannot be built; renaming onto an existing workspace is refused; worktree and execution host ids come from the workspace. * fix(layout): derive each tab's host from its owner and keep one preview fact per editor tab An editor tab names its file's owner (an SSH target or paired runtime), every other tab its partition. Where the stored tab and file preview flags disagree the tab loads permanent, so a draft can never be replaced. A row terminal that differs from the one derived from its focused pane is now reported. * fix(layout): one binding kind, one title, one host rule; records follow a reassigned pane A legacy row with no pane layout now loads as a one-pane tab bound to its terminal (reusing the leaf its records name), so the null-panes branches and legacyPtyId are gone. A pane given a new id because two tabs shared it takes its sleeping and incarnation records, scrollback and focus with it. A terminal's live title is one fact (the tab-bar label wins on load). Every tab-bar entry names its execution host by one rule. The fact-location test is now keyed by on-disk field and checked by mutating every source. * fix(layout): commands drop the legacy binding branches; rename carries closed-tab records Every terminal tab has panes now, so commands lose their null-panes branches and a close always releases the tab's terminals. The pane-record rekey is shared with the Loader. Renaming a workspace renames its closed-tab records' worktree id, so a later removal or partition move still selects them. * fix(layout): one change report by construction; records without copies The Loader now picks values by fixed precedence only and reports one field-by-field diff of the stored session against what the next save writes, over the disk-field tables (now in workspace-layout-disk-fields.ts, with session fields derived from SESSION_FIELD_OWNERS). Sleeping and closed-tab records live in their workspace without the pane key, tab and worktree id their key and workspace already give; the Serializer fills them. Tab-bar entries are written in the one tab order. * fix(layout): drop transitions with no producer yet; group moves hold no selection orphanAdopted, legacyWorkerRecovered, clientHostedPageAnnounced, agentLaunchVerdict, sleepingRecordsCaptured and moveWorkspaceToPartition ship with the PRs that produce them. The shared group moves take and return LayoutGroup only; the headless host keeps its selected tab beside them. Commands keep sleeping and closed-tab records in their workspace, so rename and removal need no record patching. * fix(layout): name the change report's key-list parameter's type * refactor(layout): key pane data by leaf, check the model itself, one legacy record - Each pane's data (terminal, incarnation, title, sleeping agent) is one leaf record keyed by leaf id, and scrollback beside it is keyed the same way. Pane keys are derived only when saving. The Loader gives a repeated leaf id a new id before attaching records, so rekeyPaneRecords is gone; records naming no pane are carried as stored. - The rules check runs on the model directly (ungrouped tab, empty group, group tree that disagrees with the groups, pane data without a pane); the disk-format rules stay separate. - The row-ownership marker and topology revision move into one legacy record outside WorkspaceLayout, updated by updateLegacyPersistence after each apply. - Fixtures include a terminal tab whose tab-bar id and terminal id differ. * refactor(layout): commands on leaf-keyed pane data and the one legacy update - Commands and transitions read and write each pane's one leaf record; drag-out moves only the tree, so the pane's data and scrollback follow by structure. No command advances the topology revision itself: applyLayoutCommand and applyLayoutTransition run updateLegacyPersistence after each apply, which also marks a workspace as owning its rows once it holds a terminal. - Preview replacement closes the preview through closeTabs, so an unsaved draft refuses it as any close does. - Tests: create then close then save with and without a reload, commands on a tab whose tab-bar and terminal ids differ, drag-out scrollback, the preview refusal. * fix(layout): join the group ids the tree-mismatch detail names * fix(layout): leave the stored revision map as is when no repo advances |
||
|
|
cce31b4b4d |
refactor(renderer-terminal-runtime): clean up Phase 3-3.8 code (#26900)
- One helper plans and runs a paste into a terminal pane (five copies before) - Reuse getShortcutPlatform instead of three private copies - runtime-git-client becomes plain re-exports - TerminalErrorToast builds each token's test/replace regex from one helper - Reuse PreconnectInputWriter for the recovery input hold - Name the session-tabs snapshot callback type once - Narrow isTerminalDriverState with 'in' instead of a cast - Inline the single-use terminal-pane-client-host wrapper Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
150bf49cb2 |
feat(kiro): report Kiro agent status and list Kiro sessions in history (#26695)
* feat(kiro): report Kiro agent status through managed hooks Kiro keeps hooks inside each agent config, so Orca installs one managed hook per event into $KIRO_HOME/agents/*.json (locally and on SSH hosts under the remote KIRO_HOME) and normalizes the payloads on /hook/kiro. Pets, sidebar, notifications and mobile now see Kiro working and done. Kiro resume is gated on a new agent-session.kiro-resume.v1 capability. * feat(kiro): list Kiro sessions in Agent session history Reads $KIRO_HOME/sessions/cli/<id>.json metadata plus the streamed <id>.jsonl transcript locally, under WSL and over SSH, skips subagent child sessions, and resumes with kiro-cli chat --tui --resume-id <id>. * fix(kiro): report a remote agent deleted mid-install as unreadable * fix(kiro): resume history rows by the validated session file name * fix(kiro): preserve hook configs and restore sessions correctly * test(kiro): use typed filesystem callbacks * test(kiro): preserve older-peer search expectations --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
9021133929 |
Add the inert workspace layout model, Loader and Serializer (#26748)
* feat(layout): add the inert workspace layout model, Loader and Serializer One model of a workspace's tab layout (all tab kinds, groups, split trees, pane bindings, titles/colors/pin, sleeping records) in src/shared/workspace-layout, with a Loader from today's stored session partition and a Serializer back to it. The structural rules check and the pure pane/group tree helpers move into the same folder so the module can run them; their callers import the new paths. Nothing in the app calls the new module yet. * fix(layout): avoid toSorted in shared code and drop an unused helper The mobile bundle includes src/shared and Hermes lacks Array#toSorted. * fix(layout): one stored home per layout fact; Loader reports every rule it applies The row's terminal id, preview flag, worktree id and execution host are now derived on save instead of kept a second time. Groups never stay empty. The Loader reports row/tab-bar disagreements, re-minted ids, worktree id and host disagreements, keeps a row stored in two workspaces where the tab bar names it, and gives a pane id repeated in a second tab a new unbound pane. The rules check runs on what the Serializer writes. Fixtures become test fixtures, and a test lists every model field with the one fact it holds. * fix(layout): derive each tab's host from its owner and keep one preview fact per editor tab An editor tab names its file's owner (an SSH target or paired runtime), every other tab its partition. Where the stored tab and file preview flags disagree the tab loads permanent, so a draft can never be replaced. A row terminal that differs from the one derived from its focused pane is now reported. * fix(layout): one binding kind, one title, one host rule; records follow a reassigned pane A legacy row with no pane layout now loads as a one-pane tab bound to its terminal (reusing the leaf its records name), so the null-panes branches and legacyPtyId are gone. A pane given a new id because two tabs shared it takes its sleeping and incarnation records, scrollback and focus with it. A terminal's live title is one fact (the tab-bar label wins on load). Every tab-bar entry names its execution host by one rule. The fact-location test is now keyed by on-disk field and checked by mutating every source. * fix(layout): one change report by construction; records without copies The Loader now picks values by fixed precedence only and reports one field-by-field diff of the stored session against what the next save writes, over the disk-field tables (now in workspace-layout-disk-fields.ts, with session fields derived from SESSION_FIELD_OWNERS). Sleeping and closed-tab records live in their workspace without the pane key, tab and worktree id their key and workspace already give; the Serializer fills them. Tab-bar entries are written in the one tab order. * fix(layout): name the change report's key-list parameter's type * refactor(layout): key pane data by leaf, check the model itself, one legacy record - Each pane's data (terminal, incarnation, title, sleeping agent) is one leaf record keyed by leaf id, and scrollback beside it is keyed the same way. Pane keys are derived only when saving. The Loader gives a repeated leaf id a new id before attaching records, so rekeyPaneRecords is gone; records naming no pane are carried as stored. - The rules check runs on the model directly (ungrouped tab, empty group, group tree that disagrees with the groups, pane data without a pane); the disk-format rules stay separate. - The row-ownership marker and topology revision move into one legacy record outside WorkspaceLayout, updated by updateLegacyPersistence after each apply. - Fixtures include a terminal tab whose tab-bar id and terminal id differ. * fix(layout): join the group ids the tree-mismatch detail names * fix(layout): leave the stored revision map as is when no repo advances |
||
|
|
6013d093e2 |
fix(mobile): one host sidebar on iPad, with the page owning the host area (#26761)
* feat(mobile): let the page own the wide host area, one sidebar only
On wide layouts a detail route's page session ran app/h/_layout.tsx inside
the detail pane, judged itself wide, and drew a second host sidebar beside
the native one.
- The page draws the host sidebar only when init carries the new optional
`ownsHostArea` fact, never from its own width.
- The page manifest's `/h/[hostId]` route declares `canOwnHostArea`.
- On wide layouts the native host route mounts one host-area page session.
A declaring bundle serves it with every served route's grants, so hops
stay in the page, and the native layout drops its sidebar. Without the
declaration, or without a bundle, the session answers native-route and
every wide detail route stays native.
- The owner is recorded per host so the layout and switches agree and a
relaunch does not flash.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): decide the wide host area per session, without a stored owner
Round 1 kept a per-host owner record that the native layout trusted. When
the record said the page owned the area, a detail route opened natively
(deep link, notification, handoff, narrow-to-wide with a detail pushed)
showed no sidebar at all.
- Drop the record. A wide session decides from the manifest it already
read: it is served only when the host route declares canOwnHostArea,
and it rebuilds when the layout class changes.
- The native layout hides its sidebar only on the host route, while that
host's page session is serving, as the shell screen reports through a
layout context. Detail routes keep the native sidebar, and a page there
draws none, so there is exactly one.
- Phones read and render nothing new.
- The host-area page can read and write every workspace-scoped key of its
host (chat tabs, live-input handles), as a per-workspace session does.
Claude-Session: lane-ipad-page-owns-host
* test(mobile): type the layout-class doubles rather than assert them
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): keep detail pages through a layout flip, guard capped keys
- A wide/narrow flip rebuilds a session only on the host route or under a
bundle without canOwnHostArea, where the answer changes. A detail page
under a declaring bundle keeps its document, scroll and draft.
- In the host-area page, a workspace key the 32-entry init cap leaves out
is named in storageOversize, so the page cannot replace the device's
chat-tab list from a default. The init frame bounds that list to the
page's schema; the host still refuses every key in the full list.
- A leaving host-area session clears only its own report.
Claude-Session: lane-ipad-page-owns-host
* refactor(mobile): host-area ownership from the reducer, storage read on demand
- The serving report is a focus-scoped boolean, so a host pushed over
another no longer leaves the first one's area unclaimed after the pop.
- The reducer takes `layout-changed {wide}`: it keeps the session when the
route view is unchanged and restarts it otherwise; `routeViewOf` returns
`ownsHostArea`.
- One web override, `usePageOwnsHostArea`; the native rule is inline in the
host layout.
- A host-area page reads a host workspace key from the shell on demand
(`native.storage.read`, granted to that session only) instead of init
enumerating the store.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): host-area page reads back its own workspace-key writes
A page read right after a page write of the same key is answered from the
page's own value, not from a shell that may not have the notify yet. A
refused read crosses as an error, never as an unset key.
Claude-Session: lane-ipad-page-owns-host
* refactor(mobile): read-your-writes from the shell's mirror, final review fixes
- The shell answers a workspace-key read from the mirror, which every
shell-side write updates synchronously (removals held as known-absent),
and reads the store only on a miss. The page shim's written-keys set
goes.
- `onLayoutChanged` compares the route view field by field; the session
is created with its layout and the layout effect fires on `wide` alone.
- The device verb dispatcher's type excludes the storage read.
- The sidebar matrix mounts the real shell screen to decide the report.
Claude-Session: lane-ipad-page-owns-host
* feat(mobile): host-area page takes the whole screen from the download on
The native sidebar steps aside once the manifest says the page owns the
host area: fetching, activating, ready and the failure screens after them
are full screen, as on a phone. It stays while the host is being checked.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): host-area workspace-key read survives a write during it
On a mirror miss the read went through the mirror's hydrate, which drops
its answer when any mirrored write lands meanwhile, so a stored key read
as unset right after a relaunch. A miss now reads the store directly, and
a store failure rejects rather than reading as unset.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): a wide detail session hands the host route to the shell
A session opened on its own under a page that can own the host area
kept a hop to the host route in-page (a missing worktree's notice, or
leaving the session), drawing a second host list beside the native
sidebar. Its pairs now leave the host route out, so the hop goes to the
shell, which opens the area-owning session. Phones keep the hop in-page.
Claude-Session: lane-ipad-page-owns-host
* test(mobile): a phone mount's layout dispatch leaves the session alone
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): leaving a wide detail session returns to the host area
The leave-session chevron and the missing-worktree bounce hand the host
route to the shell on a wide layout, which pushed it: a second host-area
session over the session, with Back reopening the session. On a wide
layout the shell now dismisses to this host's route instead, taking the
href's params, or replaces the session when nothing is below it. Phones
still push.
Claude-Session: lane-ipad-page-owns-host
* fix(mobile): a narrow-wide flip restarts a detail session's page
The route view comparison now counts the paired routes, so a detail
session under a page that can own the host area restarts on a flip
instead of keeping the narrow pairs, whose host-route pair let the page
draw a second host list beside the native sidebar.
Claude-Session: lane-ipad-page-owns-host
|