// — minimal agent creation form, POSTs to // swarm-controller's `POST /api/agents`. That endpoint only queues a // `CreateIdentity` job and returns its node id — the identity isn't // guaranteed to exist yet when this page gets a response — so this // page shows the queued confirmation and links to the Jobs page to // watch it settle, rather than duplicating JobqGraph's per-node // polling here. Scope matches the originating issue exactly: name // field only, no forge/deploy options (those aren't wired server-side // yet either). // // Lives at `/create-agent`, not `/agents` — a future agent *roster* // (listing existing agents) is the natural owner of the bare `/agents` // path, and this creation form is a distinct action from that list, // not a variant of it. Flat, not `/agents/new`: `index.html`'s asset // links are relative (`static/main.js`), which only resolve correctly // one path segment deep — a real bug, filed separately rather than // fixed here, but reason enough to avoid a nested route today. // // First real form in this package — no shared form kit exists yet, so // the input/button styling below is scoped to `CreateAgentPage.css` // rather than promoted into `ui/`. Promote the day a second page needs // one, same "don't build ahead of a second caller" rule `Panel`'s own // comment states. import { useState } from 'preact/hooks'; import { Link } from 'wouter-preact'; import { Panel } from '../ui/panel/Panel.js'; import './CreateAgentPage.css'; interface CreateAgentResponse { node_id: number; } type SubmitState = | { status: 'idle' } | { status: 'submitting' } | { status: 'done'; nodeId: number } | { status: 'error'; message: string }; // Client-side mirror of `hive_types::Ident` (1-63 chars of `[a-z0-9-]`) — // a `pattern` hint only, not a substitute for the server's own // validation; a name this rejects would fail server-side anyway, so // catching it before a round-trip is a pure UX win, not a new gate. const NAME_PATTERN = '[a-z0-9-]{1,63}'; export function CreateAgentPage() { const [name, setName] = useState(''); const [result, setResult] = useState({ status: 'idle' }); async function submit(e: Event) { e.preventDefault(); setResult({ status: 'submitting' }); try { const r = await fetch('/api/agents', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name }), }); if (!r.ok) throw new Error((await r.text()) || `http ${r.status}`); const data = (await r.json()) as CreateAgentResponse; setResult({ status: 'done', nodeId: data.node_id }); setName(''); } catch (err) { setResult({ status: 'error', message: String(err) }); } } return (

Create a new agent's swarm-level identity. This only queues the job — check{' '} jobs to watch it settle.

setName(e.currentTarget.value)} />
{result.status === 'done' && (

queued as job node #{result.nodeId} — watch it in jobs

)} {result.status === 'error' && (

failed to queue: {result.message}

)}
); }