feat(FN-1449): complete agent UI field parity with Agent Companies

This change aligns agent editing surfaces across dashboard UI, API payloads,
and import/template flows so every first-class editable agent field is
round-trippable.

Backend changes:
- Add memory and bundleConfig fields to POST/PATCH /api/agents routes
- Fix agent-companies-parser to use first-class fields (title, icon, role,
  reportsTo, instructionsText) instead of metadata fallbacks
- Update import dry-run preview to show more manifest fields

UI changes:
- Enable identity field editing (name, title, icon, role, reportsTo) in
  AgentDetailView ConfigTab
- Add instruction bundle configuration (mode, entry file, files, external
  path) to ConfigTab
- Add memory field to NewAgentDialog
- Fix AI generation mapping to preserve systemPrompt as instructionsText
- Update AgentImportModal preview to show icon, reportsTo, and instructions

Test fixes:
- Update parser tests for new first-class field behavior
- Add MissionExecutionLoop mock for serve tests

Documentation:
- Add agent field parity matrix to docs/agents.md
- Create changeset for @gsxdsm/fusion
This commit is contained in:
gsxdsm
2026-04-11 19:02:32 -07:00
parent bc3688badf
commit 9b56460ac4
9 changed files with 383 additions and 19 deletions

View File

@@ -357,10 +357,9 @@ name: Zip CEO
name: "CEO",
role: "custom",
title: "Chief Executive Officer",
instructionsText: "Lead strategy",
metadata: {
instructions: "Lead strategy",
skills: ["review"],
reportsTo: null,
sources: [{ kind: "git", repo: "acme/repo" }],
},
});
@@ -391,6 +390,45 @@ name: Zip CEO
const input = agentManifestToAgentCreateInput({ name: "Generalist" });
expect(input.role).toBe("custom");
});
it("maps manifest icon to first-class field", () => {
const input = agentManifestToAgentCreateInput({
name: "Bot",
icon: "🤖",
role: "executor",
});
expect(input).toEqual({
name: "Bot",
role: "executor",
icon: "🤖",
});
});
it("maps manifest reportsTo to first-class field", () => {
const input = agentManifestToAgentCreateInput({
name: "Worker",
reportsTo: "manager-001",
});
expect(input).toEqual({
name: "Worker",
role: "custom",
reportsTo: "manager-001",
});
});
it("maps manifest role to first-class field", () => {
const input = agentManifestToAgentCreateInput({
name: "Reviewer",
role: "reviewer",
});
expect(input).toEqual({
name: "Reviewer",
role: "reviewer",
});
});
});
describe("mapRoleToCapability", () => {

View File

@@ -284,25 +284,29 @@ export async function parseCompanyArchive(archivePath: string): Promise<AgentCom
export function agentManifestToAgentCreateInput(agent: AgentManifest): AgentCreateInput {
const metadata: Record<string, unknown> = {};
if (typeof agent.instructionBody === "string") {
metadata.instructions = agent.instructionBody;
}
// Store skills and metadata sources in metadata (skills is not a first-class field)
if (Array.isArray(agent.skills) && agent.skills.length > 0) {
metadata.skills = agent.skills;
}
if (agent.reportsTo !== undefined) {
metadata.reportsTo = agent.reportsTo;
}
if (Array.isArray(agent.metadata?.sources) && agent.metadata.sources.length > 0) {
metadata.sources = agent.metadata.sources;
}
return {
name: agent.name,
role: mapRoleToCapability("custom"),
role: agent.role ? mapRoleToCapability(agent.role) : mapRoleToCapability("custom"),
...(typeof agent.title === "string" && agent.title.trim().length > 0
? { title: agent.title }
: {}),
...(typeof agent.icon === "string" && agent.icon.trim().length > 0
? { icon: agent.icon.trim() }
: {}),
...(typeof agent.reportsTo === "string" && agent.reportsTo.trim().length > 0
? { reportsTo: agent.reportsTo.trim() }
: {}),
...(typeof agent.instructionBody === "string" && agent.instructionBody.trim().length > 0
? { instructionsText: agent.instructionBody.trim() }
: {}),
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
};
}