framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
202 lines • 10.7 kB
TypeScript
import { type ParsedAwaitGate } from './turn-gate.js';
import { type ChoicePick, type ChoiceRequest, type FrameworkEvent } from './events.js';
import type { DriverSession, DriverTurn } from './driver/index.js';
import type { AgentMessages } from './agent-messages.js';
/** What a resolved gate yields. */
export interface GateAnswer {
/** The label(s) picked, as the continuation prompt words it to the agent. */
answer: string;
/** The user picked an option the agent marked `stop` (#358): hand back rather than continue. */
stop: boolean;
}
/**
* Resolve one parsed await gate (#337) to the user's answer text, ready to seed the continuation
* prompt: emits the `choice`, parks for the pick (or the headless/abort fallback), and maps the
* picked id(s) back to label(s). Round 0 keeps a stable gate id; later rounds get a unique one so
* a dashboard never confuses a re-ask with the answer it just resolved. Shared by the build's
* `agentAwaitGate`, the direct prompt path, and the backlog loop (#323).
*
* One path, where there were four. A gate that takes several picks answers with the labels it got
* (or `(none)`); every other gate answers with the one label picked. What used to distinguish an
* approval or a browser hand-off from an ordinary question was the options the agent wrote, and
* that is all it is again.
*
* The answer also carries whether it *ends* the session, which is read off the option the user
* picked rather than off the gate: on a multi-select one stopping pick among several is still a
* stop, because an answer that says "stop" is not softened by the answers next to it.
*/
export declare function resolveAwaitGate(gate: ParsedAwaitGate, round: number, deps: {
requestChoice?: ((req: ChoiceRequest) => Promise<ChoicePick>) | undefined;
emit: (event: FrameworkEvent) => void;
signal?: AbortSignal | undefined;
}): Promise<GateAnswer>;
/** What {@link runAwaitRounds} hands back. */
export interface AwaitRoundsResult {
/** The last turn's text. */
text: string;
/** The agent was still asking when the round cap ran out. */
exhausted: boolean;
/** The user answered a gate with a `stop` option (#358), so the session ends here. */
stopped: boolean;
}
/** Inputs to {@link runAwaitRounds}. */
export interface AwaitRoundsOptions {
session: DriverSession;
/** The prompt that opens the exchange. */
prompt: string;
/** Emit the signals each turn carries (#563). */
emitTurnSignals: (text: string) => void;
requestChoice?: ((req: ChoiceRequest) => Promise<ChoicePick>) | undefined;
emit: (event: FrameworkEvent) => void;
signal?: AbortSignal | undefined;
/**
* Live chat (#714): once the agent stops asking, take the user's own messages,
* each resuming the same session. Unset for a headless agent, which then
* ends when the agent stops asking — byte-identical to before this existed.
*/
messages?: AgentMessages | undefined;
/**
* Keep the chat parked for the next message instead of ending on an idle queue (#1390).
* Only for an agent whose own terminal dashboard is the single surface — everything else
* ends itself and is reopened via `--resume`. Default off.
*/
stayOpenChat?: boolean | undefined;
/**
* Resume a prior session on the OPENING prompt (#720): when the driver session was
* seeded with a finished agent's id, this makes the first message `--resume` that
* conversation (full prior context) instead of starting fresh. Default off.
*/
resume?: boolean | undefined;
}
/** The shared deps of a turn that may hit an await gate or a chat message. */
export interface AwaitTurnDeps {
requestChoice?: ((req: ChoiceRequest) => Promise<ChoicePick>) | undefined;
emit: (event: FrameworkEvent) => void;
emitTurnSignals: (text: string) => void;
signal?: AbortSignal | undefined;
}
/**
* Resolve the await gates (#337) a turn ended on: pick the answer, continue with it, repeat until
* the agent stops asking, an answer says to stop (#358), or the {@link MAX_AWAIT_ROUNDS} cap trips.
* Returns the settled turn plus which of those ended it.
*
* Every path that runs gates shares this loop: the opening prompt, each chat message, and
* the build's `agentAwaitGate`. They differ only in how a turn is continued — a raw
* `session.prompt`, or a pass that carries its own continuation — which is what
* `continueWith` is. Keeping one loop is what stopped the per-turn signal emission from
* having to be added to each copy by hand (#563).
*/
export declare function drainGates<T extends {
text: string;
}>(turn: T, deps: AwaitTurnDeps, continueWith: (question: string, answer: string) => Promise<T>): Promise<{
turn: T;
exhausted: boolean;
stopped: boolean;
}>;
/**
* The live-chat loop (#714): deliver each of the user's messages by resuming the same
* session (full conversational context), then honor any await gate it produced. Shared by
* the direct prompt path and the build path, which both reach it once their work has settled.
*
* How it ends is the session-lifecycle rule of #1390. By default the loop only *drains*: a
* message that already arrived is processed, and once the queue is idle the session ends
* itself rather than parking on the user — merge fires at that natural end (armed + gated),
* and a follow-up reopens the conversation via `--resume` (#762), like Claude Code web.
* `stayOpen` keeps the old park-for-the-next-message lifecycle, for an agent whose own terminal
* dashboard is the only surface — it has no daemon to resume through, so ending would leave
* its composer a dead end; that agent still ends on Stop / budget cap (next -> undefined).
*
* Reports the settled `exhausted` of the *last* chat turn (#742): entering chat means the
* opening prompt's await-round cap is no longer the agent's end reason, and a phase that ends
* on Stop / close is not exhausted at all.
*/
export declare function runChatPhase(session: DriverSession, messages: AgentMessages, seed: DriverTurn, deps: AwaitTurnDeps, stayOpen?: boolean): Promise<{
turn: DriverTurn;
exhausted: boolean;
stopped: boolean;
}>;
/**
* Prompt the agent and honor its await gates (#337) until it stops asking: resolve each gate to
* the user's answer, re-prompt with it, and repeat up to {@link MAX_AWAIT_ROUNDS}. When a live-chat
* {@link AwaitRoundsOptions.messages} source is wired, the agent then stays open for the user's own
* messages (#714) rather than finishing.
*
* Every turn here is a turn like any other, so each one's signals are emitted. That is the
* point of sharing this: the direct prompt path and the backlog loop each had their own copy
* of these rounds, and the emission had to be added to each by hand (#563).
*/
export declare function runAwaitRounds(opts: AwaitRoundsOptions): Promise<AwaitRoundsResult>;
/** One option of a {@link requestChoices} single-select gate (#304). */
export interface ChoicesOption {
/** Stable id returned when this option is picked. */
id: string;
/** The label shown next to the option. */
label: string;
/** Optional one-line detail under the label. */
detail?: string;
}
/** Inputs to {@link requestChoices}. */
export interface ChoicesDeps {
/** Stable id for the gate; the pick is posted back against it. */
id: string;
/** The prompt shown above the options. */
title: string;
/** The options to choose between (pick one). */
options: readonly ChoicesOption[];
/** The option id pre-selected (autopilot auto-accepts it) and used as the headless/abort fallback. Default = the first option. */
recommended?: string;
/** The markdown file under approval; the dashboard's doc sidebar renders it. */
file?: string;
/** The interactive handler (the CLI wires it to the dashboard); omit for a headless agent. */
requestChoice?: (req: ChoiceRequest) => Promise<ChoicePick>;
/** Emit the `choice` / `choice-resolved` events onto the agent stream. */
emit: (event: FrameworkEvent) => void;
/** The agent signal; a gate parked for a pick unblocks (to the recommended option) if the agent aborts. */
signal?: AbortSignal;
}
/**
* The single-select gate (#304): show the options with the recommended one
* pre-selected, pause, and resolve to the *one* option id the user picked. The twin
* of {@link requestMultiSelect} for "pick one" — the agent-facing `showChoices()`
* from the built-in system (#326) prompt and the [Research] preset (#331) both build on it.
* A headless agent (no `requestChoice`), or one aborted mid-await, falls back to the
* recommended option without hanging, so a programmatic agent stays deterministic.
*/
export declare function requestChoices(deps: ChoicesDeps): Promise<string>;
/** One option of a {@link requestMultiSelect} checklist (#332). */
export interface MultiSelectOption {
/** Stable id returned when this option is checked. */
id: string;
/** The label shown next to the checkbox. */
label: string;
/** Optional one-line detail under the label. */
detail?: string;
/** Whether this option starts checked (e.g. a low-rated problem in the [Research] preset #331). */
default?: boolean;
}
/** Inputs to {@link requestMultiSelect}. */
export interface MultiSelectDeps {
/** Stable id for the gate; the pick is posted back against it. */
id: string;
/** The prompt shown above the checklist. */
title: string;
/** The options to choose from (checkboxes). */
options: readonly MultiSelectOption[];
/** The interactive handler (the CLI wires it to the dashboard); omit for a headless agent. */
requestChoice?: (req: ChoiceRequest) => Promise<ChoicePick>;
/** Emit the `choice` / `choice-resolved` events onto the agent stream. */
emit: (event: FrameworkEvent) => void;
/** The agent signal; a gate parked for a pick unblocks (to the defaults) if the agent aborts. */
signal?: AbortSignal;
}
/**
* The multi-select gate (#332): show a checklist with the default-checked options
* pre-selected, pause, and resolve to the *subset* of option ids the user kept
* checked. Built on the same `choice` gate + POST-back resolver as the single-select
* plan-approval gate (#304), just in checklist mode. A headless agent (no
* `requestChoice`) auto-accepts the default set without pausing, so a programmatic
* run stays deterministic. This is the primitive the [Research] preset (#331) uses
* to let the user pick which problems to deep-dive.
*/
export declare function requestMultiSelect(deps: MultiSelectDeps): Promise<string[]>;
//# sourceMappingURL=await-gate.d.ts.map