feat(voice): implement turn-detection and real-time audio streaming

This commit is contained in:
Mohamed Boudra
2026-03-14 13:46:10 +07:00
parent c45b414476
commit fc7369384b
43 changed files with 3367 additions and 1476 deletions
@@ -1,6 +1,6 @@
import { test, expect } from "./fixtures";
import type { Page } from "@playwright/test";
import { createAgentInRepo } from "./helpers/app";
import { createAgentInRepo, ensureHostSelected, gotoHome } from "./helpers/app";
import { createTempGitRepo } from "./helpers/workspace";
import {
ensureWorkspaceAgentPaneVisible,
@@ -9,6 +9,7 @@ import {
waitForWorkspaceTabsVisible,
} from "./helpers/workspace-tabs";
import { switchWorkspaceViaSidebar } from "./helpers/workspace-ui";
import { buildHostWorkspaceRouteWithOpenIntent } from "@/utils/host-routes";
async function expectComposerFocused(page: Page) {
const composer = page.getByRole("textbox", { name: "Message agent..." }).first();
@@ -123,3 +124,142 @@ test("workspace agent tab switch focuses composer on desktop web", async ({ page
await repo.cleanup();
}
});
test("workspace draft tabs keep separate prompt state", async ({ page }) => {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error("E2E_SERVER_ID is not set.");
}
const repo = await createTempGitRepo("paseo-e2e-draft-isolation-");
const seedPrompt = `seed isolation ${Date.now()}`;
const firstDraftPrompt = `first draft ${Date.now()}`;
const secondDraftPrompt = `second draft ${Date.now()}`;
try {
await createAgentInRepo(page, { directory: repo.path, prompt: seedPrompt });
await switchWorkspaceViaSidebar({ page, serverId, targetWorkspacePath: repo.path });
await waitForWorkspaceTabsVisible(page);
await ensureWorkspaceAgentPaneVisible(page);
const beforeFirstDraftIds = await getWorkspaceTabTestIds(page);
await page.getByTestId("workspace-new-agent-tab").first().click();
await expectComposerFocused(page);
const withFirstDraftIds = await getWorkspaceTabTestIds(page);
const firstDraftTabTestId = withFirstDraftIds.find((id) => !beforeFirstDraftIds.includes(id));
if (!firstDraftTabTestId) {
throw new Error("Expected first draft tab to be created.");
}
const composer = page.getByRole("textbox", { name: "Message agent..." }).first();
await composer.fill(firstDraftPrompt);
const beforeSecondDraftIds = await getWorkspaceTabTestIds(page);
await page.getByTestId("workspace-new-agent-tab").first().click();
await expectComposerFocused(page);
const withSecondDraftIds = await getWorkspaceTabTestIds(page);
const secondDraftTabTestId = withSecondDraftIds.find((id) => !beforeSecondDraftIds.includes(id));
if (!secondDraftTabTestId) {
throw new Error("Expected second draft tab to be created.");
}
await composer.fill(secondDraftPrompt);
await page.getByTestId(firstDraftTabTestId).first().click();
await expect(composer).toHaveValue(firstDraftPrompt);
await page.getByTestId(secondDraftTabTestId).first().click();
await expect(composer).toHaveValue(secondDraftPrompt);
} finally {
await repo.cleanup();
}
});
test("workspace draft promotion does not steal focus from another tab", async ({ page }) => {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error("E2E_SERVER_ID is not set.");
}
const repo = await createTempGitRepo("paseo-e2e-draft-no-focus-steal-");
const seedPrompt = `seed focus ${Date.now()}`;
const createPrompt = `background create ${Date.now()}`;
try {
await createAgentInRepo(page, { directory: repo.path, prompt: seedPrompt });
await switchWorkspaceViaSidebar({ page, serverId, targetWorkspacePath: repo.path });
await waitForWorkspaceTabsVisible(page);
await ensureWorkspaceAgentPaneVisible(page);
const beforeDraftIds = await getWorkspaceTabTestIds(page);
const firstAgentTabTestId = beforeDraftIds.find((id) => id.startsWith("workspace-tab-agent_"));
if (!firstAgentTabTestId) {
throw new Error("Expected an existing agent tab.");
}
await page.getByTestId("workspace-new-agent-tab").first().click();
await expect(page.getByRole("textbox", { name: "Message agent..." }).first()).toBeEditable();
const withDraftIds = await getWorkspaceTabTestIds(page);
const draftTabTestId = withDraftIds.find((id) => !beforeDraftIds.includes(id));
if (!draftTabTestId) {
throw new Error("Expected a draft tab to be created.");
}
const draftId = draftTabTestId.replace("workspace-tab-", "");
const draftCloseButton = page.getByTestId(`workspace-draft-close-${draftId}`).first();
const composer = page.getByRole("textbox", { name: "Message agent..." }).first();
await composer.fill(createPrompt);
await composer.press("Enter");
await page.getByTestId(firstAgentTabTestId).first().click();
await expect(page.getByText(seedPrompt, { exact: true }).first()).toBeVisible({
timeout: 30_000,
});
await expect(draftCloseButton).not.toBeVisible({ timeout: 30_000 });
await expect(page.getByText(createPrompt, { exact: true }).first()).not.toBeVisible();
await expect(page.getByText(seedPrompt, { exact: true }).first()).toBeVisible();
} finally {
await repo.cleanup();
}
});
test("workspace open intent creates exactly one draft tab in an empty workspace", async ({ page }) => {
const serverId = process.env.E2E_SERVER_ID;
if (!serverId) {
throw new Error("E2E_SERVER_ID is not set.");
}
const repo = await createTempGitRepo("paseo-e2e-draft-open-intent-");
try {
await gotoHome(page);
await ensureHostSelected(page);
await page.goto(
buildHostWorkspaceRouteWithOpenIntent(serverId, repo.path, {
kind: "draft",
draftId: "new",
})
);
await waitForWorkspaceTabsVisible(page);
await ensureWorkspaceAgentPaneVisible(page);
await expect(page.getByRole("textbox", { name: "Message agent..." }).first()).toBeEditable();
const tabIds = await getWorkspaceTabTestIds(page);
expect(tabIds).toHaveLength(1);
expect(tabIds[0]?.startsWith("workspace-tab-draft_")).toBe(true);
const tabLabel = page
.locator('[data-testid^="workspace-tab-"]:not([data-testid^="workspace-tab-context-"])')
.first();
await expect(tabLabel).toContainText("New Agent");
} finally {
await repo.cleanup();
}
});