UNPKG

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.

261 lines 13.2 kB
import { LoopEngine, type BootstrapResult, type BootstrapScope, type DeployTarget, type DomainPreset, type FrameworkDetection, type FrameworkSignals, type RunnerSession } from '@gemstack/ai-autopilot'; import type { Driver } from './driver/index.js'; import { type EcoOptions } from './system-prompt.js'; import { type BindProjectDeps, type RecordMessage } from './await-gate.js'; import { type TodoLoopResult } from './todo-loop.js'; import { type ChoicePick, type ChoiceRequest, type FrameworkEvent } from './events.js'; import type { RunMessages } from './run-messages.js'; /** * The framework's default full-fledged pass budget. Higher than ai-autopilot's * base of 3 because a from-scratch build spends its first pass or two just * bootstrapping an empty workspace before there is anything to polish (#182). */ export declare const DEFAULT_MAX_PASSES = 5; /** The deploy decision to narrate at the end (plan-only in v1: it does not ship). */ export interface DeployDecision { render: 'ssr' | 'ssg' | 'spa'; target: string; reason: string; } /** * How to actually boot and serve the generated app so the loop can gate on it * *running*, not just on an agent's review. When set, the run's checklist step * installs, (builds,) starts the app, and fetches it; a failure becomes a * blocker the loop hands back to the agent to fix. */ export interface ServeConfig { /** The command that starts the app (e.g. `npm run dev`). */ command: string; /** Install command run first (e.g. `npm install`). */ install?: string; /** Build command run after install (e.g. `npm run build`). */ build?: string; /** Port the app listens on. Default 3000. */ port?: number; /** How long to wait for it to accept connections. Default 15000ms. */ waitMs?: number; /** Path to fetch once it is up. Default `/`. */ healthPath?: string; /** * Keep the app serving after a successful run and hand back an * {@link AppPreview} the caller must {@link AppPreview.stop}. Default `false`: * the serve gate boots the app only to check it, then tears it down. The CLI * sets this so the dashboard can show a live preview link until Ctrl+C. */ keepAlive?: boolean; } /** Options for {@link runFramework}. */ export interface RunFrameworkOptions { /** What the user wants built (the one scope question's answer). */ intent: string; /** How much app: a quick prototype (no full-fledged loop) or the full thing. Default `"full"`. */ scope?: BootstrapScope; /** The wrapped coding agent. */ driver: Driver; /** Absolute workspace path the agent builds in. */ cwd: string; /** Model id to pass through to the driver. */ model?: string; /** Signals for preset detection (deps/files). Default: none, so the flagship preset wins. */ signals?: FrameworkSignals; /** * A user-authored system prompt (from `SYSTEM.md`) injected into every prompt * (#301). Load with `loadUserSystemPrompt(cwd)`. Composed after the built-in * #326 system prompt, so a repo can add its own instructions on top of the default. */ systemPrompt?: string; /** * Inject the built-in #326 system prompt into every prompt (#301). Default * `true`; pass `false` (e.g. from `the-framework.yml`) to remove it. The name * is the historical config key: #326 is the anti-lazy-pill's (#297) successor. */ antiLazyPill?: boolean; /** This run has a real browser (#824), so the system channel says so. */ browser?: boolean; /** * This is a project-less "topic" run (#1120): advertise the bind gate (#1121) in the system * channel and wire {@link bind} so an `await-bind-project` / `await-create-project` gate resolves. */ topic?: boolean; /** The bind seams (#1121) a topic run's gate resolves against. Only meaningful with {@link topic}. */ bind?: BindProjectDeps; /** Transparent mode (#625): empty the system channel entirely (raw `claude -p`); overrides antiLazyPill/eco. */ transparent?: boolean; /** Eco fine-grained control (#314): drop the enabled #326 sections to save tokens. */ eco?: EcoOptions; /** In-context directories (#439): added as one `Context:` line to the system prompt. */ context?: readonly string[]; /** * A user-picked Open Loop domain preset ({loops, prompts}) to run the build * under (#251). Its loops + prompts are materialized into a driver-backed {@link LoopEngine} * exposed as {@link RunFrameworkResult.loop}. Load it with `loadDomainPreset` / * `softwareDevelopmentPreset` (pass `modes` there to activate variants). Omit * for the framework-only run. */ preset?: DomainPreset; /** * The active modes for the run (e.g. `['autopilot']`). Narrated with the * {@link preset}, which is expected to be loaded with them already applied. * (`autopilot` no longer steers the system prompt: #556 moved the maintenance * section out, leaving the choice-gate countdown as its whole effect. See #801.) */ modes?: readonly string[]; /** * The loop event kind the review phase dispatches (#265) — this is what makes a * run a bug fix vs a feature: `bug-fix` fires the preset's bug-fix loop, the * default `major-change` fires its major-change loop. Overrides the preset's own * `defaultEvent`. A kind the preset has no loop for reviews nothing (#1372). * No-op without a preset. */ buildEvent?: string; /** Max full-fledged passes. Default {@link DEFAULT_MAX_PASSES} (5). */ maxPasses?: number; /** A deploy decision to narrate at the end. Omit to skip the deploy phase. */ deploy?: DeployDecision; /** * A real {@link DeployTarget} to *execute* the decided plan (e.g. * `cloudflareTarget` / `dokployTarget`). Requires {@link deploy}. Omit to only * narrate a plan-only decision. */ deployTarget?: DeployTarget; /** * Boot-and-serve verification for the full-fledged loop: when set, the * checklist gates on the app actually running, not just an agent review. */ serve?: ServeConfig; /** * Where the {@link serve} verification runs (#229). `"local"` (default) boots the * app on the host, adopting the agent's cwd in place. `"docker"` sandboxes it: a * throwaway container is booted, the source is copied in fresh before each check * (the build still runs on the host in this slice), deps install inside the * container, and the app serves on a mapped port — so agent-authored code never * installs or runs on the host. Requires a reachable Docker daemon; no-op without * {@link serve}. */ sandbox?: 'local' | 'docker'; /** * A pre-provisioned {@link RunnerSession} to run the serve check in, bypassing * {@link sandbox} provisioning. Advanced / testing seam — the caller owns its * lifecycle is handed to the run (it is disposed with the run). Omit to let * {@link sandbox} provision one. */ runner?: RunnerSession; /** * A link to the live agent session, shown on the dashboard. Either a literal * URL, or a template with `{sessionId}` (see {@link SESSION_ID_PLACEHOLDER}) * that resolves once the wrapped agent reports its real id via `session-update`. */ sessionLink?: string; /** Interrupt the run between phases. */ signal?: AbortSignal; /** * Pause the run on an interactive choice and await a pick (#304). Called when a * build turn stops to ask (#337/#358): the run emits a `choice` event, calls * this, and resumes on the returned option. Omit for a headless run: the gate * then auto-accepts the recommended option without pausing. The CLI wires this * to the dashboard's Accept button + autopilot countdown. */ requestChoice?: (req: ChoiceRequest) => Promise<ChoicePick>; /** * Stop the run once cumulative agent cost reaches this many USD (budget cap, * #322). Checked after each turn that reports usage: the turn that crosses the * cap finishes, then the run stops itself (a clean stop, not a failure). Omit * for no cap. This gates on what *this run* spent, which is a separate question * from where the account's quota stands (readable via #517 / #521, and gated on * by #519's consumption limits). */ budgetUsd?: number; /** * Consult the consumption limits between turns (#529): return the limit that * has been reached to pause the run, or `null` to carry on. * * Must answer from a cached reading — a live quota read spawns the whole agent * CLI (~5s). Compose one from a `QuotaPoller` and `consumptionStatus`. Omit to * leave the run ungated, which is also what a gate that throws resolves to: * an unreadable quota must never stop the user's work (Rom's call on #519). */ consumptionGate?: () => string | null; /** * Run the backlog loop (#323) after the build settles: consume the agent's own * `TODO_AGENTS.md` one entry per turn until empty, gating * before each entry when {@link requestChoice} is wired. Default: on for real * drivers, off for the fake one (its scripted demo writes no backlog and must * stay deterministic). Set explicitly to force either way. */ todoLoop?: boolean; /** Per-run cap on backlog entries worked (#323). Default 25. */ todoMaxItems?: number; /** * Continue a stopped build run's conversation (#1467): the captured agent session id to * `--resume`. When set, the build turn sends {@link RunFrameworkOptions.intent} verbatim as a * continuation message instead of rendering the build/extend prompt — the resumed transcript * already carries the scope→build framing, which is exactly why #782 refused to bolt * `--resume-session` onto a fresh build run. Everything around the turn still runs: the * bootstrap narration with its synthesize framing, the review checklist where configured, the * backlog loop and live chat — the flow resumes, not just the conversation. */ resumeSessionId?: string; /** * Live chat (#714): once the build settles, take the user's own messages, each * resuming the build session for full context. The session then ends itself when * the queue is idle (#1390) unless {@link stayOpenChat} parks it. Wired only for * an interactive run — a headless run leaves it unset and ends when the build is * done, exactly as before. */ messages?: RunMessages; /** * Keep the chat parked for the next message instead of ending on an idle queue (#1390). * Only for a run whose own terminal dashboard is the single surface — it has no daemon * to resume the session through, so ending would leave its composer a dead end. */ stayOpenChat?: boolean; /** Record each chat turn to the committed conversation (#908). Best-effort; unset = not recorded. */ recordMessage?: RecordMessage; /** Observe the unified event stream. */ onEvent?: (event: FrameworkEvent) => void; } /** * A running instance of the generated app, handed back so the caller can show a * live preview link and keep it up until the user is done (then {@link stop}). */ export interface AppPreview { /** The localhost URL the app is served at. */ url: string; /** The command that started it (e.g. `npm run dev`). */ command: string; /** Stop the app and free its runner. Idempotent. */ stop(): Promise<void>; } /** What a run returns. */ export interface RunFrameworkResult { result: BootstrapResult; detection: FrameworkDetection; events: FrameworkEvent[]; /** * The generated app, left running when a {@link ServeConfig} was supplied and * the run finished. The caller owns its lifecycle: show {@link AppPreview.url}, * then call {@link AppPreview.stop} (e.g. on Ctrl+C). Absent when no serve * config was set or the app could not be booted. */ preview?: AppPreview; /** * The domain preset's review policy, materialized against this run's driver: * its loops plus its prompts as driver-backed passes. Present only when a * {@link RunFrameworkOptions.preset} was supplied. It also drives the run's * review phase (#252): each checklist pass dispatches a `major-change` event * through it. */ loop?: LoopEngine; /** How the backlog loop (#323) ended, when it ran. */ todo?: TodoLoopResult; } /** * Run the whole turnkey flow: detect the framework preset, frame the wrapped * agent with its framework skill (page builder + docs), then drive ai-autopilot's `Bootstrap` * (scope → build → full-fledged loop → deploy) entirely *through* * the driver (option A). Every phase, plus the agent's own progress, streams as * a {@link FrameworkEvent}. Reversible: swap in a real deploy target, or a * different `Driver`, without touching this wiring. */ export declare function runFramework(opts: RunFrameworkOptions): Promise<RunFrameworkResult>; //# sourceMappingURL=run.d.ts.map