/** * The run-activity store: what a surface OUTSIDE * the conversation may know about a turn that is running inside it. * * Closing the panel is leaving, never stopping: the overlay hides its portal * and the conversation keeps streaming underneath (ENG-221). The launcher pill * lives outside that portal, so the two cannot share React state — this is a * module singleton (the same shape as the toast queue) that every thread * surface publishes its turn into and the pill/badge read. * * Facts only. Whether a finished run counts as "unseen" is answered here by * one rule — a settle is unseen until somebody says it was seen — and the * overlay marks results seen the moment the panel is open. */ import type { BeatPhase } from "../../core/index.js"; import { type UIMessage } from "ai"; /** * §3.4 — one BEAT: the transient `data-vendo-status` channel's payload, after * the receiver has decided it is words a person may read. `phase` and `appId` * are present exactly when the harness sent usable ones (the receiver never * invents either). */ export interface VendoBeat { label: string; phase?: BeatPhase; appId?: string; } /** The live step of a running turn, for the pill's label + ring. */ export interface RunActivity { running: boolean; /** WHICH conversation is running. A host may mount several thread surfaces at once (the `/concurrent` scenario mounts an embedded thread beside an overlay) and this store answers for whichever one is running — so any surface that narrates a run has to check the run is one it is showing. */ threadId?: string; /** §3.4 — the RUNNING turn's beats, oldest first. Ephemeral by the same rule as everything else here: nothing is running, so there is nothing to narrate, so the list is empty. */ beats: readonly VendoBeat[]; /** RAW tool name of the live step — the reader humanizes it (`toolTitle`). */ tool?: string; /** Tool steps of the live turn that have settled, and how many it started. `total > 1` is what makes the ring determinate: an honest count of the steps the turn has actually begun, never a guess at what it will do next. */ done: number; total: number; } /** One finished turn, as a line the user can act on from outside the panel. */ export interface RunResult { /** Bumped per settle so a reader can tell a new result from a re-render. */ id: number; /** Plain-words headline: what the turn produced. */ headline: string; /** Which conversation the record sits in (the toast's deep-link target). */ threadId?: string; /** Tool steps the turn took — the transcript's "Did N things" count. */ steps: number; } /** What a thread surface publishes: its transport status and its transcript. */ export interface ThreadRunSnapshot { threadId?: string; status: "submitted" | "streaming" | "ready" | "error"; messages: UIMessage[]; /** Omitted by a surface that narrates no beats — the same shape `threadId` has, and the same meaning as an empty list. */ beats?: readonly VendoBeat[]; } /** * One thread surface reporting its turn. Called from `useVendoThread`, so the * pill narrates whichever surface is actually running — panel, page, or a * custom thread. `key` identifies the surface (a `Symbol` per hook instance), * so an idle surface can never clobber a running one. */ export declare function publishThreadRun(key: symbol, snapshot: ThreadRunSnapshot): void; /** The surface unmounted — its run can no longer be narrated. */ export declare function retireThreadRun(key: symbol): void; export declare function subscribeRunActivity(listener: () => void): () => void; export declare function runActivity(): RunActivity; /** The last clean settle nobody has looked at yet, else undefined. */ export declare function unseenRunResult(): RunResult | undefined; /** The user looked (panel opened, or the toast's View tapped). */ export declare function markRunResultsSeen(): void; /** Test/host teardown: forget every surface and any unseen result. */ export declare function resetRunActivity(): void; /** SSR + first-render snapshots (stable identities for useSyncExternalStore). */ export declare const IDLE_RUN_ACTIVITY: RunActivity;