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.
90 lines • 5.78 kB
TypeScript
import { EventStream } from '../event-stream.js';
import type { FrameworkEvent } from '../events.js';
import { type AgentMeta } from '../store/index.js';
import type { StartAgentKind, StartAgentOptions, StartAgentResult } from './types.js';
/**
* The server-side half of "run on a connected device" (#1067). The local daemon holds the saved
* device's token, so it - not the browser - drives the remote daemon: it POSTs the agent to the
* remote's `/_relay/start` and then fetch-streams the remote's `/_relay/events` back into a local
* {@link EventStream}, which the dashboard reads over its normal same-origin `onEvents` channel. So
* the browser never talks cross-origin and the token never leaves the two daemons (issue #1067 (b)).
*
* Authentication is the shared-token cookie (#1051), sent daemon-to-daemon: `Cookie: fw_daemon=<token>` with no
* `Origin` header. The remote's guard admits a matching cookie without the browser-only `?token=`
* 302, and its `/_rpc` CSRF check (absent Origin passes) is not even on these raw routes.
*/
/** Where a relayed agent executes: the remote daemon's origin and its #1051 token. Memory-only. */
export interface RemoteTarget {
url: string;
token: string;
}
/** The body a relay start forwards to the remote's `/_relay/start`. */
export interface RelayStartBody {
prompt: string;
kind: StartAgentKind;
options: StartAgentOptions;
}
/**
* Health-check a saved device (#1072): a cookie'd `GET /_relay/ping`, true on any 2xx, false on a
* non-2xx, an unreachable host, or the timeout. The token stays in memory for the check only, never
* persisted, same as {@link startRemoteAgent}. This is how the browser's status dots learn reachable
* from not: it has the tokens, the daemon does the cross-origin request.
*/
export declare function pingRemote(target: RemoteTarget): Promise<boolean>;
/**
* Start an agent on the remote daemon and return its {@link StartAgentResult} (with the remote's own run
* id). A non-2xx or a transport failure surfaces as an `ok: false` result the dashboard shows, the
* same shape a local refusal has, so the caller does not special-case remote errors.
*/
export declare function startRemoteAgent(target: RemoteTarget, body: RelayStartBody): Promise<StartAgentResult>;
/**
* Relay one run-scoped RPC to the device that owns a remote agent (#1067 slice 2). The local daemon
* holds the device token, so a read/diff/handoff/push/PR for a relayed agent runs ON the device: POST
* {fn, args} to the remote's /_relay/rpc over the shared-token cookie (#1051) (no Origin), returning the device's
* result. Throws on an unreachable device or a non-2xx so the caller falls back to its own empty/error
* shape, the same way a failed local read does.
*/
export declare function relayRpc(target: RemoteTarget, fn: string, args: unknown[]): Promise<unknown>;
/**
* Fetch-stream a remote agent's newline-delimited events into `onEvent` until the remote closes the
* body, the agent ends, or `cancel()` is called. A 401 (the token was rotated) ends the stream
* cleanly rather than as an error, so the dashboard sees a normal `done`, not a lost connection.
* Returns a cancel function; calling it aborts the fetch and releases the reader.
*/
export declare function streamRemoteEvents(target: RemoteTarget, agentId: string, onEvent: (event: FrameworkEvent) => void, onEnd?: () => void): () => void;
/**
* The local daemon's live relayed runs (#1067), keyed by the remote agent id. Registering an agent opens
* an {@link EventStream} the dashboard reads through `onEvents`, fed by {@link streamRemoteEvents}
* from the remote. This map is where a saved device's token lives daemon-side: in memory, for the
* run's lifetime.
*
* The `targets` map outlives the event pump (#1067 slice 2): a finished remote agent's post-run reads,
* push and open-PR still have to reach the device after its event stream has ended, so the device
* target is kept until {@link dispose} clears it, not dropped when the stream closes.
*
* The `metas` map (#1077) holds a local {@link AgentMeta} stub per relayed agent so `onAgents` can show a
* remote run in the session list and re-open it after a dashboard reload; {@link list} projects it
* per project. Same lifetime as `targets`: it outlives the event stream and is cleared on dispose.
*/
export declare class RelayedAgents {
private readonly agents;
private readonly targets;
private readonly metas;
/** Open a local stream for a remote agent and start pumping the remote's events into it. */
register(agentId: string, target: RemoteTarget, meta: AgentMeta, projectId: string): void;
/** The live event stream for a relayed agent, or undefined when this daemon is not relaying it. */
get(agentId: string | undefined): EventStream<FrameworkEvent> | undefined;
/** The device a relayed agent runs on, kept past the event stream so post-run push/PR still reach it. */
target(agentId: string | undefined): RemoteTarget | undefined;
/** A project's relayed run stubs (#1077), newest-first, so `onAgents` can surface them in the list. */
list(projectId: string): AgentMeta[];
/** Fold each relayed event into the agent's list row via the store's own reducer (#1077), so the
* local stub mirrors the device: the terminal status on `end`, the waiting flag while it is parked
* (#785), the driver once its session starts. Events carry no write time, so this stamps its own. */
private apply;
/** Close a relayed agent's event stream (not its target). Idempotent. */
private endStream;
/** Stop every pump, close every stream, and forget every device target + list stub, on daemon shutdown. */
dispose(): void;
}
//# sourceMappingURL=remote-run.d.ts.map