Commit Graph
8669 Commits
Author SHA1 Message Date
Jinwoo-H d146043a6e fix(right-sidebar): keep the focus-request callback out of render
React Doctor flagged the ref written during render; useEffectEvent is the
codebase's pattern for a latest-callback the effect reads.
2026-09-16 11:48:19 -04:00
Jinwoo-H fa77b0fe47 fix(ai-vault): say OpenCode-in-WSL is not searchable from Windows yet
The scan issue for an OpenCode database on a \\wsl.localhost share read as an
error with an instruction the user cannot follow. It now surfaces on the Agent
Session Search settings page under a computer row, where it belongs as a known
limitation, so the WSL branch now reads 'OpenCode sessions inside WSL can't be
searched from Windows yet.'

Copy only: the issue keeps kind 'scope' and its path, every other branch is
untouched, and discovery, the WSL gate, and the busy-timeout behavior are
unchanged.
2026-09-16 02:10:43 -04:00
Jinwoo-H ffa164db8f feat(settings): offer one stateless Enable on all computers button
The row above the list is now just that button. It appears when a paired
server is reachable, new enough and off, acts on exactly those plus this
computer, and disappears when there is nothing left to do. What it offers is
read off the rows each render, so it cannot disagree with them.

Deletes the standing auto-enable consent with it: the persisted flag, the
code that armed and cleared it, the per-host memory of which switches the
user had touched, and the line that promised future computers would turn
themselves on. A preference that acts on hosts the user never sees is worse
than a button they press when they mean it.
2026-09-16 02:10:43 -04:00
Jinwoo-H 8ae815be15 fix(settings): turn session search on without a confirmation dialog
Each switch and Turn on all now act on the click. The dialogs restated the
row they sat under and stood between the user and a preference they can
reverse with the same control. Clearing search data keeps its dialog: that
one destroys something.
2026-09-16 02:10:43 -04:00
Jinwoo-H 11bb0df14f fix(settings): hide the fleet roll-up when no server is paired
With only this computer, the count, the Turn on all button and the two
subheads all restate the single switch under them. Show them once a paired
Orca server exists, which is the first point at which they say anything.

(cherry picked from commit 2c7ee206bd)
2026-09-15 22:23:04 -04:00
Jinwoo-H 2ff25fc818 feat(settings): redesign Agent Session Search for many computers
Renames the pane, splits the list into this computer and paired Orca
servers, and puts a count of what is on above it with a Turn on all that
skips offline and too-old hosts and keeps going past a host that refuses.
Consenting once persists a standing consent so a server that later becomes
reachable turns on without another dialog; turning one off by hand drops it.
Rows past the sixth fold away, ordered by what the user can act on. Status
sentences now say how much is searchable instead of Ready, and an off
computer says so with its switch alone.

(cherry picked from commit f4cfac3ee2)
2026-09-15 22:09:15 -04:00
Jinwoo-H 7ad59e1886 feat(right-sidebar): let a caller open the session panel ready to type
showAiVaultSearch opens the sidebar on the vault tab and sets one flag. The
panel takes the flag, widens its scope to every computer, focuses the search
box and clears the flag, so a later remount stays where the user left it.

(cherry picked from commit 2b66e20fcd)
2026-09-15 22:09:02 -04:00
Jinwoo-H 60a72bdbf7 feat(session-search): report how many messages an index holds
The status contract gains an optional messagesIndexed, read from the store
beside the file-state counts and cached the same way, so a settings row can
say what is searchable rather than how many files were opened. Optional on
the wire: a paired server that predates the field degrades to a session count.

(cherry picked from commit 366118be0f)
2026-09-15 22:09:02 -04:00
Jinwoo-H a0481b9af7 chore(session-search): schema 8 on the integration branch so OpenCode sessions are re-read with tool parts 2026-09-15 16:17:05 -04:00
Jinwoo-H 3b99de486a feat(session-search): capture OpenCode tool and reasoning parts
Text parts alone left OpenCode behind every file-based provider: a command
someone ran, what it printed, and the model's reasoning were all unsearchable.

Widens the capture query to text, reasoning and tool parts. Reasoning folds
into the turn's own words, the way the shared block list already treats a
thinking block. Each tool part becomes one `tool` message carrying the call
line and what came back, built with the same `toolCallText` every file
provider uses; OpenCode's `filePath` is renamed to the `file_path` spelling
that list knows, so a call is findable by its file argument.

Adds a decoded-size ceiling beside the existing part ceiling. It is the bound
a non-streaming source needs and a streaming one does not: a JSONL provider
publishes each message as it reads it, while this one holds a whole session
before posting it across the worker boundary. Neither ceiling truncates; both
fail the read so it is retried and surfaces.
2026-09-15 16:16:54 -04:00
Jinwoo-H 11d42ee6c2 chore(session-search): schema 7 on the integration branch so OpenCode placeholder rows are rebuilt
Integration builds already shipped schema 6 with the Codex fix; main-bound
PR #20870 bumps 5 to 6 and will need 6 to 7 once #20763 lands first.
2026-09-15 15:04:26 -04:00
Jinwoo-H 662dad89d1 test(session-search): assert every source speaks, not every agent
OpenCode has two storage shapes, so asking only that some OpenCode session
published messages was satisfied by the legacy JSON fixture while every
SQLite session in the vault stayed silent. Assert per discovered source and
keep the agent-coverage check beside it.
2026-09-15 15:04:06 -04:00
Jinwoo-H 8d166761ef feat(session-search): index OpenCode SQLite sessions
OpenCode sessions live in one SQLite database read on a worker thread, and
the worker only ever answered with the newest few messages for the panel
preview. The parser therefore published nothing over the transcript channel,
so the search index wrote a placeholder row for every OpenCode candidate and
no OpenCode message was ever searchable.

Adds a `capture` request to the worker protocol that returns the session and
every text part of every user/assistant turn from one open of the database.
The agent parser asks for it whenever a sink is listening, so OpenCode joins
the whole-document sources on the same path as Grok, Cursor and Gemini. The
placeholder path (`parserPublishesMessages`, `noteUnreachableParser`) is gone;
an OpenCode read that fails now fails like any other file.

Bumps the index schema so existing indexes drop their placeholder rows, and
adds `sessionsByAgent` to the index status, which is the count that made this
bug visible.
2026-09-15 15:04:06 -04:00
Jinwoo-H b1473b248c docs(skills): shape the search section like the other command sections 2026-09-15 01:01:21 -04:00
Jinwoo-H b23f21dceb docs(skills): teach the orca-cli guide the search command
One section: what orca search covers, one host at a time, scope and
narrowing flags, index status before searching, and that a human turns
search on.
2026-09-15 00:40:05 -04:00
Jinwoo-H 63fe9d8646 Merge remote-tracking branch 'origin/session-search-settings-hosts' into session-search-verify 2026-09-14 23:47:47 -04:00
Jinwoo-H 267af1afb3 feat(settings): product-facing copy for session search
Say search, not index or transcript; lead with what the user gets and
where it shows up; one plain privacy sentence; count sessions, not files;
drop the mechanics that change no decision (stop hint, SSH note, source
roots jargon).
2026-09-14 23:47:44 -04:00
Jinwoo-H b61c7a542a fix(session-search): mark a snippet with the route that retrieved it
A phrase hit was highlighted with the OR expression over the stop-word
stripped terms, so an exact sentence rendered as scattered bold words with
its stop words plain. The snippet now uses the expression the route
matched by: one run for a phrase, every typed word for AND, the terms for
OR.
2026-09-14 22:39:08 -04:00
Jinwoo-H c4dfe1c76c fix(ai-vault): index Codex agent replies whose content blocks are typed Text
Codex 0.153+ writes paginated rollouts whose completed agent messages carry
content blocks typed `Text`. The transcript reader matched block types
case-sensitively, so every assistant turn from those sessions was dropped
before it reached the search index while user turns and tool output were
kept. Match case-insensitively and bump the index schema so existing
indexes are rebuilt with the replies present.
2026-09-14 22:07:18 -04:00
Jinwoo-H 96be9e7068 fix(session-search): try phrase and AND routes for prose queries before OR
An exact sentence pasted out of a transcript was not returned. The route
ladder only ran the phrase and AND rungs for a literal-looking query, so
prose fell straight to OR, where the sentence's common words filled the
candidate limit with recent sessions and the old session holding the
sentence never reached ranking.

The planner now carries a `phrase` candidate: the query's tokens in order
with stop words kept, which is what the sentence is actually indexed as.
The ladder runs phrase then AND over those tokens for every query of two
or more tokens. A one-token query still takes the rung only when it
looked literal. `incomplete` is reported by the rung that answered rather
than accumulated across every rung tried.
2026-09-14 20:19:50 -04:00
Jinwoo-H b47f0d3a44 fix(session-search): only an unread backlog keeps the phase at indexing
An armed cadence sweep on a drained index is not a backlog, so it no
longer flashes the pane to indexing with nothing due.
2026-09-14 20:18:16 -04:00
Jinwoo-H 3226aec322 fix(session-search): keep the index status honest while a sweep has a backlog
A pass stops reading at its wall-clock deadline and records nothing about
the candidates it never opened, which is correct: being owed a read is a
fact about the row, not an entry in a queue. But a candidate the opening
sweep never reached has no row at all, so the store's `due` count cannot
see it. The sweep still reported `completed`, the indexer stamped
`lastSweepCompletedAt`, and `status()` answered `current` with a backlog
of thousands: "Up to date - 130 files indexed", then 630, then more.

The read loop now counts what it decided was owed and did not read and
hands the number back as `left`; the pass propagates it; the indexer
holds the last pass's count, adds it to `filesDue`, reports `indexing`
while it is non-zero or a sweep is owed, and no longer stamps a sweep
the deadline cut short as complete.
2026-09-14 20:18:15 -04:00
Jinwoo-H 92ec19c93e Merge remote-tracking branch 'origin/session-search-settings-hosts' into session-search-verify 2026-09-14 20:18:00 -04:00
Jinwoo-H 0515588681 fix(settings): turn search off before deleting its index
Delete index cleared the index while search was on, so the host closed, removed and immediately reconstructed it and everything reindexed. Turn local search off first, then clear, so the rebuild only happens when the user switches search back on.
2026-09-14 20:13:25 -04:00
Jinwoo-H c90ed50f4c Merge remote-tracking branch 'origin/session-search-settings-hosts' into session-search-verify
# Conflicts:
#	src/preload/api/ai-vault-api.ts
#	src/preload/api/ai-vault-bridge.ts
#	src/renderer/src/web/preload-api/web-ai-vault-api.ts
2026-09-14 18:38:28 -04:00
Jinwoo-H a3d751f6e7 fix(settings): treat a host-too-old status read as an outdated server 2026-09-14 18:37:59 -04:00
Jinwoo-H 1463e8a4bc fix(session-search): report a paired server without session search as host-too-old on status reads 2026-09-14 18:37:48 -04:00
Jinwoo-H 0497e6fe93 feat(settings): connected computers rows for session history indexing
Agent Session History now lists every computer that can hold an index --
this computer first, then each paired Orca server -- as one row with an
icon, a name, a single status line and its own switch. Indexing consent is
stated once above the list, and each row carries the switch for the host it
names, so turning search on for a server no longer means finding that
server's own settings.

Server rows poll aiVault.searchStatus on the same 2s/10s cadence as the
local one while the pane is visible, and report what the host actually
answered: Off, a sweep in progress, or an up-to-date count. A server that is
not connected stays listed but dimmed, with its last known switch position
and no claim about its index. A host that refuses the set call with
host-too-old flips to an update prompt that links to Remote Servers.

The old "Enable session history search" switch and the separate index-status
row are gone; their status copy moved to session-history-status-copy.ts and
their polling to use-session-search-status.ts, so every row shares one
message builder and one poll. Advanced > Delete index copy is unchanged and
still local-only, and a paired web client still sees this computer alone.

window.api.aiVault.setSearchEnabled is declared and bridged here but
implemented by the parallel backend PR.
2026-09-14 18:33:09 -04:00
Jinwoo-H 640c715fbd feat(session-search): enable indexing on paired servers from a client
Adds `aiVault.setSearchEnabled` so a desktop can turn a paired Orca server's
transcript index on or off and have the server apply it without a restart.

The runtime method refuses any caller without a `pairedDeviceId` with a
`forbidden`-class error, writes the whole resolved policy through the runtime
store so retention rides along untouched, then reaches the index through a
host-supplied hook: `applySessionSearchSettingsChange` on the desktop, the
in-process instance's new `apply` on orcad. The relay is unchanged.

Wire compatibility is Rule 1 shaped: a new optional method. A server that
predates it answers method-not-found, which the desktop IPC handler maps to an
error whose message is exactly `host-too-old`. Old clients never call it. The
method is deliberately absent from the mobile allowlist, and `aiVaultSearch`
stays out of the paired settings projection.
2026-09-14 18:32:54 -04:00
Jinwoo-H 2895c96f61 Merge remote-tracking branch 'origin/session-search-settings-ui' into session-search-verify 2026-09-14 17:07:58 -04:00
Jinwoo-H bbd2eff615 feat(settings): live index status, enable confirm, advanced delete 2026-09-14 17:06:56 -04:00
Jinwoo-H 3974546e2a Merge remote-tracking branch 'origin/session-search-panel-all-hosts' into session-search-verify 2026-09-14 15:26:49 -04:00
Jinwoo-H c6b9179316 feat(session-search): search every computer from the history panel
The panel's "All computers" scope produced no request: the hook parsed the
scope into a single host id and stopped when that was null, so the panel
answered "Choose one computer to search its sessions." The desktop already
merges every enumerated host behind `aiVault:searchSessions`, so pass the
scope straight through and stamp each hit with the host it came back on.

Hosts the merge could not search are named under the results header with a
short reason, since a silent partial answer reads as "no such session".
2026-09-14 15:25:37 -04:00
Jinwoo-H 259720202c Merge remote-tracking branch 'origin/session-search-unhide-cli' into session-search-verify 2026-09-14 15:16:35 -04:00
Jinwoo-H 18fa46c5b4 feat(cli): show orca search now the settings toggle ships 2026-09-14 15:15:52 -04:00
Jinwoo-H d35c5a79b0 Merge branch 'session-search-all-hosts' into session-search-verify 2026-09-14 14:59:33 -04:00
Jinwoo-H 288654225a fix(preload): let the search bridge address the all-computers scope 2026-09-14 14:59:29 -04:00
Jinwoo-H 820ac1ebb3 chore(i18n): regenerate catalogs after merging the session-search branches 2026-09-14 14:55:26 -04:00
Jinwoo-H 8c45c3b14c Merge remote-tracking branch 'origin/session-search-all-hosts' into session-search-verify
# Conflicts:
#	src/renderer/src/i18n/locales/en.json
2026-09-14 14:54:49 -04:00
Jinwoo-H 6454d71945 Merge remote-tracking branch 'origin/session-search-panel-ui' into session-search-verify 2026-09-14 14:54:25 -04:00
Jinwoo-H af61a2424d Merge remote-tracking branch 'origin/session-search-pr6-cli' into session-search-verify 2026-09-14 14:54:25 -04:00
Jinwoo-H 05187bf77e feat(session-search): merge all-computers search across hosts
The `all` scope on `aiVault:searchSessions` now fans out from the desktop
to every host the session list enumerates and merges the pages into one.
Legs run in parallel: the local index through the search service, SSH and
runtime hosts through the existing remote search client.

Two fixed orders, because relevance scores from independent indexes are
not comparable. `newest` asks every leg for recency and k-way merges on
`updatedAt`, nulls last, ties broken on execution host id. `relevance`
rotates hosts in host-id order by their own rank.

The merged cursor is an opaque base64url payload holding each host's
cursor, how many of its current page were already emitted, and the
generation that offset counts into, plus the page size and sort the
cursor belongs to. A host whose index moved is fenced to `stale` and
stops contributing; the rest keep paging. Per-host outcomes ride back on
one new optional `hosts` field on the results response.

`aiVault:searchStatus` with `all` stays refused, and neither the runtime
RPC nor the CLI gains the scope, so a fan-out is never two hops.
2026-09-14 14:35:33 -04:00
Jinwoo-H 73b6d02fd8 Use shared local host identifier for session index status 2026-09-14 14:05:49 -04:00
Jinwoo-H fda29317d7 feat(session-history): add local search settings and index controls 2026-09-14 14:05:49 -04:00
Jinwoo-H d0f2348cb7 feat(session-search): add ranked history panel search and consent 2026-09-14 14:05:47 -04:00
Jinwoo-H 8f8d2e3717 merge: integrate remote session-search-pr6-cli before push 2026-09-14 13:47:36 -04:00
Jinwoo-H 2b012c1401 feat(cli): hide orca search until the settings toggle ships
`orca search` stays dispatchable but leaves every discovery surface: root
help, group help, unknown-command suggestions, and `agent-context --json`.
`buildAgentContext` did not filter hidden specs, so it also stops leaking
the hidden `terminal stop`.
2026-09-14 13:47:16 -04:00
Brennan BensonandMerge Sim c6a7216984 fix(native-chat): hide activity while awaiting input (#20496)
* fix(native-chat): hide activity while awaiting input

* fix(native-chat): keep approval turns cancellable

* test(native-chat): satisfy split PR quality gate

* fix(native-chat): catalog approval cancellation label

* fix(native-chat): include approval cancellation runtime label

* fix(codex): settle prompts when cancelled turns complete

* fix(codex): settle prompt registry fallbacks

* test(native-chat): cover pending interaction fallbacks

* test(native-chat): split prompt state coverage

* test(native-chat): keep prompt state isolated

* fix(native-chat): bound prompt turn backfill

* refactor(codex): centralize prompt registry bounds

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-14 10:42:38 -07:00
Jinwoo Hong eba56f2f69 feat(ai-vault-search): construct the session search indexer in the scanner service behind a setting (#20516)
* feat(ai-vault-search): persist agent-session search consent and retention

Two booleans and nothing else: `enabled` and `historyDays`, off by default
because building the index reads every transcript on the machine. No `paused` --
the PR 3 indexer is immutable, so every change is close-and-construct.

The settings IPC normalizes a write like every other field and hands the change
to the index; there is no UI for it until PR 8.

* feat(ai-vault-search): hold one indexer and engine pair per host

The object that owns a host's live index and the three recipes that change it.
The indexer is immutable, so a settings change is close-and-construct, disabling
is close with no replacement, and clearing is close, remove the database,
construct. The new instance's first sweep purges a narrowed window and admits a
widened one, so neither needs a code path.

The database sits beside the scanner's parse cache, one file per host. A runtime
with no node:sqlite can hold no index at all, which the Node 18 floor on orcad
and the relay makes a real case rather than a hypothetical one.

* feat(ai-vault): let the scanner child own the session search index

The transcript reader runs in that child, so the index consumer has to as well:
one read serves both the session list and the index. Three request operations
(search, status, reconcile) and one fire-and-forget settings message carry
everything a parent needs; main never opens the database file.

The init frame becomes a factory because it is read at every spawn, so a
respawned child sees current consent rather than the first frame's. A child
holding a running index is never idle from the parent's side, so idle retirement
is suppressed while the index is on -- retiring it would stop the reconcile loop
until some later scan happened to respawn one.

Both files this lands in were already at the max-lines ceiling, so three
collaborators move to where they belong rather than being disabled around: the
invalidation deadline into the class that owns invalidations, call cancellation
and the start requeue into the call-state module, and orcad's flag parsing into
its own file.

* feat(ai-vault-search): register a search service on every host that answers

Without a registered service a host answers no-service, which means "this host
does not have the feature" rather than "the index is off". All three hosts now
answer the second thing.

The desktop forwards to the scanner child. orcad and the SSH relay daemon have
no such child -- orcad ships only the watcher and daemon entries, and the relay's
AI Vault sidecar runs the remote scanner, which publishes nothing to the
transcript channel -- so on those two the index lives in the process that would
drive its reads, gated on a runtime that has node:sqlite at all.

The relay registers with consent off and no way to turn it on: nothing carries a
setting to a remote host yet. That is the honest state, and it is still worth
registering, because it is what tells a client the difference between off and
too old.

* test(ai-vault-search): price a warm pass over five thousand transcripts

The number the reconcile interval will be revisited against, measured rather
than argued: a warm sweep stats every file under every root, a warm cycle stats
the newest N per agent, and neither reads what the index already holds. It does
not tune the interval.

* fix(ai-vault-search): answer the casting gate without assertions

main's new type-assertion rule reaches every file this branch touches. All nine
sites drop the cast rather than carry a SAFETY: rationale: the operation guard
narrows with `in`, the sqlite probe narrows the builtin it loads, the child test
keeps the discriminated reply instead of widening it, and the settings resolver
takes `unknown` -- which is what it really reads, since a persisted profile can
hold a value no version of this code wrote.

* fix(ai-vault-search): let a refreshed scan root reach the live index

The parent re-resolves scan roots before every policy push, precisely so a
WSL distro or extra Codex home that appeared since the child spawned enters
the window. The child forwarded only the settings to a live instance and used
the roots solely in its `??=` initializer, so those roots were dropped for the
child's lifetime.

The indexer stays immutable: a structurally different root set closes the pair
and constructs a new one, the same way a changed databasePath already does.
Compare via `sameSessionSearchRoots` rather than a plain JSON compare, because
nothing fixes the key order two producers write; lists are sorted too, since
the indexer walks every root and a re-enumeration that reorders is not a
change. An unchanged set still never restarts a running index.

The orcad and relay in-process hosts resolve roots once at install and never
re-apply, so they have no such seam.

* fix(ai-vault): restart the scanner child the index is holding

Three review items.

The hold keeps a child alive for the index, but only a queued call ever
started one: `pump()` skipped a hold with an empty queue, so an idle indexing
child that crashed, or an `ensureChild()` that failed at start, left indexing
stopped until an unrelated request happened to arrive. `pump()` now starts the
child the hold requires, which is also the restart callback the fault policy
already schedules, so the existing delay and circuit bound the retry exactly as
they bound a queued call's start. `updateSessionSearch` goes through the same
seam instead of its own `ensureChild` call.

A search registers no AbortController, so a cancel sent for a search id was
added to the `cancelled` set and never consumed. Nothing can reach that today
-- no caller passes a signal and the child answers in milliseconds -- so this
is only a leak of ids: consume it when the search settles.

The orcad argument doc claimed a `--`-prefixed value stays a flag. The parser
takes the next token regardless, and orcad-launch-contract.test.ts pins that,
so the doc is what was wrong. Behaviour is unchanged.

* fix(ai-vault): recover search indexing and refresh scan roots

* fix(ai-vault): defer search refresh policy reads

* fix(session-search): stabilize paging and host enablement

* fix(session-search): refresh host roots within full sweeps

* docs(session-search): clarify initial root fallback
2026-09-14 13:38:37 -04:00
Brennan Benson a4c11f1889 fix(native-chat): stop a bounded tail read from moving the chat cursor past unapplied rows (#20581)
* fix(native-chat): stop a bounded tail read from moving the chat cursor past unapplied rows

A structured chat pane could latch "Working for N" forever after the agent had
finished, showing the send arrow rather than Stop, while the sidebar and
`worktree ps` correctly read idle.

The client replica has one position (`state.cursor`) and one body. Two
operations keep those consistent: replace (both from one host snapshot) and
append (rows contiguous with the cursor). The `tail-page` branch was a third
thing: it took the cursor from the journal head, the items from a bounded page
(200 items, byte-capped), then merged retained client submissions over the
page's. Under continuous journal writes the client is always slightly behind,
so the branch ran on every window focus and on every pane re-activation. When
more than a page of rows had landed since a send, that send's user item fell
off the page, its submission was not carried, the retained `pending` survived,
and the cursor jumped past the dispatch-acceptance row. Nothing re-sends it: a
batch carries only touched items and that submission is never touched again.

Delete the third operation rather than guard it. A live subscription is now the
only thing that moves the cursor, and `subscribe({ cursor })` already replays
exactly the missed rows.

- remove the window `focus` listener and the owner/transport `refresh` contract
- skip warm hydration: a retained owner subscribes at its applied cursor
- cold hydration keeps its history read, applied as the existing `snapshot`
  (replace) event rather than `tail-page`
- delete the `tail-page` action and its reducer branch
- delete `resumeCursor` and `shouldAdvanceStructuredResumeCursor`; two cursors
  with two advancement rules were how position and body drifted apart

`older-page`/`loadOlder`, the unattached-refusal grace, generation guards and
the coalescer are unchanged. No host, wire or schema change.

Also fixes a second cost of the same branch: focus during a busy turn discarded
paged-in older items, shrinking the transcript to one bounded page mid-turn.

* fix(native-chat): preserve unavailable mixed-version session fences
2026-09-14 10:28:16 -07:00