diff options
Diffstat (limited to 'makima/frontend/src')
18 files changed, 0 insertions, 2943 deletions
diff --git a/makima/frontend/src/components/NavStrip.tsx b/makima/frontend/src/components/NavStrip.tsx index 9bb7777..fb95c7f 100644 --- a/makima/frontend/src/components/NavStrip.tsx +++ b/makima/frontend/src/components/NavStrip.tsx @@ -10,7 +10,6 @@ interface NavLink { const NAV_LINKS: NavLink[] = [ { label: "Listen", href: "/listen" }, - { label: "Directives", href: "/directives", requiresAuth: true }, { label: "Contracts", href: "/contracts", requiresAuth: true }, { label: "Board", href: "/workflow", requiresAuth: true }, { label: "Mesh", href: "/mesh", requiresAuth: true }, diff --git a/makima/frontend/src/components/directives/ApprovalsTab.tsx b/makima/frontend/src/components/directives/ApprovalsTab.tsx deleted file mode 100644 index dca48df..0000000 --- a/makima/frontend/src/components/directives/ApprovalsTab.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import type { DirectiveWithProgress } from "../../lib/api"; - -export function ApprovalsTab({ directive, onRefresh }: { directive: DirectiveWithProgress; onRefresh: () => void }) { - if (directive.pendingApprovals.length === 0) { - return ( - <div className="text-center py-8"> - <p className="font-mono text-sm text-[#556677]">No pending approvals</p> - </div> - ); - } - - const handleApprove = async (approvalId: string) => { - try { - const { approveDirectiveRequest } = await import("../../lib/api"); - await approveDirectiveRequest(directive.id, approvalId); - onRefresh(); - } catch (err) { - console.error("Failed to approve:", err); - } - }; - - const handleDeny = async (approvalId: string) => { - try { - const { denyDirectiveRequest } = await import("../../lib/api"); - await denyDirectiveRequest(directive.id, approvalId); - onRefresh(); - } catch (err) { - console.error("Failed to deny:", err); - } - }; - - return ( - <div className="space-y-3"> - {directive.pendingApprovals.map((approval) => { - const urgencyColor = { - low: "text-[#556677]", - normal: "text-[#75aafc]", - high: "text-yellow-400", - critical: "text-red-400", - }[approval.urgency] || "text-[#556677]"; - - return ( - <div - key={approval.id} - className="p-4 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.2)]" - > - <div className="flex items-start justify-between"> - <div> - <div className="flex items-center gap-2"> - <span className="font-mono text-sm text-[#dbe7ff]">{approval.approvalType}</span> - <span className={`font-mono text-[10px] uppercase ${urgencyColor}`}> - {approval.urgency} - </span> - </div> - <p className="font-mono text-xs text-[#9bc3ff] mt-1">{approval.description}</p> - </div> - <div className="flex gap-2"> - <button - onClick={() => handleApprove(approval.id)} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-green-700 border border-green-600 hover:bg-green-600 uppercase" - > - Approve - </button> - <button - onClick={() => handleDeny(approval.id)} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-red-700 border border-red-600 hover:bg-red-600 uppercase" - > - Deny - </button> - </div> - </div> - </div> - ); - })} - </div> - ); -} diff --git a/makima/frontend/src/components/directives/ChainTab.tsx b/makima/frontend/src/components/directives/ChainTab.tsx deleted file mode 100644 index ccefe81..0000000 --- a/makima/frontend/src/components/directives/ChainTab.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { useState, useMemo } from "react"; -import { - ReactFlow, - Edge, - Controls, - Background, - BackgroundVariant, - MarkerType, -} from "@xyflow/react"; -import "@xyflow/react/dist/style.css"; -import type { DirectiveWithProgress, DirectiveGraphResponse } from "../../lib/api"; -import { StepNodeComponent, stepStatusStyles } from "./StepNode"; - -// Node types for React Flow -const nodeTypes = { - step: StepNodeComponent, -}; - -export function ChainTab({ directive, graph }: { directive: DirectiveWithProgress; graph: DirectiveGraphResponse | null }) { - const [viewMode, setViewMode] = useState<"dag" | "list">("dag"); - - // Convert graph to React Flow nodes and edges - const { nodes, edges } = useMemo(() => { - if (!graph || !graph.nodes.length) { - // Fallback: generate positions from directive.steps - const stepNodes = directive.steps.map((step, index) => ({ - id: step.id, - type: "step" as const, - position: { - x: (index % 3) * 220 + 50, - y: Math.floor(index / 3) * 120 + 50, - }, - data: { - id: step.id, - name: step.name, - stepType: step.stepType, - status: step.status, - confidenceScore: step.confidenceScore, - confidenceLevel: step.confidenceLevel, - contractId: step.contractId, - editorX: null, - editorY: null, - }, - })); - - // Build edges from dependencies - const stepEdges: Edge[] = []; - directive.steps.forEach((step) => { - (step.dependsOn ?? []).forEach((depName) => { - const depStep = directive.steps.find((s) => s.name === depName); - if (depStep) { - stepEdges.push({ - id: `${depStep.id}-${step.id}`, - source: depStep.id, - target: step.id, - type: "smoothstep", - markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" }, - style: { stroke: "#556677", strokeWidth: 2 }, - }); - } - }); - }); - - return { nodes: stepNodes, edges: stepEdges }; - } - - // Use graph data - const graphNodes = graph.nodes.map((node) => ({ - id: node.id, - type: "step" as const, - position: { - x: node.editorX ?? 50, - y: node.editorY ?? 50, - }, - data: { ...node }, - })); - - const graphEdges: Edge[] = graph.edges.map((edge) => ({ - id: `${edge.source}-${edge.target}`, - source: edge.source, - target: edge.target, - type: "smoothstep", - markerEnd: { type: MarkerType.ArrowClosed, color: "#556677" }, - style: { stroke: "#556677", strokeWidth: 2 }, - })); - - return { nodes: graphNodes, edges: graphEdges }; - }, [graph, directive.steps]); - - if (!directive.chain) { - return ( - <div className="text-center py-8"> - <p className="font-mono text-sm text-[#556677]"> - No chain generated yet. Start the directive to generate a chain. - </p> - </div> - ); - } - - return ( - <div className="space-y-4"> - {/* Chain info header */} - <div className="flex items-center justify-between p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]"> - <div> - <div className="font-mono text-sm text-[#dbe7ff]">{directive.chain.name}</div> - <div className="font-mono text-[10px] text-[#556677]">Generation {directive.chain.generation}</div> - </div> - <div className="flex items-center gap-4"> - <div className="font-mono text-xs text-[#556677]"> - {directive.chain.completedSteps}/{directive.chain.totalSteps} steps - </div> - {/* View toggle */} - <div className="flex border border-[rgba(117,170,252,0.2)]"> - <button - onClick={() => setViewMode("dag")} - className={`px-2 py-1 font-mono text-[10px] uppercase ${ - viewMode === "dag" - ? "bg-[rgba(117,170,252,0.2)] text-[#75aafc]" - : "text-[#556677] hover:text-[#75aafc]" - }`} - > - DAG - </button> - <button - onClick={() => setViewMode("list")} - className={`px-2 py-1 font-mono text-[10px] uppercase ${ - viewMode === "list" - ? "bg-[rgba(117,170,252,0.2)] text-[#75aafc]" - : "text-[#556677] hover:text-[#75aafc]" - }`} - > - List - </button> - </div> - </div> - </div> - - {viewMode === "dag" ? ( - /* DAG visualization */ - <div className="h-[400px] border border-[rgba(117,170,252,0.1)] rounded bg-[#050d18]"> - {directive.steps.length === 0 ? ( - <div className="flex items-center justify-center h-full"> - <p className="font-mono text-sm text-[#556677]">No steps in chain</p> - </div> - ) : ( - <ReactFlow - nodes={nodes} - edges={edges} - nodeTypes={nodeTypes} - fitView - fitViewOptions={{ padding: 0.2 }} - minZoom={0.5} - maxZoom={1.5} - defaultEdgeOptions={{ - type: "smoothstep", - style: { stroke: "#556677", strokeWidth: 2 }, - }} - > - <Background variant={BackgroundVariant.Dots} gap={20} size={1} color="#1a2a3a" /> - <Controls className="!bg-[#0a1628] !border-[rgba(117,170,252,0.2)]" /> - </ReactFlow> - )} - </div> - ) : ( - /* List view */ - <div className="space-y-2"> - <h3 className="font-mono text-xs text-[#75aafc] uppercase">Steps</h3> - {directive.steps.length === 0 ? ( - <p className="font-mono text-sm text-[#556677]">No steps in chain</p> - ) : ( - directive.steps.map((step) => { - const styles = stepStatusStyles[step.status] || stepStatusStyles.pending; - - return ( - <div - key={step.id} - className="p-3 bg-[rgba(117,170,252,0.02)] border border-[rgba(117,170,252,0.1)]" - > - <div className="flex items-center justify-between"> - <div className="flex items-center gap-2"> - <span className="font-mono text-sm text-[#dbe7ff]">{step.name}</span> - <span - className="px-1.5 py-0.5 font-mono text-[10px] uppercase border" - style={{ color: styles.text, borderColor: `${styles.border}50` }} - > - {step.status} - </span> - {step.confidenceScore !== null && ( - <span className="font-mono text-[10px] text-[#556677]"> - ({Math.round(step.confidenceScore * 100)}%) - </span> - )} - </div> - <div className="font-mono text-[10px] text-[#556677]">{step.stepType}</div> - </div> - {step.description && ( - <p className="font-mono text-xs text-[#556677] mt-1">{step.description}</p> - )} - {step.dependsOn?.length > 0 && ( - <div className="font-mono text-[10px] text-[#556677] mt-1"> - Depends on: {step.dependsOn.join(", ")} - </div> - )} - </div> - ); - }) - )} - </div> - )} - </div> - ); -} diff --git a/makima/frontend/src/components/directives/CreateDirectiveModal.tsx b/makima/frontend/src/components/directives/CreateDirectiveModal.tsx deleted file mode 100644 index 7f52a7e..0000000 --- a/makima/frontend/src/components/directives/CreateDirectiveModal.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { useState, useEffect } from "react"; -import type { AutonomyLevel, RepositoryHistoryEntry } from "../../lib/api"; -import { getRepositorySuggestions } from "../../lib/api"; - -interface CreateDirectiveModalProps { - onSubmit: (goal: string, repositoryUrl: string | undefined, autonomyLevel: AutonomyLevel) => void; - onCancel: () => void; -} - -export function CreateDirectiveModal({ onSubmit, onCancel }: CreateDirectiveModalProps) { - const [goal, setGoal] = useState(""); - const [repositoryUrl, setRepositoryUrl] = useState(""); - const [autonomyLevel, setAutonomyLevel] = useState<AutonomyLevel>("guardrails"); - const [suggestions, setSuggestions] = useState<RepositoryHistoryEntry[]>([]); - const [showSuggestions, setShowSuggestions] = useState(false); - - // Load suggestions - useEffect(() => { - getRepositorySuggestions("remote", undefined, 5) - .then((res) => { - setSuggestions(res.entries); - }) - .catch(() => { - setSuggestions([]); - }); - }, []); - - const handleSubmit = () => { - if (goal.trim()) { - onSubmit(goal.trim(), repositoryUrl.trim() || undefined, autonomyLevel); - } - }; - - return ( - <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"> - <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] max-h-[90vh] overflow-y-auto"> - <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4"> - Create Directive - </h3> - - <div className="space-y-4"> - {/* Goal */} - <div> - <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1"> - Goal * - </label> - <textarea - value={goal} - onChange={(e) => setGoal(e.target.value)} - placeholder="Describe what you want to accomplish..." - rows={3} - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none" - autoFocus - /> - </div> - - {/* Repository URL */} - <div> - <label className="block font-mono text-xs text-[#8b949e] uppercase mb-1"> - Repository URL (optional) - </label> - <div className="relative"> - <input - type="text" - value={repositoryUrl} - onChange={(e) => setRepositoryUrl(e.target.value)} - onFocus={() => suggestions.length > 0 && setShowSuggestions(true)} - onBlur={() => setTimeout(() => setShowSuggestions(false), 200)} - placeholder="https://github.com/owner/repo" - className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]" - /> - {showSuggestions && suggestions.length > 0 && ( - <div className="absolute top-full left-0 right-0 mt-1 border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto z-10"> - {suggestions.map((s) => ( - <button - key={s.id} - type="button" - onClick={() => { - setRepositoryUrl(s.repositoryUrl || ""); - setShowSuggestions(false); - }} - className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0" - > - <div className="text-[#9bc3ff] truncate">{s.name}</div> - <div className="text-[10px] text-[#556677] truncate">{s.repositoryUrl}</div> - </button> - ))} - </div> - )} - </div> - </div> - - {/* Autonomy Level */} - <div> - <label className="block font-mono text-xs text-[#8b949e] uppercase mb-2"> - Autonomy Level - </label> - <div className="flex gap-2"> - {(["full_auto", "guardrails", "manual"] as const).map((level) => ( - <button - key={level} - type="button" - onClick={() => setAutonomyLevel(level)} - className={`flex-1 px-3 py-2 font-mono text-xs uppercase ${ - autonomyLevel === level - ? "text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3]" - : "text-[#556677] border border-[rgba(117,170,252,0.2)] hover:border-[#3f6fb3]" - }`} - > - {level.replace("_", " ")} - </button> - ))} - </div> - <p className="font-mono text-[10px] text-[#556677] mt-1"> - {autonomyLevel === "full_auto" && "Automatic progression without approval gates"} - {autonomyLevel === "guardrails" && "Request approval for yellow/red confidence scores"} - {autonomyLevel === "manual" && "Request approval for all step completions"} - </p> - </div> - - <p className="font-mono text-xs text-[#8b949e]"> - A directive is a top-level goal that generates a chain of steps. Each step spawns - contracts that are verified before progression. - </p> - - {/* Actions */} - <div className="flex gap-2 justify-end pt-2"> - <button - onClick={onCancel} - className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors" - > - Cancel - </button> - <button - onClick={handleSubmit} - disabled={!goal.trim()} - className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed" - > - Create - </button> - </div> - </div> - </div> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx deleted file mode 100644 index 06b24bb..0000000 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { useState } from "react"; -import type { DirectiveWithProgress, DirectiveGraphResponse } from "../../lib/api"; -import { OverviewTab } from "./OverviewTab"; -import { ChainTab } from "./ChainTab"; -import { EventsTab } from "./EventsTab"; -import { EvaluationsTab } from "./EvaluationsTab"; -import { ApprovalsTab } from "./ApprovalsTab"; -import { VerifiersTab } from "./VerifiersTab"; - -interface DirectiveDetailProps { - directive: DirectiveWithProgress; - graph: DirectiveGraphResponse | null; - loading: boolean; - onBack: () => void; - onRefresh: () => void; - onStart: () => void; - onPause: () => void; - onResume: () => void; - onStop: () => void; -} - -export function DirectiveDetail({ - directive, - graph, - loading, - onBack, - onRefresh, - onStart, - onPause, - onResume, - onStop, -}: DirectiveDetailProps) { - const [activeTab, setActiveTab] = useState<"overview" | "chain" | "events" | "evaluations" | "approvals" | "verifiers">("overview"); - - if (loading) { - return ( - <div className="panel h-full flex items-center justify-center"> - <p className="font-mono text-xs text-[#556677]">Loading...</p> - </div> - ); - } - - const statusColor = { - draft: "text-[#556677] bg-[#556677]/10 border-[#556677]/30", - planning: "text-yellow-400 bg-yellow-400/10 border-yellow-400/30", - active: "text-green-400 bg-green-400/10 border-green-400/30", - paused: "text-yellow-400 bg-yellow-400/10 border-yellow-400/30", - completed: "text-[#75aafc] bg-[#75aafc]/10 border-[#75aafc]/30", - archived: "text-[#556677] bg-[#556677]/10 border-[#556677]/30", - failed: "text-red-400 bg-red-400/10 border-red-400/30", - }[directive.status] || "text-[#556677] bg-[#556677]/10 border-[#556677]/30"; - - return ( - <div className="panel h-full flex flex-col overflow-hidden"> - {/* Header */} - <div className="p-3 border-b border-[rgba(117,170,252,0.15)]"> - <div className="flex items-center justify-between"> - <div className="flex items-center gap-3"> - <button - onClick={onBack} - className="font-mono text-xs text-[#556677] hover:text-[#9bc3ff]" - > - ← Back - </button> - <h2 className="font-mono text-sm text-[#dbe7ff]"> - {directive.title || directive.goal.slice(0, 50)} - </h2> - <span className={`px-2 py-0.5 font-mono text-[10px] uppercase border ${statusColor}`}> - {directive.status} - </span> - </div> - <div className="flex items-center gap-2"> - {directive.status === "draft" && ( - <button - onClick={onStart} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-green-700 border border-green-600 hover:bg-green-600 uppercase" - > - Start - </button> - )} - {directive.status === "active" && ( - <button - onClick={onPause} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-yellow-700 border border-yellow-600 hover:bg-yellow-600 uppercase" - > - Pause - </button> - )} - {directive.status === "paused" && ( - <button - onClick={onResume} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-green-700 border border-green-600 hover:bg-green-600 uppercase" - > - Resume - </button> - )} - {["active", "paused"].includes(directive.status) && ( - <button - onClick={onStop} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-red-700 border border-red-600 hover:bg-red-600 uppercase" - > - Stop - </button> - )} - <button - onClick={onRefresh} - className="px-3 py-1 font-mono text-[10px] text-[#9bc3ff] hover:text-[#dbe7ff]" - > - Refresh - </button> - </div> - </div> - </div> - - {/* Tabs */} - <div className="flex gap-1 p-2 border-b border-[rgba(117,170,252,0.1)]"> - {(["overview", "chain", "events", "evaluations", "approvals", "verifiers"] as const).map((tab) => ( - <button - key={tab} - onClick={() => setActiveTab(tab)} - className={`px-3 py-1.5 font-mono text-[10px] uppercase ${ - activeTab === tab - ? "text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3]" - : "text-[#556677] hover:text-[#9bc3ff]" - }`} - > - {tab} - {tab === "approvals" && directive.pendingApprovals.length > 0 && ( - <span className="ml-1 px-1 bg-yellow-500 text-black rounded text-[8px]"> - {directive.pendingApprovals.length} - </span> - )} - </button> - ))} - </div> - - {/* Tab Content */} - <div className="flex-1 overflow-y-auto p-4"> - {activeTab === "overview" && ( - <OverviewTab directive={directive} /> - )} - {activeTab === "chain" && ( - <ChainTab directive={directive} graph={graph} /> - )} - {activeTab === "events" && ( - <EventsTab directive={directive} /> - )} - {activeTab === "evaluations" && ( - <EvaluationsTab directive={directive} /> - )} - {activeTab === "approvals" && ( - <ApprovalsTab directive={directive} onRefresh={onRefresh} /> - )} - {activeTab === "verifiers" && ( - <VerifiersTab directive={directive} /> - )} - </div> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/DirectiveList.tsx b/makima/frontend/src/components/directives/DirectiveList.tsx deleted file mode 100644 index d0371e0..0000000 --- a/makima/frontend/src/components/directives/DirectiveList.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useState } from "react"; -import type { DirectiveSummary } from "../../lib/api"; -import { DirectiveListItem } from "./DirectiveListItem"; - -interface DirectiveListProps { - directives: DirectiveSummary[]; - loading: boolean; - onSelect: (id: string) => void; - onCreate: () => void; - selectedId?: string; - onArchive: (directive: DirectiveSummary) => void; -} - -export function DirectiveList({ - directives, - loading, - onSelect, - onCreate, - selectedId, - onArchive, -}: DirectiveListProps) { - const [filter, setFilter] = useState<"all" | "active" | "completed" | "failed">("all"); - - const filteredDirectives = directives.filter((d) => { - if (filter === "all") return true; - if (filter === "active") return ["draft", "planning", "active", "paused"].includes(d.status); - if (filter === "completed") return d.status === "completed"; - if (filter === "failed") return d.status === "failed"; - return true; - }); - - return ( - <div className="panel h-full flex flex-col overflow-hidden"> - <div className="flex items-center justify-between p-3 border-b border-[rgba(117,170,252,0.15)]"> - <h2 className="font-mono text-sm text-[#75aafc] uppercase">Directives</h2> - <button - onClick={onCreate} - className="px-3 py-1 font-mono text-[10px] text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" - > - + New - </button> - </div> - - {/* Filters */} - <div className="flex gap-1 p-2 border-b border-[rgba(117,170,252,0.1)]"> - {(["all", "active", "completed", "failed"] as const).map((f) => ( - <button - key={f} - onClick={() => setFilter(f)} - className={`px-2 py-1 font-mono text-[10px] uppercase ${ - filter === f - ? "text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3]" - : "text-[#556677] hover:text-[#9bc3ff]" - }`} - > - {f} - </button> - ))} - </div> - - {/* List */} - <div className="flex-1 overflow-y-auto"> - {loading ? ( - <div className="p-4 text-center"> - <p className="font-mono text-xs text-[#556677]">Loading...</p> - </div> - ) : filteredDirectives.length === 0 ? ( - <div className="p-4 text-center"> - <p className="font-mono text-xs text-[#556677]">No directives found</p> - </div> - ) : ( - filteredDirectives.map((d) => ( - <DirectiveListItem - key={d.id} - directive={d} - selected={d.id === selectedId} - onClick={() => onSelect(d.id)} - onArchive={() => onArchive(d)} - /> - )) - )} - </div> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/DirectiveListItem.tsx b/makima/frontend/src/components/directives/DirectiveListItem.tsx deleted file mode 100644 index 6ff82e4..0000000 --- a/makima/frontend/src/components/directives/DirectiveListItem.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import type { DirectiveSummary } from "../../lib/api"; - -interface DirectiveListItemProps { - directive: DirectiveSummary; - selected: boolean; - onClick: () => void; - onArchive: () => void; -} - -export function DirectiveListItem({ directive, selected, onClick, onArchive }: DirectiveListItemProps) { - const progress = directive.totalSteps > 0 - ? Math.round((directive.completedSteps / directive.totalSteps) * 100) - : 0; - - const statusColor = { - draft: "text-[#556677]", - planning: "text-yellow-400", - active: "text-green-400", - paused: "text-yellow-400", - completed: "text-[#75aafc]", - archived: "text-[#556677]", - failed: "text-red-400", - }[directive.status] || "text-[#556677]"; - - const confidenceColor = { - green: "bg-green-500", - yellow: "bg-yellow-500", - red: "bg-red-500", - }[directive.currentConfidence !== null && directive.currentConfidence >= 0.8 - ? "green" - : directive.currentConfidence !== null && directive.currentConfidence >= 0.5 - ? "yellow" - : "red"] || "bg-[#556677]"; - - return ( - <div - onClick={onClick} - className={`p-3 cursor-pointer border-b border-[rgba(117,170,252,0.1)] hover:bg-[rgba(117,170,252,0.05)] ${ - selected ? "bg-[rgba(117,170,252,0.1)]" : "" - }`} - > - <div className="flex items-start justify-between gap-2"> - <div className="flex-1 min-w-0"> - <div className="font-mono text-sm text-[#dbe7ff] truncate"> - {directive.title || directive.goal.slice(0, 50)} - </div> - <div className="flex items-center gap-2 mt-1"> - <span className={`font-mono text-[10px] uppercase ${statusColor}`}> - {directive.status} - </span> - <span className="font-mono text-[10px] text-[#556677]"> - {directive.completedSteps}/{directive.totalSteps} steps - </span> - </div> - </div> - <div className="flex flex-col items-end gap-1"> - {directive.currentConfidence !== null && ( - <div className={`w-2 h-2 rounded-full ${confidenceColor}`} title={`Confidence: ${Math.round(directive.currentConfidence * 100)}%`} /> - )} - <button - onClick={(e) => { - e.stopPropagation(); - onArchive(); - }} - className="font-mono text-[10px] text-[#556677] hover:text-red-400" - > - Archive - </button> - </div> - </div> - - {/* Progress bar */} - {directive.totalSteps > 0 && ( - <div className="mt-2 h-1 bg-[rgba(117,170,252,0.1)] overflow-hidden"> - <div - className="h-full bg-[#75aafc] transition-all duration-300" - style={{ width: `${progress}%` }} - /> - </div> - )} - </div> - ); -} diff --git a/makima/frontend/src/components/directives/EvaluationsTab.tsx b/makima/frontend/src/components/directives/EvaluationsTab.tsx deleted file mode 100644 index c1d65db..0000000 --- a/makima/frontend/src/components/directives/EvaluationsTab.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { DirectiveWithProgress } from "../../lib/api"; - -export function EvaluationsTab({ directive: _directive }: { directive: DirectiveWithProgress }) { - // TODO: Fetch evaluations separately - return ( - <div className="text-center py-8"> - <p className="font-mono text-sm text-[#556677]"> - Evaluations will be shown here after steps are evaluated - </p> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/EventsTab.tsx b/makima/frontend/src/components/directives/EventsTab.tsx deleted file mode 100644 index 4dd739a..0000000 --- a/makima/frontend/src/components/directives/EventsTab.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useMemo } from "react"; -import type { DirectiveWithProgress } from "../../lib/api"; -import { useDirectiveEventSubscription } from "../../hooks/useDirectives"; - -export function EventsTab({ directive }: { directive: DirectiveWithProgress }) { - // Subscribe to real-time events via SSE - const { events: streamEvents, isConnected, error: sseError } = useDirectiveEventSubscription(directive.id); - - // Combine initial events with streamed events (avoiding duplicates) - const allEvents = useMemo(() => { - const eventMap = new Map(); - // Add initial events first - directive.recentEvents.forEach((e) => eventMap.set(e.id, e)); - // Add streamed events (will override any duplicates) - streamEvents.forEach((e) => eventMap.set(e.id, e)); - // Sort by created_at descending (most recent first) - return Array.from(eventMap.values()).sort( - (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ); - }, [directive.recentEvents, streamEvents]); - - return ( - <div className="space-y-4"> - {/* Connection status */} - <div className="flex items-center justify-between text-[10px] font-mono"> - <div className="flex items-center gap-2"> - <span className={isConnected ? "text-green-400" : "text-[#556677]"}> - {isConnected ? "\u25CF Live" : "\u25CB Connecting..."} - </span> - {sseError && <span className="text-red-400">{sseError}</span>} - </div> - <span className="text-[#556677]">{allEvents.length} events</span> - </div> - - {/* Event list */} - {allEvents.length === 0 ? ( - <div className="text-center py-8"> - <p className="font-mono text-sm text-[#556677]">No events yet</p> - </div> - ) : ( - <div className="space-y-2"> - {allEvents.map((event) => { - const severityColors: Record<string, string> = { - info: "text-[#75aafc]", - warning: "text-yellow-400", - error: "text-red-400", - critical: "text-red-600", - }; - const severityColor = severityColors[event.severity] || "text-[#556677]"; - - return ( - <div - key={event.id} - className="p-3 bg-[rgba(117,170,252,0.02)] border border-[rgba(117,170,252,0.1)]" - > - <div className="flex items-center justify-between"> - <div className="flex items-center gap-2"> - <span className={`font-mono text-xs ${severityColor}`}>{event.eventType}</span> - <span className="font-mono text-[10px] text-[#556677]">{event.actorType}</span> - </div> - <span className="font-mono text-[10px] text-[#556677]"> - {new Date(event.createdAt).toLocaleString()} - </span> - </div> - {event.eventData != null && ( - <pre className="font-mono text-[10px] text-[#556677] mt-1 overflow-x-auto"> - {JSON.stringify(event.eventData, null, 2)} - </pre> - )} - </div> - ); - })} - </div> - )} - </div> - ); -} diff --git a/makima/frontend/src/components/directives/OverviewTab.tsx b/makima/frontend/src/components/directives/OverviewTab.tsx deleted file mode 100644 index 41cd7dc..0000000 --- a/makima/frontend/src/components/directives/OverviewTab.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import type { DirectiveWithProgress } from "../../lib/api"; - -export function OverviewTab({ directive }: { directive: DirectiveWithProgress }) { - return ( - <div className="space-y-6"> - {/* Goal */} - <div> - <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Goal</h3> - <p className="font-mono text-sm text-[#dbe7ff] whitespace-pre-wrap"> - {directive.goal} - </p> - </div> - - {/* Progress */} - <div> - <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Progress</h3> - <div className="grid grid-cols-3 gap-4"> - <div className="p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]"> - <div className="font-mono text-2xl text-[#dbe7ff]"> - {directive.chain?.completedSteps || 0} - </div> - <div className="font-mono text-[10px] text-[#556677] uppercase">Completed Steps</div> - </div> - <div className="p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]"> - <div className="font-mono text-2xl text-[#dbe7ff]"> - {directive.chain?.totalSteps || 0} - </div> - <div className="font-mono text-[10px] text-[#556677] uppercase">Total Steps</div> - </div> - <div className="p-3 bg-[rgba(117,170,252,0.05)] border border-[rgba(117,170,252,0.15)]"> - <div className="font-mono text-2xl text-[#dbe7ff]"> - {directive.chain?.currentConfidence != null - ? `${Math.round((directive.chain?.currentConfidence ?? 0) * 100)}%` - : "-"} - </div> - <div className="font-mono text-[10px] text-[#556677] uppercase">Confidence</div> - </div> - </div> - </div> - - {/* Configuration */} - <div> - <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Configuration</h3> - <div className="grid grid-cols-2 gap-2 text-sm"> - <div className="flex justify-between"> - <span className="font-mono text-[#556677]">Autonomy Level</span> - <span className="font-mono text-[#dbe7ff]">{directive.autonomyLevel}</span> - </div> - <div className="flex justify-between"> - <span className="font-mono text-[#556677]">Max Rework Cycles</span> - <span className="font-mono text-[#dbe7ff]">{directive.maxReworkCycles}</span> - </div> - <div className="flex justify-between"> - <span className="font-mono text-[#556677]">Green Threshold</span> - <span className="font-mono text-[#dbe7ff]">{directive.confidenceThresholdGreen}</span> - </div> - <div className="flex justify-between"> - <span className="font-mono text-[#556677]">Yellow Threshold</span> - <span className="font-mono text-[#dbe7ff]">{directive.confidenceThresholdYellow}</span> - </div> - </div> - </div> - - {/* Repository */} - {directive.repositoryUrl && ( - <div> - <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">Repository</h3> - <p className="font-mono text-sm text-[#9bc3ff]">{directive.repositoryUrl}</p> - </div> - )} - </div> - ); -} diff --git a/makima/frontend/src/components/directives/StepNode.tsx b/makima/frontend/src/components/directives/StepNode.tsx deleted file mode 100644 index e54f5eb..0000000 --- a/makima/frontend/src/components/directives/StepNode.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Handle, Position } from "@xyflow/react"; -import type { StepStatus, ConfidenceLevel, DirectiveGraphNode } from "../../lib/api"; - -// Step status colors for both list and DAG views -export const stepStatusStyles: Record<StepStatus, { border: string; bg: string; text: string }> = { - pending: { border: "#556677", bg: "#556677", text: "#556677" }, - ready: { border: "#3b82f6", bg: "#3b82f6", text: "#3b82f6" }, - running: { border: "#22c55e", bg: "#22c55e", text: "#22c55e" }, - evaluating: { border: "#eab308", bg: "#eab308", text: "#eab308" }, - passed: { border: "#75aafc", bg: "#75aafc", text: "#75aafc" }, - failed: { border: "#ef4444", bg: "#ef4444", text: "#ef4444" }, - rework: { border: "#f97316", bg: "#f97316", text: "#f97316" }, - skipped: { border: "#556677", bg: "#556677", text: "#556677" }, - blocked: { border: "#ef4444", bg: "#ef4444", text: "#ef4444" }, -}; - -// Confidence level colors -export const confidenceColors: Record<ConfidenceLevel, string> = { - green: "#22c55e", - yellow: "#eab308", - red: "#ef4444", -}; - -// Node dimensions -export const NODE_WIDTH = 180; -export const NODE_HEIGHT = 70; - -// Custom node component for steps -export function StepNodeComponent({ data }: { data: DirectiveGraphNode & { selected?: boolean } }) { - const styles = stepStatusStyles[data.status] || stepStatusStyles.pending; - const isRunning = data.status === "running" || data.status === "evaluating"; - - return ( - <div - className={`rounded-lg border-2 bg-[#0a1628] overflow-hidden ${ - isRunning ? "animate-pulse" : "" - }`} - style={{ - width: NODE_WIDTH, - height: NODE_HEIGHT, - borderColor: styles.border, - borderStyle: data.status === "pending" ? "dashed" : "solid", - }} - > - <Handle - type="target" - position={Position.Top} - className="!bg-[#75aafc] !w-3 !h-3 !border-2 !border-[#0a1628]" - /> - - {/* Status indicator bar */} - <div className="h-1.5" style={{ backgroundColor: styles.bg }} /> - - {/* Content */} - <div className="p-2"> - <div className="flex items-center justify-between mb-1"> - <span className="font-mono text-xs text-[#dbe7ff] truncate flex-1">{data.name}</span> - {data.confidenceScore !== null && data.confidenceLevel && ( - <div - className="w-2 h-2 rounded-full flex-shrink-0 ml-1" - style={{ backgroundColor: confidenceColors[data.confidenceLevel] }} - title={`Confidence: ${Math.round(data.confidenceScore * 100)}%`} - /> - )} - </div> - <div className="flex items-center justify-between"> - <span - className="font-mono text-[10px] uppercase px-1.5 py-0.5 rounded" - style={{ - color: styles.text, - backgroundColor: `${styles.bg}20`, - }} - > - {data.status} - </span> - <span className="font-mono text-[10px] text-[#8b949e]">{data.stepType}</span> - </div> - </div> - - <Handle - type="source" - position={Position.Bottom} - className="!bg-[#f59e0b] !w-3 !h-3 !border-2 !border-[#0a1628]" - /> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/VerifiersTab.tsx b/makima/frontend/src/components/directives/VerifiersTab.tsx deleted file mode 100644 index cfcfdd8..0000000 --- a/makima/frontend/src/components/directives/VerifiersTab.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { DirectiveWithProgress } from "../../lib/api"; - -export function VerifiersTab({ directive: _directive }: { directive: DirectiveWithProgress }) { - // TODO: Fetch verifiers separately - return ( - <div className="text-center py-8"> - <p className="font-mono text-sm text-[#556677]"> - Verifiers will be shown here. Use auto-detect to find available verifiers. - </p> - </div> - ); -} diff --git a/makima/frontend/src/components/directives/index.ts b/makima/frontend/src/components/directives/index.ts deleted file mode 100644 index 718b1f2..0000000 --- a/makima/frontend/src/components/directives/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export { DirectiveList } from "./DirectiveList"; -export { DirectiveListItem } from "./DirectiveListItem"; -export { DirectiveDetail } from "./DirectiveDetail"; -export { OverviewTab } from "./OverviewTab"; -export { ChainTab } from "./ChainTab"; -export { EventsTab } from "./EventsTab"; -export { EvaluationsTab } from "./EvaluationsTab"; -export { ApprovalsTab } from "./ApprovalsTab"; -export { VerifiersTab } from "./VerifiersTab"; -export { CreateDirectiveModal } from "./CreateDirectiveModal"; -export { StepNodeComponent, stepStatusStyles, confidenceColors, NODE_WIDTH, NODE_HEIGHT } from "./StepNode"; diff --git a/makima/frontend/src/hooks/useDirectiveDetail.ts b/makima/frontend/src/hooks/useDirectiveDetail.ts deleted file mode 100644 index 1167242..0000000 --- a/makima/frontend/src/hooks/useDirectiveDetail.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { useState, useCallback, useEffect } from "react"; -import { - getDirective, - getDirectiveGraph, - startDirective, - pauseDirective, - resumeDirective, - stopDirective, - type DirectiveWithProgress, - type DirectiveGraphResponse, - type StartDirectiveResponse, -} from "../lib/api"; - -interface UseDirectiveDetailResult { - directive: DirectiveWithProgress | null; - graph: DirectiveGraphResponse | null; - loading: boolean; - error: string | null; - refresh: () => Promise<void>; - start: () => Promise<StartDirectiveResponse | null>; - pause: () => Promise<boolean>; - resume: () => Promise<boolean>; - stop: () => Promise<boolean>; -} - -export function useDirectiveDetail(directiveId: string | undefined): UseDirectiveDetailResult { - const [directive, setDirective] = useState<DirectiveWithProgress | null>(null); - const [graph, setGraph] = useState<DirectiveGraphResponse | null>(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState<string | null>(null); - - const fetchDetail = useCallback(async () => { - if (!directiveId) { - setDirective(null); - setGraph(null); - return; - } - - setLoading(true); - setError(null); - try { - const [d, g] = await Promise.all([ - getDirective(directiveId), - getDirectiveGraph(directiveId).catch(() => null), - ]); - setDirective(d); - setGraph(g); - } catch (err) { - console.error("Failed to fetch directive detail:", err); - setError(err instanceof Error ? err.message : "Failed to fetch directive"); - setDirective(null); - setGraph(null); - } finally { - setLoading(false); - } - }, [directiveId]); - - useEffect(() => { - fetchDetail(); - }, [fetchDetail]); - - const start = useCallback(async (): Promise<StartDirectiveResponse | null> => { - if (!directiveId) return null; - try { - const response = await startDirective(directiveId); - await fetchDetail(); - return response; - } catch (err) { - console.error("Failed to start directive:", err); - setError(err instanceof Error ? err.message : "Failed to start directive"); - return null; - } - }, [directiveId, fetchDetail]); - - const pause = useCallback(async (): Promise<boolean> => { - if (!directiveId) return false; - try { - await pauseDirective(directiveId); - await fetchDetail(); - return true; - } catch (err) { - console.error("Failed to pause directive:", err); - setError(err instanceof Error ? err.message : "Failed to pause directive"); - return false; - } - }, [directiveId, fetchDetail]); - - const resume = useCallback(async (): Promise<boolean> => { - if (!directiveId) return false; - try { - await resumeDirective(directiveId); - await fetchDetail(); - return true; - } catch (err) { - console.error("Failed to resume directive:", err); - setError(err instanceof Error ? err.message : "Failed to resume directive"); - return false; - } - }, [directiveId, fetchDetail]); - - const stop = useCallback(async (): Promise<boolean> => { - if (!directiveId) return false; - try { - await stopDirective(directiveId); - await fetchDetail(); - return true; - } catch (err) { - console.error("Failed to stop directive:", err); - setError(err instanceof Error ? err.message : "Failed to stop directive"); - return false; - } - }, [directiveId, fetchDetail]); - - return { - directive, - graph, - loading, - error, - refresh: fetchDetail, - start, - pause, - resume, - stop, - }; -} diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts deleted file mode 100644 index 7ae24a5..0000000 --- a/makima/frontend/src/hooks/useDirectives.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { useState, useCallback, useEffect, useRef } from "react"; -import { - listDirectives, - getDirective, - createDirective, - updateDirective, - archiveDirective, - startDirective, - pauseDirective, - resumeDirective, - stopDirective, - getDirectiveGraph, - subscribeToDirectiveEvents, - type DirectiveSummary, - type DirectiveWithProgress, - type DirectiveGraphResponse, - type DirectiveStatus, - type DirectiveEvent, - type CreateDirectiveRequest, - type UpdateDirectiveRequest, - type StartDirectiveResponse, -} from "../lib/api"; - -interface UseDirectivesResult { - directives: DirectiveSummary[]; - loading: boolean; - error: string | null; - refresh: () => Promise<void>; - createNewDirective: (req: CreateDirectiveRequest) => Promise<DirectiveWithProgress | null>; - updateExistingDirective: ( - directiveId: string, - req: UpdateDirectiveRequest - ) => Promise<DirectiveWithProgress | null>; - archiveExistingDirective: (directiveId: string) => Promise<boolean>; - getDirectiveById: (directiveId: string) => Promise<DirectiveWithProgress | null>; - getGraph: (directiveId: string) => Promise<DirectiveGraphResponse | null>; - start: (directiveId: string) => Promise<StartDirectiveResponse | null>; - pause: (directiveId: string) => Promise<boolean>; - resume: (directiveId: string) => Promise<boolean>; - stop: (directiveId: string) => Promise<boolean>; -} - -export function useDirectives(statusFilter?: DirectiveStatus): UseDirectivesResult { - const [directives, setDirectives] = useState<DirectiveSummary[]>([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState<string | null>(null); - - const fetchDirectives = useCallback(async () => { - setLoading(true); - setError(null); - try { - const response = await listDirectives(statusFilter); - setDirectives(response.directives ?? []); - } catch (err) { - console.error("Failed to fetch directives:", err); - setError(err instanceof Error ? err.message : "Failed to fetch directives"); - } finally { - setLoading(false); - } - }, [statusFilter]); - - useEffect(() => { - fetchDirectives(); - }, [fetchDirectives]); - - const createNewDirective = useCallback( - async (req: CreateDirectiveRequest): Promise<DirectiveWithProgress | null> => { - try { - const directive = await createDirective(req); - // Refresh the list - await fetchDirectives(); - // Return the full directive with progress - return await getDirective(directive.id); - } catch (err) { - console.error("Failed to create directive:", err); - setError(err instanceof Error ? err.message : "Failed to create directive"); - return null; - } - }, - [fetchDirectives] - ); - - const updateExistingDirective = useCallback( - async ( - directiveId: string, - req: UpdateDirectiveRequest - ): Promise<DirectiveWithProgress | null> => { - try { - await updateDirective(directiveId, req); - // Refresh the list - await fetchDirectives(); - // Return the updated directive - return await getDirective(directiveId); - } catch (err) { - console.error("Failed to update directive:", err); - setError(err instanceof Error ? err.message : "Failed to update directive"); - return null; - } - }, - [fetchDirectives] - ); - - const archiveExistingDirective = useCallback( - async (directiveId: string): Promise<boolean> => { - try { - await archiveDirective(directiveId); - // Refresh the list - await fetchDirectives(); - return true; - } catch (err) { - console.error("Failed to archive directive:", err); - setError(err instanceof Error ? err.message : "Failed to archive directive"); - return false; - } - }, - [fetchDirectives] - ); - - const getDirectiveById = useCallback( - async (directiveId: string): Promise<DirectiveWithProgress | null> => { - try { - return await getDirective(directiveId); - } catch (err) { - console.error("Failed to get directive:", err); - setError(err instanceof Error ? err.message : "Failed to get directive"); - return null; - } - }, - [] - ); - - const getGraph = useCallback( - async (directiveId: string): Promise<DirectiveGraphResponse | null> => { - try { - return await getDirectiveGraph(directiveId); - } catch (err) { - console.error("Failed to get directive graph:", err); - setError(err instanceof Error ? err.message : "Failed to get directive graph"); - return null; - } - }, - [] - ); - - const start = useCallback( - async (directiveId: string): Promise<StartDirectiveResponse | null> => { - try { - const response = await startDirective(directiveId); - await fetchDirectives(); - return response; - } catch (err) { - console.error("Failed to start directive:", err); - setError(err instanceof Error ? err.message : "Failed to start directive"); - return null; - } - }, - [fetchDirectives] - ); - - const pause = useCallback( - async (directiveId: string): Promise<boolean> => { - try { - await pauseDirective(directiveId); - await fetchDirectives(); - return true; - } catch (err) { - console.error("Failed to pause directive:", err); - setError(err instanceof Error ? err.message : "Failed to pause directive"); - return false; - } - }, - [fetchDirectives] - ); - - const resume = useCallback( - async (directiveId: string): Promise<boolean> => { - try { - await resumeDirective(directiveId); - await fetchDirectives(); - return true; - } catch (err) { - console.error("Failed to resume directive:", err); - setError(err instanceof Error ? err.message : "Failed to resume directive"); - return false; - } - }, - [fetchDirectives] - ); - - const stop = useCallback( - async (directiveId: string): Promise<boolean> => { - try { - await stopDirective(directiveId); - await fetchDirectives(); - return true; - } catch (err) { - console.error("Failed to stop directive:", err); - setError(err instanceof Error ? err.message : "Failed to stop directive"); - return false; - } - }, - [fetchDirectives] - ); - - return { - directives, - loading, - error, - refresh: fetchDirectives, - createNewDirective, - updateExistingDirective, - archiveExistingDirective, - getDirectiveById, - getGraph, - start, - pause, - resume, - stop, - }; -} - -/** Hook for subscribing to real-time directive events via SSE */ -export function useDirectiveEventSubscription( - directiveId: string | null, - onEvent?: (event: DirectiveEvent) => void -): { - events: DirectiveEvent[]; - isConnected: boolean; - error: string | null; -} { - const [events, setEvents] = useState<DirectiveEvent[]>([]); - const [isConnected, setIsConnected] = useState(false); - const [error, setError] = useState<string | null>(null); - const cleanupRef = useRef<(() => void) | null>(null); - - useEffect(() => { - // Clean up any existing subscription - if (cleanupRef.current) { - cleanupRef.current(); - cleanupRef.current = null; - } - - if (!directiveId) { - setIsConnected(false); - setEvents([]); - return; - } - - // Subscribe to events - let mounted = true; - - const setupSubscription = async () => { - try { - const cleanup = await subscribeToDirectiveEvents( - directiveId, - (event) => { - if (mounted) { - setEvents((prev) => [...prev, event]); - onEvent?.(event); - } - }, - (err) => { - if (mounted) { - setError(err.message); - setIsConnected(false); - } - } - ); - - if (mounted) { - cleanupRef.current = cleanup; - setIsConnected(true); - setError(null); - } else { - // Component unmounted during setup, clean up immediately - cleanup(); - } - } catch (err) { - if (mounted) { - setError(err instanceof Error ? err.message : "Failed to subscribe to events"); - setIsConnected(false); - } - } - }; - - setupSubscription(); - - return () => { - mounted = false; - if (cleanupRef.current) { - cleanupRef.current(); - cleanupRef.current = null; - } - }; - }, [directiveId, onEvent]); - - return { events, isConnected, error }; -} diff --git a/makima/frontend/src/lib/api.ts b/makima/frontend/src/lib/api.ts index 466a794..9f5ff88 100644 --- a/makima/frontend/src/lib/api.ts +++ b/makima/frontend/src/lib/api.ts @@ -3003,1286 +3003,3 @@ export async function listTaskPatches(taskId: string, contractId: string): Promi return res.json(); } -// ============================================================================= -// Chain Types and API -// ============================================================================= - -/** Chain status */ -export type ChainStatus = "pending" | "active" | "completed" | "archived"; - -/** Chain summary for list view */ -export interface ChainSummary { - id: string; - name: string; - description: string | null; - status: ChainStatus; - contractCount: number; - completedContractCount: number; - loopEnabled: boolean; - loopCurrentIteration: number | null; - loopMaxIterations: number | null; - createdAt: string; - updatedAt: string; -} - -/** Chain repository */ -export interface ChainRepository { - id: string; - chainId: string; - name: string; - repositoryUrl: string | null; - localPath: string | null; - sourceType: string; - status: string; - isPrimary: boolean; - createdAt: string; - updatedAt: string; -} - -/** Full chain with contracts */ -export interface Chain { - id: string; - ownerId: string; - name: string; - description: string | null; - status: ChainStatus; - loopEnabled: boolean; - loopMaxIterations: number | null; - loopCurrentIteration: number | null; - loopProgressCheck: string | null; - version: number; - createdAt: string; - updatedAt: string; -} - -/** Contract detail within a chain */ -export interface ChainContractDetail { - id: string; - chainId: string; - contractId: string; - contractName: string; - contractStatus: string; - contractPhase: string; - dependsOn: string[]; - orderIndex: number; - editorX: number | null; - editorY: number | null; - createdAt: string; -} - -/** Chain with contracts (chain fields are flattened via serde(flatten)) */ -export interface ChainWithContracts extends Chain { - contracts: ChainContractDetail[]; - repositories: ChainRepository[]; -} - -/** Node in chain graph visualization */ -export interface ChainGraphNode { - id: string; - contractId: string; - name: string; - status: string; - phase: string; - x: number; - y: number; -} - -/** Edge in chain graph */ -export interface ChainGraphEdge { - from: string; - to: string; -} - -/** Chain graph response */ -export interface ChainGraphResponse { - chainId: string; - chainName: string; - chainStatus: string; - nodes: ChainGraphNode[]; - edges: ChainGraphEdge[]; -} - -/** Chain event */ -export interface ChainEvent { - id: string; - chainId: string; - eventType: string; - contractId: string | null; - eventData: Record<string, unknown> | null; - createdAt: string; -} - -/** Chain list response */ -export interface ChainListResponse { - chains: ChainSummary[]; - total: number; -} - -/** Add chain repository request */ -export interface AddChainRepositoryRequest { - name: string; - repositoryUrl?: string; - localPath?: string; - sourceType?: string; - isPrimary?: boolean; -} - -/** Create chain request */ -export interface CreateChainRequest { - name: string; - description?: string; - repositoryUrl?: string; // Legacy field for backwards compatibility - repositories?: AddChainRepositoryRequest[]; - loopEnabled?: boolean; - loopMaxIterations?: number; - loopProgressCheck?: string; - contracts?: CreateChainContractRequest[]; -} - -/** Create chain contract request */ -export interface CreateChainContractRequest { - name: string; - description?: string; - contractType?: string; - initialPhase?: string; - phases?: string[]; - dependsOn?: string[]; - tasks?: { name: string; plan: string }[]; - deliverables?: { id: string; name: string; priority?: string }[]; - editorX?: number; - editorY?: number; -} - -/** Update chain request */ -export interface UpdateChainRequest { - name?: string; - description?: string; - status?: ChainStatus; - loopEnabled?: boolean; - loopMaxIterations?: number; - loopProgressCheck?: string; - version?: number; -} - -/** List chains */ -export async function listChains( - status?: ChainStatus, - limit = 50, - offset = 0 -): Promise<ChainListResponse> { - 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/chains?${params}`); - if (!res.ok) { - throw new Error(`Failed to list chains: ${res.statusText}`); - } - return res.json(); -} - -/** Get chain by ID */ -export async function getChain(chainId: string): Promise<ChainWithContracts> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`); - if (!res.ok) { - throw new Error(`Failed to get chain: ${res.statusText}`); - } - return res.json(); -} - -/** Create a new chain */ -export async function createChain(req: CreateChainRequest): Promise<Chain> { - const res = await authFetch(`${API_BASE}/api/v1/chains`, { - method: "POST", - body: JSON.stringify(req), - }); - if (!res.ok) { - const errorText = await res.text(); - throw new Error(`Failed to create chain: ${errorText || res.statusText}`); - } - return res.json(); -} - -/** Update a chain */ -export async function updateChain( - chainId: string, - req: UpdateChainRequest -): Promise<Chain> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`, { - method: "PUT", - body: JSON.stringify(req), - }); - if (!res.ok) { - const errorText = await res.text(); - throw new Error(`Failed to update chain: ${errorText || res.statusText}`); - } - return res.json(); -} - -/** Archive a chain */ -export async function archiveChain(chainId: string): Promise<{ archived: boolean }> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`, { - method: "DELETE", - }); - if (!res.ok) { - throw new Error(`Failed to archive chain: ${res.statusText}`); - } - return res.json(); -} - -/** Get chain contracts */ -export async function getChainContracts( - chainId: string -): Promise<ChainContractDetail[]> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/contracts`); - if (!res.ok) { - throw new Error(`Failed to get chain contracts: ${res.statusText}`); - } - return res.json(); -} - -/** Get chain graph for visualization */ -export async function getChainGraph(chainId: string): Promise<ChainGraphResponse> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/graph`); - if (!res.ok) { - throw new Error(`Failed to get chain graph: ${res.statusText}`); - } - return res.json(); -} - -/** Get chain events */ -export async function getChainEvents(chainId: string): Promise<ChainEvent[]> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/events`); - if (!res.ok) { - throw new Error(`Failed to get chain events: ${res.statusText}`); - } - return res.json(); -} - -// ============================================================================= -// Chain Contract Definitions -// ============================================================================= - -/** Task definition for chain contract definitions */ -export interface ChainTaskDefinition { - name: string; - plan: string; -} - -/** Deliverable definition for chain contract definitions (optional priority) */ -export interface ChainDeliverableDefinition { - id: string; - name: string; - priority?: string; -} - -/** Validation configuration for checkpoint contracts */ -export interface CheckpointValidation { - /** Check that all required deliverables from upstream contracts exist */ - checkDeliverables?: boolean; - /** Run tests in the repository */ - runTests?: boolean; - /** Custom validation instructions for Claude */ - checkContent?: string; - /** Action on failure: "block", "retry", "warn" */ - onFailure?: "block" | "retry" | "warn"; - /** Max retry attempts for upstream contracts */ - maxRetries?: number; -} - -/** Contract definition stored in chain (before actual contract is created) */ -export interface ChainContractDefinition { - id: string; - chainId: string; - name: string; - description: string | null; - contractType: string; - initialPhase: string | null; - dependsOnNames: string[]; - tasks: ChainTaskDefinition[] | null; - deliverables: ChainDeliverableDefinition[] | null; - /** Validation config for checkpoint contracts */ - validation: CheckpointValidation | null; - editorX: number | null; - editorY: number | null; - orderIndex: number; - createdAt: string; -} - -/** Request to add a contract definition to a chain */ -export interface AddContractDefinitionRequest { - name: string; - description?: string; - contractType?: string; - initialPhase?: string; - dependsOn?: string[]; - tasks?: ChainTaskDefinition[]; - deliverables?: ChainDeliverableDefinition[]; - /** Validation config (for checkpoint contracts) */ - validation?: CheckpointValidation; - editorX?: number; - editorY?: number; - orderIndex?: number; -} - -/** Request to update a contract definition */ -export interface UpdateContractDefinitionRequest { - name?: string; - description?: string; - contractType?: string; - initialPhase?: string; - dependsOn?: string[]; - tasks?: ChainTaskDefinition[]; - deliverables?: ChainDeliverableDefinition[]; - /** Validation config (for checkpoint contracts) */ - validation?: CheckpointValidation; - editorX?: number; - editorY?: number; - orderIndex?: number; -} - -/** Response when starting a chain */ -export interface StartChainResponse { - chainId: string; - contractsCreated: string[]; - status: string; -} - -/** Node in definition graph (shows definitions + instantiation status) */ -export interface ChainDefinitionGraphNode { - id: string; - name: string; - contractType: string; - x: number; - y: number; - isInstantiated: boolean; - contractId: string | null; - contractStatus: string | null; -} - -/** Definition graph response */ -export interface ChainDefinitionGraphResponse { - chainId: string; - chainName: string; - chainStatus: string; - nodes: ChainDefinitionGraphNode[]; - edges: ChainGraphEdge[]; -} - -/** List contract definitions for a chain */ -export async function listChainDefinitions( - chainId: string -): Promise<ChainContractDefinition[]> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/definitions`); - if (!res.ok) { - throw new Error(`Failed to list chain definitions: ${res.statusText}`); - } - return res.json(); -} - -/** Create a contract definition for a chain */ -export async function createChainDefinition( - chainId: string, - req: AddContractDefinitionRequest -): Promise<ChainContractDefinition> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/definitions`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - if (!res.ok) { - throw new Error(`Failed to create chain definition: ${res.statusText}`); - } - return res.json(); -} - -/** Update a contract definition */ -export async function updateChainDefinition( - chainId: string, - definitionId: string, - req: UpdateContractDefinitionRequest -): Promise<ChainContractDefinition> { - const res = await authFetch( - `${API_BASE}/api/v1/chains/${chainId}/definitions/${definitionId}`, - { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - } - ); - if (!res.ok) { - throw new Error(`Failed to update chain definition: ${res.statusText}`); - } - return res.json(); -} - -/** Delete a contract definition */ -export async function deleteChainDefinition( - chainId: string, - definitionId: string -): Promise<{ deleted: boolean }> { - const res = await authFetch( - `${API_BASE}/api/v1/chains/${chainId}/definitions/${definitionId}`, - { method: "DELETE" } - ); - if (!res.ok) { - throw new Error(`Failed to delete chain definition: ${res.statusText}`); - } - return res.json(); -} - -/** Get definition graph for a chain */ -export async function getChainDefinitionGraph( - chainId: string -): Promise<ChainDefinitionGraphResponse> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/definitions/graph`); - if (!res.ok) { - throw new Error(`Failed to get chain definition graph: ${res.statusText}`); - } - return res.json(); -} - -/** Start a chain (creates root contracts based on DAG) */ -export async function startChain(chainId: string): Promise<StartChainResponse> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/start`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({}), - }); - if (!res.ok) { - const error = await res.json().catch(() => ({ message: res.statusText })); - throw new Error(error.message || `Failed to start chain: ${res.statusText}`); - } - return res.json(); -} - -/** Stop a chain (marks as archived) */ -export async function stopChain(chainId: string): Promise<{ stopped: boolean; status: string }> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/stop`, { - method: "POST", - }); - if (!res.ok) { - const error = await res.json().catch(() => ({ message: res.statusText })); - throw new Error(error.message || `Failed to stop chain: ${res.statusText}`); - } - return res.json(); -} - -// ============================================================================ -// Chain Repository Operations -// ============================================================================ - -/** List repositories for a chain */ -export async function listChainRepositories(chainId: string): Promise<ChainRepository[]> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/repositories`); - if (!res.ok) { - throw new Error(`Failed to list chain repositories: ${res.statusText}`); - } - return res.json(); -} - -/** Add a repository to a chain */ -export async function addChainRepository( - chainId: string, - req: AddChainRepositoryRequest -): Promise<ChainRepository> { - const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/repositories`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - if (!res.ok) { - const error = await res.json().catch(() => ({ message: res.statusText })); - throw new Error(error.message || `Failed to add chain repository: ${res.statusText}`); - } - return res.json(); -} - -/** Delete a repository from a chain */ -export async function deleteChainRepository( - chainId: string, - repositoryId: string -): Promise<{ deleted: boolean }> { - const res = await authFetch( - `${API_BASE}/api/v1/chains/${chainId}/repositories/${repositoryId}`, - { method: "DELETE" } - ); - if (!res.ok) { - throw new Error(`Failed to delete chain repository: ${res.statusText}`); - } - return res.json(); -} - -/** Set a repository as primary for a chain */ -export async function setChainRepositoryPrimary( - chainId: string, - repositoryId: string -): Promise<ChainRepository> { - const res = await authFetch( - `${API_BASE}/api/v1/chains/${chainId}/repositories/${repositoryId}/primary`, - { method: "PUT" } - ); - if (!res.ok) { - throw new Error(`Failed to set chain repository as primary: ${res.statusText}`); - } - 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(); - }; -} diff --git a/makima/frontend/src/main.tsx b/makima/frontend/src/main.tsx index c90d292..50fffe4 100644 --- a/makima/frontend/src/main.tsx +++ b/makima/frontend/src/main.tsx @@ -12,7 +12,6 @@ import HomePage from "./routes/_index"; import ListenPage from "./routes/listen"; import FilesPage from "./routes/files"; import ContractsPage from "./routes/contracts"; -import DirectivesPage from "./routes/directives"; import WorkflowPage from "./routes/workflow"; import MeshPage from "./routes/mesh"; import HistoryPage from "./routes/history"; @@ -73,22 +72,6 @@ createRoot(document.getElementById("root")!).render( } /> <Route - path="/directives" - element={ - <ProtectedRoute> - <DirectivesPage /> - </ProtectedRoute> - } - /> - <Route - path="/directives/:id" - element={ - <ProtectedRoute> - <DirectivesPage /> - </ProtectedRoute> - } - /> - <Route path="/contracts/:id/files/:fileId" element={ <ProtectedRoute> diff --git a/makima/frontend/src/routes/directives.tsx b/makima/frontend/src/routes/directives.tsx deleted file mode 100644 index 90f0854..0000000 --- a/makima/frontend/src/routes/directives.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useState, useCallback, useEffect } from "react"; -import { useParams, useNavigate } from "react-router"; -import { Masthead } from "../components/Masthead"; -import { useDirectives } from "../hooks/useDirectives"; -import { useDirectiveDetail } from "../hooks/useDirectiveDetail"; -import { useAuth } from "../contexts/AuthContext"; -import type { - DirectiveSummary, - CreateDirectiveRequest, - AutonomyLevel, -} from "../lib/api"; -import { DirectiveList } from "../components/directives/DirectiveList"; -import { DirectiveDetail } from "../components/directives/DirectiveDetail"; -import { CreateDirectiveModal } from "../components/directives/CreateDirectiveModal"; - -export default function DirectivesPage() { - const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth(); - const navigate = useNavigate(); - - // Redirect to login if not authenticated (when auth is configured) - useEffect(() => { - if (!authLoading && isAuthConfigured && !isAuthenticated) { - navigate("/login"); - } - }, [authLoading, isAuthConfigured, isAuthenticated, navigate]); - - // Show loading while checking auth - if (authLoading) { - return ( - <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> - <Masthead showNav /> - <main className="flex-1 flex items-center justify-center"> - <p className="text-[#7788aa] font-mono text-sm">Loading...</p> - </main> - </div> - ); - } - - // Don't render if not authenticated (will redirect) - if (isAuthConfigured && !isAuthenticated) { - return null; - } - - return <DirectivesPageContent />; -} - -function DirectivesPageContent() { - const { id } = useParams<{ id: string }>(); - const navigate = useNavigate(); - const { - directives, - loading, - error, - createNewDirective, - archiveExistingDirective, - } = useDirectives(); - - const { - directive: directiveDetail, - graph: directiveGraph, - loading: detailLoading, - refresh: refreshDetail, - start: handleStart, - pause: handlePause, - resume: handleResume, - stop: handleStop, - } = useDirectiveDetail(id); - - const [isCreating, setIsCreating] = useState(false); - - const handleSelect = useCallback( - (directiveId: string) => { - navigate(`/directives/${directiveId}`); - }, - [navigate] - ); - - const handleBack = useCallback(() => { - navigate("/directives"); - }, [navigate]); - - const handleCreate = useCallback(() => { - setIsCreating(true); - }, []); - - const handleCreateSubmit = useCallback( - async (goal: string, repositoryUrl: string | undefined, autonomyLevel: AutonomyLevel) => { - const data: CreateDirectiveRequest = { - goal: goal.trim(), - repositoryUrl: repositoryUrl?.trim() || undefined, - autonomyLevel, - }; - - try { - const result = await createNewDirective(data); - if (result) { - setIsCreating(false); - navigate(`/directives/${result.id}`); - } - } catch (err) { - console.error("Failed to create directive:", err); - } - }, - [createNewDirective, navigate] - ); - - const handleCreateCancel = useCallback(() => { - setIsCreating(false); - }, []); - - const handleArchive = useCallback( - async (directive: DirectiveSummary) => { - if (confirm(`Are you sure you want to archive this directive?`)) { - const success = await archiveExistingDirective(directive.id); - if (success && directive.id === id) { - navigate("/directives"); - } - } - }, - [archiveExistingDirective, id, navigate] - ); - - return ( - <div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]"> - <Masthead showNav /> - <main className="flex-1 flex flex-col p-4 pt-2 gap-4 overflow-hidden"> - {error && ( - <div className="p-3 bg-red-400/10 border border-red-400/30 text-red-400 font-mono text-sm"> - {error} - </div> - )} - - {/* Create directive modal */} - {isCreating && ( - <CreateDirectiveModal - onSubmit={handleCreateSubmit} - onCancel={handleCreateCancel} - /> - )} - - <div className="flex-1 grid grid-cols-[350px_1fr] gap-4 min-h-0"> - {/* Directive list */} - <DirectiveList - directives={directives} - loading={loading} - onSelect={handleSelect} - onCreate={handleCreate} - selectedId={id} - onArchive={handleArchive} - /> - - {/* Directive detail or empty state */} - {directiveDetail ? ( - <DirectiveDetail - directive={directiveDetail} - graph={directiveGraph} - loading={detailLoading} - onBack={handleBack} - onRefresh={refreshDetail} - onStart={handleStart} - onPause={handlePause} - onResume={handleResume} - onStop={handleStop} - /> - ) : ( - <div className="panel h-full flex items-center justify-center"> - <div className="text-center"> - <p className="font-mono text-sm text-[#555] mb-4"> - Select a directive or create a new one - </p> - <button - onClick={handleCreate} - className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase" - > - + New Directive - </button> - </div> - </div> - )} - </div> - </main> - </div> - ); -} |
