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[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 ────────────────────────────────────────────────
|
||||
|
||||
@@ -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).
|
||||
* These agents are created by the engine for task execution and should typically be
|
||||
* hidden from the default agents page listing.
|
||||
* Detect if an agent is a runtime-created ephemeral/internal agent.
|
||||
* These agents are created by the engine for task execution/system workflows and should
|
||||
* typically be hidden from the default agents page listing.
|
||||
*
|
||||
* Detection heuristics (returns true if ANY match):
|
||||
* - `agent.metadata?.agentKind === "task-worker"` — task-worker agents from InProcessRuntime
|
||||
* - `agent.metadata?.taskWorker === true` — legacy task-worker marker
|
||||
* - `agent.metadata?.managedBy === "task-executor"` — executor-managed agents
|
||||
* - `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 named "verification-agent" with no reportsTo
|
||||
*
|
||||
* @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(
|
||||
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.managedBy === "task-executor") return true;
|
||||
if (metadata.type === "spawned") return true;
|
||||
if (metadata.internal === true) return true;
|
||||
|
||||
// Legacy fallback: executor agents with "executor-" prefix and no manager
|
||||
// These are task workers that were created before metadata was standardized
|
||||
@@ -2070,6 +2073,15 @@ export function isEphemeralAgent(
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -266,12 +266,17 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
|
||||
|
||||
// Filter agents for display. "All States" means all non-ephemeral agents,
|
||||
// including disabled/terminated agents that still carry configuration.
|
||||
// When "Show system agents" is enabled, include ephemeral/internal agents.
|
||||
const displayAgents = useMemo(() => {
|
||||
return agents.filter(a => !isEphemeralAgent(a));
|
||||
}, [agents]);
|
||||
return agents.filter((agent) => showSystemAgents || !isEphemeralAgent(agent));
|
||||
}, [agents, showSystemAgents]);
|
||||
|
||||
// Filter org tree to exclude ephemeral agents in default view.
|
||||
const displayOrgTree = useMemo(() => {
|
||||
if (showSystemAgents) {
|
||||
return orgTree;
|
||||
}
|
||||
|
||||
// Recursively filter out ephemeral agents from the org tree.
|
||||
const filterNode = (node: OrgTreeNode): OrgTreeNode | null => {
|
||||
if (isEphemeralAgent(node.agent)) return null;
|
||||
@@ -285,7 +290,7 @@ export function AgentsView({ addToast, projectId }: AgentsViewProps) {
|
||||
return orgTree
|
||||
.map(filterNode)
|
||||
.filter((n): n is OrgTreeNode => n !== null);
|
||||
}, [orgTree]);
|
||||
}, [orgTree, showSystemAgents]);
|
||||
|
||||
const loadAgents = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -628,6 +628,7 @@ describe("AgentsView", () => {
|
||||
// Now the agents should be reloaded with system agents included
|
||||
await waitFor(() => {
|
||||
expect(mockFetchAgents).toHaveBeenCalledWith({ includeEphemeral: true }, projectId);
|
||||
expect(screen.getByText("executor-FN-TEST")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user