summaryrefslogtreecommitdiff
path: root/makima/frontend/src/lib/api.ts
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-05 23:42:48 +0000
committersoryu <soryu@soryu.co>2026-02-05 23:42:48 +0000
commit88a4f15ce1310f8ee8693835be14aa5280233f17 (patch)
tree5c1a0417e02071d2198d13478ffa85533b19f891 /makima/frontend/src/lib/api.ts
parentf1a50b80f3969d150bd1c31edde0aff05369157e (diff)
downloadsoryu-88a4f15ce1310f8ee8693835be14aa5280233f17.tar.gz
soryu-88a4f15ce1310f8ee8693835be14aa5280233f17.zip
Add directive-first chain system redesign
Redesigns the chain system with a directive-first architecture where Directive is the top-level entity (the "why/what") and Chains are generated execution plans (the "how") that can be dynamically modified. Backend: - Add database migration for directive system tables - Add Directive, DirectiveChain, ChainStep, DirectiveEvent models - Add DirectiveVerifier and DirectiveApproval models - Add orchestration module with engine, planner, and verifier - Add comprehensive API handlers for directives - Add daemon CLI commands for directive management - Add directive skill documentation - Integrate contract completion with directive engine - Add SSE endpoint for real-time directive events Frontend: - Add directives route with split-view layout - Add 6-tab detail view (Overview, Chain, Events, Evaluations, Approvals, Verifiers) - Add React Flow DAG visualization for chain steps - Add SSE subscription hook for real-time event updates - Add useDirectives and useDirectiveEventSubscription hooks - Add directive types and API functions Fixes: - Fix test failures in ws/protocol, task_output, completion_gate, patch - Fix word boundary matching in looks_like_task() - Fix parse_last() to find actual last completion gate - Fix create_export_patch when merge-base equals HEAD - Clean up clippy warnings in new code Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'makima/frontend/src/lib/api.ts')
-rw-r--r--makima/frontend/src/lib/api.ts758
1 files changed, 758 insertions, 0 deletions
diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts
index 2f4ee62..80a43eb 100644
--- a/makima/frontend/src/lib/api.ts
+++ b/makima/frontend/src/lib/api.ts
@@ -3527,3 +3527,761 @@ export async function setChainRepositoryPrimary(
}
return res.json();
}
+
+// =============================================================================
+// Directive Types and API
+// =============================================================================
+
+/** Directive status */
+export type DirectiveStatus =
+ | "draft"
+ | "planning"
+ | "active"
+ | "paused"
+ | "completed"
+ | "archived"
+ | "failed";
+
+/** Autonomy level */
+export type AutonomyLevel = "full_auto" | "guardrails" | "manual";
+
+/** Confidence level (traffic light) */
+export type ConfidenceLevel = "green" | "yellow" | "red";
+
+/** Step status */
+export type StepStatus =
+ | "pending"
+ | "ready"
+ | "running"
+ | "evaluating"
+ | "passed"
+ | "failed"
+ | "rework"
+ | "skipped"
+ | "blocked";
+
+/** Evaluation type */
+export type EvaluationType = "programmatic" | "llm" | "composite" | "manual";
+
+/** Directive summary for list view */
+export interface DirectiveSummary {
+ id: string;
+ title: string;
+ goal: string;
+ status: DirectiveStatus;
+ autonomyLevel: AutonomyLevel;
+ repositoryUrl: string | null;
+ currentChainId: string | null;
+ currentChainGeneration: number | null;
+ totalSteps: number;
+ completedSteps: number;
+ failedSteps: number;
+ currentConfidence: number | null;
+ totalCostUsd: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+/** Full directive */
+export interface Directive {
+ id: string;
+ ownerId: string;
+ title: string;
+ goal: string;
+ status: DirectiveStatus;
+ autonomyLevel: AutonomyLevel;
+ repositoryUrl: string | null;
+ localPath: string | null;
+ requirements: unknown | null;
+ acceptanceCriteria: unknown | null;
+ constraints: unknown | null;
+ confidenceThresholdGreen: number;
+ confidenceThresholdYellow: number;
+ maxReworkCycles: number;
+ maxTotalCostUsd: number | null;
+ maxWallTimeMinutes: number | null;
+ maxChainRegenerations: number;
+ totalCostUsd: number;
+ currentChainId: string | null;
+ version: number;
+ createdAt: string;
+ updatedAt: string;
+ startedAt: string | null;
+ completedAt: string | null;
+}
+
+/** Directive chain */
+export interface DirectiveChain {
+ id: string;
+ directiveId: string;
+ generation: number;
+ name: string;
+ description: string | null;
+ rationale: string | null;
+ planningModel: string | null;
+ status: string;
+ totalSteps: number;
+ completedSteps: number;
+ failedSteps: number;
+ currentConfidence: number | null;
+ startedAt: string | null;
+ completedAt: string | null;
+ version: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+/** Chain step */
+export interface ChainStep {
+ id: string;
+ chainId: string;
+ name: string;
+ description: string | null;
+ stepType: string;
+ contractType: string;
+ initialPhase: string | null;
+ taskPlan: string | null;
+ phases: string[];
+ dependsOn: string[];
+ parallelGroup: string | null;
+ requirementIds: string[];
+ acceptanceCriteriaIds: string[];
+ verifierConfig: unknown;
+ status: StepStatus;
+ contractId: string | null;
+ supervisorTaskId: string | null;
+ confidenceScore: number | null;
+ confidenceLevel: ConfidenceLevel | null;
+ evaluationCount: number;
+ reworkCount: number;
+ lastEvaluationId: string | null;
+ editorX: number | null;
+ editorY: number | null;
+ startedAt: string | null;
+ completedAt: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+/** Directive with progress info */
+export interface DirectiveWithProgress extends Directive {
+ chain: DirectiveChain | null;
+ steps: ChainStep[];
+ recentEvents: DirectiveEvent[];
+ pendingApprovals: DirectiveApproval[];
+}
+
+/** Directive evaluation */
+export interface DirectiveEvaluation {
+ id: string;
+ directiveId: string;
+ chainId: string | null;
+ stepId: string | null;
+ evaluationType: EvaluationType;
+ passed: boolean;
+ overallScore: number;
+ confidenceLevel: ConfidenceLevel;
+ programmaticResults: unknown | null;
+ llmResults: unknown | null;
+ compositeBreakdown: unknown | null;
+ feedback: string | null;
+ reworkInstructions: string | null;
+ verifierIds: string[];
+ evaluatedBy: string | null;
+ createdAt: string;
+}
+
+/** Directive event */
+export interface DirectiveEvent {
+ id: string;
+ directiveId: string;
+ chainId: string | null;
+ stepId: string | null;
+ eventType: string;
+ severity: string;
+ eventData: unknown | null;
+ actorType: string;
+ actorId: string | null;
+ createdAt: string;
+}
+
+/** Directive approval */
+export interface DirectiveApproval {
+ id: string;
+ directiveId: string;
+ chainId: string | null;
+ stepId: string | null;
+ approvalType: string;
+ description: string;
+ context: unknown | null;
+ urgency: string;
+ status: string;
+ requestedAt: string;
+ resolvedAt: string | null;
+ resolvedBy: string | null;
+ response: string | null;
+}
+
+/** Directive verifier */
+export interface DirectiveVerifier {
+ id: string;
+ directiveId: string;
+ name: string;
+ verifierType: string;
+ command: string | null;
+ workingDirectory: string | null;
+ timeoutSeconds: number;
+ environment: unknown;
+ autoDetect: boolean;
+ detectFiles: string[];
+ weight: number;
+ required: boolean;
+ enabled: boolean;
+ lastRunAt: string | null;
+ lastResult: unknown | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+/** Directive graph node */
+export interface DirectiveGraphNode {
+ id: string;
+ name: string;
+ stepType: string;
+ status: StepStatus;
+ confidenceScore: number | null;
+ confidenceLevel: ConfidenceLevel | null;
+ contractId: string | null;
+ editorX: number | null;
+ editorY: number | null;
+}
+
+/** Directive graph edge */
+export interface DirectiveGraphEdge {
+ source: string;
+ target: string;
+}
+
+/** Directive graph response */
+export interface DirectiveGraphResponse {
+ chainId: string;
+ directiveId: string;
+ nodes: DirectiveGraphNode[];
+ edges: DirectiveGraphEdge[];
+}
+
+/** Create directive request */
+export interface CreateDirectiveRequest {
+ goal: string;
+ repositoryUrl?: string;
+ localPath?: string;
+ autonomyLevel?: AutonomyLevel;
+ confidenceThresholdGreen?: number;
+ confidenceThresholdYellow?: number;
+ maxReworkCycles?: number;
+ maxTotalCostUsd?: number;
+ maxWallTimeMinutes?: number;
+}
+
+/** Update directive request */
+export interface UpdateDirectiveRequest {
+ title?: string;
+ goal?: string;
+ requirements?: unknown;
+ acceptanceCriteria?: unknown;
+ constraints?: unknown;
+ autonomyLevel?: AutonomyLevel;
+ confidenceThresholdGreen?: number;
+ confidenceThresholdYellow?: number;
+ maxReworkCycles?: number;
+ maxTotalCostUsd?: number;
+ maxWallTimeMinutes?: number;
+ version?: number;
+}
+
+/** Add step request */
+export interface AddStepRequest {
+ name: string;
+ description?: string;
+ stepType?: string;
+ contractType?: string;
+ initialPhase?: string;
+ taskPlan?: string;
+ phases?: string[];
+ dependsOn?: string[];
+ parallelGroup?: string;
+ requirementIds?: string[];
+ acceptanceCriteriaIds?: string[];
+ verifierConfig?: unknown;
+ editorX?: number;
+ editorY?: number;
+}
+
+/** Update step request */
+export interface UpdateStepRequest {
+ name?: string;
+ description?: string;
+ initialPhase?: string;
+ taskPlan?: string;
+ phases?: string[];
+ dependsOn?: string[];
+ parallelGroup?: string;
+ requirementIds?: string[];
+ acceptanceCriteriaIds?: string[];
+ verifierConfig?: unknown;
+ editorX?: number;
+ editorY?: number;
+}
+
+/** Create verifier request */
+export interface CreateVerifierRequest {
+ name: string;
+ verifierType: string;
+ command?: string;
+ workingDirectory?: string;
+ timeoutSeconds?: number;
+ weight?: number;
+ required?: boolean;
+ enabled?: boolean;
+}
+
+/** Update verifier request */
+export interface UpdateVerifierRequest {
+ command?: string;
+ weight?: number;
+ required?: boolean;
+ enabled?: boolean;
+}
+
+/** Approval action request */
+export interface ApprovalActionRequest {
+ response?: string;
+}
+
+/** Start directive response */
+export interface StartDirectiveResponse {
+ directiveId: string;
+ chainId: string;
+ chainGeneration: number;
+ steps: ChainStep[];
+ status: string;
+}
+
+/** Directive list response */
+export interface DirectiveListResponse {
+ directives: DirectiveSummary[];
+ total: number;
+}
+
+// =============================================================================
+// Directive API Functions
+// =============================================================================
+
+/** List directives */
+export async function listDirectives(
+ status?: DirectiveStatus,
+ limit = 50,
+ offset = 0
+): Promise<DirectiveListResponse> {
+ const params = new URLSearchParams();
+ if (status) params.set("status", status);
+ params.set("limit", String(limit));
+ params.set("offset", String(offset));
+
+ const res = await authFetch(`${API_BASE}/api/v1/directives?${params}`);
+ if (!res.ok) {
+ throw new Error(`Failed to list directives: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Get directive by ID */
+export async function getDirective(directiveId: string): Promise<DirectiveWithProgress> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}`);
+ if (!res.ok) {
+ throw new Error(`Failed to get directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Create a new directive */
+export async function createDirective(req: CreateDirectiveRequest): Promise<Directive> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives`, {
+ method: "POST",
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) {
+ const errorText = await res.text();
+ throw new Error(`Failed to create directive: ${errorText || res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Update directive */
+export async function updateDirective(
+ directiveId: string,
+ req: UpdateDirectiveRequest
+): Promise<Directive> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}`, {
+ method: "PUT",
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to update directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Archive directive */
+export async function archiveDirective(directiveId: string): Promise<{ archived: boolean }> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}`, {
+ method: "DELETE",
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to archive directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Start directive */
+export async function startDirective(directiveId: string): Promise<StartDirectiveResponse> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/start`, {
+ method: "POST",
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to start directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Pause directive */
+export async function pauseDirective(directiveId: string): Promise<Directive> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/pause`, {
+ method: "POST",
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to pause directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Resume directive */
+export async function resumeDirective(directiveId: string): Promise<Directive> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/resume`, {
+ method: "POST",
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to resume directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Stop directive */
+export async function stopDirective(directiveId: string): Promise<Directive> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/stop`, {
+ method: "POST",
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to stop directive: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Get directive chain */
+export async function getDirectiveChain(
+ directiveId: string
+): Promise<{ chain: DirectiveChain | null; steps: ChainStep[] }> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain`);
+ if (!res.ok) {
+ throw new Error(`Failed to get directive chain: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Get directive chain graph */
+export async function getDirectiveGraph(directiveId: string): Promise<DirectiveGraphResponse> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain/graph`);
+ if (!res.ok) {
+ throw new Error(`Failed to get directive graph: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Replan directive chain */
+export async function replanDirectiveChain(directiveId: string): Promise<DirectiveChain> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain/replan`, {
+ method: "POST",
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to replan directive chain: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Add step to directive chain */
+export async function addDirectiveStep(
+ directiveId: string,
+ req: AddStepRequest
+): Promise<ChainStep> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/chain/steps`, {
+ method: "POST",
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to add step: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Get step details */
+export async function getDirectiveStep(
+ directiveId: string,
+ stepId: string
+): Promise<ChainStep> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}`);
+ if (!res.ok) {
+ throw new Error(`Failed to get step: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Update step */
+export async function updateDirectiveStep(
+ directiveId: string,
+ stepId: string,
+ req: UpdateStepRequest
+): Promise<ChainStep> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/chain/steps/${stepId}`,
+ {
+ method: "PUT",
+ body: JSON.stringify(req),
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to update step: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Delete step */
+export async function deleteDirectiveStep(
+ directiveId: string,
+ stepId: string
+): Promise<{ deleted: boolean }> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/chain/steps/${stepId}`,
+ {
+ method: "DELETE",
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to delete step: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Skip step */
+export async function skipDirectiveStep(
+ directiveId: string,
+ stepId: string
+): Promise<ChainStep> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}/skip`,
+ {
+ method: "POST",
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to skip step: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** List directive evaluations */
+export async function listDirectiveEvaluations(
+ directiveId: string,
+ limit = 50
+): Promise<DirectiveEvaluation[]> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/evaluations?limit=${limit}`
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to list evaluations: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** List directive events */
+export async function listDirectiveEvents(
+ directiveId: string,
+ limit = 50
+): Promise<DirectiveEvent[]> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/events?limit=${limit}`
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to list events: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** List directive verifiers */
+export async function listDirectiveVerifiers(
+ directiveId: string
+): Promise<DirectiveVerifier[]> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/verifiers`);
+ if (!res.ok) {
+ throw new Error(`Failed to list verifiers: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Add verifier */
+export async function addDirectiveVerifier(
+ directiveId: string,
+ req: CreateVerifierRequest
+): Promise<DirectiveVerifier> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/verifiers`, {
+ method: "POST",
+ body: JSON.stringify(req),
+ });
+ if (!res.ok) {
+ throw new Error(`Failed to add verifier: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Update verifier */
+export async function updateDirectiveVerifier(
+ directiveId: string,
+ verifierId: string,
+ req: UpdateVerifierRequest
+): Promise<DirectiveVerifier> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/verifiers/${verifierId}`,
+ {
+ method: "PUT",
+ body: JSON.stringify(req),
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to update verifier: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Auto-detect verifiers */
+export async function autoDetectVerifiers(
+ directiveId: string
+): Promise<DirectiveVerifier[]> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/verifiers/auto-detect`,
+ {
+ method: "POST",
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to auto-detect verifiers: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** List pending approvals */
+export async function listDirectiveApprovals(
+ directiveId: string
+): Promise<DirectiveApproval[]> {
+ const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/approvals`);
+ if (!res.ok) {
+ throw new Error(`Failed to list approvals: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Approve request */
+export async function approveDirectiveRequest(
+ directiveId: string,
+ approvalId: string,
+ req?: ApprovalActionRequest
+): Promise<DirectiveApproval> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/approvals/${approvalId}/approve`,
+ {
+ method: "POST",
+ body: JSON.stringify(req || {}),
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to approve request: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Deny request */
+export async function denyDirectiveRequest(
+ directiveId: string,
+ approvalId: string,
+ req?: ApprovalActionRequest
+): Promise<DirectiveApproval> {
+ const res = await authFetch(
+ `${API_BASE}/api/v1/directives/${directiveId}/approvals/${approvalId}/deny`,
+ {
+ method: "POST",
+ body: JSON.stringify(req || {}),
+ }
+ );
+ if (!res.ok) {
+ throw new Error(`Failed to deny request: ${res.statusText}`);
+ }
+ return res.json();
+}
+
+/** Subscribe to directive events via SSE */
+export async function subscribeToDirectiveEvents(
+ directiveId: string,
+ onEvent: (event: DirectiveEvent) => void,
+ onError?: (error: Error) => void
+): Promise<() => void> {
+ // Get auth token for the request
+ let authToken: string | null = null;
+ if (supabase) {
+ const { data: { session } } = await supabase.auth.getSession();
+ if (session?.access_token) {
+ authToken = session.access_token;
+ }
+ }
+
+ // Build URL with auth token as query param (since EventSource doesn't support headers)
+ const url = new URL(`${API_BASE}/api/v1/directives/${directiveId}/events/stream`);
+ if (authToken) {
+ url.searchParams.set("token", authToken);
+ } else {
+ const apiKey = getStoredApiKey();
+ if (apiKey) {
+ url.searchParams.set("api_key", apiKey);
+ }
+ }
+
+ // Create EventSource connection
+ const eventSource = new EventSource(url.toString());
+
+ eventSource.onmessage = (e) => {
+ try {
+ const event = JSON.parse(e.data) as DirectiveEvent;
+ onEvent(event);
+ } catch (err) {
+ console.error("Failed to parse SSE event:", err);
+ }
+ };
+
+ eventSource.onerror = (_e) => {
+ if (onError) {
+ onError(new Error("SSE connection error"));
+ }
+ };
+
+ // Return cleanup function
+ return () => {
+ eventSource.close();
+ };
+}