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.

170 lines 9.71 kB
import { type GitRunner } from '../project.js'; import type { Driver, DriverPromptOptions, DriverSession, DriverStartOptions, DriverTurn } from './types.js'; /** * A {@link Driver} that hands the task to **Claude Code on the web** (#610): it starts a * real cloud session on claude.ai and returns its id and URL. * * The mechanism is the CLI's own `--cloud` flag, so the account, the auth and the * quota are the user's, exactly as with the local driver (#495). Nothing here drives * the claude.ai UI: no browser, no extension, no scraping — the two earlier candidates * for this issue, both of which the Usage Policy rules out. * * **Why a pty.** `--cloud` refuses to run when stdout is a pipe, because a non-interactive * invocation would silently run locally instead. That check is about the *terminal*, not * about a human, so running the CLI under a pty satisfies it. `script` supplies the pty * (present on macOS and Linux) and the prompt travels in the environment, never inside a * shell string — the command string is a fixed literal, so no prompt text can reach the * shell as syntax. * * **What this target is, and is not.** It is a hand-off: the session runs on Anthropic's * infrastructure, does its own git worktree and opens its own PR, at 0% local CPU — the * whole point of #610. What it is not is a streamed peer like the local, device and * Actions targets, and that is not a shortcut in this implementation but a property of * the surface: a cloud session exposes **no read-back API** of any kind — no status, no * transcript, no output endpoint, only the session URL. So the turn resolves once the * session is created, and following the work happens on claude.ai, or by pulling it back * with `claude --teleport <id>`. * * For the same reason there is no `readCode`: the workspace lives in a cloud VM this * machine never sees. */ export declare class CloudDriver implements Driver { private readonly opts; readonly id = "claude-web"; constructor(opts?: CloudDriverOptions); start(opts: DriverStartOptions): Promise<DriverSession>; } /** Options for {@link CloudDriver}. */ export interface CloudDriverOptions { /** Claude Code binary. Default `"claude"`. */ bin?: string; /** Give up on session creation after this long, in ms. Default 120000. */ timeoutMs?: number; /** Run one pty-hosted invocation. Injected in tests; defaults to a real `script` pty. */ runPty?: RunPty; /** Runs git for the pre-hand-off push (#1320). Injected in tests; defaults to real git. */ git?: GitRunner; /** The CLI's config file the pre-hand-off trust write (#1493) touches. Injected in tests; defaults to `~/.claude.json`. */ claudeConfig?: string; /** * Unique tag mixed into the session id. Default a random token. Injected in tests for a * stable id, and load-bearing in production for the same reason it is in the Actions * driver: a fresh `framework run` process restarts the counter, so without it every * run's first session would carry the same id. */ agentTag?: () => string; } /** One pty-hosted invocation: stream its output, resolve when it ends. */ export type RunPty = (opts: AgentPtyOptions) => Promise<void>; /** What {@link RunPty} needs to run one invocation. */ export interface AgentPtyOptions { /** Claude Code binary to run under the pty. */ bin: string; /** The prompt, handed over through the environment rather than the command line. */ prompt: string; /** Model id to pass through, when one was chosen. */ model?: string | undefined; /** * The ref the session clones at (#1320), pushed to origin just before this invocation. * Absent when that push failed — the CLI then pins its own default, which is the current * local branch and fails in-session when that branch is not on origin. */ ref?: string | undefined; /** Workspace the CLI runs in — the repo whose remote the cloud session clones. */ cwd: string; /** Called with each chunk of terminal output. */ onData: (chunk: string) => void; /** Stop the invocation: the caller has what it needs, or the agent was aborted. */ signal: AbortSignal; } /** * The project root an agent worktree belongs to, which is where trust has to be granted. * * The CLI records trust per directory and everything under a trusted directory inherits it * (verified live: a fresh worktree of a trusted root boots straight to the REPL, a fresh * worktree of an untrusted root always shows the dialog). An agent's cwd is an ephemeral * worktree — gone before the user could act on advice that names it — so the only advice * that works is: trust the root once, and every agent worktree under it is covered. */ export declare function trustRootOf(cwd: string): string; /** * The rule between the task and the injected instructions in a hand-off prompt (#1497). * Exported so a test can pin the exact seam the claude.ai reader sees. */ export declare const CLOUD_PROMPT_SEPARATOR = "==============================="; /** * Assemble the one prompt a cloud session receives (#1497). Unlike every streamed driver — * where the system channel is invisible plumbing — this whole string is what a *human* reads * when they open the claude.ai session. So the task comes first (it is what the user is * looking for), and each injected block follows behind a hard `===` rule with a one-line * label, because the blocks' own markdown headers run into each other and read as one * confusing document without it. */ export declare function cloudHandOffPrompt(task: string, ...injected: (string | undefined)[]): string; /** * One hand-off to Claude Code on the web — **exactly one, for the life of the session.** * * An agent is not a single prompt. The loop prompts again for every pass (plan, build, review, * the TODO backlog), so a driver that started a cloud session per prompt turned one agent into * six of them on the account. That is not a caveat, it is the wrong shape: the same task * handed to six independent cloud VMs is six agents racing on one repo. * * So the first prompt hands off, and every later one reports the hand-off that already * happened without spending another session. There is no continuation to offer either way — * the CLI can start a cloud session and pull one back, but it cannot send a second message * to one, so the honest answer to "keep going" is "this agent is already over there". */ export declare class CloudSession implements DriverSession { private readonly config; private readonly startOpts; readonly id: string; readonly cwd: string; private readonly emit; private readonly framing; private readonly controllers; private disposed; /** The cloud session this agent was handed to, once it exists. Set at most once. */ private handedOff; /** The hand-off anchor commit (#1601), once it is on origin. Set at most once, with the hand-off. */ private anchorSha; constructor(config: CloudDriverOptions, startOpts: DriverStartOptions); prompt(text: string, opts?: DriverPromptOptions): Promise<DriverTurn>; /** * Report where this agent went. The `first` hand-off emits the `cloud <url>` action the agent * view links through to — mirroring the Actions driver's `run <url>` — and a later pass * says the work is already there, so a loop that keeps prompting cannot read the same turn * as fresh progress and cannot spend a second session. */ private report; dispose(): Promise<void>; } /** * The shell command `script` hosts. A **fixed literal**: the prompt and the model arrive * as environment variables, so nothing the user typed is ever parsed as shell syntax. * `${FW_CLOUD_MODEL:+...}` adds the model flag only when one was chosen. * * **The prompt has to come directly after `--cloud`.** The description is that flag's own * value rather than a loose positional argument, so anything in between claims the slot and * the CLI stops with "--cloud requires a description". That is why this failed on an account * with a model preference and worked without one: the model flag was sitting in the slot. * Exported so a test can pin the order, which is load-bearing and not otherwise observable. * * `--ref` names the origin ref the session clones at (#1320) — undocumented in the CLI's * help but real, and the only revision spelling that works: the CLI's default pin is the * current local branch, which the cloud side cannot resolve when it is not on origin, and a * slash-carrying name (every `the-framework/...` branch) never resolves at all * (anthropics/claude-code#87235). The ref is pushed just before this runs; see the pre-push * in {@link CloudSession.prompt}. */ export declare const CLOUD_COMMAND = "exec \"$FW_CLOUD_BIN\" --cloud \"$FW_CLOUD_PROMPT\" ${FW_CLOUD_MODEL:+--model \"$FW_CLOUD_MODEL\"} ${FW_CLOUD_REF:+--ref \"$FW_CLOUD_REF\"}"; /** * Extra environment for the CLI invocation (#1320): with statsig disabled its gates read * false, which turns off the server-side `tengu_ccr_bundle_seed_enabled` experiment — the * flag that converts a failed GitHub-App preflight into a silent local-bundle upload with no * remote and no push access (anthropics/claude-code#81776). With the gate off, the same * failed preflight falls through to a repo-bound session that clones from GitHub and can * push, which is the entire point of handing work to the cloud. Drop this once the upstream * preflight accepts connected-account access. */ export declare const CLOUD_ENV: Readonly<Record<string, string>>; //# sourceMappingURL=cloud.d.ts.map