Commit Graph
17883 Commits
Author SHA1 Message Date
Brennan Benson 004907eb11 fix(sidebar): keep agent rows ordered by their session start once state history wraps (#14654)
* fix(sidebar): keep agent rows ordered by their session start once history wraps

Sidebar agent rows are ordered by the session's first reported state, read as
`stateHistory[0].startedAt`. `stateHistory` is a rolling window capped at
AGENT_STATE_HISTORY_MAX that trims oldest-first, so past ~20 transitions that
row is a recent turn: the row's identity clock silently becomes an activity
clock and busy agents re-sort themselves on every turn.

Latch the session origin in `firstStateStartedAt` when the entry is built and
read it through `agentEntrySessionStartedAt`, keeping the history lookup as a
fallback for entries that predate the field.

* test(sidebar): cover row order through activity and retention

* fix(sidebar): preserve session row order through activity and retention

* test: complete paired terminal status fixtures

* fix: carry accepted session origins across paired rows

* test: cover paired origin work and mobile action parity

* test(mobile): expect sessionLive in the phone chat identity after main's start-a-chat change
2026-10-29 14:27:16 -07:00
Brennan BensonandMerge Sim 6f99d480b3 fix(agents): stop a foreign agent name in task text from taking a pane (#19309)
* refactor(agents): extract the title name-anchoring vocabulary

Pure move, no behaviour change. The name tokens, display labels and the
bare-name/whole-title resolvers come out of agent-title-evidence.ts into
agent-title-name-anchoring.ts so more than one predicate can resolve a name to
an agent through the same implementation rather than by convention.

Nothing is added or reworded on the way out; the extracted functions are
unchanged and simply exported. Verified by the existing suites: the title,
identity and sidebar tests all pass untouched.

The identity ratchet moves with it — titleHasAgentName's two
parser-implementation callsites now live in the new module — so this commit is
green on its own rather than leaving the ratchet red until the next one.

* fix(agents): stop a foreign agent name in task text from taking a pane

A Claude pane's WORKING title is a bare braille spinner plus task text, so any
agent name inside that text resolved the title to a committed foreign identity.
The guard that fixed the mirror-image bug was written for the Claude label only,
so every other name short-circuited above it and took the pane from its owner —
the tab icon in use-tab-agent.ts, and the sidebar/dashboard row in
worktree-title-derived-agent-rows.ts, which never consulted the owner at all.

Make the rule agent-neutral in both directions: a title may only take a pane
from a known owner when it PRESENTS that agent, never when the name merely
occurs in free-form task text.

titlePresentsAgent sits beside collectAgentTitleEvidence and resolves names
through the vocabulary they now share. It differs from the parser in one
respect: the parser asks who a title names given no owner, so it must refuse
anything a task sentence could forge; this predicate is told which agent to
check, so it also accepts that one name in an identity POSITION — the whole
undecorated remainder, or the head of a `Name:` frame. The colon is what
separates `Codex: fix cursor offsets` from `Claude Code compare Opencode`.

resolveTabAgentFromSignals exists twice, already drifted in owner handling.
useTabAgent calls the copy in use-tab-agent.ts, which is what paints the tab
icon; every existing test imports the copy in tab-agent-from-signals.ts. Both
are fixed, and the new suite runs its whole contract through both so a future
divergence fails instead of hiding.

* fix(agents): let the title label map admit a missing label

Narrowing the map's value type to TuiAgent surfaced an inaccuracy that was
always there: noUncheckedIndexedAccess is off, so indexing Record<string, _>
types every lookup as a hit, and `label` is arbitrary text parsed off a terminal
title. The compiler concluded the `?? 'unknown'` fallback was unreachable and
the unknown-label comparison impossible.

The runtime disagrees — the map carries a fixed set of ~10 labels and misses
everything else. Deleting the branch would return undefined where the function
returns 'unknown' today, so the type is what is wrong. Partial<Record<...>>
makes absence expressible and leaves behaviour unchanged.

Pinned by a test, since nothing else forced the map to be honest about this.

* fix(agents): let an agent's own sigil still present it against an owner

titlePresentsAgent read only anchoredNames, so it missed the evidence class the
parser ranks highest: a vendor marker, the sigil an agent emits itself and task
text cannot forge. Gemini's four glyphs and Cursor's native literal are markers,
not anchored names, so every Gemini working, idle and permission title — and
Cursor's literal — failed the gate and the pane stayed on its previous owner.
Run Claude, let it finish, run gemini in the same pane: tab and sidebar both
stayed Claude. The bug this change exists to fix, mirrored.

Admitting the whole vendor class would have reintroduced #8940 the other way,
because Claude's '. ', '* ' and idle sigil are generic — OpenCode emits them
too — so they prove activity, not identity. hasGenericClaudeStatusPrefix already
modelled that distinction inside terminal-title-agent-type.ts; it moves to
agent-title-core.ts so both callers share the one definition.

Not isGenericClaudeStatusClaim, which is that model plus a clause for a
different question: it treats a 'claude' token as upgrading a generic prefix
into a real claim, which is right when asking whether a title may MINT Claude
identity and wrong when asking whether it PRESENTS Claude. Applied here it would
have let ". port the claude prompt" reclaim an OpenCode pane.

The predicate is now one memoized set per title rather than a memoized first
line and an uncached scan, so the common path — a title presenting nobody — no
longer re-runs the name matchers over every wrapper segment on each render.

* fix(agents): gate on one identity-frame grammar, ratcheted against the corpus

Two review rounds found the same class of defect: the gate was a second model of
"does this title present an agent", poorer than the model its callers use. Round
1 missed an evidence class, round 2 missed four whole ROUTES in a different
function. A third patch would have bought the fifth route.

getAgentLabel mints identity through routes the evidence parser cannot express,
because titleHasAgentName asks whether a name occurs ANYWHERE and never where.
One Codex branch therefore serves both 'codex working' and the mid-sentence
mention this change exists to reject. Route identity cannot separate them; the
gate genuinely needs position information the resolver discards.

So the models cannot be collapsed. What they can be is one GRAMMAR plus an
enforced agreement:

agentForIdentityFrame generalizes what isClaudeIdentityFrameSegment has always
described — a name, an optional status word, an optional '- action required' —
which was agent-neutral in shape and applied only to Claude. That was the same
one-way-guard mistake as the original bug, one level down. Generalized, it
covers every minting route: bare names, 'Codex: ...', em-dash frames, name plus
status, Windows launcher forms, '|'-headed titles and Pi's native format. The
positional and vendor-marker rules fold into it rather than stacking beside it.

terminal-title-pane-claim-corpus.test.ts is the enforcement. It sweeps the whole
classification corpus, and any title getAgentLabel still mints an identity from
must remain able to claim a pane. Shared code cannot enforce this because the
two models legitimately differ; the corpus can, and it names the offending title
when a future route is added.

Corpus: 14 lost claims before this commit, 0 after. One intended gain,
'claude.bat working', listed and explained in the ratchet: the old gate accepted
'aider.ps1 ready' for aider and refused the identical form for Claude purely
because the guard was Claude-only.

* fix(agents): stop reading a wrapper prefix as an identity frame

The head grammar treated '|' as a separator an agent puts after its own name.
Wrappers PREFIX the pane's title and the innermost title comes LAST
(terminal-title-wrapper-segments.ts), so a name before a '|' is a tmux window or
an ssh host, never the pane. `claude | zsh` on a Codex-owned pane therefore
handed it to Claude through all three consumers — this change's own bug,
inverted. Wrapper-joined titles already reach the real pane title through
getEvidenceTitleSegments' suffix splitting, which is what actually resolves
`ssh host | opencode ready`; the separator was not carrying that case.

'>' goes too. Pi's native `π > session` is matched before the head grammar runs,
so the only titles it affected were `name > text` breadcrumbs, which are closer
to task text than to an identity frame. That narrows `⠋ gemini > next steps` and
`⠋ codex > plan` relative to the previous gate; both are pinned as intended
rejections, since refusing leaves the pane with its known owner, which is the
safe direction on ambiguous evidence.

The corpus gain assertion is renamed to say it is corpus-scoped. Over every
string literal in the repo the change gains more than the one title this 67-title
corpus can see, mostly the same Claude symmetry, and the test should not read as
a global guarantee. Near-misses the grammar deliberately accepts are pinned too,
so the boundary is visible and moving it requires editing a list.

* fix(agents): admit identity frames before trusting terminal titles

* fix(agents): separate title identity from readiness policy

* Preserve title frame boundaries and normalization identity

* Preserve activity token whitespace at title wrapper boundaries

* test: expose typed mailbox idle evidence source

* test: scope title projection work contract to renderer

* test: distinguish task activity from agent identity

---------

Co-authored-by: Merge Sim <sim@local>
2026-10-29 14:26:46 -07:00
Neil 0c39430d01 test: return a structured command from the adoption test's launch resolver (#36081) 2026-10-29 14:23:15 -07:00
Brennan Bensonandyasuhiro nishioka d2baeff227 Skip proven other-forge projects in Tasks requests (#34219)
* Skip proven other-forge task repositories at the provider boundary

Revalidate GitLab project identity after remote configuration changes and pin resolved list calls to their host. Preserve unresolved local MR inference and settled partial results; carry additive eligibility metadata for GitHub notices.

Co-authored-by: yasuhiro nishioka <yasu00000@gmail.com>

* Keep task source eligibility scoped to each repository host

* Preserve shared host routing for GitLab task lists

* Cover runtime task source marker replacement through its notice reader

* Share initial GitLab config observation across source resolution

---------

Co-authored-by: yasuhiro nishioka <yasu00000@gmail.com>
2026-10-29 14:21:22 -07:00
Brennan Benson 2e38249a7d Keep a chat's accepted model options in what it shows and sends (#31208)
* Keep a chat's accepted pick in what it shows and sends

A pick held while a new chat is created is committed into the chat's
active option record once accepted, through the same step a normal pick
takes, and older options reads are dropped. The host also publishes the
chat's saved effort, Fast and speed as optional status fields so another
device's pick moves the option stamp and re-reads options.

* Keep chat picks ordered and capture displayed send selection

* fix: keep chat choices in one controller with accepted input capture

* fix: pass captured Codex model input to the turn request

* Keep structured chat option pickers usable during turns

* Preserve logical selection ownership across runtime transitions

* Consolidate selection lifecycle contract coverage

* Consolidate mobile structured send imports

* Preserve legacy action outcomes and opening message identity

* Consolidate repeated selection test setup and assertions

* Fix option reset admission and preserve uncertain write notices

* fix(chat): complete option contract fixture types

* test(chat): place affinity integration in cross-layer suite
2026-10-29 14:18:23 -07:00
Jinjing be4b3ffad3 fix(ci): resolve existing max-lines and command fixture violations (#36096) 2026-10-29 14:17:33 -07:00
Kelvin Amoaba 5834a3dcfd fix(browser): agent clicks keep their target when a page changes, by upgrading agent-browser to 0.39.4 (#36007)
* chore(browser): upgrade bundled agent-browser to 0.39.4

0.39.4 keeps refs stable across snapshots and refuses stale or hidden
click targets. Its replies now carry daemon lifecycle bookkeeping, which
is dropped where the JSON is parsed, and its tarball binaries lose the
executable bit pnpm's skipped postinstall used to restore, so the orcad
dev resolver now restores it like the desktop one.

* fix(browser): keep orcad's Chromium unbounded and read 0.39's stale-ref error

* fix(browser): import the shared stale-ref rule in orcad and keep the parser typed

* fix(browser): keep workers and frames running after the agent-browser helper leaves

agent-browser 0.39 asks auto-attached targets to wait for it before
starting. The proxy forwards that onto the guest's shared debugger,
which outlives the helper, so after the helper's idle exit nothing
resumed new workers or frames.
2026-10-29 21:15:04 +00:00
b2b704282c feat(browser): flash the page viewport size while the pane resizes (#31145)
* feat(browser): flash the page viewport size while the pane resizes

Resizing the built-in browser pane gave no sign of the page's size, so
landing on a CSS breakpoint meant guessing or reading innerWidth in
DevTools. The existing zoom badge now also flashes the guest's
innerWidth × innerHeight after each resize, so zoom and size share one
corner instead of stacking. The size comes from the host box, UI zoom
and the guest's live zoom level, because a UI zoom change overwrites
the guest's zoom behind the pane's remembered level.

Refs #31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(browser): cover no size flash on tab open and tab switch

The issue promises the size badge stays hidden when a tab opens or is
shown again, but the e2e only checked it after the 1.4s fade had
already passed, so a stray flash would go unnoticed. A renderer-side
counter now records every size flash, and the hook test covers a pane
that is hidden and shown again at a new size.

Refs #31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(browser): cover the badge shared by zoom and size feedback

Zoom and size feedback now share one label and one fade timer, so the
latest change wins, but nothing exercised that hook directly. The new
test checks that a size flash replaces the zoom label and restarts the
fade instead of letting the earlier timer hide it early.

Refs #31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(browser): keep the size badge exact under Chrome's zoom presets

After rebasing onto per-site zoom (#31621), zoom levels snap to Chrome
presets such as 90%, so the badge drifted a pixel from innerWidth: the
zoom factor snapped levels that UI zoom sets freely, and rounding did
not match Blink. The factor now uses the guest's level as is, and the
size mirrors Blink's AdjustForAbsoluteZoom::AdjustInt on the integer
DIP size, checked against the e2e's measured guest sizes.

Refs #31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(browser): never show a size flash that may be wrong or stale

Before dom-ready the guest's zoom cannot be read, and a saved site zoom
lands at that moment, so sizing it at the default zoom could flash a
wrong number. A flash left running when the page went inactive or was
parked at 0×0 could also come back stale, since nothing measures it
then. Both cases now skip or hide the flash, because no size is better
than a misleading one.

Refs #31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(browser): announce the first resize after a tab opens

Skipping measurements until the guest zoom is readable also dropped the
baseline, and a new tab's opening measurement usually arrives before
dom-ready, so the first resize afterwards only set a baseline and never
showed the badge. The baseline is now kept as the host box size and
converted at the zoom readable when the next measurement comes, so no
zoom is guessed and the first real resize is still announced.

Refs #31014

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test(browser): drop the size format case that only copied the string

The case asserted that the label reads exactly `1024 × 768`, which
restates the format string rather than any behavior, and main now
treats such copied-string tests as low value (#33736, #33778).

Refs #31014

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(browser): fold the size readout into its hook and drop tests that don't prove it

Moves the guest-size conversion into the hook that uses it, inlines the
one-use label formatter and predicate, and drops the e2e spec, the badge
timer test and the predicate tests. The conversion keeps its test against
sizes measured from a real guest.

* fix(browser): keep the size flash off under viewport presets and out of the pane's render

A preset pins the page size, and mobile emulation's layout viewport can
differ from the host box, so the readout only runs on a free-sized pane.
The badge now owns its label and fade state behind a ref handle, so a
resize drag re-renders only the badge instead of the whole page pane.

* fix(browser): skip the size flash while a phone or remote viewer sets the page metrics

* fix(browser): fold the screen scale into the size readout's rounding

Blink rounds the guest viewport in device pixels with the screen scale
times page zoom as its factor, so ignoring the screen scale left the
badge 1px off at most non-100% zoom levels on 2x screens.

* fix(browser): port Blink's exact zoom rounding to the size readout

Chromium 150's AdjustForAbsoluteZoom::AdjustInt adds 0.5 in float math
before dividing when zoomed in and truncates with a 0.01 slack, so the
+1 rule showed the badge 1px wide at many zoomed-in sizes.

* fix(browser): size the readout's guest frame in whole DIPs before the screen scale

Chromium rounds a guest frame to whole DIPs and only then scales it to
device pixels, so a fractional split width read one device pixel short.

* fix(browser): round the readout's guest frame up to whole DIPs

A live sweep at 1.2 UI zoom on a 2x screen showed Chromium sizes the
guest frame as the ceiling of the box in DIPs, not the nearest DIP.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-10-29 21:14:33 +00:00
Kelvin Amoaba ec58374a4c fix(browser): Orca's own buttons and links open per the open-links-in-Orca setting (#35992)
* fix(browser): every web link in Orca follows the open-links-in-Orca setting

* test(browser): pin where links from Orca UI open

* fix(browser): keep the link-routing escape modifier and owning workspace on UI links

Credential and star links stay in the system browser; shared markdown keeps its owner-aware routing.

* fix(browser): hover cards and review toasts open links in their own workspace

API-key and console pages stay in the system browser; non-web hrefs are ignored like the system bridge does.

* fix(browser): UI link wrappers pass the click so the escape modifier applies

Share to X stays in the system browser with the other account-action pages.

* fix(browser): folder workspace PR-check links open in the folder workspace

* test(browser): name UI link tests after the path they exercise

* chore(browser): keep the link-open ratchet's helpers private

* fix(browser): narrow the Linear project toast link before routing it

* fix(browser): UI links route with this desktop as their source owner

Links from Orca's own UI no longer read the Active Server setting; the opener routes by the host workspace.

* fix(browser): UI links open under the host workspace's own owner

A remote workspace's browser gets the original URL, not this desktop's localhost label.

* fix(native-chat): read browser route eligibility from its owner module

Importing it through workspace-browser-tab-open pulled the store into every link button and formed an import cycle.
2026-10-29 21:14:05 +00:00
Kelvin Amoaba 08e5522b9a feat(browser): add a shortcut to open a browser tab in a split to the right (#35994)
* feat(browser): add a shortcut to open a browser tab in a split to the right

* fix(browser): keep new-browser handlers void so the focus-read ratchet passes
2026-10-29 21:13:17 +00:00
Kelvin AmoabaandNeil 63f6d838fb refactor(browser): move the grab hook type to its owner (#36087)
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-29 21:11:19 +00:00
OrcaWinandOrcaWin 47713a90d8 fix: repair sidebar lint and adoption test command fixture (#36080)
* refactor(sidebar): move compact card reveal into existing reveal module

* test: return the current command shape in adoption fixture

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 13:58:37 -07:00
Jinjing 4c8e3dbb4f Constrain plugin panel iframe within sidebar height (#36085) 2026-10-29 13:56:59 -07:00
Kelvin Amoaba cc6da802a5 feat(browser): paste images into annotation comments and send them to the agent (#36004)
* feat(browser): paste images into annotation comments and send their paths to the agent

* fix(paste): hand an image-only clipboard to native paste so Cmd+V reaches the annotation field

* fix(browser): let an open image preview keep Escape instead of exiting Design Mode

* test(paste): drop the ownership-event test the menu-claim test already covers

* refactor(browser): reuse chat preview lifetimes and the terminal's paste host for annotation images

* fix(browser): release pasted edit images when the edited note disappears
2026-10-29 20:48:45 +00:00
Kelvin Amoaba 0cdb7b35cd fix(browser): refuse a 21st page annotation with a visible limit instead of dropping the oldest (#35979)
* fix(browser): refuse a 21st page annotation with a visible limit message instead of dropping the oldest

The store evicted the oldest note once a page held 20, so feedback vanished unseen. It now refuses the add; picking an element on a full page shows a limit toast instead of opening a draft, and the annotate banner states the limit.

* fix(browser): name the actions that free an annotation slot

* fix(browser): skip the limit toast for a selection loading already cancelled

* refactor(browser): one isBrowserAnnotationPageFull rule for the store, hook and banner

* refactor(browser): move the annotation limit message out of the grab hook

* fix(i18n): name the annotation limit placeholder
2026-10-29 20:48:13 +00:00
Kelvin Amoaba 93d8ce7767 fix(notes): name each agent in Send notes to by its sidebar name instead of its type (#35977)
* fix(notes): name each agent in the Send notes to menu the way its sidebar row does

* chore(ratchet): prune the notes-send menu from the host-list render baseline
2026-10-29 20:47:53 +00:00
Neilandzhangxaochen 8a4bc559e6 perf(mobile): skip line stats when loading Source Control status (#36061)
* perf(mobile): skip line stats when loading Source Control status

Mobile Changes rows never render per-file line counts, so the default
git.status line-stats pass (up to two `git diff --numstat` subprocesses
plus untracked file reads) was wasted host work on every load. Hosts
that predate the param strip the unknown key and behave as before.

Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>

* test(mobile): re-record Changes-load goldens for the no-line-stats status read

Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>

---------

Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>
2026-10-29 13:36:57 -07:00
Neil e8e0803672 fix(wsl): hold main's background WSL start before the cwd probe; let the user's runtime start check a WSL folder (#35971)
* fix(wsl): hold main's background WSL start in the shared preflight; folder launch scope honours the user's act

Main's background start (no wslUserAct) is now held in resolvePtySpawnStartupTarget, before the
startup cwd probe and any provider. A runtime start the user asked for (CLI, window, phone, split)
checks a WSL folder workspace's path as their act, so it may start the distro; background work is
refused with the stopped-distro message instead of folder_workspace_path_unavailable.

* fix(wsl): agent-session create and user resume check a WSL folder as the user's act; background holds with Start

* fix(wsl): agent-session ensure and a failed start's kept pane pass the user's act to the WSL folder check

* test(e2e): a prepared terminal.create dedupes as the caller's act
2026-10-29 13:31:17 -07:00
Neil 29a6ff3ffa refactor: colocate Jira cache and project selection owners (#36051)
* refactor: colocate Jira cache and project selection owners

* ci: refresh Jira refactor validation after main test fix

* ci: refresh Jira owner move after main validation fixes
2026-10-29 13:29:24 -07:00
OrcaWinandOrcaWin a01568e08c test: remove low-value metadata and registration inventories (#36066)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 13:27:15 -07:00
Brennan Benson db9cfbdb0a test(worktrees): cover both emoji branch naming modes (#35745) 2026-10-29 13:25:27 -07:00
Brennan Benson 6e0c74465c fix(claude): avoid unsupported workflow agent views (#35749)
* fix(claude): keep unsupported child rows on their parent pane

* fix(i18n): remove obsolete Claude agent denial translations

* fix: recover unavailable Claude child navigation to parent

* test: complete agent row navigation contracts and CI compliance

* test: repair agent row localization and integration ownership
2026-10-29 13:25:15 -07:00
Brennan Benson ec4d46f4f0 Use the shared default agent selection for work-item launches (#35748)
* Use the shared default agent resolver for work items

* Assert the preferred work-item agent opens as chat

* Use workspace owner inputs throughout work-item agent launch

* Use equivalent test syntax accepted by CI

* test: exercise real availability for direct launch defaults

* test: name public direct launch fixture mock types

* test: document partial launch store assertion exception
2026-10-29 13:24:10 -07:00
Jinjing f9649f114c Update structured session fixtures for command prefixes (#36058)
* Update structured session test fixtures for command prefixes

* fix: keep session record validator within lint line limit
2026-10-29 13:22:15 -07:00
Brennan Benson 9fe6e173ca Session History opens conversation files instead of storage databases (#35051)
* fix(history): open conversation files instead of storage locators

* Preserve accepted transcript extension casing and verify log consumers

* Type session log action test fixtures explicitly
2026-10-29 13:20:31 -07:00
Brennan Benson 11e7f46fc1 fix(worktrees): retire Git metadata watchers on root loss (#35067)
* fix(worktrees): retire Git metadata watchers on root loss

* Fix native watcher retirement after missing snapshots
2026-10-29 13:20:03 -07:00
Brennan Benson 920fa6cdbc Restore Session History resume for symlinked workspaces (#35052)
* Fix Session History resume for symlinked workspaces

* Bound history alias derivation and cover resume consumers

* Reuse equal alias values across history publications

* fix(history): verify adopted source directory before commit

* fix: preserve UTF-8 paths in directory admission probe

* fix: bypass transcript preparation for non-adopted starts
2026-10-29 13:17:40 -07:00
Brennan Benson e08cb10cc6 Prevent resting chats from delaying native command menus (#35069)
* Scope native chat command discovery to menu demand

* Fix command discovery CI imports and test timing

* fix: recover demanded command menus after context retirement

* test: load command host fixture before mocked consumers

* test: retain eager cache reads across command expiry

* test: cover command recovery after pending deadline

* test: count demanded command renders and query isolation

* test: load mobile command host fixture during collection

* test: type command batch fields from subscribe events

* test: check command event arrays without assertion

* Fix command discovery fixture project boundaries

* Name command fixture mocks with public Vitest types

* Assert opening metadata on resumed session snapshots

* test: assert passive command population boundaries

* test: cover mobile command capability handoff

* test: correct capability proof scope adapters
2026-10-29 13:16:35 -07:00
Brennan Benson e69baa3499 fix(linear): stop writes when their caller lifetime ends (#35020)
* fix(linear): bound write preparation and mutation to one caller lifetime

* fix(linear): isolate cancellable preparation from shared reads

* test(linear): keep CLI lifetime coverage in its compiler project

* fix(linear): preserve comment change failure guidance

* test(linear): place CLI and runtime coverage with integration tests

* test(linear): reset CLI exit status across runtimes

* fix(linear): preserve truthful confirmation deadline causes

* test(linear): support renamed workspace selector in fixture

* test(linear): adapt relation lifetime fixture to client factory
2026-10-29 13:15:07 -07:00
Brennan Benson 14676d11fb fix(native-chat): keep the session record file within the length limit (#36056)
* test(claude): call the flag-support helper after the check API rename

* fix(native-chat): reuse the non-negative integer guard so the session record stays within the file-length limit

* test(claude): leave the flag-support test to #36058
2026-10-29 13:14:03 -07:00
Neil e32c794f7b fix: restore record lint and session fixture validation (#36065)
Compose the exact changes from PR36056 and PR36058 so lint and session fixture contracts pass together.
2026-10-29 13:10:16 -07:00
Brennan Benson 7321b066e8 Show messages between agents as cards with the other agent's status (#34824)
* Snapshot agent message relations and verify task display prefixes

* Show native chat agent messages in navigable cards

* Keep agent message cards derived from canonical input and sidebar state

* test: correct agent card contracts and cover raw queue editing

* test: repair agent card runtime and operation contracts

* fix: preserve queued image previews and typed test measurements

* fix: use checked worker fixture and owned sender button typography

* fix: complete agent card fixtures and native presentation

* test: preserve queued edit exports and current authorship

* test: use namespace type imports in queued edit fixture

* Preserve sender progress and consolidate card contracts

* Simplify sender and notice contract instrumentation

* Remove duplicate sender and notice measurement mirrors

* Use a template literal for the dispatch boundary fixture

* Correct sender and notice verification phase accounting

* Consolidate agent message tests around user contracts

* Restore folded agent message preview on focus exit

* Consolidate agent message regression coverage

* Consolidate agent message fixture data

* Fix display text test lint diagnostics

* Use conversation view type for mail test records

* Reuse active copy control in card workflow test
2026-10-29 13:09:50 -07:00
Jinjing cb21824bd9 Optimize hidden chat subscriptions and tab projections (#36047)
* perf(tab-bar): patch tab agent projections per changed tab instead of rescanning every layout

The shared TabBar agent projection rebuilt agent types and tab-wide chat
fallback safety from every terminal layout (~2.5k at the captured scale) and
every status row whenever either map's identity changed. Status pings that
keep each pane's agent type now reuse the cached projection, and layout edits
re-resolve only the added, replaced or removed tabs. Key reorders, agent-type
changes and added/removed status rows still take the full projection.

* perf(ai-vault): keep rank and owner-only updates from re-collecting every agent conversation

The AI Vault title subscription ran two full agent-conversation collections
(every terminal tab, retained, sleeping and live record) whenever any owner
input changed identity, including worktree rank/unread patches and unrelated
settings writes. Compare worktree rows through the existing owner-rows token
so rank-only patches skip the check, and reuse the conversation workspace set
while the agent/tab maps it reads are unchanged, so owner-only updates compare
only those workspaces' hosts. Real host moves still schedule a title rescan.

* perf(native-chat): project only changed sessions on structured status feed ticks

Each host status feed tick re-captured every structured chat member on that
host (hundreds at the captured scale), resolving each tab's owner about three
times plus companion pin checks, even though a status event names one
session. Members whose feed facts (summary, observation, failure, sleep and
launch directory) and tab are unchanged since their last consumed capture are
now skipped, and tab owners are cached until an owner input changes, with
worktree rows compared through the rank-insensitive owner-rows token.

An input is marked consumed only after projection returns normally, so a
throwing store commit still retries on the next emission. The bridge remains
the only writer of slept-at and launch-directory pins, so skipping unchanged
members does not drop a needed re-assertion.

* perf(native-chat): skip the per-render scroll-margin offset walk in hidden chats

The transcript window re-read its scroll margin through an offsetTop walk in
a dependency-free layout effect, so every render of every retained chat forced
a synchronous layout read, including display:none panes where the walk cannot
produce a margin. The read now runs only while the pane is visible; becoming
visible re-renders and measures, and mount/resize measurements are unchanged.

* perf(native-chat): stop rank and catalog writes from rerendering every mounted chat

useNativeChatImageRuntimeContext shallow-subscribed each chat root to
worktree rows, runtime environments, SSH state and other owner maps, then
resolved the context in render. A worktree rank patch or a host catalog
timestamp refresh replaced one of those maps and re-rendered the whole
NativeChatStructuredSession of every mounted chat, hidden ones included (57 of
57 probes per write in the subscription benchmark). The selector now resolves
the context itself and keeps the previous identity while the result is
shallow-equal, so chats re-render only when their route or path changes.

* Optimize hidden chat subscriptions and release closed tab state

* Share layout diffs across tab bar projections

* Strengthen tab agent projection fuzz coverage

- Use a stable PRNG that exercises the full operation space
- Assert tab, agent, layout, and operation coverage
2026-10-29 12:22:50 -07:00
Neil 64c0992d2f test(wsl): pin the stopped-distro hold for a non-WSL shell in a WSL UNC folder (#35969)
* test(wsl): pin the UNC-cwd hold for a non-WSL shell on the daemon path

* test(wsl): pin the UNC-cwd hold for a non-WSL shell on the local path
2026-10-29 12:22:24 -07:00
Neil d7a33e1b12 test: use Claude flag support helper for launcher prefixes (#36052) 2026-10-29 12:21:58 -07:00
Brennan Benson 91efcc4df0 Add a per-host running terminal limit setting (#31396)
* feat: configure the running terminal limit for each host

* fix: bound terminal policy saves through fresh confirmation

* Fix truncated zero-size system file reads

* Load actual filesystem fixtures once per test module

* Preserve slept terminal ownership through capacity retries

* Fix terminal limit CI quality checks

* Correct slept terminal resume test types

* Move the slept terminal Resume fix to its own pull request

The Resume correction fixes a bug that already exists at the default limit,
so it now lives in #35401 and this change only adds the terminal limit
setting.

* Keep terminal limits effective when saved settings cannot be read

* Require a numeric edit before repairing terminal limits

* Derive terminal default from startup host capacity

* Apply terminal policy reads when validated results settle

* Align terminal limit tests with static analysis gates

* Inject daemon startup errors at the publication boundary

* Format terminal capacity using the app language
2026-10-29 12:18:02 -07:00
Brennan Benson 6d76f5a77b Keep a sleeping conversation resumable when the terminal limit is reached (#35401)
* Keep a sleeping conversation resumable when the terminal limit is reached

Resuming a slept pane while the host terminal limit was reached could drop
the Resume screen and fall back to a plain spawn. Route capacity refusals back
into the deferred sleep state, retry through the main-owned resume, and keep
the original session and provider until space frees up. A later Sleep or
cancellation still wins.

* Retain sleeping pane retries after transient owner failures

* Verify sleeping pane retry lookup bounds at scale

* Correct sleeping pane retry test fixture types

* Use transport attach contract in terminal test fixture

* Construct complete terminal transport test fixture
2026-10-29 12:17:37 -07:00
Brennan Benson 98f5d13689 A kill that reached the agent ends it, so the next message after Stop goes through (#34653)
* fix(native-chat): a kill that reached the agent ends it, so the next message goes through

After Stop, tab close, idle rest or workspace sleep, Orca force-kills the
agent. It used to accept the old agent as gone only when it saw the exit
report within about a second; otherwise every later message re-ran the kill
and was refused with "Couldn't stop {agent} from before." A kill delivered to
the agent itself now ends it and releases the chat with kill-delivered
evidence; the refusal remains only when the kill could not reach the agent.

When writing the chat's journal fails and the following kill can't be
confirmed, recovery now records the close with its fault instead of silently
returning, so the turn no longer stays Working and later sends retry the stop.

Tracked in STA-12354 and STA-12355.

* Preserve provider kill evidence across cleanup retries

* Exercise ACP setup gate with real managed teardown

* fix: preserve Claude writer termination evidence through settlement

* test: consolidate termination evidence contracts

* fix: restore Pi connection exit listener contract

* test: load actual ACP teardown before timed cases

* fix: correct lifecycle contract typing and test boundaries

* test: make Claude piped fixture mock types portable

* test: expect supervised OpenCode probe teardown

* test: align remaining supervised teardown expectations

* fix: align Stop feedback with terminal writer evidence

* test: remove redundant mocked Codex continuation case

* fix: keep adapter declaration and Stop fixture checks portable

* test: keep Stop fixture clock unchanged

* test: read canonical provider handle when reopening Stop journal
2026-10-29 12:15:56 -07:00
Jinjing 21cb9ebb78 perf(terminal): send typed keys to the PTY before publishing activity; split echo latency phases (#36008)
* perf(diagnostics): split terminal echo latency into queueing, round-trip, parse and paint phases

The typing diagnostic started timing at its own capture handler and lumped the
PTY round trip, renderer output scheduling and xterm parse into one interval, so
a stalled renderer could not be told apart from a slow PTY or TUI. Exact samples
now also report trusted-event input queueing, onData to first output reaching
the output scheduler, scheduler queueing, and xterm parse, plus each sample's
overlap with long animation frames. The report shape only gains fields.

* test(bench): measure terminal echo phases under accumulated workspace-update pressure

Adds a typing bench scenario that types into one real PTY while the accumulated
store receives rank-only order publications (and optional status traffic), runs
the in-app phase diagnostic beside the PTY sidecar, and moves the shared bench
knobs and report writer into a module both specs use.

* perf(terminal): send a typed key to the PTY before publishing its activity stamp

xterm fires onUserInput before onData, and the first key after a pause wrote
lastTerminalInputAtByPaneKey synchronously there, so the key waited on every
store subscriber before it was sent. Publish the stamp in a microtask instead:
the same task still records it, after the key has been handed to the transport.
At 1,620 workspaces and 2,356 store listeners, input-to-dispatch for keys after
a pause fell from p50 1.2-1.5 ms to 0.5 ms.

* Improve typing latency diagnostic accuracy

* Flush queued long tasks in live typing latency reports
2026-10-29 12:11:08 -07:00
Brennan Benson f87d68b4a4 Chat runs launcher Commands like op run -- codex for every agent (#35592)
* Support launcher command prefixes for all chat agents

* Preserve launcher path arguments and copy routing checks

* Match Japanese command guidance to the Agent settings label

* test(pi): expect version probe termination barrier

* test(chat): expect current command refusal guidance

* fix(acp): refuse copy arguments before launch preparation
2026-10-29 11:59:03 -07:00
Jinjing a4e600a663 fix(git): recognize colored stale lease rejections (#36041) 2026-10-29 11:50:37 -07:00
Jinjing d12a4b1c5c perf(sidebar): skip closed-menu catalog work and batch remote order publications (#36002)
* perf(sidebar): keep closed workspace menus and host dialogs off catalog churn

Closed row context menus subscribed to the full worktree list, worktree map
and repo map, and ran the delete-lineage traversal whenever any worktree row
changed. At 1,618 workspaces every rank-only update re-rendered each mounted
menu and rebuilt the lineage projection per menu.

- Menus now select stable empties for the catalog while closed and gate the
  lineage/sleep/delete subtree calculation on menuOpen; opening re-renders
  with the live host-qualified rows.
- Per-host HostRemoveDialog no longer resolves SSH removal scope while
  closed, retaining the last open snapshot through the exit animation.

Measured in happy-dom with 40 mounted closed menus over 1,618 rows: nine
rank bursts went from ~52 ms and 360 lineage traversals to ~2.6 ms and 0.

* perf(worktrees): batch remote order publications across repos into one commit

Hosts publish one sortOrderOnly worktreesChanged event per changed repo, and
each fast-path patch was its own store commit, so a multi-project reorder
produced back-to-back global renders (nine ~95 ms tasks in the 10-28 trace).

Published order patches now join a fixed 32 ms window and apply in a single
patchWorktreeSortOrderBatch transaction (one worktreesByRepo copy, one
sortOrderEpoch bump). Each repo handler awaits its flush, so renames and
structural refreshes queued for the same repo still run after the patch.
At flush the connection fence and host-row presence are re-checked; stale
entries fall back to the existing refetch path. Same-id host twins keep the
existing host matching.

* perf(sidebar): skip catalog index builds for closed menus

Check menuOpen before calling the catalog getters so a closed menu never
rebuilds the shared repo/worktree indexes on catalog changes.

* Prevent stale host dialog subscriptions and sort-order patches

- Unmount dialog content after exit while preserving pending removal state
- Fence batched sort updates to the submitted repository listing

* Disable host removal while its dialog closes
2026-10-29 11:47:48 -07:00
Brennan Benson e6878905be fix(wsl-host): Start connects a stopped distro once and names it when it fails (#35438)
* fix(wsl-host): Start connects a stopped distro once and names it when it fails

Start called the WSL host's connect and then the generic explicit connect, which
routes to the same WSL host again, so one click deployed and supervised the
server twice; a failure toasted main's raw text (a probe's command line).
Start now makes only the explicit connect, which still refuses a host whose
Remove is pending, and a failure says "Couldn’t start <distro>" with where to
look.

* test(wsl-host): cover moved Start admission and default failure toast

* test(wsl-host): load actual registry outside test hooks

* style(wsl-host): format admission test import

* test(wsl-host): check pending removal fixture type
2026-10-29 11:46:41 -07:00
OrcaWinandOrcaWin b3e5b9a5e0 test: remove schema inventories and redundant capability checks (#36040)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 11:38:24 -07:00
Jinjing 6a160aa5c8 fix(editor): soften file reveal flash and skip new views (#36039) 2026-10-29 11:20:49 -07:00
Neil 46e897f87b refactor(jira): group search and fallback notice owners (#36036) 2026-10-29 11:14:35 -07:00
OrcaWinandOrcaWin 2e5610316a test: remove browser capability inventories and mock receipts (#36032)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-29 11:03:49 -07:00
Jinjing a656872d02 Remove obsolete TestFlight preview link from mobile docs (#36029) 2026-10-29 11:00:22 -07:00
Jinjing e182f1daa2 Preserve force-with-lease rejection details in push errors (#36027) 2026-10-29 10:59:55 -07:00
NeilandManan Chataut bc228ff5cb fix(native-chat): keep a huge unbroken paste from freezing the chat (#32355) (#36031)
* fix(native-chat): keep a huge unbroken paste from freezing the chat

A long run with no whitespace ("xxxx…", "0000…", "abab…") wrapped with
kerning makes Chromium reshape it at every line, quadratic in run length:
128 KiB stalls 2.4 s, 1 MiB 19 s, 5 MiB never finishes. ProseMirror forces
that layout on paste and on each keystroke, so the composer freezes in
every terminal-backed chat, including paired servers and WSL. A sent
message stalls the transcript the same way: its fold clips the text but
still lays it all out (128 KiB: 2.5 s).

Runs over 256 characters now render with font-kerning: none, so prose of
any length and Shift+Enter lines keep kerning:
- composer: inline decorations, updated only for the lines a transaction
  touches (a keystroke beside 8,000 decorated lines ~1 ms, not ~190 ms)
- user messages in the transcript and expanded queued cards: the whole
  text when it holds such a run

Text, drafts and what is sent are unchanged. After: 1 MiB single line
84 ms, 5 MiB 312 ms (keystroke 248 ms), 1 MiB of 4000-char lines 50 ms
(was 2.3 s), transcript 128 KiB 80 ms. Worst case left kerned, 256-char
runs: 1 MiB 242 ms, 5 MiB 1.2 s, like 1 MiB of short lines (291 ms).

Fixes #32355

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): keep kerning on long CJK and Thai paragraphs

These scripts have no spaces but Chromium can break between their
characters, so a long paragraph is not one unbreakable run. Count
ideographs, kana, Hangul and Thai as break opportunities so such text
keeps kerning.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): rescan only the edited run, and wait out IME compositions

Splitting one run inside a block of thousands replaced every decoration
in that block, and ProseMirror's removal is quadratic: 1,411 ms for one
space in a paragraph of 8,000 long runs. A change now rescans only from
the run edge before it to the run edge after it, and removes only the
decorations that overlap: ~1 ms for the same edit.

Decorations are only mapped while an IME composition is in progress, so
the composing text node is not re-rendered. The deferred range is
rescanned with the next non-composing change, or on compositionend when
none follows.

The keystroke-timing test is replaced by deterministic ones: unaffected
decorations are reused and the result equals a full scan; a composition
defers the rescan and compositionend runs it.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): never split a CJK code point, and treat NBSP as glue

A supplementary ideograph (U+20000-U+3FFFF) broke a run at its high
surrogate only, so the decoration began on the low surrogate and split
the pair across elements. Both the run scan and the leftward edge scan
now treat the pair as one break.

NBSP joins words for Chromium's line breaker (UAX #14 class GL), so an
NBSP-joined paste is one unbreakable run and must lose kerning; drop it
from the break set. No other non-breaking characters were in it.

Drop a composer test whose coverage the decoration reuse test already
has.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

* fix(native-chat): classify surrogate pairs whole at a rescan's edge

Two lone-surrogate edge cases made the incremental rescan disagree with
a full scan:
- an edit completing a pair at the window start (a lone high surrogate,
  then a pasted low surrogate and run) began the decoration on the low
  surrogate; the window now widens past the whole pair
- a lone low surrogate after any break counted as a pair end, dropping
  it from the run; that now requires a preceding high surrogate

A seeded fuzz also found inclusive decoration edges mapping across a
joined block's boundary, where ProseMirror cannot remove them; the
decorations are no longer inclusive.

Co-authored-by: Manan Chataut <mananchataut@gmail.com>

---------

Co-authored-by: Manan Chataut <mananchataut@gmail.com>
2026-10-29 10:56:40 -07:00