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 }, permissions: { read: true },
instructionsPath: "docs/reviewer.md", instructionsPath: "docs/reviewer.md",
instructionsText: "Check test quality.", instructionsText: "Check test quality.",
soul: "Analytical and thorough.",
}), }),
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
); );
@@ -10302,6 +10303,7 @@ describe("Agent create/update routes", () => {
permissions: { read: true }, permissions: { read: true },
instructionsPath: "docs/reviewer.md", instructionsPath: "docs/reviewer.md",
instructionsText: "Check test quality.", instructionsText: "Check test quality.",
soul: "Analytical and thorough.",
}); });
}); });
@@ -10324,6 +10326,7 @@ describe("Agent create/update routes", () => {
totalOutputTokens: 21, totalOutputTokens: 21,
instructionsPath: "agents/infra.md", instructionsPath: "agents/infra.md",
instructionsText: "Focus on reliability.", instructionsText: "Focus on reliability.",
soul: "Pragmatic and efficient.",
}), }),
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
); );
@@ -10344,9 +10347,44 @@ describe("Agent create/update routes", () => {
totalOutputTokens: 21, totalOutputTokens: 21,
instructionsPath: "agents/infra.md", instructionsPath: "agents/infra.md",
instructionsText: "Focus on reliability.", 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 () => { it("POST /api/agents/:id/state returns 400 for invalid state transitions", async () => {
const res = await REQUEST( const res = await REQUEST(
buildAgentApp(), buildAgentApp(),

View File

@@ -8998,6 +8998,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
permissions, permissions,
instructionsPath, instructionsPath,
instructionsText, instructionsText,
soul,
} = req.body ?? {}; } = req.body ?? {};
if (!name || typeof name !== "string") { if (!name || typeof name !== "string") {
@@ -9027,6 +9028,12 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
if (!validateAgentInstructionsPayload(res, instructionsPath, instructionsText)) { if (!validateAgentInstructionsPayload(res, instructionsPath, instructionsText)) {
return; 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 scopedStore = await getScopedStore(req);
const { AgentStore } = await import("@fusion/core"); const { AgentStore } = await import("@fusion/core");
@@ -9044,6 +9051,7 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
permissions, permissions,
instructionsPath: instructionsPath ?? undefined, instructionsPath: instructionsPath ?? undefined,
instructionsText: instructionsText ?? undefined, instructionsText: instructionsText ?? undefined,
soul: soul ?? undefined,
}); });
res.status(201).json(agent); res.status(201).json(agent);
} catch (err: any) { } catch (err: any) {
@@ -9473,6 +9481,16 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
updates.instructionsText = body.instructionsText ?? undefined; 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 scopedStore = await getScopedStore(req);
const { AgentStore } = await import("@fusion/core"); const { AgentStore } = await import("@fusion/core");
const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() }); const agentStore = new AgentStore({ rootDir: scopedStore.getFusionDir() });