mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
565e34081aeff7814bdbdcea336101a55d3815dd
2931
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0d1e367d05 |
fix(automations): format the jobs-file limit consistently (#28220)
fix(automations): format the jobs-file limit consistently Use English number formatting in the English jobs-file limit error so non-English host locales still report 10,000 jobs. Co-authored-by: Matheus Breguêz <matbrgz@gmail.com> |
||
|
|
4d65146f2d |
fix(native-chat): Stop is saved before it acts, and a late or repeated Stop does nothing (#27076)
* 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
* fix(native-chat): queued-message actions and prompt answers accept through their own receipt
Send Now, Delete, Resume and prompt answers move onto the command-receipt route
Send uses: the receipt is inserted with the command's own write. A no-op answers
only once its receipt commits (otherwise outcome unknown), and a prompt answer's
replay reads the exact revision it was accepted at.
* fix(native-chat): replay a queued action from what its receipt recorded
Send Now's acknowledgement points at the submission it handed off; a question
answer's receipt keeps the accepted resolution; a no-op that cannot save its
receipt answers outcome unknown and holds nothing in memory; classified refusals
pass through.
* docs(native-chat): receipt comments say what a replay answers from
* fix(native-chat): Stop saves first, captures its target, and holds queued messages
Stop accepts through its own receipt: one transaction saves the Stop record
(target turn, agent generation, cutoff), returns unsent messages to the queue as
paused cards, and writes the receipt; only then does it interrupt the captured
target. A failed save leaves the agent running and says so. A late Stop is a
recorded no-op, and a retry is answered from the receipt.
* fix(native-chat): Stop round 1: late Stop is a no-op, saved-then-failed refuses, hold only what the Stop did not stop
- A Stop naming a turn that already ended is an accepted no-op.
- A card's Cancel saves its receipt with the dismissal row.
- A Stop saved but not carried out is refused as stopFailed ("Couldn't stop <agent>. Try again.") and its receipt is rewritten as rejected.
- A Stop during a start is saved first, then aborts that start; the abort-before-admission path is removed.
- The message the Stop stops is withdrawn as before; messages behind it are held as paused cards. Non-text messages are still withdrawn (TEMPORARY).
- Cleanups: one receipt commit path, dead fields and helpers removed, the returned-to-queue text names the agent.
* fix(native-chat): Stop round 2: narrow to receipts and save-first; message fate as on main
- Stop's withdrawal of queued sends is main's own rows (rejected, cancelled), written in the same
transaction as the Stop event and its receipt; a failed save withdraws nothing.
- A Stop arriving during a start aborts that wait on arrival, as on main (admits-now guard), then
captures its target once in the session lane. The out-of-lane admission is removed.
- A committed receipt is never rewritten: a failure after the save answers from the receipt and
writes the existing 'Cancellation was not confirmed.' note. 'Couldn't stop <agent>' only when
the save fails; a card Cancel that can't be saved says the question or approval wasn't cancelled.
- A Stop naming a turn a rewind removed is late (an accepted no-op).
- Removes the held-card settlement and returnedToQueue: what happens to a stopped message stays as
on main.
* test(native-chat): name the Stop row comparison for what it drops
* fix(native-chat): Stop round 3: a committed Stop always acts; notes only for unconfirmed interrupts
- A Stop whose write throws after its receipt committed still interrupts (never 'Couldn't stop').
- An interrupt that worked answers stopped even when only its note fails to write.
- A strict card interrupt that throws after its receipt committed gets the 'Cancellation was not
confirmed.' note, through one shared helper with the turn path.
- A target-less Stop stands while the main agent works, so a turn that opened while its save waited
(a send made before the Stop) is interrupted, as on main.
- A card Cancel that can't be saved says 'Couldn't cancel this question or approval. Try again.'
* fix(native-chat): Stop round 4: name the turn the Stop actually stops
- The Stop event's turn is resolved when its write runs (the named turn, else the live turn), so a
turn that opened while the save waited is the one marked interrupted and shown stopping.
- The unconfirmed note is keyed by that turn; tests cover it and its own write failing.
- Whether a Stop withdrew anything comes from its capture, so a throw after the commit can't flip it.
- A Stop counts as saved anyway only when its own write committed the receipt.
* fix(native-chat): a new chat's first send gets the Stop-aware operation receipt
Main's create path (from #26332) hands performSend the founding receipt; the
Stop changes widened the turn context's receipt to track its commit. Wrap it
with the existing observedReceipt.
* fix(native-chat): a background task the agent won't stop still says not done
A background-task Stop now saves its receipt before it asks the agent. A
refusal after that save (task still running, or a timeout / lost connection)
was being replaced by the receipt's replay, so the person never heard the Stop
failed. That refusal is now the first answer, as on main; the receipt stays
saved and a retry of the same id is answered from it without asking again.
* test(acp): a Grok Stop naming an ended turn leaves the unechoed follow-up alone
The late-Stop rule makes a Stop naming a turn that has ended an accepted no-op
for every agent. The Claude case was rewritten with it; this is Grok's.
* test(native-chat): Stop tests exercise what they name after the late-Stop rule
- The "leaves the child alone" Stop now reaches the provider (handed over, a
turn with no row yet); the at-rest refusal case was a duplicate of the late
no-op test and is removed.
- The adapter comment and two test names say the gap rule covers a named turn
with no journal row yet.
- The Grok late-Stop test checks no status row and that the follow-up completes.
- Name the two refusals refusalAnswers passes through.
* fix(native-chat): a background-task Stop answers its acceptance and notes what happened
A background-task Stop is saved before it asks the agent. The answer is
that saved acceptance: a stopped task answers cancelled:true as before
(the phone keeps the row "stopping" on it), and anything else answers
cancelled:false. A retry answers from the receipt without asking again.
What the agent then says comes back as a note keyed by the Stop's own id,
written through the existing Stop-note path, and never as a refusal:
- still running: "The background task wasn't stopped." (or "The
background tasks weren't stopped." for Stop all), the new status-row
failure kind backgroundTaskNotStopped;
- timed out or lost the agent: "Cancellation was not confirmed."
The note is the thread's, since tasks outlive their turn. A note that
cannot be written is logged and changes no answer.
The background-task body moves out of performCancel into the targeted
cancel, and the refusalAnswers flag and its receipt are deleted.
* test(runtime): the Codex strip's Stop notes a command it could not stop
A background-task Stop now answers its saved acceptance and says what happened in a note: "The background task wasn't stopped." when the command still runs, "Cancellation was not confirmed." when Codex was lost.
* test(native-chat): keep the Stop tests that pin behaviour, cut the rest
Merges the receipt, save-first, failed-after-save and background-task Stop
tests into one flow file; drops unit and per-branch tests already covered
by a flow test.
|
||
|
|
b70f13ff89 |
fix(orchestration): stop retried failed worker starts from piling up live agents (#27795)
* fix(orchestration): stop retried failed worker starts from piling up live agents A failed worker-start keeps the terminal it created (a pane the user typed into is theirs), so a coordinator retrying a start that can never succeed left one more live agent behind on every retry, with nothing to stop it. - A --retry-of start now releases the failed attempt's still-owned terminal through the normal worker-release path (output archived, user takeover kept, recovery finishes it after a restart). - A failed start now counts toward the Task's existing three-failure circuit breaker, carried across retries; a retry of a circuit-broken Dispatch is refused with a plain error. Linear: STA-7113 * test(orchestration): type the failed-start retry fixtures without casts * fix(orchestration): carry failed-start count only along --retry-of; plain breaker refusal - Only a --retry-of start inherits its predecessor's failure count, so worker exits across separate starts keep the old behaviour. - The circuit-broken refusal carries failureCount and next steps that say to stop retrying, read the last attempt, and that its pane stays open. - A repeated failed report on a circuit-broken Dispatch is a duplicate. Linear: STA-7113 * fix(orchestration): breaker refusal only points at a terminal that is still open Linear: STA-7113 * fix(orchestration): log a retry's release that could not prove the old terminal closed Linear: STA-7113 * Preserve worker diagnostics before terminal release and mask wrapped tokens |
||
|
|
26a265d704 |
Host plans agent launches with the window's runtime rule and one runtime fact (C1) (#27692)
* feat(agent-launch): host plans agent launches with the window's runtime rule and one runtime fact
Host-started agent launches now pick their platform with the same rule the
window uses (project runtime, else a \\wsl$ path, else the host), and resolve
one runtime fact that supplies both the command quoting and the PTY shell.
Adds the agent.launch.terminal-inputs.v1 capability and its optional
title/extraAgentArgs/requestKind/terminalOnly fields; no sender yet.
OpenCode's createAgentSession stays on today's rules until C2 (temporary).
* fix(agent-launch): one shared runtime rule, a derived fact, and only quoted commands carry it
Round-1 review fixes for C1:
- Window and host read a workspace's runtime through one shared resolver
(local-workspace-execution-runtime.ts); a repo-less \\wsl$ folder gets no
runtime, as in the window.
- The fact is { platform, remote, pty } with the spawn's own PTY type; the
quoting shell is derived from it, never stored.
- createAgentSession uses the same fact (its pre-C1 exception is deleted).
- Worktree startups quote from a fact for the repo and pass it on; a runtime
that needs repair fails the startup, not the create. `shell` is required
when building a launch command.
- The host planner stamps a fact only on commands quoted with it; a
caller-quoted command keeps the spawn's own rule.
- Qoder probes where the process runs (\\wsl$ cwd, then a WSL shell).
- wslShellRule 'host-legacy' keeps today's WSL-shell outcome where no
project chose a runtime (temporary, removed by C8).
- terminalOnly reaches the early-tab decision with an accurate receipt
sentence; the unused client gate is removed.
- Adds one host C0 row for a \\wsl$ repo with no saved runtime.
* fix(agent-launch): round 2: keep request fields for C2, extend main's resolver, one answer per workspace
Problem: the first round still carried request fields and window-parity
switches that only the next step sends, added a new shared resolver that
also changed window code, and left some background launches using a
different runtime answer than the one they spawn with.
- Move the agent.launch request fields, their capability and fingerprint
handling, and the prompt/Codex parity switches out to the next step;
today's host behaviour is kept.
- Drop the shared resolver: main's resolveLocalWorkspaceRuntime now finds
the project by repo id or source repos, folders borrow their single
local repo, and a \\wsl$ path is not refused on a stale distro list.
Window code is back to main.
- Agents started in an existing worktree use that worktree's answer; the
phone's fallback paths spawn with the answer they quoted for, and a
runtime that needs repair shows its message instead of a plain shell.
- The OpenCode model check looks where the agent will run.
- A Windows --shell in a \\wsl$ workspace names the path, not a runtime
setting that cannot change it.
- The temporary folder rule names its follow-up, STA-10231.
* fix(agent-launch): OpenCode model picks in WSL folders keep today's Windows runtime
Problem: once a folder follows its repo's WSL runtime, an OpenCode launch
that picks a model would be checked and started inside WSL, where Orca
cannot yet verify OpenCode models, so picks that work today could be
refused.
Where main had no runtime for the scope (only folders), a model-picked
OpenCode launch keeps main's answer. Start, resume and the worker's model
check read the same answer. Temporary until OpenCode models are verified
inside WSL.
* fix(agent-launch): round 3: OpenCode keyed on the agent, new worktrees quoted for where they land
Problem: round 2's OpenCode exception keyed on a model pick, so a resumed
chat could run somewhere other than where it started, and the worker's
model check read WSL from the Terminal shell and refused folders that work
today. A new worktree's startup used the repo's answer even when the
worktree lands in a WSL share.
- In a folder whose repo runs in WSL, every OpenCode launch keeps main's
Windows answer: create, resume and the worker's model check agree. The
model check reads WSL from the path alone, as main did. STA-10251.
- A new worktree's startup is planned for its WSL-share base folder when
one is set (newWorktreeLaunchScope).
- A \\wsl$ working directory runs in WSL whatever the project says, so
non-OpenCode agents there are written for Linux.
- The planner test derives the answer instead of passing it in.
- Smaller shapes for the PTY settings type, the cache-less resolve,
executionRuntimePlatform, spawnShellInputs and two comments.
* fix(agent-launch): round 3c: one labelled rule for where the path decides; OpenCode follows the WSL-share fix
Problem: OpenCode in a repo on a WSL share whose project is saved as
Windows host still got PowerShell quoting typed into WSL, because the
WSL-share fix skipped OpenCode. The fix and the two temporary parity
rules were also split across two returns, so deleting the temporary
ones could take the permanent fix with them.
- One expression decides when the path places the agent, each term
labelled: WSL share + Windows host (permanent fix), folder + Windows
host (STA-10231), OpenCode in a folder (STA-10251).
- OpenCode now gets the WSL-share fix too; a model pick there is refused,
as main's model check already did.
- The OpenCode entry-point tests set a WSL Terminal shell, so the model
check test fails if it reads WSL from that shell again.
- Tests trimmed with no coverage loss; the spawn-precedence unit test is
dropped (the C0 WSL-share row covers the reachable case).
* fix(agent-launch): keep the window's WSL answer for panes the window spawns
Live Windows QA (Terminal shell wsl.exe, global default Windows host, repo
with no project setting) saw an agent command written with PowerShell
quoting typed into a WSL pane, so codex lost the apostrophes in its prompt.
The host wrote the command for its own answer (Windows, PowerShell), but on
routes where the window spawns the pane (the phone while a window is open,
a focused desktop chat create, `orca terminal create --command` in a repo)
the window spawned it by the Terminal shell, which is WSL. Quoting and the
pane came from two different answers.
Those routes ran in WSL with Linux-style quoting on main and worked, so the
temporary host-legacy term (STA-10231) now also covers them: when the window
spawns the pane, the path and Terminal shell decide, as on main. Background
routes keep the approved change (PowerShell instead of cmd.exe with
Linux-style quoting, which was broken on main). createTerminal and the
pre-launch planning share one `windowSpawnsTerminal` rule so they can't
disagree about who spawns the pane.
* Revert "fix(agent-launch): keep the window's WSL answer for panes the window spawns"
This reverts commit
|
||
|
|
8a0f1ee4b6 |
Resume in New Native Chat for Grok and OMP terminal conversations (#28049)
* feat(chat): Resume in New Native Chat for Grok and OMP terminal conversations A Grok or OMP conversation started in the terminal now offers "Resume in New Native Chat" in its Session History row menu and its tab's right-click menu, like Claude and Codex: a new chat of that agent takes the conversation over, showing its history and continuing it. The host finds the conversation through the agent's transcript import and fills the chat from it, the same adoption path Claude and Codex use; the adopted handle is now any agent's, not only theirs. Both agents keep conversations per folder, so the action shows only in the workspace the conversation was recorded in, as for Claude. * fix(chat): hide Grok 1.0's runtime context and rules in a chat that takes over its conversation Grok 1.0 appends the user's rules to its runtime-context row; the decoder only knew the older shape, so a Grok chat taking over a terminal conversation showed that row as the first message. * Fix OMP and Grok transcript lookup for chat adoption * Fix scoped Grok and OMP chat history lookup |
||
|
|
975c27b780 |
fix(usage): cap Cursor's Retry-After at 24h and honor its HTTP-date form (#28406)
Cursor parsed Retry-After with a bare Number(), so a huge header could block usage refreshes for years and an HTTP-date was ignored. Claude and Cursor now both call parseUsageRetryAfterMs, which applies the 24h usage cap to the shared Retry-After parser, replacing Claude's private copy of the same algorithm. The shared parser is renamed from relay-retry-after-header/parseRelayRetryAfterMs to retry-after-header/parseRetryAfterHeaderMs: relay, the updater, mobile and usage all use it. |
||
|
|
24bda717f4 |
fix(terminal): a slow window no longer makes orca terminal create fail and then duplicate (#28158)
* fix(terminal): start CLI-created workspace terminals in main so a slow window cannot duplicate them A focused or interactive-agent `orca terminal create` asked the window to make the terminal and reported failure after 10 s, while the window still made it later; a retry then made a second one. Main now starts every terminal that names a workspace and shows it in the window best-effort, as it already did for every other create. * fix(terminal): start a main-created terminal at its worktree's measured pane size An unfocused CLI agent opens as an inactive tab that is not resized until the user opens it, so starting it at 120x40 left its output wrapped to 120 columns. Start it at the size of the pane active in the worktree's active group, where the new tab opens; fall back to 120x40 only when that worktree has no measured pane. * fix(cli): keep sending rendererBacked for hosts before v1.4.211 Hosts without #22384 leave a background-started agent's startup queries unanswered, so a new CLI talking to an old, still-running host must keep routing interactive agents to the window. Newer hosts ignore the flag. * fix(terminal): start host-created terminals at the measured size of their tab group The window publishes, on its graph sync to main only, the grid an unsplit terminal would get in each shown tab group, computed by one pure function that mirrors FitAddon.proposeDimensions and is pinned against it; a ResizeObserver on each group body republishes when that grid changes. Main starts a new terminal at the size of the group it opens in (the named group, else the active one) and falls back to 120x40 only when no window measured it. This replaces the active-pane lookup, which mis-sized split groups and could read a phone's fitted size. * fix(terminal): measure group sizes from an untitled default-font pane and use the live active group A pane with a title or banner pushes its xterm host down, which skewed every group's row count; skip it like a zoomed pane. Size from the window's latest published active group, which is where its reveal places the tab, before the saved session's. * fix(terminal): size a CLI agent in a workspace the window has not shown Co-Authored-By: Claude <noreply@anthropic.com> * fix(terminal): learn the single-group inset only from bodies shown in the content area Co-Authored-By: Claude <noreply@anthropic.com> * chore: rerun PR checks against main with #28297 Co-Authored-By: Claude <noreply@anthropic.com> * chore: rerun PR checks against main with #28301 Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b8e49222a3 |
fix(remote-runtime): wait for the desktop's terminal list before opening a terminal (#28154)
* fix(remote-runtime): wait for the host's terminal list before opening a terminal A paired client opening a workspace whose terminals it had not heard about yet read "no answer from the host" as "zero terminals" and asked the host for one. When the host's list then arrived with terminals, the workspace gained an extra terminal; when it arrived empty, the first-frame bootstrap created a second one. The client now treats an unknown host count as unknown. Activation without explicit startup work waits, and the active subscription's first real answer seeds the workspace if it is empty. An "ask me later" frame from a host that has not published yet no longer records a count of zero, and the host's later answer (sent as an update) is accepted as the first answer for seeding. * fix(remote-runtime): count an unknown host as empty unless it answers later Hosts before #25736 answer an empty workspace with "ask me later" and never follow it with a real answer, so waiting for that answer left a blank workspace with no terminal against them. Wait only on hosts that advertise session-tabs.empty-worktree-answer.v1; older hosts keep main's behaviour. Workspaces this client just created are known empty and open their first terminal without waiting. Co-Authored-By: Claude <noreply@anthropic.com> * fix(remote-runtime): log a failed delayed terminal open; drop an inert flag Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3a011e48d1 |
fix(native-chat): load long chats on the phone by keeping every update within its limit (#28203)
* fix(native-chat): fit chat stream pages in one mobile bridge frame A long native chat failed to open on a phone with "the shell ended this stream (overflow)". The host sized each history page to ~1.94 MiB (half the relay cap), but the mobile app ends any stream whose event frame is over 640 KiB, so the opening snapshot of a long chat never arrived. Pages now fit that frame; older rows load through Load earlier as before. The page also words a stream the app ended in plain language instead of the protocol's reason. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): keep the outline on the relay page share; plainer end words Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): read the bridge frame cap from its shared definition Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(native-chat): bounded frames for the phone's chat stream A phone page now asks the host (when it advertises agent-session.bounded-frames.v1) to keep every agentSession.subscribe frame within the bridge's 640 KiB frame: - each row is cut to what the phone draws (tool output to its detail length unless it draws as a diff, tool inputs to the diff view's length) and then to one row's share of a page, its long strings cut to a common length; the row is marked `truncated` with its full size - history frames carry rows and metadata held to a fixed ceiling; the draft queue and the background-task roster go in frames of their own; the command list is not sent, since the phone reads it from options - the queue's admission bound counts whole cards and is sized to one frame, so a phone always gets every card whole Other subscribers get the frames they always did. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bounded frames review round 1 - the phone waits for the host's capabilities before opening the stream, so its opening frame is never sent unbounded - rows are bounded once, by the reader that sizes the page; the item form is an options field and always a function - one cut rule (`cutLongStrings`) serves the display trims and the fit - a side list rides an empty history batch rather than a second frame; commands changes send a bounded subscriber nothing - queue admission measures the candidate list once; a legacy queue of more cards than fit lists the ones it sends first Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bounded frames review round 2 - an over-budget legacy queue cuts its text to one common length and marks the cards it cut, and no longer names a next card it left out - one predicate says whether a subscriber is owed the `/` list - a lighter invariant fixture Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(native-chat): type the shortening guards against named shapes Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bound every phone's chat stream at the host The host now bounds frames for every phone (clientKind 'mobile') at the RPC boundary, so the subscribe param, the host capability and the phone's wait for the capability probe are gone. Phones of every version already ignore the stream's slash commands, and a phone reached through the desktop relay signs in as a phone too. - A live gap of more than one page reaches a phone as the newest page, so no publish outruns the bridge's unacked window; older rows load on scroll. - Queue admission sizes each card as wide as a failed send can make it. - Rows are bounded only as a page takes them; the unread row marker is gone and a shortened queue card is marked `truncated: true`. - A bounded subscriber no longer reads or tracks the slash commands. - The stream-end words are back to main's: `overflow` also covers a page that stopped acking, which "too large" would misstate. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(native-chat): count every card in line and never shorten one Queue admission now counts every card the queue can publish again without passing admission: waiting and returned cards, and sent cards whose delivery is not yet accepted, which a refusal or a Stop puts back. The fixed per-card reserve for a failed send's words is gone. When a phone's list is over its frame room anyway (an older build's larger queue, or a card that grew after admission), the phone gets the cards it sends first, whole, and the rest once those leave. Cards are never shortened, so no app version can Edit a partial copy over the full draft, and the `truncated` card flag is gone. Bounded frames strip only the roster from an event; no event carries the other side lists. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bound only phone frames; keep main's queue and page sizes Queue admission is main's again. The phone's list is bounded where it is sent: the cards it sends first, whole, so every frame fits for any queue size and no card is shortened. History pages keep main's size for every reader but phone subscribe frames. A reader is now its item form plus its page budget, so desktop pages, the outline and phone history replies (chunked by the app's bridge) are as on main, and only bounded frames use the bridge frame's share. Drops the queue's `nextQueuedMessageId` rewrite, which no phone reads, and a test that duplicated the prefix check. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(native-chat): bound phone rows by size alone; final tidy Rows the journal already caps at a 16 KiB head gain little from a second, display-shaped cut, and the diff check behind it passed a character count as a line count. A row is now held to its frame share by shortening alone, and native-chat-diff is back to main's. Unexports file-local bounds, fixes a capability-era test comment, and adds tests for string shortening and a phone's stop-note refresh. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(native-chat): assert a phone's stop-note refresh pages Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(native-chat): run the bounded-frames suite in the Node runtime project It opens a real SQLite journal. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(native-chat): bound legacy dispatch reasons on read Rows written before the write-side bound carry a provider's full error text, which every page and phone frame attaches with its submission. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
8471b3a5c2 | fix(layout): restore the saved active group as a window does (#28174) | ||
|
|
962c338b67 |
feat(wsl): Windows path actions for WSL hosts through one owner-routed capability (WSL-via-orcad C8) (#27955)
* feat(wsl): open WSL host paths from Windows through one owner-routed capability (WSL-via-orcad C8) * test(wsl): typed shell handler lookup for the WSL owner tests * fix(wsl): read known WSL hosts through a registered accessor; e2e for the Windows folder picker * fix(wsl): open the host-path entry filled in after a Windows pick; e2e screenshots * test(wsl): read the e2e picker's default path through the main process env * refactor(wsl): read a WSL host's distro from its record instead of a new accessor * fix(wsl): skip the host browser only for a path Windows' picker or an Explorer drop filled in A path left filled from an earlier pick on an SSH or paired host opens the host browser again, as it did before. |
||
|
|
9f2abd5e70 |
feat(wsl): Add WSL distro host UI and status (WSL-via-orcad C7b, experimental) (#27934)
* feat(wsl): Add WSL distro host UI, host row status and fake-transport e2e (WSL-via-orcad C7b) Experimental toggle (Windows only), an Add WSL distro choice beside SSH host and remote server, the WSL host's own row in host pickers with its liveness state, a Connect action that starts a stopped distro only on request, a WSL hosts list in Settings, and the renderer report of which WSL hosts an open project or terminal needs. * feat(wsl): show WSL hosts as turned off while the experiment is off, and confirm their removal With Experimental → WSL distros as hosts off, a WSL host keeps its row so its projects never seem to vanish, says it is turned off, and offers Turn on (opening that setting) instead of Connect; Settings lists it the same way with Connect disabled. Removing a WSL host now asks first in the dialog SSH hosts use, with WSL copy. The e2e covers both. * fix(wsl): skip the WSL host status bridge when the preload lacks it * fix(wsl): show when this build cannot run WSL hosts instead of failing an add The UI reads main's template capability once. Without it, the add dialog says this build can't run WSL hosts and disables Add, existing hosts' rows and Settings say the same with Connect disabled, and the Experimental toggle notes it. The e2e covers a fake build without the files. * fix(wsl): re-read the WSL distro list when the add dialog's window returns, and on Refresh The add dialog read the registered distros once, so one imported while it was open never appeared. It now re-reads when the window regains focus or becomes visible, and from an explicit Refresh button, never on a timer. The selection is derived from the current list, so a refresh cannot leave a stale pick. The e2e imports a distro mid-dialog. * fix(wsl): list WSL hosts only as WSL hosts, not among the generic remote servers Settings → Remote Orca Servers, the server-update checks and the status bar's server list now leave out environments whose deployment record is a WSL host (by kind, never by name). The WSL hosts section reads them from the store itself. * fix(wsl): say a WSL host's removal ends its terminals, and report what it left running The confirmation now says removing ends the host's open terminals as well as Orca's server, and a removal main could not complete in the distro (a terminal or the daemon kept running) is reported instead of passing silently. * fix(wsl): ship the removal copy that says a WSL host's terminals end The build resolves copy from the English catalog, which still held the old text under the reused key, so the new inline default never reached the dialog. The copy gets its own key, the stale entry is dropped from both catalogs, and a test reads the shipped catalog. * fix(wsl): leave a removed host's workspace, keep its removal warning up, and prove removal in e2e After removal the active workspace is left when it lived on that host, so nothing keeps calling a host that no longer exists, and a warning about what still runs in the distro stays until dismissed. The e2e now checks that removal ends the host's terminals on the server standing in for orcad, shows no warning, leaves the workspace, and starts nothing for the host afterwards. * test(wsl): read the shipped removal copy and the fake's connect log without reflection * fix(wsl): say a WSL host is turned off in its terminal instead of 'Reconnecting' With the experiment off (or without server files) nothing retries, so a WSL host's terminal now says so, with Turn on, rather than claiming Orca is reconnecting. * test(wsl): reproduce project-removal survivors and orphan terminals in e2e The headless server standing in for orcad now carries a project whose terminal must end when the project is removed (and leave the host's own catalog), and an orphan terminal whose project the host already forgot, which removing the host must still end. Both checks fail without the fixes. * fix(wsl): drop the IPC wrapper from file-explorer and editor load errors; offer Turn on when WSL hosts are off The file explorer showed "Error invoking remote method 'runtimeEnvironments:call': Error: ..." for a WSL workspace whose host was turned off. Explorer read errors now go through the shared IPC-error unwrapping, and when the workspace's WSL host is turned off the empty pane shows the same turned-off copy and Turn on button as the terminal banner instead of a read error and a Retry that cannot work. The editor's file-load error view drops the same wrapper at render time (stored text is unchanged for retry classifiers). * test(wsl): open the internal gate for the WSL host e2e, and prove a closed gate shows nothing The fake-transport e2e launches its desktop with ORCA_INTERNAL_WSL_HOSTS=1. A new case leaves the gate closed with a WSL host already saved and the Experimental setting on: no WSL host API, list entry, host row, Add WSL distro action, Experimental toggle or Settings section appears, the fake never starts a distro, and the saved host stays on disk. * test(wsl): count only live shells when proving a removed project's terminals ended On Linux CI the hub keeps a pty-less tab record for the removed worktree after terminal.stop ended its shell, so counting every terminal.list row never reached zero. |
||
|
|
7ed23199a5 |
refactor(orcad-migration): migration source abstraction, capability-gated source.kind, path-rewrite stage (WSL-via-orcad D0) (#27790)
* test(orcad-migration): pin the SSH manifest digest before the source abstraction * feat(orcad-migration): capability-gated source.kind discriminator, SSH wire unchanged * refactor(orcad-migration): MigrationSource abstraction with SSH relay as the only source; table-driven path-rewrite stage (identity for SSH) * fix(orcad-migration): name the wire source type * chore(orcad-migration): exhaustive path-kind switch; no test assertion * test(ssh): run the relay migration source's SQLite test in the Node runtime project * perf(orcad-migration): rename record keys in linear time and skip the walk for the identity SSH exports run the path-rewrite stage on every stage and commit check; re-reading Object.keys per entry made a 10k-key record cost seconds on the main thread. |
||
|
|
a65fdc887f |
feat(wsl): WSL distro hosts, main-process wiring and persistence (WSL-via-orcad C7a, experimental) (#27933)
* feat(wsl): persist WSL host deployments in their own store and type orcad deployment links by kind * chore(wsl): drop an unused link type export * feat(wsl): add, persist and connect WSL distro hosts behind an experimental flag (WSL-via-orcad C7a) Main-process wiring for WSL hosts: list registered WSL 2 distros, add one (C4 deploy and launch through C6 liveness, then persist the paired environment and its C2 record), keep a relaunch's new device token, connect a host only for the user or an open project or terminal, adopt holders at startup, and remove a host (release its holder, drop both records). Refuses a distro whose orcad this desktop already reaches as another server (R25). * fix(wsl): read distro registrations through one reader that returns the canonical unbraced GUID C4's registry reader returned the braced GUID while the C2 record only accepts it unbraced, and C7 parsed the same output a second time. The one reader now yields lowercase unbraced ids and each registration's WSL version; the host distro list uses it. * fix(wsl): refuse to connect a WSL host while the experiment is off Adding, connecting and background connects all stop while Experimental → WSL distros as hosts is off; removing still works. The pre-deploy refusals move to their own module. * fix(wsl): stop a connected WSL host's server when the host is removed Remove ended the bridge and released the distro's holder but left orcad to exit with the distro. A connected server is now ended right away, only in a distro already running. * fix(wsl): import the orcad host errors from their new orcad/ home * fix(wsl): refuse WSL hosts in a build without the orcad template, and say so up front A build shipped without the orcad deployment template let the add dialog run and fail after seconds with a raw internal error. Main now checks the template as SSH hosts do, refuses add and connect (and skips lazy connects and holder adoption) before any deploy work, and exposes the capability so the UI can show it. * test(wsl): let the e2e WSL fake re-read its distro list, to register one mid-run * fix(wsl): end a removed host's terminals and daemon, and stop WSL hosts when the experiment is off Remove now ends the host's terminals through orcad's terminal RPC, waits for the terminal census to prove none remain, closes the bridge, and runs the managed decommission SSH hosts use, which stops orcad and retires its idle terminal daemon. What it could not end is reported back. Turning Experimental → WSL distros as hosts off closes every connection and liveness watch, releases the holders so distros can idle-stop, and keeps liveness from relaunching for visible work; turning it on resumes only the hosts open work needs. * fix(wsl): keep WSL hosts out of the servers a phone reaches through this desktop They are this desktop's own distros, connected only for work open here; a phone listing or routing to one would wake it behind the user's back. * fix(wsl): end a removed host's terminals through its own route, and never respawn it Removal reached orcad with the bridge's raw pairing offer rather than the routed environment and saved pairing the app already uses for the host; on a real orcad ending terminals failed fast and left them, the daemon and orcad running. It now calls by environment id through the same transport as every other call to the host, and the e2e fake runs these real steps against a headless server. Removal also stops tracking the host first (open work, connects, runtime calls), so nothing reconnects it while it stops, and a spawn guard in the connector refuses any holder, deploy or bridge for a distro whose host is gone (the respawn loop after removal). A WSL host that isn't connected answers runtime calls with why, instead of failing as an unreachable network server. * fix(wsl): suggest reconnecting a WSL host, not Tailscale, when it can't be reached Unreachable-server hints are chosen by the owner host's kind: a WSL host is a distro on this computer, so it points at its reconnect instead of the Tailscale advice for network servers. * test(wsl): parse the terminal list removal reads, and log the e2e fake's connects to a file * fix(wsl): close a removed host's terminals by handle and report stopped only on proof Removal stopped terminals per workspace, so one orphan whose project was gone threw selector_not_found and aborted before orcad was stopped. It now closes each terminal by handle, keeps going past failures and names them, and still asks orcad to stop. It reports stopped only when the census shows no session left, the daemon retired, and the orcad the distro records as active is the one this computer was connected to; a re-add that adopted an orcad the record does not name is reported instead of claimed stopped. * feat(wsl): close WSL hosts behind an internal-only gate in every build WSL distros as hosts must not be reachable by users until moving a project between hosts is lossless and the feature matches today's WSL support. ORCA_INTERNAL_WSL_HOSTS=1 opens the gate; otherwise main creates no WSL host service (no holder adoption, wsl.exe spawn or reconnect), refuses add, connect, remove and every runtime call to a WSL host, keeps persisted WSL hosts out of the server list, startup status probes and AI vault scans while leaving them on disk, and the preload exposes no WSL host API. * fix(wsl): keep SSH and paired servers listed whatever the WSL deployment file holds An unreadable WSL record no longer hides an SSH-linked server, and with the internal gate closed a corrupt WSL file is warned about instead of failing the whole registry. * fix(wsl): let an unreadable WSL record hide only a loopback pairing A WSL host always pairs over a loopback endpoint, so a stale record can no longer remove a remote paired server from the list. * fix(wsl): ignore unreadable WSL deployment state while the internal gate is closed A newer or stale record could otherwise hide a re-paired loopback server for users who never open the gate. |
||
|
|
5e9489c0c0 |
feat(wsl): account env for agent tabs on a WSL orcad host (WSL-via-orcad C9b) (#27940)
* feat(wsl): account env for agent tabs on a WSL orcad host (WSL-via-orcad C9b) terminal.createAgentSession and an explicit terminal.ensureAgentSession (new agent tabs, restored and sleeping agents) accept an optional accountEnv, gated by the new agent-session.account-env.v1 capability because both schemas are strict. The desktop shim attaches it only for an advertising WSL orcad, reading its capabilities from status.get; an older host keeps its default account and the desktop shows a one-time notice per distro. accountEnv stays outside the create replay fingerprint. * test(wsl): name the C9b runtime store stub's cast * test(wsl): expect the WSL account notice listener in the app-lifetime IPC lifecycle |
||
|
|
41e6ea530a |
feat(wsl): accounts shim for agents on a WSL orcad host (WSL-via-orcad C9) (#27874)
* feat(wsl): accounts shim for agents on a WSL orcad host (WSL-via-orcad C9) The desktop prepares the selected Claude/Codex account's in-distro home with the existing WSL account code and sends it as env on terminal.create / session.tabs.createTerminal, or as agent.launch's new optional accountEnv. Structured sessions keep their host-derived home and warn. Launch-time CLI resolution inside a WSL guest skips /mnt/<drive> shims. * refactor(wsl): track WSL host environments beside their registry |
||
|
|
2bb02f062d |
[CI] Reuse Windows resolver imports and retire unused helper tests (#28296)
* [CI] Retire the weak local title-corpus comparison * [CI] Retire tests for seven unconsumed helper modules * [CI] Reset the Windows launch resolver graph once per group |
||
|
|
5e09488911 |
fix(terminal-daemon): status says live, exited or unverifiable, and shows the process ceiling (#28239)
* fix(terminal-daemon): status reports live, exited or unverifiable, and the process ceiling `orca terminal-daemon status` printed 'Terminal daemon: OK.' whenever no severed daemon was recorded, including the ~60 s after a kill -9 before the next terminal respawned it. The host now probes the daemon (health check, then its pid record with Linux start-time identity) and returns an additive `daemon` report; the CLI prints the verdict and, for a live daemon, the terminal process ceiling, which no CLI command showed under Electron serve. Fixes BUG-5 and BUG-7 from the real-host Linux test run. * fix(terminal-daemon): load the ceiling description from shared so the packaged CLI can resolve it The packaged CLI cannot load out/main/daemon modules: electron-vite replaces out/main after the CLI build. The ceiling state and its description move to src/shared, which the CLI already ships. * fix(terminal-daemon): a pid the signal proved live is never exited because procfs hides it * fix(terminal-daemon): load the liveness probe lazily so the RPC table stays light Importing the probe eagerly pulled the daemon provider graph into every module that loads the runtime RPC method table, which broke the cross-version terminal wire harness. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
b724e3a24d |
fix(linux): a supervised AppImage serve follows a replaced AppImage (#28244)
* fix(linux): a supervised AppImage serve restarts from a replaced AppImage Swapping the AppImage file never reached a supervised serve: it runs the copy it unpacked, and even a full service restart went back through the stable launcher to that same copy. The extraction cache now records which AppImage each namespace came from. Before each start the supervisor compares that file's identity with the build it runs; once a settled new file is there and runs on this host, it execs the new AppImage with its own arguments (same pid), whose serve unpacks the new build and re-points the stable launcher. Fixes BUG-4 from the real-host Linux test run. * fix(linux): try a replaced AppImage once, and let it past an old build's give-up - A give-up recorded against the old build no longer stops the supervisor before it checks for a replaced AppImage. - The supervisor records the AppImage identity it is about to exec in the profile. If that build dies before its own crash brake, later starts of the old build keep running it instead of exec'ing the same broken file until systemd's start limit disables the service. A supervisor running that build, a further replacement, or --force clears the record. * test(cli): compare the namespace listing order-independently --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e09d732d17 |
perf(wsl): pooled wsl.exe command loops and orcad compile cache on the WSL wake path (C4 follow-up) (#27923)
* perf(wsl): run stdin-free probes over pooled wsl.exe command loops; cache orcad's compiled bundle The WSL wake path made ~37 sequential wsl.exe starts at ~180 ms each. Stdin-free execCommand calls now go through a few long-lived wsl.exe sh loops (one command at a time per loop, own session, stdin at EOF, base64-framed output). SSH has no such method, so its commands and options are unchanged. orcad enables Node's compile cache inside a client-managed slot, and hands it to the profile preflight child, which compiles the same bundle. * fix(orcad): share the slot compile cache with the preflight only in a managed slot An unmanaged orcad started with its own NODE_COMPILE_CACHE kept that location for its preflight before; never redirect it into the install dir. |
||
|
|
9e52645e6c |
Show Codex model capacity failures as red agent status (#28231)
* Show Codex model capacity failures in agent status * Reduce Codex capacity detection work on terminal output * Preserve Codex capacity notices across partial terminal frames |
||
|
|
4559c250b3 |
fix(windows): restrict secure files in-process instead of spawning icacls (#28135)
* fix(windows): restrict secure files in-process instead of spawning icacls Every secure write on Windows spawned icacls.exe five times on the calling thread (the Electron main thread for most stores): verify, /reset, /inheritance:r /grant:r, verify, verify. Process creation under endpoint security costs ~0.6 s each, so each write froze the UI for seconds (#26027). The /reset + /inheritance:r pair is also what behavioural EDR scores as ransomware tradecraft (#25583). Add native/windows-security (@orca/windows-security): a small N-API addon over advapi32 that reads and writes DACLs in-process. It exposes the token user SID, a DACL read as SDDL, a DACL write (sync and on the thread pool), a create-with-DACL file write, and an additive grant. Policy and verification stay in TypeScript. - writeSecureFile creates each temp file already carrying the protected user/SYSTEM/Administrators DACL (CreateFileW + SECURITY_ATTRIBUTES), reads it back once, and renames it. No ACL is modified on the write path and no process starts. Before: 5-7 spawns, 94-138 ms per write. After: 0 spawns, ~0.5 ms. - A secure directory is verified once per process. It is rewritten only when verification fails, on the thread pool, because the write walks every descendant (~2 s on a 29k-entry userData). - The verifier follows OpenSSH's rule: only the user, SYSTEM and Administrators may be granted anything, and the path must be protected (or, for a file, inherit from a secure parent). DACLs written by the icacls-era hardening pass unchanged, so there is no migration. - If the addon cannot load, the write still happens and reports unrestricted, logged once. There is no icacls or PowerShell fallback. - grantDirAcl, the startup userData grant, and the install-dir probe and package repair use the same addon, so whoami.exe and icacls.exe are no longer spawned anywhere at runtime. - orcad's Windows slots ship the addon beside their bundles (built per arch, PE-machine checked). Its data-root privacy check fails closed, so without the addon a Windows orcad would not start. Fixes #26027 Fixes #25583 * refactor(secure-file): harden existing Windows files through the sync lane The read path called restrictWindowsPathSync directly and recorded success itself; the existing sync lane already does both. One call site fewer. * refactor(windows-security): one load, one rule, no fallthrough (review #28135) - The descriptor parser is anchored, so a conditional ACE (its body holds parentheses) or anything else it cannot read fails closed instead of verifying the ACEs before it. - findSecureDaclViolation is one flat rule: protected, allow rules only for the user, SYSTEM and Administrators (OpenSSH's ACE rule), and full control for the user. The inherits-from-a-secure-parent exception is gone: it accepted exactly what loosening the parent reaches. - The addon and the user SID load once, behind one wrapper that adds the extended-length prefix to every path and runs async DACL writes to one path in order, so a startup grant cannot land after a re-lock and restore the broader DACL. - With the addon loaded, a failed born-restricted create throws with the matching Node error code instead of falling back to a plain write. - A grant leaves a NULL DACL untouched instead of turning 'everyone' into 'only this SID'. WriteFile runs once and fails on a short write. - The artifact create-intent store writes through writeSecureFile and drops its own temp writer, directory LRU and synchronous directory re-lock. - Test seams the global fake makes redundant are gone. * fix(packaging): fail a Windows build whose workspace addon binary is missing or foreign-arch The @orca/windows-* workspace links exist on every host, so their presence proved nothing: a missing or wrong-architecture .node only surfaced at runtime, as unrestricted secure files or an orcad that refuses to start. beforePack now reads each addon's PE machine against the target arch. * refactor(windows-security): prune what the native ACL left dead (review #28135) - Sync-spawn ratchet: hardenSecurePath and the sync-flag exemption it alone used are gone, and the advice and baseline header no longer say secure writers run icacls. - The install-dir probe reads ACEs through parseSddlDacl, so an ACE it cannot parse fails the whole reading closed instead of being skipped; AC is a resolved alias there now. - The packaging check reads the addon binary through node_modules, the copy that ships. - No winCode-to-errno table: nothing reads .code off a failed secure write. - The load memo is not mutated; secure-path keeps its own log-once flag. - grantInstallDirPackageAcl is a plain function, and secure-file restricts a Windows directory and a POSIX path directly instead of through one two-platform helper. * fix(windows-security): resolve LA in real-ACL tests; tear a file whose close fails - The GitHub Windows runner is the built-in Administrator (RID 500), whose SID reads back as LA. Production already resolves it through localDomainSidOf; the win32 test helpers parsed without it. One shared readDaclOf resolves aliases for every real-ACL suite, and the verifier's unit test covers an LA-aliased directory and LA as a stranger. - createFileWithDacl treats a failed CloseHandle like a failed write (lost data on a network volume) and deletes the torn file. - Drop a duplicate test reset and a typo. * fix(windows-security): load only from inside the app; read DACLs as structure (review #28135) - Loader: orcad's staged binary first, then the workspace package only if its real path is inside the app (process.resourcesPath, or this checkout). A bare specifier otherwise walks every parent up to C:\node_modules, which any local user may create, and orcad holds credentials. - The addon reads a DACL as { protected, aces: [{ type, flags, mask, sid }] } via GetAce, with a SID only for plain allow/deny ACEs and null for a NULL DACL. findSecureDaclViolation and the install-dir probe check that structure, so conditional, callback and object ACEs fail closed. The SDDL parser, its alias table, LA/localDomainSid resolution and getDaclSddl are gone; SDDL is only the write format. - Install-dir tree pass: files flagless as before, but a directory only when its own DACL is protected (inheritably). Re-granting inheriting directories re-propagated each subtree (3.2 s for 1,093 directories). The root is no longer granted, or counted, twice. The two passes are plain functions behind a { root, tree } seam. - One namespacing (path.win32.toNamespacedPath) at the addon boundary, also the per-path ordering key; the comment states the real sync-grant invariant. - restrictWindowsDirectory(dir): Promise<boolean> replaces the callback lane; the reporter is guarded once, so the 'settle' stage is gone. - Sync-spawn ratchet: the secure writers no longer spawn or block, so they leave the gate (109 -> 61 uses). - Errors name the Win32 call and code only; a failed GetSecurityDescriptorControl fails the write instead of defaulting to unprotected. * test(secure-file): assert the durable POSIX write's outcome, not the old chmod order writeSecureFile now chmods the temp file to 0o600 before fsyncing it, so the fsync also covers the mode change and the file is owner-writable when opened. Assert what matters under umask 0o200: the write succeeds, the temp file is fsynced read-only, and the published file is exactly 0o600. * fix(windows-security): namespace paths with the host's toNamespacedPath path.win32.toNamespacedPath rewrote POSIX temp paths (/tmp/x -> \tmp\x) in the Linux runs of the Windows-logic unit tests, so the fake addon wrote to the wrong place. The addon only runs on Windows, where the host function is the win32 one; elsewhere it is the identity. The long-path assertion runs on Windows only. * test(windows-security): assert the install-dir long-path prefix on Windows only The prefix comes from the host's toNamespacedPath, which is the identity off Windows; the Linux run of this suite expected the win32 escape. * fix(windows-user-data-acl): grant the root even when its children cannot be listed (review #28135) A readdir failure on userData aborted the whole grant, so the root grant that could repair a DACL refusing the listing was never attempted. The listing failure is now recorded, the root is still granted, and the marker stays withheld so the next launch retries. * test(windows-security): wait for relock propagation, accept Entra ID SIDs (review #28135) - The large-relock test waited only for the parent to turn protected, which happens before propagation reaches the children, then read a child. It now waits on the parent and both ends of the child range. - The token-user test rejected S-1-12-1 (Entra ID) accounts, failing the suite on Entra-joined dev machines. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
7246a0e269 |
Fix delayed agent launches such as sleep 30 && claude (#28205)
* fix(terminal): preserve preparation and discovery for delayed agent launches * refactor(agents): centralize launch intent recognition * refactor(agents): keep launch intent in one module apart from process recognition Move recognizeAgentLaunchCommand next to the sleep-prefix scanner so the strict process-table matcher is untouched, name the module away from the agent-launch funnel files, share one executable-name normalizer, and colocate the preparation tests with their modules. |
||
|
|
aceebea0a4 |
fix(terminal): name a new tab's first pane so its saved layout is never empty (#28168)
* fix(terminal): name a new tab's first pane so its saved layout is never empty createTab now always mints the first leaf id, so the window never saves a terminal tab with an empty layout root. With no empty roots coming from the window, main's save-time swap that kept its own root over an empty window root (from #1909) has nothing left to guard, and is deleted. * test(persistence): an empty-root layout from an old session still loads and gets its pane * fix(persistence): name an old session's empty layout root once at load A session saved before tabs named their first pane can hold an unmounted tab with an empty root. If main bound a terminal there, the window's next save of that tab dropped the binding once the save-time swap was gone. Name the pane at load instead (the same pane the window would mount), so no save carries an empty root. The rootless-pane resolver moves to shared so main and the window pick the same pane. * test(persistence): name the bind-then-stale-write order the load repair covers * fix(persistence): name an old empty tab's pane with the stable headless leaf id The load repair fell back to a random id, so an unsaved headless serve gave a phone a new tab::leaf id on every restart. Use deriveHeadlessLegacyTerminalLeafId, which phones already saw for these tabs. Also import the rootless resolver from shared directly and return the loaded state unchanged when nothing needs repair. |
||
|
|
5929350944 |
fix(linux): direct AppImage serve honours the chosen profile and --supervise (#28227)
* fix(linux): run a direct AppImage serve through the CLI so it keeps its profile and supervisor A direct `Orca.AppImage serve` stayed in the Electron process and skipped the CLI's serve front end, so it ignored ORCA_USER_DATA_PATH and silently dropped --supervise. It now execs into the bundled CLI (same pid, so service managers and signals still reach it), translating a leading --user-data-dir into ORCA_USER_DATA_PATH. Also lists terminal-daemon and reference as CLI commands so a direct binary launch of them reaches the CLI. Fixes BUG-1, BUG-3 and BUG-10 from the real-host Linux test run. * fix(linux): drop desktop switches anywhere in a direct serve launch * fix(linux): keep a direct serve's --no-sandbox for the Electron server the CLI starts --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
8cd5bb2a74 |
feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4) (#27819)
* feat(wsl): deploy and launch orcad inside a WSL distro (WSL-via-orcad C4) * fix(wsl): keep the install-dir fallback on the PATH detection searched, so a /mnt/c shim cannot re-enter * fix(wsl): call the install-dir resolver exactly as before when no PATH is given * fix(wsl): import orcad artifacts from their new orcad/ home * fix(wsl): adopt an orcad serving in the distro that the activation record does not name * fix(wsl): filter /mnt/<drive> PATH entries only in an orcad launched as a WSL host SSH-managed orcad inside a distro and Orca under WSLg kept detecting drive-mounted CLIs before this stack; the setsid launch now marks itself with ORCA_WSL_ORCAD_HOST=1 and only that process filters. * fix(wsl): keep the install-dir fallback on the process PATH outside a WSL host orcad On Windows the preflight env merges the persisted registry Path, which the fallback never searched before; only the marked WSL host runtime now passes a filtered PATH. |
||
|
|
49a1129aa6 |
build(linux): carry the pinned Node inside the AppImage so serve's slot works offline (#28204)
* build(linux): carry the pinned Node inside the AppImage so orca serve's slot works offline Linux packaging places the pinned glibc Node at resources/node-runtime/bin/node before the glibc-floor gate, proves it serves one PTY with the packaged daemon entry, and deb/rpm exclude it through fpm. orca serve's orcad slot copies that Node (hash-checked) instead of downloading. * test(serve): type the bundled Node fixture --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
e2751d4318 |
After a restart, an agent tab asks the terminal host what is running, never "Couldn't confirm the agent started" (#28043)
* fix(agent-launch): after a restart a launch pane is an ordinary pane, never "couldn't confirm" A launch pane's spawn read the launch record (and the tab's saved outcome) when no launch was running for it, so a row left pending by a quit showed "Couldn't confirm the agent started." The record never observes the process; the terminal host does. With no launch running in this process the pane now spawns exactly like any tab: a live agent reattaches, an exited or absent one gives a fresh shell, and a gone daemon shows the normal unavailable state. - the running launch tells its pane how it ended in memory (finish carries the verdict); only a definite failure says "couldn't start", an unknown outcome leaves an ordinary pane - deleted: agentLaunchPaneVerdictFromRecord, listAgentSessionOperationRowsOwningPane, the `unconfirmed` verdict and its notice string, the tab's persisted launch pane, ownedPane writes - rows written by an older build that carry ownedPane are ignored; they expire with the row * test(agent-launch): the restart pane test joins the attach suite it shares mocks with * fix(agent-launch): a launch pane whose agent couldn't start keeps its notice for the session and never spawns a shell under it; a reload saves only a pending launch * test(agent-launch): drop assertions on code that is gone, and the taking-back case the IPC suite already pins * fix(agent-launch): narrow the finished-pane check, and test the saved session's launch pane with a typed tab * fix(agent-launch): a launch whose outcome is unknown still says "Couldn't confirm the agent started" for the session, and attaches its agent if it turns up * fix(agent-launch): main keeps a failed or unknown launch's answer for its pane until it quits, so the pane never spawns a shell under its notice; the window's spawn gate goes * fix(agent-launch): a failure kept for a pane goes when the pane is closed during its launch, moved to another tab, or its workspace is removed; comments say what an unknown outcome and a reload now do |
||
|
|
8e4e7be869 |
feat(daemon): pace automatic terminal restores on the desktop too (#28202)
* feat(daemon): pace automatic terminal restores on the desktop too The desktop daemon now enforces the restore budget that orca serve and orcad already enforce: the visible pane first, at most 3 automatic restores at once (1/s), at most 8 live per machine, and the rest stay sleeping until opened. ORCA_TERMINAL_SPAWN_ADMISSION=observe rolls newly launched daemons back to count-only. * fix(daemon): never put a user's own resume back to sleep; keep every refused old-client restore A user-resume past the per-client allowance is still paced as automatic work but is no longer deferred for a pause, the live cap or its deadline, so a visible pane is never put back to sleep in front of the user. The legacy restore record holds as many refusals as the host could run. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
687190e9f1 |
fix(ssh): let existing profiles migrate without losing client history (#28141)
* fix(ssh): migrate released profiles with retained client state * fix(ssh): reuse the shared adoption rule when the local and host copies overlap The host copy replaces the older local one only where it holds tabs, the catalog places the workspace on that host, no other host claims it, no worker fence or close record disagrees, and local drafts are kept. Elsewhere both copies export and must agree, as on main. --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
f0ccfcc0d3 |
fix(native-chat): seed the chat permission default from Claude's and Codex's own arguments (#28100)
* fix(native-chat): seed the chat permission default from the Yolo/Manual toggle as it shows The one-time copy into Settings -> Chat -> Permissions gave Full access only when every agent's arguments were exactly the bypass flag, so a profile the toggle shows as Yolo (agents set differently, or an extra flag) got Ask. Follow the toggle: Manual seeds Ask, anything it shows as Yolo seeds Full access. * fix(native-chat): seed the chat permission default from Claude's and Codex's own arguments The Yolo/Manual toggle has no saved value: it summarises every agent's arguments and shows Yolo for any mix, including after Manual when another agent keeps custom arguments. Seeding from it would grant Full access to people who chose Manual. Read what Claude and Codex chats checked before they had modes instead: Full access when both launch with their bypass flag, otherwise Ask. |
||
|
|
8be17ddf3a |
feat(chat): host writes one saved row per subagent (#27947)
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * feat(chat): host writes one saved row per subagent Claude, Codex and Grok now only report child-work evidence. The host writes each subagent's row from its child record, so the chat row, strip and sidebar read one lifecycle. Deletes the shared subagent tracker, the per-agent rosters and their caps. An accepted Stop settles only the run it was sent to, provisionally, until that run reports its own ending. Work the host store has no room to record keeps the running-tasks strip and its Stop instead of reading as idle. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * fix(chat): subagent row writer reads the live turn once, stop acks survive a failed batch - the row writer reads the journal's live turn at most once per change and only for a subagent with no saved, queued or in-flight row; a revision keeps its first scope - a background stop names each accepted task as its answer lands, so a stop-all batch that times out or fails part way still ends the runs it was answered for (Claude and Grok) - the strip's untargeted Stop rule lives in the shared view (offersStopAll); both strips inline it again; incomplete tracking no longer holds commands or rewind - clients admit a frame whose only change is incomplete tracking - drop the Claude stop's dead publish; the real-CLI stop test asserts the run's own ending - list the writer test with the real SQLite tests * fix(chat): a late Grok report never revives an ended helper; Codex keeps announced helpers - Grok: once a helper's run ended, a later progress or repeated spawn sends no live evidence, so its record and saved row stay ended after the next send retires the record; the test rig now retires finished records on each turn open, as the host does - Codex: past the bound, only a child that ran and ended is evicted, so an announced helper that has not started keeps its record, row, Stop and parent - cut: Codex parentTurnId (written, never read), the Claude identities' turn-end forwarding (call the corrections directly), the duplicate Grok outcome map - update stale comments that described the deleted producers * test(chat): Claude frames to saved row, kept Codex path and usage rules, agent ingestion exemption - a captured background Claude helper keeps its saved row working past its spawn result and the parent turn's end, through the real adapter, host and child records, until its own end - restore focused tests for the Codex root path, label trim and placeholder, usage before announcement and parent usage - the per-session live limit test uses tasks: subagents are exempt from it * fix(chat): incomplete tracking follows the refused children, and the strip's Stop reaches them - the host remembers which children the store refused for room, by handle; each leaves when a later report of it is admitted or it reports its own end, so incomplete clears on a truthful signal; the provider session's end still clears all, and past 512 a flag holds until then - a parent row the store refused counts every live child it reports as refused - with work no record lists, an untargeted strip Stop while no turn runs ends the agent through the idle sweep's stop; under a running turn it leaves the agent alone - the background Stop moves into its own module * fix(chat): the incomplete Stop is offered for every agent and decides inside its step - the strip offers the untargeted Stop for work no row lists whenever no turn runs, whatever untargeted stop the provider has (ACP agents, Codex before it proves terminal stops), since the host reaches it by ending the agent; never under a running turn - the Stop reads whether work is unlisted and whether a turn runs inside its own step, after the listed stops land, so a wake-up turn opened meanwhile is never ended - Codex executions past the bound evict the oldest ended child, else the oldest never started - test the writer's unnamed-subagent label; qualify the writer's retry comment * refactor(chat): the subagent row writer joins the views with their records Drops the shared host-only row projection and its hook-server read: the writer's wiring reads the views every surface reads and the records they come from, and takes residency and the ending's basis from the record. * refactor(chat): Grok subagent evidence reads its ending through the one outcome map * refactor(chat): a subagent's group entry as one object * fix(chat): a background Stop with nothing listed asks the agent for no task stops So an agent with no stop route (an ACP agent the probe found none for) never refuses before the Stop ends the work no row lists. Tests: the unlisted case is untargeted and reads unlisted work inside the step; the repeat-press rig lists the task its Stops reach; reflow the writer header. * test(chat): a nested helper's saved parent; describe how incomplete tracking clears * fix(chat): a closed session claims no Stop; a helper's row follows its own running work - a session close Orca proves is loss of contact, not a Stop the CLI accepted: Claude no longer ends what still ran as stopped, so the host settles it unknown and its row reads unverifiable, as before this stack; stop-acknowledged comes only from a Stop the CLI accepted - a helper that finished while work it started still runs is written working, by the strip's own rule; once that work ends, or the session ends, it reads its own ending again - the CLI starting a completed helper again under the same spawn call is the helper waking when its work ends: the decoder reports it as a restart, so its one row revises in place and ends with its real duration; after any other ending such a start is still read as late - tests from a captured 2.1.280 frame order; item 4 Stops pin their stop-acknowledged basis * refactor(chat): drop the unused Stop basis on a child's own ending Only the acknowledged-Stop edge writes that basis now; reflow the decoder header. * fix(chat): an older history page keeps a turn's subagent ordinals and row A page now reaches back to the first sibling of each subagent group it holds part of, at most one more page of conversation, so loading the older page no longer renumbers same-named helpers or moves (and closes) their folded row. |
||
|
|
bcad291f35 |
fix(orcad): identify instances by kernel start ticks, not drifting wall-clock start time (#28125)
* fix(orcad): identify instances by kernel start ticks, not drifting wall-clock start time On Linux the start time is derived from /proc/stat btime, which drifts (WSL2, NTP), so a live orcad was read as a reused PID and its managed stop reported exited without ever writing the request. The lock and stop request now carry optional linuxStartTicks + bootId; readers prefer them, and a wall-clock mismatch on Linux for records without them is unverifiable, never proof of exit. Co-Authored-By: Claude <noreply@anthropic.com> * fix(orcad): match stop receipts without the incarnation an older client drops A request recovered by an older client loses linuxStartTicks/bootId, which made the newer host reject its own receipts and decision records as mismatched. Also exercise the incarnation reader against a real Linux /proc. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1104402f5e |
fix(browser): annotation card and outline follow the picked element (#27875)
* fix(browser): annotation card and outline follow the selected element The grab overlay keeps re-measuring the picked element after the click (scroll in any box, resize, element size changes), and the isolated viewport bridge reports that live box so the pending card follows it, including page zoom. The card's side pick uses its measured height. * fix(browser): bind annotation card to selected element * fix(browser): keep document annotation card on selection * fix(ci): point the main sync-spawn ratchet at the shared ratchet library (#28054) #27756's script imported baseline helpers from check-focus-read-ratchet.mjs after #27788 had moved them into ratchet-baseline.mjs, so it crashed on start and failed every PR that merges main. Use the library's helpers, and record the two daemon-process launches from #27769 that the broken check never saw. Co-authored-by: Orca Worker <orca-worker@localhost> * fix(browser): follow selected element outer-box size * refactor(chat): give the start-refusal resumable table its own module (#28085) #27946 and #26684 together put agent-session-failure-words.ts at 304 lines, over max-lines, which fails lint and the pre-commit hook on every merge of main. The refusal-code table is standalone with one reader, so it moves out. Co-authored-by: Orca Worker <orca-worker@localhost> --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai> Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
a1f8e6482b |
fix(layout): save terminal rows in the tab groups' order (#28066)
* fix(layout): save terminal rows in the tab groups' order Co-Authored-By: Claude <noreply@anthropic.com> * test(e2e): read the paired retention heap after two collections Co-Authored-By: Claude <noreply@anthropic.com> * test(e2e): fail the paired retention check when the heap cannot be read Co-Authored-By: Claude <noreply@anthropic.com> * fix(layout): name the headless flow behind the row-id group fallback Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2233fd3669 |
feat(native-chat): each Claude/Codex chat gets its own permission mode (#26116)
Each native Claude and Codex chat now has its own permission mode (Ask for approval, Accept edits for Claude, Approve for me, Full access), shown and changed from a button right after "+" in the composer. A change takes effect before the next message: the agent restarts quietly and resumes the same conversation, never interrupting a reply or background work. New chats start from Settings → Chat → Permissions, which is seeded once from Agent Permissions. The mode is stored as an ordinary chat option, and the restart reuses the existing "replace the agent before the next message" step. |
||
|
|
6440d3c520 |
fix(mobile): keep a not-sent message in place on the phone, using the desktop's logic (#27936)
* fix(native-chat): list not-sent rows at their journal places The shared projection appended rows the host recorded but never delivered after every other row, leaving the reader to sort them. A reader that draws the list as given showed an old not-sent message under newer turns. Merge them in at their journal places, after the stop rows are built, so the list is already in order. The desktop sorts by the same order, so what it draws is unchanged. * refactor(native-chat): move the recorded-rejection line into shared words The desktop's words for a message the host recorded and then rejected now live beside the other rejection words, returning notice parts each client renders its own way, so the phone can say the same thing. The desktop calls it with the same inputs; its words are unchanged. * refactor(native-chat): one projection behaviour for rejected rows Every reader now draws a message the host recorded and then rejected where it was sent, marked not sent, so the rejectedInPlace option goes. The phone was its last off caller apart from the host's conversation outline, which now leaves not-sent rows out itself; a test proves the outline is the same as if the rejected message had never been sent. * fix(mobile): show a not-sent message in place with the reason under it The phone now draws a message the host recorded and then rejected where it was sent, as the desktop does, with one small line under the bubble in the desktop's words. The line is built from the journal the view already receives; when a start-failure or sign-in row already gives the reason, it says only that the message was not sent. The load-earlier header moves to its own file to keep the view under its line limit. * fix(mobile): a send the host recorded is never handed back When the send's own reply said the host recorded the message and then rejected it, the phone put the text back in the composer with a banner, though the host and the desktop keep it in the chat. A recorded send now settles like any other: its row draws it in place as not sent (or its kept card holds it), and the composer stays empty. The banner's failure-fact plumbing, fed only by that branch, goes with it; the transcript states the sign-in guidance once. * fix(mobile): keep each structured send on the shared optimistic list Once a not-sent row stays in the chat, matching a pending bubble by its text breaks: resending the same words after a not-sent message never reached the count the bubble waited for, so a stray copy stayed, and an acknowledgement lost on that resend showed a false "Delivery unconfirmed". A structured send is now one record, kept by the structured session under the id it is sent with. Three parts match the desktop's sender: the id, the drawing (the shared projection draws it at the tail until the host's row under that id lands, in the same pass) and the settle rule (the host's own record of it, a submission, a card, or a card's hand-off, drops it by the one rule the desktop now shares). The rest stays the phone's own: the bubble shows once the host accepts the send, not from the tap; a send whose answer was lost is held undrawn and says "Delivery unconfirmed" only if its deadline passes first, instead of going back to the composer; records are kept whether or not a chat view is open; and one send in flight is the composer's rule, not the sender's. Its photo is kept under the row id it shares with the host's row, so it shows on the bubble, the landed row and a not-sent row. The drafts' text matching, with its older queued-card matching, now serves only terminal-backed chats, whose transcripts carry no ids. * fix(native-chat): merge not-sent rows by the transcript order alone The merge kept a rule for rows with no journal place that no caller reaches and that the desktop's sort does not have; drop it, and say what merging after the stop rows actually keeps. A test pins that the list the phone draws as given is already in the desktop's sorted order, with a moved stop row, a retry run, a cut-turn notice, a held card and two not-sent rows. * fix(mobile): draw the not-sent line as the desktop does Red and capped to the bubble's width, like the desktop's line and the phone's own returned-card caption, and only when there is a notice: the row had a second, unreachable source of words. A send a Stop took back is no failure, so it no longer rebuilds the notices on every streaming frame. |
||
|
|
e83e40e258 |
Session History shows OpenCode, Grok, OMP and Pi chats as chats and refuses a second terminal writer (#27183)
* refactor(chat): answer transcript adoption and Session History from the agent registration Claude and Codex's transcript importers and Session History ownership (row id, resume-command grammar) move onto their runtime registrations as required-nullable fields; every other agent writes null. agentSession.agents publishes the derived transcriptAdoption/sessionHistory flags, and a client reads a host that omits them (or lists nothing) as the old build's answer. Strict parity: only Claude and Codex answer yes anywhere. * refactor(chat): one Session History ownership index, binaries from row agents, fork offer from fork argv Review round 1: the duplicate-adoption check and Session History ownership read one index keyed by the registration's row id (an importer now requires sessionHistory); resume-command binaries derive from rowAgents through the TUI agent config; the Resume in New CLI offer asks getAgentForkArgv instead of naming Claude and Codex. Strict parity: only Claude and Codex answer yes. * feat(chat): Session History owns OpenCode, Grok, OMP and Pi native chats An OpenCode, Grok, OMP or Pi native chat's conversation listed in Session History as a plain terminal row, and resuming it ran the agent's CLI as a second writer to the conversation the chat was still using. Each agent's registration now fills `sessionHistory`, so those rows show as the chat (its name, opening the chat) and Orca refuses a terminal resume of an owned conversation, as it already did for Claude and Codex. - OpenCode lists under `opencode` and `opencode2` (2.x) rows by its ses_ id; `-s/--session`, `-c/--continue` resume, `--fork` does not. - Grok: `-r/--resume/--load` and `-c`; a non-UUID value names a title, so it is read as target-less; `--fork-session` does not resume. - OMP: `-r/--resume/--session` take an id, any prefix of it, or the session file's path; bare `--resume` and `-c` are target-less. - Pi: rows are listed by the session file name's id while the chat holds the file's path; `--session <id|prefix|path>`, `-c`, `-r` (picker). - An optional `resumeTargetMatches` on the registration covers prefix and path targets; it defaults to exact equality. - Each agent now reads every token naming its own binary, so another agent's name earlier in a command (`claude --model pi`, `cd ~/src/codex && claude --resume X`) no longer hides a resume. For Claude and Codex this only ever refuses more than before. - A renamed OpenCode chat tab names its `opencode2` row too. * fix(chat): show owned rows only to clients that open the chat, without a resume command - The list and search projection now asks, per owning agent, whether the requesting client opens that agent's chat. Current mobile renders only Claude and Codex chats, so it no longer receives OpenCode/Grok/OMP/Pi rows owned by a chat (a tap there opened an empty terminal and showed agent_session_conflict). Claude and Codex keep their rule: any client that reads structured sessions. - An owned search hit opens its chat, so it no longer carries a terminal resume command (all agents; `orca` CLI search stopped printing one for a chat). - OpenCode/Grok/OMP/Pi flags match case-sensitively, as their CLIs read them, so `rg foo src/pi -C 3` is not `pi -c`. Claude and Codex still fold case. - A test feeds every resume command Orca builds for an owned row of each agent (Session History, live-tab argv; posix, PowerShell, cmd) into the guard. * fix(chat): keep the resume guard linear and skip chat-tab lookups with no move - Each agent binary mention in a command now reads its arguments only up to that agent's next mention, which reads the rest, so every token is parsed once per agent. A 200 KB text naming agents throughout took 9.4 s on the host's main process; it now takes about 90 ms. Every refusal before still holds: a later mention still reads its own resume. - Right-clicking a native chat tab looks up its Session History row only when the agent's CLI can fork a conversation, the one move a chat tab offers, so OpenCode, Grok, OMP and Pi chat tabs run no list or rescan for nothing. Claude and Codex are unchanged. * fix(chat): read each shell command once in the resume guard The guard tokenizes the text once and splits it into shell commands at &&, ||, ;, |, & and newlines (a newline after a line-continuation mark stays inside its command). In each command, each agent reads its arguments from its first binary token to the command's end; a later token naming it is an argument. A `claude` path inside a Claude command no longer ends that command, so `--session-id <owned>` still cancels a fork after it, and a binary-named path between a resume and its fork flag keeps the fork a fork. Binary names are looked up once per token, which keeps a 16 MB send to about 2 s. Against main's parser, a Claude/Codex command is now allowed only when main refused it for a flag in a later shell command (`claude ; echo --resume X`). * fix(chat): end a resume-guard command at a bare carriage return A PTY's Enter, and many orca terminal send texts, end lines with \r. The guard now treats \r and \r\n as line breaks like \n, including after a line continuation mark, so a later line's resume is read as its own command. * refactor(chat): drop the transcript-adoption and Session History wire flags agentSession.agents no longer publishes transcriptAdoption or sessionHistory, so there is no new field for older and newer peers to reconcile. Their only consumers were renderer gates that could answer from what they already have: "Resume in New Native Chat" eligibility goes back to main's rule (adopting a CLI conversation is Claude/Codex-only, and the host still refuses any other agent from its registration's transcriptImport). Removes the AgentSessionDeclaredCapabilities split, the older-host decoder shim and the generic host-flag reader. * refactor(chat): keep main's Claude/Codex rule for the CLI fork offer "Resume in New CLI" and the chat-tab menu read the agent name again instead of building a synthetic provider session to ask the fork builder, and the chat-tab lookup loses its host Session History flag gate. Only Claude and Codex CLIs fork, so only their chat tabs look up a row, exactly as on main: no new list requests. * refactor(chat): one registration-aware ownership index, no registration union - listStructuredProviderSessionOwnership reads each record's registration for its Session History row id; the callback parameter and its one-function wrapper module are gone (no import cycle: checked value and type imports). - StructuredAgentCliConversations is a plain type: transcriptImport is nullable and sessionHistory is required, since every registered agent now owns its rows. The "importer needs history" union and its null checks go. - A test pins that no two registrations claim the same Session History row agent, since the owner lookup takes the first match. * fix(chat): read resume commands into words as a shell does Separators glued to a word (`opencode -s id; echo`, `cd /x&&claude -r id`) now end the command, and quotes are removed inside a word, so `--session="id"` reads as the id. Applies to every agent, Claude and Codex included. The acp-and-pi allowed() helper now asserts. * fix(chat): count an agent's name only where a shell would run it terminal.send checks agent prompts too, and agentPrompt does not keep the text from a shell (it falls back to a plain PTY write), so prompt text like "Fix the Pi --resume flag" was refused. The resume check now reads each command's program: its first word past VAR=x prefixes, a path, and env/exec/command/sudo/npx/bunx/pnpm dlx. Every command Orca's builders produce, Claude and Codex included and with a user's command override, is still refused. * refactor(chat): compare resume ids in any case; Claude uses the shared option reader The default target match now ignores case (ids are UUIDs or ses_ ids), so Grok's override, which only repeated it, is gone. Claude folds its flags and reads them with resumeInvocationFromOptions instead of repeating the `=` handling. * perf(chat): read terminal text in one linear pass A repeated regex group overflowed the stack on a 16 MB word. A sticky-regex scanner reads words, quoted runs and separators in one pass; an unclosed quote is searched for once per quote kind. * refactor(chat): read a command's resume only when its program is an agent * fix(chat): read an agent run under nohup, time or wsl as the command's program * chore: restore pnpm-lock.yaml * fix(chat): keep the CLI's own title on OpenCode, Grok, OMP and Pi chat rows Session History shows these agents' chat conversations under the title their CLI saved, as it does for their terminal conversations; the chat tab's name and renames stay on the tab. Claude and Codex rows keep the chat's name, as on main. * feat(chat): Resume in New CLI for Grok and Pi chats A Grok or Pi chat's Session History row and tab menu now offer "Resume in New CLI", like Claude and Codex: the CLI opens a copy of the conversation (grok --resume <id> --fork-session, pi --fork <file>) and the chat keeps its own. OpenCode's interactive CLI and OMP have no way to open a copy, so their chats keep only Resume. * fix(chat): offer Resume in New CLI only when the copy command can be built A Pi chat found by search on a remote Orca server has no session file path, so its copy command can't be built; the row no longer offers the button there. * fix(chat): refuse pi --session-id of a conversation a chat owns `pi --session-id <id>` reopens that session when it exists, so it is a second writer like --session. |
||
|
|
9c1d5d2fb7 |
perf(pty): check WSL working directories off the main thread (#27705)
* fix(wsl): start host-side wsl.exe probes through process-host so they never open a console wsl --list, wsl --status, the WSL home and directory probes, and WSL UNC deletes were started with node:child_process directly. The synchronous ones omitted windowsHide, so each spawn from the GUI-subsystem main process flashed a conhost window that could take keyboard focus. Route them through @orca/process-host, which pins windowsHide, and keep the execFile-shaped exit errors the availability cache classifies. Refs #27390 * build(tc): list wsl-exe-process.ts in the web typecheck project wsl-availability.ts is in the explicit web file list and now imports it. * refactor(wsl): drop the unused sync availability probe; classify ProcessResult directly No production code calls the sync isWslAvailable (the IPC and RPC paths already assert they never do), so delete it and its sync guest-probe twin instead of porting them. The async probe now reads the --status ProcessResult itself: an exit code is definitive, a timeout or signal kill is retryable, ENOENT on spawn is definitive. The WSL2-missing-kernel exit codes live in one named set. Availability tests now drive the async probe. * refactor(wsl): shrink wsl-exe-process to a spec builder and stdout reader With the availability cache classifying ProcessResult itself, nothing reads the execFile-shaped code/status error any more. wslExeFailure returns a plain reason string; wslExeStdout throws it; the UNC delete folds it into its own error. The home probe builds its argv with buildWslExecArgs. * perf(pty): probe WSL working directories off the main thread The in-process terminal launch path validated a \wsl.localhost cwd with a synchronous wsl.exe probe, blocking the main process ~70 ms per launch (300-530 ms when the distro is stopped, up to the 5 s timeout). The launch now awaits the existing async probe and the synchronous one is removed. Refs #20497 * fix(pty): let a canceled spawn abandon its pending WSL cwd probe The local spawn awaited the WSL directory probe without its cancellation signal, so a pane closed mid-probe waited up to the 5s wsl.exe timeout before noticing. Pass the signal through validateWorkingDirectoryAsync and report an abandoned probe as the spawn's own cancellation, as the daemon spawn preflight does. * test(wsl): mock process-host in the empty running-list case from main * docs(wsl): the availability probe no longer blocks; drop the stale word * build(ratchet): prune the wsl.exe sync launches this branch removed * build(ratchet): prune the sync WSL cwd probe this branch moved off the main thread --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
50cc50d33e |
refactor(chat): give the start-refusal resumable table its own module (#28085)
#27946 and #26684 together put agent-session-failure-words.ts at 304 lines, over max-lines, which fails lint and the pre-commit hook on every merge of main. The refusal-code table is standalone with one reader, so it moves out. Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
c0e2b73395 |
feat(native-chat): require Codex 0.136.0 for chats (#26684)
* Require Codex 0.136.0 for native chats
* Add host-owned Codex install and update actions
* Bind Codex checks and maintenance to the selected installation
* Resolve Windows updater cleanup through system helpers
* Use one child environment for Codex checks and launch
* Expire Codex readiness and reconcile current host jobs
* Resolve Codex checks against the child command search path
* Restrict global npm repair to the selected Codex install layout
* Pin the host updater directory through Windows supervision
* Synchronize Codex maintenance fixtures on readiness
* Unify Codex notices and recover version-refused chats
* Route Codex recovery to the operation that remains
* Preserve Codex setup execution errors in the job log
* Preserve UTF-8 in Codex setup logs
* Remove unused Codex host installation detector
* Require host administration for Codex maintenance
* Show a missing Codex explanation once
* test(native-chat): give scripted Codex runtimes their installation fact; keep the registry out of the relay
Runtime tests that drive a scripted Codex app-server still ran the new
version check against whatever codex was on the machine's PATH: refused
on CI (no codex), and on a dev machine it ran the real codex --version.
A scripted transport now must state the Codex it stands for
(readCodexInstallation), asserted at install, and the tests get it from
one scriptedCodexTransport fixture.
The relay's Codex maintenance path reached @orca/windows-registry through
resolvePathEnvKey's re-export in windows-environment-path; import it from
its registry-free module instead.
Also update the catalog-probe test for the shared child environment.
* refactor(native-chat): drop the Update Codex action; keep Install for a missing Codex
A too-old Codex now gets only the plain message ("Codex X is too old for
chats. Update to 0.136.0 or newer.") with no button and no job. The
existing rechecks (window focus, the <=30 s expiry of a saved result)
retry the refused chat once the user has updated Codex themselves.
A missing Codex keeps the Install Codex button: `npm install -g
@openai/codex` with the npm on the chat's PATH, as a host-run job with
its log, then a recheck and recovery. A configured Command that is
missing gets the message only, since a global npm install would not
land at that path.
Removed: the `codex update` command and its 0.126 floor, manual
instructions, npm layout and launcher identity detection, the action
descriptor (install|update) and its titles, the legacy capability, and
the SSH relay maintenance path (structured chat never runs over SSH or
WSL), so the relay preflight handler is back to main's.
* refactor(native-chat): keep Install Codex in Settings only
When Codex isn't installed, main already hides Codex from the New tab menu,
so the in-chat Install button was almost never reachable. Settings → Agents
already shows "Codex isn't installed." with Install Codex.
- Chats and the new-workspace composer now only state "Codex isn't
installed." (no button, no install job from the chat).
- The Install action, its busy spinner and the host-busy subscription move
into the Settings Codex row, the only place that starts the job.
- The store no longer re-checks every other workspace context when a job
finishes. A refused chat recovers through the same re-check a too-old
Codex uses (window focus, or the <=30 s expiry of a saved result).
- The chat recovery key no longer includes the install job id.
- Tests: Settings drives the install; a new test proves a chat refused for
a missing Codex recovers through focus after a Settings install, not
through the job; the expiry recovery test covers missing and too old.
* refactor(codex): check chat compatibility when acquiring the child
* Restore workspace submit guard and simplify Codex maintenance
* Keep only the Codex chat minimum version
|
||
|
|
6af34a4d99 |
fix(chat): name the agent when it refuses a message (#27946)
* refactor: move provider-retry pieces into a sibling words file Pure move of the six provider-retry translation pieces out of agent-session-failure-words-text.ts, which sits at the 300-line limit. No key or behaviour change. Co-Authored-By: Claude <noreply@anthropic.com> * fix(chat): name the agent when it refuses a message "The provider did not accept this message" becomes "{{agent}} didn't accept this message" ("The agent" when the name is unknown), in English and every locale. The providerRejected failure kind is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
fe1f0223af |
Native chat: read one saved row per subagent (part 1 of 2) (#27914)
* feat(chat): read one saved row per subagent Adds the singular `subagent` journal block (one row per child, revised in place, with a plain-text twin for older clients) and teaches every reader to draw it: desktop and phone fold a turn's sibling rows into the existing "Ran N subagents" row, the client and off-page rosters name children from their own rows, the reopen pass settles a row a dead host left working, and the phone and worker-transcript wires bound it. No producer writes the block yet; that switch lands in the follow-up. * fix(chat): open subagent background flag, separate nested group ids, page window skips subagent rows - background is an open boolean so a later build's value never fails a chat load - a helper's own helpers fold under a groupId of their own, so the phone opens them apart - each subagent's own row rides along with the page window instead of counting as conversation * refactor(chat): a subagent's group entry as one object |
||
|
|
fde0fe35a7 |
fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone (#28011)
* fix(daemon): stop giving new terminals to a daemon whose AppImage files are gone On Linux AppImage, a terminal daemon can outlive the mount it runs from and keep answering health checks until it SIGBUSes. Orca now reads the daemon's /proc exe link and mount table at adoption: an idle severed daemon is replaced; a busy one is kept for its sessions but quarantined, so fresh terminals use the local provider and a healthy RPC never re-admits them. Status and a verified restart are available over RPC and as `orca terminal-daemon status|restart`. Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): lift the severed quarantine once a new daemon owns the endpoint Co-Authored-By: Claude <noreply@anthropic.com> * fix(daemon): a severed-daemon restart counts fallback terminals and never stops a replacement The restart re-checks the authenticated endpoint owner before any teardown and again at shutdown, and never falls back to a pid kill when targeted. Status counts the local fallback terminals the restart also ends. Co-Authored-By: Claude <noreply@anthropic.com> * chore(daemon): merge duplicate import Co-Authored-By: Claude <noreply@anthropic.com> * fix(runtime): classify the terminalDaemon RPC namespace as host-owned Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
dedea9c76c |
fix(updater): clear a cancelled macOS install request before staging the next update (#28010)
* fix(updater): clear a cancelled macOS install request before staging the next update * fix(updater): match only Orca's ShipIt and recheck owners right before clearing --------- Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
7c13365f03 |
refactor(build): relay survivors named for what they are (#27962)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
b368ebc7a6 |
refactor(ssh): delete relay-only GC, takeover and install remnants (#27770)
Co-authored-by: m4air <m4air@Mac.localdomain> |
||
|
|
cfbf3610d5 |
fix(linux): keep Wayland clipboard text current (#27772)
Publish Linux Wayland text copies with an independent clipboard owner, then clear Chromium's stale source only after publication succeeds. Use data control where available and GTK's clipboard bridge on GNOME. Keep macOS, Windows and X11 on their existing Electron writers. Temporary workaround for https://github.com/electron/electron/issues/54645, with removal checks documented in the native README. Verified full build, macOS arm64 packaging, Linux x64/arm64 library floors, clipboard/platform tests, real Wayland copy/paste and CI packaging. Fixes #26917 Fixes #23229 |
||
|
|
822bd9f972 |
fix(ssh): keep 1.4.224's running terminals working after the upgrade (legacy relay bridge) (#27787)
* wip: legacy relay bridge client
* Revert "fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)"
This reverts commit
|
||
|
|
627716fdc1 |
perf(ports): stop leaving stuck lsof processes behind on a dead network mount (#27729)
* perf(ports): run lsof without blocking on dead network mounts Without -b, lsof stat()s every mounted file system before answering. A hard-mounted network share that stops responding blocks that call in the kernel, so each port scan left an unkillable lsof behind. Pass -b -w to the lsof queries that name no file (port list, process cwd, executable path); their output is unchanged. The -U and +D queries keep stat because -b makes lsof reject named-file arguments. Linear: STA-9017, STA-9716 * perf(ports): name the stuck lsof helper in the comment; plain readiness test name * test: query a known child executable in lsof readiness smoke test |