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.

110 lines 6.99 kB
import type { IncomingMessage, ServerResponse } from 'node:http'; import type { FrameworkEvent } from '../events.js'; import type { PreferencesStore } from '../registry.js'; import type { DiscordCredentialsStore } from '../discord-credentials.js'; import type { QuotaSource } from './quota.js'; import type { AutoPmReporter } from '../auto-pm.js'; import type { ProjectErrorsReader } from '../project-errors.js'; import type { AddProjectResult, StartAgentKind, StartAgentOptions, StartAgentResult } from './types.js'; import type { AgentMeta } from '../store/index.js'; /** Wired by the daemon so `sendStart` can reach the daemon's own `startAgent` closure. */ export type StartAgentHandler = (prompt: string, kind: StartAgentKind, options: StartAgentOptions, projectId?: string) => StartAgentResult | Promise<StartAgentResult>; /** Wired by the daemon so `sendAddProject` can install + register a repo (#433). */ export type AddProjectHandler = (path: string, directory: boolean) => AddProjectResult | Promise<AddProjectResult>; /** Resolve an agent to its live event stream: the relay feeds `onEvents` from its own in-memory stream * rather than a file on disk (#426), and the daemon feeds an agent it is relaying from a device (#1067). * Returns undefined when there is no in-memory stream, so `onEvents` falls back to tailing the log. */ export type EventsSource = (projectId: string, agentId?: string) => AsyncIterable<FrameworkEvent> | undefined; /** Look up the device a relayed agent (#1067) executes on, or undefined for an ordinary local agent. The * daemon wires this from its live relayed-agent map; a run-scoped RPC uses it to forward a remote agent's * read/steer/handoff to that device instead of resolving a (nonexistent) local checkout. */ export interface RemoteAgents { target(agentId: string | undefined): { url: string; token: string; } | undefined; /** A project's relayed run stubs (#1077), so `onAgents` can show a remote agent in the list and re-open it after a reload. */ list(projectId: string): AgentMeta[]; } /** * What every RPC acts through. * * Every field is required (D3). These used to be optional because three hosts served this same * surface — the daemon, a per-session foreground dashboard, and a public relay — each wiring a * different subset, so every RPC carried an "absent capability" branch and the client rendered a * degradation matrix. There is one host now, and it wires all of it. */ export interface DashboardContext { startAgent: StartAgentHandler; addProject: AddProjectHandler; /** The in-memory event stream for an agent relayed from a connected device (#1067), else undefined. */ eventsSource: EventsSource; /** The relayed-agent lookup (#1067 slice 2), so a run-scoped RPC can tell a local agent from one * running on a connected device and forward the call there. */ remote: RemoteAgents; /** The user-preferences store (#410), over the registry file. */ preferences: PreferencesStore; /** The quota source behind the usage panel (#533). */ quota: QuotaSource; /** The Discord credentials store (#1095), which also reloads the Discord services on a save. */ discord: DiscordCredentialsStore; /** What auto PM last decided (#1161). */ autoPm: AutoPmReporter; /** * Run an auto PM sweep now rather than at the next interval (#1210). Resolves when the sweep * does (#1433), so the trigger RPC can await it and return what it decided. */ autoPmSweep: (opts?: { drainOnly?: boolean; }) => void | Promise<void>; /** What a project currently suffers from (#1500): the daemon's error state, read per project. */ projectErrors: ProjectErrorsReader; } /** * CSRF guard for the state-changing RPCs. A browser attaches an `Origin` * header to every cross-site request, so we reject any POST whose Origin is not this * same server (or a loopback host) — otherwise a page on `evil.com` could `fetch()` the * localhost dashboard and spawn/steer an agent. An absent Origin means a non-browser caller * (curl, the test suite) with no ambient session to abuse, so it passes. Lives here beside * the mount, its only caller. */ export declare function isSameOriginRequest(req: IncomingMessage): boolean; /** * DNS-rebinding guard, the other half of the CSRF check above. A page on `evil.com` whose DNS * re-answers as `127.0.0.1` is *same-origin* with this server as far as the browser is concerned, * so its `fetch()` takes the passing branch of {@link isSameOriginRequest} — and every RPC behind * the mount, `sendStart` included, is reachable from a page the user merely visited. * * The `Host` header is what still gives the attacker away: it carries the name the browser was * asked for (`evil.com`), not the address it resolved to. So when we are bound to loopback, the * only `Host` a real user's browser can send is a loopback one (or the bound address itself) — * anything else is a rebound name and is rejected. An absent `Host` is rejected too when we are * enforcing: HTTP/1.1 requires it, and every browser sends it. * * A non-loopback bind (`--host`, #1051) is reached by a hostname we cannot predict, so there is * no allowlist to check against; that case gates behind the shared daemon token instead. Hosts * that never pass a bind host at all (the relay, which serves a public domain) are unaffected. */ export declare function isExpectedHost(req: IncomingMessage, boundHost: string | undefined): boolean; /** Where the dashboard's RPCs live. One prefix, so the static handler can decline it by path. */ export declare const RPC_PREFIX = "/_rpc"; /** * Mount the dashboard's RPC surface (#405) on the daemon's `node:http` server: `POST /_rpc/<name>` * for the calls, `GET /_rpc/events` for the live stream. It runs in the daemon process, so * `sendStart` reaches the daemon's own `startAgent` through the wired {@link DashboardContext}. * * Cross-origin POSTs are rejected (CSRF: a page on evil.com must not steer or start a session), as * are requests carrying someone else's `Host` when we are bound to loopback (DNS rebinding: the * same page must not reach us by pointing its own name at `127.0.0.1`). Pass `opts.host` — the * address the server is bound to — to enable that second check. Returns whether the request was * the RPC surface's. * * This replaced Telefunc (F3), which required a build-time transform over every `.telefunc.ts` * file, a registration table pinning each RPC to the client-baked key of the *dashboard* source * path it was re-exported from, and a request-context indirection for wiring that never varied * per request. What it bought over this was type-safety across a package boundary that A7 removed. */ export declare function makeRpcMount(context: DashboardContext, opts?: { host?: string; }): (req: IncomingMessage, res: ServerResponse) => Promise<boolean>; //# sourceMappingURL=rpc-serve.d.ts.map