/** * The thread LIFECYCLE — ids, ownership, the listing title, the guarded write. * * It lived in `@vendoai/agent` until that package was deleted, and it lands here * rather than in the store because a `Thread` carries ai's `UIMessage`: the store * block deliberately has no `ai` dependency. Its one consumer is the harness turn * door (`harness-turn.ts`), which resolves, persists and evicts through it. * * The SHAPE it reads and writes is core's, not this file's — `@vendoai/vendo/ui` reads * a thread off the wire and may not import this package. */ import { type RunContext, type StoreAdapter, type Thread, type ThreadId, type ThreadSummary, type VendoRecord } from "./core/index.js"; import type { UIMessage } from "ai"; export type { Thread, ThreadSummary }; /** Whether `resolve` will accept this id at all. Exported for the caller that * batches a turn's opening reads: it addresses the thread BEFORE `resolve` * sees it, and a malformed id must still cost a refusal rather than a read. */ export declare const isThreadId: (id: string) => boolean; /** 03-agent §5. Exported for the caller that batches a turn's opening reads: it * has to name the thread before it can ask for it, and a turn with no id yet * is still a turn with a workspace to read. */ export declare function mintThreadId(): ThreadId; /** The listing title for a transcript. Exported because a turn that APPENDS * (rather than rewriting the thread through `persist`) still has to carry a * freshly-derived title along with the write — deriving it stays client-side, * where the message shapes are understood. */ export declare function deriveTitle(messages: UIMessage[]): string; /** 03-agent §5 */ export declare class ThreadRepository { private readonly store; constructor(store: StoreAdapter); resolve(id: ThreadId | undefined, ctx: RunContext, /** The row this id reads to, when the caller ALREADY read it (the turn * envelope reads it beside the workspace index). `null` is a read absence, * the same answer the read below would have given — so it is `undefined`, * not `null`, that means "nobody read it yet". */ prefetched?: VendoRecord | null): Promise; get(id: ThreadId, ctx: RunContext): Promise; list(ctx: RunContext): Promise; delete(id: ThreadId, ctx: RunContext): Promise; persist(thread: Thread, messages: UIMessage[], /** `fresh: true` = the caller JUST resolved this id to no row (sub-1s * shipment): the first attempt goes straight to `insertIfAbsent` instead * of re-reading the absence — one fewer round-trip on every first turn. * A row that appeared in between makes the insert lose, and the ordinary * read-merge-write loop below takes over; the guarantee never moved. */ opts?: { fresh?: boolean; }): Promise; /** The Thread as it should be written for this turn: merged messages, a * freshly-derived listing title (never a stale prior title — `list` reads it * back without loading messages), and a new updatedAt. */ private updatedThread; private create; /** AGENT-11 / ENG-237: drop a subject's threads from the store on session * eviction. Store-backed: a no-op — the store's own TTL sweep already erased * the rows (02-store §4 erase cascade) before the umbrella calls this, so the * list below simply finds none. Internal-default (no `store` configured): * the ONLY place those rows get reclaimed, since nothing else sweeps them. * Returns the evicted thread ids so callers can release any per-thread state * keyed by id (ENG-252 loadouts) — otherwise a reused `thr_*` id would inherit * the evicted thread's searched-in tools. */ evictSubject(subject: string): Promise; /** Follows the store's pagination cursor to exhaustion (it pages at 100) — * otherwise a subject's >100th thread, possibly their most recently active, * would silently vanish from `list`/`evictSubject`. */ private listRecords; }