OpenWorker: initial import

Imported from andrewyng/aisuite@1b4bbf303e
(contents of its platform/ directory, hoisted to the repo root).
Development history prior to this commit lives in that repository.

Co-authored-by: Devika <devikaverma11@gmail.com>
This commit is contained in:
Rohit C Prasad
2026-07-21 11:09:41 -07:00
co-authored by Devika
commit 2b45018ffa
413 changed files with 93539 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
// LIVE smoke — API shape only (no model tokens). Hits the REAL sidecar's /v1/health and
// /v1/providers to catch integration drift between the GUI's expectations and the backend's
// responses. Skips cleanly when the backend is down, so it's safe to run anytime. No creds needed.
import { expect, test } from "@playwright/test";
import { BACKEND } from "./helpers";
async function backendUp(): Promise<boolean> {
try {
const res = await fetch(`${BACKEND}/v1/health`);
return res.ok;
} catch {
return false;
}
}
test("health reports ok with the fields the GUI reads", async () => {
test.skip(!(await backendUp()), "backend not running on :8765");
const s = await (await fetch(`${BACKEND}/v1/health`)).json();
expect(s.status).toBe("ok");
// The GUI's boot reads these three off /v1/health.
expect(s).toHaveProperty("model");
expect(s).toHaveProperty("default_workspace");
});
test("providers list has the shape the Settings pane expects", async () => {
test.skip(!(await backendUp()), "backend not running on :8765");
const providers = await (await fetch(`${BACKEND}/v1/providers`)).json();
expect(Array.isArray(providers)).toBe(true);
expect(providers.length).toBeGreaterThan(0);
// Each descriptor carries what ManageTabs renders: name/title/needs_key/fields/configured.
for (const p of providers) {
expect(p).toMatchObject({
name: expect.any(String),
title: expect.any(String),
needs_key: expect.any(Boolean),
configured: expect.any(Boolean),
});
expect(Array.isArray(p.fields)).toBe(true);
}
// The core providers Rohit tested should be present.
const names = providers.map((p: any) => p.name);
expect(names).toEqual(expect.arrayContaining(["openai", "anthropic"]));
});
+32
View File
@@ -0,0 +1,32 @@
import { test, expect } from "@playwright/test";
import { readFileSync } from "fs";
import { newestFile, scratchBaseIfReady, sendTask, startCoworkSession } from "./helpers";
// LIVE #1 — the approval gate. In the default "Ask for approval" mode a tool call must block on an
// in-transcript approval card; approving it lets execution proceed. (fib.md skips this via Full
// access.) Excluded from CI — run with `npm run e2e:live`.
test("live: a write blocks on an approval card, then completes once approved", async ({ page }) => {
const scratchBase = await scratchBaseIfReady();
test.skip(!scratchBase, "live backend not ready — start coworker-server and configure a model");
// Unique filename per run so the "doesn't exist before approval" check can't see a prior run's file.
const name = `hello-${Date.now()}.txt`;
await startCoworkSession(page);
// Leave the default "Ask for approval" mode — the write should gate.
await sendTask(page, `Create a file named ${name} containing exactly the text: hello world`);
// The tool call blocks on an approval card, and the file does not exist yet.
await expect(page.getByText("Permission required")).toBeVisible({ timeout: 120_000 });
expect(newestFile(scratchBase!, name), "file must not exist before approval").toBeNull();
// Approve it.
await page.getByRole("button", { name: "Allow once" }).click();
// Now it runs to completion and the artifact lands on disk.
await expect(page.getByText(/Artifacts \(\d+\)/)).toBeVisible({ timeout: 120_000 });
const file = newestFile(scratchBase!, name);
expect(file, `no ${name} found under ${scratchBase}`).toBeTruthy();
expect(readFileSync(file!, "utf8").toLowerCase()).toContain("hello world");
});
+33
View File
@@ -0,0 +1,33 @@
import { test, expect } from "@playwright/test";
import { readFileSync } from "fs";
import { newestFile, scratchBaseIfReady, selectMode, sendTask, startCoworkSession } from "./helpers";
// LIVE end-to-end smoke: drive the real app against the real backend + a real model, ask it to
// produce a file in Full-access mode, and verify the artifact lands on disk with correct contents.
// This is the vertical the hermetic suite mocks (model, tool execution, file I/O, WS streaming).
// Excluded from CI (separate config/dir) — run with `npm run e2e:live`.
const PROMPT =
"Compute the first 20 Fibonacci numbers and write them to fib.md with a one-line explanation at the top.";
// Distinctive Fibonacci values unlikely to appear in prose — a format-tolerant correctness check.
const EXPECTED = ["144", "377", "987", "4181"];
test("live: agent writes fib.md to its scratch workspace, verified on disk", async ({ page }) => {
const scratchBase = await scratchBaseIfReady();
test.skip(!scratchBase, "live backend not ready — start coworker-server and configure a model");
await startCoworkSession(page);
await selectMode(page, "Full access"); // run the write without an approval gate
await sendTask(page, PROMPT);
// The artifact rail gains a file once the write tool has run (model + tool time).
await expect(page.getByText(/Artifacts \(\d+\)/)).toBeVisible({ timeout: 150_000 });
// Verify on disk — the strongest signal that the whole stack worked.
const file = newestFile(scratchBase!, "fib.md");
expect(file, `no fib.md found under ${scratchBase}`).toBeTruthy();
const text = readFileSync(file!, "utf8");
for (const n of EXPECTED) {
expect(text, `fib.md should contain Fibonacci value ${n}`).toContain(n);
}
});
@@ -0,0 +1,16 @@
---
id: e2e-tester
name: E2E Tester
icon: sparkle
tagline: Throwaway persona for the live install smoke test
description: Installed by the persona-install e2e:live test; writes a file on request.
family: knowledge
workspace: deliverable
tools:
- files
default_permission_mode: auto
---
You are the E2E Tester, a persona used only by an automated live test. When the user asks you to
write a file, use your file tools to create it exactly as specified, then confirm in one short
sentence. Do nothing else.
+64
View File
@@ -0,0 +1,64 @@
import { readdirSync, statSync } from "fs";
import { homedir } from "os";
import { join } from "path";
import type { Page } from "@playwright/test";
// Shared helpers for the LIVE smoke specs (real backend + real model). Kept out of the hermetic
// suite (separate dir/config); see e2e/README.md.
export const BACKEND = "http://127.0.0.1:8765";
/** The expanded scratch base if the backend is up and a model is ready — else null (→ skip). */
export async function scratchBaseIfReady(): Promise<string | null> {
try {
const res = await fetch(`${BACKEND}/v1/settings`);
const s = await res.json();
if (res.ok && s.model_ready) {
return String(s.scratch_base || "~/OpenWorker").replace(/^~(?=\/|$)/, homedir());
}
} catch {
/* backend unreachable */
}
return null;
}
/** Newest `name` file across the per-session scratch dirs (each live session gets its own). */
export function newestFile(scratchBase: string, name: string): string | null {
let best: { path: string; mtime: number } | null = null;
let dirs: string[];
try {
dirs = readdirSync(scratchBase);
} catch {
return null;
}
for (const d of dirs) {
const f = join(scratchBase, d, name);
try {
const st = statSync(f);
if (!best || st.mtimeMs > best.mtime) best = { path: f, mtime: st.mtimeMs };
} catch {
/* not in this session dir */
}
}
return best?.path ?? null;
}
/** Open a fresh Cowork session via the split button's persona menu. */
export async function startCoworkSession(page: Page) {
await page.goto("/");
await page.getByRole("button", { name: "Choose a persona" }).click();
await page.getByText(/Produce a deliverable/).click();
}
/** Switch the composer's permission mode from the default "Ask for approval". */
export async function selectMode(page: Page, label: "Full access" | "Plan" | "Discuss") {
await page.getByText("Ask for approval").click();
await page.getByText(label, { exact: true }).click();
}
/** Type a task and send it. */
export async function sendTask(page: Page, text: string) {
await page.getByPlaceholder(/Ask the coworker/).fill(text);
// exact — "Send" is a substring of the Inbox control's "Sending approvals…" title when unattended.
await page.getByRole("button", { name: "Send", exact: true }).click();
}
+33
View File
@@ -0,0 +1,33 @@
import { test, expect } from "@playwright/test";
import { scratchBaseIfReady, sendTask, startCoworkSession } from "./helpers";
// LIVE — Inbox / Unattended. With "Send to Inbox" on, a tool call that would normally block on an
// inline approval card must instead route to the Inbox (so the agent runs unattended). We assert the
// approval shows up in the Inbox for this session. Excluded from CI — run with `npm run e2e:live`.
test("live: unattended routes an approval to the Inbox", async ({ page }) => {
const scratchBase = await scratchBaseIfReady();
test.skip(!scratchBase, "live backend not ready — start coworker-server and configure a model");
const token = `INBOX-${Date.now()}`;
const name = `inbox-${Date.now()}.txt`;
await startCoworkSession(page);
// Turn on "Send to Inbox" (unattended) via the composer's Inbox control, and wait until it's
// persisted (the icon's title flips to the unattended wording only after setUnattended resolves).
await page.getByRole("button", { name: "Inbox routing" }).click();
await page.getByRole("switch", { name: "Send approvals to the Inbox" }).click();
await expect(page.getByRole("button", { name: /works unattended/ })).toBeVisible();
await page.locator(".fixed.inset-0.z-30").click(); // close the popover
// Keep the default Ask-for-approval mode: the write would normally block inline, but unattended
// routes it to the Inbox.
await sendTask(page, `Write a file named ${name} containing exactly: ${token}`);
// Open the Inbox; the approval appears there (its session chip carries this session's title, which
// is the prompt — so it contains the unique filename).
await page.getByText("Inbox", { exact: true }).click();
await expect(page.getByText(name).first()).toBeVisible({ timeout: 120_000 });
await expect(page.getByRole("button", { name: "Approve" }).first()).toBeVisible();
});
+33
View File
@@ -0,0 +1,33 @@
import { test, expect } from "@playwright/test";
import { scratchBaseIfReady, selectMode, sendTask, startCoworkSession } from "./helpers";
// LIVE #6 — persistence & resume. After a completed turn, reloading the page must not lose the work:
// the session persists in the sidebar and reopens with its full transcript and its artifact. We
// reopen it explicitly (rather than relying on which session auto-restores — several sessions can
// share the same updated_at second). Excluded from CI — run with `npm run e2e:live`.
test("live: a session's transcript and artifact survive a page reload", async ({ page }) => {
const scratchBase = await scratchBaseIfReady();
test.skip(!scratchBase, "live backend not ready — start coworker-server and configure a model");
const token = `PERSIST-${Date.now()}`;
// Unique filename — appears early in the session title (so it survives title truncation and is a
// reliable click target in the sidebar), and is the artifact name.
const name = `note-${Date.now()}.txt`;
await startCoworkSession(page);
await selectMode(page, "Full access");
await sendTask(page, `Write a file named ${name} containing exactly: ${token}`);
// Turn finishes (artifact lands) and the token is in the transcript.
await expect(page.getByText(/Artifacts \(\d+\)/)).toBeVisible({ timeout: 150_000 });
await expect(page.getByText(token).first()).toBeVisible();
// Reload, then reopen this session from the sidebar (it must have persisted there).
await page.reload();
await page.getByText(name).first().click({ timeout: 60_000 });
// Reopened with its transcript restored (the token) and its artifact back on the rail.
await expect(page.getByText(token).first()).toBeVisible({ timeout: 30_000 });
await expect(page.getByText(/Artifacts \(\d+\)/)).toBeVisible({ timeout: 30_000 });
});
@@ -0,0 +1,72 @@
import { test, expect } from "@playwright/test";
import { readFileSync } from "fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
import { newestFile, scratchBaseIfReady, selectMode, sendTask } from "./helpers";
// LIVE capstone — install a persona from a local-directory bundle, enable + surface it, start a
// session as it, and have it do real work. Exercises the whole persona pipeline: manifest parse +
// snapshot on install, lifecycle (enable/surface), session creation, and execution. Excluded from
// CI — run with `npm run e2e:live`. Idempotent: re-installing overwrites the snapshot.
const here = path.dirname(fileURLToPath(import.meta.url));
const FIXTURE_DIR = path.join(here, "fixtures", "persona"); // holds e2e-tester.md
test("live: install a persona from a directory, enable it, and run a task as it", async ({ page }) => {
const scratchBase = await scratchBaseIfReady();
test.skip(!scratchBase, "live backend not ready — start coworker-server and configure a model");
const token = `PERSONA-${Date.now()}`;
const name = `persona-${Date.now()}.txt`;
await page.goto("/");
// Open persona management (Settings ▸ Personas) via the New-session menu.
await page.getByRole("button", { name: "Choose a persona" }).click();
await page.getByText(/Manage personas/).click();
await expect(page.getByText("Add personas")).toBeVisible();
// Install from the local directory bundle.
await page.getByRole("combobox").selectOption("dir");
await page.getByPlaceholder("/path/to/personas").fill(FIXTURE_DIR);
await page.getByRole("button", { name: "Install" }).click();
await expect(page.getByText(/Installed \d+ persona/)).toBeVisible({ timeout: 30_000 });
// Enable + surface it in the picker. Idempotent across re-runs (skip if already on), and click +
// await rather than check() — these are controlled React checkboxes (async updatePersona re-render).
const row = page.locator("div.flex.items-center.gap-4").filter({ hasText: "E2E Tester" });
const ensureChecked = async (i: number) => {
const box = row.getByRole("checkbox").nth(i);
if (!(await box.isChecked())) {
await box.click();
await expect(box).toBeChecked();
}
};
await ensureChecked(0); // Enabled
await ensureChecked(1); // In picker (enabled only once Enabled is on)
// Leave Settings (so the settings rows unmount), then start a fresh session AS the new persona.
// Select by the unique tagline — it appears only on the dropdown item, whereas the name "E2E
// Tester" also shows in the top bar/sidebar once a session is on it.
await page.getByRole("button", { name: "New session" }).click();
await page.getByRole("button", { name: "Choose a persona" }).click();
await page.getByText(/Throwaway persona/).click();
await expect(page.getByText("E2E Tester").first()).toBeVisible(); // the session is this persona
// New sessions start in "Ask for approval" regardless of the persona's declared mode (a safety
// default for freshly-installed personas), so set Full access to let the write run to completion.
await selectMode(page, "Full access");
await sendTask(page, `Write a file named ${name} containing exactly: ${token}`);
// The installed persona should do the work. Non-Cowork personas don't render the Artifacts rail,
// so wait on the file itself (ground truth) rather than a UI signal.
await expect
.poll(
() => {
const f = newestFile(scratchBase!, name);
return f ? readFileSync(f, "utf8") : "";
},
{ timeout: 150_000, message: `${name} with the token never appeared under ${scratchBase}` },
)
.toContain(token);
});