fix(FN-2210): classify legacy verification agents as ephemeral
- Extend isEphemeralAgent to treat metadata.internal agents as internal system agents - Add legacy fallback detection for executor agents named verification-agent with no reportsTo - Update AgentsView list and org tree filtering to honor the Show system agents toggle - Add regression coverage in core and dashboard tests for default filtering and includeEphemeral visibility
This commit is contained in:
@@ -1097,6 +1097,38 @@ describe("AgentStore", () => {
|
|||||||
expect(activeAll).toHaveLength(1);
|
expect(activeAll).toHaveLength(1);
|
||||||
expect(activeAll[0].id).toBe(taskWorker.id);
|
expect(activeAll[0].id).toBe(taskWorker.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("filters out agents marked with metadata.internal", async () => {
|
||||||
|
const normal = await store.createAgent({ name: "Normal Agent", role: "executor" });
|
||||||
|
await store.createAgent({
|
||||||
|
name: "internal-agent",
|
||||||
|
role: "executor",
|
||||||
|
metadata: { internal: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultAgents = await store.listAgents();
|
||||||
|
expect(defaultAgents).toHaveLength(1);
|
||||||
|
expect(defaultAgents[0].id).toBe(normal.id);
|
||||||
|
|
||||||
|
const includingEphemeral = await store.listAgents({ includeEphemeral: true });
|
||||||
|
expect(includingEphemeral).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filters legacy verification-agent fallback by default", async () => {
|
||||||
|
const normal = await store.createAgent({ name: "Normal Agent", role: "executor" });
|
||||||
|
await store.createAgent({
|
||||||
|
name: "verification-agent",
|
||||||
|
role: "executor",
|
||||||
|
metadata: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultAgents = await store.listAgents();
|
||||||
|
expect(defaultAgents).toHaveLength(1);
|
||||||
|
expect(defaultAgents[0].id).toBe(normal.id);
|
||||||
|
|
||||||
|
const includingEphemeral = await store.listAgents({ includeEphemeral: true });
|
||||||
|
expect(includingEphemeral).toHaveLength(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Org Hierarchy ────────────────────────────────────────────────
|
// ── Org Hierarchy ────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -2034,19 +2034,21 @@ export const AGENT_VALID_TRANSITIONS: Record<AgentState, AgentState[]> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect if an agent is a runtime-created ephemeral agent (task-worker or spawned child).
|
* Detect if an agent is a runtime-created ephemeral/internal agent.
|
||||||
* These agents are created by the engine for task execution and should typically be
|
* These agents are created by the engine for task execution/system workflows and should
|
||||||
* hidden from the default agents page listing.
|
* typically be hidden from the default agents page listing.
|
||||||
*
|
*
|
||||||
* Detection heuristics (returns true if ANY match):
|
* Detection heuristics (returns true if ANY match):
|
||||||
* - `agent.metadata?.agentKind === "task-worker"` — task-worker agents from InProcessRuntime
|
* - `agent.metadata?.agentKind === "task-worker"` — task-worker agents from InProcessRuntime
|
||||||
* - `agent.metadata?.taskWorker === true` — legacy task-worker marker
|
* - `agent.metadata?.taskWorker === true` — legacy task-worker marker
|
||||||
* - `agent.metadata?.managedBy === "task-executor"` — executor-managed agents
|
* - `agent.metadata?.managedBy === "task-executor"` — executor-managed agents
|
||||||
* - `agent.metadata?.type === "spawned"` — spawned child agents from TaskExecutor
|
* - `agent.metadata?.type === "spawned"` — spawned child agents from TaskExecutor
|
||||||
|
* - `agent.metadata?.internal === true` — explicitly internal/system agent marker
|
||||||
* - Legacy fallback: executor role with name starting with "executor-" and no reportsTo
|
* - Legacy fallback: executor role with name starting with "executor-" and no reportsTo
|
||||||
|
* - Legacy fallback: executor role named "verification-agent" with no reportsTo
|
||||||
*
|
*
|
||||||
* @param agent - Agent object (partial shape accepted)
|
* @param agent - Agent object (partial shape accepted)
|
||||||
* @returns true if the agent is an ephemeral/runtime-created agent
|
* @returns true if the agent is an ephemeral/runtime-created/internal system agent
|
||||||
*/
|
*/
|
||||||
export function isEphemeralAgent(
|
export function isEphemeralAgent(
|
||||||
agent: { metadata?: Record<string, unknown> | null; name?: string; role?: string; reportsTo?: string | null },
|
agent: { metadata?: Record<string, unknown> | null; name?: string; role?: string; reportsTo?: string | null },
|
||||||
@@ -2058,6 +2060,7 @@ export function isEphemeralAgent(
|
|||||||
if (metadata.taskWorker === true) return true;
|
if (metadata.taskWorker === true) return true;
|
||||||
if (metadata.managedBy === "task-executor") return true;
|
if (metadata.managedBy === "task-executor") return true;
|
||||||
if (metadata.type === "spawned") return true;
|
if (metadata.type === "spawned") return true;
|
||||||
|
if (metadata.internal === true) return true;
|
||||||
|
|
||||||
// Legacy fallback: executor agents with "executor-" prefix and no manager
|
// Legacy fallback: executor agents with "executor-" prefix and no manager
|
||||||
// These are task workers that were created before metadata was standardized
|
// These are task workers that were created before metadata was standardized
|
||||||
@@ -2070,6 +2073,15 @@ export function isEphemeralAgent(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Legacy internal system agent used by older verification flows.
|
||||||
|
if (
|
||||||
|
agent.role === "executor" &&
|
||||||
|
agent.name === "verification-agent" &&
|
||||||
|
agent.reportsTo == null
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -266,12 +266,17 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
|
|||||||
|
|
||||||
// Filter agents for display. "All States" means all non-ephemeral agents,
|
// Filter agents for display. "All States" means all non-ephemeral agents,
|
||||||
// including disabled/terminated agents that still carry configuration.
|
// including disabled/terminated agents that still carry configuration.
|
||||||
|
// When "Show system agents" is enabled, include ephemeral/internal agents.
|
||||||
const displayAgents = useMemo(() => {
|
const displayAgents = useMemo(() => {
|
||||||
return agents.filter(a => !isEphemeralAgent(a));
|
return agents.filter((agent) => showSystemAgents || !isEphemeralAgent(agent));
|
||||||
}, [agents]);
|
}, [agents, showSystemAgents]);
|
||||||
|
|
||||||
// Filter org tree to exclude ephemeral agents in default view.
|
// Filter org tree to exclude ephemeral agents in default view.
|
||||||
const displayOrgTree = useMemo(() => {
|
const displayOrgTree = useMemo(() => {
|
||||||
|
if (showSystemAgents) {
|
||||||
|
return orgTree;
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively filter out ephemeral agents from the org tree.
|
// Recursively filter out ephemeral agents from the org tree.
|
||||||
const filterNode = (node: OrgTreeNode): OrgTreeNode | null => {
|
const filterNode = (node: OrgTreeNode): OrgTreeNode | null => {
|
||||||
if (isEphemeralAgent(node.agent)) return null;
|
if (isEphemeralAgent(node.agent)) return null;
|
||||||
@@ -285,7 +290,7 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
|
|||||||
return orgTree
|
return orgTree
|
||||||
.map(filterNode)
|
.map(filterNode)
|
||||||
.filter((n): n is OrgTreeNode => n !== null);
|
.filter((n): n is OrgTreeNode => n !== null);
|
||||||
}, [orgTree]);
|
}, [orgTree, showSystemAgents]);
|
||||||
|
|
||||||
const loadAgents = useCallback(async () => {
|
const loadAgents = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|||||||
@@ -628,6 +628,7 @@ describe("AgentsView", () => {
|
|||||||
// Now the agents should be reloaded with system agents included
|
// Now the agents should be reloaded with system agents included
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mockFetchAgents).toHaveBeenCalledWith({ includeEphemeral: true }, projectId);
|
expect(mockFetchAgents).toHaveBeenCalledWith({ includeEphemeral: true }, projectId);
|
||||||
|
expect(screen.getByText("executor-FN-TEST")).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user