mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 08:03:33 +00:00
faececed4879b5bf4b54799c04ea87dceadfde4e
6179
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c867bd2c30 |
refactor(native-chat): remove the unused terminal handoff
No client ever called agentSession.requestHandoff or mounted the handoff chrome. Delete the handoff coordinator, the terminal-owner runtime, the proof write path and the unmounted UI. Keep agentSession.handoffStatus, which released desktop clients read for worktree activation, and let records an older build left mid handoff reconcile through the ordinary restart and recovery paths. |
||
|
|
fec8fe4822 |
fix(native-chat): offer a resume for every chat that was working, and say what it was doing (#22560)
* fix(native-chat): offer a resume for every chat that was working, and say what it was doing * fix(native-chat): keep a child-work resume offer after the chat is reopened The offer's cut-off work was read from the items' current state, so once the chat was opened (reattaching the provider, which rewrites the rows it lost), a chat offered for its stopped subagents or background tasks dropped out of the dialog and its retry. Judge the revision rows written after the marker's cursor instead: they only accumulate, so the reading is the same before and after reattach, and the per-run pre-reattach copy is no longer needed. Also pass the lease fence into the shared "shows work" check, as the status feed does, so a send stranded under an older fence cannot hold an unheld session's provider alive while the sidebar shows it idle. * refactor(native-chat): offer is the working check taken right before each child stops Every review loop found the same bug class in the "is this offer still owed?" re-derivation that ran when a child was stopped and again after restart. It re-read a journal the provider had already rewritten on reattach (a notice turn of its own, restated subagent rows) and kept misreading it: a send made after an earlier completed turn was dropped at shutdown, and Claude's notice turn refused the resume after restart. - Teardown now snapshots each session with the sidebar's working check in a new eviction step right before its provider child is stopped (after draining events Orca already accepted), and keeps it once the stop is proven. The capture-first/confirm-on-stop re-judgment is gone. - After restart an offer is withdrawn only by a newer user message, dismissal or expiry, beside the structural checks (record, support, lease, no fork). Listing, retryable and the pre-send check no longer re-derive work state. - The pre-send barrier drains again while a provider keeps streaming instead of refusing; both providers queue a message sent mid-turn. - Row activity stays a display-only read of the rows after the marker's journal cursor. * test(native-chat): pin the re-drained admission barrier and the proven-stop gate on offers * test(native-chat): cover a send-shaped offer across a closed provider notice turn * refactor(native-chat): drop the pre-stop drain nothing depended on * docs(native-chat): align marker and retry comments with the simplified offer rule * fix(native-chat): an unfinished admission drain no longer refuses the restart continuation The drain before the continuation's pre-send check refused the send when provider events were still arriving at its 2 s bound or the barrier failed. That refusal journaled the continuation as rejected, so its own message then read as the user moving on and the offer could never be retried. The drain is now best effort and the pre-send check judges what the journal holds. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin a stalled or failed admission barrier dispatching once Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): name a cut-off reply from the settlement's row, not the turn as restated A reattached provider may restate the offer's turn, so the row's mid-reply label read off the turn's current state could vanish and count the cut-off reply's own tool calls as background commands. Also corrects the ineligible-offer comment to match the user-moved-on rule. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(native-chat): the offer is a stop-time snapshot on the marker The marker records the main agent's own state, the pending prompts and the live child roster at the moment before its child stops; the dialog row, status bar and candidate read only that snapshot. Deletes the journal cut-off reader, journalCursor, the offer TTL and the pre-send admission drain. Superseded offers and chat closes now delete their records. * test(native-chat): every offer ending deletes the durable record * fix(native-chat): preserve restart offers on unreadable journals * fix(native-chat): release idle sessions with pending sends * fix(native-chat): keep a message held while the CLI starts from being released Idle release had been switched to the sidebar's working check minus pending sends, which dropped the rule that a send held while the provider CLI is still starting keeps the session. An unheld chat left during startup was then evicted and its message refused. Restore the release rule this branch never needed to change: an open turn, or a pending send while the child is starting. Also keeps the host file within its line budget. * fix(native-chat): delete a restart offer whose conversation forked A forked conversation can never become the marked one again, but its offer was only skipped, so with no expiry it sat unseen in the recovery file forever. Report it for deletion on the same path as a newer user message. Also corrects comments that still called listing read-only. * fix(i18n): keep Agent untranslated in the Japanese subagent activity rows The catalog keeps Agent in English for Japanese, and the localization gate rejects the translated form. * fix(native-chat): don't call a mid-reply command monitoring in the resume dialog The live task roster also lists the foreground command a reply is running, so a chat stopped mid-command read "Was mid-reply · Monitoring: <command>". Speak monitoring only for an idle lead, as the sidebar does; the row's tooltip still names every task. * chore(native-chat): state the mid-reply label rule exactly --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e0144a9eb6 |
fix(native-chat): show the Codex and Claude model picker the moment a chat opens (#22756)
* fix(native-chat): show the Codex and Claude model picker the moment a chat opens A new structured chat showed no model picker until its session had been created, spawned, initialized and had answered a model listing — and the picker then listed the models a second time. Codex's listing often goes to the network, so the picker took 0.6-2 s to appear. - Keep a host-owned model catalog per agent and account home, persisted on success only and refreshed in the background once it ages out. A new read-only agentSession.modelCatalog RPC answers from it without a live session; sessions reuse it instead of listing again. - Render the picker while the launch is still provisional, showing the saved default. A pick made before the session exists is held and applied once it publishes; only the host's acceptance saves it as the default. - Mark the model and effort set in the user's Codex config as the listing's default (config/read), so the first frame names what the chat will run. - Resolve the account a record-less read would use without running launch preparation, which writes and syncs account state. * fix(codex): disable plugins in the model catalog probe app-server * fix(native-chat): read the host model catalog only for panes on this machine * fix(native-chat): name a pre-report model only for a chat this view launched * test(native-chat): pin the launch latch across publish * test(native-chat): pin a held pick reaching the host before the first send * fix(claude): pin the catalog probe's config dir by the session spawn's rule * fix(native-chat): read the host model catalog only for a visible chat * fix(native-chat): rewrite the model catalog file only when a listing changes * test(native-chat): type-check the first-send order fixture * refactor(native-chat): keep the structured options hook under the line cap * fix(native-chat): send the first turn only after every pick held during launch settles * fix(claude): name no default effort from the catalog probe listing * fix(native-chat): name no listed default model for a chat resumed from history * refactor(native-chat): let the launch own picks made before it publishes A pick made while a chat launches had no fence to go to, so the pane held it and flushed it after publish; every other sender (the outbox, the launch prompt) then needed its own gate to wait for that flush. The launch now keeps those picks in its own state, applies them against the create receipt's fence before it counts as published, and every sender follows publish by construction. The pane flush, the outbox gate and the module-wide held-pick registry are gone. The launch also snapshots the saved selection its create seeds when the intent is built, so a pick in another chat no longer relabels one still launching, and a pick the host refuses is reported the way a refused mid-session pick is. * fix(native-chat): name no default model a workspace's own config can replace The catalog's default is the account's, read without a working directory, but a chat runs in its worktree, where a project config (Codex's .codex/config.toml between the project root and the worktree, or a Claude .claude settings file that sets a model) picks the model instead. The picker named the account default there while the chat ran the project's model. A new chat's catalog read now names its worktree. The host checks that workspace for such config (existence only for Codex, the model key for Claude) and, when any is present or the workspace is not a local directory, serves the listing with no default, so the picker names nothing until the chat reports its model. * fix(native-chat): name the listed default model before the report only for Codex * fix(claude): let an option pick made while Claude starts wait for it instead of being refused * fix(codex): name no listed default when the configured model is not in the listing * fix(native-chat): show the picker as unavailable until a published chat attaches * fix(codex): keep the catalog probe's listing when config/read stalls * fix(native-chat): write the pending model catalog save before quit * chore: drop an unrelated lockfile rewrite * fix(native-chat): rename the catalog store's listing parameter off the global fetch name * chore: drop an unrelated lockfile rewrite * fix(native-chat): name the model Claude will run before its first turn * fix(native-chat): keep Claude's pre-turn applied effort out of the saved session options |
||
|
|
8f7cbad07b |
feat(mobile): name the machine after pairing (#22104)
* feat(mobile): confirm host identity after pairing * refactor(mobile): unify host descriptor state * chore(i18n): translate the last-known host descriptor label The remote-host row's "Last known" label shipped in English only. Every other locale now carries it, worded as each catalog already words "last known". * fix(mobile): make the pairing naming step safe to abandon and show the machine live Pairing: - An unreadable status.get reply no longer strands the pairing race: the descriptor read ran inside the race's success handler and threw, so the candidate was never counted and a direct-only pairing sat on "Connecting..." until the timeout. The race now reads the status through a reader that returns null instead of throwing. - A pending pairing is now a small state machine: a save in flight owns the outcome (Cancel, back, or unmount no longer clear the journal under it), a failed save stays pending and the naming screen shows the error with Save still available, and Cancel never rejects. - When the desktop refuses relay provisioning, the journal is cleared before the naming step instead of at save, so an app kill on that screen no longer blocks every later scan with "recovery pending". - A pairing that resolves after the screen went away is cancelled rather than left with its journal. - The screens keep their root ref callbacks stable (the latest pending pairing is read from a ref) instead of re-creating them per pairing. - The label field's placeholder shows the name that an empty label saves. - "Is this an existing host" is derived from the id identity resolution hands back, so host-store and its tests go back to main's shape. Machine descriptor: - Mobile keeps the host-reported machine name and OS in memory only, filled by the status reads that already happen, and labels it "Last known" from the row's own connection state. This drops the per-host AsyncStorage copy, its web sibling and web-overrides entry, and the removal cleanup. It also fixes a latch: freshness used to stay true for the whole process once a host had answered. - Desktop reads the descriptor through lastVerifiedRuntimeStatus and marks it "Last known" using the same reachability verdict as the row's dot. - Drops the unused hostname/previousPlatform resolver inputs and moves the "OS · machine" formatting into the shared resolver. Also restores main's page-only Reconnect gate in the host header (#22326), undoes the no-op toStoredHostProfile reformat, and re-measures the web session route at 4222 modules (one fewer: the dropped web persistence file). * test(mobile): re-record RPC goldens for the deferred pairing save Repins baseline to the pairing fix commit and re-records every golden. Against main, 781 goldens move only in the header (baseline everywhere, adapterSha256 for the pairing adapter family). Six pairing goldens move in the body, and only in effect order: the pairing now resolves (closing its candidate sockets) before the naming step saves the host, and a refused relay provision clears its journal before the save rather than after. The set of effects and every outcome match main. This also removes the unhandled-rejection effects and the failed result-absent/result-null cells that the previous recording captured from the pairing race's throwing status read. * fix(mobile): keep the machine name out of the host header title while the label loads The host screen starts with an empty saved label and loads it asynchronously. With a descriptor already in memory, the resolver fell back to the machine name as the title for that window, so opening "Windows-Low Spec" briefly titled the header with the Mac's name. Read the descriptor only once the label is known. * test(mobile): build the pending-pairing status through its schema so the test typechecks The mobile tests typecheck ratchet rejected a partial status literal: the reply type keeps every optional field as a required key. Parsing the literal through the status schema yields that shape without a type assertion. * test(mobile-web): re-pin the session route closure after merging main #22301 added two src/shared modules this route reaches; its own CI never ran this suite, so the merged branch read 4224 against the 4222 pin. Measured on the merge. * feat(mobile): name each host by what its desktop reports Pairing saves the host immediately again and names it after the machine name the desktop publishes; every connection refreshes that name and OS, so a rename on the desktop reaches the phone. A name typed on the phone's Edit screen is kept as a phone-local override that wins; clearing it returns to the desktop's name. - Stored host profiles gain optional personalName, lastKnownMachineName and lastKnownHostPlatform; `name` stays the resolved value older builds read. Legacy records classify a generated "Host N" as desktop-named and any other name as a phone override. - The connection layer runs one retrying status probe per connected host and records the descriptor; the capability probe becomes a projection of it. - Rows and the header always show the OS, add the machine name under a phone override that hides it, and mark it "Last known" while the host is offline. - Removes the deferred-save naming screen and the one-shot home status fetch. - Name rules move to host-name-identity.ts and the host-list mutation queue to host-list-mutation-queue.ts; an unchanged mutation no longer rewrites storage. * test(mobile): restore the RPC recordings to main's Pairing saves the host back to back again and the recording adapter is main's, so every recording matches main byte for byte; the earlier deferred-save re-record and its baseline repin no longer apply. * fix(mobile): keep stored host name identity across snapshot saves and on the web page A connection re-saves its host profile snapshot on relay credential rotation or relay re-resolution. The re-pair merge let that snapshot's name identity win, so a phone rename cleared or changed since connect came back, and a newer desktop machine name rolled back. The stored record now keeps the name identity on every save; the save supplies the rest. The web page receives only the app's resolved host name, with no identity fields, so the docked host header treated a phone rename as "no override" and titled the host with the live machine name. The display hook now reads such a source the way storage reads a legacy record: a non-generated name is the user's label. * fix(mobile): hand the web page the host's stored name identity The page received only the app's resolved host name, so it had to guess whether that name was the phone's override or the desktop's name. It guessed "override" for any non-generated name, which froze a desktop-adopted name as the title after the desktop was renamed, and left an offline page header without the last-known OS and machine name. The shell now puts the stored personalName and last-known descriptor on the init host as optional fields. The page reads them exactly as the app does; a page handed its host by an older shell still falls back to classifying the name, and an older page ignores the fields. * fix(mobile): drop an unreadable host name field, not the whole host The stored host record and the page's init host checked the platform against a closed list and required non-empty names. A value this build does not know, such as a platform added in a later build, failed the whole record: the host list dropped the paired host and the next write persisted the list without it, and the page refused its init message. Those three optional fields are now salvaged, so an unreadable value drops only that field. Also states the one exception to the stored name rule (an OS reported without a machine name keeps the adopted name), and brings four mobile test files in line with the branch: the edit screen now saves `personalName`, and host opens now start a descriptor status probe. * refactor(mobile): name the shared host name fields for their role * fix(mobile): drop the "Last known" prefix from the host machine line The OS and machine name line under a host's name reads the same whether or not the host is connected; the connection status already says when it is offline, and a prefix that users could read as applying to the name added nothing. Removes the resolver's liveness input and the desktop row's translation key. |
||
|
|
8fa5883217 |
fix(terminal): stop dropping visible alt-screen output tagged as hidden-resize repaint (#22587)
* fix(terminal): stop dropping visible alt-screen output tagged as hidden-resize repaint After a resize that main received while a PTY was hidden, main tagged the next chunk it accepted as background. The hidden delivery gate drops chunks before they reach that tagging, so the tag outlived the hidden period and landed on the first chunk after reveal. On the alternate screen the renderer dropped that chunk without a model restore and "repaired" it with a cols-1/cols resize pulse. OpenTUI debounces SIGWINCH and ignores an unchanged size, so OpenCode's full post-reveal repaint was lost for good. Remove the hidden-resize tag lifecycle in main and the renderer's background alt-screen skip and pulse. Reveal already restores from the host model and queues live chunks until the restore completes, so later chunks are in order with the model and are written like any other chunk. The snapshot-restore path that omits a too-wide alt frame no longer pulses either: when the fit lands back on the capture grid (no real SIGWINCH), it requests a fresh model restore instead of relying on the app to repaint. * fix(terminal): restore a dropped reattach alt frame from the model when the fit lands on its capture grid The reattach replay omits a too-wide alt frame and relied on the grid push's resize plus SIGWINCH to make the app repaint it. When the fit lands back on the capture grid that is a same-size signal, which OpenTUI ignores, so the alt screen stayed blank. Record the omitted frame's capture width and request a model restore after the grid push in that case. The reattach fit moves to reattach-grid-fit.ts to keep apply-reattach-payload.ts within its line budget. * test(terminal): drop type assertions from the reattach alt-frame test * test(terminal): dispose the reattach alt-frame test's pane connection |
||
|
|
2c7609bf6a |
fix(terminal): serialize only the visible width after a column shrink (#22586)
* fix(terminal): serialize only the visible width after a column shrink xterm does not reflow the alternate buffer (or a normal buffer under pre-21376 ConPTY), so after a shrink each line keeps its old length. SerializeAddon walked every non-final row to line.length, so any snapshot taken after a shrink carried stale right-hand cells that wrapped into extra rows on replay; restores repainted that garbage and a differential TUI such as OpenCode never cleared it. Clamp the row walk and the wrap-boundary lookups to the terminal's columns in Orca's addon-serialize source patch, and regenerate the bundles, maps and lockfile hash per docs/reference/xterm-patch-regeneration.md. * test(terminal): read shrink-snapshot fixtures through public APIs Drops the private-terminal casts the casting gate flags; the normal-buffer case now drives a plain pre-21376 ConPTY terminal and its SerializeAddon directly. * fix(terminal): blank a wide glyph clipped by a column shrink when serializing After a non-reflowing shrink a width-2 glyph can have its lead half in the last column and its trailing half past the grid. Serializing the lead half makes the replay wrap it to the next row and shift every row below, so serialize that cell as a blank and keep the row exactly the grid's width. A glyph ending exactly at the edge is unchanged. * test(mobile): move the session closure pin past the main agent status modules #22452 added src/shared/main-agent-status.ts and src/shared/agent-turn-outcome.ts, which agent-status-types.ts imports, so the session route's closure grew by two local modules (4218 -> 4220). That change was src/shared-only, so its own CI never ran this suite; main has been at 4220 since, and any PR that fires the mobile web app job fails on the stale pin. Measured on |
||
|
|
7ea01279cd |
feat(search): bundle ripgrep for local, WSL, and SSH search (#22396)
* feat(search): bundle ripgrep for local, WSL, and SSH search Ship @vscode/ripgrep-universal's prebuilt rg for all six relay platforms in every desktop artifact. Local and WSL searches spawn the bundled binary and drop the git ls-files / git grep fallbacks; SSH deploys upload the remote's binary once per ripgrep version and the relay prefers it over PATH rg. * fix(search): address bundled ripgrep review findings - Key the SSH ripgrep cache on the binary's content hash; a package bump is the only update step - glibc verifier: read arch tokens below the slice root and accept static ELFs (arm64 release blocker) - Ship ripgrep/PCRE2/musl license notices; bundle rg with orcad - Packaged builds never spawn a bare rg; report fd pressure as transient - SSH: install rg before sweep/GC, size-validate installs, back off instead of disabling on launch failure - Scope Dependabot to @vscode/ripgrep-universal; revert unrelated lockfile churn * chore(search): drop bundled-ripgrep reference doc; assert full packaging layout parity * refactor(search): one entry point for spawning the bundled ripgrep Local Quick Open, Quick Open path search, the Explorer name filter, and runtime text search each repeated the same three steps: resolve the bundled command, spread in the WSL distro, spread in the WSL shell expression. Fold that into spawnBundledRipgrep so one place owns the rule that a bare 'rg' must never reach spawn, and simplify the resolver's command/packaged checks. Restore the AGENTS.md ripgrep rule dropped alongside its reference doc in |
||
|
|
1069bb053f |
fix(agent-launch): a cwd at the workspace root no longer forces a terminal (#22729)
"Continue in New Session…" always names a cwd, and both the renderer route input and the host launch-mode decision read any cwd as a custom start directory, so the continuation opened a terminal agent even when chat was the user's default. Both now share one rule: only a cwd outside the workspace root (after normalising slashes, Windows case, WSL aliases and the distro's Linux spelling) requires a terminal. A subdirectory still does, because a structured session cannot start there. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5e6fcca0b3 |
fix(native-chat): date a session by its own lifecycle, not its subagents' work (#22520)
* fix(native-chat): date a session by its own agent's rows, not its subagents' The journal reducer's lastActivityAt is the structured status summary's updatedAt, which the status row uses as its completion stamp and acknowledgement clock. It took the max over every journal row, and a session's subagents write into the same journal after its own agent has settled, so an idle parent was re-dated and marked unread on child work. A row now dates the session only when the session's own agent produced it: not a row whose producer linkage names a subagent, and not a subagent roster row (a subagent-group block), which the session writes but revises on every child transition. The roster rule is derived from the row body; no new persisted field. Replay folds through the same rule, so existing journals are re-dated to their own last row on reopen. Claude: a backgrounded subagent emits no child frames, so its re-dating came entirely from roster revisions (task_updated, task_notification) and from the stale-roster revision written when a journal reopens. Codex: the roster is revised on every child token-usage report; child-thread rows carry no producer linkage yet, and read as the session's own until they do. * fix(native-chat): a reopened journal's verdict on stale work does not date the session Reopening a journal settles rows the previous host left live (a working subagent roster, a live background task) to unverifiable. Those revisions were appended at the reopen moment, and a background-task row is the session's own non-roster row, so a crash-restarted session with a live shell was re-dated to the restart although no agent acted. The reconciler now writes each verdict revision with the row's own observed time. That is one rule at the one writer, covering both settle shapes; the render item's observedAt was already pinned to the row's first write, so nothing the transcript shows changes. The live-transition roster exclusion stays: live roster revisions are written by the providers, not here. The `recovered` row flag is not used as the discriminator: the live unexpected-exit settlement also writes recovered rows, and a clock rule keyed on it would stop dating a provider crash the host just observed. * fix(native-chat): date a session by the reducer's attribution of what a row wrote The clock read producer linkage off the raw row. A lifecycle batch names no row-level producer, a tombstone names none, and a revision may name none while the reducer still attributes the item to a subagent, so each of those dated an idle parent. The clock now asks the reducer: after a row applies, whether any item it wrote is the session's own work; before a removal, whether the item it removes was. * fix(native-chat): date a session's status by its own lifecycle edges A subagent writes into its parent's journal and keeps going after the parent settles. Every one of its rows advanced the summary's updatedAt, and the status row re-dated a done parent to it, so an idle parent read as newly finished and unread on each child step. The host now publishes statusStartedAt beside updatedAt: when the session's own agent entered its status, read off edges only it writes. Idle is when its newest turn ended; working is when the running turn was requested, or the earliest send still unanswered; attention is its own oldest pending ask, or a subagent's when that alone holds it. A turn that recovery settled after its host went away ended when that settle was written, so it reads as a completion the user has not seen; it carries no outcome, so no completion event or notification calls it a success. Render items carry recoveredAt, the recovered row's own write time, so nothing new is persisted. The sidebar bridge and the host ingest date the row and the main agent's clock from it whenever the row shows the main agent's own state, and keep their existing rules for a row child work holds open or a summary from an older host. The status feed republishes when the clock moves instead of on every idle row. * revert(native-chat): keep the journal clock over every row The row filter this branch put on the reducer's lastActivityAt decided which rows could date a session: a list of exclusions that each new row kind could slip past. The session's state is now dated by its own lifecycle edges, so the filter, its attribution helper and the backdated reopen verdicts go back to main. lastActivityAt, and the summary's updatedAt it feeds, is again the evidence clock over every row, including a subagent's. * fix(native-chat): keep republishing an idle session its live child work holds open A row held open by live child work is dated by when each reader saw the publish, and mobile decays a working row whose evidence is older than the staleness window. Suppressing row-activity republishes for every dated idle session froze that evidence, so a subagent running more than 30 minutes past its parent's turn made the row read idle on mobile. Only a session nothing holds open stays quiet on row activity now; its state clock is unchanged. * fix(activity): order an agent's timeline by when each state was seen An answered ask returns a settled parent to its own turn's end, so its done repeats the time of the done before the ask. Activity keyed and ordered events by that time: the new done collided with the old one and was dropped, and the row took its state from the newest-dated event, the blocked ask, so a done parent read Blocked and needed attention. Each state switch now records the `updatedAt` it was seen at. Events are keyed and ordered by that, while unread and "Clear completed" still compare the state's own time, so the answer neither re-lights unread nor revives a cleared done. The row's state comes from the pane's own status entry, so a clear that hid the done cannot leave it reading Blocked either. * test(activity): pin the timeline across repeated asks, a clear, and a stale turn Three parts of ordering the timeline by when each state was seen had no test that failed without them: - A second ask moves the answered done into history. Both dones share the turn's end, so only the history entry's own seen time keeps them apart; without it one done collided with the other and the timeline showed two Blocked events in a row. Three asks also exceed the per-pane cap, which must keep the most recently seen events, not the most recently started. - "Clear completed" on an answered row must cut off past the ask, which is dated after the done, or the cleared row stays listed. A done that the user cleared must also stay hidden once a later ask moves it into history. - A stale working row must not read as running just because the pane's own status says working. |
||
|
|
6ae6ed08bb |
fix(claude): open structured chat without a startup deadline, and make Retry start fresh (#22364)
* fix(claude): open structured chat without a startup deadline, and make Retry start fresh
Publish the Claude session as soon as its process is spawned instead of racing
initialize against a fixed 10s deadline. Prompts sent before startup lands are
held and written in order once it does. An exit or sign-in failure before startup
ends the session with the reason and the CLI's stderr.
A create that failed because the process provably exited now carries
ownerVerdict 'exited', so the client marks the launch failed and Retry mints a
new operation instead of replaying the stored failure.
* fix(native-chat): sending into a chat that failed to start restarts it
* fix(native-chat): a send with no live owner restarts it once
A provider child that timed out or exited hands its lease back, and every
later send was refused agent_session_ownership_unknown. Clients read that
code as "not admitted yet" and resend forever, while only a surface hold
could make a new child, once per mount, with its failure swallowed.
The send now routes to a live owner, otherwise restarts one from the
persisted resume state where resume eligibility allows it (single-flight
per session), otherwise refuses with the new settled
agent_session_owner_unrecoverable. Unverifiable, reserved and handed-off
leases are left alone. The desktop hold now logs its failure.
* test(native-chat): pin the unrecoverable refusal as settled in the outbox
* test(native-chat): pin the release clock after a send restarts an unheld owner
* test: read the sent operation id without a cast
* fix(native-chat): type the send-recovery record lookup as the store returns it
* fix(native-chat): a send ensures its owner before admission, and an unheld owner idles for 30 minutes
* fix(native-chat): a create that throws releases its event sink
A child that dies between spawn and journal attach can still write through
the host's event sink, which attach unbound in onAcquiring and never re-bound
because onAttached never ran. The orchestration released that sink only when
performAttach returned a refusal; a thrown failure (the root-exit path) kept
the sink cached with its queued write, so the next attach's drain barrier and
runtime shutdown's flush waited forever.
Also pins the publish-on-root-exit clause for a start that never proved:
deleting it reddened nothing before.
* fix(native-chat): a resend the journal answers restarts nothing, and a send joining a restart rebases from the fence it replaced
* fix(native-chat): the host learns a Claude start positively, and persists only proven options
A publish-first create used to read the session's options before Claude had
answered initialize. With startup pending that read fell back to the built-in
catalog's default, so `record.options.model` was persisted as `sonnet` for
every user whose CLI default is something else; an owner handoff or a reopen
then replayed `set_model('sonnet')` and silently switched their model.
The adapter now reports `started` once startup facts are applied and saved
options restored. The host keeps a `providerChildPhase` on the session it
owns: a starting child hands over nothing but the saved options as intent,
and the `started` event re-reads the options as fact and persists them through
the same record write a user's option change takes. The status summary carries
`hostExecutionPhase` (optional, wire-safe), and the chat pane says the agent is
still starting instead of showing nothing.
A child whose exit already reached the adapter before acquire returns is no
longer handed over as live; the create fails with the CLI's diagnostic.
* fix(native-chat): a hold and a send that find the owner gone share one restart, and a send the ledger already holds restarts nothing
* fix(native-chat): a failed create answers one refusal shape, stamped once at the boundary
A create whose Claude process was seen to exit answered twice in two shapes:
the first call threw a generic runtime error, and only the replay of the same
operation carried the `ownerVerdict: 'exited'` refusal that lets a client
retry under a new operation. Three sites stamped the verdict and the store
failure path stamped nothing.
The first-hand root exit is now returned as the refusal on the first call,
with the provider's own diagnostic as its message. The verdict is stamped in
one place, at the boundary of the attach, from the durable row the operation
settled to, so every refusal shape answers the same fact and no site can
forget it. The per-site stamps are gone.
* fix(native-chat): a send into a session whose child ended restarts it before admission
A session that published and then lost its Claude child before startup (not
signed in, for one) keeps a released lease and a chat the user can still type
into. The send was refused as ownership-unknown, the outbox parked it as
pending admission, and nothing ever restarted the child: the message sat
there until the user closed and reopened the tab.
A send reaching a session with no provider child now runs the same resume a
surface's first hold runs, before the write is admitted. The resume reserves
a new fence, so that send is answered stale with the published fence and the
client's outbox re-drives under it, as after any fence change. A resume that
fails is not this send's answer; admission reports the lease as it stands.
* chore: restore pnpm-lock.yaml to origin/main (local pnpm rewrote it)
* test(native-chat): pin the pre-handover exit as a failed acquire; stub the status feed in the delivery test
An exit the adapter observes before acquire returns now fails the acquire
with the CLI's diagnostic instead of handing over a dead child; the
published-then-ended path stays pinned by the slow-init startup case. The
delivery test renders the pane, which now activates the host status feed.
* test(native-chat): a same-ID re-hold over the wire joins the one resume, and a replay reopen goes on the idle clock
* test(native-chat): a re-hold that joins a failing resume proves one resume ran
* fix(native-chat): a create whose child was proven gone answers the refusal on the first call
The previous change answered a first-hand root exit as the exited refusal on the
first call, but the common failed start never took that path: when the close
ladder proves the whole tree dead the acquisition error is a plain one, the
store-failure classifier rethrows it, and the client still saw a runtime error
first and the refusal only on replay.
The cleanup that proves the child gone now names such a failure
`AgentSessionAcquisitionExitProvenError`, carrying the provider's diagnostic,
unless it already names its own verdict (a refusal, a typed exit proof, a host
store code). The attach answers both proven-exit kinds as the refusal its replay
gives. How a failed acquisition settles and how it is first answered now live
beside the verdict stamp, in the failed-create module.
* test(native-chat): pin the outbox re-driving a stale-refused send under the resumed fence
A send into a session whose child ended is answered stale once the host has
restarted the child. The outbox keeps that operation queued and blocked, and the
fence change the resume publishes re-drives the same operation under the new
fence; the host admits it.
* fix(native-chat): a child restarted for a send nobody holds is still released
The restart a send runs for a childless session takes no holder, on the premise
that the sending surface already holds one. A one-shot writer holds nothing, so
the child it restarted had no release clock and lived until the app quit. The
write resume now arms the clock when no holder is present, as the first-hold
resume already does. The send-after-failed-start cases also pin that the stale
answer's operation is admitted when re-sent under the new fence, and that two
racing sends restart the child once.
* test(native-chat): pin the picked Claude model across a resume whose child starts on its own default
The started event re-reads and persists what the child reports. A resumed child
answers initialize with its CLI default before the saved pick is restored over
it; the record must hold the pick while starting and after started.
* Revert "fix(native-chat): a child restarted for a send nobody holds is still released"
This reverts commit
|
||
|
|
8f18930c48 | feat(native-chat): add a hover copy button to sent messages (#22721) | ||
|
|
5610b11703 |
feat(ipynb): create a .venv when pip is locked out, and show ipykernel setup progress (#22710)
* feat(ipynb): set up ipykernel in a new .venv when pip is locked out, and show install progress in the dialog * fix(ipynb): drop the retired installFailed string from translated catalogs * refactor(ipynb): drive the setup dialog from one setup state; fix review findings - Kernel status now only describes the kernel; a single `setup` object (base, offer, phase, error) drives the dialog, replacing the extra statuses and the externallyManaged/setupError fields. - The picker's 'Create virtual environment…' opens the same dialog; success switches through selectEnvironment, so a running kernel is only replaced once the venv exists. - Async setup results are dropped when the dialog they belong to is gone (tab closed/reopened). - main verifies ipykernel imports after pip, reuses an existing .venv instead of re-running venv over it, and explains failures that printed nothing. - Windows copy command guards the install with if ($?); notebooks at a filesystem root get a correct .venv parent; 'Try again' shows for both retry paths. * fix(ipynb): close the setup prompt when another Python is picked |
||
|
|
1985c1b11e |
fix(native-chat): load older messages automatically before the reader reaches the top (#22541)
* fix(native-chat): load older messages automatically before the reader reaches the top The transcript paged older history only on an upward scroll within 80px of the top, and only when the visible row count had changed since the last request, so readers hit a "Load earlier messages" button and waited. A page that landed only hidden rows left the count unchanged and stalled paging. A sentinel above the first row is now observed by an IntersectionObserver rooted at the transcript scroller with a 600px top margin. The observer is recreated each time a page settles, so it keeps paging while the sentinel stays in range and stops once a prepend pushes it out or history runs out. The virtualizer's existing prepend anchoring keeps the reader's row in place. Both lanes' load calls now reject when a page does not land (error result, rejected read, host reset, exhausted re-anchoring). A rejection stops automatic paging and shows the manual button; a successful retry resumes it. While automatic paging works, the top shows only a polite status line. * fix(native-chat): keep the reader in place when the last older page lands The older-history row sat in the transcript column above the window, so the final page, which takes that row away, removed its height and the column gap from above the window and every row jumped up by that much. The row now sits out of flow, absolutely positioned inside the scroller's existing top padding, so its presence never changes the content height or the window's scroll margin. The sentinel keeps the same place relative to the top of the transcript, so prefetch is unaffected, and the button stays in the padding band above the first row. * fix(native-chat): keep auto-loading older history when a page is abandoned mid-flight A reconnect snapshot, replacement, or hide ends the lane's loading while its read is still outstanding. The auto-load latch stayed held until that read settled, so the recreated observer's report was dropped and paging stalled with no button. Release the latch once the lane reports loading, and have the transcript lane resolve rather than reject when a superseded read fails, so a replaced connection cannot switch the current transcript to the retry button. * fix(native-chat): let each chat lane alone own the in-flight older page The older-history autoload kept its own "page in flight" latch beside each lane's guard. It could not tell a request the lane had not yet reported from one the lane abandoned before rendering it (a reconnect snapshot in the same batch), so a read that never settled could stall paging with no retry button. The transcript lane now dedupes synchronously on the epoch its page is in flight for, so an epoch bump abandons it with no extra reset path; the structured owner already dedupes synchronously. The autoload hook drops its latch and the effect that cleared it. * test(native-chat): pin the structured page result and paging generation reaching the list The structured session test still fed a rejection into loadEarlier, a contract neither lane has any more. Assert the resolved page result and the paging generation the list needs to stop and re-arm auto-loading. |
||
|
|
59d8275996 | fix(i18n): align ko artifacts search keyword with value override (#22711) | ||
|
|
5613c4fe71 |
fix(i18n): add missing translations for artifacts and browsing (#22697)
Adds translations for artifact publishing, remote browser features, SSH workspace routing, browser identity settings, and skills management across all supported languages (Spanish, French, Japanese, Korean, Chinese). |
||
|
|
80e0bee23b |
fix(floating-workspace): keep agent launches from moving the main window's tab (#22603)
* fix(floating-workspace): keep agent launches from moving the main window's tab
Launching an agent from the floating workspace's "+" menu switched the main
window off whatever chat or editor tab it was showing and onto its terminals.
The main window's selection is supposed to move only for the worktree it is
showing: browser and editor tab creation, splits, moves and drops all check
`activeWorktreeId === worktreeId` before touching it. Two places did not:
- `launchAgentInNewTab` called `setActiveTabType('terminal')` without a
worktree, which targets the active worktree whatever worktree the launch
landed in.
- terminal `createTab` wrote the global `activeTabId` for a tab in any
worktree.
Both now follow the store rule. The launch still selects its tab within its
own worktree, which is what the floating panel renders.
The floating titlebar button had side-stepped this with an `activate: false`
opt-out plus manual selection. That opt-out had no other caller and is removed;
the button now launches and focuses like every other entry point.
* fix(tabs): scope the remaining launch surface writes to the launch's worktree
Three more launch paths create a terminal tab and then call
`setActiveTabType('terminal')` without a worktree, which targets whatever
worktree is active when the call runs rather than the one the tab landed in:
- the paired-host agent launch, after the host's asynchronous create
- Session History resume, which can target a worktree the user is not viewing
and activates it only afterwards
- sleeping-agent resume, which the activation gate runs after asynchronous
readiness checks, by which time the user may have moved to another worktree
Each now names its worktree, like the local agent launch. The new tab still
lands selected when the user switches to that worktree.
* test(tabs): pin the paired-host launch scope in its existing web-runtime test
* test(tabs): type the left-worktree resume fixture instead of casting it
* refactor(tabs): require the worktree that setActiveTabType applies to
`setActiveTabType(type, worktreeId?)` quietly fell back to the active
worktree when the caller left the worktree out. A caller acting on a tab in
another worktree (the floating workspace, a background launch, a reveal that
lands after an async step) therefore retyped whatever the main window was
showing. The launch paths fixed earlier in this branch were instances of that;
54 other callers still relied on the fallback.
The worktree is now a required argument (nullable only for the no-active-
worktree case), so every caller states which worktree it means and a new
unscoped call fails to compile. Each call site passes the worktree of the tab
it acts on; where that is by construction the active worktree (shortcuts,
palette, tab strip), the result is unchanged. `activateTabAndFocusPane`
resolves the tab's owning worktree the same way `setActiveTab` does.
End-to-end helpers that drive the store directly pass the active worktree,
which keeps their previous behaviour.
* fix(floating-workspace): let the floating New Terminal activate its own tab
The floating "+" New Terminal created its tab with `activate: false` and then
selected it with `activateTab`, because creating an active tab used to write
the main window's selected tab even for another worktree. `createTab` now
activates a tab only within its own worktree's group unless that worktree is
the one on screen, so the workaround is no longer needed.
Creating the tab active also moves the floating workspace's remembered tab to
the new one; before, it stayed on the previously selected floating tab, which
auto-acknowledge reads to decide which floating agent the user is looking at.
* refactor(floating-workspace): route every floating New Terminal through one creator
The floating "+" New Terminal had stopped deferring activation, but Cmd+T with the
floating panel focused still went through a separate creator that created the tab
inactive and activated it by hand, which left the floating workspace's remembered tab
on the previous tab. Both now call createFloatingWorkspaceTerminalTab, which creates
the tab active in its own group and focuses it.
* docs(tabs): say why an unowned tab id keeps the on-screen worktree scope
|
||
|
|
85642d0d88 |
fix(agent-status): count only agent work in stats, and read a Grok background subagent as working (#22474)
* fix(agent-status): renderer and recorder consumers read the question they mean Since #22295 a row's combined `state` reads `working` both while the lead's turn runs and while a subagent or background shell outlives a settled lead. The lead's own state now rides beside it (`lead`); each consumer in this slice reads the question it actually asks. - Smart sort and the Activity unread badge keep reading the combined state: their classes and rows are what the sidebar shows. Pinned with tests, including a restored `lead.state: 'working'` row that must never read live. - The stats recorder asks "was an agent executing" and now reads a shared derivation (`isAgentExecutionOwed`): the lead's turn, or live agent child work holding a settled lead's row open. A settled lead's background shell no longer accrues "Time agents worked". Old hosts without `lead` fall back to today's read; restored and replayed rows still never open a session. - The `agent.status.changed` plugin event gains `lead` as an optional field through one tested projection; `state` keeps its meaning and restored rows still project to nothing. - A Codex root Stop that follows an inferred interrupt keeps the `cancellation` verdict, as the Claude lane already does at its turn boundary, on both the hook and relay paths. * test(agent-status): pin that a child's approval wait no longer splits the recorded span The recorder's move to the lead fact quietly changed one more story: a Codex child's PermissionRequest turns the combined row waiting while the root's own turn keeps running. The old state read closed the span there and minted a second spawn on resume; the new read keeps one span, because the lead never stopped. Pin it at both boundaries (the shared derivation and the recorder) so the change is deliberate, not incidental. * fix(agent-status): date stats edges by this host's clocks and scope the accrual predicate to stats The recorder dated a start by the producer's mainAgent.stateStartedAt. An SSH host stamps that with its own clock while every stop is stamped locally, so each span gained or lost the clock skew. The same clock also survives a row that briefly lost the fact (an OSC repaint to another state), dating the reopen before the close already sent, and a subagent reopening a monitoring row took the row clock the hook lane pins to the main agent's turn start, re-billing the whole watch-loop window. Edges now use the row clock when the row settles or pauses and the evidence clock otherwise. Rename isAgentExecutionOwed to isAgentTimeAccruing and state that it is the stats question, not a liveness gate: it excludes watch loops, which lifecycle gates must keep treating as live. Note on the plugin schema that mainAgent.stateStartedAt is the execution host's clock. * fix(agent-status): pause agent time while the row waits on the user, whoever asked Time agents worked now accrues only while the combined row reads working. A child's approval or question wait pauses the clock exactly like the main agent's own prompt, and the pause edge is dated by the row's own clock. * refactor(agent-status): read agent time from the combined row and date edges by the row's own state Time agents worked now accrues while the combined row reads working and is not a watch loop. The shared fold emits monitoring only for a settled main agent, and hosts that predate the main agent fact did the same, so this is the same answer on every new-host row without reading mainAgent, and it applies the watch-loop rule to older hosts too instead of billing their monitoring windows. An edge that leaves working is dated by the row's state clock; an edge inside working is dated by the evidence clock. This also stops a live repeat of a hydrated working row (any row without the main agent fact, such as an OSC row) from dating its start at the persisted state clock from the earlier runtime. * fix(grok): read a background subagent as agent work, not a watch loop Grok's end-of-turn Stop lists each in-flight background task with its type (shell, monitor or subagent). Orca filed a running subagent with the shells, so a Grok subagent that outlived the main agent read "Monitoring background tasks" and, with the stats recorder now skipping watch loops, stopped the "Time agents worked" clock. Map shell and subagent entries to the shared child-work kinds and let the shared liveness classifier decide: any live subagent keeps the pane working, a shell alone or an active stop hook stays monitoring, monitors stay excluded. * test(agent-status): cover a waiting child in the fold's every-input accrual check Since the shared fold learned a child's human wait, a waiting child makes the row wait, so it must not accrue agent time whatever the main agent is doing. The exhaustive check now includes that input. |
||
|
|
122b8c25d7 |
fix(terminal): let Linux IMEs keep the candidate key for a preedit they own (#22607)
* fix(terminal): let Linux IMEs keep the candidate key for a preedit they own Sogou on fcitx draws its preedit and candidate list in its own window and opens no Chromium composition session, so every composition-scoped candidate guard is idle when the user presses Space or a candidate digit. The selector reached the PTY as literal text and the commit arrived after it. The marker the input framework does still deliver is the claimed letter keydown - `keyCode 229` / `key 'Process'` over the physical key. A bounded window armed from that claims the next Space or digit, and stands down for a real composition session, for the commit, and on expiry. Co-authored-by: nxiaobai <48705519+nxiaobai@users.noreply.github.com> Co-authored-by: Big-ios <18501866+Big-ios@users.noreply.github.com> Co-authored-by: cporoske <14369468+cporoske@users.noreply.github.com> * fix(terminal): keep the claimed-preedit window open while the IME edits it The window released on any keydown that was not a letter the IME had claimed, but the IME claims the keys that edit and page a preedit too — Backspace, the arrows, `-`/`=` for the next candidate page — and Chromium keeps the original `code` on those 229/Process keydowns. Paging to a later candidate therefore disarmed the window and the digit that picked from that page reached the PTY. A claimed keydown now refreshes the deadline instead of clearing it. Arming still requires a claimed letter, so a bare navigation key with no preedit behind it cannot open the window from cold. * fix(terminal): release the claimed-preedit window when a composition ends An engine that does run a composition session still emits the claimed `keyCode 229` letter keydowns that arm this window, and both its commit and its cancel travel as `insertCompositionText` — the one input type the commit release deliberately ignores. Nothing else closed the window, so a cancelled preedit left it open and the next literal Space was swallowed. Refreshing the deadline on every claimed keydown had widened that tail. `compositionend` now releases it, as `compositionstart` already did. The engines this guard exists for emit neither event, so their path is unchanged. The selector suite drives the state object directly and cannot see a missing listener, so the release wiring gets its own DOM-level suite. * test(terminal): pin a Space-committing composition session end to end The regression the compositionend release fixes needs the whole session to show up: compositionstart fires once, so every letter after the first re-arms the window, the commit and the cancel both travel as insertCompositionText, and the committing Space arrives claimed rather than bare. Only the end of the session closes the window before the user's next literal Space. Fails with the compositionend listener removed. * fix(terminal): end the claimed-preedit window on the selector the IME took A claimed selector means the engine picked the candidate itself, so the round is over. It reads `key: 'Process'`, which the selector predicate cannot see, so this asks the physical code instead. Belt and braces for an engine that opens a composition session and never closes it — a documented defect in at least one shipped input framework. Without a `compositionend` the session-end release never runs, and the refresh added for candidate paging would otherwise hold the window open across the commit and swallow the user's next literal Space. Paging keys still refresh, so a long candidate browse is unaffected. * fix(terminal): let a claimed Enter or Escape end the pick too Japanese and Zhuyin commit with Enter, not Space, and Escape cancels. Both arrived claimed and fell into the refresh branch, so for an engine that opens a composition session and never closes it they held the window across the commit — the same defect the Space case was written to close, reached by a different key. Arrows, `-`/`=`, Backspace and PageUp/PageDown still refresh, because those page the candidate list rather than ending the round. Physical codes rather than `key`: `key` reads `Process` on all of them, and `Digit*` survives AZERTY, Dvorak and Colemak moving the digit row. Also corrects the window comment, which still described the orphan-keyup picking window. Since claimed keys refresh it, it is now a stuck-state valve that only expires when the IME stops reporting. --------- Co-authored-by: nxiaobai <48705519+nxiaobai@users.noreply.github.com> Co-authored-by: Big-ios <18501866+Big-ios@users.noreply.github.com> Co-authored-by: cporoske <14369468+cporoske@users.noreply.github.com> |
||
|
|
ad6cb0e05c |
fix(worktrees): version every catalog publication so a stale listing cannot undo a create (#22507)
* fix(worktrees): version every catalog publication so a stale listing cannot undo a create
A worktree listing is a snapshot from when its scan began. The renderer treated any
authoritative listing that lacked a known worktree as proof of deletion, judged at apply
time against the live store, so a listing delayed past a create reply purged the new
workspace: tabs wiped, selection cleared to the landing, pending structured launch
tombstoned so the host session was closed the moment it published. #22311 re-runs a scan a
mutation overtakes, which covers a bump during the scan but not a reply that is simply
applied late, on the host or in the renderer, or a refresh that joined an older one.
The host now stamps every listing with the catalog version its scan began at (the existing
per-repo scan generation, scoped by a per-process epoch) and every create and remove reply
with the version the mutation produced. Clients keep the newest version applied per repo
and host; a listing older than that is not applied at all, not its rows, not its purge, not
the pre-merge terminal teardown. Coalesced joiners inherit the reply and therefore the rule.
Fields are optional on the wire; older hosts and clients keep today's behavior.
* fix(worktrees): relist after a refused stale listing and stop version churn
- A refused listing can be the only answer a caller gets (a change-event
refresh that joined an older in-flight listing), so fetchWorktrees lists
once more; that listing scans at or past the applied version.
- An equal catalog version keeps the held object, so a no-op listing no
longer writes store state on every refresh.
- Versions the client cannot order are treated as unstamped at ingest.
- worktree.rm takes the repo from its id selector instead of resolving the
worktree a second time, which also stamped nothing for an id two hosts share.
- A removal on one of two hosts sharing a worktree id records its version.
* test(worktrees): pin the scan-generation bump right after git worktree add on every create path
A listing is stamped with the generation its scan began at, so a create must
advance it before any post-add work can yield. Pins the local desktop, SSH and
runtime local create paths.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(worktrees): bump the scan generation right after git worktree remove
A listing is stamped with the generation its scan began at. Removals bumped it
only at the end, after watcher release, push-target cleanup and the metadata
purge, so a listing scanned before the git removal and one scanned after it
could carry the same sequence. Applied out of order, the older one restored the
removed row until the removal reply. Bump right after the git removal on the
desktop local, desktop SSH and runtime paths, as creates already do.
The ordering pins now witness the generation at the first step after the git
mutation rather than at the re-list, so moving a bump past any intervening
await fails them.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(runtime): stop leftover worker-recovery retries from firing into later tests
The legacy worker terminal recovery retry timer re-arms itself and had no
way to end, so a runtime from one aggregator test kept rescanning repo-1
through the shared listing mock during later tests, consuming the listing a
lineage create expected ("Worktree created but not found in listing").
Give the controller a dispose() that cancels pending retries and refuses to
re-arm, and have the runtime test harness dispose every controller it
constructed after each test.
* Revert "fix(runtime): stop leftover worker-recovery retries from firing into later tests"
This reverts commit
|
||
|
|
25d7c21fcb |
feat(native-chat): show context window usage in the composer (#22301)
* refactor(native-chat): move the composer's stop action into its own hook The composer is at its line budget; lifting the stop action out makes room for the context usage ring without changing what Stop does. * feat(native-chat): record the Claude CLI's context window facts on the structured journal A structured Claude session now keeps what the CLI says about its context window on journal rows, so every client reads the same answer and a restart replays it: - Each main-thread assistant response keeps its API usage. A subagent's response measures its own window, so it carries none. - The turn a result settles records the session's window: the largest contextWindow across the result's per-model usage, since side calls to a smaller model report their own smaller window. - After a result and after a compaction boundary the host asks the CLI for its /context breakdown (5s bound) and records the answer on the current or last turn. An answer is dropped when the main conversation moved, a send was accepted, a newer request was issued, or the session was released while it was in flight; a failure or an older CLI leaves the row unchanged. - A compaction boundary or conversation reset records that the used count is unknown until the next response or report, so the pre-compaction size is never shown as current. Every part carries its own host clock, and the reader takes the newest, since a revised turn row keeps its place in the transcript. The persisted validator admits every value the writer can write, including a zero auto-compact threshold: a row replay rejects truncates the journal from that row. * feat(native-chat): show context window usage in the composer A structured Claude chat shows a ring beside send once the journal can state the session's context usage. Hovering shows used/window with a bar and, when the CLI has reported its breakdown, one row per CLI category as a share of the window, largest first. Between reports the ring shows the newest response's usage against the newest window the CLI reported, marked as estimated. Before the CLI has reported any window, and after a compaction or reset until the next response, there is no ring. A terminal-backed chat shows none. * fix(native-chat): measure the context ring against the main thread's model window The result's per-model usage is cumulative across the session and includes subagents and side calls, so the largest window was often not the one the main conversation runs in: after switching from a 1M model to a 200k one, or when a subagent ran on a larger-window model, the ring read against the wrong window after every turn. Pick the entry named by the model that served the newest main-thread response, and among its [1m]/non-[1m] entries the one the result moved; fall back to the largest only when nothing names it. * fix(native-chat): keep the context ring moving through tool-only responses The live estimate lived on assistant message rows, and a response with only tool calls or thinking writes no message row, so the ring froze through long tool loops and stayed hidden after a mid-turn auto-compaction until the next text reply. Record every main-thread response's usage on its turn row instead, once per response, so the selector sees each one. * fix(native-chat): read the ring's window from the model the turn's init names The CLI keys per-model usage by the main loop's model string, [1m] included, and every turn's system/init frame carries that exact string, while a response drops the suffix. Match the init's key first, so a session that switched between the 1M and 200k variants of one model reads the right window; fall back to the newest response's model, then the largest entry. * fix(native-chat): correct the composer's control-order note for the context ring * fix(native-chat): keep a running turn's context facts when the host settles it A turn row now carries the live context estimate while it runs. When the host settles a running row itself (a crashed or stale generation, a close the translator never saw), it rebuilt the record field by field and dropped those facts, so after a crash the ring fell back to an older turn's size, or to a pre-compaction size the dropped reset had superseded. * refactor(native-chat): revise Claude turn rows from the journal so the ring survives a restart The context ring's facts were written to turn rows through an in-memory list of recent turns. A new translator is built on every acquisition, so after a restart or reattach that list was empty and every fact for a turn that was not open was dropped: a /compact as the first action after a restart never cleared the ring and never showed the fresh breakdown. Every Claude turn-row write is now a revision of the row as the bound journal holds it when the write runs. The sink gains a resolved revision that reads the target row and its body at execution; the queue runs one operation at a time, so the read-modify-write cannot interleave, and revisions are never coalesced. Lifecycle writes own the lifecycle fields and context writes own contextUsage; each keeps every other field. Only the open turn is kept in memory. A fact with no open turn lands on the newest turn row, and a report lands on the turn it was requested for. The persisted facts are simplified to a window, which now names the model it was measured for, and a single used part (report, estimate or unknown) that each write replaces. The ring reads the newest turn row carrying each part, and hides an estimate whose model the window was not measured for instead of dividing by another model's window. A turn opening, and a reset, count as activity, so a late report can never land behind a newer turn. Host settlement of a stale running turn now drops only the fields its verdict owns, so context facts and any field a newer build wrote survive it. * fix(native-chat): keep a turn row whose context facts this build cannot read Context facts are validated deeply, so one malformed or future-shaped fact made the whole turn row malformed, and replay truncates the journal from that row on. Replay now drops unreadable facts from a turn row, in item rows and in settlement batches, and keeps the row, the same way it already drops producer linkage it cannot trust. The ring shows nothing for that turn instead of the session losing its history. * test(native-chat): pin that a child exit mid-turn keeps the ring's last size A lifecycle-only revision, the end a turn gets when its child exits without a result, must keep the context facts the row already carries. * perf(native-chat): revise a named Claude turn row by key instead of scanning the journal Every Claude turn-row write walked every reduced journal item to find its row, even when it already knew the row's identity, so a long session paid O(items) per write on the main process. The journal now answers a keyed read, and a context report names its turn by row identity rather than turn id, so only a write made while no turn is open still scans. * fix(native-chat): tell a 1M window from a 200k one of the same model Responses drop the [1m] suffix, so after a switch between the 1M and 200k windows of one model the running turn was measured against the previous turn's window until its result arrived. An estimate now records the turn's init model, which keys the window exactly, and the reader requires the full model id to match. * fix(native-chat): show no ring for a context kind a newer host writes A paired client reads turn rows from the host unvalidated, so a used-count kind this build does not know fell through to the estimate branch and threw reading its missing usage. Only the kinds this build can measure now produce a ring. * fix(native-chat): keep the context ring through plan-mode turns on another model Plan mode can run a turn on a model the turn's init does not name (opusplan upgrades to Opus's 1M window). The estimate then carried only the response's id, which drops [1m], and the exact comparison against the window hid the ring for every plan-mode turn. The estimate now records the response's id beside the init's exact key, and the reader matches the base model only when no exact key was recorded. * refactor(native-chat): pair the context ring's window by model change, not by model id The ring divided the newest response's size by the newest window only when their model ids matched, which meant comparing ids from the init frame, the response, per-model usage keys and canonical ids. Those disagree in plan mode and across 1M and 200k windows of one model. The writer now knows when the model may have changed: after a model or permission-mode write that changes the value, when a restore cannot put the stored model back, and when a main-thread response comes from a different model than the one the window serves (an approved plan). It then marks the size unknown, holds estimates, and asks the CLI for its context report, which states the new model's window. Any new window, from a report or a turn result, releases the hold. The reader compares nothing: a report, or the newest estimate over the newest window. Turn rows no longer store window.model, window.canonicalModel, estimate.model or estimate.responseModel. * fix(native-chat): keep a late context report's window when only its count went stale * test(native-chat): pin that each turn's init lets its result restate the context window * fix(native-chat): open the context card on click and tap * fix(native-chat): wait a beat before a mouse hover opens the context card * fix(native-chat): publish each context write in the operation that makes it A context report answers after the turn's last frame, so a revision that waited for the next frame's publish reached live clients only on the next turn. Context writes now queue their revision and its publication as one operation. * fix(native-chat): write million-token counts with a capital M A lowercase m read as minutes on the context card. * fix(native-chat): keep the context card open while the pointer crosses into it The card closed the moment a mouse left the ring, so the pointer could not cross the gap into the card. Leaving now waits a beat, and entering the card cancels the close. * fix(native-chat): let Escape close the context card without stopping the agent The card keeps focus in the composer, so the Escape that closed it also reached the composer and interrupted the running turn. The composer now skips an Escape an open layer already handled. * fix(native-chat): show the context ring when the chat has not loaded the turn it belongs to The ring read context facts only from the rows the chat had loaded, so a reopened chat whose recent page started after the last turn row, or a live turn longer than the retained window, showed no ring until the next turn. The host now derives the newest context facts from its whole journal with the same selector the chat uses, and returns them on agentSession.options for sessions that write them. The chat prefers each fact its loaded rows carry and takes the host answer for a fact they lack. When a live batch revises a turn row older than the loaded window, the chat asks for options again so that answer stays current. * fix(native-chat): bound context refresh reads and refresh when the turn row is trimmed Each turn-row revision the loaded window missed started its own options read. Those reads share the session's host queue with sends and interrupts, and each asks the CLI for its settings, so a burst could pile reads in front of a user action and discard every answer before it landed. The chat now keeps one options read in flight and at most one behind it. A live turn longer than the retained window also lost its turn row to the trim without asking for a fresh host answer, so the ring fell back to the answer read at turn start until the next response. Trimming a turn row now asks again, like a dropped revision does. * fix(native-chat): show the context ring from the first response, sized from the session's model A new session has no measured window until its first result, so the ring stayed hidden for the whole first turn. The host now keeps the window the applied model's name implies (1M for a [1m] name, unknown for default, 200k otherwise) and writes it beside an estimate when the journal holds no window, or after a model write, until the result or the CLI's report replaces it. * fix(native-chat): imply a context window only from a [1m] model name A bare model name does not fix the window: first-party runs today's opus, sonnet and fable models natively at 1M while a gateway or cloud provider runs them at 200k, and opusplan and haiku run another model in plan mode. Sizing their first response at 200k read the ring about five times too full, so only a [1m] name implies a window now; any other name waits for the result. * fix(native-chat): size the first response from a report taken before any turn A model picked in a chat with no turn yet asks the CLI for its context report, but with no turn row the report's write lands nowhere. Recording it still marked the journal as holding a window, so the first response wrote none and the ring stayed hidden until the turn's result. The report's window now serves as the fallback a response writes while the journal holds no window, and recording a report no longer assumes its write landed. * test(native-chat): move the fake Claude connection out of the structured integration suite The context-report delivery case pushed the suite past the 800-line limit, failing repo-wide lint. The fake child now lives in its own fixture. |
||
|
|
b2940a18ff |
fix(file-explorer): toggle folder instantly on name click (#22325)
* fix(file-explorer): toggle folder instantly on name click (#22324) Single-clicking a folder name held back directory expansion for the 500ms double-click window (PR #10867), which reads as latency compared to the chevron and editors like VS Code or Zed. Toggle immediately on the first click (0ms) and skip the second click of a double-click rename so the folder does not flip back under the rename input. Delete the 500ms timer machinery. Fixes #22324 * Fix double-toggle of symlink directories during rename Treat symlinks the same as directories when skipping toggle on rename. Symlink rows remain file-shaped in the UI until activated, so checking only isDirectory would cause a stat and toggle during the rename flow. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
5ba9649058 |
test(terminal): pin Option-composed currency on a punctuation key (#22608)
#21082 widened the kitty composed-text check from ASCII to every printable codepoint, which is what restored French Option+$ -> €. Nothing pinned a composed character on a punctuation code: the Polish cases all sit on `Key*` codes, whose unmodified character the US fallback table also answers, so they pass even when the layout map is the only source of `characterWithoutOption`. Reverting the widening fails this case at kitty flags 1, 3, 5 and 7. Co-authored-by: rasata <93783+rasata@users.noreply.github.com> |
||
|
|
ca75bc4c8d |
fix(orchestration): type a request ahead of pasted dispatch briefs so Claude workers follow them (#22582)
* fix(orchestration): type a request ahead of pasted dispatch briefs so Claude workers follow them Claude Code wraps a bracketed paste in <pasted_content> and tells the model to follow instructions inside it only where the user's own message asks. Orca sent the whole dispatch brief as a bare paste, so Claude workers (Opus 5.5, Sonnet 5) refused it as suspected prompt injection. Every dispatch path now types a short lead line in the same PTY write as the paste frame, the preamble drops shouted rules, and dispatch detection accepts the lead line and pasted_content wrapper. Fixes STA-8200 * refactor(orchestration): tidy dispatch lead-line delivery after review - Share one dispatchPreambleSendOptions() across the four dispatch paths. - Fold every C0 control and DEL out of the typed lead line. - Bound the <pasted_content> tag scan and let compaction return null for non-dispatch prompts, removing the separate detector. - Restore the stay-off-other-channels rule in plain wording. - Test through the real status normalizer and trim duplicated assertions. Refs STA-8200 * test(orchestration): guard coordinator auto-dispatch lead line - Capture send options in the coordinator runtime fake and assert the auto-dispatch send uses dispatchPreambleSendOptions. - Drop the helper test that only restated its literal. - Share DispatchPreambleSendOptions with the coordinator runtime contract. Refs STA-8200 * docs(orchestration): fit the pasted-spec note inside the kernel line budget Refs STA-8200 * docs(orchestration): drop the pasted-spec note from the coordinator guide The typed lead line is the fix; the advisory note cost always-loaded context. Refs STA-8200 * fix(orchestration): type the dispatch lead line only for Claude agents Codex discards typed text that shares a PTY write with a bracketed paste, so the lead line never reached it. Only Claude Code needs the lead to follow a pasted brief, so known non-Claude agents now get the pre-lead bytes and unidentified agents keep the lead in case they are Claude. Refs STA-8200 |
||
|
|
7a4f080086 |
revert: #18790 (orchestration incarnation reap fallback and bundled Freebuff agent) (#22601)
This reverts commit
|
||
|
|
3ea15dd0a2 |
fix(native-chat): keep chats that failed to resume in the status bar and say what to do (#22448)
* fix(native-chat): keep chats that failed to resume in the status bar and say what to do After a restart, a chat whose resume did not carry on was reported only by a four-second toast that named nothing, and the status bar entry vanished because the reattach had already spent the offer. The host now files the outcome as a durable `failed` entry in the recovery capsule, with the refusal code and the prompt the offer quoted, and returns it from the restart-resume RPCs. The renderer shows a "N chats failed to resume" status bar entry, a count-only toast with Show and Dismiss, and keeps the resume dialog open with a status icon per row and a "To resume" line whose action is chosen from the reason. A failure dies on dismiss, on a successful retry, on the user's own send in that chat, or with the marker's 24h expiry. * fix(native-chat): keep the resume dialog unchanged and add failed chats as rows The failure view had replaced the resume dialog's title, checkboxes, preference box, and footer. The dialog is back to main's layout. A chat an earlier resume could not carry on is now an ordinary selectable row there, plus a status icon whose tooltip carries the reason, a dismiss control, and a "To resume" line. Selecting it and pressing Resume retries it; it is pre-selected only when a retry can succeed. Resumed chats leave the list as before. Also stubs the new failure listing on the cross-version wire host fixture, which the restart-resume RPC now reaches. * refactor(native-chat): release a failed-resume record where a send is admitted Keeps the host file at main's size, and only releases the record for a send the controller actually lets through. * fix(native-chat): note a failed restart resume in the chat and derive when it is settled The chat itself now says when Orca could not continue it after a restart, with an error (refused) or warning (unconfirmed) status row, so the failure survives the toast, a dismissed record, and another restart. A recorded failure is current only while the chat's newest user message is the one it had when the failure was filed. Listing re-derives that from the journal and prunes superseded records, replacing the in-memory set and the hook on every send. Failures move to their own optional top-level key in the recovery file, so an older build that rejects unknown entry states keeps reading its offers. A retried failure stays a failure through a rollback or a lapsed lease instead of returning as a pending offer, and the toast's Dismiss names only the chats the host listed as failed. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): check the older reader against a filed failure before any retry Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): file a failed restart resume against the chat as its attempt ended A failure was filed against the chat's newest user message read at settlement, after every chat in the action had finished. The chat's note asks the user to send a message, and one sent while other chats were still being continued became part of the filed state, so the failure stayed listed after the user had done what it asked. Each chat's newest message is now observed as its own attempt ends. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): let a reply that made a chat ineligible retire its failure When a restart resume reached a chat the user had already replied in, the attempt was refused as no longer eligible, but the failure was filed against that very reply. It then stayed listed as "finished on its own" until the user sent yet another message. An ineligible chat is no longer observed at the attempt, so its failure falls back to the reserved marker and the reply that made it ineligible supersedes it at the next listing. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): offer Retry on a failed resume only when a retry would run When the agent refused Orca's "continue" message with its own reason, the failed row fell to the generic guidance, which offers Retry and pre-selects the chat in the resume dialog. The refused message is already the chat's newest user message, so a retry is never eligible: it did nothing and the same "couldn't be resumed" toast came back. The host now reports whether a retry would run, derived at list time from the same predicate the retry applies to the failure's marker. Where it would not, the row offers Open chat and Dismiss and is not pre-selected. An older host omits the flag and the reason alone decides, as before. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): file only restart failures the user must act on A chat that stopped being resumable between listing and acting (it finished on its own or is waiting on the user) was filed as a failure with no note in the chat. It now just spends the offer. A failed reattach now writes the same in-chat note as a refused continuation, so every filed failure explains itself in the chat. An unconfirmed continuation's failure retires once the chat shows the continuation's own message opened the newest turn. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): clear a failed resume from the status bar once its chat moves on While a failed resume is listed, the restart store watches the host's status feed; when a failed chat's status or latest prompt changes after the list was read, it re-reads the host once. The host still decides whether the failure stands. Nothing is watched while nothing failed. The failure toast now counts only the requested chats the host still lists as failed, keeping the old count for a host that sends no list. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that an unjournaled continuation never retires its unconfirmed failure Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop telling the user to send a message into a chat Orca couldn't reconnect A failed reattach, or a continuation refused because another window or terminal owns the session, now leaves a note saying Orca couldn't reconnect the chat instead of advising a send that would meet the same refusal. The restart list keeps the reason-specific advice. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): keep the failed-chat re-read from undoing an action or missing a reply The status-bar re-read no longer runs while a resume or dismiss is in flight, and its answer is dropped if one settled meanwhile, so a dismissed failure cannot come back. A change to a failed chat already seen always triggers it, whatever the host's timestamp says. A chat whose unconfirmed continuation the host already retired is now reported as resumed instead of saying nothing. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that no failed-chat re-read runs under a resume in flight Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): typecheck the unjournaled-continuation case against a nullable marker Co-Authored-By: Claude <noreply@anthropic.com> * docs(native-chat): drop the stale "no arguments" note on the restart-offer params The dismiss call now names sessions, so the older comment contradicted the schema below it. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): count unconfirmed resumes apart from refused ones in the action toast The post-action toast said "N chats couldn't be resumed" for chats whose continuation may well have gone out, while the list and the chat itself say Orca couldn't confirm it. That wording invites a duplicate "continue" send. Unconfirmed chats now get their own count, classified by the outcome the host filed, so the toast matches the row it points to. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): stop offering Resume on a failure the host says cannot retry A failed row the host marks unretryable could still be ticked, sending a resume that could only fail again; its checkbox is now disabled and it never joins the action. An older host that omits the flag keeps today's selectable row. The status bar no longer calls a chat "failed to resume" when the host only couldn't confirm the resume, matching the dialog's own wording, and the mixed toast's second line now says "other" so it cannot read as the same chat. Co-Authored-By: Claude <noreply@anthropic.com> * fix(native-chat): skip unreadable failure records instead of rejecting the capsule A failure record this build cannot parse (a newer outcome, say) made the whole recovery file unreadable, so a downgraded build listed no restart offers and could not record new teardowns. Failures are advisory: parse each one on its own and drop what does not parse. Co-Authored-By: Claude <noreply@anthropic.com> * test(native-chat): pin that a failure record with an unreadable marker is skipped The skip-unreadable-failure test only covered an unknown outcome, so going back to the throwing marker parser for failure records still passed. A failure record usually outlives its offer entry, so a newer marker shape can appear only there. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8ba7f829ac |
feat(ipynb): run notebook cells in a persistent Jupyter kernel (#22581)
* feat(ipynb): run notebook cells in a persistent Jupyter kernel Replaces the fresh-process runner (which silently re-ran every earlier cell) with the user's own ipykernel, driven by a small bundled Python bridge over line-framed JSON. One kernel per open notebook: started on first Run, shut down when its tab closes or Orca exits (stdin EOF), and the kernel's own parent poller reaps it if the bridge dies. The header gains a kernel pill (workspace .venv/.conda recommended, PATH interpreters, Browse), Interrupt/Restart/Run all/Clear all, and a one-time missing-ipykernel dialog with Install. Outputs stream live per cell and are written into the document when the run finishes. * test(ipynb): cover the stalled-interrupt restart offer * refactor(ipynb): disable the kernel pill while settling; merge its classes with cn * refactor(ipynb): tie kernels to their renderer document and simplify the run flow - Main keys kernels per renderer, so a reload, renderer crash or closed window shuts them down, and two windows never share one notebook kernel. One close-driven cleanup replaces the separate exit and start-failure deletions. - The first run uses the nearest workspace env, else the first Python on PATH; the picker no longer opens itself, so its open state stays in the toolbar. Closing the picker brings the missing-ipykernel dialog back instead of dropping the queue, which also keeps a Browse pick's run. - Discovery marks the kernel starting, so a second run during it queues instead of starting a second kernel, and a tab closed mid-discovery no longer leaks one. - Running an nbformat 4.4 notebook gives its cells ids (upgrading to 4.5), so moving a cell mid-run cannot misroute its output. - The death notice drops stderr from before the kernel was ready (the unencrypted-TCP warning). - SSH and non-Python runs toast instead of writing a notice into the cell. - Windows conda envs are named after their folder. * fix(ipynb): install ipykernel into envs without pip uv-created venvs ship without pip, so Install failed with 'No module named pip' there. When pip is missing, bootstrap it with the stdlib's ensurepip and retry. The install moves beside the other interpreter probes, and the copyable install command comes from one helper. * fix(ipynb): address PR review comments on stream errors, old jupyter_client and the Windows venv hint - Swallow stdout/stderr stream errors on the bridge child, as spawnProcess requires, so a broken pipe cannot crash main. - The bridge exits (reporting the death) even when cleanup_resources is missing (jupyter_client < 6.1.5) or raises. - The install-failure hint suggests `py -m venv .venv` on Windows. * feat(ipynb): add Cancel to the missing-ipykernel dialog It does what Esc does: drops the cells waiting on the kernel. * fix(ipynb): recover from a rejected kernel start; quote the install command per shell - Discovery moves into start, so one catch turns a rejected listPythonEnvironments or startKernel into the usual failed start: the session returns to off with the error in the cell, instead of sticking at starting. - The copyable ipykernel command quotes the interpreter only when its path has whitespace, prefixing PowerShell's call operator on Windows. Install itself still spawns without a shell. * fix(ipynb): always shell-quote the copyable ipykernel install command Quote the interpreter path for every path, not only ones with whitespace, so paths with shell metacharacters like & copy as a working command. Single quotes are literal in POSIX shells and PowerShell; embedded quotes are escaped per shell, and PowerShell keeps its & call operator. |
||
|
|
5e3effc32f |
fix(native-chat): show every user message on the message rail, not just loaded ones (#22558)
* fix(native-chat): show every user message on the message rail, not just loaded ones The message rail was built only from transcript rows the renderer had loaded, so any prompt above the loaded page had no tick, and the rail lost ticks when a long live session trimmed its retained window. The host now answers `agentSession.conversationOutline`: every user message in a structured session's journal (item id, creation sequence, a preview cut to 200 characters, image count) plus the journal position it is current through. It is derived from the reduced journal on each request with the same projection the transcript runs, so an entry's id and preview are what the loaded row shows. The reply is bounded like a history page: previews shorten, then drop, and only then do the oldest entries go. The renderer asks only while the pane is visible and older history is unloaded, uses outline entries only for messages older than its loaded window (the window is authoritative for the rest), and falls back to loaded messages while the outline is stale (epoch change, or the window trimmed past what it covers). Selecting a tick with no row pages older history in until the row exists, then uses the existing rail jump. The method is negotiated with `agent-session.conversation-outline.v1`; a client never calls a host that does not advertise it, and any failure leaves the rail on loaded messages. * fix(native-chat): keep a rail jump from the bottom from re-arming follow and cancelling itself A rail jump started by a reader following the end stopped a few pixels above the bottom instead of reaching the message. Paging older history in for a jump always leaves the reader following at the very end, so jumps to unloaded messages hit it every time; a jump to a loaded message from the end did too. The jump scrolls smoothly, and only its landing is marked as the application's own scroll. Its first frames move a pixel or two, still inside the band where a reader event re-arms follow, so the list read the jump leaving the end as the reader arriving at it. The next frame, just outside the band, then read as the reader taking over and rebased the view with an instant write, which cancels the smooth scroll. Re-arming follow now needs the reader to be arriving at the end: an unmarked event that moved the view up never reattaches a detached reader. * test(native-chat): check a rail jump left the end before reading where it landed * fix(native-chat): keep the rail's message list still while a press selects an item With the whole conversation in the rail, its hover list overflows and opens scrolled to the message being read. Clicking an older item did nothing: pressing it focuses it, which turns the hover preview interactive, and that switch re-ran the effect that scrolls the lit row into view and focuses it. The list moved under the pointer between press and release, so the click landed on the list instead of the item, and focus jumped to the lit row. Revealing the lit row now follows the list opening (and its rows shifting), not the switch between hover and interactive. Entering interactive moves focus into the list only when focus is not already on one of its items. * perf(native-chat): keep the rail's outline entries stable while a trimmed window slides A long live session holds a head-trimmed window, so every new row moved the oldest-loaded edge and rebuilt the outline view even when no user message crossed it. The rail then re-merged, re-rendered and re-read the scroll geometry on each new row. The view is now reused while the set of entries older than the edge is unchanged. * fix(native-chat): let a rail jump wait out an older page already loading Scrolling to the top of the loaded window asks for the next older page. A rail jump made while that page was in flight asked again, got the lane's immediate no-op return, read it as a page with no progress, and dropped the click. The jump now waits for the in-flight page to land before deciding. * fix(native-chat): reattach follow when content shrinking clamps a reader onto the end The rule that stops a smooth scroll leaving the end from re-arming follow compared offsets, so it also refused a reader whose offset dropped because settled content folded away beneath them and the browser clamped them onto the end. They sat at the bottom without following, and the next reply grew out of view. Re-arming now requires closing on the end rather than moving down, which still rejects a scroll leaving it. * fix(native-chat): keep the rail hooks' ref writes out of render Both hooks wrote a ref while rendering, which React may replay or discard. The rail's structural-sharing baseline is now recorded after commit, and the history jump calls the lane's page loader from its effect instead of through a render-updated ref. * fix(native-chat): let the latest rail pick win over a jump still paging A jump to an unloaded message keeps paging older history until it lands. A pick made meanwhile lost to it: a loaded message scrolled into view, then the earlier jump finished and pulled the reader away; another unloaded message was ignored. Picking a loaded message now cancels the paging jump, and picking an unloaded one retargets it without asking for a second page. * fix(native-chat): step a rail history jump with a functional update The step that requests the next page wrote the pending jump from the value its effect closed over, so a pick or cancel queued since that commit would be overwritten. * refactor(native-chat): run a rail history jump as one abortable awaited loop The jump through unloaded history was an effect-driven state machine that guessed "no progress" from the message list's identity and could not be cancelled by anything but another rail pick. A diff reveal, "Jump to latest" or the reader scrolling left it paging, and when its page landed it pulled the reader away; a history read that kept failing during a live turn could repeat back to back. Loading an older page now reports how it ended, and a second request while a page is in flight joins it instead of being refused. The jump is an awaited loop that reads the rail from a commit after each page, stops on anything but a page that moved the window, and is aborted by any other navigation, reader input (wheel, touch, scroll keys, scrollbar), a session switch or unmount. The latest pick wins. * fix(native-chat): derive the rail outline from the transcript's own projection The host built the outline from user items alone, while the transcript orders every message by when it was observed, folds tool results into the turn above and then drops harness turns. An imported user row carrying a tool result beside harness text therefore got a rail tick previewing the harness text, and clicking it paged history for a row that never draws. The transcript's order-fold-strip projection now lives in one shared function. The renderer's list projection wraps it with its own tail-row order, and the host runs it over the whole journal and keeps the user rows that draw content, so the outline lists the same messages in the same order. * fix(native-chat): retry a failed rail outline read a few times A failed outline read left the rail on loaded messages until a new gap opened, the pane was shown again or the epoch changed. The client now rejects a failed read (a host without the outline still resolves to nothing, without being called), and the rail retries up to three times with doubling backoff. * test(native-chat): give the rendered-transcript fixture the older-page result contract * perf(native-chat): sort the shared transcript projection without a spread copy * fix(native-chat): let a wheel over the rail cancel a rail jump still paging The rail forwards its wheel to the transcript, so a reader scrolling there is scrolling the transcript. That wheel never reached the scroller's reader-input handlers, so the jump kept paging and later pulled the reader to its target. * fix(native-chat): keep the rail's list open while a picked message pages in Picking a message that is not loaded yet can take several pages of older history. The list closed on the pick, so its busy item was never seen and the click looked ignored. The list now stays open with that item pulsing until the jump lands or is abandoned, and stops revealing the lit row meanwhile so the rows do not move under the pointer. * fix(native-chat): keep the shared transcript projection loadable on mobile The projection moved to src/shared, which mobile's Hermes engine also loads, and switching its sort to toSorted broke the Hermes compatibility guard. Sort a copy made with Array.from instead, as other shared code does. |
||
|
|
5802b54579 |
fix(rate-limits): read OpenCode Go usage with the Go API key (#22551)
* fix(rate-limits): read OpenCode Go usage with the account API key Since OpenCode's console migration (upstream fe51b0b19a, "fix(console): restrict legacy access to Black"), an account with no Black subscription is redirected from the legacy console to /console/login, so Orca's cookie-based workspace lookup returns nothing and the Go bar stays empty. Fetch usage from GET https://opencode.ai/zen/go/v1/usage instead, which authenticates with `Authorization: Bearer <key>` and needs no console session. The key resolves in order: Orca settings override, OPENCODE_API_KEY, then whatever OpenCode itself stored on /connect -- auth.json for 1.x, the credential table for 2.x. The cookie path stays as the fallback so Black/legacy accounts keep working. A 403 EntitlementError now reads as "no OpenCode Go subscription" in the status bar instead of a generic refresh failure (#22257's reporter was misled by exactly that). * fix(rate-limits): prefer OpenCode's stored Go key over OPENCODE_API_KEY OpenCode applies the key saved on /connect after the environment, so the stored key is the one its own Go requests use. OPENCODE_API_KEY is also the Zen provider's variable, so ranking it first could read a key that OpenCode itself is not using for Go. Co-Authored-By: Claude <noreply@anthropic.com> * fix(rate-limits): name the API key when OpenCode Go usage lands on sign-in A redirected usage request arrives as a 200 sign-in page because Electron follows redirects; report it as a rejected key instead of a parse failure. The cookie path's empty workspace lookup is what non-Black accounts now hit after the console migration, so its message points at the API key rather than only the workspace override. Co-Authored-By: Claude <noreply@anthropic.com> * chore(i18n): add the OpenCode Go API key strings to the English catalog Co-Authored-By: Claude <noreply@anthropic.com> * docs(rate-limits): stop calling the credential table an OpenCode 2 marker Verified on two real Windows hosts running OpenCode 1.18.16: the `credential` table exists there too (empty, same columns), so its presence does not identify a 2.x install. Neither host had an `auth.json` at all. The resolution already probes both stores on every version, so only the comments were wrong. Says so now, and records that a 2.x install which never ran the legacy import has no `auth.json` either — which is why both tiers exist. * refactor(shared): move GhosttyImportPreview out of global-settings-types Adding `opencodeGoApiKey` pushed global-settings-types.ts one line past the 300-line ceiling, failing `oxlint` in CI. AGENTS.md forbids a max-lines suppression, so split instead: the Ghostty import preview is a distinct concern that never belonged in the settings-shape file. Re-exported from the original module so no importer changes. 293 code lines now. --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b0ae7d18a0 |
fix(opencode2): resolve subagent session lineage so child work stops taking over the pane (#22444)
OpenCode 2's plugin adapter unwraps a single-property `{ data }` success schema,
so `ctx.session.get` resolves to the bare session record. The shared lineage
lookup only accepts `result?.data?.id === sessionID`, and OpenCode 2 has no
`session.list` fallback, so `resolveRootSessionID` returned null for every
session and `childState` was permanently null.
With unknown lineage `canFailOpen` is true for attention events, so a subagent's
`permission.asked`/`question.asked` fell through and pinned an un-evictable
blocker keyed to the child's own session id — publishing a subagent as if it
were a root. Observed in hook posts: SessionBusy for a child session id whose
`session_v2` row carries a parent.
Envelope the result in the OC2 client shim so the shared lineage module works
unchanged; OpenCode 1 already receives enveloped results and is untouched.
Also adds `opencode2` to the double-Escape interrupt list, extracted into one
shared helper so the server inference and renderer gate cannot drift. A single
Escape was inferring an interrupt, and Escape is how the Subagents dock closes.
7 of 11 new lineage tests fail without the shim.
|
||
|
|
b7a4fee700 |
fix(agents): stop claiming an unconfirmed OpenCode handoff succeeded (#22546)
* fix(opencode): stop claiming a handoff prompt was delivered when it was written blind "Continue in New Session…" to OpenCode reported success even when the prompt never reached the TUI (#22479). The paste-after-ready helper falls back to a blind write when the composer-ready signal never arrives and only the agent process is known to exist; that write was indistinguishable from a real delivery, so the continuation showed its success toast. - pasteDraftWhenAgentReady / pasteDraftToAgentPtyWhenReady report the blind fallback via onUnconfirmedDelivery, plumbed to launchAgentInNewTab as onPromptDeliveryUnconfirmed. - The session continuation hedges instead of claiming success, and both the failure and the hedged notice offer "Copy prompt". - OpenCode gets Codex's 20s composer budget. Both are quiet-window-less signals anchored on DECSET 2004, which ConPTY never forwards, so on Windows that budget is the settle delay before the blind paste. * test(runtime): retarget the 8s startup budget test off OpenCode The main-runtime startup-draft budget test used `opencode` as its stand-in for "an agent without an override", which this branch invalidates by giving OpenCode 20s. It failed with "expected vi.fn() to not be called at all, but actually been called 1 times" — the readiness signal now legitimately arrives inside budget. Point it at `claude`, which still takes the 8s default, and add a companion pinning OpenCode's 20s: a readiness signal at t+10s, past the old default, must now deliver the draft. Removing the override makes that companion fail. |
||
|
|
f2ac9f29b2 |
fix(browser): let pixel capture hold its own page drawn, without the desktop window (#22534)
* fix(browser): let pixel capture hold its own page drawn, without the desktop window Screenshots were the last browser commands that still borrowed the desktop window: they took the per-page automation-visibility lease, which waits for two desktop-window animation frames (capped at 2 s) and never arrives when the window is minimized or throttled. Only pixel capture actually needs a page drawn — input, scripts, layout, the accessibility tree and PDF all work on a hidden page. Capture now takes a main-owned paint hold: a synchronous, per-page ref-count that tells the renderer one way (no reply awaited) to keep the page drawn and keeps the desktop renderer unthrottled while held. Both Orca's full-page capture and the agent-browser helper's screenshots take it in cdp-screenshot.ts and retry on a bounded schedule until the page answers with a frame; a CDP error fails fast. Deleted: the queue's needsPaint lease, the executeJavaScript acquire path and its two racing 2 s timeouts and late-token cleanup, the renderer's rAF wait and window bridge, the capture commands' own leases, the fixed 300/500 ms settle waits, and the global one-screenshot-at-a-time lock. Rebased onto main after #22528 landed; content identical to the reviewed branch head 7b390ed6a8. * fix(browser): probe for a frame instead of repeating the full capture Retrying a capture resent the caller's full request, so on an already drawn tall page (a full-page capture takes ~0.5 s) the 250 ms retry started a second full beyond-viewport capture while the first was still running. Measured on Electron 43: any later request makes a held page produce a frame, and that frame answers every pending capture with a full, correct image. So the capture is sent once and 1x1 probes follow until it answers; their results are ignored. Also report a detached debugger as detached rather than destroyed, and give the layout-metrics timeout its own "did not respond" message, since that request doesn't need a drawn page. |
||
|
|
80f5aae0f9 |
feat(agent-status): publish the main agent's own state beside the combined row state (#22452)
* feat(agent-status): publish the lead agent's own state beside the combined row state
Every status producer folded the main agent's state together with live child
work into one `state`, so a lead that had finished while a subagent still ran
read `working` and its own state was lost. The row now also carries
`lead: { state, outcome?, stateStartedAt }`, admitted by the one payload
normalizer on the relay wire, IPC and disk, and published from the Claude hook
lane, the structured host ingest and renderer bridge, Grok (now on the shared
fold) and Codex (own combine kept). The persisted child-only boundary flag is
derived from `lead` plus child evidence and no longer written; old rows map
onto `lead` at hydrate. Combined `state` and `workingMode` are unchanged for
every reader; a cross-lane parity table pins that, with the cancelled-turn
watch-loop story recorded as a known divergence.
* fix(agent-status): make Orca's inferred interrupt the primary source of a Claude lead cancellation
Current Claude Code sends no hook at all on a cancel and no is_interrupt on
Stop, so the cancellation enters the lead record from the server's inferred
interrupt and rides into the next real Stop; is_interrupt on a turn boundary
stays as the secondary source for builds that send it. Comments, the store
reference and the parity table say so; no suppression changes.
* docs(agent-status): the child-only boundary comment now describes the persisted shell fact
The old sentence said a hydrated row no longer carries the shell fact, which is
the opposite of the mechanism: claudeRunningNonAgentTask is persisted precisely
so hydration can read it, and only a pre-lead row lacks it — reading as
shell-free, the same assertion its legacy flag made at write time.
* rename the lead fact to mainAgent: the main agent's own state
* docs(agent-status): the inferred cancel comes from Ctrl+C, not Esc
* fix(agent-status): an inferred interrupt keeps an already settled main agent, and the row verdict docs name its inferred source
* fix(agent-status): a child-induced wait publishes the main agent state it displaced
* fix(agent-status): decide child-held Claude rows from the saved main agent fact
Restart seeds the Claude main agent from the row's saved mainAgent whenever it
settled and no shell held the row, instead of re-deriving a child-only shape.
OSC cannot settle or repaint a row child agents hold open, including a row
waiting on a child's permission prompt. A sticky child permission prompt still
records the main agent's own progress, and OSC repaints and inferred answers
keep the shell fact beside the main agent they preserve.
* fix(agent-status): keep a finished turn's main agent verdict and clock with that turn
A Claude SessionStart restarts the main agent's clock instead of inheriting the
previous session's last Stop. A Grok idle prompt or session end, and a late
Codex root Stop after an inferred cancel, restate the same finished turn, so
they keep its recorded verdict; only a new turn clears it.
* test(agent-status): publish the Grok verdict restatement past the late-event window
* docs(agent-status): describe hydrate seeding and the OSC refusal from the saved main agent fact
* fix(agent-status): push a held child permission row when its main agent changes
* fix(agent-status): keep the shell fact on a held child permission row so restart does not settle it
* docs(agent-status): note the held child permission row carries the shell fact and is pushed
* fix(agent-status): pair the Claude shell fact with the main agent at the one row-build point
Every non-hook rewrite (terminal-title repaint, inferred answer, held child
permission) had to re-carry the shell fact beside `mainAgent`, and each one that
forgot let a restart settle a row while a shell still ran. The row builder now
pairs the fact once: a listener event restates it, any other write keeps it only
while `mainAgent` is unchanged. Restart seeds a settled main agent only when the
row says no shell ran, and legacy child-only rows map to that explicitly.
A held child permission now also accepts the main agent event's background
evidence, as it already accepts its `mainAgent`, so the child's drain no longer
settles a row a shell still holds. The renderer keeps a previous `mainAgent`
only for writers that never carry one, so a hook row without it matches the
host snapshot.
* test(agent-status): pin that restart never seeds a main agent from a row silent about its shell
* docs(agent-status): the row builder pairs the shell fact with the main agent, and restart seeds only on an explicit no-shell
* test(agent-status): name the legacy-row case parameter for what it holds
* docs(agent-status): name which rows carry the main agent fact
|
||
|
|
800d33e5c9 |
feat: name runtime machines (#22094)
* feat: name runtime machines
* fix: preserve pairing address optionality
* fix(cli): keep host and environment listings local
Listing paired servers read each one's machine name by dialing it, so both listings made a network
round trip per server and waited out a timeout on any that were offline. They answer from this
machine's own pairing store; `orca host name --environment <name>` reads one server's name.
* fix(settings): caption the machine name paired devices actually receive
The caption read the runtime's published name once, when the pane opened, so saving an override
left it naming the old computer while phones already showed the new one. It now re-reads whenever
the saved override changes; the settings write lands in the main process before the store publishes
it, so that read already sees the new name. The name is interpolated rather than baked into the
fallback, and the caption, label and placeholder are in the English catalog.
* refactor(settings): normalize the machine name in one place
The trim and length rules for `machineName` were spelled out separately at the
renderer IPC (trim + 255), the settings load path (trim only, no cap), the RPC
schema (zod trim + 255) and the runtime reader (trim). A hand-edited or legacy
profile could therefore load a longer name than any writer accepts.
`src/shared/machine-name.ts` now owns `MACHINE_NAME_MAX_LENGTH` and
`normalizeMachineName`, and every writer and the load path use it. The RPC
schema keeps rejecting over-long names but derives its cap from the constant,
and the runtime settings controller normalizes an RPC write before storing it.
* fix(runtime): detect the machine name once and label handoffs with it
Every runtime constructed in a process (the app, plus each one a test builds)
ran its own `scutil` lookup. The friendly name is a property of the host, so the
lookup is now a single shared promise; construction still never blocks on it,
and a rejected lookup can no longer surface as an unhandled rejection.
The structured-chat handoff banner ("Agent is open in terminal on X") named this
host with the bare `os.hostname()` while paired devices saw the published name.
The transport now reads the same `RuntimeMachineName`, through a getter so a
rename in Settings is reflected without rebuilding the transport.
* fix(cli): print the name the runtime publishes and keep its envelope
`orca host name --name X` printed `undefined`: `settings.update` replies with
`{ settings }`, but the handler read a bare `machineName` off the reply, and the
test fixture mirrored the wrong shape so it passed. After a write the command
now re-reads `status.get` and prints what the runtime publishes, so a blank
`--name` prints the detected name it returned to rather than an empty string.
The read path wrapped a possibly routed answer in a local envelope, stamping
`_meta.runtimeId: "local"` on a reply from another server. It now returns the
`status.get` envelope itself, and an unreachable runtime is reported as the
usual error instead of an invented "unknown" name.
`environment list` had gained machine-name and platform columns that no caller
populated, so every row printed "platform unknown"; the columns are removed.
* refactor(settings): give the machine name field its own component
The caption under the field re-read runtime status every time the saved value
changed, relying on a comment about write ordering to show the new name. A saved
override already is what paired devices see, so the hook now derives the caption
from it and asks the runtime only for the detected name; a stale status read can
no longer show the previous name.
`MobileMachineNameField` owns the store read, the published-name hook and the
debounced input, so `MobilePairingSetupSection` returns to its prop shape and
the pass-through `MobilePanePairingOutput` wrapper is gone. Paired-device
revocation moves into `useMobilePairedDeviceRevocation`, which keeps
`MobilePane` within its line budget with an extraction that carries behavior.
The web client mounts this pane too, but its settings store kept the name
locally where nothing published it. `machineName` now rides the existing
runtime-backed settings sync so the field renames the paired runtime.
* refactor(settings): normalize the machine name at the store boundary
Every writer (desktop IPC, web RPC, CLI) reaches the store through
updateSettings, which already normalizes the other free-text settings
there. Trim and bound the machine name in that one place instead of at
two upstream edges, so a future main-process writer is covered too.
* test(settings): pin machine-name routing and detection, and make the field searchable
The shared machine-name lookup test spawned the real `scutil` twice and compared the answers, so a
slow runner could time one spawn out to the hostname and fail. It now mocks the subprocess, proves
the hostname answers until the one shared lookup lands, and that a second runtime does not spawn
again.
`host name` is no longer pinned local, but only the explicit `--environment` route was covered; an
ambient `ORCA_ENVIRONMENT` now has its own test so the pin cannot silently grow back.
The Machine name field is added to the Mobile pane's search catalog at the tail, keeping every
existing row's tie-break index.
* fix(runtime): wait for the machine-name lookup before publishing status
A status read answered in the first few milliseconds after launch published the bare
hostname because the friendly-name lookup had not landed yet, and a caption fetched in
that window never corrected itself. RuntimeMachineName now exposes the settled lookup
as a promise, and both status publishers (the status.get RPC and the desktop
runtime:getStatus IPC) await it before reading. Construction, listen, and every other
method stay unblocked; the worst case is one wait of at most a second on the first read.
* fix(cli): refuse to rename a runtime that does not publish a machine name
An older Orca runtime rejects the unknown settings field with a bare invalid_params, so
'orca host name --name' routed at one failed with no explanation. The runtime that does
not publish machineName on status cannot store one either, so the CLI reads status first
and refuses with incompatible_runtime and a message that says to update that host,
before writing anything.
* fix(ipc): introduce this desktop to remote hosts by its machine name
When this desktop connected to a remote workspace host it announced itself under a
hostname captured once at module load, so a renamed machine kept its old name on every
other device's connected-clients list. The client name is now read at send time from
the runtime's machine name (the configured override, else the detected one), passed in
where the remote workspace handlers are registered, so a rename reaches the next
presence frame without a relaunch.
* fix(runtime): keep the machine-name lookup under the status probe budget
Status publishers now wait for the one-time name lookup, and `orca status`
probes them with a one-second budget. scutil answers in milliseconds, so a
half-second cap keeps a stalled lookup from making a healthy runtime read as
"starting" while still preferring the friendly name.
* refactor(web): drop the unreachable machine-name write path
The Mobile settings section is desktop-only, so the paired web client can
never render the field. Forwarding the name through the web settings sync was
dead code, and against an older host the strict update contract would have
rejected it while the local mirror kept the value. Remove it until a web
surface exists.
* chore(i18n): translate the machine-name strings and document paired-server rows
Add the Machine name field and its Settings search entry to the five non-English
catalogs, explain in the host list spec why paired-server rows report an unknown
platform, and drop a stale timeout figure from a test comment.
* refactor(settings): make the machine name a machine-wide setting with a General home
The name other devices and hosts list this computer under is not a mobile
setting. Rename MobileMachineNameField to MachineNameField, give it a per-mount
id, and put its primary home in Settings > General under "This computer". The
Mobile pane keeps the same field. One shared search entry feeds General, the
Mobile pane, and the copy now says "other devices and hosts" in all six locales.
The web client has no machine of its own to name and its settings mirror cannot
persist one, so the field renders nothing there and General omits the section.
* feat(mobile): name this computer in the Orca Mobile pairing step
The "Pair this computer" step now shows the same machine name field above the
connection choice and code, so a user pairing a phone from the sidebar page can
name the computer right there.
* feat(settings): name this host when sharing it with other devices
Share this host produces the access link other devices use to reach this
machine, so it mounts the machine name field first. The pane's search entry
takes the shared machine-name keywords so a search lands there.
* feat(sidebar): name this desktop when adding a remote host
This desktop introduces itself to a new SSH host or remote server under its
machine name, so the Add Remote Host dialog mounts the field once, between the
header and the host fields, in both modes. Submit logic is unchanged.
* feat(settings): name this computer in the SSH pane add form
The SSH pane's add form mounts the machine name field above the host fields.
Editing a saved host leaves it out; that host already met this computer.
* fix(mobile): drop the empty machine-name grid row on the web client
The pairing step wrapped MachineNameField in its own grid-area div. On the
web client the field renders nothing, so the wrapper left an empty row and
an extra row gap between the copy and the connection options. The field now
takes a className for its root, so the grid slot disappears with it.
* fix(settings): let Enter in the machine name field submit its form like sibling inputs
The field intercepted Enter to blur and commit instead of submitting the enclosing
SSH add form. The draft is already flushed on blur and on unmount, and the name is
read from the store whenever a peer asks, so nothing is lost when the form submits
first. Enter now behaves like the neighbouring inputs; the test proves the submit
fires and the name still commits when the form closes.
* fix(mobile): keep the machine name inside the pairing copy cell
A dedicated grid row stayed in the template on the web client, where the field
renders nothing, adding an empty track and a second row gap between the copy and
the connection options. The field now sits at the end of the copy cell with the
same 18px rhythm, so an absent field leaves nothing behind.
* fix(runtime): retry a failed machine-name lookup instead of latching the hostname
On a loaded Mac the scutil lookup missed its 500 ms cap during app boot, and
because the fallback was memoized for the process, every status read and the
Settings caption showed the bare hostname for the rest of the session.
The lookup now gets a 5 s timeout, a failed attempt (timeout, spawn error,
non-zero exit, empty output) clears the shared memo so a later ready() retries
after a 30 s interval, and status publishers wait only up to a 750 ms publish
budget before answering with what read() has now. A friendly name and the
non-darwin hostname stay final.
* refactor(settings): show the machine name only where other devices join this computer
The Add Remote Host dialog, the SSH pane add form, and General all describe
another machine, so a field about this computer's own name read as a third
kind of label there. The field now mounts only where other devices pair with
or connect to this computer: the Mobile pane, the Orca Mobile pairing step,
and Remote Servers > Share this host.
|
||
|
|
98e5ea3d5f |
refactor(tabs): delete the terminal tab's dead adopted-session field (#22557)
* refactor(tabs): delete the terminal tab's dead adopted-session field and every branch that read it * refactor(tabs): drop the stale adopted-agent comment and pin legacy load on a chat terminal The comment above the terminal chat-eligibility agent fallback described the removed adopted-session agent fallback. The legacy load test now puts the retired key on a chat-mode terminal tab, the only shape that ever carried it. |
||
|
|
4bab736f90 |
fix(native-chat): dock the task strip on the goal tab (#22530)
* fix(native-chat): dock the task strip on the goal tab The background-task strip was the full width of the message box, so it did not share an edge with the narrower goal tab underneath it. When a goal is showing, the strip now uses that tab's width and keeps a square bottom, and the goal tab's top stays square so the two sit on each other. * fix(native-chat): derive the task strip and goal tab seam from adjacency The strip and goal tab were each told by the chat session whether the other was showing, through two flags that had to match what actually rendered. The session also re-derived the goal tab's own visibility rule to compute one of them. Now each bar styles its side of the seam from the DOM: the strip takes the goal tab's width and drops its bottom corners and shadow when the goal tab is its next sibling, and the goal tab drops its top border and corners when the strip comes right before it. The flags and the duplicated goal visibility check are gone, so the seam cannot disagree with what renders, and anything placed between the two bars falls back to the separate look. |
||
|
|
069dc8a1d8 |
feat(agent-launch): let a caller reserve the chat session, and start terminal launches with the session picks (#22523)
* feat(agent-launch): let a caller reserve the chat session and carry session picks to a terminal launch * fix(agent-launch): keep a caller-minted session id named for its agent, and mint the fallback the same way * test(mobile): model the older host from the launch fields, not the refined schema * docs(agent-launch): describe the reserved session id as conversation identity, not placement The caller mints the session id so it knows which conversation it started; tab placement is not keyed on it. Also puts the terminal surface's doc comment back on createTerminalSurface. * docs(agent-launch): say a terminal launch reads the session picks on the wire contract The `sessionOptions` field doc still said a terminal launch ignores them, which this branch changed. * fix(agent-launch): check a reserved session id's token after the agent name, not the whole id A hyphenated agent name failed the one-token check, so any session id for such an agent was refused at the wire, while every other agent without a chat has its id ignored on the terminal. |
||
|
|
6847390c0a |
fix(ai-vault-search): own buffered transcript rows before they pin the parent (#22563)
Capped search rows are slices of the transcript. Own each row with the shared copier before buffering it, and keep the heap regression from #22378. The sidebar test harness now includes the worktree listing map the toolchain banner reads, so that rerender no longer crashes. Co-authored-by: fancivez <fancivez@gmail.com> |
||
|
|
60bd1dfdea |
feat(native-chat): one shell-environment setting for every structured chat (#22387)
* feat(native-chat): one shell-environment setting for every structured chat Structured Codex chats started from the login-shell environment, while structured Claude chats started from Orca's own process environment, so a variable exported in .zshrc reached one and not the other. Both now start from the same base, chosen by a new setting: - on (default): the whole login-shell environment, as a terminal gets - off: Orca's environment plus PATH, locale, SSH_AUTH_SOCK, and the variable names the user lists The setting is re-read each time a chat starts or resumes. It is shown only when Chat UI, the Chat UI default view, and structured native chat are all on. Terminal-backed chat is unchanged. * fix(native-chat): normalize the shell-environment settings when a profile loads A hand-edited settings file could store the variable list as something other than an array, and the structured runtime called `.filter` on it per launch, so a malformed value failed every structured chat create and resume, and the settings pane render. Normalize both keys where the profile loads, the same way the other array settings are, through one shared normalizer the runtime policy also uses. Also pin that an uncommitted name draft survives an unrelated settings re-render. * fix(native-chat): keep the pinned account as the only source of a structured chat's Claude home The session record owns which Claude home a structured chat uses, and the acquisition pin (claudeConfigDirEnvPatch) is the only emitter of CLAUDE_CONFIG_DIR, compared against what the child would otherwise inherit. With the login-shell snapshot as the inherited base, a CLAUDE_CONFIG_DIR exported only in a shell rc flipped that comparison and produced an explicit pin to the CLI default home, which moves the CLI off its default Keychain item. Drop the inherited CLAUDE_CONFIG_DIR in the Claude launch resolver before the pin runs, as Codex already does for an inherited CODEX_HOME. A configured per-agent overlay still passes through, since the record already honors it. * fix(native-chat): drop Orca's own CLAUDE_CONFIG_DIR from a structured Claude child too The process spawner merges Orca's process env under the launch env, so a CLAUDE_CONFIG_DIR exported to Orca itself reached the child around the launch resolver's drop and unseen by the account pin. One helper now strips it from both inherited bases. Also declare the two shell-environment settings on the runtime store contract and add the six new strings to every locale catalog. * feat(native-chat): add shell variables one at a time with a removable list * fix(native-chat): return focus to the name input after removing a shell variable * fix(native-chat): use a neutral placeholder for the shell variable input The empty input showed a grey HTTPS_PROXY as its placeholder, which reads as a saved value, especially right after that exact entry is removed from the list. Use "Variable name" instead, in every locale catalog. |
||
|
|
1866a796fd |
fix: explain Xcode-blocked Git once in the sidebar and rescan on return (#22552)
* fix: explain Xcode-blocked Git once in the sidebar and rescan on return * refactor: simplify Xcode toolchain banner to one classifier and a stateless rescan * refactor: fold banner copy into one table and cover SSH/runtime gating |
||
|
|
90f0c5c8ae |
test(file-search): pin request-key listings against the real runtime shape (#22312)
* test(file-search): pin request-key listings against the real runtime shape * test(file-search): pin intermediate renders and late remote answers Strengthen the stale-answer guard to assert every intermediate render reads as loading (null), add a late-answer drop case, make the tab-entry loading pin non-vacuous, and correct the e2e comment for local listings. * test(file-search): keep only the non-duplicate runtime-listing pins Drop the remote projection cases already covered at the hook level, collapse the classifier integration to the loading pin, drop the local-only rapid-edit e2e, and fix the brittle README absent-file assertion that failed CI. |
||
|
|
845db9e5e2 |
fix(native-chat): underline only file links a click can act on (#22370)
* fix(native-chat): underline only file links a click can act on A chat message could underline a bare file name such as `deck.md` that resolved nowhere, and clicking it did nothing, so it read as a broken link. - Inline code and quoted text become file links only when they name a path (contain a `/` or `\`), matching plain prose; a bare file name stays plain code. - Every file link click now answers: it opens, or says the file was not found, that the host could not be checked, or that the path could not be resolved. - Explicit links like [x](README.md:5) route as files, and linked text keeps `#`, `?` and `%XX` literally instead of re-parsing them as URL syntax. * fix(native-chat): wrap the parsed file location so file URIs in chat text still open Linkified prose, quoted text and inline code wrapped their display text, which the literal wrapped-href route no longer URL-parses, so file:///... resolved as a relative path under the worktree. Wrap pathText[:line[:col]] from the parsed link instead. |
||
|
|
519bde81df |
feat(ipynb): render notebooks like a notebook, with seamless click-to-edit cells (#22519)
* feat(ipynb): parse ANSI SGR sequences in notebook output Tracebacks and stream output carry terminal colour codes that the notebook printed raw. Splits text into styled runs (16/256/truecolor, bold, italic, underline) and drops non-SGR escapes via the shared stripper. * feat(ipynb): render notebooks like a notebook, not a form - Prose inherits the app UI font instead of a bare terminal font name, which Chromium could not resolve and fell back to Times (removes the now-dead resolveEditorFontFamilyOrInherit). - Markdown cells render by default; double-click or Enter edits them in the same Monaco surface code cells use. Code cells activate on press so a collapsing neighbour cannot swallow the click, which also retires the root pointer-capture deactivation (Monaco blur already covers it). - Code sits on its own tinted surface, the active cell gets a ring, and the editor sizes to its content so activating a cell no longer jumps. - The always-on 8-button toolbar and native select become a hover/focus toolbar (move, delete, and a menu for insert and cell type); the Jupyter [n] prompt turns into the run button. - Outputs show only the richest MIME representation, HTML renders in a script-less, no-network sandboxed frame sized to its content, and ANSI colours use the default terminal palettes. Adopts the MarkdownPreviewBody reuse, richest-MIME selection, extra raster MIME ranks, and CSP-sandboxed auto-height HTML frame from #18542. Co-authored-by: maxidiazbattan <maxidiazbattan@gmail.com> * chore(ipynb): drop the nbformat label and BETA badge from the notebook header parseIpynb already rejects notebooks without a v4 cells array, so the label carried no actionable information; the parsed nbformat field goes with it. Removes those catalog keys and the stale lowercase code/markdown ones. * fix(ipynb): keep cells pixel-stable when they switch to editing The preview and the live editor disagreed on four things, measured over CDP: - Font: the excerpt painted the bare "SF Mono" name (or --font-mono via its row class), while Monaco appended its own fallbacks and landed on Menlo. Both now use resolveEditorFontStack, the editor font plus the terminal fallback chain. - Line height: 20px rows vs Monaco's 21px. Both read CODE_EXCERPT_LAYOUT. - Gutter: a 48px line-number column plus 12px inset vs Monaco's 25px gutter. Notebook cells drop line numbers (the Jupyter and VS Code notebook default) and Monaco's decorations lane is the same 12px inset. - Rows: colorized blank lines collapsed to 0px, and a trailing newline had no preview row. Rows are fixed-height and a trailing newline opens an empty last line, matching the Monaco model. The [n] prompt and run icon now share one grid cell, so the hover swap keeps the label's box and centre. The commented-line tint moves to a theme token. * feat(ipynb): VS Code-style run gutter above a fixed execution count Replaces the in-place [n]/play swap from |
||
|
|
a77f87ea16 |
feat(sidebar): copy workspace name from context menu (#22338)
* feat(sidebar): copy workspace name from context menu Add Copy Name directly below Copy Path in the workspace context menu. It copies the name through resolveWorktreeDisplayName, the renderer mirror of main's mergeWorktree fallback (custom name, then branch, then folder), so the copied text is what `name:` worktree selectors resolve against and a cleared custom name no longer copies `undefined`. The context-menu model now spreads the command handlers instead of listing each one twice, which keeps it under the file-length limit. Fixes #21980 Linear: STA-8068 * refactor(sidebar): rename Copy Name to Copy Worktree Name - Clarify that this copies the worktree display name, not the path - Update all locale strings and i18n keys - Rename test file to match * test(sidebar): cover folder workspace copy worktree name --------- Co-authored-by: Seongho Bae <me@seonghobae.me> |
||
|
|
641a7f36d9 |
fix(native-chat): keep one live tool-run header from a call's start to the turn's end (#22432)
* fix(native-chat): keep one live tool-run header from a call's start to the turn's end
The collapsed tool run's header was two elements, one for "a call is running"
and one for "nothing is", chosen call by call. Every call start and end
remounted it, the count disappeared while a call ran and came back one
higher, and a call that finished inside a frame still bought the whole swap.
That is the 42→43 flicker in the report.
The header is now one element whose live state belongs to the turn, not to
any call: it stays live from the run's first call until the agent moves past
it (prose, a further run, or the turn's end), and settles in place. While
live the sentence speaks in the present tense and counts the call in flight
("Running 3 commands"), with the latest call's command beside it as a muted
preview; once settled it reads as before ("Ran 3 commands ✓"). The category
glyph is the run's in both states, and the completion mark only appears once
settled, so nothing pops between calls.
Which run is live is derived where the transcript is sliced into rows: the
last row that speaks or acts is the trailing one. A reasoning aside after it
leaves it live; an answer or a further run settles it.
Present-tense forms for the ten sentence categories are added to the shared
copy and the English catalog. The transcript-file lane, which renders with
the structured activity UI off, is unchanged.
* fix(native-chat): settle a run blocked on the reader, keep it live past an approval
- A run whose question is awaiting the reader's answer no longer pulses
"Reading 1 file" while the agent is blocked; it falls back to its calls.
- An approval's receipt no longer moves past the run above it, so the call
it just approved reads as running while it runs.
- The header button is the live region, so the count is announced too.
- Drop the unused live option and record from the shared English sentence;
nothing renders it yet.
* fix(native-chat): stop the settled run's check from fading in on every mount
Windowing remounts settled rows as the reader scrolls, and a restored transcript
mounts them all at once, so the fade replayed where nothing had changed. Also
pin that the live header counts the next call on the same element.
|
||
|
|
563dd5487f |
feat(native-chat): show a Codex chat's goal above the composer, and set it from goal mode (#22377)
* feat(native-chat): show a Codex chat's goal above the composer and set it from goal mode Structured Codex chat now treats the thread goal as session state: a banner above the composer shows the current goal (pursuing / paused) with clear, pause/resume and expand; /goal enters a goal mode whose send calls thread/goal/set; the objective is journaled as a user message marked as sent as a goal. The banner is derived from the journaled goal rows, which Codex's resume snapshot refreshes, so a reopened or adopted chat shows its goal. Fixes STA-8159 * fix(native-chat): replace a recorded goal by clearing first, and recover a lost goal-change response - A set while the journal records a goal (any status) clears it before setting, so the new goal starts with its own time and token counters instead of rewriting the old goal's objective in place. - The threadGoal plan answers an unknown outcome from the goal the journal records and reruns otherwise, so one request timeout no longer refuses every later Clear/Pause/Resume as unknown for the mounted session. - The goal-mode chip says "Exit goal mode"; "Clear goal" stays the banner's action on the provider goal. - A typed bare /goal on Enter enters goal mode, the same as picking it. - The renderer reads the goal off the tail of its ordered snapshot; the host's unordered map keeps the by-sequence reader. - Drop the composer's duplicate in-flight guard; the goal controller already serializes changes. - Pin that a counter-only revision reaches a subscriber's live page under its original sequence. * fix(native-chat): keep a bare /goal inside goal mode as the entrance, and pin goal delivery and serialization - A bare `/goal` submitted while already in goal mode re-enters the mode instead of setting a goal whose objective is the literal text "/goal". - The counter-only revision pin now drives the host's own event sink bound to a real journal, so it goes red when the publish after a lifecycle transition is dropped; the previous fake sink never published. - Pin that a set which threw after journaling its objective puts that objective back exactly once when the ledger reruns the same operation id. - Cover the goal controller hook: absent without host support, the loaded window wins over the host's answer, a second change while one is unsettled answers false without a request, and a refused change frees the next one. * fix(native-chat): resume a blocked or usage-limited goal, and keep goal-mode drafts honest - The goal bar offers Resume on a blocked or usage-limited goal, which the provider resumes exactly as it resumes a paused one; a goal whose token budget is spent still offers only Clear. The rule lives beside the other goal facts in shared code so every reader answers it the same way. - A `/goal <text>` typed inside goal mode sets the objective `<text>`, as it does outside goal mode, instead of a goal whose objective is the literal command. - Setting a goal is a host round trip; a draft edited while it was in flight is no longer wiped when the goal lands, matching every other host command. - Pin that a lost status-change response is read as applied only when the recorded goal is in that status, that a cleared row in the loaded window outranks the host's earlier answer, and that the PTY lane is untouched. * fix(native-chat): keep the load-older anchor on the loaded window when a live revision lands below it A live revision of a row keeps that row's original sequence. When the row is older than the client's loaded window, the shared reducer merged it in and it became the load-older anchor, so paging `before` it skipped every row between. A goal's counter-only revisions during a long goal turn reach any client that attached after the goal row left its window, so a reopened chat lost rows on scroll-back. The reducer now admits live rows only at or above the window's oldest row while older rows remain on the host; the journal keeps the revision and the page reader serves it once the window reaches the row. With nothing older on the host the window is the whole journal, so a row below the head is admitted as before. Also drain accepted provider events before a goal set reads the journal to decide whether it replaces a recorded goal. |
||
|
|
942d993f1f |
fix(editor): map Salesforce Apex extensions to the apex language id (#14287)
Fixes #22049 Co-authored-by: Nurdaulet Bolat <204565446+nvimq@users.noreply.github.com> |
||
|
|
eb18eaf2b6 |
feat(usage): add Muse Code local usage provider (#22379)
* feat(usage): add Muse Code local usage provider Scan Muse session logs (including subagent logs, which hold usage the parent log does not) for model_completed token events and surface them as a fourth local usage provider: shared scan worker, persisted per-file cache reused by mtime/size, cross-log dedupe, Stats tab, and Usage Overview integration. Muse logs carry no price, so the provider reports tokens only. * fix(usage): name Muse in Stats & Usage copy; skip partial-cost warning when nothing is priced * fix(usage): surface unreadable Muse sessions root; name Muse in remaining Stats & Usage copy * fix(usage): count distinct same-content Muse records within one log |
||
|
|
8757e40063 |
fix(native-chat): keep a structured agent's tool line between tool calls (#22349)
* fix(native-chat): keep a structured agent's tool line between tool calls A structured session's status named a tool only while the call was still running, so the sidebar's tool line went blank whenever the agent was thinking or writing between calls. Terminal agents keep naming the finished tool until the next one starts, and clear it after a failure. The structured status projection now does the same: a running call wins, otherwise the turn's newest root call if it completed. * fix(native-chat): bound the structured tool line by the running turn, not the user row A send made while a turn is running writes its user row into the journal straight away, and the turn keeps going. Stopping the scan at that row blanked the tool line while a tool was still running. The scan now runs to the turn record and names a call only when that record is still running, so a turn that already ended never lends its last tool to a pending follow-up. This lookup was the running-only lookup's only production caller, so it replaces that lookup instead of sitting beside it. * fix(native-chat): keep naming a structured agent's failed tool until the next one Clearing the tool line after a failed call brought the blank gap back for much of a turn: Codex marks any nonzero exit as failed, so a search with no match or a red test run is enough. The failure already shows on the tool's own row in the transcript. The running turn's newest running call still wins; otherwise its newest root call is named whatever it settled to. * fix(native-chat): name a structured Codex edit on the tool line as the chat draws it Once a Codex edit's changes exist, its apply_patch call becomes a diff row, which the status lookup skipped, so the row named the command before the edit. The chat's tool-call block for a journal row now comes from one shared builder, and the status lookup reads the same definition: a diff is named as Diff with its path, and counts as settled since it carries no lifecycle. * docs(native-chat): describe the structured tool field as running-or-latest The status summary's toolName/toolInput now name the running turn's latest tool between calls, not only a running one. Update the wire type and status bridge comments that still said "the running tool". |
||
|
|
51d3cafc4f |
feat(editor): add a setting to turn off preview tabs (#22398)
Single-clicking a file in the Explorer, or following a link in Markdown source, opens it as a preview tab that the next preview open replaces. There was no way to turn that off, so browsing files kept swapping one tab. Adds `editorPreviewTabsEnabled` (General -> Navigation, on by default). A caller's `preview` flag is now an intent that `resolveEditorPreviewIntent` resolves against the setting, covering every open path - files, diffs, history diffs, conflicts - in one place. Preview-ness is derived rather than reconciled: readers treat a tab as a preview only when the stored flag and the setting agree, so a flag left over from a saved session, another window, or a host switch is inert while previews are off. Nothing rewrites stored flags when the setting changes, so no settings-landing path has to remember to clean up. Fixes #22397 |
||
|
|
83dd047fd9 |
fix(explorer): find files by name in large local workspaces (#22369)
* fix(explorer): search local workspaces by file name across every file The Explorer name filter only searched remote workspaces directly; local workspaces still filtered the first 20,001 listed files, so files beyond that cap never matched in large repos. Local name queries now rank the whole workspace on the host, and fall back to an uncapped git listing when ripgrep is not installed. * fix(explorer): filter capped local listings on the host with the Explorer word rule Replaces the Quick Open fuzzy top-32 routing, which dropped multi-word matches and capped visible results. The Explorer keeps its instant renderer-side filter; only when the local listing hits its cap does it re-list on the host with the same word rule applied before the cap. * fix(explorer): keep capped matches when host name filtering fails - Fall back to the capped listing (and stop re-listing) if the host scan fails - Keep primary matches when the ignored-file pass fails during a filtered scan - Key host scans on normalized filter words; reset capped state per filter session - Bound nameFilter size at the IPC boundary; drop the double readdir walk * fix(explorer): match name filters without locale-dependent lowercasing * fix(explorer): avoid render-time ref writes in the host name filter fallback |