Watch
0
0
Fork
You've already forked hyperhive
0

an archetype's prompt prefills the new agent's agents.md in the create form

This commit is contained in:
atlas 2026-11-11 00:40:40 +01:00 • committed by mara
commit a5470d82ae
4 changed files with 179 additions and 12 deletions

View file

@ -21,3 +21,17 @@ export function agentsMdRefusal(problem: ProblemDetails): string | null {
? problem.detail
: null;
}
/**
* The AGENTS.md box after the archetype pick changes from one whose prompt was
* `previous` to one whose prompt is `next` (both `""` for none). The pick
* replaces what is in the box only when the box is empty or still holds the
* previous pick's prompt; text the operator typed or edited stays.
*/
export function agentsMdAfterPick(
current: string,
previous: string,
next: string,
): string {
return current.trim() === "" || current === previous ? next : current;
}

View file

@ -15,6 +15,7 @@ export interface Archetype {
subagent_models?: string[];
max_subagents?: number;
};
prompt?: string;
}
// Mirrors swarm-controller's `Archetypes`.

View file

@ -63,7 +63,11 @@ import {
requestArchetype,
type ArchetypeList,
} from "./CreateAgentArchetype.js";
import { agentsMdBody, agentsMdRefusal } from "./CreateAgentAgentsMd.js";
import {
agentsMdAfterPick,
agentsMdBody,
agentsMdRefusal,
} from "./CreateAgentAgentsMd.js";
import {
NAME_PATTERN,
nextStep,
@ -191,13 +195,14 @@ export function CreateAgentForm({
})().catch((e: unknown) => setArchetypesError({ detail: String(e) }));
}, []);
// Fills permissions, caps and inference from the picked archetype; each
// stays editable, and the request carries only those fields.
// Fills permissions, caps, inference and the AGENTS.md text from the picked
// archetype; each stays editable, and the request carries only those fields.
function pickArchetype(key: string) {
setArchetype(key);
const p = prefill(
archetypes?.archetypes.find((a) => archetypeKey(a) === key),
catalogue?.default ?? [],
const next = archetypes?.archetypes.find((a) => archetypeKey(a) === key);
const p = prefill(next, catalogue?.default ?? []);
setAgentsMd((current) =>
agentsMdAfterPick(current, picked?.prompt ?? "", next?.prompt ?? ""),
);
setSelected(p.permissions);
setCpu(p.cpu);
@ -401,7 +406,8 @@ export function CreateAgentForm({
<div class="create-agent-fields">
<p class="create-agent-intro">
The text the agent's AGENTS.md starts with. Optional: leave it
empty and the agent starts without the file.
empty and the agent starts without the file. An archetype with a
prompt fills the box in; edit it freely.
</p>
<TextAreaField
id="agent-agents-md"

View file

@ -5,6 +5,7 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import {
agentsMdAfterPick,
agentsMdBody,
agentsMdRefusal,
} from "../src/pages/CreateAgentAgentsMd.ts";
@ -41,3 +42,29 @@ test("other failures stay with the general error", () => {
assert.equal(agentsMdRefusal({ status: 400 }), null);
assert.equal(agentsMdRefusal({ detail: "agents_md is too long" }), null);
});
test("a pick fills an empty box with the archetype's prompt", () => {
assert.equal(agentsMdAfterPick("", "", "Review only."), "Review only.");
assert.equal(agentsMdAfterPick(" \n", "", "Review only."), "Review only.");
});
test("a pick keeps text the operator typed or edited", () => {
assert.equal(agentsMdAfterPick("# mine", "", "Review only."), "# mine");
assert.equal(
agentsMdAfterPick("Review only. Also x.", "Review only.", "Triage."),
"Review only. Also x.",
);
});
test("a pick over an unedited prefill swaps it, and none clears it", () => {
assert.equal(
agentsMdAfterPick("Review only.", "Review only.", "Triage."),
"Triage.",
);
assert.equal(agentsMdAfterPick("Review only.", "Review only.", ""), "");
});
test("an archetype without a prompt leaves the box as it is", () => {
assert.equal(agentsMdAfterPick("# mine", "", ""), "# mine");
assert.equal(agentsMdAfterPick("", "", ""), "");
});