From b1a54b6fc46c2235684a4b85e2bf8871ece68296 Mon Sep 17 00:00:00 2001 From: Sami Shukri Date: Mon, 15 Jun 2026 13:27:16 -0600 Subject: [PATCH 1/4] refactor(web-ui): repoint GraphQL codegen at new kernel schema (E1 baseline) Repoint codegen at the new full kernel SDL (embedded/schema.graphql) and get the project to a compiling, build-passing baseline with dead surface removed. Schema repoint: - All GraphQL operations rewritten to the new lean kernel schema: subject model (subject/subjectById/subjectNext, createSubject/updateSubject/ setSubjectStatus), QueueEntry.state + QueueStats, flat Workflow with `detail` JSON blob, DaemonHealth.plugins + daemon status + daemonAgents. codegen succeeds with 0 errors; generated client regenerated. - The new schema exposes NO subscription root: the events page is now a polled daemon-activity view (daemonAgents + daemonHealth) instead of a live subscription. Pages/operations DELETED (no backend in kernel): - projects (projects-pages, project-context, projects.graphql) - planning/vision/requirements-as-distinct (planning-screens, planning.graphql) - builder (builder-pages, builder.graphql) - reviews (review-page, reviews.graphql) - settings (settings-pages, settings.graphql: saveAgentProfile/workflowConfig) - errors-page, skills-page (no backing queries) - their router routes, nav entries, and tests Pages KEPT and updated to new schema (compile + render real data): - dashboard, tasks->subjects (+ /requirements via kind), workflows, queue, daemon, dispatch, events, agents, history, ops-map, output. - requirements now route to TasksPage with kind=requirement. - CustomDispatch now functional via executeWorkflow. Verification: codegen 0 errors; vitest 36/36 pass; vite build passes. Remaining tsc(tsconfig.app.json) errors are all pre-existing, unrelated patterns (shadcn Button asChild typing, implicit-any, ReactMarkdown className) not gated by the build. Several kept pages carry `// TODO(E-followup): richer rendering` for the later rich-rebuild pass. --- src/app/agent-page.tsx | 116 +- src/app/builder-pages.tsx | 2343 ---------------- src/app/daemon-page.test.tsx | 44 +- src/app/daemon-page.tsx | 136 +- src/app/dashboard-page.tsx | 163 +- src/app/dispatch-pages.tsx | 403 +-- src/app/errors-page.tsx | 218 -- src/app/events-page.tsx | 136 +- src/app/history-page.tsx | 113 +- src/app/ops-map-page.tsx | 37 +- src/app/output-page.tsx | 219 +- src/app/planning-screens.test.tsx | 291 -- src/app/planning-screens.tsx | 1045 ------- src/app/project-context.test.ts | 57 - src/app/project-context.tsx | 120 - src/app/projects-pages.tsx | 262 -- src/app/queue-page.tsx | 115 +- src/app/review-page.tsx | 90 - src/app/router.test.ts | 7 +- src/app/router.tsx | 115 +- src/app/screens.test.ts | 12 +- src/app/screens.test.tsx | 100 - src/app/settings-pages.tsx | 391 --- src/app/shell.tsx | 57 +- src/app/skills-page.tsx | 200 -- src/app/task-workflow-control-center.test.tsx | 82 +- src/app/tasks-pages.tsx | 660 ++--- src/app/workflow-pages.tsx | 668 +---- src/lib/graphql/generated/gql.ts | 72 +- src/lib/graphql/generated/graphql.ts | 2405 +++++------------ src/lib/graphql/operations/builder.graphql | 7 - src/lib/graphql/operations/daemon.graphql | 40 +- src/lib/graphql/operations/dashboard.graphql | 37 +- src/lib/graphql/operations/dispatch.graphql | 31 +- src/lib/graphql/operations/events.graphql | 16 +- src/lib/graphql/operations/planning.graphql | 40 - src/lib/graphql/operations/projects.graphql | 22 - src/lib/graphql/operations/queue.graphql | 47 +- src/lib/graphql/operations/reviews.graphql | 3 - src/lib/graphql/operations/settings.graphql | 13 - src/lib/graphql/operations/tasks.graphql | 126 +- src/lib/graphql/operations/workflows.graphql | 54 +- 42 files changed, 1765 insertions(+), 9348 deletions(-) delete mode 100644 src/app/builder-pages.tsx delete mode 100644 src/app/errors-page.tsx delete mode 100644 src/app/planning-screens.test.tsx delete mode 100644 src/app/planning-screens.tsx delete mode 100644 src/app/project-context.test.ts delete mode 100644 src/app/project-context.tsx delete mode 100644 src/app/projects-pages.tsx delete mode 100644 src/app/review-page.tsx delete mode 100644 src/app/screens.test.tsx delete mode 100644 src/app/settings-pages.tsx delete mode 100644 src/app/skills-page.tsx delete mode 100644 src/lib/graphql/operations/builder.graphql delete mode 100644 src/lib/graphql/operations/planning.graphql delete mode 100644 src/lib/graphql/operations/projects.graphql delete mode 100644 src/lib/graphql/operations/reviews.graphql delete mode 100644 src/lib/graphql/operations/settings.graphql diff --git a/src/app/agent-page.tsx b/src/app/agent-page.tsx index aa59a5f..ef48dbf 100644 --- a/src/app/agent-page.tsx +++ b/src/app/agent-page.tsx @@ -3,11 +3,13 @@ import { Link } from "react-router-dom"; import { useQuery } from "@/lib/graphql/client"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; import { DaemonDocument } from "@/lib/graphql/generated/graphql"; +import type { DaemonQuery } from "@/lib/graphql/generated/graphql"; import { StatusDot, PageLoading, PageError, SectionHeading } from "./shared"; -const PHASE_OUTPUT_QUERY = `query PhaseOutput($workflowId: ID!, $phaseId: String, $tail: Int) { phaseOutput(workflowId: $workflowId, phaseId: $phaseId, tail: $tail) { lines phaseId hasMore } }`; +// TODO(E-followup): richer rendering — per-agent phase output is no longer +// exposed over the kernel control surface, so cards show session metadata only. +type AgentInfo = NonNullable[number]; function useElapsedTime(startedAt: string | null | undefined): string { const [, setTick] = useState(0); @@ -22,7 +24,7 @@ function useElapsedTime(startedAt: string | null | undefined): string { } function formatDuration(ms: number): string { - if (ms < 0) return "0s"; + if (ms < 0 || Number.isNaN(ms)) return "0s"; const s = Math.floor(ms / 1000); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); @@ -31,70 +33,25 @@ function formatDuration(ms: number): string { return `${h}h ${m % 60}m`; } -function AgentOutputPreview({ workflowId, phaseId }: { workflowId: string; phaseId: string | null | undefined }) { - const [result] = useQuery<{ phaseOutput: { lines: string[]; phaseId: string; hasMore: boolean } }>({ - query: PHASE_OUTPUT_QUERY, - variables: { workflowId, phaseId: phaseId ?? undefined, tail: 10 }, - pause: !workflowId, - }); - - const lines = result.data?.phaseOutput?.lines ?? []; - - if (result.fetching && lines.length === 0) { - return ( -
-

Loading output...

-
- ); - } - - if (lines.length === 0) { - return ( -
-

No output yet

-
- ); - } - - return ( -
-
- {lines.map((line, i) => ( -
- {line} -
- ))} - -
-
- ); -} - -function AgentCard({ agent }: { agent: { runId: string; taskId?: string | null; taskTitle?: string | null; workflowId?: string | null; phaseId?: string | null; status: string } }) { - const elapsed = useElapsedTime(new Date().toISOString()); +function AgentCard({ agent }: { agent: AgentInfo }) { + const elapsed = useElapsedTime(agent.startedAt); return (
- - agent-{agent.runId} + + {agent.sessionId}
- {agent.status} + {agent.provider}
- Task: - {agent.taskId ? ( - - {agent.taskId} {agent.taskTitle ? `"${agent.taskTitle}"` : ""} - - ) : ( - - - )} + Model: + {agent.model}
Workflow: @@ -115,43 +72,20 @@ function AgentCard({ agent }: { agent: { runId: string; taskId?: string | null; {elapsed || "-"}
- - {agent.workflowId && ( -
- Output (last 10 lines) - -
- )} - -
- {agent.workflowId && ( - <> - - - - )} -
); } export function AgentManagementPage() { - const [result] = useQuery({ query: DaemonDocument }); + const [result] = useQuery({ query: DaemonDocument }); const { data, fetching, error } = result; if (fetching) return ; if (error) return ; - const status = data?.daemonStatus; const health = data?.daemonHealth; - const agents = data?.agentRuns ?? []; - const activeCount = agents.filter((a: { status: string }) => a.status.toLowerCase() === "running").length; - const maxAgents = status?.maxAgents ?? health?.activeAgents ?? 0; + const agents = data?.daemonAgents ?? []; const overallHealth = agents.length > 0 ? "running" : health?.healthy ? "healthy" : "error"; @@ -160,7 +94,7 @@ export function AgentManagementPage() {

Agents

- {activeCount} active / {maxAgents} capacity + {agents.length} active
@@ -185,24 +119,24 @@ export function AgentManagementPage() { Active Agents
{agents.map((a) => ( - + ))}
)}
- System Capacity + Daemon
- Max Agents - {status?.maxAgents ?? "-"} + Status + {health?.status ?? "-"}
- Runner Connected + Healthy - {health?.runnerConnected ? ( + {health?.healthy ? ( yes ) : ( "no" @@ -210,12 +144,12 @@ export function AgentManagementPage() {
- Runner PID - {health?.runnerPid ?? "-"} + Running + {data?.daemon?.running ? "yes" : "no"}
- Daemon PID - {health?.daemonPid ?? "-"} + PID + {data?.daemon?.pid ?? "-"}
diff --git a/src/app/builder-pages.tsx b/src/app/builder-pages.tsx deleted file mode 100644 index e5b1792..0000000 --- a/src/app/builder-pages.tsx +++ /dev/null @@ -1,2343 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom"; -import { useQuery, useMutation } from "@/lib/graphql/client"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { Separator } from "@/components/ui/separator"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { WorkflowDefinitionsDocument, WorkflowConfigDocument } from "@/lib/graphql/generated/graphql"; -import { PageLoading, PageError } from "./shared"; -import { - Plus, - ChevronRight, - ChevronLeft, - ArrowLeft, - X, - Pencil, - Copy, - Trash2, - Eye, - CheckCircle2, - AlertCircle, - Save, - Layers, - PaintBucket, - FileText, - Settings, - Server, - Clock, - Users, - Terminal, - Hand, - Bot, - ChevronDown, - ChevronUp, -} from "lucide-react"; - -interface PhaseEntry { - id: string; - maxReworkAttempts: number; - skipIf: string[]; - onVerdict: { - advance: { target: string | null }; - rework: { target: string | null; allowAgentTarget: boolean }; - fail: { target: string | null }; - }; -} - -interface VariableEntry { - name: string; - description: string; - required: boolean; - default: string; -} - -interface PostSuccessConfig { - mergeStrategy: string; - targetBranch: string; - createPr: boolean; - autoMerge: boolean; - cleanupWorktree: boolean; -} - -interface WorkflowDef { - id: string; - name: string; - description: string; - phases: PhaseEntry[]; - variables: VariableEntry[]; - postSuccess: PostSuccessConfig; -} - -interface AgentProfileEntry { - name: string; - description: string; - role: string; - systemPrompt: string; - model: string; - tool: string; - mcpServers: string[]; - skills: string[]; -} - -interface ContractField { - name: string; - type: "string" | "array" | "integer" | "boolean"; - description: string; - itemsType: string; - enumValues: string[]; -} - -interface PhaseDefinitionEntry { - id: string; - mode: "agent" | "command" | "manual"; - agent: string; - directive: string; - decisionContract: { - minConfidence: number; - maxRisk: string; - allowMissingDecision: boolean; - requiredEvidence: string[]; - fields: ContractField[]; - }; - outputContract: { - kind: string; - requiredFields: string[]; - fields: ContractField[]; - }; - command: { - program: string; - args: string[]; - cwdMode: string; - timeoutSecs: number; - }; - manualInstructions: string; - approvalNoteRequired: boolean; -} - -interface McpServerEntry { - name: string; - command: string; - args: string[]; - transport: string; - tools: string[]; - env: Array<{ key: string; value: string }>; -} - -interface ScheduleEntry { - id: string; - cron: string; - workflowRef: string; - enabled: boolean; -} - -function makePhaseEntry(id: string): PhaseEntry { - return { - id, - maxReworkAttempts: 3, - skipIf: [], - onVerdict: { - advance: { target: null }, - rework: { target: null, allowAgentTarget: false }, - fail: { target: null }, - }, - }; -} - -function makeVariableEntry(): VariableEntry { - return { name: "", description: "", required: false, default: "" }; -} - -function makePostSuccess(): PostSuccessConfig { - return { mergeStrategy: "squash", targetBranch: "main", createPr: true, autoMerge: false, cleanupWorktree: true }; -} - -function makeAgentProfile(): AgentProfileEntry { - return { name: "", description: "", role: "", systemPrompt: "", model: "", tool: "", mcpServers: [], skills: [] }; -} - -function makePhaseDefinition(id?: string): PhaseDefinitionEntry { - return { - id: id ?? "", - mode: "agent", - agent: "", - directive: "", - decisionContract: { minConfidence: 0.7, maxRisk: "medium", allowMissingDecision: false, requiredEvidence: [], fields: [] }, - outputContract: { kind: "", requiredFields: [], fields: [] }, - command: { program: "", args: [], cwdMode: "project_root", timeoutSecs: 300 }, - manualInstructions: "", - approvalNoteRequired: false, - }; -} - -function makeMcpServer(): McpServerEntry { - return { name: "", command: "", args: [], transport: "stdio", tools: [], env: [] }; -} - -function makeSchedule(): ScheduleEntry { - return { id: "", cron: "", workflowRef: "", enabled: true }; -} - -const TEMPLATES: Record = { - standard: { - name: "Standard", - description: "A typical development workflow with requirements analysis, implementation, code review, and testing phases.", - phases: ["requirements", "implementation", "code-review", "testing"], - }, - "ui-ux": { - name: "UI/UX", - description: "Extended workflow for user interface work including research, wireframing, and mockup review before implementation.", - phases: ["requirements", "ux-research", "wireframe", "mockup-review", "implementation", "code-review", "testing"], - }, - blank: { - name: "Blank", - description: "Start from scratch with an empty workflow definition. Add phases as needed.", - phases: [], - }, -}; - -const ID_PATTERN = /^[a-z0-9][a-z0-9-]*$/; - -interface ValidationResult { - valid: boolean; - errors: { message: string; phaseId?: string }[]; - warnings: { message: string }[]; -} - -function validateDef(def: WorkflowDef): ValidationResult { - const errors: { message: string; phaseId?: string }[] = []; - const warnings: { message: string }[] = []; - - if (!def.id.trim()) { - errors.push({ message: "Workflow ID is required" }); - } else if (!ID_PATTERN.test(def.id)) { - errors.push({ message: "ID must start with a lowercase letter or digit and contain only lowercase letters, digits, and hyphens" }); - } - - if (!def.name.trim()) { - errors.push({ message: "Workflow name is required" }); - } - - if (def.phases.length === 0) { - errors.push({ message: "At least one phase is required" }); - } - - const seen = new Set(); - const phaseIds = new Set(def.phases.map((p) => p.id)); - for (const phase of def.phases) { - if (!phase.id.trim()) { - errors.push({ message: "Phase ID cannot be empty", phaseId: phase.id }); - } else if (!ID_PATTERN.test(phase.id)) { - errors.push({ message: `Phase "${phase.id}" has an invalid ID format`, phaseId: phase.id }); - } - if (seen.has(phase.id)) { - errors.push({ message: `Duplicate phase ID "${phase.id}"`, phaseId: phase.id }); - } - seen.add(phase.id); - - if (phase.maxReworkAttempts < 1) { - errors.push({ message: `Phase "${phase.id}" must have max rework attempts > 0`, phaseId: phase.id }); - } - - for (const [verdict, cfg] of Object.entries(phase.onVerdict)) { - const target = (cfg as { target: string | null }).target; - if (target && !phaseIds.has(target)) { - errors.push({ message: `Phase "${phase.id}" ${verdict} target "${target}" does not exist`, phaseId: phase.id }); - } - } - } - - if (def.phases.length > 0 && !errors.some((e) => e.phaseId)) { - warnings.push({ message: `${def.phases.length} phase(s) configured` }); - } - - return { valid: errors.length === 0, errors, warnings }; -} - -function defToPreview(def: WorkflowDef): string { - const obj: Record = { - id: def.id, - name: def.name, - }; - if (def.description) obj.description = def.description; - obj.phases = def.phases.map((p) => { - const phase: Record = { id: p.id }; - if (p.maxReworkAttempts !== 3) phase.max_rework_attempts = p.maxReworkAttempts; - if (p.skipIf.length > 0) phase.skip_if = p.skipIf; - const onVerdict: Record = {}; - if (p.onVerdict.advance.target) onVerdict.advance = { target: p.onVerdict.advance.target }; - if (p.onVerdict.rework.target || p.onVerdict.rework.allowAgentTarget) { - const rw: Record = {}; - if (p.onVerdict.rework.target) rw.target = p.onVerdict.rework.target; - if (p.onVerdict.rework.allowAgentTarget) rw.allow_agent_target = true; - onVerdict.rework = rw; - } - if (p.onVerdict.fail.target) onVerdict.fail = { target: p.onVerdict.fail.target }; - if (Object.keys(onVerdict).length > 0) phase.on_verdict = onVerdict; - return phase; - }); - if (def.variables.length > 0) { - obj.variables = def.variables.map((v) => { - const ve: Record = { name: v.name }; - if (v.description) ve.description = v.description; - if (v.required) ve.required = true; - if (v.default) ve.default = v.default; - return ve; - }); - } - return JSON.stringify(obj, null, 2); -} - -function PhaseNode({ - phase, - index, - total, - selected, - hasError, - onSelect, - onMoveLeft, - onMoveRight, - onRemove, -}: { - phase: PhaseEntry; - index: number; - total: number; - selected: boolean; - hasError: boolean; - onSelect: () => void; - onMoveLeft: () => void; - onMoveRight: () => void; - onRemove: () => void; -}) { - return ( -
- - )} - {index < total - 1 && ( - - )} - -
- - {index < total - 1 && } -
- ); -} - -function PhaseDetailPanel({ - phase, - allPhaseIds, - onChange, -}: { - phase: PhaseEntry; - allPhaseIds: string[]; - onChange: (updated: PhaseEntry) => void; -}) { - const otherPhases = allPhaseIds.filter((id) => id !== phase.id); - const [newSkipGuard, setNewSkipGuard] = useState(""); - - const updateField = (key: K, value: PhaseEntry[K]) => { - onChange({ ...phase, [key]: value }); - }; - - const updateVerdict = ( - verdict: "advance" | "rework" | "fail", - field: string, - value: unknown, - ) => { - onChange({ - ...phase, - onVerdict: { - ...phase.onVerdict, - [verdict]: { ...phase.onVerdict[verdict], [field]: value }, - }, - }); - }; - - return ( -
- - - Phase Config - - -
- - updateField("id", e.target.value)} - className="mt-1 font-mono text-xs h-8" - /> -
- -
- - updateField("maxReworkAttempts", Math.max(1, parseInt(e.target.value) || 1))} - className="mt-1 text-xs h-8" - /> -
- -
- -
- {phase.skipIf.map((guard, i) => ( -
- {guard} - -
- ))} -
- setNewSkipGuard(e.target.value)} - placeholder="Guard condition" - className="text-xs h-7 flex-1" - onKeyDown={(e) => { - if (e.key === "Enter" && newSkipGuard.trim()) { - updateField("skipIf", [...phase.skipIf, newSkipGuard.trim()]); - setNewSkipGuard(""); - } - }} - /> - -
-
-
- - - -
- - -
- -
- - - -
- -
- - -
-
-
-
- ); -} - -function VariableCard({ - variable, - onChange, - onRemove, -}: { - variable: VariableEntry; - onChange: (v: VariableEntry) => void; - onRemove: () => void; -}) { - return ( - - -
-
-
- - onChange({ ...variable, name: e.target.value })} - className="mt-1 font-mono text-xs h-8" - placeholder="variable_name" - /> -
-
- - onChange({ ...variable, default: e.target.value })} - className="mt-1 text-xs h-8" - placeholder="Default value" - /> -
-
- -
-
- - onChange({ ...variable, description: e.target.value })} - className="mt-1 text-xs h-8" - placeholder="What this variable controls" - /> -
- -
-
- ); -} - -function TransitionsTable({ phases }: { phases: PhaseEntry[] }) { - if (phases.length === 0) { - return

No phases configured

; - } - - return ( -
- - - - - - - - - - - {phases.map((p, i) => ( - - - - - - - ))} - -
PhaseAdvanceReworkFail
{p.id} - {p.onVerdict.advance.target ?? (i < phases.length - 1 ? `${phases[i + 1].id} (auto)` : "end")} - - {p.onVerdict.rework.target ?? `${p.id} (self)`} - {p.onVerdict.rework.allowAgentTarget && agent} - - {p.onVerdict.fail.target ?? "stop"} -
-
- ); -} - -function TagInput({ - values, - onChange, - placeholder, -}: { - values: string[]; - onChange: (v: string[]) => void; - placeholder?: string; -}) { - const [input, setInput] = useState(""); - return ( -
-
- {values.map((v, i) => ( - - {v} - - - ))} -
- setInput(e.target.value)} - placeholder={placeholder ?? "Type and press Enter"} - className="text-xs h-7 font-mono" - onKeyDown={(e) => { - if (e.key === "Enter" && input.trim()) { - e.preventDefault(); - onChange([...values, input.trim()]); - setInput(""); - } - }} - /> -
- ); -} - -function ListInput({ - values, - onChange, - placeholder, -}: { - values: string[]; - onChange: (v: string[]) => void; - placeholder?: string; -}) { - const [input, setInput] = useState(""); - return ( -
- {values.map((v, i) => ( -
- {v} - -
- ))} -
- setInput(e.target.value)} - placeholder={placeholder ?? "Add item"} - className="text-xs h-7 font-mono flex-1" - onKeyDown={(e) => { - if (e.key === "Enter" && input.trim()) { - e.preventDefault(); - onChange([...values, input.trim()]); - setInput(""); - } - }} - /> - -
-
- ); -} - -function KeyValueEditor({ - entries, - onChange, -}: { - entries: Array<{ key: string; value: string }>; - onChange: (v: Array<{ key: string; value: string }>) => void; -}) { - return ( -
- {entries.map((entry, i) => ( -
- onChange(entries.map((en, j) => j === i ? { ...en, key: e.target.value } : en))} - placeholder="KEY" - className="text-xs h-7 font-mono flex-1" - /> - = - onChange(entries.map((en, j) => j === i ? { ...en, value: e.target.value } : en))} - placeholder="value" - className="text-xs h-7 font-mono flex-1" - /> - -
- ))} - -
- ); -} - -function AgentProfileCard({ - profile, - onEdit, - onDelete, -}: { - profile: AgentProfileEntry; - onEdit: () => void; - onDelete: () => void; -}) { - return ( - - -
-
-

{profile.name || "Unnamed"}

- {profile.description &&

{profile.description}

} -
- {profile.role && {profile.role}} - {profile.model && {profile.model}} - {profile.tool && {profile.tool}} -
-
-
- - -
-
-
-
- ); -} - -function AgentProfileEditor({ - profile, - onSave, - onCancel, -}: { - profile: AgentProfileEntry; - onSave: (p: AgentProfileEntry) => void; - onCancel: () => void; -}) { - const [draft, setDraft] = useState({ ...profile, mcpServers: [...profile.mcpServers], skills: [...profile.skills] }); - return ( - - -
-
- - setDraft({ ...draft, name: e.target.value })} className="mt-1 font-mono text-xs h-8" placeholder="agent-name" disabled={!!profile.name} /> -
-
- - setDraft({ ...draft, role: e.target.value })} className="mt-1 text-xs h-8" placeholder="implementer" /> -
-
-
- - setDraft({ ...draft, description: e.target.value })} className="mt-1 text-xs h-8" placeholder="What this agent does" /> -
-
- -