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
TypeScript
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