mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* feat(session-search): add ranked history panel search and consent * test: wait for initial session indexing before refreshing results * feat(session-history): add local search settings and index controls * Use shared local host identifier for session index status * 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. * fix(preload): let the search bridge address the all-computers scope * feat(settings): live index status, enable confirm, advanced delete * 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". (cherry picked from commitc6b9179316) * 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. (cherry picked from commit640c715fbd) * fix(session-search): report a paired server without session search as host-too-old on status reads (cherry picked from commit1463e8a4bc) * fix(settings): let Button and Collapsible own their spacing and type
37 lines
1.5 KiB
TypeScript
37 lines
1.5 KiB
TypeScript
import { resolveAiVaultSearchSettings } from '../../shared/ai-vault-search-settings'
|
|
import type { GlobalSettings } from '../../shared/global-settings-types'
|
|
import type { RuntimeStore } from './runtime-store-contract'
|
|
|
|
/**
|
|
* How this host reaches the index it owns after the store write lands: the scanner
|
|
* child on the desktop, the in-process instance on orcad. Null on a host that owns
|
|
* none, where the write is still recorded and nothing is reconstructed.
|
|
*/
|
|
export type SessionSearchSettingsApply = (
|
|
before: Pick<GlobalSettings, 'aiVaultSearch'>,
|
|
after: Pick<GlobalSettings, 'aiVaultSearch'>
|
|
) => void
|
|
|
|
/** Only the two store members this write needs, so a caller need not own the whole runtime store. */
|
|
export type SessionSearchSettingsStore = Pick<RuntimeStore, 'getSettings' | 'updateSettings'>
|
|
|
|
/** Consent for this host's transcript index, written by a paired client rather than the local UI. */
|
|
export class RuntimeSessionSearchSettingsController {
|
|
constructor(
|
|
private readonly store: SessionSearchSettingsStore | null,
|
|
private readonly apply: SessionSearchSettingsApply | null
|
|
) {}
|
|
|
|
async setEnabled(enabled: boolean): Promise<void> {
|
|
if (!this.store?.getSettings || !this.store.updateSettings) {
|
|
throw new Error('runtime_unavailable')
|
|
}
|
|
const before = this.store.getSettings()
|
|
this.store.updateSettings(
|
|
{ aiVaultSearch: { ...resolveAiVaultSearchSettings(before), enabled } },
|
|
{ notifyListeners: true }
|
|
)
|
|
this.apply?.(before, this.store.getSettings())
|
|
}
|
|
}
|