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.

182 lines 10.4 kB
import type { DriverSession } from './driver/index.js'; import type { ChoicePick, ChoiceRequest, FrameworkEvent } from './events.js'; export { FLAT_TODO_FILE, TICKETS_DIR, ticketFromQueueEntry, todoPriorityForTicket } from './tickets.js'; /** * The backlog loop (#323): once the main work settles, consume the agent's own * TODO backlog one entry per turn until it is empty. The agent writes the * backlog itself (a very large scope, the Maintenance follow-ups, or the * [Research] preset's deep-dive picks all append entries); the framework only * drives: read the file, gate ("start the next item?") when someone can answer, * prompt the agent to complete exactly one entry and check it off, repeat. * Termination is Rom's call on the issue: stop when the backlog is empty. The * dashboard's autopilot auto-accepts the per-item gate, so `[x] autopilot` * consumes the whole backlog unattended; autopilot off pauses before each entry. */ /** A located backlog: its filename and the entries still open. */ export interface TodoBacklog { /** The backlog filename (workspace-root relative, e.g. `TODO_AGENTS.md`). */ name: string; /** The open (unchecked) entries, in file order. */ entries: string[]; } /** * The open entries of a backlog document: markdown list items (`-`, `*`, or * `1.`), where a task checkbox counts only while unchecked (`- [ ]`); a checked * `- [x]` entry is done. Headings, prose, and blank lines are not entries. */ export declare function parseTodoEntries(md: string): string[]; /** * Append an open entry to the queue on the data branch (#1582), creating {@link FLAT_TODO_FILE} * when the branch has none. Resolves with the file written, or `undefined` if it couldn't be. * * This is how a paused agent leaves word to pick itself up again (#529): the * backlog is already the thing a later agent drains, so a resume note needs no * machinery of its own. Never throws — it is called while an agent is already * unwinding, and must not mask the reason it stopped. */ export declare function appendTodoEntry(cwd: string, entry: string): Promise<string | undefined>; /** * Append an open entry to the queue with a priority, creating {@link FLAT_TODO_FILE} when there * is none. * * The difference from {@link appendTodoEntry} is the priority placement (#1164): a dashboard * pick (#697) lands in its `## Priority N` section rather than at the end of the file. */ export declare function appendFlatTodoEntry(cwd: string, entry: string, priority?: number): Promise<string | undefined>; /** * Place an open entry in the backlog's priority section (`prompts/todo_format.md`), creating the * section when the file has none, and return the new document. * * Pure, because the placement is the whole point and it is much easier to pin here than through * the filesystem. The old behaviour was a plain append, which put a just-queued ticket at the * *end* of the file — and since the drain preset works "the FIRST open entry" and * {@link parseTodoEntries} reads in file order, queueing something meant it would be worked last, * behind everything already there (#1164). * * Placement rules, in the order they are tried: * - a section for this priority already exists: the entry joins the end of it, so a queue keeps * its arrival order within a priority * - otherwise the section is created before the first *lower*-priority section, since the format * sorts high to low * - with no priority sections at all, it goes above the first heading of any kind: the file's * own sections are then unranked, and burying a deliberate pick under them is the bug */ export declare function insertTodoEntry(md: string, entry: string, priority: number): string; /** * Retire one open entry in a queue document: check its box (or give a bare bullet one, checked). * The same "open" grammar {@link parseTodoEntries} reads (#1164/#1297), the retire half of what * `queue-promote.ts` did before #1582 made the daemon the queue's one local writer. */ export declare function checkOffEntry(md: string, entry: string): string; /** * The backlog and its open entries, read off the data branch (#1582) — the queue's one location, * readable from the project checkout and from any agent worktree alike. Returns `undefined` when * no queue exists or it has no open entry. Session-scoped `TODO_<slug>.agent.md` files are * retired (#1369). * * `fresh: true` re-fetches the branch first: for a long-lived agent process about to act on the * queue, where the local ref may trail what other writers pushed meanwhile. */ export declare function findTodoBacklog(cwd: string, opts?: { fresh?: boolean; }): Promise<TodoBacklog | undefined>; /** * Does this session's own backlog still have open work (#1363)? * * Reads only `TODO_<SESSION_NAME>.agent.md` — the file the [Research] preset (and a very-large * scope) has the agent keep for its own session. Never the global `TODO_AGENTS.md`: the queue is * decoupled from sessions (#1390), and withholding a merge on it would mean auto-merge never * fires while the project has any backlog at all. `false` on a missing or unreadable file, and on * a session name that could not name a file — no pendingness known is not pendingness. * * TEMPORARY SAFETY BELT, built to be deleted (#1390): the agent's setReadyForMerge() is the * authorization, and this only catches the agent declaring done while its own session file says * otherwise. When the agent's word is deemed enough, delete this function and its single call * site in `maybeAutoHandoff`. */ export declare function agentTodoPending(cwd: string, sessionName: string | undefined): Promise<boolean>; /** * The ticket the next drain agent will pick up, or `undefined` when there is none (#1117). * * "Next" is the first open entry of the flat backlog, because that is what the [Drain queue] * preset says to work ("the FIRST open entry only") and {@link parseTodoEntries} returns entries * in file order. Read from the project checkout, the same copy the sweep already consults when it * decides whether there is anything to drain, so the entry this names is the entry that decision * was made on. * * A best guess by construction: the agent reads its own worktree a moment later, and an entry * checked off in between would move it on. Being wrong here costs a mislabelled lane on the * Overview and nothing else — no run is started or steered by this. */ export declare function nextQueuedTicket(cwd: string): Promise<string | undefined>; /** * The ticket an agent started by hand is about to implement, when that agent is a drain (#1117). * * The daemon already does this for the sweep's own drain, off the `drains` flag on the job. An agent * fired from the dashboard reaches the same start with none of that context, so a hand-fired drain * showed up working on nothing: the agent implemented the ticket, and the lane it belonged in stayed * empty. Same read as the sweep's, so both agree on which entry is next. * * Undefined for anything that is not a drain, and for a drain over an empty queue. The `read` seam * is for tests; production always takes the default. */ export declare function ticketForPrompt(prompt: string, cwd: string, read?: (cwd: string) => Promise<string | undefined>): Promise<string | undefined>; /** Why the loop ended. */ export type TodoLoopReason = /** The backlog is empty (or was never written) — the success case. */ 'empty' /** The user picked "stop" at a per-item gate. */ | 'stopped' /** Two check-offs in a row could not be written to the data branch. */ | 'stalled' /** The item cap was reached with entries still open. */ | 'max-items'; /** What {@link runTodoLoop} resolves with. */ export interface TodoLoopResult { /** Backlog entries worked (turns taken), regardless of outcome. */ completed: number; /** Why the loop ended. */ reason: TodoLoopReason; /** The backlog filename, when one was found. */ file?: string; /** * An answer marked `stop` (#358) inside an item's turn — a plan the user declined — ends the * whole session, not just the loop. Distinct from the benign `reason: 'stopped'` per-item gate, * which only stops draining the backlog. The caller aborts the session on this. */ sessionStopped?: boolean; } /** Options for {@link runTodoLoop}. */ export interface TodoLoopOptions { /** The live driver session the agent already owns. */ session: DriverSession; /** The workspace the backlog lives in. */ cwd: string; /** Emit the loop's events onto the agent stream. */ emit: (event: FrameworkEvent) => void; /** * The interactive gate handler (#304). When wired, the loop pauses before each * entry ("start the next item?") — the dashboard's autopilot auto-accepts, so * autopilot off means a human gate per item (#323). Headless runs don't pause. */ requestChoice?: ((req: ChoiceRequest) => Promise<ChoicePick>) | undefined; /** The agent signal; aborting (Stop button / budget cap #322) ends the loop. */ signal?: AbortSignal | undefined; /** Hard cap on entries worked in one agent. Default {@link DEFAULT_MAX_TODO_ITEMS}. */ maxItems?: number | undefined; } /** The default per-agent cap on backlog entries — a backstop beside the budget cap (#322). */ export declare const DEFAULT_MAX_TODO_ITEMS = 25; /** * Drive the backlog to empty: read the next open entry (fresh off the data branch), gate, prompt * the agent to complete exactly that entry, check it off on the data branch, and repeat. The * check-off is the framework's, not the agent's (#1582): the queue lives on a branch the agent's * checkout does not hold, and the one writer model keeps every edit going through the same * funnel. Caps make it safe to leave unattended (#322's concern): the agent's budget/abort * signal ends any turn, a hard item cap bounds the agent, and two check-offs in a row failing to * land stop the loop instead of re-working the same entry. A backlog turn is a turn like any * other: await gates (`showChoices()` / `showMultiSelect()`) and the signals (`showMarkdown()`, * `setSessionName()`, `setReadyForMerge()`) are honored here too. */ export declare function runTodoLoop(opts: TodoLoopOptions): Promise<TodoLoopResult>; //# sourceMappingURL=todo-loop.d.ts.map