Commit Graph
8359 Commits
Author SHA1 Message Date
Brennan BensonandMerge Sim d15a6df224 Render task checklists with update diffs and a composer progress panel (#19230)
* Render native chat task lists with incremental checklist updates

* Keep native chat task-list review plan out of repository root

* Render live Codex plan notifications through task checklists

* fix(native-chat): keep checklist test fixture within shared boundary

* Keep agent task progress in one composer panel

* Restore inline task checklists and historical update diffs

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 23:50:32 -07:00
Brennan BensonandMerge Sim addd9f3da7 fix(orchestration): accept a missing Run id from older federation coordinators (#19689)
* fix(orchestration): accept v1.4.198 coordinators on federationAttachStart

#19542 made runId required on the attach RPC and backfilled existing
attachments with '', on the premise that federation was unreleased. It
shipped in v1.4.198, so a v1.4.198 coordinator got 'Missing Run ID' from
an upgraded worker host, and every pre-upgrade attachment lost its mailbox
because home_run_id='' matches no Run.

- runId is optional on the wire; an absent id mints a per-attachment stub
  Run (run_federated_<dispatch>) through the same INSERT OR IGNORE path.
- migrate-v40 backfills existing attachments with the stub and inserts the
  stub Runs, so in-flight workers keep reporting back.
- create SQL gives home_run_id a DEFAULT '' so a rolled-back v1.4.198 host
  can still insert into a v1.4.199-created table.

Stub Runs never carry run_legacy_local, and #19542's attachment-mailbox
exclusion in the skew probe is untouched, so no adoption replay path is
reintroduced (probe test added).

* fix(orchestration): repair empty federated home Run ids on every open

A host on v1.4.199 that rolls back to v1.4.198, attaches workers (rows
land with home_run_id='' via the DEFAULT), then upgrades again never
re-runs the v40 backfill because user_version is already 40, so those
attachments stay without a Run and their control mail is refused.

Lift the two idempotent set-based statements out of migrate-v40 into
backfillFederatedStubHomeRuns and run it from both the v40 migration and
the OrchestrationDb constructor after migrate(), matching the existing
on-open rememberCurrentRunCoordinatorHandles repair.

Tests: reopen a v40 file DB holding a v1.4.198-shaped '' row through the
constructor and assert the stub Run and mailbox; pin the wire schema
accepting a v1.4.198 request with no runId ('' -> undefined, whitespace
passes the schema and is refused at the DB layer).

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 23:41:46 -07:00
Brennan BensonandMerge Sim 3a801d213d fix(orchestration): worker-start settles readiness on observed turn start, not write acceptance (#19423)
* fix(orchestration): worker-start settles readiness on observed turn start, not write acceptance

A dispatched PTY worker whose agent wedged at startup (six codex workers on
2026-09-07) was reported 'ok: true, state: ready, stage: input_accepted': the
preamble write was acknowledged with observationTimeoutMs: 0 and nothing ever
verified a turn began. The corpse and the healthy worker produced identical
receipts.

worker-start now runs the existing second-stage prompt observer
(observeTerminalAgentPrompt) after acceptance, inside the 30s window the
client RPC grace already budgets for (orchestration-worker-start-prompt-budget):

- turn observed (or provider ack for structured sessions) -> ready
- permission prompt -> ready; positive liveness, surfaced in the receipt
- provider without a turn-start signal -> ready; observation: unsupported
- observation supported and nothing started -> worker state start_unknown,
  response state outcome_unknown with nextCommands. Honest 'unverifiable',
  never a death claim: the capability and terminal are kept, and
  worker-report settlement already reconnects a start_unknown worker that
  recovers and reports.

Also fixes the effect-verb lie that misdirected the first diagnosis of this
incident: agent-first worktree creation labeled its own brand-new agent
terminal 'reused_agent_terminal' (a role test picking a lifecycle verb) on
both the local and federation paths. It now says 'created'; readers keep
accepting the retired verb for rows persisted before the rename.

* fix: preserve worker authority through start observation

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 22:53:48 -07:00
Brennan BensonandMerge Sim fe9d538b6f fix(native-chat): keep collapsible chevrons beside their header text (#19656)
Co-authored-by: Merge Sim <sim@local>
2026-09-08 22:45:30 -07:00
Brennan BensonandMerge Sim 210ac1f77c fix(native-chat): expand Claude slash commands sent with an attachment (#19654)
* fix(native-chat): keep the text block last in Claude dispatch content

Claude reads a streamed user message as a slash-command invocation only when
the last content block is text. The composer builds the send body as
text-then-images, and the Claude dispatch mapper preserved that order, so any
slash command sent with an attachment arrived as literal prose instead of
running -- /review, /init and the rest, not just one command.

Emit images ahead of text in the mapper. Images-before-text is also the order
Claude prefers for vision, so this is unconditional rather than slash-only.

Fixed in the Claude mapper rather than the shared send body: that body also
defines rendered journal block order for every provider.

* fix(native-chat): derive Claude slash-command recognition from the sent prompt

Claude encodes a user turn as attachment blocks followed by the typed text and
recovers the prompt by reading only the trailing text block, so a body ending in
an image has no recoverable prompt and its `/command` reaches the model as prose.
The composer builds text-then-images, so that was every send with an attachment.

Emit one trailing text block rather than appending each block: a partitioned
`[...images, ...texts]` still strands a command ahead of trailing prose, because
only the last block is read. Derive `acceptsResult` from that same sent content
so the mapper and the dispatch waiter cannot disagree about whether a command
ran; the previous `blocks.some(trimStart)` was strictly more permissive than
Claude, which does not trim before matching `/`.

* test(native-chat): pin the sent Claude content order and the joined prompt verdict

The attachment scenario passed unchanged against main: it only exercised
acceptsResult, which was already true there. Assert the send payload, and
pin that joining narrows a command hiding behind leading prose.

Reuse claudeRecord for the content-key narrowing the same file now imports.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 21:52:25 -07:00
Brennan BensonandMerge Sim 1c1cb7115a fix(orchestration): unify structured-session status across worktree ps and sidebar (#19217)
* fix(orchestration): stop worktree ps reporting a busy structured session as idle

A worktree running a structured Claude or Codex chat read as idle to `orca
worktree ps`, while the desktop sidebar showed the same session working. The
sidebar was right: the host already projects a status summary for every
structured session and publishes it, and the renderer maps it into an agent
row. `worktree ps` simply never consumed it, so the agent-facing surface was
the blind one.

Structured sessions have no PTY, so they reach neither the hook snapshots nor
the retained ones that every other row is built from. This reads the summaries
the host has already published and applies the same projection the sidebar
does — working, attention as blocked, otherwise done — so the CLI and the GUI
cannot disagree about one session.

Two things worth knowing:

The connected-PTY evidence gate had to be skipped for these rows. It exists to
drop a row whose PTY is gone, which is the wrong question for a session that
never had one; a structured row's liveness evidence is the status feed that
produced it. The exemption is keyed on the row being structured, so every
PTY-backed row keeps today's behaviour.

`RuntimeWorktreeAgentRow` needed no change. It was already a non-PTY shape —
paneKey, state, agentType, and no ptyId, connected or writable — so a
structured row fits without inventing a fake terminal coordinate.

The pane key is the DERIVED one the renderer already publishes, never the
orchestration bearer handle or the minted worker pane key: both are
credentials, since `orchestration check` is identity-gated and accepts a
caller-supplied pane key.

`orca terminal list` is deliberately untouched, and a test pins that. Adding
rows there breaks real consumers: mobile mounts a terminal WebView per row that
can never receive a frame, a `connected`-keyed refresh check goes permanently
true and pins shipped clients to a fast cadence with no exit, and the plugin
projection has no field that can carry `writable: false`. Every safe consumer
of a terminal summary checks `ptyId`; the breaking ones key off `connected` or
mere row presence, which no added field can qualify. An honest partial-listing
count there is a separate change.

* fix(runtime): report only live structured sessions in worktree ps

The status feed's `published` map is a broadcast cache, not a roster. It
deliberately never retracts — an evicted idle session is still idle, and a
reloading renderer must not lose every settled row — so enumerating it lists
every session the host has ever opened, and eviction's `forget-session` step
deletes the session from the live map while touching nothing else.

Reading it as a roster made `worktree ps` report a closed chat forever. The
sharp edge is a chat closed while an approval was pending: a deliberate close
does not settle a pending prompt, so the retained summary stays `attention`,
maps to a `blocked` row, and merges the worktree to `permission` for the whole
30-minute freshness window — on the CLI and on the mobile sidebar it backs.

The poller now answers from the sessions the host still holds, intersecting the
live map with the retained projections. `subscribe()` and its snapshot are
untouched: retention there is the point. Gating on the live session set rather
than the visible tabs keeps a headless orchestration worker listed, which is
what the agent-facing surface is for.

Also folds out two things the enumerator left behind: the working/attention/idle
to working/blocked/done mapping now lives once in the shared projection module
instead of once per process, which is what actually enforces "the CLI and the
GUI cannot disagree"; and the structured row source no longer builds a
write-only `payload` behind an `as` cast that compensated for nothing. The
structured source construction moves to its own module to keep
runtime-worktree-agent-rows.ts clear of the 300-line cap.

* test(runtime): execute the structured-host call site in worktree ps

Both structured-row suites called attachRuntimeWorktreeAgentRows directly
with summaries they built themselves, so nothing ever ran getWorktreePs's
own `getStructuredAgentSessionHost()?.liveSessionStatusSummaries()`. That
file carries `@ts-nocheck`, so renaming the accessor was green in typecheck
and in the suite, while `orca worktree ps` and mobile's 3s poll would throw
a TypeError for every user — the `?.` optional-chains the host, not the
method. Swapping the call back to a whole-cache read was equally invisible:
the liveness suite injects feed.liveSessionSummaries() itself, and the
string-match guard only needs the identifier to appear somewhere in the file.

Drives the real runtime with a stub host over a real status feed that has
published two sessions and forgotten one, asserting the live session's row
reaches ps output, that the live accessor is the one called, and that the
`?? []` fallback still returns a page with no host installed. The stub is
typed against the real host, so a class-side rename reddens tc here.

* refactor(runtime): admit agent sources before worktree row projection

* fix(runtime): preserve host-authoritative structured status

* Fix structured host session activity lifecycle

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 21:50:51 -07:00
Brennan BensonandMerge Sim 72befaf360 feat(native-chat): show Claude subagent activity on the shared carrier (#18806)
* feat(native-chat): show Claude subagent activity on the shared carrier

Claude's `message:system:task_*` frames are classified `status-chrome` and
reach the transcript as nothing at all, so a turn that spawns subagents
renders as an idle turn. The journal translator now reads them into the
shared subagent-group carrier — no new UI, and the frames stay
`status-chrome` so nothing prints a raw opcode row.

`local_agent`, `local_workflow` and `local_bash` tasks share that channel
and all carry a `tool_use_id`, so `task_type` is the discriminator and a
backgrounded `sleep 20` stays out of the roster; `subagent_type` covers
releases that predate `task_type`. `skip_transcript` tasks never render,
`is_backgrounded` children survive the turn-end sweep, and a resumed task
re-announced under a fresh tool id is aliased onto its `task_id` rather
than duplicated.

A child still reported as working when the turn — or the session — ends
becomes `unverifiable`: contact was lost, which is not evidence it exited.

* fix(native-chat): stop the Claude subagent roster dropping its own rows

The roster published under the same coalescing key it appends the row
with, and the sink queue replaces any queued operation sharing a key
regardless of kind: once a write was in flight, each new append evicted
the pending publish and the next publish evicted that append, so the
body never reached the journal and `lastSerialized` had already moved
past it. Publish now takes the sink's own slot, as the Codex streams do.

A tombstoned row could never come back: the non-batch item-row builder
derived its revision from `items` alone, so a re-add was built at
revision 1 against a tombstone at 2 and the reducer discarded it
forever. It now takes the same `max(items, tombstones)` the batch
builder already used — reachable here because an announcement that
reveals a `local_bash` task empties and tombstones the group row that
a genuine subagent later in the turn reuses.

`settleTurn` swept whatever group the key named at the time it ran, so
children rostered before any turn key existed were never swept, and a
turn whose result never arrives was left working forever. The ending
turn's key is now an argument, a superseding turn start settles the
turn it replaces, and every turn end also sweeps the outside-turn
group. Teardown without an `ended` event, and eviction past the group
bound, both lose contact instead of stranding a row at `working`.

Label ordinals are a high-water mark now: releasing one on a re-label
handed the next child an ordinal that was already on screen.

* fix(native-chat): bound subagent-group blocks on every wire that carries one

Adding a fifth arm to `NativeChatBlock` made every consumer that assumed
four wrong. Two of them ended in `return block`, so they compiled while
handing a roster straight through: the mobile RPC sanitizer shipped it
unclipped past both mobile char caps, and the legacy transcript import
stored an untrusted roster unbounded. Both now clip each label and cap
the entry count the way they bound their other blocks.

The remaining three sites did not compile at all. The worker transcript
payload and the live-session benchmark get real arms rather than casts —
a cast would have turned the transcript one into a third silent
passthrough inside the wire byte budget — and the CLI worker output
renders a roster with its shared summary instead of `[image omitted]`.

The mobile sanitizer moves to a sibling module beside the image-block
one: the file sat exactly on the max-lines bound, and the block bounds
are a self-contained concern with their own caps.

Also caps the roster's `subagent_type` label fallback, which reached the
journal uncapped, and covers the new block type in the schema audit.

* test(native-chat): cover the capped subagent_type label

The roster stores the frame's label verbatim, so the cap on the
`subagent_type` fallback is the only thing bounding it.

* fix(native-chat): type the roster fixture so the suite typechecks

The mobile-cap test built its entries with an inferred `state: string`, which
is not a `NativeChatSubagentState` — the only typecheck failure on the branch.

* fix(native-chat): stop child traffic rostering an id Claude never announced

`observeChildActivity` minted a provisional row for any `parent_tool_use_id`
outside the excluded set. An id that was never announced is never excluded, so
a nested Task, a workflow child, or a grandchild parented to a tool id inside
the sidechain each produced a permanently unlabelled `subagent` row that could
only ever end `unverifiable`. The bounded exclusion set cannot cover an id no
frame ever declared, and in a long session it can forget a genuine exclusion.

Track instead whether this CLI announces tasks at all — set by ANY
`task_started`, including one the subagent filter rejects. Once it has, an
undeclared child is provably not a new subagent, so no row is created. The
provisional path now serves only releases that announce no task frames, which
is what its comment already said it was for.

The label-ordinal test moves to an announcement-driven removal, the scenario
that path now actually reaches; it still fails if `remove` releases the ordinal.

* fix(native-chat): outrank the tombstone when building one too

`buildJournalTombstoneRow` still built its revision from `items` alone, leaving
it asymmetric with the item builder. It is correct today only because
`upsertItem` clears the tombstone whenever a re-add wins — an invariant that
lives in the reducer and was not pinned. Apply the same `Math.max`, and pin the
invariant so the reducer cannot drop it silently.

* fix(native-chat): stop an unrelated turn end settling an outside-turn child

`settleTurn` swept the `outside-turn` group on every turn end, so a child
Claude announced while no turn was live — a frame trailing the previous
turn's result, or one that arrives before the first turn starts — was
marked `unverifiable` by the next, unrelated turn ending. That state is
terminal and latches, so the `task_updated: completed` that followed was
discarded: loss of contact was recorded as the child's outcome on
evidence that was never about that child.

A turn end now sweeps exactly the group its key names. `outside-turn`
belongs to no turn, so only an end with no key of its own reaches it, and
what no turn end reaches `settleSession` does — reliably, since teardown
without an `ended` event also routes through it. The cost is a child
outside every turn showing `working` a little longer; the alternative
prints a wrong outcome that nothing can revise.

Also pins that a subagent announced after a task the filter rejected
still rosters: the announcement path was never what the child-traffic
gate closes.

* fix(native-chat): bound a subagent entry's id, not just its label

Every site that bounds a `subagent-group` block clipped the label and
handed the id through whole. From the Claude producer the id is bounded
upstream, but the legacy transcript import reads an untrusted file, so an
oversized id survived into the journal and then out to every wire that
replays it — 64 entries of it, since only the entry count was capped.

Each site now clips the id with the helper it already uses for its other
bounded fields: the journal's inline-text bound on import, the
transcript payload's metadata clip, and the mobile char cap (renamed,
since it is no longer a label-only cap).

* fix(native-chat): surface an adverse subagent outcome in the fallback sentence

The roster row's plain-text stand-in counted only `working`, so a fan-out whose
children all latched `unverifiable` (or `failed`, or `stopped`) rendered as
"Ran 3 subagents" — a completion claim. Mobile and paired web have no roster
renderer, so that write-time-frozen sentence is the entire row there, and
collapsing `unverifiable` into something that reads like success is exactly what
the SSH execution boundary forbids.

It now appends the worst adverse count, worst-first across failed/stopped/
unverifiable, and shows it even while siblings still work — matching the Codex
lane's shared `subagentGroupFallbackText` verbatim so collapsing the two copies
later is a deletion, not a behaviour change.

Also bounds the provisional entry id. `observeChildActivity` wrote the
`parent_tool_use_id` straight into the entry's durable id with no length cap,
while the announced path already rejects an over-long id via `claudeTaskId`.
Both now share `isBoundedClaudeTaskId`, and the provisional path rejects rather
than truncates, as the announced one does.

* fix(native-chat): stop a subagent label ordinal and a clipped roster key colliding

- claimLabel probes the labels the group actually rendered instead of a
  per-base counter, so a generated `Audit 2` cannot duplicate a provider's
  own `Audit 2`.
- Bound `NativeChatSubagentEntry.id` with a head plus a digest of the whole
  id at every site that bounds it. The id is the roster key: a prefix clip
  merged two distinct children onto one entry.
- Correct a stale journal-reducer test comment: tombstone cleanup is a
  map-state invariant, no longer load-bearing for revision ordering.

* fix(claude): merge duplicate unhandled-provider-frame imports

* fix(claude): preserve subagent lifecycle and bounded invocation identity

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 21:44:10 -07:00
Brennan BensonandMerge Sim 852495d35c fix(native-chat): unify launch routing and support structured worker placement (#19431)
* fix(orchestration): let worker-start actually produce a structured chat

`orchestration.workerStart` reads the user's "open agent tabs in chat"
default, but two placement checks downgraded a structured-preferring
worker to a PTY terminal agent for the two flags a routine dispatch
always passes:

  --worktree new-child / new-top-level  -> worktree_creation
  --model / --effort                    -> launch_preferences

so in practice a structured worker never happened.

launch_preferences was stale. PR #19040 gave AgentSessionAttachParams
`options` and added resolveStructuredLaunchSeedOptions, which narrows a
saved selection to exactly `model` and `effort` — the two ids both
structured providers accept as strings. --model/--effort now go through
that same narrowing (extracted as narrowStructuredLaunchSeedOptions) and
seed the worker's session instead of forcing a terminal. An option set
that narrows to nothing resolves to undefined, never `{}`, which would
fail the record's bounded-string guard under a code that is not a wire
refusal and strand the launch with no fallback.

worktree_creation was a consequence of createWorkerWorktree creating
agent-first: its startup terminal WAS the worker, so the structured
branch below it was unreachable for any new worktree. A structured
worker now creates the worktree with no startup agent and creates its
session for the worktree afterwards — the order the renderer's own
structured worktree create already uses. Because the executing host can
only answer agentSession.createSupport for a workspace that exists, that
verdict moved after creation: a refusal (WSL, and the rest) becomes a
terminal agent in the worktree just created, never a failed start.

--on and --terminal still downgrade, with their reasons intact, and
every remaining downgrade still states itself in the mode receipt.

The wait-for-setup gate is preserved explicitly. A PTY worker got it for
free — agent-first creation sequences the agent's startup command behind
the setup runner, so tui-idle could not arrive until setup exited. A
structured session has no startup command to sequence, so the gate is
now awaited directly, bounded by the start's own timeout.

Split out worker-worktree-creation.ts and worker-start-agent-placement.ts
rather than growing two files that were both pinned at the max-lines cap.

* refactor(native-chat): make shared feasibility authoritative for launch routing

* Type the structured setup gate's absent blocked reason so the wait union stays property-typed

The type-aware audit rejected the blocked-reason template literal: narrowing the
wait union with an 'in' check left the field typed unknown. Declaring that a
structured setup gate never carries a blocked reason restores the direct read.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 21:40:23 -07:00
98df62d8ff perf: reuse normalized task-page repository selection (#19454)
* perf: reuse normalized task-page repository selection

* test(task-page): cover ineligible persisted repo IDs in initial selection

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:55:25 -07:00
eedaf2bdfc perf: check backfill date cardinality before expanding ranges (#19456)
* perf: check backfill date cardinality before expanding ranges

* test(codex): pin the backfill cardinality gate to the enumerated range

Differential coverage at maxDates === length and length - 1 across leap days,
century rules, year rollover and DST switch dates.

* test(codex): type the backfill cardinality table as date tuples

Untyped it.each rows widen to string[], which tsc rejects when cast to the
3-tuple CodexSessionBackfillDate.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
2026-09-08 19:53:24 -07:00
2558bf1b17 perf: count command-line escapes without regex match arrays (#19499)
* perf: count command-line escapes without regex match arrays

* test(windows): pin command-line budget counting against the regex oracle

Covers every BMP code unit, astral and lone-surrogate adjacency, trailing
backslashes, %VAR% and carets, plus randomized quote-heavy command lines.

* perf(windows): count command-line escapes by seeking, not scanning

Counting every character regressed the shape this estimator actually guards: a multi-KB WSL script with almost no escapes went 25-38x slower on Windows. Seek escapes with indexOf so the cost tracks their count, and hand the rest to a plain scan once they are dense enough to pay for it.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:46:30 -07:00
6d2d63104a perf: reuse naturally ordered unique Codex trust ranges (#19492)
* perf: reuse naturally ordered unique Codex trust ranges

* test(codex): pin the trust-range ordering the dedup removal relies on

Removing the pairwise dedup+sort is only sound while the scanner emits
strictly ascending, non-overlapping spans. Guard that precondition so a
future scanner change cannot silently widen or drop a trust block.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:44:31 -07:00
e373c89536 perf: cache update timestamps for Linear and Jira result sorting (#19473)
* perf: cache update timestamps for Linear and Jira result sorting

* perf(issues): build updatedAt key map without an intermediate tuple array

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <neil@stably.ai>
2026-09-08 19:44:13 -07:00
5f63b850a5 perf: select checks-panel workspace attribution in one pass (#19471)
* perf: select checks-panel workspace attribution in one pass

* perf(checks-panel): normalize candidate paths only after the cwd filter

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <neil@stably.ai>
2026-09-08 19:43:59 -07:00
f74222d82c perf: cache AI-vault project attribution by host and cwd (#19470)
* perf: cache AI-vault project attribution by host and cwd

* test(ai-vault): guard both halves of the project attribution cache key

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:43:48 -07:00
063c1caa6f perf: reuse AI-vault path matchers and query and sort keys (#19469)
* perf: reuse AI-vault path matchers and query and sort keys

* test(ai-vault): add cross-platform path and sort-key differentials

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:43:36 -07:00
0f4bca26d4 perf: index usage session breakdowns during aggregation and merge (#19487)
* perf: index usage session breakdowns during aggregation and merge

* test(usage): cover key injectivity and merge-index freshness

Also restore both module docstrings to the top of their files.

Quote/backslash location and model keys prove the JSON tuple key stays
injective, and a second source carrying a location/model the merge itself
appended must fold into that row rather than duplicate it.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:43:25 -07:00
4a502d2e19 perf: index terminal neighbor order and remaining membership (#19453)
* perf: index terminal neighbor order and remaining membership

* test(session): prove indexed terminal close picks the pre-index neighbor

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:43:10 -07:00
8b7b670c40 perf: validate terminal adoption MRU membership with group sets (#19482)
* perf: validate terminal adoption MRU membership with group sets

* test(runtime): pin orphan-adoption group membership cardinality

Covers empty tab order, a tab claimed by two groups, duplicate group ids, an uncovered claimed tab, omitted/empty recentTabIds and both valid two-tab splits. The two-groups case is mutation-verified: swapping the global no-duplicate rule for the new per-group set fails it.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:43:06 -07:00
35bbc5ce5c perf: skip folder-scope construction for separate repository imports (#19452)
* perf: skip folder-scope construction for separate repository imports

* refactor(project-groups): share one mode flag between scope skip and root guard

Also cover the separate-import path with real repo paths, which the throwing
getter test no longer exercises.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:43:02 -07:00
782c4618fd perf: index observable skill installations by locked name (#19451)
* perf: index observable skill installations by locked name

* fix(skills): stop the convergence gate reading snapshots off Object.prototype

Lock names come straight from a JSON file on disk, so a skill directory named 'constructor' or 'toString' made knownSnapshots[name] resolve to a prototype function and threw TypeError out of the whole freshness inventory.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:42:51 -07:00
8983e026f7 perf: prune metadata caches only when an entry can expire (#19450)
* perf: prune metadata caches only when an entry can expire

* fix(metadata-cache): gate next sweep on the oldest capacity-eviction survivor

Capacity eviction drops the oldest entries after nextCacheExpiryAt is
computed, so the gate pointed at an expiry that no longer existed and
forced one needless full sweep.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:42:41 -07:00
60456107e6 perf: index closed browser page IDs during focus cleanup (#19449)
* perf: index closed browser page IDs during focus cleanup

* perf(browser): omit closed page focus keys instead of rebuilding the records

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:42:32 -07:00
f242c99af4 perf: scope activation inventory to the owning host and workspace (#19447)
* perf: scope activation inventory to the owning host and workspace

* fix(activation): keep an unscoped census fallback when the owning host is unnameable

Scoping the activation inventory made resolveActivationPtyListScope throw for
paired-runtime workspaces and made a detached relay reject the scoped list, and
both collapse to a 'blocked' gate. 'blocked' skips the sleeping-agent resume and
the caller's reseed, so an SSH target on the bounded offline floor lost its
initial pane and peer workspaces stopped resuming.

Fall back to the unscoped inventory that shipped in exactly those two cases; the
scoped fast path still covers local, folder and attached-SSH workspaces. Also OR
the host-reported worktreeId with the id-prefix match instead of preferring it,
because a relay seeds worktreeId from the host's own ORCA_WORKTREE_ID and a
session dropped from the census is one the gate forks a second writer onto.

* test(activation): update forkbomb fakes to the scoped session.tabs.list shape

The gate now asks the host for one workspace's snapshot instead of the whole session.tabs.listAll inventory and refuses an answer that does not name its scope, so the old snapshots-array fakes made it block instead of resume.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:42:25 -07:00
44eb95fc6b perf: bound tool pairing and avoid unused attribution arrays (#19468)
* perf: bound tool pairing and avoid unused attribution arrays

* test(native-chat): cover mobile pair-limit shapes and group allocation tests

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:42:18 -07:00
1dcd792649 perf: index selected skills when reporting bundle failures (#19481)
* perf: index selected skills when reporting bundle failures

* refactor(skills): reuse the shared renderer collator for delete-plan roots

src/renderer/src/lib/locale-text-collators.ts already memoises a base-sensitivity collator for six renderer modules; building another one per call in skill-delete-copy duplicated it and paid ICU setup on every summary render. Also pin dedup/order parity for the Set-based selected-skill filter.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:42:02 -07:00
126a69367c perf: drop oversized diagnostic records before retaining serialized text (#19445)
* perf: drop oversized diagnostic records before retaining serialized text

* fix(observability): leave a marker where an oversized trace record was dropped

Oversize records were discarded silently, leaving an unexplained gap in the
trace. Emit a tiny timestamped placeholder naming the span instead.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:58 -07:00
f0c210a906 perf: index Resource Manager labels and accumulated workspace rows (#19480)
* perf: index Resource Manager labels and accumulated workspace rows

* fix(status-bar): always build the resource tab-label index

The includeTabLabels flag left tabsByIdByWorktree empty for the orphan-count caller while the type declared it present, so a future reader would silently lose session labels. The only non-merge caller is memoized behind panel-open. Adds parity tests for the first-wins tab id and duplicated-worktree row rules the removed linear scans relied on.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:52 -07:00
ee2faa5a2d perf: index selected ancestor coverage during sidebar drag (#19443)
* perf: index selected ancestor coverage during sidebar drag

* test(sidebar): pin nested and sibling lineage expansion invariants

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:50 -07:00
db5df2f6f6 perf: index selected-host SSH leases by PTY (#19467)
* perf: index selected-host SSH leases by PTY

* perf(ssh): build lease index lazily and cover pty id normalization

* style(ssh): restore repository oxfmt formatting

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:44 -07:00
Neil c3a415487b perf(terminal): avoid quadratic status-marker scans in output bursts (#19373)
* perf(terminal): reuse forward OSC status terminator searches

* test(terminal): pin cached OSC terminator reuse across BEL frames

Document that forward match reuse requires a monotonic search offset and cover a distant ST held across many intervening BEL frames.
2026-09-08 19:41:42 -07:00
d872a5a8b8 perf: avoid repeated pending SOCKS payload copies (#19439)
* perf: avoid repeated pending SOCKS payload copies

* test(browser): cover pipelined SOCKS request-tail ordering in pending buffer

Also document takeBuffer's view-not-copy contract and restore node: import order.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:37 -07:00
d7162ef570 perf: reuse collators when scanning Warp themes (#19441)
* perf: reuse collators when scanning Warp themes

* perf(warp-themes): filter before collating and skip trivial sorts

Warp discovery collated every entry in the user's home or %APPDATA%\\warp before discarding the non-Warp ones; filter first so ICU only sees candidate names (order is unchanged: filtering commutes with a stable total-order sort). Also skip the collator entirely for 0/1-entry directories and single-file dialog picks, and drop the sort that ran only to be thrown away when the preview budget expired.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:15 -07:00
a0ea66a13d perf: reuse collators when sorting discovered skills (#19440)
* perf: reuse collators when sorting discovered skills

* perf(skills): share the discovery-source label sorter

Both native and WSL discovery built the same one-off source collator inline; hoist it next to sortDiscoveredSkills with the same <2 short-circuit, and pin ordering parity against the per-call comparator over a wide collation corpus.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:11 -07:00
400f70b006 perf: index tab membership when rebasing authoritative sessions (#19510)
* perf: index tab membership when rebasing authoritative sessions

* fix(runtime): drop host-removed tab ids from rebased group recents

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:07 -07:00
b68d2c46f6 perf: skip fuzzy ranking when exact file matches fill the window (#19509)
* perf: skip fuzzy ranking when exact file matches fill the window

* test(tab-bar): pin exact-match ordering against the pre-skip rank-then-slice pipeline

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:41:02 -07:00
b38c1313c4 perf: index discovered skill IDs and names for batch selection (#19507)
* perf: index discovered skill IDs and names for batch selection

* perf(skills): index only the selectors a share request asked for

Indexing every discovered skill made the common one-or-two-selector share slower than the linear scan it replaced (200 skills / 1 ID selector: 0.04us -> 38us). Scoping both indexes to the requested selector set keeps the O(selectors x skills) collapse and beats the unscoped index at every size measured, including 512x512 (5547us old, 176us unscoped, 64us scoped).

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:57 -07:00
de771ae4af perf: concatenate only carried JSONL line prefixes (#19506)
* perf: concatenate only carried JSONL line prefixes

* perf(ai-vault): avoid per-line carry reset and sweep every JSONL chunk boundary

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:51 -07:00
e0cac96772 perf: measure sidebar row geometry once before sorting delete anchors (#19503)
* perf: measure sidebar row geometry once before sorting delete anchors

* test(sidebar): prove the hoisted measure picks the same delete anchor

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:46 -07:00
5f2127ad12 perf: group chat history sequences lazily within result windows (#19501)
* perf: group chat history sequences lazily within result windows

* test(agent-session): prove lazy sequence grouping matches eager at every window edge

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:41 -07:00
8413a5b33c perf: cache same-axis pane subtree weights during equalization (#19500)
* perf: cache same-axis pane subtree weights during equalization

* test(pane-manager): prove cached equalize weights match the uncached walk

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:36 -07:00
6a9c5d8ce3 perf: replay pending chat asks with a FIFO cursor (#19496)
* perf: replay pending chat asks with a FIFO cursor

* perf(native-chat): track the pending-ask FIFO with counters instead of a queue

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:30 -07:00
96216601bc perf: retire exhausted buckets from host-balanced listings (#19493)
* perf: retire exhausted buckets from host-balanced listings

* test(listing): guard the bucket-retirement invariant and starvation fix

Document why in-place compaction keeps bucket[round] defined and preserves
first-appearance host order, and cover cap-exact filling plus small-host
representation so a drifting retirement predicate cannot emit blank rows.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:25 -07:00
814d3525d4 perf: lazily index workspace tabs during terminal binding replay (#19466)
* perf: lazily index workspace tabs during terminal binding replay

* test(persistence): cover same-worktree duplicate tab ids in binding replay

* style(persistence): restore repository oxfmt formatting

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:21 -07:00
2c5cd845a2 perf: append to owned project source lists without repeated copying (#19465)
* perf: append to owned project source lists without repeated copying

* docs(projects): record draft-ownership rule for in-place project merge

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:40:10 -07:00
96b33d7725 perf: index unified tabs for browser palette entries (#19464)
* perf: index unified tabs for browser palette entries

* test(palette): pin the duplicate-workspace guard the tab index relies on

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:58 -07:00
df8a43724f perf: index document history workspace and path identities (#19461)
* perf: index document history workspace and path identities

* docs(browser): note doc-history dedupe must mirror location equality

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:42 -07:00
86fe80da25 perf: preserve store state on unchanged document titles (#19459)
* perf: preserve store state on unchanged document titles

* fix(browser): compare every doc-history field before skipping a title refresh

A hand-listed title check would silently swallow any field added to
WorkspaceDocHistoryEntry later. Cover the over-cap trim path too.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:27 -07:00
c69d4d4f8b perf: track pane alias singleton or ambiguity without copying buckets (#19479)
* perf: track pane alias singleton or ambiguity without copying buckets

* test(persistence): pin pane-alias ambiguity cardinality parity

Covers 0/1/2/3/4 rows per tab plus a mixed ordering case, so a regression from has() to a truthy check would resurrect an ambiguous tab and fail.

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-08 19:39:26 -07:00
Brennan BensonandMerge Sim e80fae0c4d Summarize turn file changes and preserve resolved prompt receipts (#19229)
* feat(chat): summarize turn changes and retain resolution receipts

* fix(chat): defer turn diff details and localize resolution times

* fix: complete approval projection test fixture

* Align turn diff disclosure chevron and indent details

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 17:50:51 -07:00