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.
108 lines • 5.61 kB
TypeScript
import type { IncomingMessage, ServerResponse } from 'node:http';
/**
* The browser bridge (#1237): the one endpoint an extension running in the user's own Claude
* session posts to, so a question a cloud agent is parked on becomes visible in the dashboard.
*
* **Why this route carries its own token, unlike every other one.** The #1051 guard in
* `startDashboard` only exists on a non-loopback bind, and what protects a loopback daemon is
* the same-origin check on `/_rpc`: a page on another origin is refused outright. This
* route is the first that is *meant* to be reached from another origin, so neither of those
* protects it, and it demands `Authorization: Bearer <daemonToken>` unconditionally instead.
*
* **No CORS headers, on purpose.** An extension's service worker holding `host_permissions`
* fetches without a preflight, so the bridge does not need `Access-Control-Allow-Origin` and
* must not have it: a wildcard here would let any page the user visits post to their daemon.
* The cost is that the extension has to post from its background worker rather than from the
* content script, which is a line in the extension and a much better trade.
*
* **What it accepts is deliberately tiny.** One shape, fully validated, with no path, command,
* prompt or free text anywhere in it. The worst a stolen token buys is a bogus question card in
* someone's dashboard, which is the point: this is attached to a daemon that spawns processes.
*/
export declare const BRIDGE_PREFIX = "/_bridge";
/**
* The extension version this daemon speaks (#1519). The extension states its own version on
* every call, in this header, and a daemon expecting another refuses outright: a version-skewed
* extension does not fail loudly, it half-works — missed messages, silently ignored fields —
* which reads as a framework bug and burns a debugging session. The extension's manifest must
* carry the same number; a test keeps the two in lockstep.
*/
export declare const EXPECTED_EXTENSION_VERSION = "0.8.1";
/** The header the extension states its version in. Lowercase, as node presents all headers. */
export declare const EXTENSION_VERSION_HEADER = "x-tf-extension-version";
/** A question a cloud session is parked on, as reported by the bridge. */
export interface BridgeQuestion {
/** The cloud session that asked, which joins back to an agent through `AgentMeta.sessionId`. */
sessionId: string;
title: string;
options: {
label: string;
detail?: string;
}[];
recommended?: string;
/** When the daemon accepted it. Set here, never by the caller. */
receivedAt: string;
}
/**
* One thing a cloud session did, as scraped from its page (#1237).
*
* `seq` is the message's position in the transcript, assigned by the extension, and it is what
* makes this idempotent: the page is re-read on every DOM change, so the same message arrives
* many times and the daemon keeps one copy per position rather than a growing pile of repeats.
*/
export interface BridgeEvent {
sessionId: string;
seq: number;
role: 'agent' | 'user';
text: string;
receivedAt: string;
}
/** What the page half of the bridge reports about itself, for diagnosis. */
export interface BridgeHello {
version: string;
sessionId?: string | undefined;
note: string;
at: string;
}
/** A cloud session the extension should be watching. */
export interface BridgeSession {
id: string;
url: string;
}
/** What the daemon wires behind the bridge. Absent when the feature is off, which 404s it. */
export interface BridgeHandlers {
/** The shared secret every bridge call must present. */
token: string;
/**
* The extension version to insist on (#1519). When set, every route past the token demands a
* matching {@link EXTENSION_VERSION_HEADER} and answers 426 otherwise — no degraded mode,
* ping included, so the only path forward from a stale extension is updating it.
*/
expectedExtensionVersion?: string;
/** What version the caller claimed and whether it was turned away, for the dashboard. */
extensionVersion?: (got: string, blocked: boolean) => void;
record: (question: BridgeQuestion) => void;
/** Record what the session said, keyed by its position in the transcript. */
recordEvent?: (event: BridgeEvent) => void;
/** Note that something reached the bridge, including when it was refused. */
contact?: (route: string, status: number) => void;
/** What the injected page script reports about itself. */
hello?: (hello: BridgeHello) => void;
/**
* The cloud sessions worth watching, newest first. The extension cannot know an agent started:
* it only sees pages the user is already on, so without this the bridge works only when
* somebody happens to be looking at claude.ai. This is how a tab gets opened for them.
*/
sessions?: () => Promise<BridgeSession[]>;
/** The answer queued in the dashboard for that session, waiting to be delivered (#1237). */
answer?: (sessionId: string) => {
id: string;
label: string;
} | undefined;
/** The extension's word on what a delivery attempt did. */
answered?: (sessionId: string, id: string, ok: boolean, note?: string) => void;
now?: () => Date;
}
/** Route a `/_bridge/*` request. A daemon with the bridge off 404s every route. */
export declare function handleBridgeRequest(req: IncomingMessage, res: ServerResponse, pathname: string, handlers: BridgeHandlers | undefined): Promise<void>;
//# sourceMappingURL=bridge-endpoints.d.ts.map