feat(FN-1085): align agent routing and runtime contracts

- Harden core AgentStore lifecycle behavior and heartbeat runtime integration paths
- Align dashboard agent APIs, server routes, and agent UI flows with the updated contract
- Tighten CLI agent/message command routing and validate payload handling semantics
- Expand test coverage across core, dashboard, engine, and CLI for route, heartbeat, and instruction regressions
This commit is contained in:
gsxdsm
2026-04-08 00:44:33 -07:00
parent 92e6aa2b49
commit 07697b2f5b
19 changed files with 942 additions and 169 deletions

View File

@@ -175,6 +175,43 @@ describe("AgentStore", () => {
expect(updated.metadata).toEqual({ preserved: true }); // preserved
});
it("allows clearing optional fields via explicit undefined", async () => {
const created = await store.createAgent({
name: "Clearable",
role: "executor",
title: "Worker",
instructionsText: "Initial instructions",
});
const withTransientState = await store.updateAgent(created.id, {
pauseReason: "manual",
lastError: "oops",
});
expect(withTransientState.pauseReason).toBe("manual");
expect(withTransientState.lastError).toBe("oops");
const cleared = await store.updateAgent(created.id, {
title: undefined,
instructionsText: undefined,
pauseReason: undefined,
lastError: undefined,
});
expect(cleared.title).toBeUndefined();
expect(cleared.instructionsText).toBeUndefined();
expect(cleared.pauseReason).toBeUndefined();
expect(cleared.lastError).toBeUndefined();
});
it("rejects whitespace-only names", async () => {
const created = await store.createAgent({
name: "Rename Me",
role: "executor",
});
await expect(store.updateAgent(created.id, { name: " " })).rejects.toThrow("Agent name cannot be empty");
});
it("throws for non-existent agent ID", async () => {
await expect(
store.updateAgent("agent-missing", { name: "Nope" })
@@ -555,7 +592,10 @@ describe("AgentStore", () => {
await s.recordHeartbeat(agent.id, "missed");
await s.updateAgentState(agent.id, "active");
await s.assignTask(agent.id, "KB-999");
await s.updateAgent(agent.id, { lastError: "something broke" });
await s.updateAgent(agent.id, {
pauseReason: "manual",
lastError: "something broke",
});
await s.updateAgentState(agent.id, "terminated");
return agent;
}
@@ -567,6 +607,24 @@ describe("AgentStore", () => {
expect(reset.state).toBe("idle");
});
it("can reset directly from running", async () => {
const agent = await store.createAgent({ name: "RunningReset", role: "executor" });
await store.recordHeartbeat(agent.id, "ok");
await store.updateAgentState(agent.id, "active");
await store.updateAgentState(agent.id, "running");
await store.assignTask(agent.id, "KB-123");
await store.updateAgent(agent.id, {
pauseReason: "stalled",
lastError: "runner failed",
});
const reset = await store.resetAgent(agent.id);
expect(reset.state).toBe("idle");
expect(reset.taskId).toBeUndefined();
expect(reset.pauseReason).toBeUndefined();
expect(reset.lastError).toBeUndefined();
});
it("clears lastError", async () => {
const agent = await createTerminatedAgent(store, "ResetClearsError");
const reset = await store.resetAgent(agent.id);
@@ -574,6 +632,13 @@ describe("AgentStore", () => {
expect(reset.lastError).toBeUndefined();
});
it("clears pauseReason", async () => {
const agent = await createTerminatedAgent(store, "ResetClearsPause");
const reset = await store.resetAgent(agent.id);
expect(reset.pauseReason).toBeUndefined();
});
it("clears taskId", async () => {
const agent = await createTerminatedAgent(store, "ResetClearsTask");
const reset = await store.resetAgent(agent.id);