improve goose2 agent management flows (#8737)

Signed-off-by: tulsi <tulsi@block.xyz>
This commit is contained in:
tulsi
2026-04-21 17:54:48 -07:00
committed by GitHub
parent 23b3b3dcac
commit 7e2fb3ee5c
46 changed files with 1219 additions and 524 deletions
+3 -3
View File
@@ -13,9 +13,9 @@ test.describe("Draft persistence", () => {
// Wait for the 300ms debounce to persist the draft
await page.waitForTimeout(500);
// Navigate away to Personas
await page.getByRole("button", { name: "Personas" }).click();
await expect(page.locator("h1", { hasText: "Personas" })).toBeVisible();
// Navigate away to Agents
await page.getByRole("button", { name: "Agents" }).click();
await expect(page.locator("h1", { hasText: "Agents" })).toBeVisible();
// Navigate back to Home
await page.getByRole("button", { name: "Home" }).click();
+58 -6
View File
@@ -36,6 +36,58 @@ export function buildInitScript(options?: {
const PROJECTS = ${projects};
const FAKE_ACP_URL = "ws://127.0.0.1:0/mock-acp";
const ACP_SESSIONS = [];
const PROVIDER_INVENTORY = [
{
providerId: "claude",
providerName: "Claude",
description: "Claude provider",
defaultModel: "claude-sonnet-4-20250514",
configured: true,
providerType: "Preferred",
configKeys: [],
setupSteps: [],
supportsRefresh: true,
refreshing: false,
lastUpdatedAt: null,
lastRefreshAttemptAt: null,
lastRefreshError: null,
stale: false,
modelSelectionHint: null,
models: [
{
id: "claude-sonnet-4-20250514",
name: "Claude Sonnet 4",
family: "Claude",
recommended: true,
},
],
},
{
providerId: "openai",
providerName: "OpenAI",
description: "OpenAI provider",
defaultModel: "gpt-4.1",
configured: true,
providerType: "Preferred",
configKeys: [],
setupSteps: [],
supportsRefresh: true,
refreshing: false,
lastUpdatedAt: null,
lastRefreshAttemptAt: null,
lastRefreshError: null,
stale: false,
modelSelectionHint: null,
models: [
{
id: "gpt-4.1",
name: "GPT-4.1",
family: "OpenAI",
recommended: true,
},
],
},
];
const skillToSourceEntry = (s) => ({
type: "skill",
@@ -126,7 +178,7 @@ export function buildInitScript(options?: {
return jsonRpcResult(message.id, { stopReason: "end_turn" });
}
case "_goose/providers/list":
return jsonRpcResult(message.id, { entries: [] });
return jsonRpcResult(message.id, { entries: PROVIDER_INVENTORY });
case "_goose/providers/inventory/refresh":
return jsonRpcResult(message.id, { started: [], skipped: [] });
case "_goose/working_dir/update":
@@ -222,7 +274,7 @@ export function buildInitScript(options?: {
case "create_persona":
return Promise.resolve({
id: "mock-" + Math.random().toString(36).slice(2, 10),
displayName: args?.displayName ?? "New Persona",
displayName: args?.displayName ?? "New Agent",
systemPrompt: args?.systemPrompt ?? "",
isBuiltin: false,
createdAt: new Date().toISOString(),
@@ -233,7 +285,7 @@ export function buildInitScript(options?: {
case "update_persona":
return Promise.resolve({
id: args?.id ?? "mock-updated",
displayName: args?.displayName ?? "Updated Persona",
displayName: args?.displayName ?? "Updated Agent",
systemPrompt: args?.systemPrompt ?? "",
isBuiltin: false,
createdAt: new Date().toISOString(),
@@ -348,13 +400,13 @@ export async function waitForHome(page: Page) {
});
}
export async function navigateToPersonas(page: Page) {
export async function navigateToAgents(page: Page) {
await page.goto("/");
await expect(page.getByText(/Good (morning|afternoon|evening)/)).toBeVisible({
timeout: 10_000,
});
await page.getByRole("button", { name: "Personas" }).click();
await expect(page.locator("h1", { hasText: "Personas" })).toBeVisible();
await page.getByRole("button", { name: "Agents" }).click();
await expect(page.locator("h1", { hasText: "Agents" })).toBeVisible();
}
export async function navigateToSkills(page: Page) {
+117 -107
View File
@@ -1,150 +1,155 @@
import {
test,
expect,
navigateToPersonas,
navigateToAgents,
buildInitScript,
} from "./fixtures/tauri-mock";
test.describe("Personas view", () => {
test("navigates to personas view from sidebar", async ({
test.describe("Agents view", () => {
test("navigates to agents view from sidebar", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
// Assert heading, subtitle, and sections are visible
await expect(page.locator("h1", { hasText: "Personas" })).toBeVisible();
await expect(page.getByText("Custom persona configurations")).toBeVisible();
await navigateToAgents(page);
await expect(page.locator("h1", { hasText: "Agents" })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Active Agents" }),
page.getByText("Custom agent configurations for specific workflows"),
).toBeVisible();
await expect(page.getByText("No active agents")).toBeVisible();
});
test("displays persona cards from mock data", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
// All 3 persona cards should be visible with their aria-labels
await expect(page.getByLabel("Persona: Solo")).toBeVisible();
await expect(page.getByLabel("Persona: Scout")).toBeVisible();
await expect(page.getByLabel("Persona: Code Reviewer")).toBeVisible();
test("displays agent cards from mock data", async ({ tauriMocked: page }) => {
await navigateToAgents(page);
await expect(page.getByLabel("Agent: Solo")).toBeVisible();
await expect(page.getByLabel("Agent: Scout")).toBeVisible();
await expect(page.getByLabel("Agent: Code Reviewer")).toBeVisible();
});
test("shows Built-in badge on builtin personas", async ({
test("shows Built-in badge on built-in agents", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
// Solo and Scout are builtin — their cards should contain "Built-in" text
const soloCard = page.getByLabel("Persona: Solo");
await navigateToAgents(page);
const soloCard = page.getByLabel("Agent: Solo");
await expect(soloCard.getByText("Built-in")).toBeVisible();
const reviewerCard = page.getByLabel("Persona: Code Reviewer");
const reviewerCard = page.getByLabel("Agent: Code Reviewer");
await expect(reviewerCard.getByText("Built-in")).not.toBeVisible();
});
test("shows create new persona button", async ({ tauriMocked: page }) => {
await navigateToPersonas(page);
await expect(page.getByLabel("Create new persona")).toBeVisible();
test("shows create new agent button", async ({ tauriMocked: page }) => {
await navigateToAgents(page);
await expect(page.getByLabel("Create new agent")).toBeVisible();
});
test("opens create persona dialog via New Persona button", async ({
test("opens create agent dialog via New Agent button", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await navigateToAgents(page);
await page
.getByRole("button", { name: "New Persona", exact: true })
.getByRole("button", { name: "New Agent", exact: true })
.first()
.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
await expect(
dialog.locator("h2", { hasText: "New Persona" }),
).toBeVisible();
// Check form fields
await expect(dialog.locator("h2", { hasText: "New Agent" })).toBeVisible();
await expect(dialog.getByPlaceholder("e.g. Code Reviewer")).toBeVisible();
await expect(
dialog.getByPlaceholder("You are a helpful assistant that..."),
).toBeVisible();
});
test("opens create persona dialog via plus card", async ({
test("opens create agent dialog via plus card", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await page.getByLabel("Create new persona").click();
await navigateToAgents(page);
await page.getByLabel("Create new agent").click();
await expect(page.getByRole("dialog")).toBeVisible();
});
test("create dialog has disabled Create button when fields are empty", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await navigateToAgents(page);
await page
.getByRole("button", { name: "New Persona", exact: true })
.getByRole("button", { name: "New Agent", exact: true })
.first()
.click();
const dialog = page.getByRole("dialog");
// Create button should be disabled
await expect(dialog.getByRole("button", { name: "Create" })).toBeDisabled();
});
test("create dialog enables Create button when name and prompt are filled", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await navigateToAgents(page);
await page
.getByRole("button", { name: "New Persona", exact: true })
.getByRole("button", { name: "New Agent", exact: true })
.first()
.click();
const dialog = page.getByRole("dialog");
await dialog.getByPlaceholder("e.g. Code Reviewer").fill("Test Persona");
await dialog.getByPlaceholder("e.g. Code Reviewer").fill("Test Agent");
await dialog
.getByPlaceholder("You are a helpful assistant that...")
.fill("You are a test persona");
.fill("You are a test agent");
await expect(dialog.getByRole("button", { name: "Create" })).toBeEnabled();
});
test("closes create persona dialog via Close button", async ({
test("closes create agent dialog via Close button", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await navigateToAgents(page);
await page
.getByRole("button", { name: "New Persona", exact: true })
.getByRole("button", { name: "New Agent", exact: true })
.first()
.click();
await expect(page.getByRole("dialog")).toBeVisible();
await page.getByRole("button", { name: "Close" }).click();
await page.getByRole("button", { name: "Cancel" }).click();
await expect(page.getByRole("dialog")).not.toBeVisible();
});
test("opens edit dialog when clicking a custom persona card", async ({
test("clicking a custom agent card opens details with edit actions", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await page.getByLabel("Persona: Code Reviewer").click();
await navigateToAgents(page);
await page.getByLabel("Agent: Code Reviewer").click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
await expect(
dialog.locator("h2", { hasText: "Edit Persona" }),
dialog.locator("[data-slot='dialog-title']").filter({
hasText: "Code Reviewer",
}),
).toBeVisible();
// Fields should be pre-filled
await expect(dialog.getByPlaceholder("e.g. Code Reviewer")).toHaveValue(
"Code Reviewer",
);
await expect(dialog.getByText(/^Provider$/)).toBeVisible();
await expect(dialog.getByText("claude-sonnet-4-20250514")).toBeVisible();
await expect(dialog.getByRole("button", { name: "Edit" })).toBeVisible();
});
test("builtin persona opens read-only dialog with Duplicate button", async ({
test("built-in agent opens read-only details with Duplicate button", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
await page.getByLabel("Persona: Solo").click();
await navigateToAgents(page);
await page.getByLabel("Agent: Solo").click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// Header shows persona name for read-only
await expect(dialog.locator("h2", { hasText: "Solo" })).toBeVisible();
// Duplicate button instead of Create/Save
await expect(
dialog.locator("[data-slot='dialog-title']").filter({
hasText: "Solo",
}),
).toBeVisible();
await expect(
dialog.getByRole("button", { name: /Duplicate/ }),
).toBeVisible();
// Should NOT have Create or Save buttons
await expect(
dialog.getByRole("button", { name: "Edit" }),
).not.toBeVisible();
await expect(
dialog.getByRole("button", { name: "Create" }),
).not.toBeVisible();
@@ -153,13 +158,14 @@ test.describe("Personas view", () => {
).not.toBeVisible();
});
test("persona card dropdown menu shows correct items", async ({
test("custom agent card dropdown menu shows correct items", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
// Open dropdown for Code Reviewer (custom persona)
const card = page.getByLabel("Persona: Code Reviewer");
await card.getByLabel("Persona options").click();
await navigateToAgents(page);
const card = page.getByLabel("Agent: Code Reviewer");
await card.getByLabel("Agent options").click();
const menu = page.getByRole("menu");
await expect(menu).toBeVisible();
await expect(menu.getByRole("menuitem", { name: "Edit" })).toBeVisible();
@@ -170,15 +176,19 @@ test.describe("Personas view", () => {
await expect(menu.getByRole("menuitem", { name: "Delete" })).toBeVisible();
});
test("builtin persona dropdown menu does not show Delete", async ({
test("built-in agent dropdown menu does not show Edit or Delete", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
const card = page.getByLabel("Persona: Solo");
await card.getByLabel("Persona options").click();
await navigateToAgents(page);
const card = page.getByLabel("Agent: Solo");
await card.getByLabel("Agent options").click();
const menu = page.getByRole("menu");
await expect(menu).toBeVisible();
await expect(menu.getByRole("menuitem", { name: "Edit" })).toBeVisible();
await expect(
menu.getByRole("menuitem", { name: "Edit" }),
).not.toBeVisible();
await expect(
menu.getByRole("menuitem", { name: "Duplicate" }),
).toBeVisible();
@@ -189,12 +199,13 @@ test.describe("Personas view", () => {
});
test("Delete triggers confirmation dialog", async ({ tauriMocked: page }) => {
await navigateToPersonas(page);
const card = page.getByLabel("Persona: Code Reviewer");
await card.getByLabel("Persona options").click();
await navigateToAgents(page);
const card = page.getByLabel("Agent: Code Reviewer");
await card.getByLabel("Agent options").click();
await page.getByRole("menuitem", { name: "Delete" }).click();
// Confirmation dialog
await expect(page.getByText("Delete persona?")).toBeVisible();
await expect(page.getByText("Delete agent?")).toBeVisible();
await expect(
page.getByText(/Are you sure you want to delete.*Code Reviewer/),
).toBeVisible();
@@ -205,46 +216,45 @@ test.describe("Personas view", () => {
test("Cancel in delete confirmation closes dialog", async ({
tauriMocked: page,
}) => {
await navigateToPersonas(page);
const card = page.getByLabel("Persona: Code Reviewer");
await card.getByLabel("Persona options").click();
await navigateToAgents(page);
const card = page.getByLabel("Agent: Code Reviewer");
await card.getByLabel("Agent options").click();
await page.getByRole("menuitem", { name: "Delete" }).click();
await expect(page.getByText("Delete persona?")).toBeVisible();
// Click Cancel within the delete confirmation dialog container
await page
.locator("text=Delete persona?")
.locator("..")
.locator("..")
.getByRole("button", { name: "Cancel" })
.click();
await expect(page.getByText("Delete persona?")).not.toBeVisible();
// Persona card should still be there
await expect(page.getByLabel("Persona: Code Reviewer")).toBeVisible();
await expect(page.getByText("Delete agent?")).toBeVisible();
const confirmDialog = page.locator(".max-w-sm", {
has: page.getByText("Delete agent?"),
});
await confirmDialog.getByRole("button", { name: "Cancel" }).click();
await expect(page.getByText("Delete agent?")).not.toBeVisible();
await expect(page.getByLabel("Agent: Code Reviewer")).toBeVisible();
});
test("search filters personas", async ({ tauriMocked: page }) => {
await navigateToPersonas(page);
await page.getByPlaceholder("Search personas...").fill("Solo");
await expect(page.getByLabel("Persona: Solo")).toBeVisible();
await expect(page.getByLabel("Persona: Scout")).not.toBeVisible();
await expect(page.getByLabel("Persona: Code Reviewer")).not.toBeVisible();
// Clear search
await page.getByPlaceholder("Search personas...").clear();
await expect(page.getByLabel("Persona: Solo")).toBeVisible();
await expect(page.getByLabel("Persona: Scout")).toBeVisible();
await expect(page.getByLabel("Persona: Code Reviewer")).toBeVisible();
test("search filters agents", async ({ tauriMocked: page }) => {
await navigateToAgents(page);
await page.getByPlaceholder("Search agents...").fill("Solo");
await expect(page.getByLabel("Agent: Solo")).toBeVisible();
await expect(page.getByLabel("Agent: Scout")).not.toBeVisible();
await expect(page.getByLabel("Agent: Code Reviewer")).not.toBeVisible();
await page.getByPlaceholder("Search agents...").clear();
await expect(page.getByLabel("Agent: Solo")).toBeVisible();
await expect(page.getByLabel("Agent: Scout")).toBeVisible();
await expect(page.getByLabel("Agent: Code Reviewer")).toBeVisible();
});
test("empty persona state shows only create button", async ({
test("empty agent state shows only create button", async ({
tauriMocked: page,
}) => {
// Override mock data with empty personas before navigation
await page.addInitScript({
content: buildInitScript({ personas: [], skills: [] }),
});
await navigateToPersonas(page);
await expect(page.getByLabel("Create new persona")).toBeVisible();
// No persona cards should be visible
await expect(page.getByLabel(/^Persona: /)).not.toBeVisible();
await navigateToAgents(page);
await expect(page.getByLabel("Create new agent")).toBeVisible();
await expect(page.getByLabel(/^Agent: /)).not.toBeVisible();
});
});
+1 -1
View File
@@ -12,7 +12,7 @@ test.describe("Skills view", () => {
await navigateToSkills(page);
await expect(page.locator("h1", { hasText: "Skills" })).toBeVisible();
await expect(
page.getByText("Reusable instructions for your AI personas"),
page.getByText("Reusable instructions for your AI agents"),
).toBeVisible();
});
+1 -1
View File
@@ -21,7 +21,7 @@ test.describe("Smoke tests", () => {
await page.goto("/");
await expect(
page.getByPlaceholder(/Message .*, @ to mention personas/),
page.getByPlaceholder(/Message .*, @ to mention agents/),
).toBeVisible({ timeout: 10_000 });
});
});