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
5 changes: 3 additions & 2 deletions apps/web/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The console runs beside the administrator's own Core, with execution, files and

## Information Architecture

- **Monitor**: Overview (service status, running Sessions, sandbox slots, Sessions needing attention, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, with a popover glance at each, the attention table, usage by project), Core metrics (the Core process's CPU and memory, execution slots and the Turn queue, connected daemons, the database and background jobs), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (node capacity and hosted Runtimes across projects; a node or a sandbox opens in a dialog with its figures and CPU and memory charts), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log.
- **Monitor**: Overview (service status, running Sessions, sandbox slots, the Needs attention count for the last 24 hours, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, with a popover glance at each, the Needs attention table, whose rows name each Session, its project, its one-line reason and last activity and open it, with older ones left to the Session log, usage by project), Core metrics (the Core process's CPU and memory, execution slots and the Turn queue, connected daemons, the database and background jobs), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (node capacity and hosted Runtimes across projects; a node or a sandbox opens in a dialog with its figures and CPU and memory charts), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log.
- **Resources**: Agents, Environment templates, Skills, Files, Vaults. Each list shows one project or all projects, with a Project column when all are shown and a Creator column naming the creating key. Detail pages show the resource's facts and offer Delete. An empty list says, in one sentence, that applications create these resources through the Agents API, and links to that resource's section of the API guide; Agent metrics does the same for Sessions.
- **Platform**: Projects and keys (projects, their assets and usage, named keys, write history), Nodes (the node list, capacity observations, host figures, allocations and individual removal operations), and Settings, the single entry for platform configuration. Settings has four groups: Sandbox and scheduling, Workspace storage, Models and credentials, and Service and deployment. Sandbox and scheduling owns setup, resource edits, rollout, reset, idle policy, per-node capacity and enrollment. Add node asks for limits before issuing its one-time command; installers use Core's public URL and require supported node artifacts. Nodes links to these forms; removal stays on the node page and offers the host's uninstall command. Models and credentials owns each harness's default model and shows whether each harness is ready: Ready when it is enabled and has a default model provider, otherwise the missing step, Model provider not set (with Set as the primary action) or Disabled (with a link to the startup settings). Getting started uses the same readiness. Service and deployment owns installation facts (the public and API base URLs, with the installation ID and source commit behind Installation details), Domain and HTTPS actions and read-only startup settings, each named by its environment variable, with a link to the configuration guide's section on changing them.
- A node whose provider is not ready names the reason as one Provider-neutral readiness class (provider unavailable, host unsupported, provider files or Runtime image missing, Runtime download failed, a host too small) and its fix in the help tip beside its status, wherever that status shows.
Expand All @@ -52,8 +52,9 @@ The console runs beside the administrator's own Core, with execution, files and
- **Secrets stay write-only.** Credential tokens, Template environment variables and setup commands are never returned, to the administrator included. An Agent's saved model provider shows its protocol, base URL, limits and whether a key is configured, never the key.
- **Creators.** Core records the key behind every write. The console shows the creating key of each asset and a project's write history; an asset without a record shows as Unknown.
- **Waiting for results.** Overview, Session log and Session details name the function whose result the calling application must submit. The console cannot submit that result; environment connection waits stay distinct from function waits.
- **Session names.** Wherever Sessions are listed, and on the Session page, a Session is named by its metadata `title`, else its Agent's name, else Untitled Session.
- **Session history is read-only.** A Session page reads the Session, its Items and Turns and polls while work is in flight; there is no live event stream.
- **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible.
- **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. The Session page adds one next step where the reason has one, such as checking the model shown on the Session page and the Session's model provider; lists show the reason alone, with the next step in its tooltip. A next step must hold for every Session kind and deployment: a self-hosted Session never uses the default model provider, and an E2B deployment has no nodes. Provider observations in Settings show the reason alone. Core never returns the Harness's or provider's own message, so an unclassified failure says so. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible.
- **Executor credentials.** Core issues executor credentials; the console does so with the deployment's Core key. A Session page whose environment is self-hosted has an Executor credentials section: issue a credential (shown once as one line of JSON, to copy or download, never stored), rotate it (the old one stops working immediately) or revoke it (the executor disconnects; installed Runtime state and workspace contents are not deleted). The file lets one executor connect for that environment only; it cannot call the Agents API.
- **Default provider observations.** Each configured harness offers Usage details for Core's last successful use and any newer classified provider error. Missing records remain unknown; an error at or before the last success is no longer actionable. These are best-effort observations, not readiness checks. Failed refreshes qualify retained records, and replacing the provider starts a new observation history.
- **Host connection.** Core's connection observation and credential metadata share one five-second read while visible. Never connected, connected, disconnected, bound credential revoked, and unknown are distinct; a recent heartbeat alone never proves connectivity. Only a fresh connected read marks Host connected. Stale or failed reads withhold completion. Recovery rotates the bound key, stops the installed daemon, replaces the host credential file and starts the daemon again.
Expand Down
2 changes: 1 addition & 1 deletion apps/web/e2e/diagnostics.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test("reads classified failures and separate Item receipts without execution wri
await page.getByRole("radio", { name: /^Failed/ }).click();
const row = page.getByRole("row").filter({ hasText: "The model provider rejected authentication" }).first();
await row.getByRole("button", { name: /^Open Session / }).click();
await expect(page.getByLabel("Session facts")).toContainText("Check its API key");
await expect(page.getByLabel("Session facts")).toContainText("Check the API key of the Session's model provider");
await page.getByRole("radio", { name: "Trace", exact: true }).click();
await page.locator(".trace-ledger-row-tools").first().click();
await page.getByRole("tab", { name: "Timing", exact: true }).click();
Expand Down
3 changes: 3 additions & 0 deletions apps/web/e2e/overview-readiness.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ test("Overview preserves partial activity and visibly qualifies a failed project
const activity = page.getByRole("region", { name: "Session activity", exact: true });
await expect(activity.locator(".ts-chart-plot")).toBeVisible();
await expect(activity).toContainText("may be incomplete or out of date");
// Needs attention counts from the same Session reads, so its tile and section are qualified too.
await expect(page.locator(".metric-tile").filter({ hasText: "Needs attention" })).toContainText("Partial");
await expect(page.getByRole("region", { name: "Needs attention", exact: true })).toContainText("Partial");
await page.unroute(sessionLists);
await activity.getByRole("button", { name: "Retry", exact: true }).click();
await expect(activity.locator(".error-state")).toHaveCount(0);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/features/metrics/SandboxMetricsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ import {
matchesRuntime,
runtimeSnapshot,
runtimeSnapshotWhere,
sessionTitle,
type HostedRuntimeLoad,
type HostedRuntimeRow,
} from "./sandbox-runtime";
import "./MetricsView.css";
import { RuntimeCharts } from "./RuntimeCharts";
import { hostedRuntimesQuery } from "./metrics-queries";
import { SessionRuntimeSection } from "../sessions/SessionRuntimeSection";
import { sessionTitle } from "../../lib/session-title";
import type { SandboxDeployment, SandboxNode } from "@oac/agents-client";

const healthTone: Record<NodeHealth, Tone> = { available: "ok", degraded: "warning", offline: "danger" };
Expand Down Expand Up @@ -410,7 +410,7 @@ function RuntimeTable({ rows, showProject, showNode, showCompute, onOpen }: { ro
return (
<tr key={`${observation.project_id}:${observation.session_id}`} className="clickable-row" onClick={open}>
<th scope="row">
<NameCell name={sessionTitle(session)} id={observation.session_id} fallback={t("sandbox.untitled")} onOpen={open} openLabel={t("sandbox.runtimeDialog.openLabel", { name: sessionTitle(session) ?? observation.session_id })} />
<NameCell name={sessionTitle(session)} id={observation.session_id} fallback={tCommon("untitledSession")} onOpen={open} openLabel={t("sandbox.runtimeDialog.openLabel", { name: sessionTitle(session) ?? observation.session_id })} />
</th>
{showProject ? <td><ProjectName project={byId.get(observation.project_id)} /></td> : null}
{showNode ? <td>{row.node ? row.node.name || row.node.id : <span className="table-muted">{MISSING}</span>}</td> : null}
Expand Down
8 changes: 1 addition & 7 deletions apps/web/src/features/metrics/sandbox-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AgentSession, SandboxAllocation, SandboxNode } from "@oac/agents-client";

import type { ProjectClient } from "../../lib/projects";
import { sessionTitle } from "../../lib/session-title";
import type { RuntimeDashboardSnapshot } from "../dashboard/runtime-snapshot";
import { type OwnedRuntimeObservation } from "../../lib/admin-view";

Expand Down Expand Up @@ -163,13 +164,6 @@ export function hostedRuntimeRows(
});
}

export function sessionTitle(session: AgentSession | null): string | null {
if (!session) return null;
const title = session.metadata?.title;
if (typeof title === "string" && title.trim()) return title;
return session.agent?.name?.trim() ? session.agent.name : null;
}

/** Local search over a Runtime row's Session, Agent, host and IDs. */
export function matchesRuntime(row: HostedRuntimeRow, query: string): boolean {
const needle = query.trim().toLocaleLowerCase();
Expand Down
Loading
Loading