/** * `GET /search` (api-types 0.127.0): one query over runs, projects, repos, transcripts (chat * transcripts and unit outputs), rules, memories and documents. * * The index ({@link SearchIndex}) is built LAZILY: nothing is read until the first search, and that * read runs off the request path — the first request waits a short while for it and answers with * the sources still reading marked `indexing`. After that the index follows the daemon: * - engine frames: a run's lifecycle re-reads the run list (debounced), a finished unit's output is * read once, a chat reply re-reads that chat's transcript, a closed chat leaves; * - the daemon's own writes: a successful mutating request re-reads the sources its path can change * (`noteWrite`), debounced; * - sources written outside the daemon (memories, rules a worker lands, documents) are also re-read * when a search finds them older than their TTL — in the background, never on the request. * A source that fails to read is `unavailable` with its reason; the others still answer. */ import type { FastifyInstance } from 'fastify'; import type { ConformanceRule, CoreEvent, InteractiveDocIndexRow, MemoryItem, Project, RepoEntry, SearchResponse, SearchType, SessionView } from '../core/types.js'; import { SearchIndex, type SearchIndexOptions } from './search-index.js'; /** The sources the service reads. `chat` and `unit` both feed the `transcript` type. */ type Source = 'run' | 'project' | 'repo' | 'chat' | 'rule' | 'memory' | 'doc'; export interface SearchServiceDeps { sessionsDetail: () => Promise; workOutput: (unitId: string) => Promise; projectList: () => Promise; listRepos: () => Promise; listRules: () => Promise; /** The run→project and chat→project membership. */ projectOf: (id: string) => string | undefined; /** What the daemon remembers about a run beyond the engine's row (unix SECONDS). */ runFacts?: { chatIdFor(runId: string): string | undefined; createdAtFor(runId: string): number | undefined; endedAtFor(runId: string): number | undefined; }; /** The chat transcript directory (`.jsonl` per live chat); absent = no chat transcripts. */ chatsDir?: () => string; /** The memory store (estate `memory.list`, shaped); absent = memories are not searched. */ memories?: () => Promise; /** The documents index; absent = documents are not searched. */ docs?: () => Promise; log?: (m: string) => void; index?: SearchIndexOptions; ttlMs?: Partial>; /** Write/frame bursts coalesce for this long before a re-read (ms). */ debounceMs?: number; /** How long the first search waits for a source's first read (ms). */ firstReadWaitMs?: number; /** The query's hard time budget (ms). */ budgetMs?: number; } export declare class SearchService { readonly index: SearchIndex; private readonly deps; private readonly states; /** Off until the first search: before it, frames and writes cost nothing. */ private active; private closed; /** The last run list, for titling unit transcripts. */ private runsById; /** Unit outputs read (or found empty) — a backfill does not read them again; a `unitDone` does. */ private readonly unitsTried; private readonly unitQueue; private unitWorkers; private readonly chatTimers; constructor(deps: SearchServiceDeps); private state; private ttl; /** Whether the source exists on this daemon. */ private available; close(): void; /** One engine frame (the daemon's fan-in calls this for every frame; cheap until the first search). */ ingest(event: CoreEvent): void; /** A mutating request answered 2xx/3xx: re-read what its path can change. */ noteWrite(method: string, url: string): void; private markDirty; private markChat; /** Re-read one source (coalesced: a refresh asked for mid-read runs once more after it). */ private refresh; /** Before a query: read what was never read, re-read what is past its TTL (both off the request). */ private ensure; private load; private runUpdatedAt; private loadRuns; private enqueueUnit; private drainUnits; private readUnit; private loadChats; /** One chat's transcript file: its head and tail (bounded), as one entity. */ private readChat; search(q: string, opts: { types: readonly SearchType[]; limit: number; offset: number; signal?: AbortSignal; }): Promise; private sourceState; } /** Parse `types`: comma-separated; unknown → `null`. */ export declare function parseTypes(raw: string | undefined): SearchType[] | null; export declare function registerSearchRoutes(app: FastifyInstance, service: SearchService): void; export {}; //# sourceMappingURL=search-routes.d.ts.map