feat(kanban): persist board, reference pages, and run agents
Replace the localStorage kanban with the backend-backed board, add typed clients and a React hook with optimistic updates. Cards can reference static doc pages and user-created custom pages (new "custom" reference type with purple chips). Add the agentic orchestrator UI: a per-card panel to launch `pi` runs, watch a live tool/thought stream over SSE, steer mid-run, and stop — while the board stays fully interactive. The board page wires the orchestrator and custom-pages stores into every card.
This commit is contained in:
102
apps/docs/src/lib/orchestratorApi.ts
Normal file
102
apps/docs/src/lib/orchestratorApi.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Typed client for the @void-nav/api orchestrator backend.
|
||||
*
|
||||
* The orchestrator turns the implementation board into an agentic system: each
|
||||
* run hands a kanban card to a `pi` subprocess (running inside an isolated git
|
||||
* worktree) and streams its progress back to the UI. The agent drives the board
|
||||
* and the documentation through the server's internal endpoints, so the board
|
||||
* stays fully interactive and the docs remain the central store of truth.
|
||||
*
|
||||
* Keep these types in sync with `apps/api/src/types.ts`.
|
||||
*/
|
||||
|
||||
export type RunStatus = 'queued' | 'running' | 'completed' | 'failed' | 'stopped';
|
||||
|
||||
export interface AgentRun {
|
||||
id: string;
|
||||
cardId: string;
|
||||
status: RunStatus;
|
||||
useWorktree: boolean;
|
||||
branch: string | null;
|
||||
worktreePath: string | null;
|
||||
prompt: string;
|
||||
summary: string | null;
|
||||
commitSha: string | null;
|
||||
error: string | null;
|
||||
createdAt: string;
|
||||
startedAt: string | null;
|
||||
finishedAt: string | null;
|
||||
}
|
||||
|
||||
export type RunEventType =
|
||||
| 'status'
|
||||
| 'text'
|
||||
| 'tool_start'
|
||||
| 'tool_end'
|
||||
| 'log'
|
||||
| 'done'
|
||||
| 'error';
|
||||
|
||||
export interface RunEvent {
|
||||
seq?: number;
|
||||
type: RunEventType;
|
||||
data: Record<string, unknown>;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
const BASE = '/api/orchestrator';
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(message: string, readonly status: number) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
async function req<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
...init,
|
||||
headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) },
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = `${res.status} ${res.statusText}`;
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body?.error) detail = body.error;
|
||||
} catch {
|
||||
/* keep status text */
|
||||
}
|
||||
throw new ApiError(detail, res.status);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
export interface StartRunInput {
|
||||
cardId: string;
|
||||
prompt?: string;
|
||||
useWorktree?: boolean;
|
||||
cleanupOnFinish?: boolean;
|
||||
}
|
||||
|
||||
export const orchestratorApi = {
|
||||
listRuns: (cardId?: string) =>
|
||||
req<{ runs: AgentRun[] }>(`/runs${cardId ? `?cardId=${encodeURIComponent(cardId)}` : ''}`),
|
||||
|
||||
getRun: (id: string, since = 0) =>
|
||||
req<{ run: AgentRun; events: RunEvent[] }>(`/runs/${id}?since=${since}`),
|
||||
|
||||
startRun: (input: StartRunInput) =>
|
||||
req<{ run: AgentRun }>(`/runs`, { method: 'POST', body: JSON.stringify(input) }),
|
||||
|
||||
messageRun: (id: string, text: string, mode: 'steer' | 'followUp') =>
|
||||
req<{ ok: boolean }>(`/runs/${id}/message`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ text, mode }),
|
||||
}),
|
||||
|
||||
stopRun: (id: string) =>
|
||||
req<{ ok: boolean }>(`/runs/${id}/stop`, { method: 'POST' }),
|
||||
|
||||
deleteRun: (id: string) => req<void>(`/runs/${id}`, { method: 'DELETE' }),
|
||||
};
|
||||
Reference in New Issue
Block a user