feat(FN-1444): complete Step 1 — add soul to POST/PATCH agent routes with validation

This commit is contained in:
gsxdsm
2026-04-10 20:41:48 -07:00
parent 1fe55b22f1
commit 402eac6cb4
2 changed files with 56 additions and 0 deletions

View File

@@ -10286,6 +10286,7 @@ describe("Agent create/update routes", () => {
permissions: { read: true },
instructionsPath: "docs/reviewer.md",
instructionsText: "Check test quality.",
soul: "Analytical and thorough.",
}),
{ "Content-Type": "application/json" },
);
@@ -10302,6 +10303,7 @@ describe("Agent create/update routes", () => {
permissions: { read: true },
instructionsPath: "docs/reviewer.md",
instructionsText: "Check test quality.",
soul: "Analytical and thorough.",
});
});
@@ -10324,6 +10326,7 @@ describe("Agent create/update routes", () => {
totalOutputTokens: 21,
instructionsPath: "agents/infra.md",
instructionsText: "Focus on reliability.",
soul: "Pragmatic and efficient.",
}),
{ "Content-Type": "application/json" },
);
@@ -10344,9 +10347,44 @@ describe("Agent create/update routes", () => {
totalOutputTokens: 21,
instructionsPath: "agents/infra.md",
instructionsText: "Focus on reliability.",
soul: "Pragmatic and efficient.",
});
});
it("POST /api/agents returns 400 when soul exceeds 10,000 characters", async () => {
const longSoul = "x".repeat(10001);
const res = await REQUEST(
buildAgentApp(),
"POST",
"/api/agents",
JSON.stringify({
name: "Soul Test Agent",
role: "executor",
soul: longSoul,
}),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(400);
expect(res.body.error).toContain("soul must be at most 10,000 characters");
});
it("PATCH /api/agents/:id returns 400 when soul exceeds 10,000 characters", async () => {
const longSoul = "x".repeat(10001);
const res = await REQUEST(
buildAgentApp(),
"PATCH",
`/api/agents/${agentId}`,
JSON.stringify({
soul: longSoul,
}),
{ "Content-Type": "application/json" },
);
expect(res.status).toBe(400);
expect(res.body.error).toContain("soul must be at most 10,000 characters");
});
it("POST /api/agents/:id/state returns 400 for invalid state transitions", async () => {
const res = await REQUEST(
buildAgentApp(),

View File

@@ -8998,6 +8998,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
permissions,
instructionsPath,
instructionsText,
soul,
} = req.body ?? {};
if (!name || typeof name !== "string") {
@@ -9027,6 +9028,12 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
if (!validateAgentInstructionsPayload(res, instructionsPath, instructionsText)) {
return;
}
if (soul !== undefined && soul !== null && typeof soul !== "string") {
throw badRequest("soul must be a string");
}
if (typeof soul === "string" && soul.length > 10000) {
throw badRequest("soul must be at most 10,000 characters");
}
const scopedStore = await getScopedStore(req);
const { AgentStore } = await import("@fusion/core");
@@ -9044,6 +9051,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
permissions,
instructionsPath: instructionsPath ?? undefined,
instructionsText: instructionsText ?? undefined,
soul: soul ?? undefined,
});
res.status(201).json(agent);
} catch (err: any) {
@@ -9473,6 +9481,16 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
updates.instructionsText = body.instructionsText ?? undefined;
}
if ("soul" in body) {
if (body.soul !== null && typeof body.soul !== "string") {
throw badRequest("soul must be a string");
}
if (typeof body.soul === "string" && body.soul.length > 10000) {
throw badRequest("soul must be at most 10,000 characters");
}
updates.soul = body.soul ?? undefined;
}
const scopedStore = await getScopedStore(req);
const { AgentStore } = await import("@fusion/core");
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });