// — 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 — its name field + submit button now // come from the shared `ui/` form kit (`TextField`/`Button`) rather than // page-scoped input/button chrome, so a hive-picker soon landing on this // same page has something to reuse instead of copying this page's CSS. import { useState } from 'preact/hooks'; import { Link } from 'wouter-preact'; import { ApiErrorPanel } from '@hive/shared/api-error-panel.js'; import { readApiError, type ProblemDetails } from '@hive/shared/api-error.js'; import { Panel } from '../ui/panel/Panel.js'; import { TextField } from '../ui/text-field/TextField.js'; import { Button } from '../ui/button/Button.js'; import './CreateAgentPage.css'; interface CreateAgentResponse { node_id: number; } type SubmitState = | { status: 'idle' } | { status: 'submitting' } | { status: 'done'; nodeId: number } | { status: 'error'; problem: ProblemDetails }; // 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. // // Hyphen escaped (`\-`) rather than left trailing in the class: modern // browsers validate the `pattern` attribute's regex in Unicode-set (`v`) // mode, which is far stricter about hyphen placement than classic mode — // `[a-z0-9-]` throws "Invalid character class" under `v` mode even though // it's valid classic-mode regex (a trailing `-` is unambiguous there). // Reproduced directly: `new RegExp('[a-z0-9-]', 'v')` throws, // `new RegExp('[a-z0-9\\-]', 'v')` doesn't — confirmed via mara's console // exception report on this page. 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) { setResult({ status: 'error', problem: await readApiError(r) }); return; } const data = (await r.json()) as CreateAgentResponse; setResult({ status: 'done', nodeId: data.node_id }); setName(''); } catch (err) { setResult({ status: 'error', problem: { detail: String(err) } }); } } return (

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

{result.status === 'done' && (

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

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