feat(FN-3327): add agent delegation and org hierarchy tools to pi extension

Adds `list_agents` and `delegate_task` tools to the pi extension for inter-agent delegation, along with supporting reference docs and 248 new tests. The sync script was also updated to keep the skill tools in sync with the extension implementation.

Fusion-Task-Id: FN-3327
This commit is contained in:
Fusion
2026-05-04 02:05:32 -07:00
committed by gsxdsm
parent 94fdbc3481
commit cbb87ae00e
7 changed files with 669 additions and 2 deletions

View File

@@ -185,6 +185,10 @@ describe.skipIf(!SHOULD_RUN_EXTENSION_INTEGRATION)("fn pi extension", () => {
"fn_feature_link_task",
"fn_agent_stop",
"fn_agent_start",
"fn_list_agents",
"fn_delegate_task",
"fn_agent_show",
"fn_agent_org_chart",
"fn_skills_search",
"fn_skills_install",
] as const;
@@ -1262,4 +1266,248 @@ describe("fn pi extension (runnable structured-output regression slice)", () =>
expect(result.details.error).toContain("ephemeral/runtime agent");
expect(result.content[0].text).toContain(ephemeralId);
});
describe("fn_list_agents", () => {
it("returns agent list", async () => {
await seedAgent(tmpDir, { name: "alpha-agent" });
await seedAgent(tmpDir, { name: "beta-agent" });
const tool = api.tools.get("fn_list_agents")!;
const result = await tool.execute("la-1", {}, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("alpha-agent");
expect(result.content[0].text).toContain("beta-agent");
expect(result.details.count).toBeGreaterThanOrEqual(2);
});
it("filters by role", async () => {
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
await agentStore.init();
await agentStore.createAgent({ name: "exec-agent", role: "executor", metadata: {} });
await agentStore.createAgent({ name: "review-agent", role: "reviewer", metadata: {} });
const tool = api.tools.get("fn_list_agents")!;
const result = await tool.execute("la-2", { role: "executor" }, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("exec-agent");
expect(result.content[0].text).not.toContain("review-agent");
});
it("filters by state", async () => {
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
await agentStore.init();
const active = await agentStore.createAgent({ name: "active-agent", role: "executor", metadata: {} });
await agentStore.updateAgentState(active.id, "active");
const tool = api.tools.get("fn_list_agents")!;
const result = await tool.execute("la-3", { state: "active" }, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("active-agent");
expect(result.details.agents.every((a: any) => a.state === "active")).toBe(true);
});
it("excludes ephemeral agents by default", async () => {
const ephemeralId = await seedAgent(tmpDir, { ephemeral: true, name: "eph-agent" });
await seedAgent(tmpDir, { name: "real-agent" });
const tool = api.tools.get("fn_list_agents")!;
const result = await tool.execute("la-4", {}, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).not.toContain("eph-agent");
expect(result.content[0].text).toContain("real-agent");
expect(result.details.agents.every((a: any) => a.id !== ephemeralId)).toBe(true);
});
it("returns empty list message when no agents", async () => {
const tool = api.tools.get("fn_list_agents")!;
const result = await tool.execute("la-5", {}, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("No agents found");
expect(result.details.count).toBe(0);
});
});
describe("fn_delegate_task", () => {
it("delegates task to agent", async () => {
const agentId = await seedAgent(tmpDir, { name: "delegate-target" });
const tool = api.tools.get("fn_delegate_task")!;
const result = await tool.execute(
"dt-1",
{ agent_id: agentId, description: "Do important work" },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.content[0].text).toContain("delegate-target");
expect(result.content[0].text).toContain(agentId);
expect(result.details.agentId).toBe(agentId);
expect(result.details.agentName).toBe("delegate-target");
expect(result.details.taskId).toBeTruthy();
// Verify task was actually created
const store = new TaskStore(tmpDir);
await store.init();
const task = await store.getTask(result.details.taskId);
expect(task).toBeTruthy();
expect(task!.assignedAgentId).toBe(agentId);
expect(task!.column).toBe("todo");
});
it("rejects unknown agent", async () => {
const tool = api.tools.get("fn_delegate_task")!;
const result = await tool.execute(
"dt-2",
{ agent_id: "agent-no-such", description: "Will fail" },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("not found");
});
it("rejects ephemeral agent", async () => {
const ephemeralId = await seedAgent(tmpDir, { ephemeral: true, name: "eph-delegate" });
const tool = api.tools.get("fn_delegate_task")!;
const result = await tool.execute(
"dt-3",
{ agent_id: ephemeralId, description: "Will fail" },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("ephemeral/runtime agent");
});
it("wires dependencies correctly", async () => {
const agentId = await seedAgent(tmpDir, { name: "dep-agent" });
// Create a real task to use as a dependency
const store = new TaskStore(tmpDir);
await store.init();
const depTask = await store.createTask({ description: "Prerequisite", column: "todo" });
const tool = api.tools.get("fn_delegate_task")!;
const result = await tool.execute(
"dt-4",
{ agent_id: agentId, description: "Dependent work", dependencies: [depTask.id] },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.content[0].text).toContain(depTask.id);
const task = await store.getTask(result.details.taskId);
expect(task!.dependencies).toEqual([depTask.id]);
});
});
describe("fn_agent_show", () => {
it("shows agent by ID", async () => {
const agentId = await seedAgent(tmpDir, { name: "show-agent" });
const tool = api.tools.get("fn_agent_show")!;
const result = await tool.execute("as-1", { id: agentId }, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("show-agent");
expect(result.content[0].text).toContain(agentId);
expect(result.details.agent.id).toBe(agentId);
});
it("shows agent by name", async () => {
await seedAgent(tmpDir, { name: "resolve-by-name" });
const tool = api.tools.get("fn_agent_show")!;
const result = await tool.execute("as-2", { id: "resolve-by-name" }, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("resolve-by-name");
expect(result.details.agent.name).toBe("resolve-by-name");
});
it("returns error for unknown agent", async () => {
const tool = api.tools.get("fn_agent_show")!;
const result = await tool.execute("as-3", { id: "no-such-agent" }, undefined, undefined, makeCtx(tmpDir));
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("not found");
});
it("shows reports-to and direct reports", async () => {
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
await agentStore.init();
const manager = await agentStore.createAgent({ name: "the-manager", role: "executor", metadata: {} });
const report = await agentStore.createAgent({
name: "the-report",
role: "executor",
reportsTo: manager.id,
metadata: {},
});
const tool = api.tools.get("fn_agent_show")!;
// Check manager sees direct reports
const mgrResult = await tool.execute("as-4a", { id: manager.id }, undefined, undefined, makeCtx(tmpDir));
expect(mgrResult.content[0].text).toContain("the-report");
expect(mgrResult.details.directReports.length).toBeGreaterThan(0);
// Check report sees reports-to
const rptResult = await tool.execute("as-4b", { id: report.id }, undefined, undefined, makeCtx(tmpDir));
expect(rptResult.content[0].text).toContain("the-manager");
});
});
describe("fn_agent_org_chart", () => {
it("returns full tree", async () => {
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
await agentStore.init();
await agentStore.createAgent({ name: "ceo", role: "executor", metadata: {} });
await agentStore.createAgent({ name: "worker", role: "executor", metadata: {} });
const tool = api.tools.get("fn_agent_org_chart")!;
const result = await tool.execute("oc-1", {}, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("ceo");
expect(result.content[0].text).toContain("worker");
expect(result.details.count).toBeGreaterThanOrEqual(2);
});
it("returns subtree by root agent", async () => {
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
await agentStore.init();
const manager = await agentStore.createAgent({ name: "org-manager", role: "executor", metadata: {} });
await agentStore.createAgent({ name: "org-report", role: "executor", reportsTo: manager.id, metadata: {} });
const tool = api.tools.get("fn_agent_org_chart")!;
const result = await tool.execute("oc-2", { root_agent_id: manager.id }, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("org-manager");
expect(result.content[0].text).toContain("org-report");
});
it("returns empty message when no agents", async () => {
const tool = api.tools.get("fn_agent_org_chart")!;
const result = await tool.execute("oc-3", {}, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("No agents found");
expect(result.details.count).toBe(0);
});
it("returns single agent for lone agent", async () => {
const agentStore = new AgentStore({ rootDir: join(tmpDir, ".fusion") });
await agentStore.init();
const lone = await agentStore.createAgent({ name: "lone-agent", role: "executor", metadata: {} });
const tool = api.tools.get("fn_agent_org_chart")!;
const result = await tool.execute("oc-4", { root_agent_id: lone.id }, undefined, undefined, makeCtx(tmpDir));
expect(result.content[0].text).toContain("lone-agent");
});
});
});