Compare commits
5 changed files with 3 additions and 114 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
Loading…
Reference in a new issue