* feat(native-chat): name an agent's message by its current title, and open it from the queued card
The sender line on another agent's message and on its queued card now shows the
name Orca shows for that agent right now (its sidebar agent row or chat tab
name), falling back to the name recorded when it was sent. The queued card's
names open the sender, as the transcript row's already do.
* fix(orchestration): name a CLI agent sender by its tab's own name, not just its agent
A terminal agent's recorded sender name used only a manual tab rename, else the
agent type, so mail from CLI agents read as "Codex" or "Claude". The host now
names every sender the way Orca shows it: a chat by its rename or saved name, a
terminal agent by its sidebar agent row's name (rename, quick command, generated
or meaningful live title; a split pane only by its own title). The task title and
generic agent label are fallbacks. The renderer shows the live own name, else the
recorded one.
* fix(orchestration): preserve known sender names in message snapshots
* Preserve sender names across chat clears and terminal reuse
* fix: keep sender links on the recipient host across older runtimes
* test: type sender naming fixtures with their declared identities
* Fix sender ownership publication after clearing a chat
* Drop /clear sender lineage now that /clear keeps the same conversation
Main's #26579 keeps a native chat in the same session across /clear, so a
message's sender id still names the live chat. Remove the host's
orchestration-root projection, the optional status field, and the renderer's
lineage lookup and feed capability probe that only served it.
* fix(native-chat): underline a file path only once its workspace confirms it exists
Chat underlined any path-shaped text, so an example path an agent made up
(`src/app.ts`) looked clickable and a click said "File not found". The
terminal already checks a path on its host before underlining it; chat now
goes through that same code: the terminal's path finder, one shared
resolve-and-check step (terminal-file-link-target.ts), and the same
longest-match rule. The terminal's hover and click fallback use the shared
step too.
Fixes STA-9683
* fix(native-chat): recheck every shown file link when a turn ends or the host returns
Chat only forgot "missing" answers at turn end, so a file deleted or renamed
during a turn kept its underline, and a path the host could not answer
stayed plain until the message remounted.
Each answer now records the recheck generation it was asked in. A turn end,
or a reconnect / re-pair of the host answering for the workspace, starts a
new generation and re-asks only the paths shown by mounted messages; old
answers keep showing until the host replies, and only messages whose answer
changed re-render. A reply to an older question cannot overwrite a newer
answer. A host that cannot answer keeps the previous answer and is retried
three times with backoff, ending when no message shows the path or at the
next recheck. While an SSH workspace's connection is unresolved, paths are
treated as unknown instead of being checked on this machine, and the chat
starts over once the connection resolves.
* fix(native-chat): keep main's file-path shape rules and check existence on top
Chat detection goes back to main's rules: a candidate needs a folder plus a
rooted prefix, a line suffix or an extension, and prose spans are split into
tokens as before. Bare names and folders are not underlined, and ordinary
prose no longer sends word prefixes to the host. Existence is required on top,
so the underlined set only shrinks compared with main.
A rooted spaced span ("/Users/me/repo/src/x.ts to use src/app.ts") now also
offers its tokens, so both real paths link; a real spaced rooted path still
wins as the longest one that exists.
Tests: bare names and folders stay plain even when they exist, slash tokens
and versions stay plain when everything exists, `My Notes.md:7` is rejected by
shape, explicit links and fenced code never reach the existence check, plus
the rooted-then-relative and longest-span cases.
* fix(native-chat): catch up file links answered before subscribe; tighter recheck triggers
- A message renders (and asks) before it subscribes; an answer landing in
between was never shown. Each message records the answer its render used,
and on subscribe it re-renders if the answer differs and re-asks outdated
paths. A failure for a path no message shows yet no longer arms a retry.
- A rooted path ending a sentence ("/Users/me/r/a.ts.") inside a longer span
now links, with the punctuation kept outside the link. Unrooted spans are
unchanged, matching main.
- The checker takes the workspace's SSH connection id and decides
"connection not known yet" from it, so the provider memo needs no lint
suppression.
- SSH reconnects recheck only when the connection reaches connected or gets
a new generation, not on every intermediate status.
* fix(native-chat): never check a network share outside the workspace without a click
Chat checks every path in a finished reply on its own. On Windows, a local
check of a path like \\server\share\a.ts or //server/share/notes.md opens an
SMB connection to that server and sends the user's credentials, and agent
text can be steered by prompt injection. Such a path is now never asked
about and stays plain text, unless it is inside the workspace or is a known
workspace root. WSL paths (\\wsl$, \\wsl.localhost) and checks made by an SSH
or remote runtime host are unaffected. Terminal hover and click behaviour is
unchanged.
* chore: restore main's copy of structured-agent-session-queued-mutations.ts
The merge commit's format hook dropped one blank line in this unrelated file.
Committed without the hook so the file matches main byte for byte.
* refactor(native-chat): own the file-link checker in NativeChatView, not each chat view
The provider now sits where NativeChatView picks the chat view, and derives
the file-link context from the tab id. The two chat views keep only a
one-line turn-end hook, since only they know whether a turn is running.
Same lifetime as before: inside the session-keyed owner for structured
chats, and per resolution for terminal-backed ones. No behaviour change;
it keeps NativeChatResolvedView under its line limit after main's find-in-chat.
* fix(native-chat): refuse network shares on SSH hosts too; allow only the workspace's WSL distro
- The "no network share outside the workspace" rule now applies to SSH
targets as well: a Windows SSH host's relay would otherwise stat
//server/share/... and open SMB with no click. Remote runtime paths stay
exempt, since they are always inside the worktree.
- WSL paths are allowed only for the workspace's own distro (from the pane
runtime or the worktree path, via the same helper the path mapping uses),
after `..` is normalized, so `//wsl.localhost/../x` or another distro is
refused.
- Drop the \\?\UNC\ chat test case: the real detector never produces it.
* refactor(terminal): keep file-link host path helpers out of the open flow
terminal-file-link-target imported its pure path helpers from
terminal-file-open-routing, which also opens files and so loads worktree
activation, launch and the agent catalog. Since NativeChatView now hosts the
chat file-link checker, that chain reached tests that render NativeChatView
with a minimal i18n mock (native-chat-view-file-drop.test.tsx failed in CI).
getTerminalFileContext, mapTerminalFilePath, terminalLinkWslDistro and
terminalPathWslDistro move unchanged to terminal-file-path-mapping.ts;
terminal-file-open-routing imports and re-exports them, so its callers and
their mocks are unchanged.
* Show 'Pi isn't signed in' before the first send
A Pi chat whose agent started with no model to use now says so in the
existing availability notice, and the next send replaces that agent so
a sign-in made since takes effect.
* Re-read the sign-in notice when the chat's agent stops; ignore an exited Pi
A Pi's start verdict ends with that Pi: the notice now re-reads when the
host starts or stops the chat's agent, and a Pi whose process already
exited reports nothing.
* Keep one sign-in line after a refused send; re-read on every agent start or stop
The notice steps aside while the newest send was refused for the same
reason, and the chat re-reads its verdict whenever its agent starts or
stops, dropping an answer read before.
* Step the notice aside only while the refused send's own line is on screen
Uses the host's send order and the chat's rendered delivery lines, so a
deleted returned card or a trimmed row brings the sign-in notice back.
* Keep the sign-in notice when the refused message is no longer loaded
Agents often answer a failed session/prompt with a generic JSON-RPC message ("Internal error") and keep the real reason in error.data. Only OMP and Grok had hooks that read it, so OpenCode and future ACP agents showed the bare message.
The prompt error detail now reads, for every ACP agent: the agent's hook, else string data, else data.details, else the message. Other structured data is metadata and is ignored. OMP's details hook and Grok's string-data branch are now covered by that rule and removed.
Folder rows used the bare folder ID for active state, DOM IDs, virtual keys
and activation, so same-ID folders on two hosts collided. Use the existing
host-qualified worktree identity, matching git worktree rows.
Co-authored-by: m4air <m4air@Mac.localdomain>
* Preserve editor file identity during SSH host conversion
* Keep a dirty in-session editor tab on its file record during SSH conversion
A file opened over the relay in the current session carries route provenance,
so the no-host-evidence check repointed its tab to a fresh clean record and
hid the unsaved draft. Keep any retained record with no runtime owner instead.
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Distinguish remote browser service failures from server outages
* Use the shared browser_unavailable code in the stream failure classifier
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Sleeping-session resume, AI vault resume and the plain quick-command
terminal each carried a line-for-line copy of persistAgentLaunchTabOrder.
Call the helper instead. No behavior change.
* fix(native-chat): read a queued card's whole text, and keep Edit from hiding it behind a prompt
A queued card showed one clipped line, so a long message (often another
agent's, never typed by the reader) could not be read before Steer or
Delete. A clipped card now opens in place to its whole text; the phone's
two-line card opens on tap.
Edit moved the card's text into the composer and deleted the card even
while a question or approval card stood in the composer's slot, so the
text vanished from the page. Edit now waits, saying why, until the
prompt is answered; the pane and the queue read one check for whether a
prompt holds the composer's slot.
* fix(native-chat): keep the full-text hover title on a folded queued card
* fix(native-chat): phone card opens only when it clips; test the composer-slot check
The phone card measured nothing and made every card a button; it now lays
the text out unseen at its width and offers Show more only past two lines.
The Edit reason is no longer dimmed twice, and the shared composer-slot
check and the desktop toggle's keyboard reach have their own tests.
* fix(mobile): the unseen measuring copy of a queued card takes no touches
* fix(native-chat): open a queued card's full text below its row, at the card's width
In a narrow chat the opened text sat beside the card's actions and wrapped
a few characters per line. It now opens under the row, indented to the
text, the way the transcript's awaiting-input row opens its question.
* fix(native-chat): the queued card's toggle names the opened text only while it exists
* refactor(native-chat): the queue reads pending prompts itself, keeping the session hook under its line cap
* refactor(native-chat): keep this PR to reading a queued message in full; Edit moves to in-place editing
* test(native-chat): queue list rig passes the pending-prompt flag main's controller takes
* fix(orcad): provide execution host CLI in Unix managed terminals
* fix(orcad): keep the server starting when the CLI launcher cannot be written
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
* Native chat: Cmd/Ctrl+F opens find in window
Mod+F inside a focused native chat opens a find bar at the chat's top-right
corner. The bar is a separate sandboxed view stacked over the main window, so
its own input is never a match; main runs the window's find-in-page and relays
the match count back. Enter/Shift+Enter step, Escape closes and clears.
Also stops the terminal search bar from opening over a chat that covers a
terminal pane.
* Native chat: Cmd/Ctrl+F finds inside the chat transcript
Replaces the window-level find panel with a find bar inside the chat.
The panel searched the whole window, pasted into the composer, outlived
its chat, and let Escape interrupt the agent.
- Searches only the transcript text the reader can see and paints matches
with CSS highlights, reusing the markdown preview's search, now shared
as lib/dom-text-search-highlights.ts.
- Re-searches as the transcript streams or scrolls, keeping the current
match; a step leaves "follow latest" like a reader scroll.
- Escape closes the bar first and returns focus; paste and editing keys
stay in the bar's input.
- Terminal guard yields any chat.find chord inside a chat cover; a PDF
no longer takes Mod+F from another surface.
* Native chat find: round-2 fixes
- Escape: the chat root's React capture handler closes the bar before the
composer's own capture handler can interrupt the turn.
- One visibility rule for picking and revealing a match: typing no longer
scrolls to a match that is already on screen, and the bar's real rect,
not a full-width band, decides what it covers.
- Reveal scrolls nested scroll boxes (code lines, tool output, thinking);
text cut off by a box that cannot scroll is not counted.
- PDF find opens from its own tab and the file explorer again, but not
from another group, a chat, or a text field.
- Prompt cards no longer take focus from the find input; clicks on the
bar keep typing in it; buttons keep their own Enter.
- A match whose row scrolled away restarts from the first visible match.
- Streamed text growth re-searches every 120 ms instead of every frame,
and searches reuse their Range objects.
- Hover-only row chrome is marked and skipped instead of read by opacity.
- Chat find stays off in the web client, where the browser's find keeps
Mod+F.
- The text matcher moves to lib/text-match-ranges.ts with neutral names.
* Native chat find: round-3 fixes
- Text that held a match re-searches before the next paint: React
rewrites a growing text node whole, collapsing its ranges, so the
streaming paragraph's highlights no longer blink. Other text growth
keeps the 120 ms re-search.
- A disclosure's height animation ending re-searches, so an output
opened while find is open is counted.
- A prompt card that arrived while the find bar held focus gets it when
find closes.
- A reveal that cannot scroll no longer stops following the latest.
- PDF find follows the panel's chord owner (its tab is the active tab
of the focused group, the rule the browser pane uses), so it no longer
takes Mod+F from a chat, terminal or browser in another split, and it
opens again after clicking its own editor header.
* Native chat find: PDF find answers only its own window surface
The floating workspace panel keeps its own focused group, so a PDF that
owns its panel's chords no longer takes Mod+F from a chat in the other
surface (main window vs floating panel), matching how the browser pane
scopes a floating workspace's chords.
* feat(native-chat): one-time upgrade tip for pre-upgrade Experimental Chat UI users
Show the new native chat upgrade tip through the normal startup feature-tip
flow, only to profiles whose saved Experimental Chat UI setting was on before
this release. Main records that audience once, from the profile as saved by the
previous build, and never re-derives it from the live setting, so later
opt-ins, new installs and the future graduation stay excluded.
* fix(native-chat): keep the upgrade tip readable and dismissed
Focus Got it without scrolling and tighten the ja/es/fr copy so the title is
visible on open. Show the tip exactly like other startup tips (no resume-dialog
wait), keep the illustration's upward menu unclipped, and merge seen tip ids
on save so a stale client cannot bring a dismissed tip back.
* fix(native-chat): say "between chat and CLI" in French and pin the audience basis in tests
* Offer Resume in New CLI / Native Chat from the tab menu
Right-clicking a native chat tab now offers "Resume in New CLI", and
right-clicking a terminal tab running Claude or Codex offers "Resume in
New Native Chat": the same items, gating and handlers the Session History
row menu uses.
The tab finds its Session History row through the existing mappings (the
chat tab's session id against the row's structuredSession, and a terminal
tab's agent-reported provider session, the mapping tab titles use, against
the row's agent and session id), read from the Session History list scoped
to the tab's workspace and host. Both surfaces now share one gate,
resolveAiVaultSessionSurfaceSwitchTargets, one menu-item component, and
call handleResumeInNewCli / handleResumeInNewChat from
useAiVaultSessionLaunchActions.
* Share the Session History list request with the tab menu's chat/CLI move
The tab menu looked its history row up with its own list request, so it
never shared the panel's cached list (and evicted it), forced a full
rescan on every miss outside the panel's rescan budget, and inserted the
item above Pin/Close seconds after the menu opened.
- One list request (scope paths, depth, host, Antigravity flag) and one
renderer-cache read/write, used by the panel and the tab lookup; the
tab builds the panel's own request for its workspace.
- A warm menu answers from the panel's cached list at first paint with no
IPC; a cold one sends the panel's non-forced request and keeps its
result in the panel's cache.
- A forced rescan on a miss now takes the panel's shared forced-rescan
budget; no other force.
- No lookup on hosts where neither move can ever be offered (non-local).
- The item moves to the end of the menu, so a late insert shifts nothing.
- The panel and the tab compose the row's resume inputs through one
function; parity is pinned by unmocked tests.
* Revalidate the tab menu's chat/CLI move and allow it for remote-server chats
- The cached first paint is always replaced by the fresh list answer, so a
conversation a chat has since taken no longer offers Resume in New
Native Chat from a stale cached row.
- Native chat tabs in a remote Orca server workspace look their row up on
that server (it projects chat ownership), so Resume in New CLI shows
there as it does on the row. CLI tabs stay local-only; SSH stays skipped.
- A menu that closed between the two list calls no longer starts the
forced rescan or spends the shared budget; forced rescans stay local.
* Keep the tab menu's history lookup from writing the panel's result cache
A tab lookup wrote its list into the Session History panel's renderer
cache, so a panel opened afterwards could paint that pre-launch list
without asking the host and show a chat-adopted row under its old title.
The tab now only reads that cache for its first paint; the host's cached
list is already shared through the identical request.
* Offer the tab menu's chat/CLI move only from a fresh history answer
The panel's renderer cache never carries current chat ownership (the host
projects it per reply), so painting the menu item from it first flashed
"Resume in New Native Chat" on a terminal tab whose conversation a chat
had taken, and missed the move on that chat's own tab. The item now
appears only once the fresh (non-forced) list answer lands.
* Put the tab menu's chat/CLI move above Pin Tab and hide Switch to chat view
* feat(native-chat): add a chat-owned command receipt store
Adds the agent_session_command_receipts table and an insert-if-absent
receipt write that runs inside a command's own journal transaction. A
receipt records the command's identity, canonical hash, status and a
pointer to the row holding its result; it dies with its chat record.
Nothing uses it yet: the existing operation ledger stays authoritative.
* fix(native-chat): key command receipts like today's ledger ids
Rows are keyed by caller and operation id; a send looks across callers and
other methods look up only their caller's row, matching the existing ledger.
Bad rows and newer table shapes read as unreadable instead of throwing.
* fix(native-chat): order receipt lookups by insertion and key by operation id
Global lookups now take rows in insertion order, so a read and an insert
agree on which receipt answers an id regardless of the host clock. The
primary key leads with the operation id, so its own index serves both
lookups.
* test(terminal): check the runtime's workspace layout against structural rules
A pure rules check over the runtime's session partitions: a terminal in two
panes, a pane in two tabs, a tab outside exactly one group, tab rows and tab
bar disagreeing, two tab orders, and ids changing for the same pane, tab or
group across an update. Wired into the seeded layout model test after every
step, plus late-spawn cases (one known on main, kept as an expected failure).
* test(terminal): check layout rules on every write and race client commands with the window
The layout model now checks the structural rules on every in-memory
workspace-session write, not only at each step's end, naming the writer
of any breach. Three documented two-writer transients (main mints a tab
row before the window writes its tab bar) are allow-listed by rule, op
and writer; they must still heal by the step's end.
New model ops race a stale window save against a paired client's tab
close, a host-created split, and main's PTY-exit retirement. A new file
runs main's updateMobileSessionPaneLayout (old phone pane trees) against
the real Store; the case with no topology revision is an it.fails: main
drops a live pane and its binding there.
* Use catalog snapshots for native chat workspace reads
* Resolve native chat workspace directories from the catalog snapshot
- Stop falling through to folder-path parsing once the catalog is loaded. The fallthrough let a folder workspace on another host resolve to a local path.
- Look up model discovery settings by the worktree's runtime owner instead of the agent tab's, so a session's model list matches the worktree it runs in.
* commit
* Use worktree owner settings for native chat model discovery
* Resolve native chat files from catalog snapshots
DSH's managed hook posts to the hook server and never emits OSC 9999, so the
dsh-only first-party done lane never fired and tui-idle waits ran to timeout.
Give DSH turn-end hook authority through an agent-state-rules file, mark its
SessionStart as a session boundary, and drop the dead dsh special case.
* ci: share test transforms and remove six redundant checks
* test: remove obsolete project copies and two build string checks
* test: use the injected clock for exact launch readiness budgets
* fix: retain IME confirmation and retire disconnected Windows PTYs
* ci: include relay installer in existing Windows boundaries
* perf: import the canonical SSH manager for filesystem operations
* test: remove 42 source-spelling checks
* test: advance deadlines after real entry and await actual PTY exit
* Preserve native Windows exit acknowledgments and skip daemon-only smoke triggers
* Keep clear in the same chat and reset its provider context
* Derive clear pauses only from journal membership and cover older rewind clients
* Point queued command rewind test at the structured hook
* Group queue consume errors and rewind replay assertions
* feat(native-chat): pace and fade streamed reply text
Snapshots land a clause at a time, 50-700 ms apart. Drawing behind them
at an eased speed is what makes a reply read as flowing.
* feat(native-chat): glide the transcript when following and jumping to latest
The offset is still pinned at once; only what the pin moved is eased, so
nothing races the virtualizer or a reply that keeps growing.
* chore: list remend with the other bundled renderer packages
dependencies is for packages the main process loads at runtime.
* Bound streamed reply reveal and preserve chat interactions
* Smooth overlapping chunks without postponing reveal deadlines
* Fix first-reply following and pin settled layout changes
* Pin concurrent media layout changes without a follow glide
* Keep native chat layout stable when replies complete
* Model delayed browser scroll events in navigation tests
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Recognize native chat sign-in failures across structured agents
* Fix authentication failures and chat sign-in guidance
* Preserve sign-in diagnostics and command retry context
* fix(native-chat): keep visible sign-in guidance and localize startup rows
* fix(native-chat): separate auth diagnostics from retry guidance
* test(native-chat): align startup guidance expectations
* fix(native-chat): treat OMP's signed-out 'No model selected' as not signed in
* fix(native-chat): start a new agent for the send after it reports not signed in
* fix(native-chat): scope the signed-out check by fence and read without a snapshot
* fix(native-chat): keep a signed-out agent while it owes work, and read only its own rows
* fix(native-chat): keep a signed-out agent that may owe a wake-up turn, and skip the check during a Stop
* Recognize native chat sign-in failures across structured agents
* Fix authentication failures and chat sign-in guidance
* Preserve sign-in diagnostics and command retry context
* fix(native-chat): keep visible sign-in guidance and localize startup rows
* fix(native-chat): separate auth diagnostics from retry guidance
* test(native-chat): align startup guidance expectations
* fix(native-chat): start a new agent for the send after it reports not signed in
* fix(native-chat): scope the signed-out check by fence and read without a snapshot
* fix(native-chat): keep a signed-out agent while it owes work, and read only its own rows
* fix(native-chat): keep a signed-out agent that may owe a wake-up turn, and skip the check during a Stop
* Let automations pass extra agent arguments on each run
Automations could only use the host's default agent Arguments, so a
per-automation choice such as a different model needed a settings change
that affected every run. Automations can now save extra arguments that
are appended to the host's defaults on each fresh run. Extras are
allowlisted to model, effort, and Claude's --add-dir, and are refused for
permission, config, prompt, and session options.
- Add the field through the CLI (`automations create/edit
--extra-agent-args`), the RPC contract, the automation store, and the
editor's Advanced section, with validation at save and at launch.
- Merge extras over the defaults by replacing a model or effort option
the defaults already set, then re-tokenize to confirm the launch argv
matches what was validated.
- Gate writes behind a new runtime capability, and refuse to save or
migrate extras to a host that would silently drop them.
- Require a fresh session, since reused sessions cannot take new
arguments.
* Simplify automation argument validation and launch handling
* improve the extra args display
* fix translation
* Validate automation extra args during ORCA migration
Dormant automations imported from the migration manifest skipped the extra-args check. A disallowed flag, or Reuse combined with extra args, could persist and then fail every later run. The manifest parser now rejects those rows.
Also move the automation runtime capability constants into `automation-runtime-capabilities.ts`. `protocol-version.ts` re-exports them, so existing imports keep working.
* add more demo with effort
* fix(native-chat): a /compact sent while the agent works waits in line instead of being refused
* test(native-chat): build the admission test's submissions without a type assertion
* fix(native-chat): a /clear refused while the agent works says so plainly; a /compact behind an unsent message waits for it quietly
* test(native-chat): name the admission test's cases for the sends they model
* fix(native-chat): a /compact refused while the agent works reads plainly too; a command card offers no Edit
* test(native-chat): a refused /compact reads the plain sentence
* fix(native-chat): a refused command card is spent and said once; a command card offers Send while the agent is idle; /compact behind an unsent message is not taken
* test(native-chat): keep the queued receipt's narrowing inside the wait
* fix(native-chat): spend a command card only when its own turn says why; refuse a queued command without a hand-off, with its reason; a queue send on its way reads as sending
* fix(native-chat): a recorded send is no sending card; queue lists land at once; a spent card's wait says to run it again; only /compact or /clear give way to returned text
* refactor(native-chat): derive the sending cards inside the queued-messages hook
* fix(native-chat): keep protocol-version and the agent-session params under the line limit; the send capabilities get their own module
* fix(native-chat): with the agent idle, a command behind this window's own unsent message says it is still being sent
* fix(native-chat): the queued-commands capability stays a plain constant beside the others; main's send capabilities back to its text
* test(native-chat): keep the rewind suite under its line limit after main's growth
* fix(native-chat): one import per module in the command send; a rewind on its way holds commands, tested
* test(native-chat): type the rewind module mock with a type import
* test(native-chat): a command card cut short by a restart waits or is kept like any card; kept-card captions name only controls shown
* fix(native-chat): a held command card, which the queue skips, no longer forces later sends to queue
* fix(native-chat): a command's refusal line goes once what it waits on is gone
A refusal that names something the chat shows (the agent working, a pending prompt, background
tasks, an unsent message) is said only while that still holds, derived each render, and dropped
once it ends. Other composer errors are unchanged.
* fix(mobile): a command's refusal banner goes once what it waits on is gone
The phone's banner keeps a refusal naming the agent working or a pending prompt only while the
chat still shows it, dropped each render once it ends. Background tasks, which the phone can't
see, and generic errors behave as before. The host-reason mapping is shared with desktop.
* fix(native-chat): a refusal behind an earlier message names whether it is being sent or offers Retry
Each line now goes when its own state ends: a send that fails stops being sent, and a resend
stops offering Retry.
* test(native-chat): a Stop behind a typed /compact keeps the taken-back message on screen; the in-doubt case says so
* fix(native-chat): a command refused while the queue is about to send reads working, as the pane does
* Update held command fixture for the merged queue model
* Restart one host at a time in the recovery ordering test
* fix: preserve person queue reserve for chat commands
* fix(window): re-hide a covered window when the renderer-throttle lease is released
Restoring background throttling only clears Electron's disable_hidden flag; the
occlusion or minimize hide swallowed while the lease was held is never replayed,
so a covered window kept compositing at ~60 rAF/s until the next cover cycle.
After the last release, a discarded empty-rect stayHidden capturePage makes
Chromium's capturer-count decrement re-apply the current visibility, so the
pending hide lands without the page ever reporting visible. Skipped for a
focused renderer, best-effort and never awaited.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(window): let a broken re-hide capture surface instead of swallowing it
Drops the synchronous try/catch around the stayHidden capture so a missing
capturePage fails loudly; only the promise rejection is swallowed. Test
doubles that reach a final lease release now provide capturePage. Keeps the
focused-window skip (a covered window loses focus) and the empty rect.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* fix(browser): keep a streamed active tab painting, and re-hide it when the stream ends
When the streamed tab is the foreground desktop tab, the guest's own widget is
what a cover hides, and lifting the window throttle does not reach it, so the
phone froze after one or two frames. The page stream now re-applies the guest's
setBackgroundThrottling(false) at start and on the window's hide/minimize while
live. At stream end it drops those listeners, re-throttles the guest and replays
its swallowed hide with the same stayHidden capture the window lease uses
(now exported as replaySwallowedHide), so the guest stops painting.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* refactor(browser): hold the streamed guest through the renderer-throttle lease
The page stream now takes a rendererPublicationThrottle lease on the guest
instead of a bespoke guest-paint helper. The start unthrottle sets the guest
widget's disable_hidden flag (copied onto new widgets), so the window
hide/minimize re-apply listeners were no-ops and are gone. The guest gets the
lease's end-of-stream re-throttle and stayHidden re-hide, including the focus
skip, which holds for a guest because its isFocused requires the embedder's
window to be key and its root view focused.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
* test(runtime): give the mobile-session window doubles a capturePage
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Placement only measured the gap below the target, so a tall target or a short
pane sent the card to a side with no room and Radix slid it back over the
selection.
* fix(editor): route zoom gestures and app zoom to the PDF viewer
Ctrl-wheel / trackpad pinch over a PDF scrolled it instead of zooming, and
Cmd/Ctrl +/-/0 changed the (invisible) editor font zoom because the main
process intercepts zoom shortcuts before the PDF's keydown listener runs.
- The pdf.js session owns a non-passive ctrl-wheel listener for its
container and zooms to an absolute scale anchored at the pointer.
- App zoom commands on an editor tab are first offered to the PDF in the
command-owning (focused) editor pane; unclaimed commands keep editor font
zoom.
- Wheel, toolbar and shortcut zoom all record the persisted scale.
Co-authored-by: Infinity_X <Infinity_X@sinomo.net>
* fix(editor): make pinch zoom track the trackpad more closely
Halve the wheel-zoom divisor so pinch zoom in PDFs and images moves twice as fast, and halve the per-event delta cap so a single event still zooms less than 2x.
Co-authored-by: Infinity_X <Infinity_X@sinomo.net>
* test(editor): type the PDF ownership test's document fixture
* fix(editor): keep fast pinch bursts anchored to the pointer
pdf.js refreshes its saved location on the next frame, so a second wheel event in the same frame zoomed around the old location and drifted. Call viewer.update() after the anchor scroll. Also make the ownership test's reset assertion depend on reset running.
---------
Co-authored-by: Infinity_X <Infinity_X@sinomo.net>
* Keep foreground Codex commands out of the background strip
* Run the command-strip host regression in Node
* Exercise retained commands after their source turns end
#26416 pushed structured-agent-session-host.ts one line over the 300-line limit,
so main's oxlint is red and merges from main fail the commit hook. Move the
pure knownAgentIds logic into structured-agent-session-provider-support.ts
beside the other agent-support checks.
* feat(native-chat): host-owned startup attempt and hold-and-deliver barrier
The host now mints one startup attempt before any adapter acquires (lease
identity, spawn token, pinned launch context, cancellation, a 60 s deadline
from the mint that activity never renews, option intent, and the event and
child-work sinks), and the shared delivery loop hands nothing to a child
still proving its start. A send is still accepted and journaled at once;
readiness (the normalized `started` settlement) wakes delivery, which
re-checks the child, fence, close and Stop before handover. A start that
fails, crashes, or misses its deadline leaves held messages definitely
unsent; a dispatch that throws after handover is `unknown`, never inferred
unsent from the child's phase.
* fix(native-chat): time agent starts from spawn by progress, and release Claude on initialize
Review round 1 on the host-owned startup attempt.
- The startup clock now starts when the process exists and fails a start
only after 60 s with no stdout frame or stderr line, or 10 minutes after
spawn however chatty it is. Every agent's child reports output through
the one managed-process spawn, so a slow migration or history replay is
no longer killed mid-way. Each start's spawn-to-outcome time is recorded
as an `agentSession.startup` span for tuning.
- Claude reports `started` on its initialize answer; the settings read
follows and is applied and persisted as a later `options-reported`
update, so messages no longer wait on it.
- A started child's reported options are persisted in their own step after
the held messages are handed over, and only if no pick changed the
record's options since.
- An attempt now ends whenever its child ends or an expiry stopped it.
- Removed `optionsAsOf`, which was not an option revision, and comments
that described the old publish-and-write-at-once behaviour.
* fix(native-chat): keep the startup attempt's contract free of host and process code
The adapter contract imported the attempt type from the module that also runs
its clock and imports the host types, which pulled the model catalog and
process-scan code into the mobile test typecheck. The attempt's shape and limits
now live in a dependency-free contract module.
* fix(native-chat): never let a stale option report undo a later pick
A report of the child's options is now persisted only if no pick, no
model-not-found heal and no newer report reached the chat since its read
began. The host keeps one option revision per chat; the startup attempt
hands the adapter a reader for it, and each report carries the revision
it was read at. This replaces the record-snapshot check `started` had and
covers the settings report, which had none, so a settings read answered
just after a pick can no longer write the older model back.
Also: the option read a ready child does inside its start now ends with
the start (limit, Stop or close); a start that turns ready as the limit
fires is recorded as ready; the startup-attempt host tests run under the
Node SQLite runtime; and the retired-model test follows the new startup
order.
* fix(native-chat): require the option revision and order reports by arrival
The host can no longer mint a startup attempt without the conversation's
option revision, so no host path can stamp a report at a stale zero and
drop every report after the first pick. Adapter tests may still omit it.
A report is now dropped only when a pick or a value the child showed it
cannot run came after its read. Reports are ordered among themselves by
when the host takes them, and only the newest admitted one is persisted.
The settings read's stamp therefore holds however late the host takes
the start's report, instead of relying on that report being handled
synchronously before the read begins.
Tests: a settings read stamped before the host took the start's report is
kept; a report read after a failed pick is persisted; the minted attempt's
revision reader answers the host's current revision.