diff --git a/frontend/e2e/agents.spec.ts b/frontend/e2e/agents.spec.ts
new file mode 100644
index 0000000..9675a62
--- /dev/null
+++ b/frontend/e2e/agents.spec.ts
@@ -0,0 +1,278 @@
+/**
+ * E2E tests for Agents feature
+ *
+ * Tests the agents UI in settings and chat integration
+ */
+
+import { test, expect } from '@playwright/test';
+
+test.describe('Agents', () => {
+ test('settings page has agents tab', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Should show agents tab content - use exact match for the main heading
+ await expect(page.getByRole('heading', { name: 'Agents', exact: true })).toBeVisible({
+ timeout: 10000
+ });
+ });
+
+ test('agents tab shows empty state initially', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Should show empty state message
+ await expect(page.getByRole('heading', { name: 'No agents yet' })).toBeVisible({ timeout: 10000 });
+ });
+
+ test('has create agent button', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Should have create button in the header (not the empty state button)
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await expect(createButton).toBeVisible({ timeout: 10000 });
+ });
+
+ test('can open create agent dialog', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Click create button (the one in the header)
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ // Dialog should appear with form fields
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await expect(page.getByLabel('Name *')).toBeVisible();
+ });
+
+ test('can create new agent', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Open create dialog
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ // Wait for dialog
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+
+ // Fill in agent details
+ await page.getByLabel('Name *').fill('Test Agent');
+ await page.getByLabel('Description').fill('A test agent for E2E testing');
+
+ // Submit the form - use the submit button inside the dialog
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ // Dialog should close and agent should appear in the list
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+ await expect(page.getByRole('heading', { name: 'Test Agent' })).toBeVisible({ timeout: 5000 });
+ });
+
+ test('can edit existing agent', async ({ page }) => {
+ // First create an agent
+ await page.goto('/settings?tab=agents');
+
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Edit Me Agent');
+ await page.getByLabel('Description').fill('Will be edited');
+
+ // Submit via dialog button
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ // Wait for agent to appear
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+ await expect(page.getByText('Edit Me Agent')).toBeVisible({ timeout: 5000 });
+
+ // Click edit button (aria-label)
+ const editButton = page.getByRole('button', { name: 'Edit agent' });
+ await editButton.click();
+
+ // Edit the name in the dialog
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Edited Agent');
+
+ // Save changes
+ await dialog.getByRole('button', { name: 'Save Changes' }).click();
+
+ // Should show updated name
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+ await expect(page.getByText('Edited Agent')).toBeVisible({ timeout: 5000 });
+ });
+
+ test('can delete agent', async ({ page }) => {
+ // First create an agent
+ await page.goto('/settings?tab=agents');
+
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Delete Me Agent');
+ await page.getByLabel('Description').fill('Will be deleted');
+
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ // Wait for agent to appear
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+ await expect(page.getByText('Delete Me Agent')).toBeVisible({ timeout: 5000 });
+
+ // Click delete button (aria-label)
+ const deleteButton = page.getByRole('button', { name: 'Delete agent' });
+ await deleteButton.click();
+
+ // Confirm deletion in dialog - look for the Delete button in the confirm dialog
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ const confirmDialog = page.getByRole('dialog');
+ await confirmDialog.getByRole('button', { name: 'Delete' }).click();
+
+ // Agent should be removed
+ await expect(page.getByRole('heading', { name: 'Delete Me Agent' })).not.toBeVisible({ timeout: 5000 });
+ });
+
+ test('can navigate to agents tab via navigation', async ({ page }) => {
+ await page.goto('/settings');
+
+ // Click on agents tab link
+ const agentsTab = page.getByRole('link', { name: 'Agents' });
+ await agentsTab.click();
+
+ // URL should update
+ await expect(page).toHaveURL(/tab=agents/);
+
+ // Agents content should be visible
+ await expect(page.getByRole('heading', { name: 'Agents', exact: true })).toBeVisible();
+ });
+});
+
+test.describe('Agent Tool Selection', () => {
+ test('can select tools for agent', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Open create dialog
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Tool Agent');
+ await page.getByLabel('Description').fill('Agent with specific tools');
+
+ // Look for Allowed Tools section
+ await expect(page.getByText('Allowed Tools', { exact: true })).toBeVisible({ timeout: 5000 });
+
+ // Save the agent
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ // Agent should be created
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+ await expect(page.getByText('Tool Agent')).toBeVisible({ timeout: 5000 });
+ });
+});
+
+test.describe('Agent Prompt Selection', () => {
+ test('can assign prompt to agent', async ({ page }) => {
+ await page.goto('/settings?tab=agents');
+
+ // Open create dialog
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Prompt Agent');
+ await page.getByLabel('Description').fill('Agent with a prompt');
+
+ // Look for System Prompt selector
+ await expect(page.getByLabel('System Prompt')).toBeVisible({ timeout: 5000 });
+
+ // Save the agent
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ // Agent should be created
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+ await expect(page.getByText('Prompt Agent')).toBeVisible({ timeout: 5000 });
+ });
+});
+
+test.describe('Agent Chat Integration', () => {
+ test('agent selector appears on home page', async ({ page }) => {
+ await page.goto('/');
+
+ // Agent selector button should be visible (shows "No agent" by default)
+ await expect(page.getByRole('button', { name: /No agent/i })).toBeVisible({ timeout: 10000 });
+ });
+
+ test('agent selector dropdown shows "No agents" when none exist', async ({ page }) => {
+ await page.goto('/');
+
+ // Click on agent selector
+ const agentButton = page.getByRole('button', { name: /No agent/i });
+ await agentButton.click();
+
+ // Should show "No agents available" message
+ await expect(page.getByText('No agents available')).toBeVisible({ timeout: 5000 });
+
+ // Should have link to create agents
+ await expect(page.getByRole('link', { name: 'Create one' })).toBeVisible();
+ });
+
+ test('agent selector shows created agents', async ({ page }) => {
+ // First create an agent
+ await page.goto('/settings?tab=agents');
+
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Chat Agent');
+ await page.getByLabel('Description').fill('Agent for chat testing');
+
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+
+ // Now go to home page and check agent selector
+ await page.goto('/');
+
+ const agentButton = page.getByRole('button', { name: /No agent/i });
+ await agentButton.click();
+
+ // Should show the created agent
+ await expect(page.getByText('Chat Agent')).toBeVisible({ timeout: 5000 });
+ await expect(page.getByText('Agent for chat testing')).toBeVisible();
+ });
+
+ test('can select agent from dropdown', async ({ page }) => {
+ // First create an agent
+ await page.goto('/settings?tab=agents');
+
+ const createButton = page.getByRole('button', { name: 'Create Agent' }).first();
+ await createButton.click();
+
+ await expect(page.getByRole('dialog')).toBeVisible({ timeout: 5000 });
+ await page.getByLabel('Name *').fill('Selectable Agent');
+ await page.getByLabel('Description').fill('Can be selected');
+
+ const dialog = page.getByRole('dialog');
+ await dialog.getByRole('button', { name: 'Create Agent' }).click();
+
+ await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
+
+ // Go to home page
+ await page.goto('/');
+
+ // Open agent selector
+ const agentButton = page.getByRole('button', { name: /No agent/i });
+ await agentButton.click();
+
+ // Select the agent
+ await page.getByText('Selectable Agent').click();
+
+ // Button should now show the agent name
+ await expect(page.getByRole('button', { name: /Selectable Agent/i })).toBeVisible({ timeout: 5000 });
+ });
+});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index e913e5f..47da1be 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -36,6 +36,7 @@
"@testing-library/svelte": "^5.3.1",
"@types/node": "^22.10.0",
"autoprefixer": "^10.4.20",
+ "fake-indexeddb": "^6.2.5",
"jsdom": "^27.4.0",
"postcss": "^8.4.49",
"svelte": "^5.16.0",
@@ -2557,6 +2558,16 @@
"node": ">=12.0.0"
}
},
+ "node_modules/fake-indexeddb": {
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz",
+ "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/fast-glob": {
"version": "3.3.3",
"license": "MIT",
diff --git a/frontend/package.json b/frontend/package.json
index 42174ae..1238781 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -26,6 +26,7 @@
"@testing-library/svelte": "^5.3.1",
"@types/node": "^22.10.0",
"autoprefixer": "^10.4.20",
+ "fake-indexeddb": "^6.2.5",
"jsdom": "^27.4.0",
"postcss": "^8.4.49",
"svelte": "^5.16.0",
diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts
index 98b959c..c50fcc2 100644
--- a/frontend/playwright.config.ts
+++ b/frontend/playwright.config.ts
@@ -8,7 +8,7 @@ export default defineConfig({
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
- baseURL: 'http://localhost:7842',
+ baseURL: process.env.BASE_URL || 'http://localhost:7842',
trace: 'on-first-retry',
screenshot: 'only-on-failure'
},
diff --git a/frontend/src/lib/components/chat/AgentSelector.svelte b/frontend/src/lib/components/chat/AgentSelector.svelte
new file mode 100644
index 0000000..6d3da70
--- /dev/null
+++ b/frontend/src/lib/components/chat/AgentSelector.svelte
@@ -0,0 +1,217 @@
+
+
+
+ Create specialized agents with custom prompts and tool sets +
+Total Agents
+{stats.total}
++ Create agents to combine prompts and tools for specialized tasks +
+ +No agents match your search
++ {agent.description} +
+ {/if} ++ Agents combine a system prompt with a specific set of tools. When you select an agent for a + chat, it will use the agent's prompt and only have access to the agent's allowed tools. +
+Defines the agent's personality and behavior
+Restricts which tools the agent can use
+