Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
5 changed files with 3 additions and 114 deletions

View file

@ -386,20 +386,6 @@ controller puts back what it replaced. Each agent name keys its settings and
destroying an agent doesn't clear them, so a name set up before keeps its
stored settings when you leave the fields empty.
### Initial AGENTS.md at creation
A third collapsed section, **instructions**, holds a plain text box for the
text the new agent's `AGENTS.md` starts with. Left empty, or holding only
whitespace, it sends nothing and the agent starts without the file. Otherwise
the text goes as typed in the optional `agents_md` field of `POST /api/agents`.
The agent writes it on its first start, and only when it has no `AGENTS.md`
yet, so creating an agent again under a name that has one never replaces it.
The controller refuses a text over 64 KiB. Its reason shows under the
section, open or not, and the swarm creates no agent. After a successful
create the box empties. The
[instructions editor](#editing-an-agents-instructions) changes the file later.
The [swarm README](README.md#an-agents-agentsmd) has the wire details.
### Linking external accounts
Each agent on `/agents` opens three dialogs that write a credential for it

View file

@ -1,23 +0,0 @@
// The create-agent form's initial `AGENTS.md` field, kept out of the
// component so `npm test` can run it.
import type { ProblemDetails } from "@hive/shared/api-error.js";
/**
* The `agents_md` to send with a create: nothing while the box is empty or
* holds only whitespace, so the agent starts without the file; otherwise the
* text as typed.
*/
export function agentsMdBody(text: string): { agents_md?: string } {
return text.trim() === "" ? {} : { agents_md: text };
}
/**
* The controller's refusal of the text, when `problem` is one: a 400 whose
* detail starts with the field's name. It shows beside the box, so any other
* failure stays with the form's general error.
*/
export function agentsMdRefusal(problem: ProblemDetails): string | null {
return problem.status === 400 && problem.detail?.startsWith("agents_md ")
? problem.detail
: null;
}

View file

@ -69,11 +69,6 @@
.create-agent-result-ok {
color: var(--green);
}
/* The controller's refusal of the AGENTS.md text, under its section. */
.create-agent-field-error {
margin: 0;
color: var(--red);
}
/* The picked archetype's description, under its select. */
.create-agent-archetype-description {
margin: 0;

View file

@ -38,7 +38,6 @@ import {
SelectField,
type SelectOption,
} from "../ui/select-field/SelectField.js";
import { TextAreaField } from "../ui/text-area-field/TextAreaField.js";
import { Button } from "../ui/button/Button.js";
import { capsBody, LimitsFields } from "../agent/detail/AgentLimits.js";
import {
@ -61,7 +60,6 @@ import {
requestArchetype,
type ArchetypeList,
} from "./CreateAgentArchetype.js";
import { agentsMdBody, agentsMdRefusal } from "./CreateAgentAgentsMd.js";
import "./CreateAgentForm.css";
// Mirrors swarm-controller's `HiveEntry` — same shape `HivesPage`
@ -146,8 +144,6 @@ export function CreateAgentForm({
null,
);
const [archetype, setArchetype] = useState("");
// The text the agent's `AGENTS.md` starts with; empty sends none.
const [agentsMd, setAgentsMd] = useState("");
const picked = archetypes?.archetypes.find(
(a) => archetypeKey(a) === archetype,
);
@ -215,10 +211,6 @@ export function CreateAgentForm({
label: h.name,
}));
const hivesReady = hives !== null && hives.length > 0;
// The controller's size refusal of the AGENTS.md text, shown under its
// section so a collapsed section still shows it.
const refusal =
result.status === "error" ? agentsMdRefusal(result.problem) : null;
async function submit(e: Event) {
e.preventDefault();
@ -240,7 +232,6 @@ export function CreateAgentForm({
? requestPermissions(selected, catalogue.default)
: undefined,
archetype: requestArchetype(picked),
...agentsMdBody(agentsMd),
}),
});
if (!r.ok) {
@ -250,7 +241,6 @@ export function CreateAgentForm({
const data = (await r.json()) as CreateAgentResponse;
setResult({ status: "done", nodeId: data.node_id });
setName("");
setAgentsMd("");
pickArchetype("");
if (catalogue)
setSelected(
@ -268,8 +258,8 @@ export function CreateAgentForm({
<p class="create-agent-intro">
Create a new agent's swarm-level identity. This only queues the job
— check <Link href="/jobs">jobs</Link> to watch it settle. Resource
caps, inference and instructions are optional; leave them empty to
set nothing. A name set up before keeps the settings stored for it.
caps and inference are optional; leave them empty to set nothing. A
name set up before keeps the settings stored for it.
</p>
{hivesError && (
<ApiErrorPanel
@ -375,22 +365,6 @@ export function CreateAgentForm({
/>
</div>
</details>
<details class="create-agent-section">
<summary>instructions</summary>
<div class="create-agent-section-body">
<TextAreaField
id="agent-agents-md"
label="AGENTS.md"
value={agentsMd}
rows={8}
placeholder="the text the agent's AGENTS.md starts with; leave empty for none"
onInput={setAgentsMd}
/>
</div>
</details>
{refusal !== null && (
<p class="create-agent-field-error">{refusal}</p>
)}
<Button
variant="primary"
type="submit"
@ -410,7 +384,7 @@ export function CreateAgentForm({
<Link href="/jobs">watch it in jobs</Link>
</p>
)}
{result.status === "error" && refusal === null && (
{result.status === "error" && (
<ApiErrorPanel context="failed to queue" problem={result.problem} />
)}
</Panel>

View file

@ -1,43 +0,0 @@
// What the create-agent form sends for the initial AGENTS.md, and which error
// it shows beside the box. Run by `npm test`, which Node runs with its
// built-in test runner and type stripping, so the `.ts` module is imported as
// it is.
import assert from "node:assert/strict";
import { test } from "node:test";
import {
agentsMdBody,
agentsMdRefusal,
} from "../src/pages/CreateAgentAgentsMd.ts";
test("an empty or whitespace-only box sends no agents_md", () => {
assert.deepEqual(agentsMdBody(""), {});
assert.deepEqual(agentsMdBody(" \n\t \n"), {});
});
test("text is sent as typed, whitespace and all", () => {
assert.deepEqual(agentsMdBody("# me\n\nDo the thing.\n"), {
agents_md: "# me\n\nDo the thing.\n",
});
assert.deepEqual(agentsMdBody(" indented"), { agents_md: " indented" });
});
test("the size refusal shows beside the box", () => {
const detail = "agents_md is 70000 bytes; the limit is 65536";
assert.equal(agentsMdRefusal({ status: 400, detail }), detail);
});
test("other failures stay with the general error", () => {
assert.equal(
agentsMdRefusal({ status: 400, detail: "name is not a valid identifier" }),
null,
);
assert.equal(
agentsMdRefusal({
status: 500,
detail: "agents_md is 70000 bytes; the limit is 65536",
}),
null,
);
assert.equal(agentsMdRefusal({ status: 400 }), null);
assert.equal(agentsMdRefusal({ detail: "agents_md is too long" }), null);
});