mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
07981bd12ee738fe96bb2d5990cc385dc22c7d7d
12862
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
07981bd12e |
fix(native-chat): chat copy that promised a retry, an update, or a coming change (#25157)
* fix(native-chat): chat copy that promised a retry, an update, or a coming change
- Subagent and background-task rows Orca can no longer settle read "status
unavailable" ("2 agents with status unavailable"), not "no recent update",
which implied an update was still coming.
- /compact or /clear refused behind an unconfirmed earlier message says "Wait
for the agent to finish, then try again." There is no Retry for that message.
- The skills menu in SSH chats says skills can't be listed there; a skill typed
by name still goes to the agent.
- A write the host refuses because this agent or location can't run as a chat
says "Orca can't run this agent in a chat here." plus what didn't happen.
- Spanish gains the chat notice, background-task and subagent rows it lacked.
* fix(native-chat): whole count sentences for background-task states, and translation fixes
- Background-task header counts use one translated sentence per state
(stateWorkingCount ... stateIdleCount), so Spanish and French can agree
the state with the count; English output is unchanged and pinned by a test.
- es: subagent failed/idle/stopped counts and the waiting/blocked reasons
read correctly for any count.
- fr: "listées" agrees with the feminine "skills" in the SSH skills line.
- ja: subagent status-unavailable count uses the 件 counter.
- Correct the refusal-table comment: hostUnsupported is also returned and
recorded, not only thrown.
* fix(native-chat): es/fr background-task counts agree with any task kind
* revert(native-chat): leave the /compact unconfirmed-message refusal to its own PR
Restores main's settleEarlierMessage copy in every locale and drops its comment.
The refusal's behaviour, not only its words, is being fixed separately.
* test(native-chat): expect this PR's Spanish blocked reason in the tool-run language test
|
||
|
|
defd85a9eb |
feat(floating-workspace): render the floating panel through the shared workspace surface (#22302)
* docs(floating-workspace): plan structured native chat in the floating panel
* feat(floating-workspace): resolve the floating workspace so a structured session can run there
* docs(floating-workspace): plan the unification onto the shared workspace surface
* chore(floating-workspace): keep the unification plan out of the branch
* test(floating-workspace): pin the structured route on capability, not workspace kind
The host half removed the 'floating-workspace' blocker; this pin still asserted it and went red
on the merge. Floating now routes like any workspace, so with capabilities un-negotiated the
resolver answers 'runtime-capability-unknown'.
* feat(floating-workspace): resolve the floating workspace's renderer row without a worktree record
The renderer had no Worktree row for the floating id: markdown creation, tab-bar path entry,
native-chat file links, attachments and model discovery all resolve a workspace through
getKnownWorktreeById and silently fail for it. The host already answers with a synthetic row
minted from the resolved floating directory; this gives the renderer the same single seam.
- The repo slice stores the host-resolved floating directory (floatingWorkspacePath); the panel's
existing cwd resolution now writes it there instead of panel-local state, so there is one copy.
- findKnownWorktreeById mints the shared floatingWorkspaceToWorktree row from that path, local
host only, cached while the directory is unchanged — mirroring the folder-workspace branch it
sits beside.
- No consumer changes needed: every row-dependent path already goes through this resolver.
* feat(tabs): scope editor and browser activation to the owning workspace
Shared editor/browser activation always wrote global selection (activeFileId,
activeBrowserTabId, activeTabType). The floating panel is an overlay above the active
workspace, and its active tab must never become global selection — the invariant
documented in agent-auto-ack-targets.ts. Group-scoped activation is a prerequisite for
rendering the floating workspace through the shared tab-group surface.
- setActiveFile and setActiveBrowserTab take an optional target workspace, mirroring
setActiveTabType: global selection moves only when the target IS the globally active
workspace; per-workspace state always updates. Omitted keeps legacy behavior.
- The shared tab-group activation/creation commands pass their workspace, which is a
no-op for the main surface (hidden surfaces are inert, so commands only fire on the
active workspace) and confines floating activation to floating state.
- Terminal setActiveTab was already owner-scoped; now pinned by test.
* refactor(tab-group): separate the workspace drag scope and group tree from main-window chrome
Preparation for hosting the floating workspace on the shared surface, whose shell owns its own
titlebar tab strip and cannot mount the main chrome:
- WorkspaceTabDragLayer: the one dnd-kit scope for a workspace's strips and tree, extracted from
TabGroupSplitLayout as a render-prop layer so a host can put shell-owned chrome (a titlebar
strip) inside the same drag scope as the tree.
- TabGroupSplitNodeTree: the chrome-free recursive group tree, with an explicit tabStrip
composition point on TabGroupPanel ('attached' keeps today's per-group strip; 'external' hosts
render no per-group strip at all — a hidden one would register duplicate drag sortables).
- WorkspacePaneOverlayLayers: the retained pane hosts extracted from WorktreeSplitSurface so any
tree host mounts the same pane lifecycle stack.
- TerminalPaneOverlayLayer gains ownsNativeChatToggleShortcut, following the existing rule that
only one live workspace registers the chord.
TabGroupSplitLayout and WorktreeSplitSurface recompose these parts; the main surface's DOM is
unchanged.
* feat(floating-workspace): decide the panel's emptiness and layout atomically
An empty floating workspace has no tab-group layout at all (the slice starts empty and
hydration skips tab-less workspaces), while the shared group tree requires one. This model
resolves the two together: the panel shows its empty state exactly until the first tab exists,
and mounts the tree only with a layout plus a live focused group (stored focus, else the group
with an active tab, else the first group — matching the shell chrome's focused-group policy).
* feat(floating-workspace): render the floating panel through the shared workspace surface
The panel previously projected exactly one tab group and hand-rendered its panes, so any tab
that left that group — 'Move Tab to Split' creates a second group — stayed alive in state but
vanished from the panel. The body now mounts the shared group tree and retained pane overlay
stack for the floating workspace id, so every group renders, splits work, and structured chat
finally has a floating surface.
- One drag scope: WorkspaceTabDragLayer wraps the titlebar strip AND the body tree, so exactly
one DndContext owns floating tab drag; strip drops reorder, body-edge drops split. The
floating-only FloatingWorkspaceTabDragContext is deleted.
- The titlebar keeps the panel's single TabBar, now fed by the shared per-group workspace model
of the focused group (tabStrip='external' keeps per-group strips out of the shell), with
workspace-scoped activation commands so global selection never moves.
- Panes render through WorkspacePaneOverlayLayers: terminals gate on the resolved cwd and a
settled viewport exactly as before; browser guests ride the shared retention layer; agent
session tabs appear in the strip and render through the structured overlay.
- The empty state renders exactly until the first resolvable tab exists (stale unified entries
still show it), per the atomic layout/emptiness model.
- Deleted: the single-group projection, the hand-written pane maps, FloatingBrowserSlot, and
the floating pane-handle registry.
Known behavior deltas, both narrowing floating onto main-workspace semantics: Cmd/Ctrl+W with
panel (not terminal) focus closes the active tab rather than one pane of a multi-pane tab, and
a pane-owned close of the last tab no longer arms the panel focus reclaim (strip and shortcut
closes still do).
* test: re-register moved pane-ownership sites with the extraction
The retained overlay stack moved into WorkspacePaneOverlayLayers and the floating chrome model
became a guest-paint consumer; the structured-pane layering pin and the browser-guest retention
census both name files, so they follow the seam.
* fix(floating-workspace): mount the shared group tree in a flex frame so panes own their height
The tree's nodes size themselves as flex items, and the floating shell mounted
them in a block container, so every group body — and every pane anchored to
one via anchor-size() — measured 0px tall. Wrap the tree and overlay layers in
the same 'absolute inset-0 flex' frame the worktree split surface uses, record
the host contract on the tree, pin the frame in the panel's element-tree test,
and add a real-renderer e2e spec that measures group-body and pane boxes for
the single-group and split layouts (jsdom computes no layout, which is how the
0px regression passed 50k tests).
* fix(runtime): import the floating worktree minting the structured create path calls
floatingWorkspaceToResolvedWorktree called floatingWorkspaceToWorktree without
importing it. The module is @ts-nocheck (mechanically split runtime), so tsc
never saw the missing import and it only failed at call time — the structured
createSupport probe for the floating workspace threw a ReferenceError. Add the
import and a test that runs the real method, which fails on the missing import
where the typechecker cannot.
* fix(browser-pane): resolve floating and folder workspace paths in notebook navigation
navigateBrowserPageToUrl looked the workspace up in allWorktrees(), which has
no row for folder or floating workspaces, so their notebook navigations ran
with an undefined workspace path. Use getKnownWorktreeById, the same resolver
every other consumer was moved to.
* fix(tabs): keep a workspace's tab selection out of the main window's global selection
* refactor(floating-workspace): launch and close panes like every other workspace
* refactor(tab-group): let the surface that mounts tab groups describe its own chrome
* refactor(floating-workspace): render the floating panel through the shared workspace surface
* fix(floating-workspace): resolve the floating directory once on the host, and keep the controls' inset
* test(floating-workspace): pin the unified floating panel against the shared surface
* fix(floating-workspace): read the floating directory's paths through the app environment
The runtime now resolves the floating directory itself, and the headless
runtime has no Electron app, so the Electron path lookup threw there.
* fix(floating-workspace): keep the main window's frame and shortcuts out of the floating panel
The shared layout drew the main window's 4px titlebar band and sidebar seam
inside the floating panel, and the chat-view toggle fired in every workspace
on screen, so one key press flipped the main window's tab and the panel's.
* fix(floating-workspace): close floating tabs without taking over the main window
An unsaved floating note's save prompt made the floating panel the main
window's active worktree, and never appeared on a fresh launch because the
workbench that owns the prompt was not mounted. The prompt now shows a
floating note in the panel, and the workbench mounts while the panel holds
tabs.
Every close path now reads the same captured emptied-workspace reaction, so a
floating pane closing itself, its shell exiting, a group close, and an unsaved
note close all keep the panel's keyboard ownership, as a tab close already did.
* test(tab-group): pin that a dirty editor close carries its emptied reaction
* refactor(tabs): one rule for when a selection moves the main window's selection
Opening a file kept a floating-only exception while every other writer checked
whether the tab's workspace is the active one. Every writer now asks the same
question, so opening a file in a background worktree no longer points the main
window's editor selection into that worktree.
* refactor(floating-workspace): drop what the shared surface left unused
The terminal pane's imperative close handle lost its last consumer when the
floating panel stopped keeping its own pane registry, and the floating and
folder workspaces built the same lineage-free resolved worktree twice.
* test(floating-workspace): type test fixtures without casts and drop the removed pane ref
* fix(floating-workspace): keep panel focus when a close lands after the panel emptied
Saving the last floating note closes it after an awaited write, so the panel
could render empty before the close armed its keyboard reclaim, leaving the
intent armed with nothing left to consume it. The panel now re-checks whenever
the intent arms as well as when its tab count changes.
A pinned tab's confirmation took focus before the close read panel ownership;
the dispatcher now captures it before any prompt.
* refactor(floating-workspace): finish what the shared surface left behind
- The floating panel no longer accepts AI vault session drops: a session
cannot resume into it, and its window-level drop handler fired beside the
main window's.
- The last global-selection writers that spelled the active-workspace check
inline now use the shared rule.
- Remove the save-dialog hook and item counter the old floating body used, and
correct comments that said the workbench only mounts once a workspace exists.
* fix(floating-workspace): keep a floating leaf detach from changing the main window's tab type
* fix(floating-workspace): give floating chats their workspace identity and directory
* fix(floating-workspace): read one visibility fact for chat attention and auto-ack
* docs(floating-workspace): correct comments the shared surface made stale
* fix(attention): one on-screen rule for every tab's unread marker, main window and floating panel
* fix(agent-session): pin the folder a session launched in; floating chats resume there or refuse
* fix(native-chat): resolve a floating chat's file links, images and skills in its pinned folder
* fix(agent-session): open a floating chat's terminal handoff in its pinned folder
* fix(agent-session): surface refusal text on chat open, require the launch-directory rule, and keep bookkeeping from gating a launch
* fix(floating-workspace): wait for a floating chat's pinned folder and read panel visibility from the store
* test(floating-workspace): assert the toggle event without a cast
* fix(native-chat): show why a chat could not be opened instead of dropping the host's refusal
* fix(floating-workspace): close chat tabs through shared commands
* fix(i18n): remove obsolete floating editor loading key
* fix(floating-workspace): preserve tab ownership on activation and browser close
* fix(floating-workspace): keep terminal activation and focus reclaim in sync
* fix(readme): point translations at tracked media
* fix(floating-workspace): retain split surface with an empty focused group
* docs(tabs): describe workspace-owned activation accurately
* fix(floating-workspace): finish the main merge and name the pin a launch directory
Port what the merge left on main's newer shapes:
- The floating chat's "folder is gone" refusal becomes a typed reason,
launchFolderMissing, worded like every other start refusal (copy, desktop
words, five translations). Older clients drop the unknown reason and show
the code's generic words.
- The PR's launch-folder tests run on main's journal-database record store;
the attach-level case is a row in the pre-spawn refusal table.
- Tests main added since the PR's base learn the PR's close-queue settling,
workspace-scoped notebook lookup and tab-stamped chat host.
- Move the store's visible-tab-index reads into the tab table, so the record
store stays under the line cap with the pin writer in it.
- Drop a duplicate Worktree import the merge left in a @ts-nocheck file.
Rename the persisted field, the status summary field and the renderer slice
from workspacePath to launchDirectory: it records the folder a session first
launched in, not its workspace's path, and it has not shipped yet.
* Fix floating chat visibility, closes, and directory pinning
* fix(i18n): drop the floating save dialog's strings with the dialog
The floating panel now closes through the shared unsaved-changes prompt, so
its own dialog's six strings had no caller and failed the runtime-catalog
check.
* fix(native-chat): pin floating founders and retain pinned editors
* test: align auto-ack fixtures with unified tab visibility
* test: give the forget-status attach fixture a location
Attach now reads params.location to decide whether a new record is a floating founder, so the partial fixture needs the location real attach params always carry.
* test: type the forget-status attach fixture instead of casting it
The cast tripped the changed-code type-assertion gate.
* fix(native-chat): read a floating chat's model defaults in the folder it was created in
The model picker read the floating setting's current folder, so a chat
pinned to another folder could show that folder's project default.
* test(cross-version): give the stub host the record store the catalog read now checks
* test(claude): give main's thinking-display launch stub the store shape the resolver takes
|
||
|
|
e63e4f80da | Update README downloads badge | ||
|
|
128d154347 |
Fix Codex chat creation after its own model listing fails (#25754)
* fix(native-chat): keep Codex chat actions independent of catalog listing * fix(native-chat): accept a Codex option pick before the model list arrives - A live model, effort or Fast pick with no stored model list is kept as the next turn's intent instead of refused; Codex judges the model on turn/start and Fast without a known tier still sends Standard. - Restoring saved Fast checks the saved model the turn will send, not the resumed thread's old model; the thread's effort is only reused for its own model. - turn/start reads the exact Fast tier from the account's stored catalog at send time, so a tier any writer stored applies; the per-session copy is gone. - Revert the delivery-loop extraction; that file matches main again. * test(native-chat): pass the agent registry to the new acquisition attach tests Merged main (#25159) requires agents on attach and an agent definition for catalog access; the PR's new acquisition attach tests and Fast-mode tests now pass them. |
||
|
|
8d049b594d |
fix(codex): approve Orca's hook in managed Codex homes with Codex's own hash (#25742)
* feat(codex): ask Codex for its hash of Orca's hook in a throwaway home, cross-checked by position and path * feat(codex): cache Codex's hook hashes per binary and version, asked one at a time and only by the app * feat(codex): write a hook approval before its entry, and take back only its own on failure * feat(codex): approve Orca's hook in managed Codex homes with Codex's own hash, written first Managed homes (the shared mirror and per-account homes) no longer run a background approval session. Status reads the home's files against Codex's answer, and turning hooks off recognizes every saved version's hashes. * feat(codex): managed homes approve Orca's hook with Codex's hash; drop their background approval The previous commit carried only the managed resume's wait; this one holds the managed install it relies on. Managed homes (the shared mirror and per-account homes) write Codex's hash before the entry, fall back to their own approvals when the answer is late, and strip Orca's entry only when Codex itself answered with nothing to approve. Status reads the home's files against Codex's answer, and turning hooks off recognizes every saved version's hashes. * feat(codex): only an Orca-launched Codex waits up to 3 s for the hook hash; warm it after PATH hydration * feat(cli): name the file each agent hook status reports on * test(codex): real-Codex contract for the derived hook hash in managed homes, on both pins and latest * test(codex): type the hook-hash test fixtures and drop a duplicate import * fix(codex): give a Codex launch its own install run instead of joining a plain terminal's * test(codex): a user hook's approval stays put in an event Codex does not list * test(codex): cover late answers, first-install mirroring, stale approvals and opt-out re-asking * test(codex): a long managed home gets the daemon guard on its first install * fix(codex): until Codex answers, approve a managed home's hook with Orca's own hash, as main did A late, temporary or missing answer with no earlier approval in the home now writes main's self-computed approval instead of leaving the hook out. Codex's answer replaces it at the next install, a definitive answer (no hooks/list, a refused cross-check, 0.128) never uses it, and status says the approval is Orca's until Codex confirms it. * test(codex): status flags an unapproved entry while Codex has not answered * fix(codex): managed stopgap fills each missing event Until Codex answers, a managed home kept only the events it had already approved and dropped Orca's entry from the rest. Each event now keeps the home's approval, else gets Orca's own hash, as main wrote every event. One reader of the approval at Orca's entry serves the stopgap and status. * refactor(codex): one Codex answer type, one in-process answer map, a disk-only memo - One answer type with a kind (hashes, refused, pending) replaces two types and the three-field decoding at each caller. - The lookup keeps one in-process answer per binary path, replacing the process memo, the global latest answer and the transient-failure map; status now reads the answer for the codex on PATH, not the last one asked. - The memo file keeps Codex's refusals per version, like its hashes. - Derivation takes the version it is given; one 30 s version-probe timeout. - The launch wait reuses withTimeout, and launch prep passes launchesCodex down instead of a wait in milliseconds. - Turning hooks off no longer forgets Codex's answer. - Tests mock the derivation instead of a test-only resolver in production. * chore(codex): list the approval reader for the CLI build; fold two identical scope checks * fix(codex): count an approval at Orca's key only when it holds a hash Orca's entry may carry * refactor(codex): one append for hook trust tables * refactor(codex): the lookup keeps no entry for a missing Codex, and status checks the binary's fingerprint Also names the lookup functions for the answer they return. * refactor(codex): one stopgap reader for the managed home; the refused branch reads its own status * refactor(codex): drop defaults and exports only tests relied on * fix(codex): a failed ask of Codex stays pending instead of refusing its version * chore(ci): run the real-Codex contract when the approval reader changes * fix(codex): only a scratch home Codex loaded can refuse; the memo takes any hash and writes only on change * fix(codex): hooks turned off during a launch's wait win, Off re-keys mirrored user approvals, and one rule says which hashes are Orca's * fix(codex): an approval counts only under every key spelling Orca writes, as Codex on Windows reads only the backslash one |
||
|
|
f27ef42aa0 |
fix(runtime): build the host terminal model when a phone opens an idle terminal (#25780)
* fix(runtime): hydrate the host model on a phone subscribe to an idle PTY After a relaunch the reattach leaves the host model empty and an idle TUI never paints the byte that hydrates it. A phone subscribing then got the hidden desktop pane's screen at desktop size, missed its viewport on every resubscribe, and gave up with "Couldn't fit the terminal to this screen". The subscribe now runs the existing renderer hydrate after the phone fit, so the pane's screen is reflowed onto the PTY grid, and serves the phone from that model instead of adopting the pane's desktop-sized screen. Claude-Session: lane-host-hydrate * test(e2e): phone subscribe to an idle relaunched terminal fits the phone Relaunches the app over a live daemon PTY running an idle program, hides its pane behind another tab, and subscribes as a mobile client at 47x40. Before the host-side hydrate the first snapshot came back at the hidden pane's size (135x60 here). Claude-Session: lane-host-hydrate * fix(runtime): join a renderer seed the resize repaint already started A TUI answering the phone fit's SIGWINCH can deliver a byte before the subscribe reaches its hydrate. That byte runs the same renderer seed, so the subscribe saw "already hydrating", fell back to adopting the pane's desktop-sized screen and seeded the model at desktop size again. Keep the model's renderer seed beside it so the subscribe joins that result. Claude-Session: lane-host-hydrate * fix(runtime): join the resize repaint's seed without per-PTY seed state Replaces the rendererSeedsByPtyId map with a check over existing state. If an on-data seed was already considered before the subscribe's hydrate, the subscribe awaits the model's write chain and reads the outcome from the hydration state, the installed model and the provider-preferred flag. Claude-Session: lane-host-hydrate * fix(runtime): serve on-demand tab mounts to the phone from the PTY-grid model A phone opening a tab not mounted since relaunch makes the desktop mount it. The freshly mounted pane is hidden, so it answers at desktop size, and both the mount-wait snapshot and the late renderer-mount-ready recovery published that raw screen and seeded the model at that size. The recovery seed now reflows the model onto the PTY grid, and both sites publish the model's snapshot through one shared step, so the phone is only served from the host model. A late recovery whose model moved past the renderer high-water while it was rebuilt is still withheld. Claude-Session: lane-host-hydrate * fix(runtime): seed the model when a buffer read falls back to the pane With no host model, a terminal buffer read answered with the desktop pane's screen at the pane's size. The phone's resize restream and pending-output recovery read through it, so a hidden pane could still hand the phone a desktop-sized screen. The fallback now seeds the model from that screen onto the PTY grid and answers from the model, unless output landed during the read. The desktop's own hidden-output recovery keeps the raw renderer fallback. Claude-Session: lane-host-hydrate * revert: drop the race join, on-demand mount and buffer-fallback rounds Reverts |
||
|
|
468e4e1167 |
fix(native-chat): a prompt card owns the chat input until its answer lands (terminal-backed chat, desktop and phone) (#25761)
* fix(native-chat): an answerable prompt card owns the chat input until its answer lands * fix(mobile): a terminal chat's composer waits while its prompt card is up * test(native-chat): type the prompt card fixtures without casts * fix(native-chat): scope replies to acknowledged prompt occurrences * fix(native-chat): preserve answer ordering and verified delivery * test(native-chat): keep mock RPC client inside test boundary * test(native-chat): place mock fixtures in the test-only scope * Keep runtime comments within the module size limit * test: preserve prompt delivery coverage in desktop CI * Treat an older host's accepted write as delivered A newer desktop or phone talking to a host that predates the write settlement field read every accepted reply as "unconfirmed". Prompt cards never dismissed, the phone showed "Response unconfirmed" on every tap and ordinary chat messages were held as "Delivery unconfirmed". The reader now uses writeSettlement when present and otherwise keeps the host's whole-write accepted/refused verdict, exactly as before this branch. Only prompt answers ask for provider settlement; ordinary callers (follow-up delivery, paste drafts, option commands, composer sends) are back on the original contract, so the legacy-handoff error class, its flag, the sequence-only send helper and the mobile handoff hook are gone. * Keep terminal-pane Escape on the plain accepted write Every pane's Escape/Ctrl+C goes through pty:writeAccepted. This branch had switched that IPC to wait for provider settlement, which dropped the "remount this pane" signal for a daemon session awaiting recovery and could stall later keystrokes behind a slow daemon acknowledgment. pty:writeAccepted is back to its original local-only, synchronous write. Prompt answers opt into settlement with requireWriteSettlement on the same channel, and a settled refusal while the daemon recovers now sends the same remount signal. Ordinary verified sends regain their original fallback write. * Report a partly accepted local paste as unconfirmed A settled local write split into chunks returned plain false when a later chunk was refused after earlier ones were accepted. Callers read false as "nothing was written", so chat showed "Message not sent" with a prefix already in the agent's input. It now reports the write as unconfirmed, the same verdict the paired host gives for a partial write. * Hide the chat composer under a prompt card instead of unmounting it When an approval or question card took the input region, the composer unmounted. A message still waiting for its Enter was cancelled and its bubble deleted after the draft had already been cleared, so the message vanished without a notice; composer history was also wiped each time. The composer now stays mounted but hidden while a card owns input, so its state survives. A send that has not submitted yet is still stopped (its Enter would answer the card), but its bubble stays with "Message not sent" so the text is not lost. The composer ref is detached while hidden, so root typing, paste and reveal focus never reach it. * Keep an answered prompt hidden after the chat view remounts The "answered" dismissal lived in component state. Toggling chat to terminal and back, a PTY reconnect, or leaving the phone session and coming back while the approved tool was still running brought the answered approval back, and it then took over the input again. Desktop now keeps the answered occurrence per pane outside the view; phone keeps it per chat tab outside the controller. Both still retire it when the pane observes the prompt clear or change, desktop also when the tab retires, and both maps are size-bounded. * Update the prompt-reply reliability gate for the review fixes Older hosts' accepted answers now dismiss like acknowledged ones, the composer stays mounted under a card, and answered prompts survive a view remount. The gate's invariant, oracle, assertion list, new test files and the two latest evidence runs now describe that contract. * Let users hide a prompt card, keep Escape from denying, and gate only Send on the phone The chat input could stay locked behind a card the host never closes (for example after a Deny typed in the terminal), and Escape on a focused approval card denied the tool even when the user meant to close a picker. - A Hide control (chevron) on terminal approval and question cards, desktop and phone, hides that prompt occurrence and gives the input back. It writes nothing to the agent and uses the same per-occurrence dismissal as an acknowledged answer, so a new occurrence shows the card again. - On desktop, Escape on a card now does the same Hide instead of Deny, and a card that appears while the user is typing no longer takes focus. - On the phone, a card blocks only Send: typing, dictation and image attach keep working on the draft. The placeholder is back to the normal one. * Fix two comments that still called older-host replies unconfirmed Since an older host's accepted write now counts as delivered, the requireWriteSettlement comment and the reliability gate's oracle said the opposite of the code. Both now describe the current rule. * Collapse prompt cards to a strip instead of hiding them, and close the round-2 gaps Hide removed a card completely, so nothing on screen said a prompt was still waiting, and several edges let the chat type into a live prompt. - Collapse (the header chevron, or Escape on desktop) folds the card to a one-line strip above the composer; the strip's chevron expands it back. Collapsing writes nothing, frees the composer, and is disabled while an answer is still being written. Each pane or tab keeps the occurrence as answered or collapsed, so a remount restores the same view. - Questions now carry the host wait's start like approvals, so an identical question in a new wait shows again (desktop and phone). A transcript-only prompt, which has no wait start, is dropped when the view stops observing it, and a transcript still loading no longer clears a dismissal. - Desktop: while a card owns the input, the hidden composer cannot send or interrupt even if it still has keyboard focus, and the card takes focus in the same commit. A send the card retires no longer types Ctrl+U under it. - Phone: an Ask hides the heuristic card read from the same waiting status, and the dismissal store is scoped by host, worktree and tab. * Keep a collapsed card's partial answer, and scope its focus to its own pane Collapsing a question card unmounted it, so expanding it again lost the chosen step, selections and typed "Other" text; Escape typed in that text field collapsed the card. A card arriving while the user typed in another surface (sidebar, notes, a browser URL bar) also took the keyboard. - The collapsed card now stays mounted but hidden (and inert on desktop) under its strip, on desktop and phone, so a partial answer survives collapse and expand. Escape inside the card's text field no longer collapses it. The question card shows the same focus ring as the approval card. - A card takes focus only from inside its own pane (its hidden composer) or from the page body, never from a text field elsewhere. - Desktop and phone share one dismissal store in src/shared, bounded by the existing scope-cache helper, which moves to src/shared with it. - The card send imports the verified helper from its own module, and the phone files are split so each name matches its contents (header action, strip, lane selector). * Return focus to the composer after a prompt card collapses Since a collapsed card stays mounted, Escape or the chevron left keyboard focus inside the now hidden, inert card. The composer's reveal-focus took that as focus already in the pane and stood down, then the browser dropped focus to the page body, so typed keys went nowhere. Reveal-focus now treats focus inside a hidden or inert subtree as not in the pane and focuses the composer. On the phone, collapsing a card also dismisses the keyboard so a hidden reply field does not keep it. * Keep the question card's collapse chevron beside its Cancel button The question card header spread its three items with justify-between, which put the new chevron in the middle of the header. The title now takes the free space, as in the approval card, so the chevron sits next to Cancel at the right edge. * Run the prompt tests on the merged main Main now runs Vitest under Bun, which resolves a long data: URL import as a package name, so the SSH delivery test loads its bundled mobile module from a temp file instead. The phone prompt harnesses mock the live line that main's view now renders, and add Platform, which main's text-selection helper reads, the same way main's own view tests do. |
||
|
|
ccab2a3110 |
fix(native-chat): a long reply keeps its Working bar and Stop after a reload, from the host's turn (#25751)
* fix(native-chat): the chat reads whether a turn runs from the host, not the loaded history page * fix(native-chat): the Working bar, card cancel and phone Stop follow the host's turn when its rows are not loaded * fix(native-chat): keep the phone's Stop as it was, naming the host's running turn Drops the conversation-wide phone Stop: the phone shows Stop only once a turn is running, so a Stop naming no turn could not be reached and only changed the ordinary Stop's request. Stop and card cancel still name the turn the host says is running. * fix(native-chat): the host's turn answer never outlives its host, and a half-loaded turn shows no edit totals - A batch carrying rows restates the host's newest turn record, so one without it (an older host after a downgrade) drops back to the loaded rows instead of holding the last answer; the event coalescer merges by the same rule. - A turn whose record is above the loaded rows is grouped only while it runs, and its loaded tail gets no "N changed files" total, which would undercount. - "Thinking" reads the host's running turn too, so it shows when the turn's record is not loaded. - The wire doc for observedAt says what the journal stores: creation time. * refactor(native-chat): totals per turn come from their own hook, keeping the transcript list under its line limit Also drops the chat zoom prop main removed from the unloaded live turn test. |
||
|
|
842c6c667a |
fix(jira): tolerate malformed nested Jira status fields (#25656)
* fix(jira): reject malformed nested status data * fix(jira): tolerate malformed status fields * fix(jira): tie status parsing to the shared types Allowed auth types and credential protections are coverage records over JiraAuthType and SecretAtRestProtection, and the parsed status must list every JiraConnectionStatus key, so a union or field change in the shared types fails tsc instead of being silently dropped by this parser. Adds a populated multi-site round-trip fixture and keeps connected statuses with a malformed viewer or sites value connected. * fix(jira): require every site and viewer field in status parsing Site and viewer parsing now lists every JiraSite and JiraViewer key, like the top-level status, so a new optional field fails tsc instead of being dropped. |
||
|
|
cad8ea4fa7 |
fix(tabs): stop tab and close tooltips firing while grazing the strip (#25808)
* fix(tabs): delay tab and close tooltips past grazing
Tab title and close-icon tooltips fired near-instantly while dragging
across the strip: Radix's provider-wide skipDelayDuration reopens the
next tooltip with no delay for 300ms after any tooltip closes.
Scope a nested TooltipProvider to the tab strip with a 700ms delay and
skipDelayDuration=0, so each tab tooltip waits the full delay.
* fix(tabs): drop close-button tooltip and lengthen tab tooltip delay
Remove the hover tooltip from the tab close button entirely (aria-label
keeps the accessible name), and raise the tab-title tooltip delay from
700ms to 1500ms so grazing or scrolling the strip no longer pops a label.
Add an interaction test proving the delay is enforced and Radix's
skip-delay is disabled.
* fix(tabs): delay the agent-state glyph tooltip to match the strip
The tab's leading agent-state dot renders its own feature tooltip with a
hardcoded 200ms delay, so tabs with a live agent state (working/done/
needs-attention) popped almost instantly while plain tabs waited the full
strip delay — the 'every other tab' inconsistency. Thread an optional
delay through StateIndicatorTooltip/AgentStateDot and pass the tab-strip
1500ms from TerminalTabLeadingIcon; other surfaces keep 200ms.
* fix(tabs): remove the agent-state glyph tooltip on tabs
Hovering a tab's status glyph (spinner/check/question) popped its own
tooltip after 200ms, so tabs with a live agent state popped while plain
tabs stayed quiet. Remove that tooltip entirely via title={null}; the
glyph stays self-describing with its aria-label, and the tab title keeps
its tooltip. Reverts the now-unused delay plumbing.
* reduce the time down from 1500 ms to 500 ms
* fix(tabs): restore the close-tab shortcut hint with a slower delay
Close buttons share one TabCloseTooltip that waits 1s, longer than the 500ms tab-title tooltip. Also merges the tooltip provider tests.
|
||
|
|
7faa9f7cd3 |
fix(native-chat): reasoning rows with a real open/finished state, and readable Claude thinking (#19221)
* fix(native-chat): render structured reasoning as collapsible messages
* fix(native-chat): align expanded reasoning with summary
* fix(native-chat): place reasoning chevron after summary
* De-emphasize reasoning headlines with observed timing labels
* Exclude later turn work from observed reasoning duration
* fix(native-chat): give reasoning rows a host-owned open/closed lifecycle
A reasoning row now says whether its block is still streaming (`state`)
and, when the host saw it end, when (`completedAt`). The row's own
start is its first-write time, so "Thought for N s" is measured on the
execution host instead of by whichever window happened to be watching.
Rows open with their first non-empty text and close on every path that
ends them: the block's final frame, a new message in the same stream,
every Claude turn end through one hook on the open turn, Codex
item/completed, turn and session settlement, active-item eviction, and
both host sweeps for a dead generation. Closing writes are lifecycle
writes so backpressure cannot leave a row open. Rows without the field
(older hosts, older journals) never read as live.
The reasoning row's headline reads the row's lifecycle and its own
turn's liveness: Thinking while open in a running turn, then Thought
for N s, Thought when no span was seen, Reasoning when the host kept no
lifecycle.
* feat(native-chat): ask Claude for readable thinking summaries
Under Orca's launch the Claude CLI streams thinking blocks with empty
text, so no reasoning row ever had anything to show. Pass only
`--thinking-display summarized`: it fills thinking blocks with the
API's summaries without turning thinking on, so a user who disabled
thinking keeps it off.
Orca runs the user's own binary, and a CLI older than the flag exits on
it before the session starts. The launch probes the binary it is about
to run, overlapping the rest of launch resolution, and passes the flag
only when that probe has already answered with 2.1.94 or newer. A slow
or failed probe never delays a launch and is never remembered; a
successful one is kept per binary until the binary changes.
* test(native-chat): type the superseding send in the reasoning lifecycle test
* fix(native-chat): stop reading an ended reasoning row as thinking
The spinner line infers "Thinking" from the newest root row being a
reasoning message. With summaries on, a closed reasoning row stays the
newest row while Claude streams a tool's input, so the line read
Thinking for the whole Write. A reasoning row now counts only while its
own state is running; a row from a host that keeps no state reads as
before.
* fix(native-chat): measure reasoning from the block's start, not its first text
A row opens with its first summary text, which trails the thinking
block's start by seconds, and the journal stamps a row with its queued
append time. So "Thought for N s" read 1 s for blocks that ran 4.65 s
and 12.95 s. The block registry now records the host time at each
block's content_block_start (else its first delta), and every write of
that block's row carries it as the row's observed time; the close keeps
the frame's own receipt time. Codex reasoning rows likewise carry their
item/started time.
* perf(native-chat): stop rewriting the reasoning row for every thinking token
Claude sends a thinking_tokens frame after every thinking delta. Every
frame the stream path did not consume forced the streamed text out
first, bypassing the checkpoint widening and the coalescing window, so
a single thinking block was rewritten and republished once per token:
212 full-row writes and 258 KB for one captured block. A frame that can
write no row (token tallies, stream deltas no registry carries, pings)
no longer forces that flush; every frame that can write still does.
The same block now takes 14 writes.
* fix(native-chat): time Codex reasoning by receipt and close evicted rows
Codex summary text lands 13-50 ms before item/completed, inside one
coalescing window, so a reasoning row's first write can be its
completion. Item boundaries are now stamped with their host receipt
time the way turn boundaries already were, so a retried or buffered
delivery keeps it, and the item translator falls back to the host
clock rather than Date.now(). The row carries its item/started time on
every first write, including the completion, so its span is
item/started to item/completed.
An evicted active item is now closed from the text streamed so far,
like both settle paths, and the eviction runs before the incoming item
is tracked: tracking first let the stream bound drop the evictee's text
before the eviction could close its row, stranding it running.
completedAt now has one meaning everywhere: the host time the message
was seen to end, or the end of the turn or stream that cut it off;
absent only when no end was seen live.
* refactor(native-chat): keep the Codex streaming body translation pure
The streaming translation preserves a reasoning body as-is again; the
stream writer, which is what knows the item has not completed, stamps
it running.
* test(native-chat): keep a re-collapsed reasoning row collapsed through a revision
* fix(native-chat): estimate a collapsed reasoning row as its trigger
A reasoning row renders collapsed, as one small button, but its height
was estimated from its full text: a 4,129-character summary reserved
about 950 px for a 24 px row, so long chats jumped as rows were
measured. It is now estimated at the trigger's height; opening the row
remeasures it.
* fix(native-chat): probe the CLI the launch will run, and learn from a refusal
The thinking-display gate probed `claude --version` with Orca's own cwd
and env, while the launch spawns with the workspace's cwd and the shell
env. Behind a version manager's shim those can pick different CLIs, so
the probe could approve a CLI the launch never ran, and an older CLI
exits on the unknown flag before the session starts. The probe also
only counted if it had already finished when resolution did, so a
first launch, or the first after a CLI update, usually went without
the flag.
The probe now runs with the launch's own resolved cwd and env, is keyed
by the binary and the workspace, and a launch waits up to 200 ms for it
(about 3x the probe's measured p95) before going without the flag. Only
answers are kept, so a slow or failed probe is asked again next launch.
A child that exits with commander's "unknown option '--thinking-display'"
marks that binary in that workspace so the next launch skips the flag;
that one start fails exactly as any CLI startup failure does today.
* test(native-chat): type the thinking-display probe mock with both of its parameters
* fix(native-chat): recheck the account switch after the probe, last as before
Moving the invocation ahead of the probe, the transcript check and the
permission mode put its account-switch recheck before those awaits, so a
switch that began during them launched unchecked. The invocation is the
last await again. The probe gets its own env from the same sources the
launch uses, the inherited env and the overlay with the CLI's runtime on
PATH, built by the same code, minus every credential: asking a CLI its
version needs none.
* fix(native-chat): wait up to 1.5 s for a cold probe, and remember every outcome
200 ms only covered a warm CLI; a cold disk, a node install or an
antivirus scan exceeds it, and that chat's child then ran its whole
life without summaries. A launch now waits up to 1.5 s, once per binary
per workspace, measured from when that binary's probe began, so a later
launch never waits again on a probe already past it. The probe gets its
own 10 s kill timeout, and every outcome, including no version printed,
a failure or a kill, is kept for the binary's life, so a probe that
hangs costs one launch rather than every one. A refusal seen while a
probe still runs wins over its late answer.
* fix(native-chat): leave Thinking to the activity line while reasoning runs
A reasoning row still being written drew a pulsing "Thinking…" header
right under the turn's activity line, which already says Thinking: two
live indicators for one fact. In a running turn an open reasoning row
now draws nothing and reserves no height; it appears when it closes, as
"Thought for N s". A row from a host that keeps no state, a closed row,
and a row left open by a turn that ended draw as before. The row's
Thinking headline is gone with its catalog key.
* fix(native-chat): end every unfinished Codex item through one rule
A reasoning completion with no text of its own left the row its stream
wrote running for good: the completion translated to nothing and the
item left the active set, so no settle could reach it. It now closes
from the text streamed so far.
Settlement, eviction and that completion now build an unfinished item's
row through one choice (the streamed text when there is any, else the
item as it started) and end it through one rule. An evicted file change
with streamed tool output no longer keeps that output as its patch; it
reads as interrupted, as a settled one does. A completion whose start
was never recorded claims no span, so it reads "Thought".
* fix(native-chat): catalogue Claude's stream keep-alive as benign
An uncatalogued `ping` stream frame classified as substantive, so the
fallback wrote a visible "claude · message:stream_event:ping" row, and
since such frames no longer force streamed text out first, a ping
inside a coalescing window landed above the open reasoning row. A ping
is now benign: it writes no row.
* fix(native-chat): bound finding the binary by the probe budget, and keep it LRU
Resolving the command's real path and its mtime was awaited before the
budgeted wait, so a slow filesystem could hold a launch indefinitely;
it now counts against the same budget, and running out caches nothing.
The cache is least recently used rather than first written, and holds
32 binary-and-workspace entries rather than 16.
* feat(mobile): collapse reasoning rows the way desktop does
With summaries on, every Claude turn now carries reasoning text, and the
phone drew all of it inline, dimmed, between the prompt and the answer.
Mobile now draws a reasoning row as desktop does: collapsed to "Thought
for N s", "Thought" or "Reasoning", its text mounted only once opened,
and nothing at all while the row is still being written in the live
turn or has no text. The headline and the visibility rule live in one
shared module both clients read, so they cannot drift.
* fix(native-chat): let a failed CLI probe heal instead of latching
A probe killed at its timeout, failing to spawn under a loaded boot, or
printing no version was cached as "no flag" for the binary's life, so
that workspace never got summaries again in that run. Only a version
(either side of the floor) or the CLI's own refusal is kept for good
now; a probe that gave no version is kept for 10 minutes, so a hung CLI
still costs one wait per stretch and a boot-time failure heals.
* docs(native-chat): say exactly what the version probe's env leaves out
* fix(native-chat): record a Codex item's start whatever its first frame carried
The start was recorded only for an item/started that wrote no row, so a
reasoning item that started with text lost it and its completion
claimed no span. Every tracked item/started now records its receipt
time, and the started write carries it too.
* fix(mobile): label the reasoning toggle and give it a full touch target
The toggle now tells a screen reader what it is, "Reasoning: Thought
for 3s", as desktop's prefix does, and reaches a 44 pt target. The
shared English copy stays private to the module that formats it.
* refactor(native-chat): build reasoning rows from one provider-neutral helper
Claude, Codex and the terminal sweeps each built the reasoning row body and
its running/ended stamp themselves. They now share journal-reasoning-row:
blank text journals no row, text is bounded the same way, and an end carries
completedAt only when the host saw it.
* refactor(codex): move the active journal item type into the contracts file
codex-unfinished-item-body imported the type from the settlement module,
which imports values from it.
* test(claude): read the launch PATH the way Windows spells it
* test(claude): compare the probe's PATH to the launch's without Orca's CLI dir
When the CLI's directory also holds node (Linux CI's /usr/local/bin), the
runtime pairing puts that directory first, ahead of the Orca CLI directory the
launch adds, so the launch PATH no longer ends with the probe's. Both still
resolve the same claude and shims. The test now checks that exactly, for a CLI
with and without a sibling node.
* feat(native-chat): lead the reasoning row with a brain glyph in the tool-row column
* fix(native-chat): route the reasoning glyph through the shared icon names, keep its chevron findable, and match it on mobile
* fix(mobile): keep the long-press actions sheet on reasoning rows for Android
* fix(native-chat): forward every exit argument through the thinking-display connection wrapper
* refactor(codex): keep the receipt-timed notification methods with the event they stamp
* feat(native-chat): read an open reasoning block through the one live "Thinking" line
While the agent's open reasoning block has text, the turn's live activity line is its
disclosure: collapsed by default, expandable to the live text (capped and scrollable), and
the block's row draws nothing meanwhile. When the block ends, its row appears in place,
open if the reader opened it live, because the line and the row read one disclosure key.
Which block the line discloses is derived from the line's own render condition, so a row
is never hidden while nothing on screen shows it; any other open block (a subagent's, or
one a prompt pushed off the line) draws as "Reasoning". Desktop and mobile alike; no host
or wire change.
* fix(native-chat): a slot kept for its turn bar or diff rollup no longer draws its message
The transcript row drew the message of every message slot, while the slot builder pushes a slot
for a row it declined to draw whenever that row also carries its turn's bar or diff rollup. So the
open reasoning block the live line discloses still drew as a "Reasoning" row when it was a
provider-opened turn's first row or the last row of a turn that changed files, and one click
opened both. The builder's decision now travels on the slot (`drawsMessage`) and the row draws
only the bar and rollup when it is false; the row-level `folded` guard it made redundant is gone.
* refactor(native-chat): draw the live line from one shared value, with one live region
Desktop and mobile now render the live activity line from one pure function,
`nativeChatLiveLine`: whether it draws, what it says, and the open reasoning block it
discloses with the text it has so far. The open block's row is hidden from that same value,
so desktop no longer restates the line's render condition beside it, and the lines no longer
re-derive the text.
The line keeps one element, and so one live region, through every state; only its trigger
and body come and go, so a screen reader hears "Thinking" and the label after it. On mobile
the live text gets the finished row's Android long press (copy or select through the message
actions sheet), the line's touch target is the row's 44 pt, and its label and body sit in
the finished row's column so nothing moves when the row takes over.
* fix(mobile): keep the live text's actions sheet on the block it was opened for
On Android the sheet opened by a long press on the live reasoning was a flag gated on a live
block: it vanished when the block ended, mid Select text, and the stale flag reopened it
unprompted on the next block. The sheet now holds the message it was opened for.
* test(native-chat): the reasoning body owns its tone, live and once landed
Rendered QA on a pre-merge build showed the live line's open reasoning in full foreground and the
landed row's in muted text, so it dimmed as the block landed: the body set no colour of its own and
inherited one from wherever it was mounted. Since the main merge (
|
||
|
|
5cf3585b78 |
fix(native-chat): keep a message accepted before a quit or crash as a held card (#24660)
* fix(native-chat): keep a message accepted before a quit or crash as a held card A send the host accepted while the agent was still starting, and never handed over, was rejected unseen at quit or at the next open after a crash. The next open now keeps a person's message (typed, or a launch's first prompt) as a waiting card at the head of the queue, held until Resume, Send now, Edit or Delete; quit no longer rejects it. Each submission records its source so a restart knows which leftovers to keep. A direct send's replay answers from its own record, never the queued arm. Cards shown without the queue capability hide Turn off queueing and the steer chord. * fix(native-chat): keep an unsent message as a held card at every close, not only a restart The host now keeps a person's message it accepted and never handed over with one rule wherever it can no longer hand it over: a quit or crash (settled at the next open) and a close of the chat (tab close, worktree teardown, orchestration stop). - The hold is card state: a new per-card hold_reason 'kept', published as the existing pausedReason, instead of a fake host_instance value. host_instance means the owner again, and the pause clause, adoption filter and /clear carry special cases are gone. A kept card holds the cards behind it until the person sends, edits or deletes it; a send_failed card still does not. - A card hand-off rejected by a restart or a close returns as a kept card (rejectedDraftSettlement), so a person's next message can never release it. - One hold function, parameterized by cause (hostRestarted / chatClosed), replaces the close path's plain rejection. - The phone shows published cards and per-card holds whatever the queue capability says; only queueing a new send stays gated. - source gains 'dispatch' for the orchestration preamble (still rejected); an unknown source is kept as written and never takes the legacy rule. - Kept cards from earlier settlements stay ahead of a batch's new ones. * test(native-chat): the dispatch preamble records its source * fix(native-chat): skip a kept card like a failed one, and make a quit leave the queue as a crash does - A kept card is held on its own, as a send_failed one is: the queue sends the cards behind it, and the "a message ahead needs attention" caption no longer appears behind it (desktop and phone). - Quit disposes the queue's drain together with delivery, so it mints no hand-off that only the next process could settle. - Only a Send the person asked for (origin client) that a restart or close cut short returns kept; the queue's own hand-off returns where it stood, under the restart's pause, as on main. - Comments that said only a capable host gets cards or the card actions now say the capability gates only queueing a new send. * refactor(native-chat): one dispose gate in the queue drain's step * test(native-chat): the downgrade test names the older build's /clear exception * fix(native-chat): re-check the drain's quit gate right before it appends a hand-off A drain step already past its first check when quit begins no longer makes a hand-off. Adds regression tests for that, for Send now on an ordinary card cut short by a quit, and for the open-time repair deriving kept from the hand-off's origin; fixes the pause and settlement comments that called a kept card one the queue never passes. * fix(native-chat): a card held on its own starts no restart pause for the others After a second restart a kept (or send_failed) card is another process's, but it waits for its own Send, so it no longer pauses every other card under "Queue paused because Orca restarted". * fix(native-chat): record on a kept send which card holds it, so no trace survives an Edit or Delete The rejection row of a send the host kept as a card now names that card (`keptAsQueuedMessageId`), in the same transaction that writes the card, and the fold publishes it on the submission. The shared projection draws such a send only as its card: once the card is sent, edited or deleted, neither the send nor the sending desktop's local copy of it shows. * chore(native-chat): leave the unused submission schema as main has it No client parses published submissions with it (they arrive as typed frames, and the host's history pages carry the field, as the Edit/Delete test reads); listing the field put the file over its line budget. * fix(native-chat): retire a kept send's local copy instead of only hiding it The outbox reconcile and the send disposition drop an entry whose submission the host rejected as kept as a card, as they already do for a Stop's withdrawal, so the copy never comes back as "Not sent / Retry" once the submission falls out of the loaded page. The projection reads the reconciled outbox, so its separate filter goes. * test(native-chat): move the queued-message rig's scripted provider into its own fixture The rig fixture grew past the 300-line limit once main's changes merged in. * fix(native-chat): hide a kept send by its own record, not by its card still existing The transcript hid a rejected send while a queued card held it under its id, so the card's Edit or Delete brought back a "Not sent" row. It now reads the send's own keptAsQueuedMessageId, which the host records with the rejection, and the live card list is no longer threaded to the transcript or the delivery notices. * refactor(native-chat): move a sent message's row writes into their own journal collaborator The journal store went past its line limit once main's ledger receipt joined this branch's transaction hook. The submission and dispatch-transition writes, and what commits in their transaction, now live in JournalSubmissionWriter; the store's methods delegate to it unchanged. * fix(native-chat): list a kept send's card id in the submission schema The schema drops keys it does not list, so a reader that kept a parsed submission would lose keptAsQueuedMessageId and source, both persisted with the row. The submission schema and the failure fact it shares with item bodies move to their own modules, with room for both fields. * test(native-chat): match the transcript and outbox hook signatures main and the swap changed * test(native-chat): import the journal types once in the queue-delivery test * fix(mobile): a resend the host kept as a card shows no error and returns no text The host answers a resend of a message it kept as a card with that message's rejected submission, marked keptAsQueuedMessageId. The phone read it as any rejection: "Message not sent" and the text back in the composer, while the card showed the same text. It now answers like a queued send, as the desktop's send disposition does: the id is spent, no error, and the card holds the text. * test(native-chat): pin that quit's first step stops the queue's hand-off Quit now stops delivery, the queue drain included, at its first step (stopDelivery), before teardown drains recovery. The drain-step quit test runs from that step as well as from the flush. * test(native-chat): give cards their source and store unknown sources as another build would #25078 made a card's source required, so the tests that insert a card pass the person's. The hold's unknown-kind and unreadable-source cases now rewrite the stored row the way a newer build would leave it, instead of casting a type. * refactor(native-chat): stop delivery and the queue drain in one line at quit Main's #25159 left the host at its line limit; quit's stop now disposes both in one expression instead of a block. * test(native-chat): hold the drain step without reading the call stack Bun formats a method's stack frame without its class ("at step"), so the quit test's caller check never matched, the step was never held, and both cases timed out once CI ran Vitest on Bun (#25840). Only the drain step heals owed queue bookkeeping, so the hold needs no caller check. |
||
|
|
b75213100b |
Add a Chat settings page for structured native chat (#25685)
* Add a Chat settings page for structured native chat * Remove unused chat appearance summary translations * Fix chat settings search entries and preview anchor * Keep shortcut formatting available in settings sidebar tests * Clarify the Chat settings page description * Update Chat settings test and remove unused summary alias |
||
|
|
3ec38b8c6d |
Run Vitest on Bun with Node runtime contracts (#25840)
* Run Vitest on Bun while preserving Node runtime contracts * Preserve runtime timing provenance and keep the Bun pin in config * Scope builtin compatibility mocks to test-only lint exceptions * Give capture retention fixtures distinct filesystem timestamps * Await the copy button success state in the React fixture * Bound Node test worker shutdown and tighten migration fixtures |
||
|
|
5b8a982f8f |
Fix private Linear images in task descriptions (#25849)
* Fix private Linear images in task descriptions * Strengthen Linear image signing validation and typed access |
||
|
|
768c1ea967 |
Add confirmed conversation rewind from structured chat messages (#19338)
* feat(native-chat): confirm destructive conversation rewind from user messages * fix(native-chat): retain rewind uncertainty from host status * fix(native-chat): await the authoritative rewind epoch reset * Pause restored chat outbox during rewind recovery * Verify rewind targets after message acceptance * test(native-chat): verify rewind copy against host reason contract * fix: type rewind hook test props explicitly * Show rewind explanations on keyboard focus * fix(native-chat): read renamed background stop-all capability * fix(native-chat): consolidate epoch test vitest imports * fix(native-chat): restore rewind refusal delivery after merge * fix(native-chat): satisfy main's design-system and cast gates in rewind Match the rewind button to the copy button beside it instead of restyling the shared Button, read refusal reasons without a cast, and merge a duplicate import. * fix(native-chat): let the host govern an in-doubt rewind; offer Edit from here only on turn openers - Pending starts after Confirm and ends with the request; a confirmed rewind waits up to 120 s for its new conversation, then lets go with one toast. - An unknown outcome no longer blocks sending: the host recovers it on the next send. Its latch only disables the action. The message goes back to the composer on success and on an unknown outcome; refusals and timeouts are toasted once instead of holding the error line. - The action is offered only on sent prompts that opened their own turn (no steers, unsent, queued, /compact or goal rows), and not at all where the provider cannot rewind. - Rewind is held behind host-queued cards, gets the command's 195 s remote timeout, focuses the composer after returning the message, drops the message count, and uses plain copy. * fix(native-chat): say why a send waits while a rewind is in flight * fix(native-chat): settle an in-doubt rewind on the next send, even with the agent running - Host: a send (or /clear, /compact) that finds a rewind in doubt with the agent already running now runs the same provider recovery an attach would. Proven: the conversation is replaced and the send proceeds; proven not done: the record is refused and the send proceeds; still unknown: the send is refused as before, but before the ledger records it, so a Retry of the same id is decided afresh instead of replaying the refusal for a day. - Client: a confirmed rewind whose pane was hidden during the wait lets go silently (a hidden pane reads nothing); the timeout and busy copy say what happened and what is needed. * fix(native-chat): keep /clear's settled refusal while a rewind is in doubt; type the live-send test fixture * test(native-chat): give the live-send rewind host the declared agents main now requires * fix(native-chat): offer Edit from here only where the next send settles a rewind, and never on URL images - New runtime capability agent-session.rewind-send-recovery.v1, advertised by this host: the rewind UI hands an in-doubt prompt back for the next send to settle, which hosts with only the rewind RPC refuse while the agent runs. The action is hidden until the host (local or remote) advertises it, and while that is unknown. - A prompt with an image that has no local file is not offered: the image could not go back to the composer after the rewind discards it. - Row and provider eligibility move to native-chat-rewind-eligibility.ts. * fix(native-chat): keep when each kept message was first seen across a rewind A rewind rebuilt the provider's kept items with the rewind's own time, so every kept message showed the rewind time. The merge already carried a held row's scope and producer onto the provider item; it now carries its first-seen time too, for the rewind and its recovery alike. * fix(native-chat): hand rows the rewind through context so the list and pane stay within their line budgets after main's appearance changes * fix(native-chat): fit the rewind recovery capability within protocol-version's line budget; hoist a test context value * fix(native-chat): label the action "Rewind to here" --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
d9cf07d3f4 | Fix pinned workspace reveal expanding other hosts (#25836) | ||
|
|
4de9f9f85a | Move orchestration capabilities out of protocol version registry (#25839) | ||
|
|
ee917205bd |
fix(native-chat): a chat you return to drops background tasks that finished while it was hidden (#24305)
* fix(native-chat): a chat you return to drops background tasks that finished while it was hidden A background command that finished while its chat pane was hidden stayed in the strip as running, its timer counting for hours, and Stop failed with "The background task wasn't stopped." The pane stops listening while hidden, so it missed the "no tasks" update; once the idle sweep stopped the agent, the host reopened the pane's subscription without any roster at all, and the client reads a missing roster as "unchanged". The roster now rides each subscriber's frames the way the slash-command list and the message queue already do. The subscriber registry reads it from the host's child records on a subscriber's first frame and on every snapshot and reset, and re-sends it to each subscriber whose last copy differs when the records change. The channel always answers: a conversation with no running child is "none" whether or not an agent holds it. Ordinary journal batches never read it. This also fixes the reverse case: a snapshot or reset sent to a live pane carried no roster, which cleared a still-running task from the strip until the roster next changed. The client treats a resumed subscription's first batch as stating the roster, so a pane reconnecting to an older host that still omits it drops its stale copy too. Fixes #24227 * refactor(native-chat): require the background-task roster read and drop the unused close observers The host's delivery layer now requires every hook it is built with, so production wiring cannot leave out the roster read (an opening frame without it reads as "no tasks" to current clients). The conversations map's close observers lost their only caller in this PR and are removed. The reducer test comment states the old-host omission rule precisely. * refactor(native-chat): keep structured-agent-session-host.ts within the line limit Main brought the host file to the 300-line lint limit, and this PR's roster wiring adds one line. Name the lease reconciler's type by its factory, as the neighbouring fields do. * test(native-chat): build the roster test's Claude provider handle with claudeProviderHandle Main made the provider handle opaque (#24991); build it with the existing helper, as main's own tests now do. |
||
|
|
670c59d23a |
Translate ACP traffic into shared timeline events (#25090)
* Add standalone ACP protocol client and session runtime
* Protect ACP transport teardown from late stream errors
* Retire incoming ACP request ids before publishing responses
* Narrow ACP configuration requests and transport message types
* Remove redundant ACP request handler return unions
* Keep ACP waits caller-owned and preserve protocol extensions
* Preserve open ACP decisions through prompt completion
* Move the turn message ordinals and the turn-row revision to the neutral timeline folder
Pure moves so a shared timeline assembler can use them: Codex's message
ordinal counter becomes ProviderTurnMessageOrdinals and Claude's turn-row
revision becomes the provider-neutral agent-journal turn-row revision. Only
names and import paths change.
* Admit one provider event's writes as one transition, and let rows be found again after a restart
- A sink transition is admitted whole or not at all; its steps run back to
back at their turn in the journal's write queue, and each resolver reads
the fold with every earlier write landed. A resolver may also say where the
row belongs (turn scope, provider reference), and the writer always hears
how the transition landed. A resolved lifecycle batch chooses its
settlement mutations from the fold at execution.
- New optional row field providerItemRef: the provider's own reference for
the item a row is, written only where the row's identity cannot spell it
(Codex keys messages by their place in the turn and renumbers its item ids
on resume). Set by the creating write, kept by revisions, indexed by the
journal fold, never read by clients. A downgrade test shows an older host
and client render such rows unchanged.
- Provider timeline identity schemes (shared legacy arm, Codex) and the join
index that resolves a provider item to its row from memory or the fold:
ordinals and request incarnations are read back from the rows, so a
restart or an evicted entry finds the original row instead of placing a
new one.
* Recover message ordinals from the journal's highest place, and forget joins read from a replaced epoch
A fresh join index continued a turn's messages at the first free place, so a journal holding only a
later ordinal (an imported or removed earlier row) had its sequence back-filled. The place is now
one past the highest ordinal any row or echoed send holds there, read through a pure scheme reader.
The join caches also drop what they read when the journal's epoch is replaced.
* Spell the subagent thread's message slot without spreading an identity union
* Add a provider timeline grammar and a shared assembler that decides at its turn in the journal
Adapters translate their provider's dialect into a small grammar (turns,
items, streamed text, requests, context facts, session end/reset); one shared
assembler turns it into the journal rows every structured lane writes.
Each event is planned as one sink transition. Which row a write lands on,
whether a replay writes anything, and every change to what the assembler
knows (its ledger) are decided by the transition's resolvers at the event's
turn in the journal's write queue, against the fold as it stands then. A
forecast (the ledger plus admitted events still queued) only answers apply()
at once. So a refused event allocates nothing, a write the journal rejects
leaves no trace in memory, and a restart or evicted cache finds the same rows
again. Text and full snapshots of one provider item share one row and one
lifecycle; reset always flushes text and settles the old session from the
journal; the open-work budget is derived from what is actually open.
Codex migration contracts compare against the existing Codex translator,
including a restart mid-stream and a repeat that outlives the join cache.
* Fix the types and the exhaustive event switch CI reported for the assembler
* Let the journal decide stream lifetimes, request reuse, named sends and background work
A third review found two blockers with the earlier rounds' cause, a remembered interpretation
trusted after the journal moved on:
- A reused request id was judged by its earlier prompt's settled turn before asking which turn the
new one lands in, so a real approval in a later turn was dropped. The target turn now decides:
the old turn again is a replay; a different live turn opens the next prompt beside it.
- A text stream checked its row's turn only on its first write, and a turn's end released streams
by the turn planning expected. Every write now checks the row, a turn's end stops the streams
whose rows are in it, and turn status reads the journal first, so another writer's Stop wins.
Also: a message boundary drawn by an event the journal held as a replay no longer splits an
anonymous message; a send naming a turn not yet open waits for that turn; the budget charges a
stream's thread and turn strings and the turn caches are byte-bounded; the open turn ends when the
journal shows it settled; a turn's opener is read from the journal's row.
Background work is now Orca's existing background-task row instead of a tool call flagged
`outlivesTurn` (a flag remembered only in memory, so a restart failed the task). A turn's end
never settles that row, so it survives restarts; session end leaves one in flight unverifiable.
Three tests that opened a background tool call with `outlivesTurn` now open a background-task row
and keep their original expectations about which turn the row stays in.
* Type the unbound assembler helper's drain as the void it reports
* Bound the rows kept for a continued anonymous message and the stopped streams
A row kept for the anonymous stream that may continue it, and the marker that a stopped stream's
queued writes write nothing, lived in the live-stream map and were never removed when no stream
followed. They now live in their own bounded maps, so the live map holds open streams only.
* Translate ACP session traffic into shared timeline events
* Preserve fixture answer linkage and handle all typed ACP updates
* Keep replay chunks and long-turn tool snapshots bounded
* Forward ACP joins to the journal-derived timeline assembler
* Use exact ACP prompt identity and recover incomplete timeline replay
* Detect short and Unicode home paths in encoded ACP fixtures
* Use generic privacy patterns for ACP fixtures
* Translate Grok background work into durable task rows
* Recover background task origin from its persisted launch tool
* Restate background task fallback from its terminal evidence
* Require session identity on Grok task notifications
* Honor explicit Grok task completion without an exit code
* Match the background reset test to the final timeline grammar
* Give task completion fixtures a specific evidence type
* Settle ACP background tasks from live and replayed evidence
* Honor background task outcomes carried by replayed tool results
* Keep the journal store under its line limit after the main merge
* Drop the provider item reference, join index and identity schemes from the transition PR
Nothing in production reaches the state they defended (an assembler that lost its memory while
its child keeps streaming the same turn), and the stored Codex id was positional. The legacy
identity scheme moves to the assembler PR with its first caller; the Codex scheme and any
persisted reference wait for Codex to move onto the assembler. The Codex ordinal counter goes
back to codex/, since no neutral code imports it.
* Write a resolved settlement in one transaction through enqueueRows
A settlement too large for one row now commits all its rows or none, through the journal's
existing all-or-nothing write, instead of a row-by-row writer. Every row is built before any
commits, so a settlement naming one item twice is refused before anything is written.
* Drop the transition's landing report; keep the turn-row write fire-and-forget
Nothing reads which steps of a transition wrote. A failed step fails the sink, leaving the
steps before it written; the header says so, and tests cover it plus a settlement whose second
row fails inside the transaction. writeAgentJournalTurnRow returns nothing again, as on main.
* Generate open ACP enums and check the generated schema offline
A newer or vendor enum value (tool kind, tool status, option kind, stop
reason) no longer fails the whole message: generated enums accept the known
literals plus any other string, typed so callers can still narrow on the
known ones. The generated header now records the pinned input digests, the
generator digest and a body hash, so `verify:acp-protocol` catches a stale or
hand-edited file without network access; it runs in lint and the PR workflow.
* Land the ACP runtime contract the agent adapters use
- Deliver notifications other than session/update through
onExtensionNotification, in arrival order with session updates.
- Accept _meta on prompt, setMode, setModel, setConfigOption and cancel.
- cancel() always sends session/cancel once the session runs, since the
agent can be in a turn it began itself; only a successful send is shared,
so a failed write is retried.
- Cancel aborts each open agent request's signal and lets its handler send
its own answer; -32800 only when the handler rejects.
- Permission requests validate only the session, tool call id and options;
unreadable fields are dropped with a diagnostic, and any answer Orca
cannot send is `cancelled` instead of a JSON-RPC error. Agent-started
turns may ask; whether to show it is the caller's decision.
- AcpAgentError marks the agent's own errors; AcpInvalidResponseError keeps
the raw answer and validation issues for answers Orca could not read.
- Lines over the size limit are classified by prefix (shared with the Codex
reader): the owed request fails, an oversized agent request is answered
with an error, and an unattributable response closes the connection.
* Run a transition's steps as a prefix; drop the paced flag and resolved options
A failed step no longer lets the steps after it write: each step checks, at its own turn in the
journal's queue, whether the write handed over just ahead of it completed, using the queue's count
of completed write bodies (a promise would report the failure only after the next step ran). The
sink fails only once every step has had its turn. The item step's `paced` size bypass and the
resolver's replacement `options` are removed; nothing planned uses them.
* Rebuild the timeline assembler on one admission-order state
The assembler kept a second copy of its state (a forecast beside a ledger), hydrated
the open turn from the journal, and recognised replays, all to recover from losing its
memory while the provider child kept streaming. That never happens: one assembler lives
exactly as long as one child, and a new child is a new assembler in a new generation
whose events land after the dead-generation sweep.
- One state, changed only when the sink admits an event (minted keys included), so a
refused event takes nothing.
- Every journal-dependent choice is made when the write runs, by keyed reads: a turn row
is written only where none is, a stream checks its row's turn on every write, a running
snapshot never lands in a settled turn or relights a settled tool, a request takes the
first incarnation the journal holds no row for.
- Rows are found by spelling their ids (provider-timeline-rows.ts); no join cache.
- The identity scheme (legacy arm only) lives here with its first caller; requests are
spelled in their acquisition generation, since JSON-RPC ids restart per process.
- Saved history goes in as `input.history` plus ordinary events with the provider's ids,
into an empty journal; `session.reset` and every replay rule are gone.
- One terminal-body function (`terminalAgentJournalBody`) is shared with the
dead-generation settlement.
- The test rig's restart now sweeps and starts a new generation, as production does.
* Answer every agent request after an ACP cancel
A cancel that lands before a permission handler starts now still runs the
permission path, so the agent gets the `cancelled` outcome rather than a
request-cancelled error. A handler that ignores the abort no longer leaves
the agent waiting: once the abort has run through, any request still
unanswered gets request-cancelled. Handlers that answer on abort keep their
own reply.
Also renames a lint-rejected helper parameter, replaces a Reflect.apply in a
test, and stops the permission diagnostic from firing with an empty list.
* Cover new running work in a turn the sweep ended
* Run a transition's steps in one queued write that loops over them
The steps of one event now share one turn in the journal's write queue: a
loop writes each in its own transaction through the row writer's
synchronous writeRows (split out of enqueueRows) and stops at the first
throw. Prefix semantics and "nothing lands between the steps" now hold by
construction, so the completed-write counter on the queue, the step gate
and the allSettled barrier are gone; the queue is back to main's bytes.
* Let each ACP request handler own its answer after a cancel
Removes the next-event-loop-turn fallback that answered request-cancelled
for any handler still silent after a cancel. It raced answers that were
still being saved (an approval mid-journal-write reached the agent as an
error) and made the outcome depend on event-loop timing. The handler that
owns an agent request now always sends its answer, or throws for
request-cancelled; a request it never answers ends when the connection
closes. A permission whose handler had not started still answers
`cancelled`.
* End a turn another writer settled the way the provider's end does
A person's Stop settled the open turn's row without a word to the assembler.
The assembler then forgot the turn: its running tools and pending prompts were
never settled, the provider's own end and withdrawal were dropped, and the
turn's text streams stayed counted against the open budget for the life of the
process. Text the provider kept streaming afterwards could land as a message
outside the stopped turn.
- The open turn the journal shows settled ends first, as one transition, through
the same settlement the provider's turn.end plans; its streams stop and their
keys drop later text until that turn's end or the next turn opens.
- turn.end and request.withdrawn are admitted for a turn or request the journal
holds; their settlement writes nothing for rows already settled.
- The budget's re-check frees streams whose turn settled.
- A settled tool keeps its terminal body against any differing write.
- Session-end settlement of lost background work uses the journal's own
lostLiveWorkJournalBody instead of a copy.
- The rig's window elapses before every read, and restart swaps and disposes
the old assembler.
* Adopt ACP load history only into an empty journal, chosen by the translator's creator
The translator now takes `adopt` from whoever creates it instead of guessing from the
journal (which reads empty before the sink binds). Without adoption, history a provider
replays during session/load is dropped except its context usage. With adoption, history
becomes ordinary events with provider or position ids and the user's saved messages become
`input.history`, so re-running an interrupted adoption lands the same rows.
The translator no longer reads the journal: it lives exactly as long as its provider child.
Its tool snapshots are bounded by the assembler's open budget, and a small record of each
tool's turn keeps a late background-task notice beside the tool that started it.
* Leave a stopped turn's running tools to the agent's own end
When another writer settles the open turn (a person's Stop), the assembler
now only stops that turn's text and cancels its pending prompts. Running tool
calls stay the agent's: a progress update or completion it reports after the
Stop lands as reported, and whatever is still running settles at the agent's
turn end for that turn, the next turn's open, or the session's end.
An agent's end for an earlier turn while a newer one is open no longer clears
the open turn's activity line or ends its anonymous reply. An unnamed end right
after a Stop ends the stopped turn instead of being dropped. The test rig's
restart no longer writes the dead assembler's window text, matching dispose.
* Pin that a stopped turn's running tools hold budget until the agent's end
* Type the stopped turn's tool progress update as a tool body
* List every event the assembler hands to the decision step
The type-aware lint requires an exhaustive switch with no default case.
Also retitle a Stop test to say what it asserts.
* Say why a Grok turn failed, and keep task rows in Grok's own words
A failed Grok turn ended with no reason on screen: the translator dropped
every copy of Grok's message. The failed turn now gets one status row in
Orca's existing "provider did not accept this message" words with Grok's
reason, read from whichever copy arrives first (the given-up retry, the
turn's end, the prompt's completion notice, or the prompt's error answer);
later copies only fill a reason the row still lacks.
A running background command no longer reads "Background task <id>
started": a task's summary is mapped only once it has settled. A monitor
stays a monitor when the agent reads its output: a frame that names no
kind keeps the known one, and a "[monitor" command is a monitor.
A prompt's turn is marked started, so a late frame for an ended prompt
neither reopens it nor becomes the active turn. A tool's turn is held in
one place at a time.
* Read a monitor from Grok's exact output prefix
* Word a failed Grok turn in Grok's own text, not as a refused message
A turn that started and then failed was told "The provider did not accept
this message", Orca's sentence for a message refused before its turn. The
row now reads as a Codex turn-ending error does: an error status row with the
provider's own words. With no words, the dialect names the failure ("Grok
ended this turn with an error." / "Grok usage limit reached."), else the
agent's display name does.
* Register the ACP schema verify step in the PR preflight phase test
* Read ACP permissions, session events and prompt errors through the protocol client's own types
The translator now reads a permission request with the client's lenient reader, a session update
with its session-event reader, and takes only the agent's own error answer as a failed prompt's
reason, so an Orca-side error never reads as the provider's words. Tests cover protocol values
newer than this build.
* refactor(native-chat): drop saved-history adoption from the timeline assembler
The common pattern discards the history a provider replays while loading a
session, so the assembler has no use for an input.history event.
* refactor(acp): drop session/load history adoption from the translator
The common pattern discards the history an agent replays during session/load,
keeping only what it says about the context window. Remove the adoption path
(acp-history-adoption.ts, the adopt option, and the historical background-task
liveness rewrite it fed) so load replay is always dropped except usage.
* refactor(native-chat): a pending input is only Orca's send now
Review follow-up to the adoption removal: drop the comment naming the
provider's saved message, and make requestedAt required since every pending
input comes from input.accepted.
* test(acp): keep the task-result status table on live frames
Review follow-up to the adoption removal: the result-status mapping was only
tested through adopted history, so run the same table on live frames, and
cover an unmarked task notice during a load being dropped.
* Use current provider handles in transition tests
* Use current provider handles in timeline fixtures
* Require the ACP directory in the runtime import check
|
||
|
|
13ea35973c |
Stop expensive checks when an unmerged PR closes (#25829)
* Cancel active checks when an unmerged PR closes * Register owned-branch cancellation qualification * Keep temporary cancellation qualification outside the review diff |
||
|
|
f6f96db6be | Build SSH hostile-host Linux slots independently (#25821) | ||
|
|
634c43787c |
Fix premature Claude automation completion and inherited CI failures (#24878)
* fix(agent-status): keep a Claude pane working until owed task wake-ups arrive Claude wakes the main agent for each background task that ends, after the task stops running. "Nothing running" was read as done, so automations closed the terminal before the final turn. Fixes #23942 * fix(agent-status): keep owed task wake-ups through failed turns, the cap and nested exits A failed turn's task list now marks a vanished shell owed like a normal turn end does. Past the cap only an already-announced sub-agent is forgotten. A process exit clears what is owed only where the server admits it from the pane's owner. * refactor(agent-status): move pane-scoped cache entry helpers out of listener-state listener-state.ts passed the 300-line limit once main's and this branch's additions met. * fix(agent-status): verify Claude task wake-up completion on its execution host * fix(agent-status): canonicalize Claude background task identifiers * Preserve out-of-order Claude task notification evidence * Verify retained released parser in cross-version checkout test * Reuse watcher directory-cache enumeration without losing fresh keys --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
0bcd49c04c |
Let ACP connections own their agent processes (#25810)
* Let ACP connections own their supervised agent process * Preserve ACP cleanup evidence and isolate exit observers * Expose ACP cleanup observations and type the permission fixture |
||
|
|
a2f197fde7 |
Refresh visible reviews automatically and stop settled merged polling (#25788)
Refresh only actual on-screen review cards and the selected visible review panel, with a single metadata owner per execution host. Open reviews refresh every 60 seconds when selected and 120 seconds otherwise. Settled merged reviews stop automatic refresh; pending merged checks continue, hidden rows stop, and work changes or stale re-exposure discover fresh state. Remove the polling setting. Preserve provider/SSH/runtime ownership, mixed-version cache behavior, failure backoff, bounded foreground admission, request coalescing, and mutation invalidation. Cover older persisted caches and unknown HEADs. Validated with 1,628 focused tests, typechecking, lint/code-quality gates, hidden Electron viewport checks, and parallel Codex/Claude Opus 5.5 adversarial reviews. Measurements and policy details are in PR #25788. Fixes #25746 Co-authored-by: ggbdpq <ggbdpq@gmail.com> |
||
|
|
9905765e3b |
feat(native-chat): open structured chat's wire and stored records to registered agents, behind a negotiated capability (#25159)
* refactor(native-chat): keep the provider resume handle opaque to shared code
Shared structured-chat code parsed each provider's resume handle: Claude's
session id and branch leaf, Codex's thread id, through a 'claude' | 'codex'
union every new agent had to widen. The in-memory handle is now
{ transport, agent, nativeId, providerData? }: shared readers use nativeId,
lease and handle-chain checks compare transport and agent, and only the
Claude adapter reads its leaf (providerData).
Stored and wire forms are unchanged for Claude and Codex. One encoding
module writes their typed shapes and decodes both those and the neutral
shape a new transport uses, which an older build refuses as unreadable
rather than reading as Codex. Key and root strings, which fork seeds,
superseded creations and resume offers persist, stay byte-identical.
The journal's own handle type becomes the journal-row and attach-wire
encoding of the same handle, and the journal identity carries the
neutral handle (null before the provider proves one).
No user-visible change.
* fix(native-chat): derive journal-row provider handles from the journal identity
The journal row converter now takes the identity every caller already holds,
so a row's handle has one obvious constructor. Tests that wrote the in-memory
handle straight into journal rows now build it through that converter, and the
processless Claude fixture names a not-yet-proved handle as null.
* fix(native-chat): refuse a stored provider handle written in both forms
A typed Claude or Codex handle that also carries the neutral form's
transport, agent, native id or provider data named two identities; it
was read as Claude or Codex and the next write dropped the other one.
Such a row now stays unreadable and is set aside untouched.
* refactor(native-chat): route structured agents through registered definitions
The structured-chat shared layer named Claude and Codex in a closed router
type, in option reads, in frame classification, and in renderer label and
validation branches, so adding an agent meant editing shared types. Each
agent now has a definition (capabilities, resting option rules) owned by its
own module; the router is a registry of definition + adapter pairs built at
runtime setup, and shared code reads the definition instead of the name.
No behavior change for Claude or Codex; no wire or stored shape change.
* refactor(native-chat): name the structured agent list once in host types
The host types, conversation commands, provider-session ownership and the
runtime's create path spelled the structured agent list inline; they now use
the one shared type the record and wire already validate against.
* fix(native-chat): narrow the record before reading its agent's option rules
* refactor(native-chat): make the router's registrations the only agent definition lookup
The at-rest option reads looked definitions up in a second, separately populated map, so a
registered definition could route and declare capabilities while the static map decided which
options a chat at rest accepted. The router now answers `definition(agent)` from its
registrations, the at-rest readers take it through the host's adapter, and the built-in
definitions are listed only where the runtime composes the router.
* feat(native-chat): open the structured-chat wire and stored records to registered agents
A host's structured agents are the ones its runtime registered. Records, RPC
params, persisted tabs and the model catalog accept any registered agent instead
of naming Claude and Codex; each agent's definition declares the transport its
handles live in and the variable its account home pins. A new runtime
capability, agent-session.structured.registered-agents.v1, advertises that a
host accepts and lists its agents (agentSession.agents, with each agent's
capability record), and the host withholds any other agent's tabs and restart
offers from clients that do not advertise it.
* test(native-chat): cover registered agents on the wire, in storage and across versions
* refactor(native-chat): let the record store decide which agents' tabs exist
* test(native-chat): declare the pilot test agent's storage
* test(native-chat): read the old build's saved tabs through a parsed shape
* fix(native-chat): act on restart offers only for agents the calling client can show
A paired client too old to show an agent's chat was listed only the offers it could show, but
dismissing or continuing all reached every offer on the host, and a named continuation answered
with the host's whole remaining inventory. The client's audience now goes to the host with every
restart operation: only offers it sees are reserved, dismissed or returned. Without an audience
(this host's own process, or a client that shows every agent) nothing changes.
* test(native-chat): read an agent-registering baseline's storage on its own terms
The registered-agents downgrade test assumed its baseline release predates registered agents: it
expected the saved-tab parser to erase an unknown agent and called the record reader without the
agents list. Once a release with this change becomes the baseline, both break. The expectations now
follow what the baseline host advertises, and an agent-registering baseline is handed its own
Claude and Codex storage.
* refactor(native-chat): derive record-store admission from the runtime's agent registrations
Which agents a stored record may name and which agents the router drives came from two lists in
the runtime, so a newly registered agent could be routed while its records were set aside. One
list of registrations now holds each agent's definition and the factory for its adapter: the
store's admitted agents are derived from it before the store opens, and the adapters are built
from it once it has.
* fix(native-chat): hand the exit drain a promise for every registered agent
* fix(native-chat): let the adoption conflict check read any agent's ownership
Ownership rows name any registered agent since the stored records opened to them; the adoption
check compares by agent, so it takes the same open id. Only Claude and Codex still adopt.
* test(native-chat): use opaque handle in queued rejection fixture
* test(native-chat): share one Codex journal identity in the integration suite
Main grew the suite to the 800-line limit; the opaque-handle import pushed it
over. The two tests built the same identity inline.
* refactor(agent-session): name the handle's adapter state resumeCursor
Rename the neutral provider handle's providerData to resumeCursor before any
row persists the neutral form: it is an adapter-owned resume position (Claude's
transcript leaf), never identity. Claude/Codex stored and wire bytes are
unchanged; their typed shapes never carried the field.
State the stored-form contract (a handle's field set is closed; later per-link
data goes on the chain link, which every build preserves) and pin it with a
record round-trip test. Document that transport records the id space the
native id was minted in, which can differ from the agent's current transport.
* refactor(agent-session): one required agent registry; declarations admit what they claim
A StructuredAgentRegistry, built once from the {definition, adapter}
registrations, is now a required host dependency and the router routes with
it. The adapter interface loses its optional router-only capabilities?() and
definition?(); every reader (options at rest, thread goal, rewind, the
/compact handover) asks the registry. A live session still narrows rewind
through the adapter, and the host combines declared and narrowed in one
helper. The registry refuses a registration that declares compact, a thread
goal or rewind without the adapter method behind it.
/compact is admitted by the declared capability, so an agent that declares
compact:false gets the commandRefused fact instead of a thrown error.
Also: the cut-turn notice names the agent from the catalog, create-support
builds the account home through agentSessionAccountHome, and the turn
status text older clients read names the session's agent instead of
defaulting to Codex (Claude/Codex text unchanged).
* refactor(agent-session): the router applies the declared rewind itself
The router holds the registry, so its rewindSupport answers the owner's
declared rewind narrowed by the adapter, in one place no reader can bypass.
The host-side combining helper is gone; readers ask the adapter they hold.
* test(agent-session): register the agents the merged-in tests now need
The adoption replay test builds its host over this build's registered agents
instead of an adapter definition method, and the stored-form test passes the
stored agents the record guard now requires.
* chore(agent-session): the registry is the one lookup; drop the test-only empty capability record
* fix(agent-session): keep "dismiss all" restart offers dismissed for the desktop
The desktop renderer calls the restart methods as a paired runtime client
without the registered-agents capability, so it was handed a Claude/Codex
audience and its "dismiss all" took the scoped path: no dismissal fence and
no clearing of unwritten teardown witnesses, so a late teardown write could
bring a dismissed offer back.
The audience is now derived against this host's registry: a client that can
show every agent the host registered gets none, and dismisses exactly as
before (one fence for every offer). A client that truly cannot show some
agent still gets a final dismissal for what it sees: each cleared session is
fenced on its own (bounded, superseded by a dismiss-all fence) and only the
witnesses of agents it sees are dropped.
One predicate now answers which agents a client renders, for both tab
projection and restart offers; the Claude capability rule is folded in.
* fix(agent-session): a changed agent definition never hides that agent's chats
A record was readable only if every handle used the transport its agent's
definition declares today, and its account variable was declared by some
registered agent. A later build that drives the same agent over another
protocol, or under a renamed variable, would have set every existing chat of
that agent aside: no tab, no history, no error.
Readability now asks only that the record agree with itself: its agent is
registered, every handle shares one namespace owned by that agent, and the
account variable is a well-formed name. Whether this build can drive it (the
chain's transport is the one the agent speaks, and the account variable is
that agent's own, since it becomes the child's environment) is decided in
performAttach, the one admission every start of every agent passes through,
and refused there as hostUnsupported. A record pinning another agent's
variable is refused the same way rather than passing because some agent
declares it.
* refactor(agent-session): each agent's registration says where it runs and which account it pins
createSupport, create and the model catalog's account read still chose a
location rule and an account resolver by name (Claude, Codex, else none),
so a new agent would have needed a third branch beside the registration
list that already decides storage, routing and publication.
Each runtime registration now carries supportsLocation(location) and
resolveAccountHomePath({ launchEnv, location, purpose, workspacePath }),
and Claude's and Codex's rules move into their entries unchanged: a read
still syncs no home and starts no bridge, a Codex launch still trusts the
workspace first, and WSL locations resolve as before. The list exists
before the host is built, so none of these reads installs the host, and an
agent the list does not hold is answered no without opening the journal.
* test(agent-session): drop a duplicate registry key; keep fences out of the capsule state
A spread input already carries the registry, so the explicit key was
overwritten (TS2783). The session-fence helper now returns only the fence,
not the capsule state it was given.
* fix(agent-session): a scoped dismiss-all persists no per-session fence
The per-session dismissal fence added a forever-persisted capsule field
with an arbitrary cap that served no caller: only the local desktop calls
the restart methods, and on a host whose agents it all shows it gets no
audience and takes the unscoped, fenced dismissal. A scoped dismiss-all
(a caller that cannot show some registered agent) keeps that agent's
offers, clears only its audience's unwritten witnesses, and writes no
fence; a late write from this process is serialized behind it. The field
never left this unreleased branch.
* fix(agent-session): refuse an attach whose agent is not the session's own
The start check judges the record's agent while the router starts the adapter of params.agent, and the attach wire let the two differ. Admission now refuses agent !== provider as requestMalformed, so the agent checked is the agent started. Every host-built attach already sets them equal.
* fix(agent-session): offer to start a chat only when the start would accept it
The restart offer, a failure's retryable flag and the pre-send check asked only whether the adapter runs the chat's location, while the start also refuses a record this build cannot drive; such a chat was offered Resume and its Retry failed forever. One host predicate, hostCanStartRecord (location support and agentDrivesSession), now answers all four; adapterSupportsRecord stays the reader gate, and an undrivable chat's offer is kept, not retired.
* fix(agent-session): the model-catalog probe uses a record's account only if this build drives it
A record-scoped catalog read started the agent's lister under the record's account path whatever variable the record pinned. It now uses that path only when agentDrivesSession holds, and otherwise resolves the account as for a read with no record.
* refactor(agent-session): the record store admits agent ids; comments say where transport is checked
The store only ever asked whether an agent is registered, so its admission list is now the registered ids; the definition is the one home of an agent's transport and account variable. Comments that said the record store checks transport, and one that cited a nonexistent function, now point at agentDrivesSession and the attach admission. The isPersistedAgentSessionRecord(value, agents) call shape and the test fixture the cross-version probe imports are kept.
* docs(agent-session): the record store admits the registered agents' ids
* test(native-chat): dismiss-all through a remote client's audience keeps a newer Orca's offer
Uses an audience production sends (one that cannot show every agent), per review.
* chore(native-chat): keep the record store and recovery capsule under max-lines after the main merge
* test(native-chat): build the Stop-opened-turn test's identity and turn context the current way
The test (#25056) landed before the opaque provider handle (#24991), so main still built
the old {kind, threadId} handle; the turn context also needs this branch's agent registry.
* test(ratchet): require src/main/provider-process now that it has landed
* test(native-chat): keep main's opaque-handle import in the Stop-opened-turn test
Main's #25706 and this branch both added the import at different lines; the merge kept both.
* Keep saved chats readable without provider registration
* Keep stored-record compatibility checks independent of registration
* Keep saved providers in restart client audiences
* test(native-chat): type reveal fixtures without assertions
* test(wire): expose known agents in structured host fixture
* Supply startability dependency in the new Codex catalog fixture
|
||
|
|
c4ea14cd9f |
Show a tool call you stopped as interrupted, not failed (#25181)
* Move the turn message ordinals and the turn-row revision to the neutral timeline folder Pure moves so a shared timeline assembler can use them: Codex's message ordinal counter becomes ProviderTurnMessageOrdinals and Claude's turn-row revision becomes the provider-neutral agent-journal turn-row revision. Only names and import paths change. * Admit one provider event's writes as one transition, and let rows be found again after a restart - A sink transition is admitted whole or not at all; its steps run back to back at their turn in the journal's write queue, and each resolver reads the fold with every earlier write landed. A resolver may also say where the row belongs (turn scope, provider reference), and the writer always hears how the transition landed. A resolved lifecycle batch chooses its settlement mutations from the fold at execution. - New optional row field providerItemRef: the provider's own reference for the item a row is, written only where the row's identity cannot spell it (Codex keys messages by their place in the turn and renumbers its item ids on resume). Set by the creating write, kept by revisions, indexed by the journal fold, never read by clients. A downgrade test shows an older host and client render such rows unchanged. - Provider timeline identity schemes (shared legacy arm, Codex) and the join index that resolves a provider item to its row from memory or the fold: ordinals and request incarnations are read back from the rows, so a restart or an evicted entry finds the original row instead of placing a new one. * Recover message ordinals from the journal's highest place, and forget joins read from a replaced epoch A fresh join index continued a turn's messages at the first free place, so a journal holding only a later ordinal (an imported or removed earlier row) had its sequence back-filled. The place is now one past the highest ordinal any row or echoed send holds there, read through a pure scheme reader. The join caches also drop what they read when the journal's epoch is replaced. * Spell the subagent thread's message slot without spreading an identity union * Add a provider timeline grammar and a shared assembler that decides at its turn in the journal Adapters translate their provider's dialect into a small grammar (turns, items, streamed text, requests, context facts, session end/reset); one shared assembler turns it into the journal rows every structured lane writes. Each event is planned as one sink transition. Which row a write lands on, whether a replay writes anything, and every change to what the assembler knows (its ledger) are decided by the transition's resolvers at the event's turn in the journal's write queue, against the fold as it stands then. A forecast (the ledger plus admitted events still queued) only answers apply() at once. So a refused event allocates nothing, a write the journal rejects leaves no trace in memory, and a restart or evicted cache finds the same rows again. Text and full snapshots of one provider item share one row and one lifecycle; reset always flushes text and settles the old session from the journal; the open-work budget is derived from what is actually open. Codex migration contracts compare against the existing Codex translator, including a restart mid-stream and a repeat that outlives the join cache. * Fix the types and the exhaustive event switch CI reported for the assembler * Let the journal decide stream lifetimes, request reuse, named sends and background work A third review found two blockers with the earlier rounds' cause, a remembered interpretation trusted after the journal moved on: - A reused request id was judged by its earlier prompt's settled turn before asking which turn the new one lands in, so a real approval in a later turn was dropped. The target turn now decides: the old turn again is a replay; a different live turn opens the next prompt beside it. - A text stream checked its row's turn only on its first write, and a turn's end released streams by the turn planning expected. Every write now checks the row, a turn's end stops the streams whose rows are in it, and turn status reads the journal first, so another writer's Stop wins. Also: a message boundary drawn by an event the journal held as a replay no longer splits an anonymous message; a send naming a turn not yet open waits for that turn; the budget charges a stream's thread and turn strings and the turn caches are byte-bounded; the open turn ends when the journal shows it settled; a turn's opener is read from the journal's row. Background work is now Orca's existing background-task row instead of a tool call flagged `outlivesTurn` (a flag remembered only in memory, so a restart failed the task). A turn's end never settles that row, so it survives restarts; session end leaves one in flight unverifiable. Three tests that opened a background tool call with `outlivesTurn` now open a background-task row and keep their original expectations about which turn the row stays in. * Type the unbound assembler helper's drain as the void it reports * Bound the rows kept for a continued anonymous message and the stopped streams A row kept for the anonymous stream that may continue it, and the marker that a stopped stream's queued writes write nothing, lived in the live-stream map and were never removed when no stream followed. They now live in their own bounded maps, so the live map holds open streams only. * Read a tool call a stop or the session's end cut short as interrupted, not failed A call still running when its turn was interrupted, when the provider child was seen to exit, or that the provider cancelled now carries `endedAs: 'interrupted'` beside `state: 'failed'`. Every build that predates the field keeps reading the call as the failure it always showed; this build reads both through one shared reader and counts the call apart from failures in the run header, without the error tint on its partial output. The shared assembler, the restart settlement and the Codex lane settle a running call through one rule: only a proven interruption cuts it short, so a turn the provider completed around an unclosed call, or work the host lost track of, still reads failed. * Assert a completed Codex turn leaves its unclosed call plainly failed * Assert an unverifiable restart settlement leaves its running call plainly failed * Let a later death proof correct the calls an unverifiable settle closed A settle with no proof of the child's death closed its running calls as plain failed, so a proof written afterwards corrected the turn to interrupted but could not find the calls. The call now keeps endedAs: 'unverifiable' beside its failed state, and the stale settle revises exactly those calls whose owner the proof names. Older builds still read them as failed. * Render a cut-short call's output on mobile, with and without proof * Find the mobile result box by walking to its View, and type the unverified-ending block * Walk to the mobile result box without a type assertion * Keep the journal store under its line limit after the main merge * Drop the provider item reference, join index and identity schemes from the transition PR Nothing in production reaches the state they defended (an assembler that lost its memory while its child keeps streaming the same turn), and the stored Codex id was positional. The legacy identity scheme moves to the assembler PR with its first caller; the Codex scheme and any persisted reference wait for Codex to move onto the assembler. The Codex ordinal counter goes back to codex/, since no neutral code imports it. * Write a resolved settlement in one transaction through enqueueRows A settlement too large for one row now commits all its rows or none, through the journal's existing all-or-nothing write, instead of a row-by-row writer. Every row is built before any commits, so a settlement naming one item twice is refused before anything is written. * Drop the transition's landing report; keep the turn-row write fire-and-forget Nothing reads which steps of a transition wrote. A failed step fails the sink, leaving the steps before it written; the header says so, and tests cover it plus a settlement whose second row fails inside the transaction. writeAgentJournalTurnRow returns nothing again, as on main. * Run a transition's steps as a prefix; drop the paced flag and resolved options A failed step no longer lets the steps after it write: each step checks, at its own turn in the journal's queue, whether the write handed over just ahead of it completed, using the queue's count of completed write bodies (a promise would report the failure only after the next step ran). The sink fails only once every step has had its turn. The item step's `paced` size bypass and the resolver's replacement `options` are removed; nothing planned uses them. * Rebuild the timeline assembler on one admission-order state The assembler kept a second copy of its state (a forecast beside a ledger), hydrated the open turn from the journal, and recognised replays, all to recover from losing its memory while the provider child kept streaming. That never happens: one assembler lives exactly as long as one child, and a new child is a new assembler in a new generation whose events land after the dead-generation sweep. - One state, changed only when the sink admits an event (minted keys included), so a refused event takes nothing. - Every journal-dependent choice is made when the write runs, by keyed reads: a turn row is written only where none is, a stream checks its row's turn on every write, a running snapshot never lands in a settled turn or relights a settled tool, a request takes the first incarnation the journal holds no row for. - Rows are found by spelling their ids (provider-timeline-rows.ts); no join cache. - The identity scheme (legacy arm only) lives here with its first caller; requests are spelled in their acquisition generation, since JSON-RPC ids restart per process. - Saved history goes in as `input.history` plus ordinary events with the provider's ids, into an empty journal; `session.reset` and every replay rule are gone. - One terminal-body function (`terminalAgentJournalBody`) is shared with the dead-generation settlement. - The test rig's restart now sweeps and starts a new generation, as production does. * Cover new running work in a turn the sweep ended * Run a transition's steps in one queued write that loops over them The steps of one event now share one turn in the journal's write queue: a loop writes each in its own transaction through the row writer's synchronous writeRows (split out of enqueueRows) and stops at the first throw. Prefix semantics and "nothing lands between the steps" now hold by construction, so the completed-write counter on the queue, the step gate and the allSettled barrier are gone; the queue is back to main's bytes. * End a turn another writer settled the way the provider's end does A person's Stop settled the open turn's row without a word to the assembler. The assembler then forgot the turn: its running tools and pending prompts were never settled, the provider's own end and withdrawal were dropped, and the turn's text streams stayed counted against the open budget for the life of the process. Text the provider kept streaming afterwards could land as a message outside the stopped turn. - The open turn the journal shows settled ends first, as one transition, through the same settlement the provider's turn.end plans; its streams stop and their keys drop later text until that turn's end or the next turn opens. - turn.end and request.withdrawn are admitted for a turn or request the journal holds; their settlement writes nothing for rows already settled. - The budget's re-check frees streams whose turn settled. - A settled tool keeps its terminal body against any differing write. - Session-end settlement of lost background work uses the journal's own lostLiveWorkJournalBody instead of a copy. - The rig's window elapses before every read, and restart swaps and disposes the old assembler. * Leave a stopped turn's running tools to the agent's own end When another writer settles the open turn (a person's Stop), the assembler now only stops that turn's text and cancels its pending prompts. Running tool calls stay the agent's: a progress update or completion it reports after the Stop lands as reported, and whatever is still running settles at the agent's turn end for that turn, the next turn's open, or the session's end. An agent's end for an earlier turn while a newer one is open no longer clears the open turn's activity line or ends its anonymous reply. An unnamed end right after a Stop ends the stopped turn instead of being dropped. The test rig's restart no longer writes the dead assembler's window text, matching dispose. * Pin that a stopped turn's running tools hold budget until the agent's end * Type the stopped turn's tool progress update as a tool body * List every event the assembler hands to the decision step The type-aware lint requires an exhaustive switch with no default case. Also retitle a Stop test to say what it asserts. * End a running call as its turn's journal row ends A call still running when its turn ends takes the state of that turn's row: a row another writer settled first (a person's Stop) stands, so its calls read interrupted whatever the provider's later end reports. The no-ending path that settled calls from the Stop row is gone, since a Stop now leaves running calls to the provider. Adds the two Spanish strings. * Settle a stopped turn's running call as its turn row ended after a restart too The restart sweep ended every running call by the death evidence alone, so after a person's Stop with no proof the child died the call read failed under a turn that read interrupted. The sweep and the live dead-generation settlement now ask the same rule the assembler does: a call in a turn already settled ends as that row ended; only a turn still running leaves its calls to the evidence. * Keep the dead-generation settlement under the line cap * refactor(native-chat): drop saved-history adoption from the timeline assembler The common pattern discards the history a provider replays while loading a session, so the assembler has no use for an input.history event. * refactor(native-chat): a pending input is only Orca's send now Review follow-up to the adoption removal: drop the comment naming the provider's saved message, and make requestedAt required since every pending input comes from input.accepted. * test(ratchet): require src/main/provider-process now that it has landed * test(native-chat): build this stack's journal identities with main's opaque provider handle Main's #24991 replaced the {kind, ...} handle with {transport, agent, nativeId}; three test files from this stack still wrote the old shape. Same lines the downstream ACP branch uses. |
||
|
|
41f293c264 |
fix(notifications): replay completion sounds reliably (#15940)
* fix(notifications): replay completion sounds reliably The in-flight `isNotificationSoundPlaying` gate dropped every notification sound that arrived while the previous one was still ringing, so bursts played once. Drop the gate and restart the cached Audio instead, and reuse an entry a concurrent call already cached for the same path so parallel first playbacks share one Audio rather than revoking each other's blob URL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016S8SZpkXGcnU5UWwA8UiF2 * test(notifications): verify real sound replay and remove unused playback listeners --------- Co-authored-by: Laku <laku@LakudeMacBook-Pro.local> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
6a9ba9d733 |
Keep remote worktree creation from navigating paired clients (#25793)
* fix(workspaces): keep remote creation from navigating paired viewers * fix(workspaces): revoke pending navigation after connection replacement * fix(workspaces): provision when the host renderer is unavailable * test: align navigation and cache-scan expectations with current behavior * fix(workspaces): provision broadcast creates without a host renderer |
||
|
|
8507111d87 |
Document Apple Git ref recovery retirement condition
Name the affected Apple Git-154 (2.39.5) build and document when corruption recovery can be retired while retaining current-Git disambiguation. Comments only; lint, formatting, and review pass. |
||
|
|
29e669680d |
fix(codex): never write a Codex config.toml that Codex can't load, and approve both symlink spellings (#25741)
* fix(codex): never write a hook approval Codex cannot load, and approve both symlink spellings - Refuse any hooks.state write into a Codex config.toml (upsert, move, remove, mirrored enabled state, SSH installer) that would turn a loadable file into one Codex cannot load; write nothing and surface the reason. - Read approvals written as dotted keys or inline tables. - Approve and remove Orca's ~/.codex hook under both the spelled and the resolved key when ~/.codex or HOME is a symlink; move user approvals under both keys. - Stale runtime trust cleanup no longer keeps an unexpected key whose conflicting duplicate tables read as no hash. * refactor(codex): pass every hooks.json spelling as one sourcePaths list * fix(codex): sweep retired-hook approvals under every key spelling; read literal-string trusted_hash |
||
|
|
1de3aa405f |
Fix truncated Unicode branch names in base ref search
Preserve complete branch selectors when Git truncates or disambiguates short names, and keep slash-named local branches separate from remote-tracking refs through worktree creation and reuse. Keep native/SSH namespace boundaries and mixed-version client capability gates intact. Accept Git-valid dotted components and cover collisions with configured and orphan tracking refs. Adapted from @nishino-tsukasa's #19540. Independently reviewed in seven adversarial rounds; 235 focused tests and 77 affected-Apple-Git tests pass, along with typecheck, quality checks, and the desktop build. Correct the inherited file-explorer cache-scan expectation while retaining its exact constant-work bound. Correct the inherited release-parser compatibility assertion to match the pinned test dependency; all 121 cross-version tests pass locally. Fixes #19515 Co-authored-by: kino <nishinotsukasavirgo@gmail.com> |
||
|
|
3ec030e80a | Update README downloads badge | ||
|
|
ac3a25ca37 |
Wait for Codex's live composer before pasting linked issue drafts (#25779)
* Wait for Codex's live composer before pasting launch drafts * Keep timed-out Codex draft delivery consumed across remounts * Document the checked terminal fixtures used by the remount test * Distinguish Codex's reserved footer row from early multiline input * Complete transcript baselines and suppress hidden terminal strings * Align watcher scan budget with linked-directory reconciliation * Drop unused snapshots after main removed the readiness census |
||
|
|
eaaae0196f |
feat(native-chat): record fresh sessions after failed restoration (#25747)
* feat(native-chat): record a fresh provider conversation that replaced one the agent could not restore A chat whose saved conversation the agent cannot reopen can now continue in a fresh one: the handle chain records the new conversation as a creation that replaces the lost one (which, why, and when), keeping every earlier link. Rows keep a shape older builds read: the stored chain starts at the latest replacement and carries the earlier links inside it. * refactor(native-chat): store a replaced conversation flat; refuse it where older builds read the row Older builds only read Claude and Codex records, so the nested stored form protected rows no replacement can reach while adding a cap mismatch after a downgrade. Store the chain as held, refuse a replacement in a Claude or Codex chain until one has a stored shape older builds read, and refuse a supersession key on a replacement that names no creation in the chain. * test(native-chat): prove replacement rows survive downgrade and re-upgrade |
||
|
|
d7a95782d1 |
Add a shared timeline assembler for structured agent chats (not wired yet) (#25064)
* Move the turn message ordinals and the turn-row revision to the neutral timeline folder Pure moves so a shared timeline assembler can use them: Codex's message ordinal counter becomes ProviderTurnMessageOrdinals and Claude's turn-row revision becomes the provider-neutral agent-journal turn-row revision. Only names and import paths change. * Admit one provider event's writes as one transition, and let rows be found again after a restart - A sink transition is admitted whole or not at all; its steps run back to back at their turn in the journal's write queue, and each resolver reads the fold with every earlier write landed. A resolver may also say where the row belongs (turn scope, provider reference), and the writer always hears how the transition landed. A resolved lifecycle batch chooses its settlement mutations from the fold at execution. - New optional row field providerItemRef: the provider's own reference for the item a row is, written only where the row's identity cannot spell it (Codex keys messages by their place in the turn and renumbers its item ids on resume). Set by the creating write, kept by revisions, indexed by the journal fold, never read by clients. A downgrade test shows an older host and client render such rows unchanged. - Provider timeline identity schemes (shared legacy arm, Codex) and the join index that resolves a provider item to its row from memory or the fold: ordinals and request incarnations are read back from the rows, so a restart or an evicted entry finds the original row instead of placing a new one. * Recover message ordinals from the journal's highest place, and forget joins read from a replaced epoch A fresh join index continued a turn's messages at the first free place, so a journal holding only a later ordinal (an imported or removed earlier row) had its sequence back-filled. The place is now one past the highest ordinal any row or echoed send holds there, read through a pure scheme reader. The join caches also drop what they read when the journal's epoch is replaced. * Spell the subagent thread's message slot without spreading an identity union * Add a provider timeline grammar and a shared assembler that decides at its turn in the journal Adapters translate their provider's dialect into a small grammar (turns, items, streamed text, requests, context facts, session end/reset); one shared assembler turns it into the journal rows every structured lane writes. Each event is planned as one sink transition. Which row a write lands on, whether a replay writes anything, and every change to what the assembler knows (its ledger) are decided by the transition's resolvers at the event's turn in the journal's write queue, against the fold as it stands then. A forecast (the ledger plus admitted events still queued) only answers apply() at once. So a refused event allocates nothing, a write the journal rejects leaves no trace in memory, and a restart or evicted cache finds the same rows again. Text and full snapshots of one provider item share one row and one lifecycle; reset always flushes text and settles the old session from the journal; the open-work budget is derived from what is actually open. Codex migration contracts compare against the existing Codex translator, including a restart mid-stream and a repeat that outlives the join cache. * Fix the types and the exhaustive event switch CI reported for the assembler * Let the journal decide stream lifetimes, request reuse, named sends and background work A third review found two blockers with the earlier rounds' cause, a remembered interpretation trusted after the journal moved on: - A reused request id was judged by its earlier prompt's settled turn before asking which turn the new one lands in, so a real approval in a later turn was dropped. The target turn now decides: the old turn again is a replay; a different live turn opens the next prompt beside it. - A text stream checked its row's turn only on its first write, and a turn's end released streams by the turn planning expected. Every write now checks the row, a turn's end stops the streams whose rows are in it, and turn status reads the journal first, so another writer's Stop wins. Also: a message boundary drawn by an event the journal held as a replay no longer splits an anonymous message; a send naming a turn not yet open waits for that turn; the budget charges a stream's thread and turn strings and the turn caches are byte-bounded; the open turn ends when the journal shows it settled; a turn's opener is read from the journal's row. Background work is now Orca's existing background-task row instead of a tool call flagged `outlivesTurn` (a flag remembered only in memory, so a restart failed the task). A turn's end never settles that row, so it survives restarts; session end leaves one in flight unverifiable. Three tests that opened a background tool call with `outlivesTurn` now open a background-task row and keep their original expectations about which turn the row stays in. * Type the unbound assembler helper's drain as the void it reports * Bound the rows kept for a continued anonymous message and the stopped streams A row kept for the anonymous stream that may continue it, and the marker that a stopped stream's queued writes write nothing, lived in the live-stream map and were never removed when no stream followed. They now live in their own bounded maps, so the live map holds open streams only. * Keep the journal store under its line limit after the main merge * Drop the provider item reference, join index and identity schemes from the transition PR Nothing in production reaches the state they defended (an assembler that lost its memory while its child keeps streaming the same turn), and the stored Codex id was positional. The legacy identity scheme moves to the assembler PR with its first caller; the Codex scheme and any persisted reference wait for Codex to move onto the assembler. The Codex ordinal counter goes back to codex/, since no neutral code imports it. * Write a resolved settlement in one transaction through enqueueRows A settlement too large for one row now commits all its rows or none, through the journal's existing all-or-nothing write, instead of a row-by-row writer. Every row is built before any commits, so a settlement naming one item twice is refused before anything is written. * Drop the transition's landing report; keep the turn-row write fire-and-forget Nothing reads which steps of a transition wrote. A failed step fails the sink, leaving the steps before it written; the header says so, and tests cover it plus a settlement whose second row fails inside the transaction. writeAgentJournalTurnRow returns nothing again, as on main. * Run a transition's steps as a prefix; drop the paced flag and resolved options A failed step no longer lets the steps after it write: each step checks, at its own turn in the journal's queue, whether the write handed over just ahead of it completed, using the queue's count of completed write bodies (a promise would report the failure only after the next step ran). The sink fails only once every step has had its turn. The item step's `paced` size bypass and the resolver's replacement `options` are removed; nothing planned uses them. * Rebuild the timeline assembler on one admission-order state The assembler kept a second copy of its state (a forecast beside a ledger), hydrated the open turn from the journal, and recognised replays, all to recover from losing its memory while the provider child kept streaming. That never happens: one assembler lives exactly as long as one child, and a new child is a new assembler in a new generation whose events land after the dead-generation sweep. - One state, changed only when the sink admits an event (minted keys included), so a refused event takes nothing. - Every journal-dependent choice is made when the write runs, by keyed reads: a turn row is written only where none is, a stream checks its row's turn on every write, a running snapshot never lands in a settled turn or relights a settled tool, a request takes the first incarnation the journal holds no row for. - Rows are found by spelling their ids (provider-timeline-rows.ts); no join cache. - The identity scheme (legacy arm only) lives here with its first caller; requests are spelled in their acquisition generation, since JSON-RPC ids restart per process. - Saved history goes in as `input.history` plus ordinary events with the provider's ids, into an empty journal; `session.reset` and every replay rule are gone. - One terminal-body function (`terminalAgentJournalBody`) is shared with the dead-generation settlement. - The test rig's restart now sweeps and starts a new generation, as production does. * Cover new running work in a turn the sweep ended * Run a transition's steps in one queued write that loops over them The steps of one event now share one turn in the journal's write queue: a loop writes each in its own transaction through the row writer's synchronous writeRows (split out of enqueueRows) and stops at the first throw. Prefix semantics and "nothing lands between the steps" now hold by construction, so the completed-write counter on the queue, the step gate and the allSettled barrier are gone; the queue is back to main's bytes. * End a turn another writer settled the way the provider's end does A person's Stop settled the open turn's row without a word to the assembler. The assembler then forgot the turn: its running tools and pending prompts were never settled, the provider's own end and withdrawal were dropped, and the turn's text streams stayed counted against the open budget for the life of the process. Text the provider kept streaming afterwards could land as a message outside the stopped turn. - The open turn the journal shows settled ends first, as one transition, through the same settlement the provider's turn.end plans; its streams stop and their keys drop later text until that turn's end or the next turn opens. - turn.end and request.withdrawn are admitted for a turn or request the journal holds; their settlement writes nothing for rows already settled. - The budget's re-check frees streams whose turn settled. - A settled tool keeps its terminal body against any differing write. - Session-end settlement of lost background work uses the journal's own lostLiveWorkJournalBody instead of a copy. - The rig's window elapses before every read, and restart swaps and disposes the old assembler. * Leave a stopped turn's running tools to the agent's own end When another writer settles the open turn (a person's Stop), the assembler now only stops that turn's text and cancels its pending prompts. Running tool calls stay the agent's: a progress update or completion it reports after the Stop lands as reported, and whatever is still running settles at the agent's turn end for that turn, the next turn's open, or the session's end. An agent's end for an earlier turn while a newer one is open no longer clears the open turn's activity line or ends its anonymous reply. An unnamed end right after a Stop ends the stopped turn instead of being dropped. The test rig's restart no longer writes the dead assembler's window text, matching dispose. * Pin that a stopped turn's running tools hold budget until the agent's end * Type the stopped turn's tool progress update as a tool body * List every event the assembler hands to the decision step The type-aware lint requires an exhaustive switch with no default case. Also retitle a Stop test to say what it asserts. * refactor(native-chat): drop saved-history adoption from the timeline assembler The common pattern discards the history a provider replays while loading a session, so the assembler has no use for an input.history event. * refactor(native-chat): a pending input is only Orca's send now Review follow-up to the adoption removal: drop the comment naming the provider's saved message, and make requestedAt required since every pending input comes from input.accepted. * Use current provider handles in transition tests * Use current provider handles in timeline fixtures |
||
|
|
f9c8cd4fc3 | Reduce redundant test coverage and unnecessary CI waits (#25806) | ||
|
|
4e64fa9940 |
fix(native-chat): start a Codex chat without waiting on the background model probe (#23831)
* fix(native-chat): start a Codex chat without waiting on the background model probe Opening a Codex chat kicks a session-less model-catalog probe (a throwaway read-only `codex app-server`) for the account, and the new chat's own options read then joined that probe's in-flight refresh through the catalog store's single-flight. When the probe's Codex hung, chat start waited out the probe's 15s deadline and failed with "codex app-server session exceeded 15000ms", even though the chat's own app-server was up. Single-flight now joins only a refresh by the same kind of lister. A live session lists over the connection it already holds; the probe still records its own failure in the store, and the picker keeps serving whichever listing succeeded. * fix(native-chat): never let a chat's model listing join another chat's listing Single-flight in the model catalog store was split by lister kind, so a new chat's acquire-time options read no longer joined the session-less probe, but it still joined any other live chat's in-flight listing for the same account. When that other chat's Codex was wedged or closing, the new chat waited out that chat's request timeout and failed to start with its error. Key in-flight listings by the lister's identity instead: a live session by its own connection, the probe by the probe itself. A lister still joins its own in-flight listing, and shouldRefresh still holds back a probe while any listing for the account is in flight. * test(native-chat): pin that the catalog store releases an account once every listing settles shouldRefresh reads 'any listing in flight for this account' from whether the per-account in-flight map exists, so that map must be dropped exactly when its last listing settles. Nothing covered this: removing the cleanup, or dropping the map on the first settle, passed every catalog test. A leftover map would stop every later background refresh and probe for the account. * fix(native-chat): name the catalog lister type instead of a bare object A live session is keyed by its per-spawn catalog handle (minted in the same acquire as its connection), the probe by itself. * test(codex): pin that a chat's own concurrent option reads share one listing * fix(native-chat): keep model picker current across parallel listings |
||
|
|
020cebeff6 |
Add standalone Agent Client Protocol client layer (#24990)
* Add standalone ACP protocol client and session runtime * Protect ACP transport teardown from late stream errors * Retire incoming ACP request ids before publishing responses * Narrow ACP configuration requests and transport message types * Remove redundant ACP request handler return unions * Keep ACP waits caller-owned and preserve protocol extensions * Preserve open ACP decisions through prompt completion * Generate open ACP enums and check the generated schema offline A newer or vendor enum value (tool kind, tool status, option kind, stop reason) no longer fails the whole message: generated enums accept the known literals plus any other string, typed so callers can still narrow on the known ones. The generated header now records the pinned input digests, the generator digest and a body hash, so `verify:acp-protocol` catches a stale or hand-edited file without network access; it runs in lint and the PR workflow. * Land the ACP runtime contract the agent adapters use - Deliver notifications other than session/update through onExtensionNotification, in arrival order with session updates. - Accept _meta on prompt, setMode, setModel, setConfigOption and cancel. - cancel() always sends session/cancel once the session runs, since the agent can be in a turn it began itself; only a successful send is shared, so a failed write is retried. - Cancel aborts each open agent request's signal and lets its handler send its own answer; -32800 only when the handler rejects. - Permission requests validate only the session, tool call id and options; unreadable fields are dropped with a diagnostic, and any answer Orca cannot send is `cancelled` instead of a JSON-RPC error. Agent-started turns may ask; whether to show it is the caller's decision. - AcpAgentError marks the agent's own errors; AcpInvalidResponseError keeps the raw answer and validation issues for answers Orca could not read. - Lines over the size limit are classified by prefix (shared with the Codex reader): the owed request fails, an oversized agent request is answered with an error, and an unattributable response closes the connection. * Answer every agent request after an ACP cancel A cancel that lands before a permission handler starts now still runs the permission path, so the agent gets the `cancelled` outcome rather than a request-cancelled error. A handler that ignores the abort no longer leaves the agent waiting: once the abort has run through, any request still unanswered gets request-cancelled. Handlers that answer on abort keep their own reply. Also renames a lint-rejected helper parameter, replaces a Reflect.apply in a test, and stops the permission diagnostic from firing with an empty list. * Let each ACP request handler own its answer after a cancel Removes the next-event-loop-turn fallback that answered request-cancelled for any handler still silent after a cancel. It raced answers that were still being saved (an approval mid-journal-write reached the agent as an error) and made the outcome depend on event-loop timing. The handler that owns an agent request now always sends its answer, or throws for request-cancelled; a request it never answers ends when the connection closes. A permission whose handler had not started still answers `cancelled`. * Register the ACP schema verify step in the PR preflight phase test * feat(acp): a steer's cancel asks once and never ends the agent The runtime had one cancel: send session/cancel, wait at most 10 s for Orca's prompt to settle, then close the connection, which ends the agent. A steer used it too, so a slow agent lost its process just because the person added a message. requestSteerCancel() now sends session/cancel once per prompt, cancels the agent's open requests and answers later permissions cancelled, and never bounds or closes: the prompt's own reply ends it and the steer's prompt follows. cancel() stays the Stop: bounded, then close. A Stop after a steer still bounds and closes. Both cancel paths move into acp-prompt-cancel.ts over one cancel channel. * fix(acp): a repeated steer shares the cancel in flight; say what the caller owns Per review: a second steer before the first write lands returns that write instead of resolving early. The steer's JSDoc says the wait for the prompt's reply is unbounded and that a prompt that fails instead must not take the steer until the caller rebuilds the session; the Stop's says a prompt that settles in time leaves the agent for the Stop's owner to end. The steer test now gives the runtime a handler that would allow: the open permission's signal aborts and the late one never reaches it. * test(ratchet): require src/main/acp now that this PR lands it |
||
|
|
635e7f53cb |
feat(github-projects): render Board project views as a kanban with drag-and-drop (#19074)
* Add board layout support for GitHub project views Board-layout views render as a kanban. Columns come from the view's verticalGroupByFields (the host retries without the selection on older GHES schemas and the renderer falls back to the Status field), with one column per single-select option in option order — empty ones included — plus a trailing no-value column whose drop clears the field. Card drops reuse the table's field mutation path, committed from a document-level capture listener because the preload's native-drop bridge stops drop events before React's root ever sees them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(github-projects): harden board capability probe and drop lifecycle Review follow-up. The verticalGroupByFields capability probe now matches parsed GraphQL error messages instead of substring-scanning the whole response body — partial-error responses echo the field name as a data key on healthy schemas, so one SAML/FORBIDDEN partial error could permanently degrade github.com boards for the session. Covered by new project-view-config tests per the capability-cache testing contract. Also: only single-select/iteration vertical fields shape columns (a drifted field kind no longer yields a clear-on-drop no-value column), optimistic patches resolve the column field through the board/group config, drag cleanup moved to a document-level dragend listener, the column dot follows dark mode via the chip CSS variables, the supported- layout allowlist is a single shared predicate, and the board's edit handler is stable so its drop listener stops re-registering per render. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(github-projects): serialize board edits and verify rendered drops * fix(github-projects): preserve refresh baselines and order edits across views * fix(github-projects): accept source settings projection in cache scope * fix(github-projects): distinguish view switches from refreshed field baselines * test(github-projects): type board IPC recordings and verify clear request --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
ca4e239861 | Remove low-value test inventories and duplicate fuzz oracles (#25791) | ||
|
|
c0b07d3a71 |
Admit one provider event's journal writes as one queued operation, decided when it runs (#25141)
* Move the turn message ordinals and the turn-row revision to the neutral timeline folder Pure moves so a shared timeline assembler can use them: Codex's message ordinal counter becomes ProviderTurnMessageOrdinals and Claude's turn-row revision becomes the provider-neutral agent-journal turn-row revision. Only names and import paths change. * Admit one provider event's writes as one transition, and let rows be found again after a restart - A sink transition is admitted whole or not at all; its steps run back to back at their turn in the journal's write queue, and each resolver reads the fold with every earlier write landed. A resolver may also say where the row belongs (turn scope, provider reference), and the writer always hears how the transition landed. A resolved lifecycle batch chooses its settlement mutations from the fold at execution. - New optional row field providerItemRef: the provider's own reference for the item a row is, written only where the row's identity cannot spell it (Codex keys messages by their place in the turn and renumbers its item ids on resume). Set by the creating write, kept by revisions, indexed by the journal fold, never read by clients. A downgrade test shows an older host and client render such rows unchanged. - Provider timeline identity schemes (shared legacy arm, Codex) and the join index that resolves a provider item to its row from memory or the fold: ordinals and request incarnations are read back from the rows, so a restart or an evicted entry finds the original row instead of placing a new one. * Recover message ordinals from the journal's highest place, and forget joins read from a replaced epoch A fresh join index continued a turn's messages at the first free place, so a journal holding only a later ordinal (an imported or removed earlier row) had its sequence back-filled. The place is now one past the highest ordinal any row or echoed send holds there, read through a pure scheme reader. The join caches also drop what they read when the journal's epoch is replaced. * Spell the subagent thread's message slot without spreading an identity union * Keep the journal store under its line limit after the main merge * Drop the provider item reference, join index and identity schemes from the transition PR Nothing in production reaches the state they defended (an assembler that lost its memory while its child keeps streaming the same turn), and the stored Codex id was positional. The legacy identity scheme moves to the assembler PR with its first caller; the Codex scheme and any persisted reference wait for Codex to move onto the assembler. The Codex ordinal counter goes back to codex/, since no neutral code imports it. * Write a resolved settlement in one transaction through enqueueRows A settlement too large for one row now commits all its rows or none, through the journal's existing all-or-nothing write, instead of a row-by-row writer. Every row is built before any commits, so a settlement naming one item twice is refused before anything is written. * Drop the transition's landing report; keep the turn-row write fire-and-forget Nothing reads which steps of a transition wrote. A failed step fails the sink, leaving the steps before it written; the header says so, and tests cover it plus a settlement whose second row fails inside the transaction. writeAgentJournalTurnRow returns nothing again, as on main. * Run a transition's steps as a prefix; drop the paced flag and resolved options A failed step no longer lets the steps after it write: each step checks, at its own turn in the journal's queue, whether the write handed over just ahead of it completed, using the queue's count of completed write bodies (a promise would report the failure only after the next step ran). The sink fails only once every step has had its turn. The item step's `paced` size bypass and the resolver's replacement `options` are removed; nothing planned uses them. * Run a transition's steps in one queued write that loops over them The steps of one event now share one turn in the journal's write queue: a loop writes each in its own transaction through the row writer's synchronous writeRows (split out of enqueueRows) and stops at the first throw. Prefix semantics and "nothing lands between the steps" now hold by construction, so the completed-write counter on the queue, the step gate and the allSettled barrier are gone; the queue is back to main's bytes. * Use current provider handles in transition tests |
||
|
|
dc8b3934b2 |
Add chat appearance settings: text size, code size, width (#25657)
* feat(native-chat): add chat-scoped color tokens * feat(native-chat): soften transcript and composer appearance * fix(native-chat): refine code spacing and faint text styling * fix(native-chat): wrap prose links at word boundaries * test(native-chat): refresh background task strip snapshots * Add native chat appearance settings and card scaffold * Persist chat text and code sizes with appearance width controls * Fix chat appearance typing, shortcut routing, and scoped typography * Use chat call identities in typography regression fixture * Keep markdown metadata typography independent of chat text size * Preserve terminal undo chords and stabilize chat resize estimates * Show only primary shortcuts in chat appearance settings * fix(native-chat): preserve appearance edits and configured zoom shortcuts * Remove unused chat appearance summary translation * Preserve command classification when applying chat code size |
||
|
|
6c693edf40 |
Show native chat tool calls as plain sentences (#25654)
* feat(native-chat): add chat-scoped color tokens * feat(native-chat): soften transcript and composer appearance * fix(native-chat): refine code spacing and faint text styling * fix(native-chat): wrap prose links at word boundaries * test(native-chat): refresh background task strip snapshots * feat(native-chat): show tool calls as plain sentences * fix(native-chat): make tool sentences reflect call state * fix(native-chat): clarify failed commands and subagent sentences * test(native-chat): exercise command disclosure with real results * fix(native-chat): preserve command inputs and localize failure rows * fix(native-chat): retain complete padded command input * fix(native-chat): read named tool input fields safely * Refresh native chat tool rows when the UI language changes |
||
|
|
8d2e9264bc |
fix(native-chat): ignore the terminal launch command when starting a native chat (#25720)
A custom launch command in settings made structured native chat unavailable: the renderer route, the host launch-mode decision, and the host's create-support all treated it as terminal-only, so a user with the chat default on silently got a terminal. The command names the CLI binary and now applies to terminal launches only; native chat ignores it. A start directory outside the workspace still forces a terminal. The internal route field and blocker are renamed to that concept; the wire reason `tui_launch_command` is kept and its receipt text now names the start directory. |
||
|
|
54ded3bc18 |
fix(relay): commit the cell counter in one round trip; cells boot without the database (#25765)
* fix(relay): commit the cell counter in one round trip; cells boot without the DB Step 2 (option B) cell image: - One-round-trip counter commit at acquireActivity, releaseActivity and activateControl: the final counter UPDATE and COMMIT go as one simple-query message. Server errors mean COMMIT never ran (retry as today; 22012 = no row, rolled back and disambiguated outside the transaction); a lost connection is never retried. - Cells skip the schema apply and region backfill, so they listen while the database is down and turn ready on their first successful query. - G13: rehome target connection headroom folded into the existing NOWAIT UPDATE, excluding the host's own reservation by key. - fixLevel on every runtime metrics line, plus declared (not applied) cell fix-level metrics and alert. - Per-desktop drain disconnect-gap measurement from existing log lines. - Census test that fails on floating database promises; fixes two shutdown sites. Lock-wait sample keeps the combined role. * fix(relay): make the outdated-image alert creatable: one PromQL condition, 1 h lookback, fixed floor A PromQL condition must be the only condition in its policy, and alerts on log-based metrics may look back at most 25 h. Replace the 6-day/7-day design with relay_cell_min_fix_level (tfvars, raised by a targeted apply after each wave) and one query: a serving cell below the floor or reporting no level, sustained 6 h. Drops the separate without-level metric. * fix(relay): review fixes: gap-script ordering, wider promise census, fused-path guard, row-busy as scheduled - Drain gap script: sort closes by time (gcloud exports newest first) and refuse an invalid drain start. - Census: any floating promise in relay src, including callback-discarded and never-read ones, with a reviewed never-rejects list. - Test the fused counter commit through the store the server builds, so a wrapper that stops forwarding commitWithFinal fails CI. - Same-cap shadow gate: a row-busy refusal (the host's own release still holds its row) is a scheduled 503, like an own early retry. No client change. * test(relay): judge drain redials by no host refused twice, not a refusal count The row-busy count tracks how many releases are still in flight at the dial (80 of 180 every run at 1 s, against a bar of 90). What matters is that the release has finished by the next dial: assert no host is refused twice, keep the time-to-placed p95 bound. * fix(relay): cap row-busy as scheduled at the drain-return admissions; bound the gap script's window Shadow gate: a row-busy refusal of a drained host follows its drain-return lane admission, so per minute only that many (plus a rounding margin of 2) are scheduled; the rest stay non-drain, so row contention the drain does not explain still fails the budget. Gap script: --drain-ended-at excludes the new container's closes after the roll; later grants still close a gap. |
||
|
|
fff8718c76 |
Improve Quick Open matching, file locations and recent history (#25371)
* Match Quick Open queries across path terms and identifier separators * Honor ignored-file and symlink preferences on file inventory hosts * Open pasted file locations and remember successful workspace file visits * Verify bounded directory listing preferences across execution routes * Preserve symlink preferences on legacy directory fallback * Preserve bounded Quick Open terms and separator alternatives * Revoke stale Quick Open selections and preserve literal file locations * Validate recent Quick Open candidates independently on their host * Use complete old-host inventory fixtures for Quick Open compatibility * Keep Quick Open validation current across palette and workspace changes * Route recent candidates through relay file listing dispatch * Record Quick Open hook renders as test snapshots * Localize Quick Open eligibility errors and search aliases * Validate file inventory with bundled search and runtime preferences * test: use directory junction fixtures on Windows * fix: negotiate Quick Open policies with nested SSH hosts * fix: refresh cached linked folders after target changes * Retry stale expanded links after reads settle and targets recover * Scope stale refresh request lifetime to the visible workspace * test: seed linked-folder fixture before watcher startup * Keep Quick Open responsive and compatible with older hosts * Exercise directory discovery with unprivileged Windows junctions |
||
|
|
c8ff7e8875 |
Stop repeated recovery of finished workers (#25679)
* Settle completed worker assignments and bound recovery retries * Reset queued explicit recovery before starting its retry budget * Use current provider identity in the inherited journal fixture * Keep late worker recovery automatic without repeating persistence work * Repair inherited validation fixtures after updating main * Keep the released parser available to compatibility tests * Limit recovery queries and background process inspection to pending work * Verify historical repair safety and live SSH process retention * Preserve the same provider import as main in the merge result * Reduce historical repair and duplicate workspace recovery work * Align startup prompt fixture with upstream Windows coverage * Keep healthy worker repair indexed and read-only * Align startup prompt tests with current main * Keep pending terminal releases progressing during recovery retries |
||
|
|
e2ccf52dc3 |
fix(remote-runtime): a paired terminal accepts input again after its host app relaunches (#25736)
* test(remote-runtime): reproduce dead input after a paired host relaunch A relaunched desktop host accepts RPC before its renderer publishes a window graph. During that gap session.tabs.list answers with an unpublished empty graph (publicationEpoch "none", snapshotVersion 0, tabs []). The client's reconnect inventory treats that as removal and retires the pane (retireRemoteTerminalId(-1)): the pane goes to "ended", the reconnect overlay disappears, and keystrokes never reach the surviving daemon PTY. Both tests are red on main by design; they are the repro for the fix. - e2e: holds the relaunched host's runtime:syncWindowGraph so the client's reconnect deterministically meets the unpublished host (4/4 red). - unit: transport-level repro of the same retirement (red in ~2s). - restart helper gains a beforeFirstWindow hook; LaunchOptions moves to its own module to stay under the max-lines budget. * fix(remote-runtime): a paired terminal survives its host app relaunching After the host app quit and relaunched (daemon still running), a paired client's terminal cleared its reconnect overlay and then ignored all input. The relaunched host answers session.tabs.list before its renderer publishes, with a synthesized empty frame. A paired client sees that frame through its navigation projection as epoch "none:client-navigation", which the shared "does this frame answer for the worktree" check did not recognise, so the pane read it as removal and retired itself. - hostSnapshotAffirmsWorktreeContents treats the client-projected placeholder as no answer at any version (the projection adds its navigation revision). - The reconnect inventory keeps polling on such a frame instead of retiring; a published frame lacking the surface still retires the pane. - Pushed frames that affirm nothing no longer report the surface absent. - When the bounded inventory wait ends without evidence, the first published snapshot carrying the same handle now reattaches the pane, rather than waiting out the ~3 minute auto-recovery deadline. Client-only: the host already labels the frame, old hosts send the same frame, and the wire is unchanged. The e2e helper drops its type assertions. * refactor(remote-runtime): simplify the host-relaunch reconnect fix - One epoch rule: the placeholder epoch, bare or with the shared client-navigation suffix, is no answer. Hosts only ever send it at version 0, so the version check is dropped. - Unpublished frames are dropped where pushed snapshots enter, instead of a nullable per-subscriber update. - A published same-handle snapshot fires the parked retry through retryNow(), which now also fires a retry parked while still 'recovering' (nothing is in flight then). This replaces state-reading wiring in the listener and lets online/resume fire it too. - The Windows e2e runs the fixture through PowerShell's call operator. * test(remote-runtime): prove the e2e hold beat the relaunched host's first publish The spec now asks the held host for its tab list and requires the unpublished placeholder before releasing, so a hold installed too late fails instead of passing against unfixed code. The helper keeps its gate in a typed global instead of Reflect lookups. * fix(remote-runtime): keep a fenced handle's parked retry revivable A retry parked for a handle that needs a replacement was consumed by an early external trigger and then refused by the epoch's snapshot-wait guard, leaving nothing to revive the pane. An external trigger now takes over that snapshot wait as a fresh attempt, and a republished fenced handle no longer fires the retry. Also refreshes comments that still paired the placeholder epoch with version 0. * fix(remote-runtime): a host answers an empty worktree for real once it has published The host sent its unpublished placeholder for any worktree it had no entry for, including one its window had never opened, long after startup. With the placeholder now read as "ask me later", a paired client opening such a worktree never got its first terminal. The host now sends the placeholder only until its graph first publishes. Afterwards a worktree with no tabs gets a real empty answer, and a client that was told "ask me later" during startup is sent that answer once on publication. Against an older host the client withholds the automatic first terminal for such a worktree, which the user can still create. A host push now fires only a parked retry, never cutting a scheduled backoff short. * chore(reliability-gates): reference the empty-worktree tests in gate commands * test(remote-runtime): find the never-opened worktree by repo id on every platform |
||
|
|
661372d514 |
Cancel abandoned file searches and prevent stale results (#25370)
* Bound relay git-grep records and clean up capacity failures Credits @OrcaWin for the original bounded-record proposal. * Bound filesystem listing and transfer metadata at the execution host Apply mobile limits before transport, list Markdown through its semantic producer, and retain complete directory results only within explicit capacity budgets. Stream SFTP directory packets and reject oversized transfer plans before reporting success; preserve narrow older-peer fallbacks and validate streamed response retention. * Release inactive Markdown candidates and support folder scopes Keep one current document snapshot per consumer and attach completion candidates to the actual editor model lifetime. Resolve folder workspace roots through existing workspace identities so their previews and completions receive the same authorized listing as worktrees. * Preserve full runtime inventories under aggregate byte budgets Leave unqualified inventories complete beyond 20,000 files. Charge retained paths and serialized bytes at local and relay producers, reject oversized full inventories explicitly, and bound old-peer response reassembly; keep caller-specific mobile and explicit result limits. * Bound SFTP directory handle cleanup waits Send CLOSE even after cancellation, stop waiting after five seconds without an acknowledgement, and ignore late callbacks. Preserve the original capacity or cancellation error and close handles returned after an aborted OPENDIR. * Preserve registered root spelling in Markdown document paths * Reject incomplete SFTP directory cleanup and final cancellation * Bound pending response bytes before stream ownership arrives * Preserve bounded directory reads on legacy SSH hosts * Allow bounded per-chunk padding at response byte boundaries * Bound response payload retention after stream metadata * Bound complete legacy Quick Open inventories and cache retention by bytes * Localize Markdown document listing fallback * Exercise relay search decoding with real byte streams * Preserve concrete relay stream fixture types * test: compare distinct bundled ripgrep platforms on Windows * Preserve Markdown discovery across Windows and legacy SSH hosts * Make WSL process termination available to the bounds layer * Cancel abandoned file searches and preserve search keyboard navigation * Invalidate searches when workspace roots or owners change * Cover root replacement and legacy search cancellation together * Exercise search cancellation through the current desktop access seam * Keep search ownership and cancellation contracts current * Share local text-search execution and close canceled subscriptions |