/// Gateway API client — used when running in gateway mode. /// /// The gateway mode is detected by checking `GET /gateway/mode`. If it returns /// `{ "mode": "gateway" }` the frontend switches to the gateway UI. export interface JoinedAgent { id: string; label: string; address: string; registered_at: number; /// Unix timestamp of the last heartbeat from this agent. last_seen: number; /// Project this agent is assigned to, if any. assigned_project?: string; } export interface GatewayProject { name: string; url: string; } export interface GatewayInfo { active: string; projects: GatewayProject[]; } export interface PipelineItem { story_id: string; name: string; stage: string; agent?: { agent_name: string; model: string; status: string } | null; blocked?: boolean; retry_count?: number; merge_failure?: string; } export interface ProjectPipelineStatus { active: PipelineItem[]; backlog: { story_id: string; name: string }[]; backlog_count: number; error?: string; } export interface AllProjectsPipeline { active: string; projects: Record; } export interface GenerateTokenResponse { token: string; } export interface ServerMode { mode: "gateway" | "standard"; } async function gatewayRequest( path: string, options: RequestInit = {}, ): Promise { const res = await fetch(path, { headers: { "Content-Type": "application/json", ...(options.headers ?? {}) }, ...options, }); if (!res.ok) { const text = await res.text(); throw new Error(text || `Request failed (${res.status})`); } // DELETE /gateway/agents/:id returns 204 No Content. if (res.status === 204) { return undefined as unknown as T; } return res.json() as Promise; } export const gatewayApi = { /// Returns `{ mode: "gateway" }` if this server is a gateway, otherwise rejects. getServerMode(): Promise { return gatewayRequest("/gateway/mode"); }, /// Generate a one-time join token for a new build agent. generateToken(): Promise { return gatewayRequest("/gateway/tokens", { method: "POST", }); }, /// List all build agents that have registered with this gateway. listAgents(): Promise { return gatewayRequest("/gateway/agents"); }, /// Remove a registered build agent by its ID. removeAgent(id: string): Promise { return gatewayRequest(`/gateway/agents/${id}`, { method: "DELETE", }); }, /// Assign an agent to a project, or unassign it by passing null. assignAgent(id: string, project: string | null): Promise { return gatewayRequest(`/gateway/agents/${id}/assign`, { method: "POST", body: JSON.stringify({ project }), }); }, /// Get the list of registered projects from the gateway. getGatewayInfo(): Promise { return gatewayRequest("/api/gateway"); }, /// Add a new project to the gateway config. addProject(name: string, url: string): Promise { return gatewayRequest("/api/gateway/projects", { method: "POST", body: JSON.stringify({ name, url }), }); }, /// Remove a project from the gateway config. removeProject(name: string): Promise { return gatewayRequest( `/api/gateway/projects/${encodeURIComponent(name)}`, { method: "DELETE" }, ); }, /// Send a heartbeat for an agent to update its last-seen timestamp. heartbeat(id: string): Promise { return gatewayRequest(`/gateway/agents/${id}/heartbeat`, { method: "POST", }); }, /// Fetch pipeline status from all registered projects. getAllProjectsPipeline(): Promise { return gatewayRequest("/api/gateway/pipeline"); }, /// Switch the active project. switchProject(project: string): Promise<{ ok: boolean; error?: string }> { return gatewayRequest<{ ok: boolean; error?: string }>( "/api/gateway/switch", { method: "POST", body: JSON.stringify({ project }) }, ); }, };