feat(FN-1171): add agent soul memory and employees workflows
- Add soul and memory fields to agent types and AgentStore with persistence/update test coverage - Add dashboard routes and API helpers to fetch and update agent soul/memory data - Extend AgentDetailView with Soul, Memory, and Employees tabs and rename children labels to employees - Normalize employee route params for type safety and add focused route/component tests for the new flows
This commit is contained in:
@@ -81,6 +81,22 @@ describe("AgentStore", () => {
|
||||
expect(agent.metadata).toEqual({ version: 2, tags: ["test"] });
|
||||
});
|
||||
|
||||
it("persists soul and memory fields on create", async () => {
|
||||
const agent = await store.createAgent({
|
||||
name: "With Soul",
|
||||
role: "executor",
|
||||
soul: "Calm and precise.",
|
||||
memory: "Prefers concise code examples.",
|
||||
});
|
||||
|
||||
expect(agent.soul).toBe("Calm and precise.");
|
||||
expect(agent.memory).toBe("Prefers concise code examples.");
|
||||
|
||||
const persisted = await store.getAgent(agent.id);
|
||||
expect(persisted?.soul).toBe("Calm and precise.");
|
||||
expect(persisted?.memory).toBe("Prefers concise code examples.");
|
||||
});
|
||||
|
||||
it("throws when name is empty", async () => {
|
||||
await expect(
|
||||
store.createAgent({ name: "", role: "executor" })
|
||||
@@ -540,6 +556,41 @@ describe("AgentStore", () => {
|
||||
expect(updated.metadata).toEqual({ preserved: true }); // preserved
|
||||
});
|
||||
|
||||
it("updates soul and memory fields", async () => {
|
||||
const created = await store.createAgent({
|
||||
name: "Knowledge Agent",
|
||||
role: "executor",
|
||||
});
|
||||
|
||||
const updated = await store.updateAgent(created.id, {
|
||||
soul: "Collaborative, practical mentor",
|
||||
memory: "Avoids broad rewrites; prefers incremental changes.",
|
||||
});
|
||||
|
||||
expect(updated.soul).toBe("Collaborative, practical mentor");
|
||||
expect(updated.memory).toBe("Avoids broad rewrites; prefers incremental changes.");
|
||||
|
||||
const persisted = await store.getAgent(created.id);
|
||||
expect(persisted?.soul).toBe("Collaborative, practical mentor");
|
||||
expect(persisted?.memory).toBe("Avoids broad rewrites; prefers incremental changes.");
|
||||
});
|
||||
|
||||
it("does not clear soul when updates.soul is undefined", async () => {
|
||||
const created = await store.createAgent({
|
||||
name: "Stable Soul",
|
||||
role: "executor",
|
||||
soul: "Patient reviewer",
|
||||
});
|
||||
|
||||
const updated = await store.updateAgent(created.id, {
|
||||
soul: undefined,
|
||||
memory: "Remembers coding preferences",
|
||||
});
|
||||
|
||||
expect(updated.soul).toBe("Patient reviewer");
|
||||
expect(updated.memory).toBe("Remembers coding preferences");
|
||||
});
|
||||
|
||||
it("allows clearing optional fields via explicit undefined", async () => {
|
||||
const created = await store.createAgent({
|
||||
name: "Clearable",
|
||||
@@ -619,7 +670,7 @@ describe("AgentStore", () => {
|
||||
expect(revisions[0].after.name).toBe("Renamed");
|
||||
});
|
||||
|
||||
it("records revisions for runtimeConfig, permissions, instructionsPath, and instructionsText changes", async () => {
|
||||
it("records revisions for runtimeConfig, permissions, instructions, soul, and memory changes", async () => {
|
||||
const created = await store.createAgent({
|
||||
name: "Configurable",
|
||||
role: "executor",
|
||||
@@ -631,6 +682,8 @@ describe("AgentStore", () => {
|
||||
await store.updateAgent(created.id, { permissions: { canReview: true, canExecute: true } });
|
||||
await store.updateAgent(created.id, { instructionsPath: "docs/agent.md" });
|
||||
await store.updateAgent(created.id, { instructionsText: "Follow safety checks." });
|
||||
await store.updateAgent(created.id, { soul: "Thoughtful collaborator" });
|
||||
await store.updateAgent(created.id, { memory: "Knows the repository architecture" });
|
||||
|
||||
const revisions = await store.getConfigRevisions(created.id);
|
||||
const changedFields = revisions.flatMap((revision) => revision.diffs.map((diff) => diff.field));
|
||||
@@ -639,6 +692,8 @@ describe("AgentStore", () => {
|
||||
expect(changedFields).toContain("permissions");
|
||||
expect(changedFields).toContain("instructionsPath");
|
||||
expect(changedFields).toContain("instructionsText");
|
||||
expect(changedFields).toContain("soul");
|
||||
expect(changedFields).toContain("memory");
|
||||
});
|
||||
|
||||
it("does not create a revision when only non-config fields change", async () => {
|
||||
|
||||
@@ -102,6 +102,8 @@ interface AgentData {
|
||||
lastError?: string;
|
||||
instructionsPath?: string;
|
||||
instructionsText?: string;
|
||||
soul?: string;
|
||||
memory?: string;
|
||||
bundleConfig?: InstructionsBundleConfig;
|
||||
}
|
||||
interface AgentLock {
|
||||
@@ -173,6 +175,8 @@ export class AgentStore extends EventEmitter {
|
||||
...(input.permissions && { permissions: input.permissions }),
|
||||
...(input.instructionsPath && { instructionsPath: input.instructionsPath }),
|
||||
...(input.instructionsText && { instructionsText: input.instructionsText }),
|
||||
...(input.soul && { soul: input.soul }),
|
||||
...(input.memory && { memory: input.memory }),
|
||||
...(input.bundleConfig && { bundleConfig: input.bundleConfig }),
|
||||
};
|
||||
|
||||
@@ -628,6 +632,8 @@ export class AgentStore extends EventEmitter {
|
||||
...("totalOutputTokens" in updates && { totalOutputTokens: updates.totalOutputTokens }),
|
||||
...("instructionsPath" in updates && { instructionsPath: updates.instructionsPath }),
|
||||
...("instructionsText" in updates && { instructionsText: updates.instructionsText }),
|
||||
...(updates.soul !== undefined && { soul: updates.soul }),
|
||||
...(updates.memory !== undefined && { memory: updates.memory }),
|
||||
...("bundleConfig" in updates && { bundleConfig: updates.bundleConfig }),
|
||||
};
|
||||
|
||||
@@ -1544,6 +1550,8 @@ export class AgentStore extends EventEmitter {
|
||||
| "permissions"
|
||||
| "instructionsPath"
|
||||
| "instructionsText"
|
||||
| "soul"
|
||||
| "memory"
|
||||
| "bundleConfig"
|
||||
| "metadata"
|
||||
> {
|
||||
@@ -1557,6 +1565,8 @@ export class AgentStore extends EventEmitter {
|
||||
permissions: snapshot.permissions ? { ...snapshot.permissions } : undefined,
|
||||
instructionsPath: snapshot.instructionsPath,
|
||||
instructionsText: snapshot.instructionsText,
|
||||
soul: snapshot.soul,
|
||||
memory: snapshot.memory,
|
||||
bundleConfig: snapshot.bundleConfig
|
||||
? {
|
||||
...snapshot.bundleConfig,
|
||||
@@ -1756,6 +1766,8 @@ export class AgentStore extends EventEmitter {
|
||||
lastError: data.lastError,
|
||||
instructionsPath: data.instructionsPath,
|
||||
instructionsText: data.instructionsText,
|
||||
soul: data.soul,
|
||||
memory: data.memory,
|
||||
bundleConfig: data.bundleConfig,
|
||||
};
|
||||
}
|
||||
@@ -1783,6 +1795,8 @@ export class AgentStore extends EventEmitter {
|
||||
lastError: agent.lastError,
|
||||
instructionsPath: agent.instructionsPath,
|
||||
instructionsText: agent.instructionsText,
|
||||
soul: agent.soul,
|
||||
memory: agent.memory,
|
||||
bundleConfig: agent.bundleConfig,
|
||||
};
|
||||
|
||||
|
||||
@@ -1818,6 +1818,10 @@ export interface Agent {
|
||||
instructionsPath?: string;
|
||||
/** Inline custom instructions appended to the agent's system prompt at execution time. Max 50,000 chars. */
|
||||
instructionsText?: string;
|
||||
/** Agent personality/identity description — defines the agent's character, tone, and behavioral traits. Max 10,000 chars. */
|
||||
soul?: string;
|
||||
/** Per-agent accumulated knowledge — stores learnings, preferences, and context the agent has gathered. Max 50,000 chars. */
|
||||
memory?: string;
|
||||
/** Structured instruction bundle configuration for managed/external markdown files. */
|
||||
bundleConfig?: InstructionsBundleConfig;
|
||||
}
|
||||
@@ -1919,6 +1923,8 @@ export interface AgentCreateInput {
|
||||
permissions?: Record<string, boolean>;
|
||||
instructionsPath?: string;
|
||||
instructionsText?: string;
|
||||
soul?: string;
|
||||
memory?: string;
|
||||
bundleConfig?: InstructionsBundleConfig;
|
||||
}
|
||||
|
||||
@@ -1938,6 +1944,8 @@ export interface AgentUpdateInput {
|
||||
totalOutputTokens?: number;
|
||||
instructionsPath?: string;
|
||||
instructionsText?: string;
|
||||
soul?: string;
|
||||
memory?: string;
|
||||
bundleConfig?: InstructionsBundleConfig;
|
||||
}
|
||||
|
||||
@@ -2028,6 +2036,8 @@ export interface AgentConfigSnapshot {
|
||||
permissions?: Record<string, boolean>;
|
||||
instructionsPath?: string;
|
||||
instructionsText?: string;
|
||||
soul?: string;
|
||||
memory?: string;
|
||||
bundleConfig?: InstructionsBundleConfig;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
@@ -2073,6 +2083,8 @@ export function agentToConfigSnapshot(agent: Agent): AgentConfigSnapshot {
|
||||
permissions: agent.permissions ? { ...agent.permissions } : undefined,
|
||||
instructionsPath: agent.instructionsPath,
|
||||
instructionsText: agent.instructionsText,
|
||||
soul: agent.soul,
|
||||
memory: agent.memory,
|
||||
bundleConfig: agent.bundleConfig
|
||||
? {
|
||||
...agent.bundleConfig,
|
||||
@@ -2098,6 +2110,8 @@ export function diffConfigSnapshots(
|
||||
"permissions",
|
||||
"instructionsPath",
|
||||
"instructionsText",
|
||||
"soul",
|
||||
"memory",
|
||||
"bundleConfig",
|
||||
"metadata",
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user