Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -1571,8 +1571,8 @@ guide in each artifact checksum list so extracted documentation matches its buil
The distribution includes the sandbox-node binary. An enabled local node uses a
persistent private state directory, explicitly separate from read-only configuration.
Docker grants Core write access only to that node-state mount; native Core uses the
same installation-owned directory. Zero-node installs create neither node identity
state nor sandbox administrator credentials.
same installation-owned directory. Zero-node installs create no node identity
state, but retain the paired administrator credential for first setup.

One Runtime image contains the existing daemon, shared helpers and three native
harness packages. Their differences remain in the adapters. Core keeps exclusive
Expand All @@ -1586,14 +1586,23 @@ must not change on a repeated install.

`services/core-console` serves the production Web build and forwards public `/v1`
requests to one configured Core using its project bearer, after console Basic
authentication. Its explicit sandbox administration routes instead require a
unique browser-supplied Bearer credential and forward it unchanged for Core to
verify; console Basic access does not confer deployment administration. Never
substitute the project bearer on those routes or give the console a shared admin
credential. The installer keeps the administrator key and digests separate from
project configuration and exposes only the digest file to Core. The Web keeps an
entered administrator credential in memory. Node registration, identity and
WebSocket transport are not console routes; nodes connect directly to Core.
authentication. The paired console uses the same login for allowlisted sandbox
management routes and supplies its private server-side administrator token from
`CORE_CONSOLE_SANDBOX_ADMIN_TOKEN_FILE`. The browser receives only capability
flags through `/console/config`, never the deployment bearer. Project API keys
retain their separate authority. The installer mounts only the administrator
key file into Web and only its digest file into Core. Node/daemon transport uses
its own authenticated finite routes and credentials, never that admin token.

The Web manager offers no manual administrator-key fallback. A console without
paired management configuration shows setup guidance; direct remote project API
connections do not silently administer the console's configured deployment.
Chinese/English sandbox text, status and diagnostic formatting live in the shared
`apps/web/src/lib/` locale modules. A persisted explicit language preference wins
before the first browser language; unrelated product surfaces are outside this
translation scope. Preserve zero-node setup and node installation behavior when
localizing their controls.

Both proxy paths retain fixed-origin, cross-site, safe-path, redirect and Upgrade
restrictions through the standard Go reverse proxy with streaming/cancellation.
The console implements no product identity, resource semantics, Runtime discovery
Expand Down
5 changes: 3 additions & 2 deletions apps/web/e2e/fixture-sandbox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export function handleSandboxFixture(request, response, url, sendJson, sendError
sendJson(response, { node_id: "node-local", node_name: "Core server", available: !diagnostic, state: "active", compute_phase: "running", diagnostic }); return true;
}
if (!path.startsWith("/core/v1/sandbox/")) return false;
calls.push({ path, method: request.method, authorized: request.headers.authorization === "Bearer fixture-admin-key" });
if (request.headers.authorization !== "Bearer fixture-admin-key") { sendError(response, 401, "A deployment admin key is required.", "invalid_admin_key"); return true; }
calls.push({ path, method: request.method, authorization: request.headers.authorization ?? null });
// This fixture represents authenticated console routes, not direct Core administration.
if (request.headers.authorization) { sendError(response, 400, "Browser admin credentials are not accepted.", "unexpected_authorization"); return true; }
const deployment = () => ({ installation_id: "fixture-installation", provider, core_url: coreUrl, maintenance: false, owner_epoch: 1 });
if (path.endsWith("/deployment") && request.method === "POST") {
let body = "";
Expand Down
105 changes: 87 additions & 18 deletions apps/web/e2e/sandbox-manager.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { expect, test, type Page } from "@playwright/test";
const fixture = `http://127.0.0.1:${process.env.AGENTS_FIXTURE_PORT ?? 18092}`;
async function connectAdmin(page: Page) {
async function openManager(page: Page) {
await page.getByRole("button", { name: "Hosted Sandbox Manager", exact: true }).click();
await page.getByLabel("Deployment admin key").fill("fixture-admin-key");
await page.getByRole("button", { name: "Connect admin", exact: true }).click();
await expect(page.getByRole("heading", { name: "Nodes", exact: true })).toBeVisible();
}
test.beforeEach(async ({ page, request }) => {
await page.route("**/console/config", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify({ sandbox_admin: true, node_installer: false }) }));
await request.post(`${fixture}/__fixture/reset`);
await page.goto("/");
await expect(page.getByRole("button", { name: "Sessions", exact: true })).toBeVisible();
});
test("admin access, node health, guarded removal and enrollment remain separate from project credentials", async ({ page, request }) => {
await page.getByRole("button", { name: "Hosted Sandbox Manager", exact: true }).click();
expect((await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls).toHaveLength(0);
await page.getByLabel("Deployment admin key").fill("project-key");
await page.getByRole("button", { name: "Connect admin", exact: true }).click();
await expect(page.getByRole("alert")).toContainText("deployment admin key is required");
await page.getByRole("button", { name: "Disconnect admin" }).click();
await connectAdmin(page);
test("console access needs no browser admin credential; removal and enrollment are guarded", async ({ page, request }) => {
await openManager(page);
await expect(page.getByLabel("Deployment admin key")).toHaveCount(0);
await expect(page.getByRole("button", { name: /Connect admin|Disconnect admin/ })).toHaveCount(0);
const calls = (await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls;
expect(calls.length).toBeGreaterThan(0);
expect(calls.every((call: { authorization: unknown }) => call.authorization === null)).toBe(true);
await expect(page.getByRole("region", { name: "Sandbox nodes", exact: true })).toContainText("Provider ready");
await expect(page.getByRole("region", { name: "Sandbox nodes", exact: true })).toContainText("Host metrics unavailable");
await expect(page.getByRole("region", { name: "Sandbox allocations", exact: true })).toContainText("session_snapshot");
Expand All @@ -38,20 +36,20 @@ test("admin access, node health, guarded removal and enrollment remain separate
expect(storage).not.toContain("fixture-admin-key"); expect(storage).not.toContain("fixture-once-token");
expect(page.url()).not.toContain("fixture-admin-key");
await page.reload();
await expect(page.getByLabel("Deployment admin key")).toHaveValue("");
await expect(page.getByLabel("Deployment admin key")).toHaveCount(0);
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
});
test("microsandbox shares the manager and mobile tables stay contained", async ({ page, request }) => {
await request.post(`${fixture}/__fixture/sandbox-microsandbox`);
await page.setViewportSize({ width: 390, height: 844 });
await connectAdmin(page);
await openManager(page);
await expect(page.locator(".sandbox-summary")).toContainText("microsandbox");
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
const table = page.getByRole("region", { name: "Sandbox nodes", exact: true });
await expect(table).toBeVisible();
const bounds = await table.boundingBox();
expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390);
await expect(page.getByRole("button", { name: "Disconnect admin" })).toBeInViewport();
await expect(page.getByLabel("Language / 语言")).toBeVisible();
await page.getByLabel("Core URL reachable from the node").scrollIntoViewIfNeeded();
await expect(page.getByLabel("Core URL reachable from the node")).toBeInViewport();
});
Expand Down Expand Up @@ -86,7 +84,7 @@ test("Session details show the actual Core placement", async ({ page }) => {
});
test("empty nodes and a failed refresh have distinct states", async ({ page }) => {
await page.route("**/core/v1/sandbox/nodes", (route) => route.fulfill({ contentType: "application/json", body: JSON.stringify({ data: [] }) }));
await connectAdmin(page);
await openManager(page);
await expect(page.getByText("No nodes registered. Add a node to provide hosted capacity.")).toBeVisible();
await expect(page.getByText("No sandbox allocations.")).toBeVisible();
await page.route("**/core/v1/sandbox/deployment", (route) => route.fulfill({ status: 503, contentType: "application/json", body: JSON.stringify({ error: { message: "Deployment unavailable." } }) }));
Expand Down Expand Up @@ -120,7 +118,7 @@ test("late placement reads cannot replace another Session's placement", async ({
});
test("disconnect diagnostics clear after reconnection in manager and Session details", async ({ page, request }) => {
await request.post(`${fixture}/__fixture/sandbox-diagnostic?value=node_unavailable`);
await connectAdmin(page);
await openManager(page);
await expect(page.getByRole("region", { name: "Sandbox allocations", exact: true })).toContainText("Node disconnected");
await expect(page.getByRole("region", { name: "Sandbox allocations", exact: true })).toContainText("Existing resources stay assigned");
await page.getByRole("button", { name: "Sessions", exact: true }).click();
Expand All @@ -132,13 +130,13 @@ test("disconnect diagnostics clear after reconnection in manager and Session det
await expect(dialog).toContainText("Available · Recorded allocation");
await expect(dialog).not.toContainText("Node disconnected");
await page.getByRole("button", { name: "Close dialog", exact: true }).click();
await connectAdmin(page);
await openManager(page);
await expect(page.getByRole("region", { name: "Sandbox allocations", exact: true })).toContainText("No reported issue");
await expect(page.getByRole("region", { name: "Sandbox allocations", exact: true })).not.toContainText("Node disconnected");
});
test("a missing resource preserves ownership and offers inspection without replacement", async ({ page, request }) => {
await request.post(`${fixture}/__fixture/sandbox-diagnostic?value=resource_missing`);
await connectAdmin(page);
await openManager(page);
const allocations = page.getByRole("region", { name: "Sandbox allocations", exact: true });
await expect(allocations).toContainText("Sandbox resource missing");
await expect(allocations).toContainText("retains the ownership record");
Expand All @@ -150,3 +148,74 @@ test("a missing resource preserves ownership and offers inspection without repla
const requests = await (await request.get(`${fixture}/__fixture/requests`)).json();
expect(requests.filter((entry: { method: string; path: string }) => entry.method === "POST" && entry.path === "/v1/agents/sessions")).toHaveLength(0);
});

test("Chinese defaults from browser preference, persists, and translates manager actions and diagnostics", async ({ page, request }) => {
await page.addInitScript(() => Object.defineProperty(navigator, "languages", { get: () => ["zh-CN", "en-US"] }));
await request.post(`${fixture}/__fixture/sandbox-diagnostic?value=resource_missing`);
await page.reload();
await page.getByRole("button", { name: "托管沙箱管理", exact: true }).click();
await expect(page.getByRole("heading", { name: "节点", exact: true })).toBeVisible();
const allocations = page.getByRole("region", { name: "沙箱资源分配", exact: true });
await expect(allocations).toContainText("沙箱资源缺失");
await expect(allocations).toContainText("活跃");
await expect(allocations).toContainText("运行中");
await expect(page.getByRole("region", { name: "沙箱节点", exact: true })).toContainText("主机指标不可用(心跳已过期)");
await page.getByRole("button", { name: "移除 Core server", exact: true }).click();
await page.getByRole("button", { name: "确认移除", exact: true }).click();
await expect(page.getByRole("alert")).toContainText("节点仍有活跃分配或保留资源");
await page.getByRole("button", { name: "取消移除", exact: true }).click();
await page.getByRole("button", { name: "刷新沙箱状态" }).click();
await expect(page.getByRole("alert")).toHaveCount(0);
await page.getByLabel("节点可访问的 Core 地址").fill("https://core.example");
await page.getByRole("button", { name: "生成注册命令", exact: true }).click();
await expect(page.getByLabel("一次性注册命令")).toHaveValue(/fixture-once-token/);
await page.getByLabel("Language / 语言").selectOption("en");
await expect(page.getByRole("heading", { name: "Hosted Sandbox Manager" })).toBeVisible();
await expect(page.getByLabel("One-time enrollment command")).toHaveValue(/fixture-once-token/);
await page.reload();
await expect(page.getByLabel("Language / 语言")).toHaveValue("en");
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
await page.getByLabel("Language / 语言").selectOption("zh");
await page.route("**/core/v1/sandbox/deployment", (route) => route.fulfill({ status: 503, contentType: "application/json", body: JSON.stringify({ error: { code: "sandbox_admin_not_configured", message: "Sandbox administration is not configured on this console" } }) }));
await page.getByRole("button", { name: "刷新沙箱状态" }).click();
await expect(page.getByRole("alert")).toContainText("此控制台尚未配置沙箱管理权限");
});

test("a direct Core connection never requests sandbox administration and clears enrollment", async ({ page, request }) => {
await openManager(page);
await page.getByLabel("Core URL reachable from the node").fill("https://core.example");
await page.getByRole("button", { name: "Generate enrollment command" }).click();
await expect(page.getByLabel("One-time enrollment command")).toHaveValue(/fixture-once-token/);
const before = (await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls.length;
await page.getByRole("button", { name: "Configure Agent Core connection", exact: true }).click();
const connection = page.getByRole("dialog", { name: "Connect an Agent Core", exact: true });
await connection.getByRole("radio", { name: /Other compatible Core/ }).check();
await connection.getByLabel("Compatible Core base URL").fill(`${new URL(page.url()).origin}/v1`);
await connection.getByLabel("Bearer token").fill("project-only");
await connection.getByRole("button", { name: "Apply connection", exact: true }).click();
await expect(page.getByText("Sandbox management is available through the signed-in console connection.", { exact: false })).toBeVisible();
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
await expect(page.getByRole("heading", { name: "Nodes", exact: true })).toHaveCount(0);
expect((await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls).toHaveLength(before);
});

test("an uncertain enrollment write is not retried and a late response cannot survive navigation", async ({ page }) => {
await openManager(page);
let attempts = 0;
let release: () => void = () => {};
const pending = new Promise<void>((resolve) => { release = resolve; });
await page.route("**/core/v1/sandbox/enrollment-tokens", async (route) => {
attempts += 1;
await pending;
await route.fulfill({ contentType: "application/json", body: JSON.stringify({ token: "stale-token", expires_at: "2026-09-24T00:00:00Z" }) }).catch(() => {});
});
await page.getByLabel("Core URL reachable from the node").fill("https://core.example");
await page.getByRole("button", { name: "Generate enrollment command" }).click();
await expect.poll(() => attempts).toBe(1);
await page.getByRole("button", { name: "Sessions", exact: true }).click();
await openManager(page);
release();
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
await expect(page.getByLabel("Core URL reachable from the node")).toHaveValue(new URL(page.url()).origin);
expect(attempts).toBe(1);
});
Loading
Loading