/** * "@preset:" chain expansion. * * A queued prompt whose content starts with `@preset:` references a * backend preset. When such an item is dequeued, it expands into: * 1. the preset's QUEUE follow-up messages, enqueued (in order, trimmed) * BEFORE the turn starts, and * 2. the preset's main prompt with its INJECT follow-up messages (and any * remainder text from the reference) appended as paragraphs, which runs * as the turn — the same composition the frontend apply-preset flow * produces for the composer text. * * Because follow-ups are enqueued verbatim and each dequeued item is itself * checked for a reference, a follow-up that is `@preset:` expands at * its own dequeue — chained presets therefore behave like programs. * * Safety valves: * - `PRESET_CHAIN_BUDGET` caps expansions between non-reference prompts so * a preset that (indirectly) references itself terminates instead of * looping forever. Budget resets whenever a non-reference item is seen. * - Every failure mode (resolver down, unknown preset, blank prompt) * falls back to running the raw reference text — a queued item is never * silently dropped. * * Everything here is pure/injectable so it is unit-testable without a * backend; the only I/O helper is `resolvePresetByName`. */ /** Expansions allowed before a raw-run fallback (reset on non-references). */ export declare const PRESET_CHAIN_BUDGET = 25; /** Maximum follow-up messages enqueued per expansion. Mirrors the backend * resolver cap (`MAX_FOLLOW_UP_MESSAGES`). */ export declare const MAX_PRESET_FOLLOW_UPS = 20; /** A preset row as returned by `GET /v1/presets/resolve`. */ export interface ResolvedPreset { id: string; name: string; prompt: string; followUpMessages: unknown; scope?: "TEAM" | "PROJECT" | null; projectId?: string | null; intervalMinutes?: number | null; } /** Why an expansion did not happen. */ export type PresetSkipReason = "not-reference" | "budget" | "unresolved"; /** Result of {@link parsePresetReference}. */ export interface PresetReference { name: string; remainder: string; } /** * Parse a queued prompt into a preset reference, or `null` when the content * is not a reference. `@preset:` without a name does NOT match. */ export declare function parsePresetReference(content: string): PresetReference | null; /** * Resolve a preset by name through the backend machine-JWT REST route. * Never throws: any failure (network, timeout, non-2xx, malformed body) * resolves to `null` so callers fall back to the raw reference text. */ export declare function resolvePresetByName(backendUrl: string, machineJwt: string, teamId: string | undefined, name: string): Promise; /** * A follow-up message extracted from the stored `followUpMessages` JSON, * with its delivery mode preserved. */ export interface PresetFollowUp { content: string; mode: "INJECT" | "QUEUE"; } /** * Extract follow-up messages from the stored `followUpMessages` JSON, * preserving each entry's mode. Canonical entries are `{ content, mode }` * objects; plain strings are tolerated for robustness and default to INJECT * (matching the backend resolver and the frontend apply-preset flow, which * only special-case QUEUE). Empty/blank entries are skipped, output is * capped at {@link MAX_PRESET_FOLLOW_UPS}, and non-arrays yield `[]`. */ export declare function extractFollowUpContents(raw: unknown): PresetFollowUp[]; /** * Compose the turn prompt for an expansion: the preset prompt with the * reference remainder appended as a separate paragraph (when non-empty). */ export declare function composeExpandedPrompt(presetPrompt: string, remainder: string): string; /** * Compose the full running prompt for a preset expansion: the preset prompt, * then its INJECT follow-up contents, then the reference remainder — all * joined as paragraphs (identical shape to the frontend apply-preset flow's * composer text). Blank parts are dropped. */ export declare function composePresetRunPrompt(presetPrompt: string, injectContents: readonly string[], remainder: string): string; /** Injectable collaborators for {@link expandIfPresetReference}. */ export interface PresetChainDeps { parse: (content: string) => PresetReference | null; resolve: (name: string) => Promise; enqueue: (content: string) => void; run: (content: string) => void; onSkip: (reason: PresetSkipReason) => void; } export interface ExpandPresetReferenceInput extends PresetChainDeps { /** The dequeued prompt content. */ content: string; /** Expansions still allowed; defaults to {@link PRESET_CHAIN_BUDGET}. */ chainBudget?: number; } export interface PresetExpansionOutcome { /** True when a preset resolved and its follow-ups were enqueued. */ expanded: boolean; /** True when `run` was invoked (all paths except `not-reference`). */ ran: boolean; /** Content passed to `run` (null when nothing ran). */ ranContent: string | null; /** QUEUE follow-up contents enqueued, in order (INJECT follow-ups are * folded into `ranContent` instead). */ enqueuedFollowUps: string[]; /** Why expansion did not happen (null on success). */ skipReason: PresetSkipReason | null; /** Budget left after this call (unchanged on skip paths). */ budgetRemaining: number; /** True when the caller should reset its stored budget to the default. */ budgetReset: boolean; } /** * Orchestrate one dequeued item: * - not a reference → skip signal + budget reset; nothing runs * (the caller keeps its plain dequeue path). * - reference, budget spent → run the raw content, skip "budget". * - reference, resolve fails → run the raw content, skip "unresolved". * - otherwise → enqueue each QUEUE follow-up (capped, in * order), THEN run the composed prompt * (`preset prompt + INJECT follow-ups + * remainder`, paragraph-separated); spend one * unit of budget. A preset whose follow-ups * are all INJECT enqueues nothing. * * `resolve` never throws (see {@link resolvePresetByName}); errors thrown by * the injected `enqueue`/`run` deps propagate to the caller. */ export declare function expandIfPresetReference(input: ExpandPresetReferenceInput): Promise; //# sourceMappingURL=preset-chain.d.ts.map