feat(agents): let a custom agent speak ACP (#3863)
Co-authored-by: Joao Manso <X363081@bcpcorp.net> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
This commit is contained in:
@@ -640,6 +640,15 @@ export class ApiClient {
|
||||
return this.request("GET", "/api/v1/agents/available");
|
||||
}
|
||||
|
||||
/** Removes a custom agent by slug, so a spec that creates one leaves the
|
||||
* worker's agent list as it found it. Missing is not an error. */
|
||||
async deleteCustomAgentByName(name: string): Promise<void> {
|
||||
const { agents } = await this.listAgents();
|
||||
const agent = agents.find((candidate) => candidate.name === name);
|
||||
if (!agent) return;
|
||||
await this.request("DELETE", `/api/v1/agents/${agent.id}`);
|
||||
}
|
||||
|
||||
async deleteAgentProfile(profileId: string, force?: boolean): Promise<void> {
|
||||
const qs = force ? "?force=true" : "";
|
||||
await this.request("DELETE", `/api/v1/agent-profiles/${profileId}${qs}`);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from "../../fixtures/test-base";
|
||||
|
||||
const AGENT_NAME = "E2E Acp Agent";
|
||||
const AGENT_SLUG = "e2e-acp-agent";
|
||||
|
||||
test.describe("Custom ACP agent", () => {
|
||||
test.afterEach(async ({ apiClient }) => {
|
||||
await apiClient.deleteCustomAgentByName(AGENT_SLUG);
|
||||
});
|
||||
|
||||
test("registers a custom agent kandev drives over ACP", async ({ testPage, apiClient }) => {
|
||||
await testPage.goto("/settings/agents");
|
||||
await testPage.getByTestId("new-agent-button").click();
|
||||
|
||||
const dialog = testPage.getByRole("dialog", { name: "Add custom agent" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.getByTestId("mcp-strategy-select")).toBeVisible();
|
||||
|
||||
await dialog.getByTestId("agent-protocol-select").click();
|
||||
await testPage.getByRole("listbox").getByRole("option").filter({ hasText: /^ACP/ }).click();
|
||||
|
||||
// Resolved MCP servers reach an ACP agent through session/new, so the
|
||||
// passthrough strategy picker is not offered for it.
|
||||
await expect(dialog.getByTestId("mcp-strategy-select")).toHaveCount(0);
|
||||
|
||||
await dialog.getByLabel("Display Name").fill(AGENT_NAME);
|
||||
await dialog.getByLabel("Command").fill(`${AGENT_SLUG} --acp`);
|
||||
await dialog.getByRole("button", { name: "Create" }).click();
|
||||
|
||||
await expect(testPage.getByTestId(`agent-group-${AGENT_SLUG}`)).toBeVisible();
|
||||
await expect(testPage.getByTestId(`agent-mcp-strategy-${AGENT_SLUG}`)).toHaveCount(0);
|
||||
|
||||
// The stored definition is what a restart replays, so it has to say ACP
|
||||
// rather than leaving the agent to come back as a terminal one.
|
||||
const { agents } = await apiClient.listAgents();
|
||||
const created = agents.find((agent) => agent.name === AGENT_SLUG);
|
||||
expect(created?.tui_config?.protocol).toBe("acp");
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ test.describe("Custom TUI MCP strategy selector", () => {
|
||||
await testPage.goto("/settings/agents");
|
||||
await testPage.getByTestId("new-agent-button").click();
|
||||
|
||||
const dialog = testPage.getByRole("dialog", { name: "Add TUI Agent" });
|
||||
const dialog = testPage.getByRole("dialog", { name: "Add custom agent" });
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
const trigger = dialog.getByTestId("mcp-strategy-select");
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { test, expect } from "../../fixtures/test-base";
|
||||
|
||||
const AGENT_NAME = "Mobile ACP Agent";
|
||||
const AGENT_SLUG = "mobile-acp-agent";
|
||||
|
||||
test.describe("Custom ACP agent on mobile", () => {
|
||||
test.afterEach(async ({ apiClient }) => {
|
||||
await apiClient.deleteCustomAgentByName(AGENT_SLUG);
|
||||
});
|
||||
|
||||
test("selects ACP and keeps the protocol controls usable", async ({ testPage, apiClient }) => {
|
||||
await testPage.goto("/settings/agents");
|
||||
await testPage.getByTestId("new-agent-button").tap();
|
||||
|
||||
const dialog = testPage.getByRole("dialog", { name: "Add custom agent" });
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
const protocol = dialog.getByTestId("agent-protocol-select");
|
||||
await expect
|
||||
.poll(async () => (await protocol.boundingBox())?.height ?? 0)
|
||||
.toBeGreaterThanOrEqual(44);
|
||||
|
||||
await protocol.tap();
|
||||
const listbox = testPage.getByRole("listbox");
|
||||
const acpOption = listbox.getByRole("option", { name: "ACP" });
|
||||
await expect(acpOption).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => (await acpOption.boundingBox())?.height ?? 0)
|
||||
.toBeGreaterThanOrEqual(44);
|
||||
await acpOption.tap();
|
||||
|
||||
await expect(dialog.getByTestId("mcp-strategy-select")).toHaveCount(0);
|
||||
await dialog.getByLabel("Display Name").fill(AGENT_NAME);
|
||||
await dialog.getByLabel("Command").fill(`${AGENT_SLUG} --acp`);
|
||||
await dialog.getByRole("button", { name: "Create" }).tap();
|
||||
|
||||
await expect(testPage.getByTestId(`agent-group-${AGENT_SLUG}`)).toBeVisible();
|
||||
const { agents } = await apiClient.listAgents();
|
||||
const created = agents.find((agent) => agent.name === AGENT_SLUG);
|
||||
expect(created?.tui_config?.protocol).toBe("acp");
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ test.describe("Custom TUI MCP strategy selector on mobile", () => {
|
||||
await testPage.goto("/settings/agents");
|
||||
await testPage.getByTestId("new-agent-button").tap();
|
||||
|
||||
const dialog = testPage.getByRole("dialog", { name: "Add TUI Agent" });
|
||||
const dialog = testPage.getByRole("dialog", { name: "Add custom agent" });
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
const trigger = dialog.getByTestId("mcp-strategy-select");
|
||||
|
||||
Reference in New Issue
Block a user