diff --git a/.env.example b/.env.example index 8379d84b9..ee396f69d 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ OAC_WEB_DEV_PROXY_TARGET=http://127.0.0.1:8091 # Legacy development proxy only. The console never calls /v1; the Vite server # still forwards /v1 with this Project API key for older tooling such as -# scripts/core-doctor.mjs (see docs/web/roadmap.md). Plaintext bearer file read +# scripts/core-doctor.mjs. Plaintext bearer file read # only by the local Vite server; if unset, it checks this conventional path. OAC_WEB_DEV_PROXY_TOKEN_FILE=~/.oac/dev/web-token diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff36aacd9..57d1bd581 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,8 @@ This guide owns how to work in the repository: documentation ownership, the repo | MiniMax Code and Claude Runtime adapter rules | [MiniMax Code Runtime](services/agents-api/deploy/mcode/README.md), [Claude Runtime](services/agents-api/deploy/claude/README.md) | | CI, distribution builds, installer lifecycle and managed HTTPS, release publication | [Maintainer guide](docs/maintainers.md) | | Operator installation, installation layout and configuration | [Installation](docs/getting-started/install.md), [installation options](docs/getting-started/install-options.md), [configuration](docs/configuration.md), [operations](docs/getting-started/operations.md) | -| Core Web console server and sign-in | [Web README](apps/web/README.md) | -| Web components, interaction and visual rules | [Web design](apps/web/DESIGN.md) and [Web architecture](docs/web/architecture.md) | +| Core Web console server and sign-in | [Console server](docs/web/console-server.md) | +| Web components, interaction and visual rules | [Web design](apps/web/DESIGN.md) and [Web product](apps/web/PRODUCT.md) | | Documentation website generation | [Docs app](apps/docs/README.md) | ## Repository boundary diff --git a/apps/docs/content/docs/bootstrap-projects-keys.mdx b/apps/docs/content/docs/bootstrap-projects-keys.mdx index 7b2998099..a75a05ace 100644 --- a/apps/docs/content/docs/bootstrap-projects-keys.mdx +++ b/apps/docs/content/docs/bootstrap-projects-keys.mdx @@ -3,90 +3,143 @@ title: "Sign in and issue application keys" description: "Keep the Core key on the management side and issue Project API keys through Web." --- -`services/core-console` serves built Web assets, signs administrators in with the -Core key and forwards every signed-in, same-origin `/core/v1/*` request to Core. The -backend contract and the React screens that use it are implemented; the console has -no execution controls. - -## Connection model - -The browser calls same-origin `/core/v1` through `AdminClient` and -`CoreMetricsClient`, and the `/core/v1/sandbox` management routes through the -sandbox client. The console server forwards each signed-in `/core/v1/*` request by -prefix to its configured Core upstream, with the Core key (`OAC_WEB_CORE_KEY_FILE`) -as the upstream credential; Core alone decides whether the route exists. Browser -code must never receive that credential. - -Applications call Core's `/v1` directly with their own Project API keys and the -public API's route-specific headers. Nodes and Runtime daemons call Core's `/api/v1` -directly with their own machine credentials. The console returns 404 for `/v1` and -`/api/v1`, even with an explicit Bearer token. Deployment routing must send both to Core. -The console endpoint and the public application endpoint serve different purposes, -even if they share a host. - -Use the [installation guide](/install) for deployment and the -[operations guide](/troubleshooting) for storage, same-release repair and node -management. A Core, Web and PostgreSQL installation may have zero execution nodes. -Opening the console neither allocates compute nor invokes a model. Deployment -sandbox management selects E2B, Docker or microsandbox independently of an -application's caller-managed `self_hosted` Runtime, including its own E2B setup. - -## Server configuration and login - -The installer generates these from its `config.json` and `secrets/`; set them -yourself only for a Web you run without the installer. - -| Setting | Purpose | +The console server (`services/core-console`, the `oac-web` process) serves the built console, signs the administrator in with the Core key and forwards the signed-in browser's `/core/v1` requests to Core with that key. The browser never holds the Core key or any API key. Applications, nodes and self-hosted executors call Core directly; the console forwards none of their traffic. + +## Request boundary + +```mermaid +flowchart LR + browser["Administrator browser"] + console["Console server"] + core["Core"] + database[("PostgreSQL")] + application["Application / official SDK"] + machine["Nodes and Runtime daemons"] + installer["Installer domain service"] + + browser -->|"same origin: /console/*, /core/v1/*; session cookie"| console + console -->|"/core/v1/* with the Core key"| core + console -->|"domain setup, Unix socket"| installer + application -->|"/v1 with a Project API key"| core + machine -->|"/api/v1 with machine credentials"| core + core <--> database +``` + +The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation options](/install-options#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: + +| Path | Sign-in | Handling | +| --- | --- | --- | +| `/healthz` | No | `GET` or `HEAD` answers `200 ok` | +| `/v1`, `/api/v1` and below | — | 404, whatever credential the request carries | +| `/node-install/*` | No | The node installation payload (see [Node installation payload](#node-installation-payload)) | +| `/console/auth`, `/console/auth/login`, `/console/auth/logout` | No | [Sign-in](#sign-in) | +| `/`, `/index.html`, `/favicon.svg`, `/oac-mark.svg`, `/assets/*` | No | Static console assets | +| `/console/config` | Yes | [Console configuration](#console-configuration) | +| `/console/installation/domain` | Yes | [Domain setup](#domain-setup) | +| `/core/v1/*` | Yes | [Forwarded to Core](#forwarding-to-core) | +| `/core` and other paths under `/core/` | Yes | 404 | +| Any other path | Yes | Static assets; a path without a file extension falls back to `index.html` | + +Every request except `/healthz`, `/v1` and `/api/v1` must pass these checks first: + +1. **Host and origin.** The `Host` header must equal the host of `OAC_WEB_ORIGIN`. An `Origin` header, when present, must equal that origin, and `Sec-Fetch-Site` must be `same-origin` or `none`. A write that carries neither `Origin` nor `Sec-Fetch-Site: same-origin` needs a same-origin `Referer`. Otherwise the console answers 403. `/node-install/*` checks only the host and the path. +2. **Safe request.** The path must start with `/` and contain no `%`, backslash, NUL, dot segment or empty segment. Absolute-form request targets, `CONNECT`, `TRACE` and any request with an `Upgrade` header get 400. A request can therefore never leave `/core/v1` on Core, and the console carries no WebSocket. +3. **Sign-in.** Paths that need sign-in answer 401 without a valid session cookie. + +Under `/core`, these failures use the Core error envelope with the codes in [console-generated failures](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/core-errors.md#console-generated-failures); elsewhere they return `{"error": "…"}`, or plain text for an unsafe request. Every response carries `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer` and `Content-Security-Policy: frame-ancestors 'none'`. + +## Forwarding to Core + +The console forwards each signed-in `/core/v1/*` request by prefix to `OAC_WEB_UPSTREAM`, with its path and query unchanged. Core alone decides whether the route exists, and its responses and errors pass through unchanged. The console therefore needs no change when Core adds a `/core/v1` route. + +On the way to Core, the console: + +- removes the browser's `Authorization`, `Proxy-Authorization`, `Cookie`, `Origin` and `Referer` headers; +- sends `Authorization: Bearer `; +- sets `X-Core-Console-Actor: console`, replacing any value the browser sent. Core records it as a display-only audit label ([administrator API](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/admin-api.md)); +- ignores ambient HTTP proxy settings, so the Core key reaches only the configured Core; +- streams responses without buffering. + +On the way back, it removes `Set-Cookie`, `WWW-Authenticate`, `Location`, `Refresh` and every `Access-Control-*` header. A redirect from Core, or a failed connection to Core, becomes 502 `core_unreachable`. + +The console never retries a request. Browser code calls `/core/v1` through the typed clients in [`packages/agents-client`](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/packages/agents-client/README.md); [console API usage](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-api-usage.md) lists what each page reads and writes. + +## Sign-in + +| Method and route | Request | Result | +| --- | --- | --- | +| `GET /console/auth` | No body | `200 {"mode":"login"}` or `200 {"mode":"authenticated"}` | +| `POST /console/auth/login` | `Content-Type: application/json`; body `{"core_key":"…"}` with no other member, at most 4 KiB | `200 {"mode":"authenticated"}` and the session cookie | +| `POST /console/auth/logout` | No body | `200 {"mode":"login"}`; ends the session and clears the cookie | + +The administrator signs in with the deployment's [Core key](/troubleshooting#core-key). There are no console accounts, usernames or setup step, and signing in grants the whole console. + +- The console compares SHA-256 digests of the submitted and configured keys in constant time. It never logs or returns the key. +- The session cookie `core_console_session` is HttpOnly, `SameSite=Strict`, and `Secure` when `OAC_WEB_ORIGIN` is HTTPS. It lasts 12 hours. +- Sessions live only in the console's memory, at most 64 at a time; the oldest is dropped first. A console restart or a Core key rotation signs everyone out. +- At most two sign-in checks run at once; another attempt gets 429 with `Retry-After: 1`. +- Failed attempts share a budget of 10 per minute; beyond it, a wrong key gets 429 with `Retry-After: 60`. The correct key always signs in, which is why the console refuses to start with a Core key shorter than 32 characters. + +Sign-in errors: 400 for a malformed body, 401 `Invalid Core key`, 405 for a method other than `POST`, 415 for a body that is not JSON, 429 as above, and 503 when the console cannot create a session. + +## Console configuration + +`GET /console/config` returns what the signed-in browser needs to add nodes: + +| Field | Meaning | | --- | --- | -| `OAC_WEB_ADDR` | Console listener address | -| `OAC_WEB_ORIGIN` | Exact browser-facing origin used for host and origin checks | -| `OAC_WEB_UPSTREAM` | Core HTTP(S) origin, without credentials, query or resource path | -| `OAC_WEB_CORE_KEY_FILE` | Absolute path to the private regular file containing the Core key | -| `OAC_WEB_DIST` | Absolute directory containing the built Web assets | - -The console exposes `GET /console/auth` and `POST /console/auth/login` and -`/logout`. The administrator signs in with the deployment's Core key, which the -installer writes to `secrets/core.key` under the installation directory (by default -`~/.oac/core/secrets/core.key`; see [Core key](/troubleshooting#core-key)). -The server compares it in constant time and answers with a same-origin session -cookie held only in its memory; the key is never logged or returned, and the -browser does not store it. A console restart or a Core key rotation requires -signing in again. There are no console accounts, usernames or setup step, and the -Core key cannot call `/v1`. `GET /console/config` provides safe console -configuration to an authenticated browser. - -Use TLS for remote browser access and loopback listeners for local development. -Preserve the host/origin checks and the forwarding rules. The console refuses -requests with an `Upgrade` header, CONNECT and TRACE, and any path that `safePath` -rejects: an encoded `%`, dot segments, empty segments or backslashes. Before -forwarding, it strips the browser's Authorization, Cookie, Origin and Referer headers -and overwrites the actor header (`X-Core-Console-Actor`) with `console`, so a browser -cannot choose the audit actor label the service reports. The label is caller-declared -and display only. Keep deployment, application, node and provider credentials out of -`VITE_*`, browser storage, source files, URLs and logs. - -## Projects and application keys - -Installation creates no Project or key. Create them on **Projects and keys** or -through the [administrator API](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/admin-api.md); see -[Projects and API keys](/troubleshooting#projects-and-api-keys). - -## Verification and diagnosis - -1. Core `/healthz` proves process liveness only. -2. Console login followed by `GET /core/v1/projects` proves the authenticated - browser-to-console and console-to-Core path. -3. A Project key must work on its public resources and fail on management routes. - The Core key must fail on `/v1`; `/v1` through the console stays 404. -4. Cross-origin management writes must be rejected. Audit actor labels must ignore - a forged browser header. -5. Runtime observations and history report execution state separately from the - sandbox deployment read (`GET /core/v1/sandbox/deployment`). Neither a login nor - a successful deployment read proves model or sandbox readiness. - -A console login failure belongs to console authentication. An upstream 401 on a -management request points to the console's Core key or Core connection. A resource -deletion conflict must remain visible; it does not authorize an execution call. -Node and daemon `/api/v1` routes and native Runtime interfaces retain their own authentication. - -[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/core-connection.md) +| `node_installer` | Whether the console serves a node installation payload | +| `node_installer_sha256` | SHA-256 of that payload's `node-install.pyz`; Add node commands verify it before running the installer | +| `node_artifacts` | The providers (`docker`, `microsandbox`) whose node artifacts the payload holds, locally or as a pinned release download. Read on every request, so artifacts added by rerunning the installer appear without a restart | + +## Node installation payload + +With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution's node payload at `/node-install/` without sign-in: `node-install.pyz`, `manifest.json`, `SHA256SUMS`, `runtime/seccomp.json`, and the node artifacts the manifest declares under `artifacts/`. An artifact missing locally redirects (307) to its pinned release download. Node install and uninstall commands download from `/node-install/`, so the reverse proxy must send that path to the console. Nodes verify every checksum themselves. + +## Domain setup + +`GET` and `POST /console/installation/domain` let **System → Domain and HTTPS** configure a managed installation's domain. They are console routes, not Core routes. After the same origin and sign-in checks, the console passes the request body (at most 2 KiB) to the installer's Unix socket at `OAC_WEB_INSTALLATION_SOCKET`, authenticated with the Core key, and returns the installer's JSON answer and status. The request times out after 20 seconds. + +| Method | Request | Result | +| --- | --- | --- | +| `GET` | No body | The domain status | +| `POST` | `{"hostname":"core.example.com"}`, optionally with `"confirm_public_url_change":"https://core.example.com"` | 202 and the status; the installer checks and applies the domain in the background | + +The status has `supported`, `state` (`unconfigured`, `checking`, `applying`, `ready` or `failed`), and nullable `public_url`, `target_url` and `message`. Installer errors use `{"error":{"code":"…","message":"…"}}`. Changing an address that nodes or executors already use returns 409 `public_url_confirmation_required` until the request confirms the new URL; pending `config.json` edits, an installation that is not applied or not running, hand-edited generated files, and another installation operation holding the lock (`installation_busy`) also return 409. + +Without `OAC_WEB_INSTALLATION_SOCKET` (external reverse proxy installations), `GET` reports `supported: false` and `POST` returns 400 `domain_setup_unavailable`. An unreachable installer or an invalid answer returns 502 `installation_unreachable`. + +The System page submits a hostname once, polls the status every 2 seconds while it is `checking` or `applying`, and asks for confirmation when the installer requires it. It never retries a write. Applying the domain restarts the console, which ends every session; the page keeps a sign-in link to the new HTTPS address. Only the `ready` state confirms HTTPS; the browser does not probe the new origin. The installer owns certificates, locking and recovery ([managed HTTPS](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/deploy/install/README.md#managed-https)). + +`OAC_WEB_BOOTSTRAP=1`, which the installer sets while no public URL is configured, lets the console also accept plain HTTP requests addressed to a literal IP address, treating `http://` as the origin, so an operator can sign in through the server's IP address. Host names still require `OAC_WEB_ORIGIN`, so DNS rebinding cannot reach the console. + +## Settings + +The installer sets these variables from `config.json`; set them yourself only when you run the console without the installer. Of the installation's secrets, the installer gives the console only `secrets/core.key`. + +| Variable | Default | Meaning | +| --- | --- | --- | +| `OAC_WEB_ADDR` | `:8080` | Listener address | +| `OAC_WEB_ORIGIN` | `http://127.0.0.1:8080` | The exact browser-facing origin, HTTP or HTTPS, without a path. Host and origin checks use it; HTTPS makes the session cookie `Secure` | +| `OAC_WEB_UPSTREAM` | `http://core:8091` | Core's origin, HTTP or HTTPS, without credentials, query or path | +| `OAC_WEB_CORE_KEY_FILE` | `/admin/core.key` | Absolute path of a regular file with no group or other permissions, holding the Core key: at least 32 characters, no whitespace, at most 4 KiB | +| `OAC_WEB_DIST` | `/www` | Absolute directory of the built console; must contain `index.html` | +| `OAC_WEB_NODE_PAYLOAD_DIR` | unset | Absolute path of the matched distribution's node payload (the installer's `node-payload/`). Unset, `/node-install/*` is not served and Add node is unavailable | +| `OAC_WEB_INSTALLATION_SOCKET` | unset | Absolute path of the installer's domain socket. Unset, domain setup reports unsupported | +| `OAC_WEB_BOOTSTRAP` | `0` | `1` accepts literal-IP hosts before a domain is configured. Requires an `http://` origin and `OAC_WEB_INSTALLATION_SOCKET` | + +An invalid `OAC_WEB_*` value stops the console at startup with a message naming the variable. The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](/configure#appendix-core-environment-without-the-installer)); unknown values fall back to their defaults. Use HTTPS for any browser that is not on the same machine. + +## Verification + +After installing or changing the console, check: + +1. `GET /healthz` on the console and on Core. Each proves only that the process answers. +2. Sign in, then read `GET /core/v1/projects` in the browser. This proves the browser-to-console and console-to-Core path and the console's Core key. +3. A Project API key works on `/v1` and fails on `/core/v1`. The Core key fails on `/v1`, and `/v1` sent to the console answers 404. +4. A cross-origin write to the console is rejected, and a forged `X-Core-Console-Actor` header does not change the audit label. +5. Neither sign-in nor the sandbox deployment read (`GET /core/v1/sandbox/deployment`) proves that a model or a sandbox is ready. Runtime observations and history report execution separately. + +A sign-in failure belongs to the console. A 401 from Core on a signed-in request means the console's Core key does not match Core's digest, or the console reaches the wrong Core. The [troubleshooting table](/troubleshooting#troubleshooting) covers the common symptoms. + +[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-server.md) diff --git a/apps/docs/content/docs/configure.mdx b/apps/docs/content/docs/configure.mdx index 0a5d41982..f2e05452c 100644 --- a/apps/docs/content/docs/configure.mdx +++ b/apps/docs/content/docs/configure.mdx @@ -158,6 +158,6 @@ Core reads only its environment. The installer renders `generated/core.env` from Core logs the file paths it loads, never environment values or file contents. -A Web you run without the installer reads the variables in [Connecting the administrator console to Core](/bootstrap-projects-keys#server-configuration-and-login), plus `OAC_WEB_NODE_PAYLOAD_DIR`: the absolute path of the matched distribution's node payload (the installer's `node-payload/`). Without it, Add node is unavailable. +A Web you run without the installer reads the variables in [Console server settings](/bootstrap-projects-keys#settings), plus `OAC_WEB_NODE_PAYLOAD_DIR`: the absolute path of the matched distribution's node payload (the installer's `node-payload/`). Without it, Add node is unavailable. [Repository source](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/configuration.md) diff --git a/apps/docs/content/docs/console.mdx b/apps/docs/content/docs/console.mdx index 76b682aa4..3e4f607cf 100644 --- a/apps/docs/content/docs/console.mdx +++ b/apps/docs/content/docs/console.mdx @@ -3,64 +3,56 @@ title: "Administrator console" description: "Monitor Core, inspect resources and manage deployment settings through Web." --- -Core Web is the administrator console for a Core deployment. Its Go service -provides Core key login and forwards signed-in, same-origin `/core/v1` requests to -Core. Applications use Core's public Agents API directly with their own Project API -keys. +Web is the administrator console of one OpenAgentCore deployment. Administrators use it to watch health, capacity, usage and failures, inspect each Project's resources and execution history, and manage Projects, keys, nodes and deployment settings. Applications do not use Web; they call Core's Agents API (`/v1`) with their own Project API keys. -The React console (`apps/web`) uses this contract: every browser request goes through -the console's same-origin management routes with `AdminClient` and the sandbox -management client, and it sends nothing to `/v1`. +![OpenAgentCore Web overview](/images/source/docs/assets/console-overview-en.webp) -![Core Web overview](/images/source/apps/web/public/onboarding/monitor-en.webp) +## Sign in + +[Sign in](/install#sign-in-to-web) with the deployment's [Core key](/troubleshooting#core-key); the console has no user accounts. The browser keeps only a session cookie, and the [console server](/bootstrap-projects-keys) sends the Core key to Core on its behalf; [sign-in](/bootstrap-projects-keys#sign-in) describes how long a session lasts. + +Signing in opens the Overview. While any step is still to do, its **Getting started** checklist leads through four steps in any order: sandboxes ready, a default model provider, a Project with an active key, and a first Session. An optional tour of the console opens from it. ## Console pages | Group | Page | Purpose | | --- | --- | --- | | Monitor | Overview | Service status, running Sessions, sandbox slots and work needing attention; 24-hour Session activity; Core and its nodes as a topology, each with a popover glance; Sessions needing attention; usage by Project | -| Monitor | Core metrics | The Core process: execution slots, the Turn queue, connected daemons, database latency and pool, background jobs | +| Monitor | Core metrics | The Core process: CPU and resident memory against their limits, execution slots and the Turn queue, connected daemons, database latency and pool, background jobs | | Monitor | Agent metrics | Requests, errors, duration, tokens, models, tools, Agents and API keys over 1 h, 6 h, 24 h or 7 d | | Monitor | Sandbox metrics | Node capacity and hosted Runtime CPU and memory across Projects | -| Monitor | Session log | Every Session, opening one Session's read-only conversation, trace and Turns; a self-hosted Session's page also manages its executor credentials and gives the command that connects a host | -| Resources | Agents, Environment templates, Skills, Files, Vaults | Inspection and permitted deletion | -| Platform | Projects and keys, Nodes, System | Project and key lifecycle; sandbox deployment and nodes; System: the installation's public address, API base URL, ID and source commit (read-only), each harness's default model provider (write-only key) beside its read-only startup state, the sandbox configuration every Project shares: provider, sandbox size, Runtime or E2B template build, idle suspension and reset, and Core's config.json startup settings with where to change them | - -Missing data is shown as missing (—), never as zero. How each figure is read and -bounded is recorded in [management interface coverage](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/protocol-coverage.md). - -## Management scope - -Administrators can create, rename and archive Projects; issue and revoke their -keys; inspect resources and execution history; delete supported resources; and -issue, rotate and revoke the executor credentials of a self-hosted Session's -environment on its Session page. -They can also read summaries, Runtime observations and audit history, and manage -deployment sandbox nodes. Deployment sandbox management selects E2B, Docker or -microsandbox; caller-managed `self_hosted` Runtimes remain a separate application -path. - -How Projects and keys behave, and what administrators can and cannot do, is in -the [design principles](/concepts#projects-own-assets). - -## Connect and develop - -Follow the [installation guide](/install) for Core, Web and -PostgreSQL with zero execution nodes. Installation creates no Project or application -key; an administrator creates them on the console's **Projects and keys** page or -through the management API. The browser signs in to the console with the Core key; -only the console server sends it to Core. - -- [Connection and authentication](/bootstrap-projects-keys) -- [Architecture and ownership](/execution-model) -- [Management interface coverage](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/protocol-coverage.md) -- [Frontend handoff and acceptance](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/roadmap.md) -- [React application](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/apps/web/README.md) - -The [administrator API contract](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/admin-api.md) defines -management routes and resource behavior. The [public API contracts](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/README.md) -define the separate application interface. See the [design principles](/concepts) -for ownership and [contributor guide](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/CONTRIBUTING.md) for required checks. +| Monitor | Session log | Every Session, and each Session's read-only conversation, trace and Turns with the classified reason of a failure; a self-hosted Session's page also manages its executor credentials and gives the command that connects a host | +| Resources | Agents, Environment templates, Skills, Files, Vaults | Inspection and permitted deletion, with the Project and the creating key of each resource | +| Platform | Projects and keys | Create, rename and archive Projects; issue and revoke keys; each Project's usage, write history and how to call the API | +| Platform | Nodes | Add, edit and remove Docker or microsandbox nodes; each node's readiness, capacity and allocations | +| Platform | System | The installation's public address, API base URL, ID and source commit; **Domain and HTTPS**; each harness's default model; **Sandbox configuration**; Core's `config.json` startup settings, read-only, with where to change them | + +Missing data is shown as missing (—), never as zero. [Console API usage](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-api-usage.md) lists what each page reads and how its figures are bounded. + +## What administrators do here + +| Task | Where | +| --- | --- | +| Give the installation an HTTPS address | **System → Domain and HTTPS**, on an installation with managed ingress; see [Make Core reachable](/install#configure-the-domain-and-https) | +| Choose the sandbox backend (Docker, microsandbox or E2B), the sandbox size and Runtime, or reset the backend | **System → Sandbox configuration**; see [change the sandbox configuration](/hosted-providers#change-the-sandbox-configuration) | +| Add or remove execution nodes | **Nodes**; see the [nodes guide](/hosted-providers) | +| Set the default model of a harness | **System → Default model configuration**; see [default models](/configure#default-models) | +| Create a Project and issue its API keys | **Projects and keys**; see [Projects and API keys](/troubleshooting#projects-and-api-keys) | +| Issue, rotate or revoke a self-hosted executor's credential, or copy its install command | The Session's page in the **Session log**; see [self-hosted executors](/self-hosted-execution) | +| Delete a resource, for example a leaked Credential | The resource's row in its list, or its page; Files are deleted from the Files list. The public deletion rules apply | + +Installation creates no Project or key. Opening the console neither allocates compute nor calls a model, and an installation may have zero nodes. Web never starts a Session, sends input or cancels work; the [design principles](/concepts#what-administrators-can-and-cannot-do) state what administrators can and cannot do. + +The deployment's sandbox backend serves hosted Sessions. An application's `self_hosted` Runtime, including one in its own E2B account, is a separate path that the sandbox configuration does not change. + +A loopback public address (`local_only`) keeps nodes and remote applications from reaching Core. The console stays reachable at its own address and [warns about it](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-api-usage.md#provenance-and-monitoring). + +## More + +- [Console server](/bootstrap-projects-keys): request boundary, sign-in, settings and verification. +- [Console API usage](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-api-usage.md): the Core routes each page uses. +- [Web package](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/apps/web/README.md): developing the console. +- [Administrator API](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/admin-api.md): the `/core/v1` routes behind the console. OpenAgentCore Web is available under the [MIT License](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/LICENSE). diff --git a/apps/docs/content/docs/development.mdx b/apps/docs/content/docs/development.mdx index 7173802e1..23bceb506 100644 --- a/apps/docs/content/docs/development.mdx +++ b/apps/docs/content/docs/development.mdx @@ -93,7 +93,7 @@ site; `pnpm dev:docs` starts its development server. | `apps/parsar-daemon/internal/dispatch` | Runtime preparation, Executor reuse, Turn and cleanup ownership | [Harness lifecycle](/harness-onboarding#required-adapter-interfaces) | | `apps/parsar-daemon/internal/agent` | Native harness adapters | [Native references](/harness-onboarding#native-references) | | `services/agents-api/internal/sandbox` | Provider interfaces and managed compute lifecycle | [Provider onboarding](/sandbox-provider) | -| `services/core-console` | Console login and the server-side management proxy | [Web architecture](/execution-model) | +| `services/core-console` | Console login and the server-side management proxy | [Console server](/bootstrap-projects-keys) | | `apps/web` and `packages/agents-client` | Console UI and typed clients | [Web guide](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/apps/web/README.md) | | `deploy/install` and `scripts` | Distribution, installation and validation tools | [Maintainers](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/maintainers.md) | | `contracts/agents-api` | Pinned schema, local semantic contracts and qualification evidence | [Coverage ledger](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/README.md) | diff --git a/apps/docs/content/docs/execution-model.mdx b/apps/docs/content/docs/execution-model.mdx index b44304ec0..2474c102d 100644 --- a/apps/docs/content/docs/execution-model.mdx +++ b/apps/docs/content/docs/execution-model.mdx @@ -5,108 +5,143 @@ description: "Applications call the public API; the administrator browser calls ![Application, administration and machine credential boundaries](/images/architecture.svg) -Core Web manages a Core deployment. Applications, including Parsar, use the public -Agents API independently with their own Project keys. The management backend, -`AdminClient` and the React console built on them are implemented. +The console server (`services/core-console`, the `oac-web` process) serves the built console, signs the administrator in with the Core key and forwards the signed-in browser's `/core/v1` requests to Core with that key. The browser never holds the Core key or any API key. Applications, nodes and self-hosted executors call Core directly; the console forwards none of their traffic. -The [design principles](/concepts) define identity and authority. -The [administrator contract](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/admin-api.md) defines exact -routes, payloads, pagination and audit records. - -## Request boundaries +## Request boundary ```mermaid flowchart LR browser["Administrator browser"] - console["Core console service"] - core["Core API"] + console["Console server"] + core["Core"] database[("PostgreSQL")] application["Application / official SDK"] - runtime["Runtime and native adapters"] - - browser -->|"Same-origin management requests; console login"| console - console -->|"/core/v1/* by prefix, including sandbox; Core key"| core - application -->|"/v1; Project API key"| core + machine["Nodes and Runtime daemons"] + installer["Installer domain service"] + + browser -->|"same origin: /console/*, /core/v1/*; session cookie"| console + console -->|"/core/v1/* with the Core key"| core + console -->|"domain setup, Unix socket"| installer + application -->|"/v1 with a Project API key"| core + machine -->|"/api/v1 with machine credentials"| core core <--> database - core <--> runtime ``` -React management code must use the Core clients from `packages/agents-client`: -`AdminClient` for `/core/v1`, `CoreMetricsClient` for `/core/v1/metrics` and the -sandbox management client for `/core/v1/sandbox`. The console service -returns 404 for `/v1` and `/api/v1`, including requests with an explicit Bearer -token. It has no application key and does not impersonate the selected Project. - -The console signs the browser in with the Core key, checks the host and origin, and -forwards every signed-in `/core/v1/*` request to Core by prefix; Core alone decides -whether the route exists. It strips the browser's Authorization, Cookie, Origin and -Referer headers and supplies the Core key as its private upstream credential. Core -rejects application keys on management routes and the Core key on `/v1`. -The audit actor label is declared by the caller and is display only, never Core authorization: the console server declares `console`, and operator scripts calling Core with the Core key directly leave it empty. - -`GET` and `POST /console/installation/domain` are the scoped installation-management -exception: the console authenticates the same browser session and origin, then -calls the installer's private Unix socket with its server-held Core key. This is -not a Core `/core/v1` route and does not use `AdminClient`. It can configure only -the managed domain; it cannot submit shell commands or arbitrary process settings. -The [installer rules](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/deploy/install/README.md#managed-https) own application, -certificates and recovery. Before a domain is configured, the console accepts -same-origin HTTP requests at literal IP addresses; after apply, only the configured -HTTPS origin is accepted. - -Node and daemon connections use `/api/v1` with their own credentials. The reverse -proxy sends them directly to Core; the console never forwards them, and they do not -grant a browser execution authority. - -## Ownership - -| Component | Responsibility | +The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation options](/install-options#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: + +| Path | Sign-in | Handling | +| --- | --- | --- | +| `/healthz` | No | `GET` or `HEAD` answers `200 ok` | +| `/v1`, `/api/v1` and below | — | 404, whatever credential the request carries | +| `/node-install/*` | No | The node installation payload (see [Node installation payload](#node-installation-payload)) | +| `/console/auth`, `/console/auth/login`, `/console/auth/logout` | No | [Sign-in](#sign-in) | +| `/`, `/index.html`, `/favicon.svg`, `/oac-mark.svg`, `/assets/*` | No | Static console assets | +| `/console/config` | Yes | [Console configuration](#console-configuration) | +| `/console/installation/domain` | Yes | [Domain setup](#domain-setup) | +| `/core/v1/*` | Yes | [Forwarded to Core](#forwarding-to-core) | +| `/core` and other paths under `/core/` | Yes | 404 | +| Any other path | Yes | Static assets; a path without a file extension falls back to `index.html` | + +Every request except `/healthz`, `/v1` and `/api/v1` must pass these checks first: + +1. **Host and origin.** The `Host` header must equal the host of `OAC_WEB_ORIGIN`. An `Origin` header, when present, must equal that origin, and `Sec-Fetch-Site` must be `same-origin` or `none`. A write that carries neither `Origin` nor `Sec-Fetch-Site: same-origin` needs a same-origin `Referer`. Otherwise the console answers 403. `/node-install/*` checks only the host and the path. +2. **Safe request.** The path must start with `/` and contain no `%`, backslash, NUL, dot segment or empty segment. Absolute-form request targets, `CONNECT`, `TRACE` and any request with an `Upgrade` header get 400. A request can therefore never leave `/core/v1` on Core, and the console carries no WebSocket. +3. **Sign-in.** Paths that need sign-in answer 401 without a valid session cookie. + +Under `/core`, these failures use the Core error envelope with the codes in [console-generated failures](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/core-errors.md#console-generated-failures); elsewhere they return `{"error": "…"}`, or plain text for an unsafe request. Every response carries `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer` and `Content-Security-Policy: frame-ancestors 'none'`. + +## Forwarding to Core + +The console forwards each signed-in `/core/v1/*` request by prefix to `OAC_WEB_UPSTREAM`, with its path and query unchanged. Core alone decides whether the route exists, and its responses and errors pass through unchanged. The console therefore needs no change when Core adds a `/core/v1` route. + +On the way to Core, the console: + +- removes the browser's `Authorization`, `Proxy-Authorization`, `Cookie`, `Origin` and `Referer` headers; +- sends `Authorization: Bearer `; +- sets `X-Core-Console-Actor: console`, replacing any value the browser sent. Core records it as a display-only audit label ([administrator API](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/admin-api.md)); +- ignores ambient HTTP proxy settings, so the Core key reaches only the configured Core; +- streams responses without buffering. + +On the way back, it removes `Set-Cookie`, `WWW-Authenticate`, `Location`, `Refresh` and every `Access-Control-*` header. A redirect from Core, or a failed connection to Core, becomes 502 `core_unreachable`. + +The console never retries a request. Browser code calls `/core/v1` through the typed clients in [`packages/agents-client`](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/packages/agents-client/README.md); [console API usage](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-api-usage.md) lists what each page reads and writes. + +## Sign-in + +| Method and route | Request | Result | +| --- | --- | --- | +| `GET /console/auth` | No body | `200 {"mode":"login"}` or `200 {"mode":"authenticated"}` | +| `POST /console/auth/login` | `Content-Type: application/json`; body `{"core_key":"…"}` with no other member, at most 4 KiB | `200 {"mode":"authenticated"}` and the session cookie | +| `POST /console/auth/logout` | No body | `200 {"mode":"login"}`; ends the session and clears the cookie | + +The administrator signs in with the deployment's [Core key](/troubleshooting#core-key). There are no console accounts, usernames or setup step, and signing in grants the whole console. + +- The console compares SHA-256 digests of the submitted and configured keys in constant time. It never logs or returns the key. +- The session cookie `core_console_session` is HttpOnly, `SameSite=Strict`, and `Secure` when `OAC_WEB_ORIGIN` is HTTPS. It lasts 12 hours. +- Sessions live only in the console's memory, at most 64 at a time; the oldest is dropped first. A console restart or a Core key rotation signs everyone out. +- At most two sign-in checks run at once; another attempt gets 429 with `Retry-After: 1`. +- Failed attempts share a budget of 10 per minute; beyond it, a wrong key gets 429 with `Retry-After: 60`. The correct key always signs in, which is why the console refuses to start with a Core key shorter than 32 characters. + +Sign-in errors: 400 for a malformed body, 401 `Invalid Core key`, 405 for a method other than `POST`, 415 for a body that is not JSON, 429 as above, and 503 when the console cannot create a session. + +## Console configuration + +`GET /console/config` returns what the signed-in browser needs to add nodes: + +| Field | Meaning | | --- | --- | -| React frontend | Project selection, permitted management actions and operational views; cached reads (TanStack Query) that keep the last data on screen while refreshing | -| `AdminClient` | Typed management requests and validation, sharing resource parsers with the public client | -| `services/core-console` | Core key login, host/origin checks, and prefix forwarding of `/core/v1/*` with the Core key as the private upstream credential | -| Core API and PostgreSQL | Project isolation, resource state, deletion preconditions, audit and scheduling | -| Runtime and native adapters | Existing allocation, process lifecycle and execution protocols | - -Projects, keys and administrator authority follow the -[design principles](/concepts#projects-own-assets). The console adds -no execution path: a deletion conflict is never resolved by an implicit cancellation. - -Secret fields remain write-only; Skill source and Artifact content have explicit -read routes, while Source File content does not have an administrator download -route. - -## Deployment and application Runtime paths - -Deployment sandbox management selects one provider at a time: E2B, Docker or -microsandbox. E2B uses the deployment's provider integration; Docker and microsandbox -use operator-managed machines. Provider setup, reset and node administration -belong to the existing sandbox management surface. - -An application's `self_hosted` Runtime, including one it provisions in its own E2B -account, is a separate caller-managed path. It does not choose or reconfigure the -deployment provider. This console contract changes neither native Runtime protocols -nor application Session creation semantics. - -## Frontend state and validation - -Session inspection uses paginated durable history and bounded polling. There is no -management Session SSE endpoint. Project changes must discard stale reads and -pending operation state before displaying results in another Project. - -The client sends each write once per explicit action. An uncertain result stays -visible until the administrator checks state and decides how to proceed. Issued -key plaintext must not enter browser storage or logs. Key issuance recovery -follows the administrator contract. - -The sandbox deployment read (`GET /core/v1/sandbox/deployment`) describes the saved -selection. It does not prove a reachable model, valid provider credentials or -execution readiness. Runtime observations, usage coverage and audit history must -retain the distinctions defined by Core. In E2B views, the running sandbox count -comes from the deployment's allocations; the hosted Runtime total counts hosted -observation records across projects and reported lifecycle states. These sources -have different coverage and refresh independently, so the console does not infer -resource retention or cleanup from their difference. -Native execution ownership remains governed by [CONTRIBUTING.md](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/CONTRIBUTING.md). - -[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/architecture.md) +| `node_installer` | Whether the console serves a node installation payload | +| `node_installer_sha256` | SHA-256 of that payload's `node-install.pyz`; Add node commands verify it before running the installer | +| `node_artifacts` | The providers (`docker`, `microsandbox`) whose node artifacts the payload holds, locally or as a pinned release download. Read on every request, so artifacts added by rerunning the installer appear without a restart | + +## Node installation payload + +With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution's node payload at `/node-install/` without sign-in: `node-install.pyz`, `manifest.json`, `SHA256SUMS`, `runtime/seccomp.json`, and the node artifacts the manifest declares under `artifacts/`. An artifact missing locally redirects (307) to its pinned release download. Node install and uninstall commands download from `/node-install/`, so the reverse proxy must send that path to the console. Nodes verify every checksum themselves. + +## Domain setup + +`GET` and `POST /console/installation/domain` let **System → Domain and HTTPS** configure a managed installation's domain. They are console routes, not Core routes. After the same origin and sign-in checks, the console passes the request body (at most 2 KiB) to the installer's Unix socket at `OAC_WEB_INSTALLATION_SOCKET`, authenticated with the Core key, and returns the installer's JSON answer and status. The request times out after 20 seconds. + +| Method | Request | Result | +| --- | --- | --- | +| `GET` | No body | The domain status | +| `POST` | `{"hostname":"core.example.com"}`, optionally with `"confirm_public_url_change":"https://core.example.com"` | 202 and the status; the installer checks and applies the domain in the background | + +The status has `supported`, `state` (`unconfigured`, `checking`, `applying`, `ready` or `failed`), and nullable `public_url`, `target_url` and `message`. Installer errors use `{"error":{"code":"…","message":"…"}}`. Changing an address that nodes or executors already use returns 409 `public_url_confirmation_required` until the request confirms the new URL; pending `config.json` edits, an installation that is not applied or not running, hand-edited generated files, and another installation operation holding the lock (`installation_busy`) also return 409. + +Without `OAC_WEB_INSTALLATION_SOCKET` (external reverse proxy installations), `GET` reports `supported: false` and `POST` returns 400 `domain_setup_unavailable`. An unreachable installer or an invalid answer returns 502 `installation_unreachable`. + +The System page submits a hostname once, polls the status every 2 seconds while it is `checking` or `applying`, and asks for confirmation when the installer requires it. It never retries a write. Applying the domain restarts the console, which ends every session; the page keeps a sign-in link to the new HTTPS address. Only the `ready` state confirms HTTPS; the browser does not probe the new origin. The installer owns certificates, locking and recovery ([managed HTTPS](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/deploy/install/README.md#managed-https)). + +`OAC_WEB_BOOTSTRAP=1`, which the installer sets while no public URL is configured, lets the console also accept plain HTTP requests addressed to a literal IP address, treating `http://` as the origin, so an operator can sign in through the server's IP address. Host names still require `OAC_WEB_ORIGIN`, so DNS rebinding cannot reach the console. + +## Settings + +The installer sets these variables from `config.json`; set them yourself only when you run the console without the installer. Of the installation's secrets, the installer gives the console only `secrets/core.key`. + +| Variable | Default | Meaning | +| --- | --- | --- | +| `OAC_WEB_ADDR` | `:8080` | Listener address | +| `OAC_WEB_ORIGIN` | `http://127.0.0.1:8080` | The exact browser-facing origin, HTTP or HTTPS, without a path. Host and origin checks use it; HTTPS makes the session cookie `Secure` | +| `OAC_WEB_UPSTREAM` | `http://core:8091` | Core's origin, HTTP or HTTPS, without credentials, query or path | +| `OAC_WEB_CORE_KEY_FILE` | `/admin/core.key` | Absolute path of a regular file with no group or other permissions, holding the Core key: at least 32 characters, no whitespace, at most 4 KiB | +| `OAC_WEB_DIST` | `/www` | Absolute directory of the built console; must contain `index.html` | +| `OAC_WEB_NODE_PAYLOAD_DIR` | unset | Absolute path of the matched distribution's node payload (the installer's `node-payload/`). Unset, `/node-install/*` is not served and Add node is unavailable | +| `OAC_WEB_INSTALLATION_SOCKET` | unset | Absolute path of the installer's domain socket. Unset, domain setup reports unsupported | +| `OAC_WEB_BOOTSTRAP` | `0` | `1` accepts literal-IP hosts before a domain is configured. Requires an `http://` origin and `OAC_WEB_INSTALLATION_SOCKET` | + +An invalid `OAC_WEB_*` value stops the console at startup with a message naming the variable. The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](/configure#appendix-core-environment-without-the-installer)); unknown values fall back to their defaults. Use HTTPS for any browser that is not on the same machine. + +## Verification + +After installing or changing the console, check: + +1. `GET /healthz` on the console and on Core. Each proves only that the process answers. +2. Sign in, then read `GET /core/v1/projects` in the browser. This proves the browser-to-console and console-to-Core path and the console's Core key. +3. A Project API key works on `/v1` and fails on `/core/v1`. The Core key fails on `/v1`, and `/v1` sent to the console answers 404. +4. A cross-origin write to the console is rejected, and a forged `X-Core-Console-Actor` header does not change the audit label. +5. Neither sign-in nor the sandbox deployment read (`GET /core/v1/sandbox/deployment`) proves that a model or a sandbox is ready. Runtime observations and history report execution separately. + +A sign-in failure belongs to the console. A 401 from Core on a signed-in request means the console's Core key does not match Core's digest, or the console reaches the wrong Core. The [troubleshooting table](/troubleshooting#troubleshooting) covers the common symptoms. + +[Repository source](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/web/console-server.md) diff --git a/apps/docs/content/docs/public-api.mdx b/apps/docs/content/docs/public-api.mdx index 92fd66085..f3de2ad0b 100644 --- a/apps/docs/content/docs/public-api.mdx +++ b/apps/docs/content/docs/public-api.mdx @@ -136,6 +136,6 @@ owns expiry, retry and credential ownership; the The console-local `GET`/`POST /console/installation/domain` surface uses the signed-in browser session and same-origin checks. It delegates only domain setup to the installer, with the server-held Core key over a private Unix socket; it is not part -of the Agents API or Core management API. See [Web request boundaries](/execution-model#request-boundaries). +of the Agents API or Core management API. See [console domain setup](/bootstrap-projects-keys#domain-setup). [Repository source](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/docs/api/README.md) diff --git a/apps/docs/content/guide-sources.json b/apps/docs/content/guide-sources.json index b22e342ef..1d586a695 100644 --- a/apps/docs/content/guide-sources.json +++ b/apps/docs/content/guide-sources.json @@ -3,57 +3,56 @@ "sources": { "docs/getting-started/README.md": "d93c270e208fdf84edaf71827ed635a017be8cf67e0a4afef7279e3945d53173", "docs/design-principles.md": "334e0d477c255552f874f3ca8a2db603d0d07892ad7d58930bf8e3d06b86c636", - "docs/web/architecture.md": "cf694aa624cf6078a154c2f8fdd67055d41dea0c0bb80b8dc8f0cdbc5d572fb1", + "docs/web/console-server.md": "e995515b80450f2964bbd8e0c850d05b9935ba65512f8817368ca87628eafb37", "docs/getting-started/install.md": "16a77eeeefeb608e329df63318edcc9e111c37273c27c0994054ab5c659fa1bc", "docs/getting-started/install-options.md": "51fb99f87310f137ee3842137f69d181d15547f1121b0ba43765f990ef1cbd25", - "docs/configuration.md": "c4034b1b36c065f3e11afce355338a9d8b54c8af0728b24990cba3f282c5d8e9", - "docs/web/core-connection.md": "861c75c32676fd17b84eb356b263096703af5750c1ceb71bb339e84607fffc56", + "docs/configuration.md": "ff24b556096841bb89df3747da04e57331346020f349d7208f708e6bbfab5589", "docs/getting-started/quickstart.md": "51374df1c86cc3376a92da4599915818d05b94863d728b234bd12be445d1156d", - "docs/api/README.md": "319c1dc63a23864b1324e5c98df7bfc95da60070465a62ec2e6958994e11a5d0", + "docs/api/README.md": "04ebcfdb1cc0fdca650a631508a667efb69499d228bae9b02ac2af44ed253af7", "contracts/agents-api/execution-tools.md": "fe1e3cf471fe9c7ef04afa7230e6b7742fbf2146c74bd944a7a22d5d4d4197da", "docs/api/public-agent-api.md": "e1111aaf5215392178246969e281b930374b22ee380d529b08b2482836d6333d", "docs/examples.md": "c12c34adc5b2fa57c81602b23d8ecc8317596f9c5a4aedbf1f1fe934a08a94f5", "contracts/agents-api/environments.md": "404cdc48ce09bb61729c7808bc2a79c5493ffbdb0f230f871d47160522798de8", "docs/getting-started/nodes.md": "395d04a29b95a9299a2474d76955d65e66edebdfd5bf3d8ce798e1bbda223148", "docs/getting-started/self-hosted.md": "653a9132ea6bbc39186f7917fa1596027d091071172e6a6afd9f618fc1dab725", - "apps/web/public/onboarding/monitor-en.webp": "29dc220cb1250c7016b7c4bf7f30e9510b07c07b814a48c3aa9303b2d76f20b2", - "docs/web/README.md": "47159689b2488f0a94a1af8488bcf98b465e0cca4003d64a4699d7b4db24e086", + "docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", + "docs/web/README.md": "b8fcda4f7494af63225ca39e489540233c1654a3cd6d2d17fbe9d4114cf0bc30", "docs/api/web-management.md": "efba58e8d38734e167e767d2c75d5991202d97f519ec1829d2e531b0e3ea4af2", "contracts/agents-api/runtime-observability-api.md": "cd46e777fe716a7e76374f4655c96dfbad9d8be1bfca6203ed6118ab87874c2b", "docs/getting-started/operations.md": "5cf3f4c6fa26b6445645c501ed53f78f450e9d9a31667166afebfaf02131f9a7", "docs/user-guide.md": "078ad930003dd333e65beb152dab11809b08d58dcc9d5d108c61f60dc3a320e2", "docs/assets/development-architecture.png": "24e6d0145d4f16ad70b07b6bc643808a6455aaf6398434d199cf74def200fca6", - "docs/development.md": "f5c340253036a2cabc43e22aecdf70522d90280d6511e7649278ae93712ab8b6", + "docs/development.md": "f71297b08e51e939d84801bed2274a0a5cbcf265e8a3659b98bbaab949afeea5", "contracts/agents-api/harness-onboarding.md": "03b069b0c2ae18248cf6b1d1c82b6c3a6cd74719746bd343acb128a86d2c67a2", "docs/runtime-bootstrap.md": "0d49aed73b298039e04453e6f465b0e925fb2227fa35d4206820bb3acbd8df39", "docs/runtime-protocol.md": "4bce016e8ec239281969c84c8483044cf3247051eb062ec30d3effa00b661001", "docs/sandbox-provider.md": "7c05d1799fa027897451032ee444ab01e444795030659da59ff33ac03d45ab18", - "apps/docs/scripts/guides.json": "219d019f1cc36756eb178306a07fe1fe2bc643d1d05a169abdedab5632d5aa85" + "apps/docs/scripts/guides.json": "3b91ba5094ead91a719fa8260f5aad87e1e5aac7ecf9703b546fdef21772355b" }, "outputs": { "content/docs/index.mdx": "55c6f5320b163a46dc74c705581cc34416766132b9fb10b0f5a1cf5832b46f2f", "content/docs/concepts.mdx": "b9aceda2f72f3f1239e5518c3cecff8c9c0aa11a3eb622ddf9db6af2984a1abc", - "content/docs/execution-model.mdx": "7b8d8498a1b157270f00a8c370c9a86547fa481df9ffa8904c810946b1626b38", + "content/docs/execution-model.mdx": "faa01d7499f34fd1bacdf04860e63df0015084ab941ee9e157060c831e18266e", "content/docs/install.mdx": "90cd9f76a0ef5116363c2d20ad21465171bd0dca55f9d5d9b0df443cc3c844b3", "content/docs/install-options.mdx": "090f8840f5c164f41d6438b7b403c406ea1b7c0d695adbca3c3f06ba2193a5aa", - "content/docs/configure.mdx": "e0e335aba3b37273fb3f540c9ac3a669596ddfddf93380acdc0b2f836a942a6f", - "content/docs/bootstrap-projects-keys.mdx": "91a6f62cbe41737adfce9e8ec56e6dc3e2fb0ec8f6b677576f941cdfb378dc47", + "content/docs/configure.mdx": "1613d7a2f5c57b832cf6336a4ab51852b76ac59b8be2ca7b2b3054c32e627c7c", + "content/docs/bootstrap-projects-keys.mdx": "5aa4b230ac8b8608d0e454bbc3badd128789ceb7acb9dd7996534a649caa6655", "content/docs/quickstart.mdx": "e389d12e7417456494b67047fa5de922678634539154bd6486ff424b08344126", - "content/docs/public-api.mdx": "62d2ed6b5ddb71fd04ab2bfc62fe770b89b8100c0bb1877fdfdf68c70e7bbd10", + "content/docs/public-api.mdx": "5caac0e2c857c6f887b903c7a9b6112320dce8081ca920f6ed2ccddaac91c403", "content/docs/agents-and-tools.mdx": "3ca16d2e2ff1c759251fbdf6e071ff09a93d5efc7acaca36a48854597d4f9576", "content/docs/sessions.mdx": "e8693563738f690c21be92e0ea09e925528bc85bea2c0fb4799c3f1c4074cfba", "content/docs/examples.mdx": "5f1dde8043695c40edf775345159d93b2444d5ce6a109829b78678b0b5de0d46", "content/docs/environments-and-files.mdx": "6a59efd3c4b2f37b389b3801c596efccef5128cae65d9b56b47cdd2e1a417b13", "content/docs/hosted-providers.mdx": "da86d65fe1fa44f27600c3a0ea61e4954f894339724310be972d5e2bc03163e5", "content/docs/self-hosted-execution.mdx": "a8e6500e41c666eacb2c75882b2b8ee0cf122fe19ea36f39ef89f5dcf17b68e1", - "public/images/source/apps/web/public/onboarding/monitor-en.webp": "29dc220cb1250c7016b7c4bf7f30e9510b07c07b814a48c3aa9303b2d76f20b2", - "content/docs/console.mdx": "a930ce36035de74f0c2bef71bed067fc2287ba1c70a485758bf100d484f6adfd", + "public/images/source/docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", + "content/docs/console.mdx": "74a676fe64513cb1c76a2fc2c5400c520109efcec352fc354e9b3d64edb4e6a4", "content/docs/admin-api.mdx": "52bfea0ffc4d1e3bd1b9e476c32250e787e82b1d167c2343e662d7550b6a64e9", "content/docs/observability.mdx": "c81214e1865517a9163c48c7ce7c396f5490c9d162080dfa05fa5ebc147f6c8b", "content/docs/troubleshooting.mdx": "cf5b9e9d28fe9b56146bccc8e43d1804b3c6c5cc8ff2bb345aa4a3961329e838", "content/docs/user-guide.mdx": "92b839c8d1a2fbf4d35388c414c4524734f32724c30c07395edc994fa11cf702", "public/images/source/docs/assets/development-architecture.png": "24e6d0145d4f16ad70b07b6bc643808a6455aaf6398434d199cf74def200fca6", - "content/docs/development.mdx": "2e5249ddfca571d264e93fd1e0e390a4bd5b0b623bda100cd02f2982929850bb", + "content/docs/development.mdx": "777d3a7b7dc03d49cfa094b439d7ab905367951a7150fcc77644edbb1239f552", "content/docs/harness-onboarding.mdx": "e434d62bd0b558745774c8d729e4d86d2b40ed3026e3d77b777c48aef141c659", "content/docs/runtime-bootstrap.mdx": "58982955811c9ad46a9fc04d8d2aef5a762fc9d25d5833f88aa75ee3fc46523f", "content/docs/runtime-protocol.mdx": "560f887d5fc9fa673275a6481220f5fb997c4ece30c09384b81b14dbc7aa0dd4", diff --git a/apps/docs/public/images/source/apps/web/public/onboarding/monitor-en.webp b/apps/docs/public/images/source/apps/web/public/onboarding/monitor-en.webp deleted file mode 100644 index 70aab640b..000000000 Binary files a/apps/docs/public/images/source/apps/web/public/onboarding/monitor-en.webp and /dev/null differ diff --git a/apps/docs/public/images/source/docs/assets/console-overview-en.webp b/apps/docs/public/images/source/docs/assets/console-overview-en.webp new file mode 100644 index 000000000..b12e17ca6 Binary files /dev/null and b/apps/docs/public/images/source/docs/assets/console-overview-en.webp differ diff --git a/apps/docs/scripts/guides.json b/apps/docs/scripts/guides.json index f3dc9bab8..1cb63cf15 100644 --- a/apps/docs/scripts/guides.json +++ b/apps/docs/scripts/guides.json @@ -13,7 +13,7 @@ }, { "slug": "execution-model", - "source": "docs/web/architecture.md", + "source": "docs/web/console-server.md", "title": "Execution and API architecture", "description": "Applications call the public API; the administrator browser calls Web; Runtime connects to Core." }, @@ -37,7 +37,7 @@ }, { "slug": "bootstrap-projects-keys", - "source": "docs/web/core-connection.md", + "source": "docs/web/console-server.md", "title": "Sign in and issue application keys", "description": "Keep the Core key on the management side and issue Project API keys through Web." }, @@ -93,10 +93,7 @@ "slug": "console", "source": "docs/web/README.md", "title": "Administrator console", - "description": "Monitor Core, inspect resources and manage deployment settings through Web.", - "images": { - "docs/web/images/overview.png": "apps/web/public/onboarding/monitor-en.webp" - } + "description": "Monitor Core, inspect resources and manage deployment settings through Web." }, { "slug": "admin-api", diff --git a/apps/web/.impeccable/design.json b/apps/web/.impeccable/design.json index 02d60e6ec..05af4657e 100644 --- a/apps/web/.impeccable/design.json +++ b/apps/web/.impeccable/design.json @@ -1,235 +1,287 @@ { "schemaVersion": 2, - "generatedAt": "2026-09-24T09:59:21.057068+00:00", + "generatedAt": "2026-09-30T07:33:58.959421+00:00", "title": "Design System: OpenAgentCore Console", "extensions": { "colorMeta": { "ink": { "role": "neutral", "displayName": "Ledger Ink", - "canonical": "#37352f", - "cssVar": "--fg", - "dark": "#d4d4d4", + "canonical": "oklch(0.247 0.006 258.361)", + "cssVar": "--ink", + "dark": "oklch(0.964 0.002 247.839)", "tonalRamp": [ - "#292823", - "#4a483f", - "#6b675c", - "#8c8778", - "#a8a499", - "#c4c2ba", - "#e0dfdb", - "#f3f3f1" + "oklch(0.15 0.006 258.361)", + "oklch(0.27 0.006 258.361)", + "oklch(0.39 0.006 258.361)", + "oklch(0.51 0.006 258.361)", + "oklch(0.63 0.006 258.361)", + "oklch(0.75 0.006 258.361)", + "oklch(0.87 0.006 258.361)", + "oklch(0.95 0.006 258.361)" ] }, "ink-muted": { "role": "neutral", "displayName": "Graphite", - "canonical": "#787774", - "cssVar": "--fg-muted", - "dark": "#9b9b9b", + "canonical": "oklch(0.506 0.01 264.477)", + "cssVar": "--ink-2", + "dark": "oklch(0.731 0.008 260.731)", "tonalRamp": [ - "#272726", - "#464544", - "#656462", - "#848380", - "#a2a19f", - "#c0c0be", - "#dededd", - "#f2f2f2" + "oklch(0.15 0.01 264.477)", + "oklch(0.27 0.01 264.477)", + "oklch(0.39 0.01 264.477)", + "oklch(0.51 0.01 264.477)", + "oklch(0.63 0.01 264.477)", + "oklch(0.75 0.01 264.477)", + "oklch(0.87 0.01 264.477)", + "oklch(0.95 0.01 264.477)" ] }, "ink-subtle": { "role": "neutral", "displayName": "Pencil", - "canonical": "#9b9a97", - "cssVar": "--fg-subtle", - "dark": "#7b7b7b", + "canonical": "oklch(0.695 0.009 264.505)", + "cssVar": "--ink-3", + "dark": "oklch(0.541 0.01 264.484)", "tonalRamp": [ - "#272726", - "#464644", - "#656462", - "#848380", - "#a2a29f", - "#c0c0be", - "#dededd", - "#f2f2f2" + "oklch(0.15 0.009 264.505)", + "oklch(0.27 0.009 264.505)", + "oklch(0.39 0.009 264.505)", + "oklch(0.51 0.009 264.505)", + "oklch(0.63 0.009 264.505)", + "oklch(0.75 0.009 264.505)", + "oklch(0.87 0.009 264.505)", + "oklch(0.95 0.009 264.505)" ] }, "sidebar-ink": { "role": "neutral", "displayName": "Sidebar Ink", - "canonical": "#5f5e5a", - "cssVar": "--sidebar-fg", - "dark": "#bdbdbd", + "canonical": "oklch(0.506 0.01 264.477)", + "cssVar": "--ink-2", + "dark": "oklch(0.731 0.008 260.731)", "tonalRamp": [ - "#272725", - "#474643", - "#666561", - "#85847f", - "#a3a29e", - "#c1c0be", - "#dfdedd", - "#f3f2f2" + "oklch(0.15 0.01 264.477)", + "oklch(0.27 0.01 264.477)", + "oklch(0.39 0.01 264.477)", + "oklch(0.51 0.01 264.477)", + "oklch(0.63 0.01 264.477)", + "oklch(0.75 0.01 264.477)", + "oklch(0.87 0.01 264.477)", + "oklch(0.95 0.01 264.477)" ] }, "surface": { "role": "neutral", "displayName": "Paper", - "canonical": "#ffffff", + "canonical": "oklch(1 0 0)", "cssVar": "--surface", - "dark": "#191919", + "dark": "oklch(0.26 0.006 271.191)", "tonalRamp": [ - "#262626", - "#454545", - "#636363", - "#828282", - "#a1a1a1", - "#bfbfbf", - "#dedede", - "#f2f2f2" + "oklch(0.15 0 0)", + "oklch(0.27 0 0)", + "oklch(0.39 0 0)", + "oklch(0.51 0 0)", + "oklch(0.63 0 0)", + "oklch(0.75 0 0)", + "oklch(0.87 0 0)", + "oklch(0.95 0 0)" ] }, "surface-subtle": { "role": "neutral", "displayName": "Margin Gray", - "canonical": "#fafafa", - "cssVar": "--surface-subtle", - "dark": "#202020", + "canonical": "oklch(0.979 0.002 247.839)", + "cssVar": "--inset", + "dark": "oklch(0.243 0.004 264.492)", "tonalRamp": [ - "#262626", - "#454545", - "#636363", - "#828282", - "#a1a1a1", - "#bfbfbf", - "#dedede", - "#f2f2f2" + "oklch(0.15 0.002 247.839)", + "oklch(0.27 0.002 247.839)", + "oklch(0.39 0.002 247.839)", + "oklch(0.51 0.002 247.839)", + "oklch(0.63 0.002 247.839)", + "oklch(0.75 0.002 247.839)", + "oklch(0.87 0.002 247.839)", + "oklch(0.95 0.002 247.839)" ] }, "surface-muted": { "role": "neutral", "displayName": "Well Gray", - "canonical": "#f1f1f0", - "cssVar": "--surface-muted", - "dark": "#2a2a2a", + "canonical": "oklch(0.961 0.001 286.375)", + "cssVar": "--field", + "dark": "oklch(0.293 0.006 271.223)", "tonalRamp": [ - "#282825", - "#474742", - "#676760", - "#86867e", - "#a4a49d", - "#c1c1bd", - "#dfdfdd", - "#f3f3f2" + "oklch(0.15 0.001 286.375)", + "oklch(0.27 0.001 286.375)", + "oklch(0.39 0.001 286.375)", + "oklch(0.51 0.001 286.375)", + "oklch(0.63 0.001 286.375)", + "oklch(0.75 0.001 286.375)", + "oklch(0.87 0.001 286.375)", + "oklch(0.95 0.001 286.375)" ] }, + "canvas": { + "role": "neutral", + "displayName": "Canvas", + "canonical": "oklch(0.961 0.002 247.84)", + "cssVar": "--canvas", + "dark": "oklch(0.231 0.004 264.487)", + "tonalRamp": [ + "oklch(0.15 0.002 247.84)", + "oklch(0.27 0.002 247.84)", + "oklch(0.39 0.002 247.84)", + "oklch(0.51 0.002 247.84)", + "oklch(0.63 0.002 247.84)", + "oklch(0.75 0.002 247.84)", + "oklch(0.87 0.002 247.84)", + "oklch(0.95 0.002 247.84)" + ] + }, + "frame-line": { + "role": "neutral", + "displayName": "Card Edge", + "canonical": "color-mix(in oklch, oklch(0.247 0.006 258.361) 11%, transparent)", + "cssVar": "--frame-line", + "dark": "oklch(1 0 0 / 10%)", + "tonalRamp": [] + }, "line": { "role": "neutral", "displayName": "Hairline", - "canonical": "#e9e9ec", + "canonical": "oklch(0.946 0.003 264.542)", "cssVar": "--line", - "dark": "rgb(255 255 255 / 9%)", + "dark": "oklch(0.308 0.006 258.354)", "tonalRamp": [ - "#232329", - "#40404a", - "#5c5c6b", - "#79798b", - "#9a9aa8", - "#bbbbc4", - "#dbdbe0", - "#f1f1f3" + "oklch(0.15 0.003 264.542)", + "oklch(0.27 0.003 264.542)", + "oklch(0.39 0.003 264.542)", + "oklch(0.51 0.003 264.542)", + "oklch(0.63 0.003 264.542)", + "oklch(0.75 0.003 264.542)", + "oklch(0.87 0.003 264.542)", + "oklch(0.95 0.003 264.542)" ] }, "line-muted": { "role": "neutral", "displayName": "Faint Rule", - "canonical": "#efeff1", - "cssVar": "--line-muted", - "dark": "rgb(255 255 255 / 6%)", + "canonical": "oklch(0.966 0.002 264.542)", + "cssVar": "--line-soft", + "dark": "oklch(0.278 0.006 258.354)", "tonalRamp": [ - "#242429", - "#404049", - "#5d5d6a", - "#7a7a8a", - "#9a9aa7", - "#bbbbc3", - "#dcdce0", - "#f1f1f3" + "oklch(0.15 0.002 264.542)", + "oklch(0.27 0.002 264.542)", + "oklch(0.39 0.002 264.542)", + "oklch(0.51 0.002 264.542)", + "oklch(0.63 0.002 264.542)", + "oklch(0.75 0.002 264.542)", + "oklch(0.87 0.002 264.542)", + "oklch(0.95 0.002 264.542)" ] }, "line-strong": { "role": "neutral", "displayName": "Firm Rule", - "canonical": "#d6d7dc", + "canonical": "oklch(0.912 0.005 258.326)", "cssVar": "--line-strong", - "dark": "rgb(255 255 255 / 16%)", + "dark": "oklch(0.356 0.007 264.474)", "tonalRamp": [ - "#232429", - "#3f414a", - "#5c5e6b", - "#787b8c", - "#999ca8", - "#babcc4", - "#dbdce0", - "#f1f2f3" + "oklch(0.15 0.005 258.326)", + "oklch(0.27 0.005 258.326)", + "oklch(0.39 0.005 258.326)", + "oklch(0.51 0.005 258.326)", + "oklch(0.63 0.005 258.326)", + "oklch(0.75 0.005 258.326)", + "oklch(0.87 0.005 258.326)", + "oklch(0.95 0.005 258.326)" ] }, "hover": { "role": "neutral", - "displayName": "Hover Wash", - "canonical": "rgb(55 53 47 / 3%)", + "displayName": "Hover", + "canonical": "oklch(0.97 0.002 247.839)", "cssVar": "--hover", - "dark": "rgb(255 255 255 / 5.5%)", - "tonalRamp": [] + "dark": "oklch(0.289 0.006 271.22)", + "tonalRamp": [ + "oklch(0.15 0.002 247.839)", + "oklch(0.27 0.002 247.839)", + "oklch(0.39 0.002 247.839)", + "oklch(0.51 0.002 247.839)", + "oklch(0.63 0.002 247.839)", + "oklch(0.75 0.002 247.839)", + "oklch(0.87 0.002 247.839)", + "oklch(0.95 0.002 247.839)" + ] }, "pressed": { "role": "neutral", - "displayName": "Pressed Wash", - "canonical": "rgb(55 53 47 / 6%)", - "cssVar": "--pressed", - "dark": "rgb(255 255 255 / 10%)", - "tonalRamp": [] + "displayName": "Pressed", + "canonical": "oklch(0.933 0.003 247.86)", + "cssVar": "--hover-2", + "dark": "oklch(0.318 0.007 274.747)", + "tonalRamp": [ + "oklch(0.15 0.003 247.86)", + "oklch(0.27 0.003 247.86)", + "oklch(0.39 0.003 247.86)", + "oklch(0.51 0.003 247.86)", + "oklch(0.63 0.003 247.86)", + "oklch(0.75 0.003 247.86)", + "oklch(0.87 0.003 247.86)", + "oklch(0.95 0.003 247.86)" + ] }, "tile": { "role": "neutral", - "displayName": "Tile Wash", - "canonical": "rgb(55 53 47 / 7%)", - "cssVar": "--tile", - "dark": "rgb(255 255 255 / 10%)", - "tonalRamp": [] + "displayName": "Tile", + "canonical": "oklch(0.933 0.003 247.86)", + "cssVar": "--hover-2", + "dark": "oklch(0.318 0.007 274.747)", + "tonalRamp": [ + "oklch(0.15 0.003 247.86)", + "oklch(0.27 0.003 247.86)", + "oklch(0.39 0.003 247.86)", + "oklch(0.51 0.003 247.86)", + "oklch(0.63 0.003 247.86)", + "oklch(0.75 0.003 247.86)", + "oklch(0.87 0.003 247.86)", + "oklch(0.95 0.003 247.86)" + ] }, "accent": { "role": "primary", - "displayName": "Parsar Indigo", - "canonical": "#4f46e5", + "displayName": "OpenAgentCore Indigo", + "canonical": "oklch(0.52 0.165 277)", "cssVar": "--accent", - "dark": "#8b90f6", + "dark": "oklch(0.72 0.12 277)", "tonalRamp": [ - "#0d0943", - "#171179", - "#2119ae", - "#2f24e0", - "#625ae8", - "#958fef", - "#c8c5f7", - "#eae9fc" + "oklch(0.15 0.165 277)", + "oklch(0.27 0.165 277)", + "oklch(0.39 0.165 277)", + "oklch(0.51 0.165 277)", + "oklch(0.63 0.165 277)", + "oklch(0.75 0.165 277)", + "oklch(0.87 0.165 277)", + "oklch(0.95 0.165 277)" ] }, "accent-emphasis": { "role": "primary", "displayName": "Pressed Indigo", - "canonical": "#4338ca", - "cssVar": "--accent-emphasis", - "dark": "#a5a9f8", + "canonical": "oklch(0.47 0.16 277)", + "cssVar": "--accent-ink", + "dark": "oklch(0.8 0.1 277)", "tonalRamp": [ - "#13103c", - "#231d6d", - "#332a9d", - "#453aca", - "#726ad7", - "#a09ae4", - "#cecbf1", - "#ecebfa" + "oklch(0.15 0.16 277)", + "oklch(0.27 0.16 277)", + "oklch(0.39 0.16 277)", + "oklch(0.51 0.16 277)", + "oklch(0.63 0.16 277)", + "oklch(0.75 0.16 277)", + "oklch(0.87 0.16 277)", + "oklch(0.95 0.16 277)" ] }, "accent-fg": { @@ -237,299 +289,331 @@ "displayName": "On Indigo", "canonical": "#ffffff", "cssVar": "--accent-fg", - "dark": "#14142b", + "dark": "oklch(0.2 0.03 277)", + "tonalRamp": [] + }, + "data": { + "role": "data", + "displayName": "Data", + "canonical": "oklch(0.56 0.14 277)", + "cssVar": "--data", + "dark": "oklch(0.68 0.13 277)", "tonalRamp": [ - "#262626", - "#454545", - "#636363", - "#828282", - "#a1a1a1", - "#bfbfbf", - "#dedede", - "#f2f2f2" + "oklch(0.15 0.14 277)", + "oklch(0.27 0.14 277)", + "oklch(0.39 0.14 277)", + "oklch(0.51 0.14 277)", + "oklch(0.63 0.14 277)", + "oklch(0.75 0.14 277)", + "oklch(0.87 0.14 277)", + "oklch(0.95 0.14 277)" ] }, "success": { "role": "signal", "displayName": "Healthy Green", - "canonical": "#16a34a", - "cssVar": "--success", - "dark": "#3fb950", + "canonical": "oklch(0.6 0.12 158)", + "cssVar": "--green", + "dark": "oklch(0.72 0.12 158)", "tonalRamp": [ - "#09431f", - "#107937", - "#18af50", - "#23e169", - "#59e98e", - "#8ff0b3", - "#c5f7d7", - "#e9fcf0" + "oklch(0.15 0.12 158)", + "oklch(0.27 0.12 158)", + "oklch(0.39 0.12 158)", + "oklch(0.51 0.12 158)", + "oklch(0.63 0.12 158)", + "oklch(0.75 0.12 158)", + "oklch(0.87 0.12 158)", + "oklch(0.95 0.12 158)" ] }, "warning": { "role": "signal", "displayName": "Caution Amber", - "canonical": "#d97706", - "cssVar": "--warning", - "dark": "#f5a524", + "canonical": "oklch(0.68 0.135 62)", + "cssVar": "--orange", + "dark": "oklch(0.76 0.12 65)", "tonalRamp": [ - "#4a2902", - "#864904", - "#c26a05", - "#f88a0c", - "#faa747", - "#fcc483", - "#fde0be", - "#fef3e6" + "oklch(0.15 0.135 62)", + "oklch(0.27 0.135 62)", + "oklch(0.39 0.135 62)", + "oklch(0.51 0.135 62)", + "oklch(0.63 0.135 62)", + "oklch(0.75 0.135 62)", + "oklch(0.87 0.135 62)", + "oklch(0.95 0.135 62)" ] }, "danger": { "role": "signal", "displayName": "Fault Red", - "canonical": "#dc2626", - "cssVar": "--danger", - "dark": "#f05252", + "canonical": "oklch(0.585 0.17 25)", + "cssVar": "--red", + "dark": "oklch(0.68 0.15 25)", "tonalRamp": [ - "#420b0b", - "#771313", - "#ab1c1c", - "#dc2828", - "#e55d5d", - "#ed9191", - "#f6c6c6", - "#fbe9e9" + "oklch(0.15 0.17 25)", + "oklch(0.27 0.17 25)", + "oklch(0.39 0.17 25)", + "oklch(0.51 0.17 25)", + "oklch(0.63 0.17 25)", + "oklch(0.75 0.17 25)", + "oklch(0.87 0.17 25)", + "oklch(0.95 0.17 25)" ] }, "status-queued": { "role": "signal", "displayName": "Queued Gray", - "canonical": "#9a9ca4", - "cssVar": "--status-queued", - "dark": "#71737b", - "tonalRamp": [ - "#242528", - "#414348", - "#5e6069", - "#7c7e89", - "#9c9ea6", - "#bcbdc3", - "#dcdde0", - "#f2f2f3" - ] - }, - "status-idle": { - "role": "signal", - "displayName": "Idle Gray", - "canonical": "#b4b4b9", - "cssVar": "--status-idle", - "dark": "#5e5e62", + "canonical": "oklch(0.695 0.009 264.505)", + "cssVar": "--ink-3", + "dark": "oklch(0.541 0.01 264.484)", "tonalRamp": [ - "#252528", - "#424247", - "#606067", - "#7e7e86", - "#9d9da4", - "#bdbdc1", - "#dddddf", - "#f2f2f3" + "oklch(0.15 0.009 264.505)", + "oklch(0.27 0.009 264.505)", + "oklch(0.39 0.009 264.505)", + "oklch(0.51 0.009 264.505)", + "oklch(0.63 0.009 264.505)", + "oklch(0.75 0.009 264.505)", + "oklch(0.87 0.009 264.505)", + "oklch(0.95 0.009 264.505)" ] }, "series-1": { "role": "data", - "displayName": "Cobalt", - "canonical": "#2a78d6", + "displayName": "Indigo", + "canonical": "oklch(0.56 0.14 277)", "cssVar": "--series-1", - "dark": "#3987e5", + "dark": "oklch(0.68 0.13 277)", "tonalRamp": [ - "#0c2440", - "#164173", - "#205da7", - "#2d7ad7", - "#619be1", - "#94bbea", - "#c7dcf4", - "#eaf1fb" + "oklch(0.15 0.14 277)", + "oklch(0.27 0.14 277)", + "oklch(0.39 0.14 277)", + "oklch(0.51 0.14 277)", + "oklch(0.63 0.14 277)", + "oklch(0.75 0.14 277)", + "oklch(0.87 0.14 277)", + "oklch(0.95 0.14 277)" ] }, "series-2": { "role": "data", - "displayName": "Persimmon", - "canonical": "#eb6834", + "displayName": "Teal", + "canonical": "oklch(0.7 0.09 195)", "cssVar": "--series-2", - "dark": "#d95926", + "dark": "oklch(0.74 0.09 195)", "tonalRamp": [ - "#461907", - "#7d2c0c", - "#b54012", - "#e9561c", - "#ee7f53", - "#f4a98b", - "#f9d2c3", - "#fdeee8" + "oklch(0.15 0.09 195)", + "oklch(0.27 0.09 195)", + "oklch(0.39 0.09 195)", + "oklch(0.51 0.09 195)", + "oklch(0.63 0.09 195)", + "oklch(0.75 0.09 195)", + "oklch(0.87 0.09 195)", + "oklch(0.95 0.09 195)" ] }, "series-3": { "role": "data", - "displayName": "Jade", - "canonical": "#1baf7a", + "displayName": "Ochre", + "canonical": "oklch(0.78 0.11 80)", "cssVar": "--series-3", - "dark": "#199e70", + "dark": "oklch(0.8 0.1 80)", "tonalRamp": [ - "#0a422e", - "#127753", - "#1bac78", - "#27de9c", - "#5ce6b4", - "#91eecd", - "#c6f6e5", - "#e9fcf5" + "oklch(0.15 0.11 80)", + "oklch(0.27 0.11 80)", + "oklch(0.39 0.11 80)", + "oklch(0.51 0.11 80)", + "oklch(0.63 0.11 80)", + "oklch(0.75 0.11 80)", + "oklch(0.87 0.11 80)", + "oklch(0.95 0.11 80)" ] }, "series-4": { "role": "data", - "displayName": "Saffron", - "canonical": "#eda100", + "displayName": "Coral", + "canonical": "oklch(0.66 0.12 20)", "cssVar": "--series-4", - "dark": "#c98500", + "dark": "oklch(0.72 0.11 20)", "tonalRamp": [ - "#4c3400", - "#8a5e00", - "#c78700", - "#ffaf05", - "#ffc242", - "#ffd680", - "#ffeabd", - "#fff7e5" + "oklch(0.15 0.12 20)", + "oklch(0.27 0.12 20)", + "oklch(0.39 0.12 20)", + "oklch(0.51 0.12 20)", + "oklch(0.63 0.12 20)", + "oklch(0.75 0.12 20)", + "oklch(0.87 0.12 20)", + "oklch(0.95 0.12 20)" ] }, "series-5": { "role": "data", - "displayName": "Rose", - "canonical": "#e87ba4", + "displayName": "Slate", + "canonical": "oklch(0.62 0.06 250)", "cssVar": "--series-5", - "dark": "#d55181", + "dark": "oklch(0.7 0.06 250)", "tonalRamp": [ - "#410b20", - "#751439", - "#a91e52", - "#da2a6c", - "#e35e90", - "#ec92b4", - "#f5c7d8", - "#fbe9f0" + "oklch(0.15 0.06 250)", + "oklch(0.27 0.06 250)", + "oklch(0.39 0.06 250)", + "oklch(0.51 0.06 250)", + "oklch(0.63 0.06 250)", + "oklch(0.75 0.06 250)", + "oklch(0.87 0.06 250)", + "oklch(0.95 0.06 250)" ] }, "series-6": { "role": "data", - "displayName": "Forest", - "canonical": "#008300", + "displayName": "Sage", + "canonical": "oklch(0.72 0.08 145)", "cssVar": "--series-6", - "dark": "#008300", + "dark": "oklch(0.76 0.08 145)", "tonalRamp": [ - "#004c00", - "#008a00", - "#00c700", - "#05ff05", - "#42ff42", - "#80ff80", - "#bdffbd", - "#e5ffe5" + "oklch(0.15 0.08 145)", + "oklch(0.27 0.08 145)", + "oklch(0.39 0.08 145)", + "oklch(0.51 0.08 145)", + "oklch(0.63 0.08 145)", + "oklch(0.75 0.08 145)", + "oklch(0.87 0.08 145)", + "oklch(0.95 0.08 145)" ] }, "series-other": { "role": "data", "displayName": "Series Other", - "canonical": "#a3a3a8", + "canonical": "oklch(0.82 0.008 264)", "cssVar": "--series-other", - "dark": "#6d6d72", + "dark": "oklch(0.45 0.01 264)", "tonalRamp": [ - "#252527", - "#434347", - "#616166", - "#7f7f86", - "#9e9ea3", - "#bdbdc1", - "#dddddf", - "#f2f2f3" + "oklch(0.15 0.008 264)", + "oklch(0.27 0.008 264)", + "oklch(0.39 0.008 264)", + "oklch(0.51 0.008 264)", + "oklch(0.63 0.008 264)", + "oklch(0.75 0.008 264)", + "oklch(0.87 0.008 264)", + "oklch(0.95 0.008 264)" ] }, - "meter-track": { + "meter-fill": { "role": "data", - "displayName": "Meter Track", - "canonical": "rgb(55 53 47 / 8%)", - "cssVar": "--meter-track", - "dark": "rgb(255 255 255 / 9%)", + "displayName": "Meter Fill", + "canonical": "color-mix(in srgb, oklch(0.247 0.006 258.361) 62%, transparent)", + "cssVar": "--meter-fill", + "dark": "color-mix(in srgb, oklch(0.964 0.002 247.839) 62%, transparent)", "tonalRamp": [] } }, "typographyMeta": { + "metric": { + "displayName": "Metric", + "purpose": "The four Overview tiles; tabular numerals." + }, "display": { "displayName": "KPI Figure", - "purpose": "KPI strip values only; tabular numerals." + "purpose": "KPI strip values; tabular numerals, one step above body text." }, "headline": { "displayName": "Page Title", - "purpose": "One per page in the 64px header." + "purpose": "One per page in the page header." }, "title": { "displayName": "Section Title", - "purpose": "Section headings; chart captions step down to 13px/600." + "purpose": "Section and card headings; dialog titles step up to 15px." }, "body": { "displayName": "Body", - "purpose": "Table cells, controls, fields; document base is 14px/20px." + "purpose": "Table cells, controls, fields, dialog text; document base is 14px/20px." }, "label": { "displayName": "Label", - "purpose": "KPI labels, status labels, text actions, segmented options." + "purpose": "KPI labels, column headers, text actions, segmented options, the list count." }, "mono": { "displayName": "Mono", - "purpose": "Identifiers and code in Graphite." + "purpose": "IDs in name cells (Graphite) and other code in tables (Pencil)." } }, "shadows": [ { - "name": "shadow-control", - "value": "0 1px 2px rgb(0 0 0 / 6%)", - "dark": "0 1px 2px rgb(0 0 0 / 40%)", - "purpose": "Raised controls only: primary/outline buttons, fields, active segment, active filter tab, selected fleet target." + "name": "panel-shadow", + "value": "0 0 0 1px var(--line), 0 1px 2px oklch(0 0 0 / 3%), 0 8px 24px -12px oklch(0 0 0 / 8%)", + "dark": "0 0 0 1px oklch(1 0 0 / 8%), 0 8px 28px -12px oklch(0 0 0 / 50%)", + "purpose": "The white page panel on the canvas." }, { - "name": "shadow-floating", - "value": "0 1px 2px rgb(24 24 27 / 4%), 0 8px 24px -12px rgb(24 24 27 / 18%)", - "dark": "0 1px 2px rgb(0 0 0 / 30%), 0 8px 24px -12px rgb(0 0 0 / 55%)", - "purpose": "Help-tip popovers, chart tooltips, menus and dialogs." + "name": "shadow-card", + "value": "0 0 0 1px var(--frame-line)", + "dark": "0 0 0 1px oklch(1 0 0 / 10%)", + "purpose": "Cards, KPI strips, table frames, chart grids and empty states: a ring only, no drop shadow." + }, + { + "name": "shadow-btn", + "value": "0 0 0 1px var(--line-strong), var(--shadow-xs)", + "dark": "0 0 0 1px oklch(1 0 0 / 0.1), 0 1px 2px oklch(0 0 0 / 0.3)", + "purpose": "Outline buttons, the active segment, and search fields and selects in toolbars." + }, + { + "name": "shadow-hairline", + "value": "0 0 0 1px var(--line)", + "dark": "0 0 0 1px var(--line)", + "purpose": "The ring of inputs and selects inside cards and dialogs, and of count and value pills." + }, + { + "name": "shadow-overlay", + "value": "0 0 0 1px var(--line), var(--shadow-lg)", + "dark": "0 0 0 1px oklch(1 0 0 / 0.15), 0 8px 28px oklch(0 0 0 / 0.34)", + "purpose": "Anchored popovers, menus, dialogs, help tips and chart tooltips." } ], "motion": [ { - "name": "ease-settle", - "value": "cubic-bezier(0.22, 1, 0.36, 1)", - "purpose": "Default easing for colour, background and opacity state changes (150ms)." + "name": "ease-out-strong", + "value": "cubic-bezier(0.23, 1, 0.32, 1)", + "purpose": "Colour, background and press transitions of buttons, navigation items and segments (150ms); press scales buttons to 0.96." }, { "name": "ease-spring", "value": "cubic-bezier(0.34, 1.56, 0.64, 1)", - "purpose": "Button press scale to 0.97 (120ms)." + "purpose": "Dialog entry: pop-in from 96% scale in 200ms." + }, + { + "name": "ease-settle", + "value": "cubic-bezier(0.22, 1, 0.36, 1)", + "purpose": "Dialog exit (150ms) and overlay fades." + }, + { + "name": "layout-glide", + "value": "spring, 320ms, no bounce (Motion shared layout)", + "purpose": "The navigation chip and segmented thumbs glide to a new choice." + }, + { + "name": "popover-in", + "value": "opacity 0 to 1, scale 98% to 100%", + "purpose": "Anchored popovers." }, { "name": "page-in", - "value": "opacity 0 to 1, 160ms ease-settle", - "purpose": "Page change settle; no choreography. Disabled under reduced motion." + "value": "opacity 0 to 1, 160ms", + "purpose": "Page change; no choreography." }, { - "name": "help-tip-in", - "value": "opacity 0 to 1, 120ms ease-settle", - "purpose": "Help-tip popover appearance." + "name": "skeleton-sweep", + "value": "900ms linear, repeating", + "purpose": "First-read skeleton bars; stops under reduced motion." }, { - "name": "refetch-dim", - "value": "opacity 0.62, 180ms ease-settle", - "purpose": "Page body while refetching." + "name": "fleet-link-flow", + "value": "4s linear, repeating", + "purpose": "The moving dash on a live topology link; none on offline links, a stale topology or under reduced motion." } ], "breakpoints": [ + { + "name": "sidebar-rail", + "value": "640px" + }, { "name": "min-desktop", "value": "960px" @@ -545,148 +629,159 @@ "name": "Primary Button", "kind": "button", "refersTo": "button-primary", - "description": "The one filled action on a page; indigo is reserved for this and selection.", - "html": "", - "css": ".ds-btn { display:inline-flex; height:28px; align-items:center; gap:6px; padding:0 10px; font:500 13px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; border:1px solid transparent; border-radius:6px; cursor:pointer; transition: background-color 150ms cubic-bezier(0.22,1,0.36,1), border-color 150ms cubic-bezier(0.22,1,0.36,1), transform 120ms cubic-bezier(0.34,1.56,0.64,1); } .ds-btn-primary { color: var(--accent-fg, #fff); background: var(--accent, #4f46e5); border-color: var(--accent, #4f46e5); box-shadow: 0 1px 2px rgb(0 0 0 / 6%); } .ds-btn-primary:hover { background: var(--accent-emphasis, #4338ca); border-color: var(--accent-emphasis, #4338ca); } .ds-btn:focus-visible { outline:none; border-color: var(--accent, #4f46e5); box-shadow: 0 0 0 1px var(--accent, #4f46e5); } .ds-btn:active { transform: scale(0.97); }" + "description": "The one filled button in a header or a non-destructive dialog; ink, not indigo.", + "html": "", + "css": ".ds-btn { display:inline-flex; height:30px; align-items:center; gap:6px; padding:0 13px; font:500 13px/1 \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; border:0; border-radius:8px; cursor:pointer; transition: transform 150ms cubic-bezier(0.23,1,0.32,1), background-color 150ms cubic-bezier(0.23,1,0.32,1), opacity 150ms cubic-bezier(0.23,1,0.32,1); } .ds-btn-primary { color: var(--canvas); background: var(--ink); box-shadow: inset 0 1px 0 rgb(255 255 255 / 14%), var(--shadow-xs); } .ds-btn-primary:hover { opacity: 0.88; } .ds-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .ds-btn:active { transform: scale(0.96); }" }, { - "name": "Outline Button (Refresh)", + "name": "Outline Button", "kind": "button", "refersTo": "button-outline", - "description": "Default page-header action; Refresh keeps its last-updated time in the tooltip.", - "html": "", - "css": ".ds-btn-outline { display:inline-flex; height:28px; align-items:center; gap:6px; padding:0 10px; color: var(--fg, #37352f); font:500 13px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--surface, #fff); border:1px solid var(--line-strong, #d6d7dc); border-radius:6px; box-shadow: 0 1px 2px rgb(0 0 0 / 6%); cursor:pointer; transition: background-color 150ms cubic-bezier(0.22,1,0.36,1); } .ds-btn-outline:hover { background: var(--hover, rgb(55 53 47 / 3%)); } .ds-btn-outline:focus-visible { outline:none; border-color: var(--accent, #4f46e5); box-shadow: 0 0 0 1px var(--accent, #4f46e5); }" + "description": "Every action in a card or section header; Paper with the Firm Rule ring.", + "html": "", + "css": ".ds-btn-outline { display:inline-flex; height:30px; align-items:center; gap:6px; padding:0 13px; font:500 13px/1 \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; border:0; border-radius:8px; cursor:pointer; transition: transform 150ms cubic-bezier(0.23,1,0.32,1), background-color 150ms cubic-bezier(0.23,1,0.32,1), opacity 150ms cubic-bezier(0.23,1,0.32,1); color: var(--ink); background: var(--surface); box-shadow: var(--shadow-btn); } .ds-btn-outline:hover { background: var(--inset); } .ds-btn-outline:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .ds-btn-outline:active { transform: scale(0.96); }" }, { "name": "Text Field", "kind": "input", "refersTo": "input-field", - "description": "28px form field with a firm rule border and indigo focus ring.", + "description": "30px filled field with a Hairline ring inside cards and dialogs; focus turns it Paper with an indigo ring.", "html": "", - "css": ".ds-input { width:240px; min-height:28px; padding:4px 8px; color: var(--fg, #37352f); font:400 13px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--surface, #fff); border:1px solid var(--line-strong, #d6d7dc); border-radius:6px; box-shadow: 0 1px 2px rgb(0 0 0 / 6%); } .ds-input:focus { outline:none; border-color: var(--accent, #4f46e5); box-shadow: 0 0 0 1px var(--accent, #4f46e5); }" + "css": ".ds-input { width:240px; height:30px; padding:0 10px; color: var(--ink); font:400 13px/18px \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; background: var(--field); border:0; border-radius:8px; box-shadow: var(--shadow-hairline); } .ds-input::placeholder { color: var(--ink-3); } .ds-input:hover { background: var(--hover-2); } .ds-input:focus { outline:none; background: var(--surface); box-shadow: 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-tint); }" }, { "name": "Segmented Range Control", "kind": "chip", "refersTo": "segmented-option", - "description": "The single style for time ranges and mode switches.", - "html": "
", - "css": ".ds-seg { display:inline-flex; gap:2px; padding:2px; background: var(--surface-muted, #f1f1f0); border-radius:7px; } .ds-seg button { height:24px; padding:0 10px; color: var(--fg-muted, #787774); font:500 12.5px -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Segoe UI', sans-serif; background:transparent; border:0; border-radius:5px; cursor:pointer; transition: color 150ms cubic-bezier(0.22,1,0.36,1), background-color 150ms cubic-bezier(0.22,1,0.36,1); } .ds-seg button:hover { color: var(--fg, #37352f); } .ds-seg button[aria-checked='true'] { color: var(--fg, #37352f); background: var(--surface, #fff); box-shadow: 0 1px 2px rgb(0 0 0 / 6%); } .ds-seg button:focus-visible { outline:2px solid var(--accent, #4f46e5); outline-offset:2px; }" + "description": "The single style for ranges, order and status filters; the chosen option sits on a Paper thumb.", + "html": "
", + "css": ".ds-seg { display:inline-flex; gap:2px; padding:2px; background: var(--hover-2); border-radius:8px; } .ds-seg button { height:26px; padding:0 11px; color: var(--ink-2); font:500 12.5px/1 \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; background: transparent; border:0; border-radius:6px; cursor:pointer; } .ds-seg button:hover { color: var(--ink); } .ds-seg button[aria-checked=\"true\"] { color: var(--ink); background: var(--surface); box-shadow: var(--shadow-btn); }" }, { "name": "Sidebar Navigation", "kind": "nav", "refersTo": "nav-item", - "description": "Grouped sidebar items on the subtle ground; active item takes the pressed wash.", - "html": "", - "css": ".ds-nav { width:212px; padding:10px; color: var(--sidebar-fg, #5f5e5a); background: var(--surface-subtle, #fafafa); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .ds-nav-label { margin:0; padding:14px 8px 4px; color: var(--fg-muted, #787774); font-size:12px; line-height:16px; } .ds-nav button { display:flex; align-items:center; gap:8px; width:100%; height:30px; margin:1px 0; padding:0 8px; color:inherit; font-size:14px; background:transparent; border:0; border-radius:6px; cursor:pointer; transition: background-color 150ms cubic-bezier(0.22,1,0.36,1); } .ds-nav button:hover { background: var(--hover, rgb(55 53 47 / 3%)); } .ds-nav button.active { color: var(--fg, #37352f); font-weight:500; background: var(--pressed, rgb(55 53 47 / 6%)); } .ds-nav button:focus-visible { outline:none; box-shadow: 0 0 0 1px var(--accent, #4f46e5); }" + "description": "Grouped items on the canvas; the active item is a white chip ringed like the page panel.", + "html": "", + "css": ".ds-nav { width:216px; padding:10px 8px; background: var(--canvas); font-family: \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-nav-label { margin:0; padding:14px 8px 6px; color: var(--ink-3); font-size:12px; font-weight:500; } .ds-nav button { display:flex; width:100%; height:32px; align-items:center; gap:8px; margin:0 0 1px; padding:0 8px; color: var(--ink-2); font-size:14px; font-weight:500; background: transparent; border:0; border-radius:8px; cursor:pointer; } .ds-nav button:hover { color: var(--ink); background: var(--hover-2); } .ds-nav button.active { color: var(--ink); background: var(--surface); box-shadow: 0 0 0 1px var(--frame-line), 0 1px 2px oklch(0 0 0 / 4%); }" }, { "name": "KPI Strip", "kind": "card", "refersTo": "kpi-cell", - "description": "One hairline frame of figures divided by internal rules, each label with a help tip.", - "html": "
Service ?
Degraded
Sandbox slots
12 / 16
Reported tokens
4.16M
P95 latency
—
", - "css": ".ds-kpis { display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); margin:0; overflow:hidden; border:1px solid var(--line, #e9e9ec); border-radius:8px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .ds-kpi { display:flex; flex-direction:column; gap:6px; padding:14px 16px 16px; box-shadow: inset -1px 0 var(--line, #e9e9ec); } .ds-kpi dt { display:flex; align-items:center; gap:4px; color: var(--fg-muted, #787774); font-size:12.5px; line-height:18px; } .ds-q { display:inline-grid; place-items:center; width:13px; height:13px; border:1.2px solid var(--fg-subtle, #9b9a97); border-radius:50%; color: var(--fg-subtle, #9b9a97); font-size:9px; } .ds-kpi dd { display:flex; align-items:center; gap:7px; margin:0; color: var(--fg, #37352f); font-size:24px; font-weight:600; line-height:28px; letter-spacing:-0.02em; font-variant-numeric: tabular-nums; } .ds-tone { width:8px; height:8px; border-radius:50%; } .ds-tone.warn { background: var(--warning, #d97706); }" + "description": "One card of equal cells divided by inset rules; figures at 20px/500, units small beside them.", + "html": "
Service
Degraded
Sandbox slots
12 / 16
Reported tokens
4.16M
P95 duration
—
", + "css": ".ds-kpis { display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); margin:0; overflow:hidden; background: var(--surface); border-radius:12px; box-shadow: var(--shadow-card); font-family: \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-kpi { display:grid; gap:4px; padding:14px 16px 16px; box-shadow: inset -1px 0 var(--grid-line), inset 0 -1px var(--grid-line); } .ds-kpi dt { color: var(--ink-2); font-size:12.5px; font-weight:500; } .ds-kpi dd { display:flex; align-items:center; gap:8px; margin:0; color: var(--ink); font-size:20px; font-weight:500; line-height:26px; letter-spacing:-0.015em; font-variant-numeric: tabular-nums; } .ds-tone { width:8px; height:8px; border-radius:50%; } .ds-tone.warn { background: var(--orange); }" }, { "name": "Status Dot", "kind": "custom", "refersTo": "status-dot", - "description": "A 7px dot plus a plain label; never colour alone, never a pill.", + "description": "A 6px dot plus a plain 13px label; never colour alone, never a pill.", "html": "Running Needs action Failed Idle", - "css": ".ds-status { display:inline-flex; align-items:center; gap:6px; margin-right:12px; color: var(--fg, #37352f); font:400 12.5px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .ds-status i { width:7px; height:7px; border-radius:50%; background: var(--fg-subtle, #9b9a97); } .ds-status.ok i { background: var(--success, #16a34a); } .ds-status.warn i { background: var(--warning, #d97706); } .ds-status.danger i { background: var(--danger, #dc2626); } .ds-status.idle i { background: var(--status-idle, #b4b4b9); }" + "css": ".ds-status { display:inline-flex; align-items:center; gap:6px; margin-right:12px; color: var(--ink); font:400 13px/18px \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-status i { width:6px; height:6px; border-radius:50%; background: var(--ink-3); } .ds-status.ok i { background: var(--green); } .ds-status.warn i { background: var(--orange); } .ds-status.danger i { background: var(--red); }" }, { "name": "Meter", "kind": "custom", "refersTo": "meter", - "description": "Ratio against a limit; series fill until 80%, amber to 95%, red beyond.", - "html": "
Active sandboxes12 / 16
gpu-worker-027 / 8
", - "css": ".ds-meter-row { display:grid; grid-template-columns: 120px 1fr auto; align-items:center; gap:12px; margin:6px 0; color: var(--fg-muted, #787774); font:400 12.5px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .ds-meter-row b { color: var(--fg, #37352f); font-weight:500; font-variant-numeric: tabular-nums; } .ds-meter { position:relative; display:block; height:6px; overflow:hidden; background: var(--meter-track, rgb(55 53 47 / 8%)); border-radius:999px; } .ds-meter > span { position:absolute; inset:0 auto 0 0; background: var(--meter-fill, #2a78d6); border-radius:999px; } .ds-meter.warn > span { background: var(--warning, #d97706); }" + "description": "A 5px Well Gray rail with a neutral ink fill that turns amber at 90% and red at 100% of its limit.", + "html": "
Active sandboxes12 / 16
node-0211 / 12
", + "css": ".ds-meter-row { display:grid; grid-template-columns: 120px 1fr auto; align-items:center; gap:12px; margin:6px 0; color: var(--ink-2); font:400 12.5px/18px \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-meter-row b { color: var(--ink); font-weight:500; font-variant-numeric: tabular-nums; } .ds-meter { position:relative; display:block; height:5px; overflow:hidden; background: var(--field); border-radius:999px; box-shadow: inset 0 0 0 1px var(--grid-line); } .ds-meter > span { position:absolute; inset:0 auto 0 0; background: var(--meter-fill); border-radius:999px; } .ds-meter.warn > span { background: var(--orange); }" }, { "name": "Data Table", "kind": "custom", "refersTo": "table-row", - "description": "Hairline-framed table with sticky subtle header, 44px rows, right-aligned tabular numerics.", + "description": "A card with a 36px Paper header over a Hairline, 44px rows divided by Faint Rules, right-aligned tabular numerics.", "html": "
AgentRequestsError rate
Contract checker571.8%
Code reviewer20—
", - "css": ".ds-table { overflow:auto; border:1px solid var(--line, #e9e9ec); border-radius:8px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } .ds-table table { width:100%; border-collapse:collapse; font-size:13px; line-height:18px; } .ds-table th { height:34px; padding:0 12px; color: var(--fg-muted, #787774); font-size:12px; font-weight:500; text-align:left; background: var(--surface-subtle, #fafafa); border-bottom:1px solid var(--line, #e9e9ec); } .ds-table td { height:44px; padding:6px 12px; color: var(--fg, #37352f); border-bottom:1px solid var(--line-muted, #efeff1); } .ds-table tr:last-child td { border-bottom:0; } .ds-table tbody tr:hover { background: var(--hover, rgb(55 53 47 / 3%)); } .ds-table td strong { font-weight:500; } .ds-table tbody tr:hover strong { color: var(--accent, #4f46e5); } .ds-table .n { text-align:right; font-variant-numeric: tabular-nums; }" + "css": ".ds-table { overflow:auto; background: var(--surface); border-radius:12px; box-shadow: var(--shadow-card); font-family: \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-table table { width:100%; border-collapse:collapse; font-size:13px; line-height:18px; } .ds-table th { height:36px; padding:0 12px; color: var(--ink-2); font-size:12.5px; font-weight:500; text-align:left; background: var(--surface); border-bottom:1px solid var(--line); } .ds-table td { height:44px; padding:6px 12px; color: var(--ink); border-bottom:1px solid var(--grid-line); } .ds-table tr:hover td { background: var(--hover); } .ds-table .n { text-align:right; font-variant-numeric: tabular-nums; }" } ], "narrative": { "northStar": "The Operator's Ledger", - "overview": "The console is an operations back office, not a developer showroom. Every screen reads like a ledger page: one header rule, one strip of figures, then ruled sections of evidence (charts, bar lists, tables) sitting on white. Structure comes from 1px hairlines and whitespace, never from floating cards; colour is spent on data and state, almost never on decoration. The one indigo accent means \"you selected this\" or \"this is the primary action,\" and nothing else.\n\nDensity is deliberately high and calm: 13px body, 44px table rows, 28px controls, tabular figures in every column. The system is bilingual (zh-CN and English) and ships light and dark themes on the same token names; dark swaps values, never structure. It honours reduced motion and treats keyboard focus as a first-class state (2px indigo outline).\n\nThe data contract is part of the look. Core reports only what it observes, so the interface shows absence honestly: an em dash, a gap in a line, the word \"Unavailable\". An explained figure keeps its explanation one click away behind a circled question mark, so the page stays a ledger rather than a leaflet.", + "overview": "The console is a management tool, not a developer showroom. Every screen reads like a ledger page laid on a desk: a quiet canvas, one white page panel, one header, then white cards holding the evidence (figures, charts, tables). Structure comes from the card edge, 1px internal rules and whitespace; there are no cards inside cards. Colour is spent on problems and on the data itself, almost never on decoration. The indigo accent means \"you selected this\" or \"this is a link\"; its data shade (`--data`) means \"this is the single measured quantity\". Primary actions are filled with ink.\n\nDensity is deliberately high and calm: 13px body, 44px table rows, 30px controls, tabular figures in every column. The system is bilingual (zh-CN and English) and ships light and dark themes on the same token names; dark swaps values, never structure. It honours reduced motion and treats keyboard focus as a first-class state (2px indigo outline).\n\nThe data contract is part of the look. Core reports only what it observes, so the interface shows absence honestly: an em dash, a gap in a line, the word \"Unavailable\" or \"Unknown\". Explanations stay one click away behind a circled question mark, so the page stays a ledger rather than a leaflet.", "keyCharacteristics": [ - "Neutral warm-gray ink on white, one indigo accent for selection and primary actions only.", - "Hairline frames divided by internal rules; no nested cards, no decorative shadows.", - "A six-slot categorical palette for data, bound to the entity, not its rank.", + "Each page sits in one white panel with 14px corners on a cool gray canvas, which also holds the sidebar. Inside the panel, cards are drawn by a hairline ring, and the page header is white with a hairline rule under it.", + "One indigo voice for selection, focus, links and single-series data (`--data`); the primary button is ink.", + "Meters are neutral ink; green, amber and red appear only when something is wrong or a state needs reporting.", + "A six-slot categorical palette for multi-series data, bound to the entity, not its rank.", + "One list grammar on every resource page: project filter, search, count, name with compact ID, creator, row actions.", "Tabular numerals everywhere a number can line up.", "Status is always a dot plus a plain-language label.", - "Explanations live behind \"?\" help tips; errors, warnings and safety notices stay visible." + "Explanations live behind \"?\" help tips; errors, warnings and safety notices stay visible. No small print: an empty state's explanation is a help tip beside its title, a field's rules a help tip beside its label, and filler lines are cut." ], "rules": [ { - "name": "The One Voice Rule", - "body": "Indigo is for selection and primary actions only. Data never wears the accent; charts, bar lists and meters draw from `--series-1..6`, `--series-other` and `--meter-fill`.", + "name": "The Colour Only for Problems Rule", + "body": "A healthy state is drawn in ink. Meters fill in neutral ink and turn amber or red only past their thresholds; tone dots appear only on figures that report a state.", "section": "colors" }, { - "name": "The Entity Owns Its Colour Rule", - "body": "A categorical colour follows the entity (model, tool, node), never its rank. An entity keeps its slot while visible; only slots of entities that left the view are reused. Anything beyond six series collapses into Series Other.", + "name": "The One Voice Rule", + "body": "Indigo is for selection, focus, links and the single `--data` series. Multi-series charts draw from `--series-1..6` and `--series-other`, never from the accent.", "section": "colors" }, { - "name": "The Signal Is Not Decoration Rule", - "body": "Green, amber and red appear only when they report a state. A healthy meter stays in the series ramp; it turns amber or red only when the value crosses its threshold.", + "name": "The Entity Owns Its Colour Rule", + "body": "A categorical colour follows the entity (model, tool), never its rank. An entity keeps its slot while visible; only slots of entities that left the view are reused. Anything beyond six series collapses into Series Other.", "section": "colors" }, { "name": "The Columns Line Up Rule", - "body": "Every figure that can share a column uses tabular numerals (`font-variant-numeric: tabular-nums`): KPI values, numeric table cells (right-aligned), bar-list values, axis ticks, tooltip values, counts.", + "body": "Every figure that can share a column uses tabular numerals: KPI and tile values, numeric table cells (right-aligned), legend totals, axis ticks, tooltip values, counts.", "section": "typography" }, { "name": "The Honest Figure Rule", - "body": "Missing data renders as \"—\", a gap in the line, or \"Unavailable\"; never as 0. Compact numbers keep two decimals only when the integer part is a single digit (\"1.04M\"), otherwise one (\"415.7万\"); values under 10,000 print whole. Durations read \"850 ms / 12.4 s / 4m 12s / 3h 5m\".", + "body": "Missing data renders as \"—\", a gap in the line, \"Unavailable\" or \"Unknown\"; never as 0. Compact numbers keep two decimals only when the integer part is a single digit (\"1.04M\"), otherwise one (\"415.7万\"); values under 10,000 print whole. Durations read \"850 ms / 12.4 s / 4m 12s / 3h 5m\".", "section": "typography" }, { "name": "The Plain Vocabulary Rule", - "body": "zh-CN copy uses one term per concept: 沙箱 (sandbox), 运行时 (runtime), 已上报 (reported), 活跃 (active), 提供方 (provider). Time ranges read \"1 小时 / 6 小时 / 24 小时 / 7 天\" (English \"1h / 6h / 24h / 7d\"), always in the one segmented control style.", + "body": "zh-CN copy uses one term per concept: 项目 (project), 沙箱 (sandbox), 运行时 (runtime), 创建者 (creator), 已上报 (reported), 活跃 (active), 提供方 (provider). API terms stay in English (Agent, Session, Turn, Skill, Vault, Credential, API key). Time ranges read \"1 小时 / 6 小时 / 24 小时 / 7 天\" (English \"1h / 6h / 24h / 7d\"), always in the one segmented control style.", "section": "typography" }, + { + "name": "The Liveness Only Rule", + "body": "The moving dash on a topology link shows that a connection is live, never traffic or work. An offline link is dashed and still, a stale topology shows no movement, and reduced motion stops the animation.", + "section": "layout" + }, { "name": "The One Page Grammar Rule", - "body": "Every page, new or legacy, uses PageHeader, PageBody and Section. No page invents its own header height, gutter or section rhythm.", + "body": "Every page uses PageHeader, PageBody and Section from `components/console-ui.tsx`, and every resource list uses the list grammar from `components/list-ui.tsx`. No page invents its own header height, gutter, section rhythm or toolbar.", "section": "layout" }, { - "name": "The One Frame Rule", - "body": "Charts and KPI figures sit in one hairline frame divided by internal rules (inset 1px lines), never in nested cards. A frame never contains another bordered, shadowed container.", + "name": "The One Card Rule", + "body": "Figures, charts and tables sit in one card divided by 1px internal rules. A card never contains another bordered, shadowed container; an empty list is itself one card.", "section": "elevation" } ], "dos": [ - "Do put every explanation of a figure, section or page behind a circled \"?\" help tip; keep errors, warnings and safety notices (deletion confirmation, uncertain writes, secrets) visible on the page.", - "Do reserve Parsar Indigo for selection, focus and primary actions; draw data from `--series-1..6` and `--series-other`, and meters from `--meter-fill`.", - "Do place charts and KPI figures in one hairline frame (8px) divided by 1px internal rules.", - "Do render missing data as \"—\", a chart gap, or \"Unavailable\".", - "Do show status as a 7px dot plus a plain label.", - "Do use tabular numerals for every aligned figure and right-align numeric columns.", - "Do use the one segmented control for time ranges, labelled \"1 小时 / 6 小时 / 24 小时 / 7 天\".", - "Do keep compact numbers at two decimals only when the integer part is one digit.", - "Do build every page from PageHeader, PageBody and Section with the 24px gutter and 28px section gap." + "**Do** put every explanation of a figure, column, section or page behind a circled \"?\" help tip; report errors in a dialog or a toast; keep warnings and safety notices (deletion consequences, a key shown once) visible.", + "**Do** start every project-scoped toolbar with the project filter, then search, with the count on the right.", + "**Do** end every resource table with the Creator column and then the row actions.", + "**Do** confirm every deletion in ConfirmDialog.", + "**Do** keep meters in neutral ink and let amber and red mean a threshold was crossed.", + "**Do** reserve OpenAgentCore Indigo for selection, focus, links and the single `--data` series.", + "**Do** place figures, charts and tables in one card divided by 1px internal rules.", + "**Do** render missing data as \"—\", a chart gap, \"Unavailable\" or \"Unknown\".", + "**Do** show status as a 6px dot plus a plain label.", + "**Do** use tabular numerals for every aligned figure and right-align numeric columns.", + "**Do** build every page from PageHeader, PageBody and Section." ], "donts": [ - "Don't add lines of small explanatory print under headings, KPIs or charts.", - "Don't colour data, bars, lines or meters with the indigo accent.", - "Don't nest cards inside frames or lift sections with shadows; shadows are for raised controls and floating layers only.", - "Don't render an unreported value as 0 or draw a missing interval as a zero line.", - "Don't use coloured status pills or colour-only status.", - "Don't reassign a categorical colour by rank when data re-sorts.", - "Don't add uppercase letter-spaced micro-labels or eyebrow lines above headings; a section is named by its title alone.", - "Don't mix synonyms in zh-CN copy (for example alternating 沙盒 with 沙箱)." + "**Don't** add lines of small explanatory print under headings, KPIs, fields or charts.", + "**Don't** colour healthy meters, bars or states; colour is for problems and data.", + "**Don't** colour multi-series data with the indigo accent.", + "**Don't** nest cards inside cards or draw a dashed empty state.", + "**Don't** render an unreported value as 0 or draw a missing interval as a zero line.", + "**Don't** use coloured status pills or colour-only status.", + "**Don't** reassign a categorical colour by rank when data re-sorts.", + "**Don't** show full IDs in list columns; show the compact ID with its copy button.", + "**Don't** add uppercase letter-spaced micro-labels or eyebrow lines above headings; a section is named by its title alone.", + "**Don't** mix synonyms in zh-CN copy (for example alternating 沙盒 with 沙箱, or API 密钥 with API key)." ] } -} \ No newline at end of file +} diff --git a/apps/web/.impeccable/surfaces/src-app-tsx.md b/apps/web/.impeccable/surfaces/src-app-tsx.md index 80cdd025a..2c5bee4f0 100644 --- a/apps/web/.impeccable/surfaces/src-app-tsx.md +++ b/apps/web/.impeccable/surfaces/src-app-tsx.md @@ -8,18 +8,16 @@ related_targets: ["src/ConsoleApp.tsx"] # Administrator console (operate) Scope: the signed-in console shell and every page behind it, including Getting started on the Overview and the optional console tour. Visitor mode: Operate. -Audience: the administrator of one OpenAgentCore deployment. Task: judge health, capacity, usage and failures; inspect and delete or copy project assets; manage projects, keys, nodes and each harness's default model. +Audience: the administrator of one OpenAgentCore deployment. Task: judge health, capacity, usage and failures; inspect and delete project assets; manage projects, keys, nodes and each harness's default model. Constraints: Web API only (`/core/v1`); missing data stays visibly missing; no small print, explanations live in help tips; API terms stay English in Chinese copy; zh-CN and English, light and dark. -Information architecture: Monitor (Overview, Agent metrics, Sandbox metrics, Session log) · Resources (Agents, Environment templates, Skills, Files, Vaults) · Platform (Projects and keys, Nodes, System). - -Unresolved: deployment configuration wizard waits for backend fields. +Information architecture: Monitor (Overview, Core metrics, Agent metrics, Sandbox metrics, Session log) · Resources (Agents, Environment templates, Skills, Files, Vaults) · Platform (Projects and keys, Nodes, System). ## Direction contract THESIS: One calm instrument panel for a whole deployment; every screen speaks one component language so the administrator reads state, not layout. Refuses the assembled dashboard of mismatched widgets and loading spinners. -OWN-WORLD: Beautiful UI's foundation: cool near-white canvas, white cards drawn by a hairline ring and smooth layered shadow, neutral ink ramp, pill buttons (ink primary), Inter with CJK system fallback, tabular numerals, semantic tints as condiment; Parsar indigo as the only accent, for selection, links and data. -STORY: The administrator lands on health, sees what needs attention, drills into a project, Session or node, and acts (delete, copy, issue, revoke) without waiting on a spinner. +OWN-WORLD: Beautiful UI's foundation: cool near-white canvas, white cards drawn by a hairline ring, neutral ink ramp, 8px-radius buttons (ink primary; only status badges are pills), Inter with CJK system fallback, tabular numerals, semantic tints as condiment; Parsar indigo as the only accent, for selection, links and data. +STORY: The administrator lands on health, sees what needs attention, drills into a project, Session or node, and acts (delete, issue, revoke) without waiting on a spinner. FIRST VIEWPORT: Page header with title, filters and refresh on one line; KPI strip; the page's primary card (chart grid, table or topology); nothing above the fold is a loader. FORM: User-pinned world (Beautiful UI + Parsar indigo, 2026-09-24); concept roll skipped because a user-pinned direction beats the roll. FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance diff --git a/apps/web/DESIGN.md b/apps/web/DESIGN.md index 0ba727c52..8efa23278 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -1,88 +1,88 @@ --- name: OpenAgentCore Console -description: The management console for one self-hosted OpenAgentCore deployment; projects, their assets and keys, health and capacity, on raised cards over a quiet canvas. +description: The management console for one self-hosted OpenAgentCore deployment; projects, their assets and keys, health and capacity, on hairline cards in a white page panel over a quiet canvas. colors: - ink: "#37352f" - ink-muted: "#787774" - ink-subtle: "#9b9a97" - sidebar-ink: "#5f5e5a" - surface: "#ffffff" - surface-subtle: "#fafafa" - surface-muted: "#f1f1f0" - canvas: "#f5f5f6" - card-border: "rgb(20 20 30 / 11%)" - line: "#e9e9ec" - line-muted: "#efeff1" - line-strong: "#d6d7dc" - hover: "rgb(55 53 47 / 3%)" - pressed: "rgb(55 53 47 / 6%)" - tile: "rgb(55 53 47 / 7%)" - accent: "#4f46e5" - accent-emphasis: "#4338ca" + ink: "oklch(0.247 0.006 258.361)" + ink-muted: "oklch(0.506 0.01 264.477)" + ink-subtle: "oklch(0.695 0.009 264.505)" + sidebar-ink: "oklch(0.506 0.01 264.477)" + surface: "oklch(1 0 0)" + surface-subtle: "oklch(0.979 0.002 247.839)" + surface-muted: "oklch(0.961 0.001 286.375)" + canvas: "oklch(0.961 0.002 247.84)" + frame-line: "color-mix(in oklch, oklch(0.247 0.006 258.361) 11%, transparent)" + line: "oklch(0.946 0.003 264.542)" + line-muted: "oklch(0.966 0.002 264.542)" + line-strong: "oklch(0.912 0.005 258.326)" + hover: "oklch(0.97 0.002 247.839)" + pressed: "oklch(0.933 0.003 247.86)" + tile: "oklch(0.933 0.003 247.86)" + accent: "oklch(0.52 0.165 277)" + accent-emphasis: "oklch(0.47 0.16 277)" accent-fg: "#ffffff" - data: "#4f46e5" - success: "#16a34a" - warning: "#d97706" - danger: "#dc2626" - status-queued: "#9a9ca4" - status-idle: "#b4b4b9" - series-1: "#2a78d6" - series-2: "#eb6834" - series-3: "#1baf7a" - series-4: "#eda100" - series-5: "#e87ba4" - series-6: "#008300" - series-other: "#a3a3a8" - meter-fill: "color-mix(in srgb, #37352f 62%, transparent)" - meter-track: "rgb(55 53 47 / 8%)" + data: "oklch(0.56 0.14 277)" + success: "oklch(0.6 0.12 158)" + warning: "oklch(0.68 0.135 62)" + danger: "oklch(0.585 0.17 25)" + status-queued: "oklch(0.695 0.009 264.505)" + series-1: "oklch(0.56 0.14 277)" + series-2: "oklch(0.7 0.09 195)" + series-3: "oklch(0.78 0.11 80)" + series-4: "oklch(0.66 0.12 20)" + series-5: "oklch(0.62 0.06 250)" + series-6: "oklch(0.72 0.08 145)" + series-other: "oklch(0.82 0.008 264)" + meter-fill: "color-mix(in srgb, oklch(0.247 0.006 258.361) 62%, transparent)" typography: metric: - fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" - fontSize: "30px" - fontWeight: 600 - lineHeight: "36px" - letterSpacing: "-0.025em" + fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" + fontSize: "20px" + fontWeight: 500 + lineHeight: "28px" + letterSpacing: "-0.015em" fontFeature: "\"tnum\"" display: - fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" - fontSize: "24px" + fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" + fontSize: "20px" fontWeight: 500 - lineHeight: "28px" - letterSpacing: "-0.01em" + lineHeight: "26px" + letterSpacing: "-0.015em" fontFeature: "\"tnum\"" headline: - fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" - fontSize: "20px" + fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" + fontSize: "17px" fontWeight: 600 - lineHeight: "26px" - letterSpacing: "-0.02em" + lineHeight: "24px" + letterSpacing: "-0.015em" title: - fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" - fontSize: "15px" + fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" + fontSize: "14px" fontWeight: 600 - lineHeight: "22px" + lineHeight: "20px" letterSpacing: "-0.01em" body: - fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" + fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" fontSize: "13px" fontWeight: 400 lineHeight: "18px" label: - fontFamily: "-apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" + fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" fontSize: "12.5px" fontWeight: 500 lineHeight: "18px" mono: - fontFamily: "ui-monospace, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", \"Noto Sans Mono\", monospace" + fontFamily: "\"Geist Mono Variable\", ui-monospace, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace" fontSize: "11.5px" fontWeight: 400 rounded: hairline: "4px" - control: "6px" - control-inner: "5px" - segment: "7px" - popover: "8px" + control: "8px" + control-inner: "6px" + segment: "8px" + tooltip: "8px" + popover: "14px" frame: "12px" + window: "14px" pill: "999px" spacing: xs: "4px" @@ -96,70 +96,69 @@ components: backgroundColor: "{colors.surface}" rounded: "{rounded.frame}" button-primary: - backgroundColor: "{colors.accent}" - textColor: "{colors.accent-fg}" + backgroundColor: "{colors.ink}" + textColor: "{colors.canvas}" rounded: "{rounded.control}" - padding: "0 10px" - height: "28px" - typography: "{typography.label}" - button-primary-hover: - backgroundColor: "{colors.accent-emphasis}" + padding: "0 13px" + height: "30px" button-outline: backgroundColor: "{colors.surface}" textColor: "{colors.ink}" rounded: "{rounded.control}" - padding: "0 10px" - height: "28px" + padding: "0 13px" + height: "30px" button-outline-hover: - backgroundColor: "{colors.hover}" + backgroundColor: "{colors.surface-subtle}" button-danger: backgroundColor: "{colors.danger}" textColor: "#ffffff" rounded: "{rounded.control}" - padding: "0 10px" - height: "28px" + padding: "0 13px" + height: "30px" button-ghost: textColor: "{colors.ink-muted}" rounded: "{rounded.control}" - padding: "0 10px" - height: "28px" + padding: "0 13px" + height: "30px" refresh-button: textColor: "{colors.ink-muted}" - rounded: "{rounded.segment}" - size: "32px" + rounded: "{rounded.control}" + size: "30px" back-button: textColor: "{colors.ink-muted}" rounded: "{rounded.control}" - size: "28px" + size: "30px" text-action: textColor: "{colors.ink-muted}" typography: "{typography.label}" + rounded: "{rounded.control-inner}" + height: "24px" input-field: - backgroundColor: "{colors.surface}" + backgroundColor: "{colors.surface-muted}" textColor: "{colors.ink}" rounded: "{rounded.control}" - padding: "4px 8px" - height: "28px" + padding: "0 10px" + height: "30px" search-field: backgroundColor: "{colors.surface}" textColor: "{colors.ink}" rounded: "{rounded.control}" - height: "28px" + height: "30px" select: - backgroundColor: "{colors.surface}" + backgroundColor: "{colors.surface-muted}" textColor: "{colors.ink}" rounded: "{rounded.control}" - padding: "0 28px 0 9px" - height: "28px" + padding: "0 30px 0 10px" + height: "30px" segmented-track: - backgroundColor: "{colors.surface-muted}" + backgroundColor: "{colors.pressed}" rounded: "{rounded.segment}" padding: "2px" segmented-option: textColor: "{colors.ink-muted}" rounded: "{rounded.control-inner}" - padding: "0 10px" - height: "24px" + padding: "0 11px" + height: "26px" segmented-option-active: backgroundColor: "{colors.surface}" textColor: "{colors.ink}" @@ -167,25 +166,25 @@ components: textColor: "{colors.sidebar-ink}" rounded: "{rounded.control}" padding: "0 8px" - height: "30px" + height: "32px" nav-item-active: - backgroundColor: "{colors.pressed}" + backgroundColor: "{colors.surface}" textColor: "{colors.ink}" metric-tile: backgroundColor: "{colors.surface}" textColor: "{colors.ink}" typography: "{typography.metric}" rounded: "{rounded.frame}" - padding: "16px 18px 18px" + padding: "16px" kpi-cell: textColor: "{colors.ink}" typography: "{typography.display}" padding: "14px 16px 16px" table-header: - backgroundColor: "{colors.surface-subtle}" + backgroundColor: "{colors.surface}" textColor: "{colors.ink-muted}" padding: "0 12px" - height: "34px" + height: "36px" table-row: textColor: "{colors.ink}" typography: "{typography.body}" @@ -193,9 +192,9 @@ components: height: "44px" empty-state: backgroundColor: "{colors.surface}" - textColor: "{colors.ink-muted}" + textColor: "{colors.ink-subtle}" rounded: "{rounded.frame}" - padding: "40px 24px" + padding: "36px 24px" help-tip: textColor: "{colors.ink-subtle}" rounded: "{rounded.pill}" @@ -203,11 +202,11 @@ components: modal: backgroundColor: "{colors.surface}" textColor: "{colors.ink}" - rounded: "{rounded.popover}" + rounded: "{rounded.window}" meter: - backgroundColor: "{colors.meter-track}" + backgroundColor: "{colors.surface-muted}" rounded: "{rounded.pill}" - height: "6px" + height: "5px" --- # Design System: OpenAgentCore Console @@ -216,693 +215,247 @@ components: **Creative North Star: "The Operator's Ledger"** -The console is a management tool, not a developer showroom. Every screen reads like -a ledger page laid on a desk: a quiet canvas, one header, then raised white cards -holding the evidence (figures, charts, tables). Structure comes from the card edge, -1px internal rules and whitespace; there are no cards inside cards. Colour is spent -on problems and on the data itself, almost never on decoration. The indigo accent -means "you selected this", "this is the primary action" or, as `--data`, "this is -the single measured quantity". - -Density is deliberately high and calm: 13px body, 44px table rows, 28px controls, -tabular figures in every column. The system is bilingual (zh-CN and English) and -ships light and dark themes on the same token names; dark swaps values, never -structure. It honours reduced motion and treats keyboard focus as a first-class -state (2px indigo outline). - -The data contract is part of the look. Core reports only what it observes, so the -interface shows absence honestly: an em dash, a gap in a line, the word -"Unavailable" or "Unknown". Explanations stay one click away behind a circled -question mark, so the page stays a ledger rather than a leaflet. +The console is a management tool, not a developer showroom. Every screen reads like a ledger page laid on a desk: a quiet canvas, one white page panel, one header, then white cards holding the evidence (figures, charts, tables). Structure comes from the card edge, 1px internal rules and whitespace; there are no cards inside cards. Colour is spent on problems and on the data itself, almost never on decoration. The indigo accent means "you selected this" or "this is a link"; its data shade (`--data`) means "this is the single measured quantity". Primary actions are filled with ink. + +Density is deliberately high and calm: 13px body, 44px table rows, 30px controls, tabular figures in every column. The system is bilingual (zh-CN and English) and ships light and dark themes on the same token names; dark swaps values, never structure. It honours reduced motion and treats keyboard focus as a first-class state (2px indigo outline). + +The data contract is part of the look. Core reports only what it observes, so the interface shows absence honestly: an em dash, a gap in a line, the word "Unavailable" or "Unknown". Explanations stay one click away behind a circled question mark, so the page stays a ledger rather than a leaflet. **Key Characteristics:** -- White cards with a faint border and shadow on a light-gray canvas, beside a white - sidebar; the page header is solid canvas. -- One indigo voice for selection, primary actions and single-series data (`--data`). -- Meters are neutral ink; green, amber and red appear only when something is wrong - or a state needs reporting. -- A six-slot categorical palette for multi-series data, bound to the entity, not its - rank. -- One list grammar on every resource page: project filter, search, count, name with - compact ID, creator, row actions. +- Each page sits in one white panel with 14px corners on a cool gray canvas, which also holds the sidebar. Inside the panel, cards are drawn by a hairline ring, and the page header is white with a hairline rule under it. +- One indigo voice for selection, focus, links and single-series data (`--data`); the primary button is ink. +- Meters are neutral ink; green, amber and red appear only when something is wrong or a state needs reporting. +- A six-slot categorical palette for multi-series data, bound to the entity, not its rank. +- One list grammar on every resource page: project filter, search, count, name with compact ID, creator, row actions. - Tabular numerals everywhere a number can line up. - Status is always a dot plus a plain-language label. -- Explanations live behind "?" help tips; errors, warnings and safety notices stay - visible. No small print: an empty state's explanation is a help tip beside its - title, a field's rules a help tip beside its label, and filler lines are cut. +- Explanations live behind "?" help tips; errors, warnings and safety notices stay visible. No small print: an empty state's explanation is a help tip beside its title, a field's rules a help tip beside its label, and filler lines are cut. ## Colors -A restrained neutral ledger with one indigo voice, three signal colours and a -separate categorical palette that belongs to multi-series data alone. +A restrained neutral ledger with one indigo voice, three signal colours and a separate categorical palette that belongs to multi-series data alone. ### Primary -- **OpenAgentCore Indigo** (accent): keyboard focus outlines and rings, primary buttons, - hover on name links and text actions, text selection wash. Deepens to **Pressed - Indigo** (accent-emphasis) on primary hover. It is the brand colour shared with - the public OpenAgentCore landing. -- **Data** (`--data`, an alias of accent): the one measured series of a chart that - has only one, such as Sessions created per hour on Overview, drawn as a tint - (62% into the surface) rather than full strength. +- **OpenAgentCore Indigo** (accent): keyboard focus outlines and rings, the focus ring of fields, the text caret and the text selection wash. Deepens to **Pressed Indigo** (accent-emphasis) for hovered name links. It is the console's only accent; the public landing (`site/`) uses its own violet, `#5a43c7`. +- **Data** (`--data`, the same colour as Series 1, a lighter indigo): the one measured series of a chart that has only one, such as Sessions created per hour on Overview, drawn as a tint (62% into the surface) rather than full strength. ### Neutral - **Ledger Ink** (ink): primary text, figures, table cells, headings. -- **Graphite** (ink-muted): secondary text, column headers, KPI labels, axis ticks, - inactive controls, row actions at rest. +- **Graphite** (ink-muted): secondary text, column headers, KPI labels, axis ticks, inactive controls, row actions at rest. - **Pencil** (ink-subtle): help-tip glyphs, crosshairs, untoned dots. - **Sidebar Ink** (sidebar-ink): navigation text. -- **Canvas** (canvas): the ground of the main column (`#121212` in dark). -- **Paper** (surface): cards, tables, KPI strips, chart grids, empty states, - dialogs, popovers, the sidebar. -- **Card Edge** (card-border): the 1px border of raised cards. -- **Margin Gray** (surface-subtle): table header band, coverage notes. -- **Well Gray** (surface-muted): segmented-control track, secondary buttons. -- **Hairline** (line): internal dividers of cards, chart gridlines, dialog rules. +- **Canvas** (canvas): the app frame around the page panel, and the sidebar's ground (`oklch(0.231 0.004 264.487)` in dark). +- **Paper** (surface): the page panel, the page header, cards, tables, KPI strips, chart grids, empty states, dialogs and the active navigation chip. +- **Card Edge** (frame-line): the 1px ring of cards, ink at 11%. +- **Margin Gray** (surface-subtle): coverage notes, dialog footers, empty-state icon tiles, and the hover of outline buttons. +- **Well Gray** (surface-muted): the fill of inputs and selects inside cards and dialogs, meter rails, count pills and value pills. +- **Hairline** (line): internal dividers of cards, the page header rule, chart gridlines, dialog rules and the ring of inputs. - **Faint Rule** (line-muted): row dividers inside tables. -- **Firm Rule** (line-strong): control borders (inputs, selects, search, outline - buttons) and the pending-key notice. -- **Hover / Pressed / Tile washes**: translucent ink at 3% / 6% / 7% for hover, the - active navigation item, and count pills. +- **Firm Rule** (line-strong): the ring of outline buttons, and of search fields and selects in toolbars and headers. +- **Hover / Pressed / Tile**: opaque cool grays. Hover is the wash of table rows, ghost buttons and text actions; Pressed is the wash of navigation items and icon buttons and the segmented-control track. ### Signal -- **Healthy Green** (success), **Caution Amber** (warning), **Fault Red** (danger): - status dots, KPI and tile tone dots, meter fills past their thresholds, error text, - error notices, destructive buttons and the hover of destructive row actions. -- **Queued Gray** (status-queued) is the pending KPI tone; **Idle Gray** - (status-idle) marks idle and neutral status dots. A running or pending status dot - uses Series 1. +- **Healthy Green** (success), **Caution Amber** (warning), **Fault Red** (danger): status dots, KPI and tile tone dots, meter fills past their thresholds, error text, error notices, destructive buttons and the hover of destructive row actions. +- **Queued Gray** (status-queued, the same value as Pencil) is the pending KPI tone; idle and neutral status dots use Pencil. A running or pending status dot uses Series 1. ### Data (categorical) -- **Series 1–6** (Cobalt, Persimmon, Jade, Saffron, Rose, Forest) and **Series - Other**: lines, stacked bars and legend keys of multi-series charts (requests by - model, calls by tool, average against P95 duration, Runtime trends). Dark theme - re-tunes each slot under the same name. +- **Series 1–6** (Indigo, Teal, Ochre, Coral, Slate, Sage) and **Series Other**: lines, stacked bars and legend keys of multi-series charts (requests by model, calls by tool, average against P95 duration, Runtime trends). Dark theme re-tunes each slot under the same name. - **Meter Fill** (meter-fill): ink at 62%, the healthy fill of every meter. -- **Meter Track** (meter-track): the empty rail under meters. +- Meter rails are Well Gray with an inset hairline. ### Named Rules -**The Colour Only for Problems Rule.** A healthy state is drawn in ink. Meters fill -in neutral ink and turn amber or red only past their thresholds; tone dots appear -only on figures that report a state. +**The Colour Only for Problems Rule.** A healthy state is drawn in ink. Meters fill in neutral ink and turn amber or red only past their thresholds; tone dots appear only on figures that report a state. -**The One Voice Rule.** Indigo is for selection, focus, primary actions and the -single `--data` series. Multi-series charts draw from `--series-1..6` and -`--series-other`, never from the accent. +**The One Voice Rule.** Indigo is for selection, focus, links and the single `--data` series. Multi-series charts draw from `--series-1..6` and `--series-other`, never from the accent. -**The Entity Owns Its Colour Rule.** A categorical colour follows the entity (model, -tool), never its rank. An entity keeps its slot while visible; only slots of -entities that left the view are reused. Anything beyond six series collapses into -Series Other. +**The Entity Owns Its Colour Rule.** A categorical colour follows the entity (model, tool), never its rank. An entity keeps its slot while visible; only slots of entities that left the view are reused. Anything beyond six series collapses into Series Other. ## Typography -**Display Font:** system UI sans (-apple-system, Segoe UI, with PingFang SC / -Microsoft YaHei / Noto Sans SC for Chinese) -**Body Font:** the same system stack -**Label/Mono Font:** ui-monospace / SF Mono / Menlo for identifiers and code +- **Display Font:** Inter Variable (with the system UI sans as fallback: -apple-system, Segoe UI, and PingFang SC / Microsoft YaHei / Noto Sans SC for Chinese) +- **Body Font:** Inter Variable, the same stack, with Inter's `cv11` and `ss01` alternates +- **Label/Mono Font:** Geist Mono Variable (with ui-monospace / SF Mono / Menlo) for identifiers and code -**Character:** One quiet system sans in several weights, sized for dense reading; -hierarchy comes from weight and a tight scale, not from a second typeface. Mono -appears only for machine identifiers, key prefixes, models and commands. +**Character:** One quiet sans in several weights, sized for dense reading; hierarchy comes from weight and a tight scale, not from a second typeface. Mono appears only for machine identifiers, key prefixes, models and commands. ### Hierarchy -- **Metric** (600, 30px, 36px, -0.025em, tabular): the four Overview tiles; the - largest type in the console. -- **Display** (500, 24px, 28px, -0.01em, tabular): KPI strip figures. -- **Headline** (600, 20px, 26px, -0.02em): the page title in the 64px page header; - one per page. Detail pages put the back button before it. -- **Title** (600, 15px, 22px, -0.01em): section headings. Card headings and - empty-state titles step down to 600 at 14px. -- **Body** (400, 13px, 18px): table cells, controls, form fields, dialog text. The - document base is 14px/20px; help popovers run 12.5px/19px. -- **Label** (500, 12.5px, 18px): KPI labels (400), status labels, text actions, - segmented options; column headers 500 at 12px; fact labels 12px Graphite; axis - ticks 11px; the list count 12px. -- **Mono** (400, 11.5px): IDs and code in tables and name cells, in Graphite. +- **Metric** (500, 20px, 28px, -0.015em, tabular): the four Overview tiles. +- **Display** (500, 20px, 26px, -0.015em, tabular): KPI strip figures. Figures stand out by weight and position, one step above body text. +- **Headline** (600, 17px, 24px, -0.015em): the page title in the page header; one per page. Detail pages put the back button before it. +- **Title** (600, 14px, 20px, -0.01em): section and card headings. Dialog titles are 600 at 15px; empty-state titles 500 at 13.5px. +- **Body** (400, 13px, 18px): table cells, controls, form fields, dialog text. The document base is 14px/20px; help tips run 12px/18px. +- **Label** (500, 12.5px, 18px): KPI labels, column headers, text actions, segmented options and the list count; fact labels 12px/500 Pencil; axis ticks 11px. Status labels are 13px. +- **Mono** (400, 11.5px): IDs in name cells in Graphite; other code in tables in Pencil. ### Named Rules -**The Columns Line Up Rule.** Every figure that can share a column uses tabular -numerals: KPI and tile values, numeric table cells (right-aligned), legend totals, -axis ticks, tooltip values, counts. +**The Columns Line Up Rule.** Every figure that can share a column uses tabular numerals: KPI and tile values, numeric table cells (right-aligned), legend totals, axis ticks, tooltip values, counts. -**The Honest Figure Rule.** Missing data renders as "—", a gap in the line, -"Unavailable" or "Unknown"; never as 0. Compact numbers keep two decimals only when -the integer part is a single digit ("1.04M"), otherwise one ("415.7万"); values under -10,000 print whole. Durations read "850 ms / 12.4 s / 4m 12s / 3h 5m". +**The Honest Figure Rule.** Missing data renders as "—", a gap in the line, "Unavailable" or "Unknown"; never as 0. Compact numbers keep two decimals only when the integer part is a single digit ("1.04M"), otherwise one ("415.7万"); values under 10,000 print whole. Durations read "850 ms / 12.4 s / 4m 12s / 3h 5m". -**The Plain Vocabulary Rule.** zh-CN copy uses one term per concept: 项目 -(project), 沙箱 (sandbox), 运行时 (runtime), 创建者 (creator), 已上报 (reported), -活跃 (active), 提供方 (provider). API terms stay in English (Agent, Session, Turn, -Skill, Vault, Credential, API key). Time ranges read "1 小时 / 6 小时 / 24 小时 / -7 天" (English "1h / 6h / 24h / 7d"), always in the one segmented control style. +**The Plain Vocabulary Rule.** zh-CN copy uses one term per concept: 项目 (project), 沙箱 (sandbox), 运行时 (runtime), 创建者 (creator), 已上报 (reported), 活跃 (active), 提供方 (provider). API terms stay in English (Agent, Session, Turn, Skill, Vault, Credential, API key). Time ranges read "1 小时 / 6 小时 / 24 小时 / 7 天" (English "1h / 6h / 24h / 7d"), always in the one segmented control style. ## Layout -A fixed 232px white sidebar beside a full-height main column on the canvas; below -640px the sidebar collapses to a 52px icon rail. The desktop minimum is 960px. -Every page uses the same frame: a 64px header (title, optional help tip, actions -on the right) in solid canvas, then a scrolling body -padded `20px 28px 48px` with sections stacked 28px apart. Inside a section the -heading row sits 12px above its content. - -The recurring shapes in the body are the KPI strip (auto-fit columns, min 158px; -three per row below 1180px), chart grids (two equal columns, single below 1180px), -full-width table cards, and a fact row on detail pages. Overview has its own -arrangement: Getting started while a step is to do, four metric tiles, Session -activity beside the fleet topology (Core in the middle, nodes left and right, -solid lines online and dashed offline; Core and each node open an anchored popover with a two-column glance and links to -their pages), then the attention table and usage by project, each on its own -card with a 16px gap. Popovers are the overlay card (14px radius, overlay shadow, -16px padding): a 14px title, 12px labels over 13px values, links at a ruled foot. Nodes itself is a plain list with a detail page. - -Spacing follows a 4px base: 4, 8, 12, 16, 28 (page gutter and section gap). -Controls are 28px tall, segmented options 24px, table rows 44px (32px compact), -table headers 34px. - -**The One Page Grammar Rule.** Every page uses PageHeader, PageBody and Section -from `components/console-ui.tsx`, and every resource list uses the list grammar -from `components/list-ui.tsx`. No page invents its own header height, gutter, -section rhythm or toolbar. +A fixed 232px sidebar on the canvas beside the main column, where each page sits in one white panel with 14px corners; below 640px the sidebar collapses to a 52px icon rail. The desktop minimum is 960px. Every page uses the same frame: a header at least 56px tall (title, optional help tip, actions on the right) on white with a Hairline rule under it, then a scrolling body padded `20px 28px 48px` with sections stacked 28px apart. Inside a section the heading row sits 10px above its content. + +The recurring shapes in the body are the KPI strip (auto-fit columns, min 158px; three per row below 1180px), chart grids (two equal columns, single below 1180px), full-width table cards, and a fact row on detail pages. Overview has its own arrangement: Getting started while a step is to do, four metric tiles, Session activity beside the fleet topology (Core in the middle, nodes left and right, solid lines online and dashed offline; Core and each node open an anchored popover with a two-column glance and links to their pages), then the attention table and usage by project, each on its own card with a 16px gap. Popovers are the overlay card (14px radius, overlay shadow, 16px padding): a 14px title, 12px labels over 13px values, links at a ruled foot. Nodes itself is a plain list with a detail page. + +Spacing follows a 4px base: 4, 8, 12, 16, 28 (page gutter and section gap). Controls are 30px tall, segmented options 26px, table rows 44px (32px compact), table headers 36px. + +**The Liveness Only Rule.** The moving dash on a topology link shows that a connection is live, never traffic or work. An offline link is dashed and still, a stale topology shows no movement, and reduced motion stops the animation. + +**The One Page Grammar Rule.** Every page uses PageHeader, PageBody and Section from `components/console-ui.tsx`, and every resource list uses the list grammar from `components/list-ui.tsx`. No page invents its own header height, gutter, section rhythm or toolbar. ## Elevation & Depth -Depth comes from the canvas-to-card step, not from stacked shadows. +Depth comes from the canvas-to-panel step, not from stacked shadows. ### Shadow Vocabulary -- **Card** (no shadow; a 1px `card-border` edge at 11% ink): KPI strips, table - frames, chart grids, Overview cards, the Session transcript and the deployment - panel. Cards are flat; no page surface is translucent or blurred. -- **Control lift** (`0 1px 2px rgb(0 0 0 / 6%)`): primary and outline buttons, - inputs, selects, the search field, the active segment. -- **Floating** (`0 1px 2px rgb(24 24 27 / 4%), 0 8px 24px -12px rgb(24 24 27 / - 18%)`): help-tip popovers, chart tooltips, menus and dialogs. +- **Page panel** (a Hairline ring with a soft shadow, `0 1px 2px` at 3% and `0 8px 24px -12px` at 8% black): the white panel that holds each page. +- **Card** (no shadow; a 1px `frame-line` ring at 11% ink): KPI strips, table frames, chart grids, Overview cards, the Session transcript and the deployment panel. Cards are flat; no page surface is translucent or blurred. +- **Control ring** (a 1px Firm Rule ring with an extra-small shadow): outline buttons, the active segment, and search fields and selects in toolbars. Inputs inside cards and dialogs carry only a Hairline ring. +- **Overlay** (a 1px Hairline ring with a large soft shadow): anchored popovers, menus, dialogs, help tips and chart tooltips. ### Named Rules -**The One Card Rule.** Figures, charts and tables sit in one card divided by 1px -internal rules. A card never contains another bordered, shadowed container; an -empty list is itself one card. +**The One Card Rule.** Figures, charts and tables sit in one card divided by 1px internal rules. A card never contains another bordered, shadowed container; an empty list is itself one card. ## Shapes -12px corners on cards, tables, KPI strips, chart grids, empty states, coverage -notes and the pending-key notice; 8px on dialogs and help popovers; 7px on the -segmented track, the refresh button and chart tooltips; 6px on buttons, inputs, -selects and the search field; 5px on inner segments; 4px on small inline marks and -flags; full pills for meters and count badges; circles for status dots (7px), KPI -tone dots (8px) and tile dots (10px). Legend keys are 9px squares with 2px corners, -or 12×2px strokes for line series. Borders are always 1px. +14px corners on the page panel, dialogs and anchored popovers; 12px on cards, tables, KPI strips, chart grids and empty states; 8px on buttons, icon buttons, inputs, selects, the search field, the segmented track, coverage notes, help tips and chart tooltips; 6px on segment options and text actions; 4px on small inline marks and segment counts; full pills for meters, count badges and value pills; circles for status dots (6px), KPI tone dots (8px) and tile dots (10px). Legend keys are 9px squares with 2px corners, or 12×2px strokes for line series. Borders are always 1px. ## Components ### Buttons -Compact and quiet; the primary button is the only filled accent in a header. -- **Shape:** 6px corners, 28px tall, 0 10px padding, 13px/500 label, optional 14px - Lucide icon. -- **Primary:** OpenAgentCore Indigo fill, white text, control lift; deepens on hover. Used - for the one affirmative header action (Create project) and for the submit button - of non-destructive dialogs (create, rename, issue, continue). -- **Outline:** Paper face, Firm Rule border, control lift; hover takes the ink wash. - Used for every action in a card or section header (Issue key, Manage nodes, - Session log, Projects and keys), Download on the Skill page, Cancel in dialogs - and empty-state actions. -- **Danger:** Fault Red fill, white text. Used for Delete on detail pages and for - the confirm button of every destructive dialog. -- **Ghost:** transparent with Graphite text; darkens on hover. -- **Focus / Press:** focus draws an indigo border plus 1px indigo ring; press scales - to 0.97. -- **Text action:** borderless Graphite 12.5px/500 that takes the hover wash; used - only for per-row actions in tables (Rename, Archive, Delete) and links in a - popover's foot, never in a header. A destructive text action turns red on hover. +Compact and quiet; the primary button is the only filled button in a header. +- **Shape:** 8px corners, 30px tall, 0 13px padding, 13px/500 label, optional 14px Lucide icon. No button is a pill. +- **Primary:** Ledger Ink fill with Canvas text and a faint inner highlight; hover lowers it to 88% opacity. Used for the one affirmative header action (Create project) and for the submit button of non-destructive dialogs (create, rename, issue, continue). +- **Outline:** Paper face with the control ring; hover takes Margin Gray. Used for every action in a card or section header (Issue key, Manage nodes, Session log, Projects and keys), Download on the Skill page, Cancel in dialogs and empty-state actions. +- **Danger:** Fault Red fill, white text: the confirm button of every destructive dialog. On a page, a destructive button such as Delete on a detail page is red text on an outline button that takes a red tint on hover. +- **Ghost:** transparent with Graphite text; hover takes Ink on the Hover wash. +- **Focus / Press:** focus draws a 2px indigo outline 2px outside the button; press scales to 0.96. +- **Text action:** borderless Graphite 12.5px/500, 24px tall with 6px corners, that turns Ink on the Hover wash; used only for per-row actions in tables (Rename, Archive, Delete) and links in a popover's foot, never in a header. A destructive text action turns red on hover. ### Refresh button -A 32px ghost icon button with the refresh glyph. Controls that scope the whole page -(project filter, time range) come before it; on detail pages it leads, followed by -any outline actions and Delete. It spins while reading; its tooltip carries the last update time -instead of a visible timestamp. +A 30px ghost icon button with the refresh glyph. Controls that scope the whole page (project filter, time range) come before it; on detail pages it leads, followed by any outline actions and Delete. It spins while reading; its tooltip carries the last update time instead of a visible timestamp. ### Segmented control -The single style for ranges, order and status filters. A Well Gray track (2px -padding, 8px corners) holds 26px options in Graphite; the chosen option sits on a -Paper thumb with control lift and Ledger Ink text, and the thumb glides to a new -choice (Motion shared layout). Options may carry a tabular count. It is a -radiogroup with arrow-key movement. +The single style for ranges, order and status filters. A Pressed-gray track (2px padding, 8px corners) holds 26px options in Graphite; the chosen option sits on a Paper thumb with the control ring and Ledger Ink text, and the thumb glides to a new choice (Motion shared layout). Options may carry a tabular count. It is a radiogroup with arrow-key movement. ### Selects and the project filter -Selects are 28px Paper fields with a Firm Rule border, control lift and a drawn -chevron; focus swaps the border to indigo with a 1px ring. The project filter is a -select whose first option is **All projects**; archived projects are listed with -"· archived". +Selects and inputs are 30px fields filled Well Gray with a Hairline ring inside cards and dialogs, and Paper with the control ring in toolbars and headers. Focus turns them Paper with a 1px indigo ring and a 4px indigo tint around it. Selects draw their own chevron. The project filter is a select whose first option is **All projects**; archived projects are listed with "· archived". ### List grammar Every resource list, the Session log and the project list share one grammar: -- **ListToolbar**: on project-scoped lists the project filter first, then the - SearchField (280px, search icon, Paper, Firm Rule border), then any further - filters (segmented status or order, selects); the count sits on the right in 12px - Graphite ("12 total", "3 of 12", "40 loaded" when more exist). -- **Project column**: shown only while All projects is selected, right after the - name; archived projects are muted. -- **NameCell**: the first column. The name at 500 weight (a link that turns indigo - on hover when the row opens a detail page; a muted fallback such as "Untitled" - when the resource has no name) with the compact ID underneath in 11.5px mono. The - ID's copy button appears on row hover or focus; the full ID lives in its tooltip. -- **Creator column**: the last column before the actions, headed "Creator" with a - help tip. It shows the creating key's name (its prefix when unnamed) with a small - "Revoked" flag for revoked keys, "Admin copy" in Graphite for an asset an - administrator copied in an earlier release, "Unknown" in Graphite when Core has no - record, and "—" while loading or when the lookup failed. -- **RowActions**: text actions right-aligned at the end of the row, 16px apart, - ending with Delete (red on hover). A row click opens the detail page; action - clicks do not. -- **Partial failure**: when some projects fail to load, one red line names them - above the table; the other projects still show. -- **Empty state**: a solid card (Paper, 1px Hairline, 12px, 40px 24px padding) with - an optional 20px outline icon, a 14px/600 title, an optional one-line description - and an optional action. "No matches" offers Clear search. +- **ListToolbar**: on project-scoped lists the project filter first, then the SearchField (280px, search icon, Paper with the control ring), then any further filters (segmented status or order, selects); the count sits on the right in 12.5px Pencil ("12 total", "3 of 12", "40 loaded" when more exist). +- **Project column**: shown only while All projects is selected, right after the name; archived projects are muted. +- **NameCell**: the first column. The name at 500 weight (a link that turns indigo on hover when the row opens a detail page; a muted fallback such as "Untitled" when the resource has no name) with the compact ID underneath in 11.5px mono. The ID's copy button appears on row hover or focus; the full ID lives in its tooltip. +- **Creator column**: the last column before the actions, headed "Creator" with a help tip. It shows the creating key's name (its prefix when unnamed) with a small "Revoked" flag for revoked keys, "Admin copy" in Graphite for an asset Core records as an administrator copy, "Unknown" in Graphite when Core has no record, and "—" while loading or when the lookup failed. +- **RowActions**: text actions right-aligned at the end of the row, 16px apart, ending with Delete (red on hover). A row click opens the detail page; action clicks do not. +- **Partial failure**: when some projects fail to load, one red line names them above the table; the other projects still show. +- **Empty state**: a solid card (Paper, card ring, 12px, 36px 24px padding) with an optional outline icon in a 32px Margin Gray tile, a 13.5px/500 title, an optional one-line description and an optional action. "No matches" offers Clear search. - **Load more**: an outline button centred under its table when more rows exist. ### Detail pages -- The page header starts with a **back button** (28px ghost icon button, arrow-left, - Graphite) before the title; the actions on the right start with Refresh, continue - with outline actions such as Download, and end with Delete (danger). -- Under the header, **resource-facts** lays out the facts as a grid of up to four - label/value pairs per row (12px Graphite label over a 13px value, 14px by 40px - gaps, two columns below 900px). It starts with the ID (with its copy button) and - the Project and includes the Creator. +- The page header starts with a **back button** (30px ghost icon button, arrow-left, Graphite) before the title; the actions on the right start with Refresh, continue with outline actions such as Download, and end with Delete (red text on an outline button). +- Under the header, **resource-facts** lays out the facts in one card as an auto-fill grid of label/value pairs, each column at least 176px wide (a 12px/500 Pencil label over a 13px value, 14px by 24px gaps). It starts with the ID (with its copy button) and the Project and includes the Creator. - Sections follow: usage figures in a KPI strip, then tables in cards. -- A Session's **History** header holds an outline "Jump to the failed Turn" (with - the count when several failed) before the view switch while any Turn failed; - it shows the conversation (the Turn table when there are no Items), scrolls the - page body to the next failed Turn and focuses it. -- An active project's page ends its keys with a **How to call** section (see - Dialogs) before its write operations. -- A self-hosted Session's **Executor credentials** section ends with **Connect - a host**, independent of console installer assets. Keep native distribution - guidance, a Linux/macOS or PowerShell selector and one copyable command here. - The command pre-fills the Session remote URL, Environment ID and workspace; - interactive installation asks for a privately saved credential file and local - installation choices. Link the native guide instead of inventing a release - download URL. Requirements and reconnection details belong in the title help. - Reconnection after credential rotation requires `stop`, replacement of the - configured credential file, then `start`; disconnection does not imply process exit. - Installation does not start the daemon; connection status comes only from Core. - Missing connection facts show a note instead of a command. Loopback ws is valid - for native local connections. Archived projects require an existing credential. +- A Session's **History** header holds an outline "Jump to the failed Turn" (with the count when several failed) before the view switch while any Turn failed; it shows the conversation (the Turn table when there are no Items), scrolls the page body to the next failed Turn and focuses it. +- An active project's page ends its keys with a **How to call** section (see Dialogs) before its write operations. +- A self-hosted Session's **Executor credentials** section ends with **Connect a host**: a Linux/macOS or PowerShell selector, the one copyable command Core generated for that platform, and a link to the native installation guide. The console shows Core's command as it is and never builds one. The command installs the daemon and its Harnesses, starts it and checks its connection; its authorization expires after 30 minutes. Requirements and reconnection details belong in the title help. Reconnection after credential rotation requires `stop`, replacement of the configured credential file, then `start`; disconnection does not imply process exit. Connection status comes only from Core. When Core has no command, a note replaces it; an archived project shows a note instead. ### Dialogs -Dialogs are 448px Paper cards with 8px corners, a 48px header and a 52px footer -separated by Hairlines, and the floating shadow. They cannot be closed while a -request runs. -- **ConfirmDialog**: the one grammar for destructive actions. The body states what - will be deleted and its consequences; the footer holds Cancel (outline) and the - confirm button (danger), whose label changes while busy. Core's reason for a - rejection, or an uncertain-outcome warning, appears in red inside the dialog. - The Skill page's delete dialogs follow the same grammar; deleting a whole Skill - also requires typing its name. Archiving a project says in bold that it can't be - undone, then how many active keys it revokes (the project read's count, or more - when its loaded key list shows more) and that assets and accepted work stay; - with active keys it too requires typing the project's name, shown in mono with - its inner spaces kept (surrounding spaces are forgiven, Unicode compared in NFC). - While the project list is read again Archive waits; if that read failed, a red - line says the count may be out of date and Archive stays disabled. -- **Key dialogs**: name fields carry their rules in a help tip and their problem in - red underneath. The issued key appears in a read-only field with a copy button, - under a notice that it is shown once; only "I've saved this key" dismisses it. - Closing the dialog moves the key into a pending notice card on the page. -- **Executor credential dialog** (640px): the shown-once notice, then a prompt - to save the JSON privately before Done. Download credential file is primary; - Copy credential is secondary. Installation commands are not repeated here. - Done forgets the credential; closing preserves it in the pending card. The - native installer reads the unchanged JSON file through its interactive prompt - or `--credential-file`; tokens never enter command arguments. -- **Add node**: the sandbox limits first, then the one-time command in a Terminal - block (expiry countdown and Copy command in its header), the three progress - steps, and, once the installer's minute passes, an amber card with the reason - and a copyable system-service log command. Below, the Host requirements - Hairline disclosure is open until this browser has shown it once. Installation - requires root or sudo, creates the `oac-node` system service, and serves one Core - per host because nodes share the service account. For Docker, explain that - membership in the docker group is root-equivalent. Do not expose an ordinary-user - installation command or user-service prerequisites. The command downloads from the - installation's public URL, never the browser's address, so it works as shown on - any host. Until the installation is read, a line says it is being checked; a - failed read, a public URL other machines can't use (loopback or not HTTPS), or a - console without the provider's node files replaces the limits with one line - saying why (the failed read with Try again), and the footer offers nothing to - generate. Once the node is ready, while Getting started is open, one line under - the green status names the next step (set a default model provider, or finish Getting - started) with a text action to System or the Overview. -- **Clean up the host**: after a node is removed, a dialog gives the host's - uninstall command in the same Terminal block, a Graphite line that it deletes no - sandboxes, volumes or images (and, for microsandbox, keeps its image store and - data). The command requires root or sudo; there is no user-service alternative. A - node enrolled with an earlier Core address adds an "Old Core address gone?" - disclosure with the `--force` form. The command, too, downloads from the public - URL, which the dialog reads again if it is not at hand: until then one line says - it is being checked, a failed read says so with Try again, and a public URL other - machines can't use (loopback, or none) gets a line saying the service stays on the - host and no command can be given. Done dismisses it and focus returns to the page - heading. -- **Use Docker instead of microsandbox?**: choosing Docker in sandbox setup lists - what it gives up, each point a 600 Ink lead over a Graphite line: weaker - isolation (containers share the host kernel; microsandbox gives each sandbox - its own microVM), root-equivalent access (the node's account joins the docker - group) and limited use (trusted workloads, or hosts without KVM). The footer - holds Use Docker (outline) and Keep microsandbox (primary), which takes focus; - closing or Escape keeps microsandbox too. -- **Edit node**: the name, then the sandbox limit with one 12px Graphite line under - it once the node's heartbeat has the host's CPUs and memory: the host, each - sandbox's size and at most how many fit. The Nodes list and a node's Capacity - show "Active / limit" for Docker and microsandbox alike, so a saved limit shows - where it was set. -- **How to call**: wherever a new key is shown, a card under it gives three - copyable samples, each a Margin Gray block with a Hairline and its label and copy - button in a header row: a Shell block exporting `OPENAI_BASE_URL` (the - installation's API base URL) and `OPENAI_API_KEY` (the new key) together, then - curl and Python (with the pinned SDK), each listing the project's Agents and - creating a Session with a first message (`environment`, an inline `agent` with - `model: ""`, and `input`). A copy the clipboard refuses - selects the sample and says so in red underneath. One Graphite line says to put - a model the model provider serves in place of ``, and that running an - Agent needs a model provider: in each request, saved on the Agent, or the - deployment default. An active project's page shows the same samples as a - section without any key: the Shell block exports a quoted placeholder, and a - Graphite line above the samples says to use a key issued for this project, - shown only once at issuance. When the public address is loopback, a note above the - samples says the API is reachable only on the Core machine; without a public - address only a note to set one shows. Before the installation is read, a - skeleton holds the first sample's place. +Dialogs are 448px Paper cards (960px when wide) with 14px corners, a 52px header and a 56px Margin Gray footer separated by Hairlines, and the overlay shadow. They cannot be closed while a request runs. +- **ConfirmDialog**: the one grammar for destructive actions. The body states what will be deleted and its consequences; the footer holds Cancel (outline) and the confirm button (danger), whose label changes while busy. Core's reason for a rejection, or an uncertain-outcome warning, appears in red inside the dialog. The Skill page's delete dialogs follow the same grammar; deleting a whole Skill also requires typing its name. Archiving a project says in bold that it can't be undone, then how many active keys it revokes (the project read's count, or more when its loaded key list shows more) and that assets and accepted work stay; with active keys it too requires typing the project's name, shown in mono with its inner spaces kept (surrounding spaces are forgiven, Unicode compared in NFC). While the project list is read again Archive waits; if that read failed, a red line says the count may be out of date and Archive stays disabled. +- **Key dialogs**: name fields carry their rules in a help tip and their problem in red underneath. The issued key appears in a read-only field with a copy button, under a notice that it is shown once; only "I've saved this key" dismisses it. Closing the dialog moves the key into a pending notice card on the page. +- **Executor credential dialog** (640px): the shown-once notice, then a prompt to save the JSON privately before Done. Download credential file is primary; Copy credential is secondary. Installation commands are not repeated here. Done forgets the credential; closing preserves it in the pending card. The native installer reads the unchanged JSON file: its absolute path is entered during interactive installation or passed with `--credential-file`; tokens never enter command arguments. +- **Add node**: the sandbox limits first, then the one-time command in a Terminal block (expiry countdown and Copy command in its header), the three progress steps, and, once the installer's minute passes, an amber card with the reason and a copyable system-service log command. Below, the Host requirements Hairline disclosure is open until this browser has shown it once. Installation requires root or sudo, creates the `oac-node` system service, and serves one Core per host because nodes share the service account. For Docker, explain that membership in the docker group is root-equivalent. Do not expose an ordinary-user installation command or user-service prerequisites. The command downloads from the installation's public URL, never the browser's address, so it works as shown on any host. Until the installation is read, a line says it is being checked; a failed read, a public URL other machines can't use (loopback or not HTTPS), or a console without the provider's node files replaces the limits with one line saying why (the failed read with Try again), and the footer offers nothing to generate. Once the node is ready, while Getting started is open, one line under the green status names the next step (set a default model provider, or finish Getting started) with a text action to System or the Overview. +- **Clean up the host**: after a node is removed, a dialog gives the host's uninstall command in the same Terminal block, a Graphite line that it deletes no sandboxes, volumes or images (and, for microsandbox, keeps its image store and data). The command requires root or sudo; there is no user-service alternative. A node enrolled with an earlier Core address adds an "Old Core address gone?" disclosure with the `--force` form. The command, too, downloads from the public URL, which the dialog reads again if it is not at hand: until then one line says it is being checked, a failed read says so with Try again, and a public URL other machines can't use (loopback, or none) gets a line saying the service stays on the host and no command can be given. Done dismisses it and focus returns to the page heading. +- **Use Docker instead of microsandbox?**: choosing Docker in sandbox setup lists what it gives up, each point a 600 Ink lead over a Graphite line: weaker isolation (containers share the host kernel; microsandbox gives each sandbox its own microVM), root-equivalent access (the node's account joins the docker group) and limited use (trusted workloads, or hosts without KVM). The footer holds Use Docker (outline) and Keep microsandbox (primary), which takes focus; closing or Escape keeps microsandbox too. +- **Edit node**: the name, then the sandbox limit with one 12px Graphite line under it once the node's heartbeat has the host's CPUs and memory: the host, each sandbox's size and at most how many fit. The Nodes list and a node's Capacity show "Active / limit" for Docker and microsandbox alike, so a saved limit shows where it was set. +- **How to call**: wherever a new key is shown, a card under it gives three copyable samples, each a Margin Gray block with a Hairline and its label and copy button in a header row: a Shell block exporting `OPENAI_BASE_URL` (the installation's API base URL) and `OPENAI_API_KEY` (the new key) together, then curl and Python (with the pinned SDK), each listing the project's Agents and creating a Session with a first message (`environment`, an inline `agent` with `model: ""`, and `input`). A copy the clipboard refuses selects the sample and says so in red underneath. One Graphite line says to put a model the model provider serves in place of ``, and that running an Agent needs a model provider: in each request, saved on the Agent, or the deployment default. An active project's page shows the same samples as a section without any key: the Shell block exports a quoted placeholder, and a Graphite line above the samples says to use a key issued for this project, shown only once at issuance. When the public address is loopback, a note above the samples says the API is reachable only on the Core machine; without a public address only a note to set one shows. Before the installation is read, a skeleton holds the first sample's place. ### Navigation -Sidebar groups Monitor, Resources and Platform with 12px Graphite group labels; -items are 30px rows with a 15px outline icon and Sidebar Ink text. Hover takes the -ink wash; the active item sits on a white chip (the page panel's surface, ringed) -with Ledger Ink at 500, and the chip glides to the next item on navigation. The -Platform group sits below a hairline. A secondary page (one Session) highlights its -parent. The footer holds Show Getting started, then sign-out and the language/theme menu. A detail page's back arrow returns to the page it was opened -from (a Skill opened from a template goes back to the template); opened directly, -it goes to its list. The arrow is labelled plainly "Back". +Sidebar groups Monitor, Resources and Platform with 12px Pencil group labels; items are 32px rows with a 15px outline icon and Sidebar Ink text. Hover takes the Pressed gray; the active item sits on a white chip (the page panel's surface, ringed) with Ledger Ink at 500, and the chip glides to the next item on navigation. The Platform group sits below a hairline. A secondary page (one Session) highlights its parent. The footer holds Show Getting started, then sign-out and the language/theme menu. A detail page's back arrow returns to the page it was opened from (a Skill opened from a template goes back to the template); opened directly, it goes to its list. The arrow is labelled plainly "Back". ### KPI strip and metric tiles -A KPI strip is one card of equal cells separated by inset rules. Each cell: a -12.5px Graphite label with an optional help tip, then the figure at 20px/500 with -any unit or limit small beside it, optionally led by an 8px tone dot. Overview uses -four separate metric tiles instead: a 13px label with a help tip, the same 20px -figure (the service status as a dot and a word), and one 12.5px line of context. -Figures ellipsize rather than wrap. Live figures on monitor pages roll their digits -to a new value on refresh (NumberFlow) instead of swapping. +A KPI strip is one card of equal cells separated by inset rules. Each cell: a 12.5px Graphite label with an optional help tip, then the figure at 20px/500 with any unit or limit small beside it, optionally led by an 8px tone dot. Overview uses four separate metric tiles instead: a 13px label with a help tip, the same 20px figure (the service status as a dot and a word), and one 12.5px line of context. Figures ellipsize rather than wrap. Live figures on monitor pages roll their digits to a new value on refresh (NumberFlow) instead of swapping. ### Help tip -An 18px circular button holding a 13px circled "?" in Pencil; hover or open takes -Ledger Ink on the ink wash. It opens on hover, focus or click (click pins it), -closes on Escape, scroll or resize, and renders a 12.5px popover (Paper, Hairline, -8px, floating shadow, max 288px) in a portal. The text also exists in a visually -hidden element for assistive technology. +An 18px circular button holding a 13px circled "?" in Pencil; hover or open takes Ledger Ink on the Pressed gray. It opens on hover, focus or click (click pins it), closes on Escape, scroll or resize, and renders a dark 12px tooltip (8px corners, overlay shadow, max 288px wide) in a portal. The text also exists in a visually hidden element for assistive technology. ### Status dot -A 7px circle plus a plain label at 12.5px: ok green, warning amber, danger red, -pending Series 1 with a soft expanding ring while work is in progress, neutral -Idle Gray. A waiting Session names the result its application must submit under -the label in lists, with the caller's responsibility in a help tip. Its detail -page shows both in the Waiting for facts. -A failed Session's reason, as Core sent it, stays visible under the label -in 12px Graphite: in full on the Session page, its line breaks kept; in the -Session log on one truncated line, with the full text in its tooltip, that never -widens the status column. Never a coloured pill, never colour alone. +A 6px circle plus a plain 13px label: ok green, warning amber, danger red, pending Series 1 with a soft expanding ring while work is in progress, neutral Pencil. A waiting Session names the result its application must submit under the label in lists, with the caller's responsibility in a help tip. Its detail page shows both in the Waiting for facts. A failed Session's reason, as Core sent it, stays visible under the label in 12px Graphite: in full on the Session page, its line breaks kept; in the Session log on one truncated line, with the full text in its tooltip, that never widens the status column. Never a coloured pill, never colour alone. ### Meter -A 6px pill rail in Meter Track with a neutral ink fill. The fill turns amber at 90% -and red at 100% of its limit by default, and a nonzero ratio shows at least 3% -width. An unknown ratio draws an empty rail. A share meter may carry a fixed -identity colour and then ignores thresholds. +A 5px pill rail in Well Gray with an inset hairline and a neutral ink fill. The fill turns amber at 90% and red at 100% of its limit by default, and a nonzero ratio shows at least 3% width. An unknown ratio draws an empty rail. A share meter may carry a fixed identity colour and then ignores thresholds. ### Charts -Time-series charts live in chart panels (caption 13px/600, legend with series -totals, plot) inside one chart-grid card. Lines are 2px round-joined with a -surface-ringed end dot; gridlines are crisp Hairlines with 11px tabular ticks; -hovering draws a Pencil crosshair, a hover-wash band and a floating tooltip. Missing -buckets are gaps, not zeros. Every chart has a 26px table toggle at its top right -that reveals the numbers in a 220px scrolling table. When a range is first shown, -bars rise from the baseline in a short left-to-right wave and lines trace from their -first point; refreshes of the same range redraw in place. +Time-series charts live in chart panels (caption 13px/600, legend with series totals, plot) inside one chart-grid card. Lines are 2px round-joined with a surface-ringed end dot; gridlines are crisp Hairlines with 11px tabular ticks; hovering draws a Pencil crosshair, a hover-wash band and a dark tooltip. Missing buckets are gaps, not zeros. Every chart has a 26px table toggle at its top right that opens its numbers in a dialog, so the chart grid keeps its layout. When a range is first shown, bars rise from the baseline in a short left-to-right wave and lines trace from their first point; refreshes of the same range redraw in place. ### Tables -A card with a sticky 34px Margin Gray header in Graphite 12px/500, 44px rows divided -by Faint Rules, hover wash, right-aligned tabular numerics, clickable rows where a -detail page exists, and the list grammar above. Agent metrics' By Agent table -links a saved Agent's name to its page and a nonzero Failed figure (in its red) to -the Session log with its project and Agent filters set to that Agent, every -status; both turn indigo on hover. The figure counts failed Turns in the range, as -the column's help tip says, so the link's name and tooltip give that count and say -it opens the Agent's Sessions. A -key count in a section heading reads "3 active · 1 revoked" (revoked left out -at zero). +A card with a sticky 36px Paper header in Graphite 12.5px/500 over a Hairline, 44px rows divided by Faint Rules, hover wash, right-aligned tabular numerics, clickable rows where a detail page exists, and the list grammar above. Agent metrics' By Agent table links a saved Agent's name to its page and a nonzero Failed figure (in its red) to the Session log with its project and Agent filters set to that Agent, every status; both turn indigo on hover. The figure counts failed Turns in the range, as the column's help tip says, so the link's name and tooltip give that count and say it opens the Agent's Sessions. A key count in a section heading reads "3 active · 1 revoked" (revoked left out at zero). ### Notices -On Overview and Session log, failed reads that leave a section unavailable replace -its contents with ErrorState and Retry. Partial or stale reads keep useful rows -and figures, with a durable ErrorState and Retry beside them explaining that -coverage may be incomplete or out of date. A failed read never supplies a zero -chart or an all-clear; successfully read zero values stay zero. Session log status -counts stay missing until the reads succeed. A failed summary retains its last -rows, and a failed project Session read retains only that project's last rows; -successful sources update independently. Retention never crosses project scopes. - -A failed action whose outcome needs a decision (a sandbox change with no answer, -a timeout or a 5xx) opens an error dialog with the reason and the next step as its -primary button. Failed refreshes and project reads also raise an error toast; -other failed actions, Core's clear refusal of a sandbox change among them, are -reported there with the reason. A refusal leaves the page usable as it was. -Errors inside a dialog or a form stay beside what they concern. Coverage notes -(Margin Gray, Hairline, 12px corners, 12.5px Graphite) state bounded aggregation. -Standing warnings that need action use an amber-tinted line at the top of the page -body. On Nodes, this names nodes still bound to an old Core address; each of those -nodes' status reads Old address (amber dot) with "Remove and add again" under it -in 12px Graphite. Partial-data chips are amber-tinted pills with a help tip. Safety -notices (a key shown once, a destructive consequence) stay visible in body text. - -A local-only installation has the same amber notice on Overview, Nodes and System: -other machines cannot connect, followed by Core's configuration path and apply -command as copyable values. If Core has no configuration snapshot, state that -those instructions are unavailable; never fill in a path or command. Add node is -disabled with its reason beside the action, and Getting started leaves its first -step to do with the address fix visible. A pending or failed installation read -cannot complete that step; a failed read shows Unknown and Retry. +On Overview and Session log, failed reads that leave a section unavailable replace its contents with ErrorState and Retry. Partial or stale reads keep useful rows and figures, with a durable ErrorState and Retry beside them explaining that coverage may be incomplete or out of date. A failed read never supplies a zero chart or an all-clear; successfully read zero values stay zero. Session log status counts stay missing until the reads succeed. A failed summary retains its last rows, and a failed project Session read retains only that project's last rows; successful sources update independently. Retention never crosses project scopes. + +A failed action whose outcome needs a decision (a sandbox change with no answer, a timeout or a 5xx) opens an error dialog with the reason and the next step as its primary button. Failed refreshes and project reads also raise an error toast; other failed actions, Core's clear refusal of a sandbox change among them, are reported there with the reason. A refusal leaves the page usable as it was. Errors inside a dialog or a form stay beside what they concern. Coverage notes (Margin Gray, Hairline ring, 8px corners, 12.5px Graphite) state bounded aggregation. Standing warnings that need action use an amber-tinted line at the top of the page body. On Nodes, this names nodes still bound to an old Core address; each of those nodes' status reads Old address (amber dot) with "Remove and add again" under it in 12px Graphite. Partial-data chips are amber-tinted pills with a help tip. Safety notices (a key shown once, a destructive consequence) stay visible in body text. + +A local-only installation has the same amber notice on Overview, Nodes and System: other machines cannot connect, followed by Core's configuration path and apply command as copyable values. If Core has no configuration snapshot, state that those instructions are unavailable; never fill in a path or command. Add node is disabled with its reason beside the action, and Getting started leaves its first step to do with the address fix visible. A pending or failed installation read cannot complete that step; a failed read shows Unknown and Retry. ### Onboarding -Signing in and the console tour share one frame: a dark stage on the left (always -dark, whatever the theme) and the task panel on the right, which follows the -theme. The stage is the product's one authored moment: a flickering indigo dot -grid under slow light rays (Magic UI's flickering grid and light rays), Core as -the OpenAgentCore mark on a tile with a travelling border beam, and two orbits of -Agents, Sessions, Skills, Vaults, files, templates and machines around it; the -OpenAgentCore mark is itself nodes on a ring. Brand copy sits bottom-left in solid -ink; it is a paragraph, not a heading, because the panel's title names the task. -Signing in asks for one thing, the deployment's Core key, in a single password -field; the default key location and a copyable read command stay visible beneath -it, with a reminder to substitute a custom installation directory. The key’s -authority stays in a help tip. A refused key, too many attempts or an unavailable console is an error -beside the field. Signing in opens the console on the Overview. The optional tour -has three chapters — Monitor, Resources, Platform — whose stage shows a real dark -screenshot of those pages, tilted towards the panel; it takes the place of the -console until its last button, Skip or Escape, and then returns the focus to the -control that opened it. Entering the console or the tour, and leaving the tour, -happen inside a View Transition: the old page dissolves forward and the new one -is revealed in a circle growing from the pressed button. With reduced motion the -orbits hold their places, the grid is a still frame and no transition runs. +Signing in and the console tour share one frame: a dark stage on the left (always dark, whatever the theme) and the task panel on the right, which follows the theme. The stage is the product's one authored moment: a flickering indigo dot grid under slow light rays (Magic UI's flickering grid and light rays), Core as the OpenAgentCore mark on a tile with a travelling border beam, and two orbits of Agents, Sessions, Skills, Vaults, files, templates and machines around it; the OpenAgentCore mark is itself nodes on a ring. Brand copy sits bottom-left in solid ink; it is a paragraph, not a heading, because the panel's title names the task. Signing in asks for one thing, the deployment's Core key, in a single password field; the default key location and a copyable read command stay visible beneath it, with a reminder to substitute a custom installation directory. The key’s authority stays in a help tip. A refused key, too many attempts or an unavailable console is an error beside the field. Signing in opens the console on the Overview. The optional tour has three chapters — Monitor, Resources, Platform — whose stage shows a real dark screenshot of those pages, tilted towards the panel; it takes the place of the console until its last button, Skip or Escape, and then returns the focus to the control that opened it. Entering the console or the tour, and leaving the tour, happen inside a View Transition: the old page dissolves forward and the new one is revealed in a circle growing from the pressed button. With reduced motion the orbits hold their places, the grid is a still frame and no transition runs. ### Getting started -The first card on the Overview while any step is to do: a card header ("Getting -started", "n of 4 done", a help tip, then a ghost Take the tour button and an icon -button that hides it) over four rows split by Faint Rules. Each row has a 22px -numbered ring (a check on the tile wash when done), a 13px/600 title over one -12.5px Graphite line, a status dot (Done in green, To do in Idle Gray, Checking -pending, Unknown for a failed read) and one outline action while the step is to -do: Set up sandboxes, Add node, Open Nodes or Open sandbox backend; Open System; -Create project (which continues to the new project's first key) or Issue key; -See how to call (the newest active project, preferring one with an active key), or -Projects and keys without an active project. Add node, Create project and Issue key -open their page with the dialog already open; Open System brings the Default -model provider section to the top of the page body and focuses the default harness's Set or -Replace; See how to call opens the project and, once its keys, usage and address -are read, brings its How to call heading to the top of the page body, focused. Only the page body scrolls; the page header stays. Every step done turns it into one line, "You're set", with Take the tour and -Dismiss; it stays, through the tour, until dismissed, and the checklist does not -come back on its own. The choice is kept per installation in the browser, also -while the deployment cannot be read; Show Getting started, a quiet row above the -sidebar's account controls, opens it again at any time. +The first card on the Overview while any step is to do: a card header ("Getting started", "n of 4 done", a help tip, then a ghost Take the tour button and an icon button that hides it) over four rows split by Faint Rules. Each row has a 22px numbered ring (a check on the tile wash when done), a 13px/600 title over one 12.5px Graphite line, a status dot (Done in green, To do in Pencil, Checking pending, Unknown for a failed read) and one outline action while the step is to do: Set up sandboxes, Add node, Open Nodes or Open sandbox backend; Open System; Create project (which continues to the new project's first key) or Issue key; See how to call (the newest active project, preferring one with an active key), or Projects and keys without an active project. Add node, Create project and Issue key open their page with the dialog already open; Open System brings the Default model provider section to the top of the page body and focuses the default harness's Set or Replace; See how to call opens the project and, once its keys, usage and address are read, brings its How to call heading to the top of the page body, focused. Only the page body scrolls; the page header stays. Every step done turns it into one line, "You're set", with Take the tour and Dismiss; it stays, through the tour, until dismissed, and the checklist does not come back on its own. The choice is kept per installation in the browser, also while the deployment cannot be read; Show Getting started, a quiet row above the sidebar's account controls, opens it again at any time. ### Sandbox setup -Setting up hosted sandboxes is a set of pages inside System’s Sandbox configuration secondary page, one decision each: where sandboxes run (own -machines or E2B), then the backend or the E2B account, then the size of each sandbox -(three presets; E2B skips it, since each sandbox takes the template build's size), -then a review. Choices are large cards that advance on a click; short indigo dashes -show the progress; pages slide and blur across. The backend page compares -microsandbox and Docker behind a help tip; microsandbox comes first, preselected (a -saved backend stays selected), with a neutral Recommended pill beside its title. -Docker takes a confirmation (see Dialogs) once per visit to setup; a saved Docker -deployment has already made it. The review states where sandboxes run, the size, the -Runtime (taken from this console's distribution manifest) and the Core address, -read-only: it is config.json's `public_url`, and the console never asks for it. A -loopback address carries an amber line under it: only the Core machine reaches it. -When Core rejects the configuration for it (E2B with a loopback `public_url`), a -red-tinted block under the review keeps Core's message and adds the config file and -apply command as copyable values. A save attempt clears the transient E2B key. Initial setup then asks for it again, -with a link to that step; an update may leave it blank to keep the committed key. -Advanced settings, one link away, hold the complete form: resources (not for -E2B), the Runtime release and the E2B template. A change keeps the saved size -and Runtime while the backend stays the same (a saved size outside the presets is -offered as Current). Same-backend editing starts at size or E2B credentials with -the provider fixed. It is an online configuration update, including when older -sandboxes remain: existing node identities and resource ownership are retained. -Changing the backend or E2B team requires reset and then a new setup. E2B updates -can omit the key to retain it; every explicitly entered key takes the verified -replacement path and advances the target generation on success, including the same -value. Rejections remain inline with a safe -reason and a deliberate way back to reset; never infer teams from a key, auto-reset -or auto-resubmit. Optional explanations sit behind help tips; errors and safety -consequences remain visible. +Setting up hosted sandboxes is a set of pages inside System’s Sandbox configuration secondary page, one decision each: where sandboxes run (own machines or E2B), then the backend or the E2B account, then the size of each sandbox (three presets; E2B skips it, since each sandbox takes the template build's size), then a review. Choices are large cards that advance on a click; short indigo dashes show the progress; pages slide and blur across. The backend page compares microsandbox and Docker behind a help tip; microsandbox comes first, preselected (a saved backend stays selected), with a neutral Recommended pill beside its title. Docker takes a confirmation (see Dialogs) once per visit to setup; a saved Docker deployment has already made it. The review states where sandboxes run, the size, the Runtime (taken from this console's distribution manifest) and the Core address, read-only: it is config.json's `public_url`, and the console never asks for it. A loopback address carries an amber line under it: only the Core machine reaches it. When Core rejects the configuration for it (E2B with a loopback `public_url`), a red-tinted block under the review keeps Core's message and adds the config file and apply command as copyable values. A save attempt clears the transient E2B key. Initial setup then asks for it again, with a link to that step; an update may leave it blank to keep the committed key. Advanced settings, one link away, hold the complete form: resources (not for E2B), the Runtime release and the E2B template. A change keeps the saved size and Runtime while the backend stays the same (a saved size outside the presets is offered as Current). Same-backend editing starts at size or E2B credentials with the provider fixed. It is an online configuration update, including when older sandboxes remain: existing node identities and resource ownership are retained. Changing the backend or E2B team requires reset and then a new setup. E2B updates can omit the key to retain it; every explicitly entered key takes the verified replacement path and advances the target generation on success, including the same value. Rejections remain inline with a safe reason and a deliberate way back to reset; never infer teams from a key, auto-reset or auto-resubmit. Optional explanations sit behind help tips; errors and safety consequences remain visible. ### Configuration generations -A single rollout row opens a details dialog for Core's target generation, -previous-generation sandboxes and rollout counts. Poll rapidly only while Core reports preparing, or -while the independent reset is active. Settled is preparation state, not proof that -all nodes are ready or all older Sessions have ended. Retained old resources alone -must not keep rapid polling alive. Render failed, update-required and unknown target -states distinctly. Keep offline/live-provider status separate from a node's durable -serving-generation pin; the pin alone never means the node is online or ready. -Node detail shows the serving generation and target preparation; allocation detail -shows the owned configuration generation. Do not calculate rollout completion from -these rows or promise immediate placement on the target. - -A generation-only update within the same installation/backend lifecycle retains -compatible previous node/allocation evidence while refreshing. Failed or pending -reads visibly qualify those observations; never replace them with fabricated zeros. -Reset, backend and installation lifecycle changes still discard incompatible data. -The shared deployment query and write ownership below continue to govern navigation, -late reads, explicit retries and login isolation. This final online experience ships -only with the qualified node-generation protocol; fixture results alone do not -establish native online-upgrade capability. +A single rollout row opens a details dialog for Core's target generation, previous-generation sandboxes and rollout counts. Poll rapidly only while Core reports preparing, or while the independent reset is active. Settled is preparation state, not proof that all nodes are ready or all older Sessions have ended. Retained old resources alone must not keep rapid polling alive. Render failed, update-required and unknown target states distinctly. Keep offline/live-provider status separate from a node's durable serving-generation pin; the pin alone never means the node is online or ready. Node detail shows the serving generation and target preparation; allocation detail shows the owned configuration generation. Do not calculate rollout completion from these rows or promise immediate placement on the target. + +A generation-only update within the same installation/backend lifecycle retains compatible previous node/allocation evidence while refreshing. Failed or pending reads visibly qualify those observations; never replace them with fabricated zeros. Reset, backend and installation lifecycle changes discard incompatible data. The shared deployment query and write ownership below govern navigation, late reads, explicit retries and login isolation. ### Sandbox reset -The deployment section offers explicit reset rather than maintenance/resume. Reuse -its existing panels and confirmation dialogs. Keep the confirmation to one concise -consequence paragraph, two mode choices, the auto deadline and footer actions. -Put cleanup sequencing and preservation details in help tips. Auto clear is selected first, with a -one-hour deadline editable from 5 minutes to 24 hours; Force clear and escalation -require destructive confirmation. State directly that hosted work is archived, -remaining active work may be cancelled, archived Sessions cannot resume and -unpersisted workspace contents may be lost. Details about preserved histories, Files/Artifacts and unaffected self-hosted -execution live behind the reset impact help tip. Cancelling an active reset stops -further clearing but cannot undo completed archives. - -A persistent progress panel uses Core's busy, idle and cleanup counts, deadline and -named offline-node blockers. Bring blocked nodes online for confirmed cleanup; -never offer a browser-side force-release shortcut. Poll the deployment every five -seconds only while its reset is non-null. A passed deadline does not establish force -or completion; only a Core response does. Completion opens the existing setup flow, -with a new explicit save using the generation read from Core, including zero on a -fresh install. Reset and online configuration rollout have independent authoritative -progress; neither automatically replays configuration. - -Read deployment progress independently of node details. Partial failures retain -successful facts with a visible stale/unavailable notice. An uncertain write opens -the existing recovery dialog and requires a new authoritative read before another -mutation; refresh reads state and never resubmits the write. The connection's -QueryClient owns both the authoritative deployment and pending or uncertain writes -across route transitions. Leaving Sandbox configuration cannot cancel or forget a submitted reset, -and a cached node snapshot cannot replace a newer reset or completion learned on -Overview. Returning to Nodes or Sandbox configuration reads the shared deployment immediately and -refreshes node evidence separately. Only a successful authoritative read begun after -the write settles can release the mutation block; an earlier or still-pending read -cannot. Submitting consumes the reset confirmation even if its outcome is uncertain; -recovery uses the separate read-and-review dialog. Observation retries preserve -applicable non-secret configuration drafts. A changed installation, owner epoch, -backend, mode or generation discards the prior draft and confirmation. Logout clears -this connection-scoped state. +Sandbox configuration offers an explicit reset through its panels and confirmation dialogs. The confirmation keeps to one concise consequence paragraph, two mode choices, the auto deadline and footer actions. Put cleanup sequencing and preservation details in help tips. Auto clear is selected first, with a one-hour deadline editable from 5 minutes to 24 hours; Force clear and escalation require destructive confirmation. State directly that hosted work is archived, remaining active work may be cancelled, archived Sessions cannot resume and unpersisted workspace contents may be lost. Details about preserved histories, Files/Artifacts and unaffected self-hosted execution live behind the reset impact help tip. Cancelling an active reset stops further clearing but cannot undo completed archives. + +A persistent progress panel uses Core's busy, idle and cleanup counts, deadline and named offline-node blockers. Bring blocked nodes online for confirmed cleanup; never offer a browser-side force-release shortcut. Poll the deployment every five seconds only while its reset is non-null. A passed deadline does not establish force or completion; only a Core response does. Completion opens the setup flow, with a new explicit save using the generation read from Core, including zero on a fresh install. Reset and online configuration rollout have independent authoritative progress; neither automatically replays configuration. + +Read deployment progress independently of node details. Partial failures retain successful facts with a visible stale/unavailable notice. An uncertain write opens the recovery dialog and requires a new authoritative read before another mutation; refresh reads state and never resubmits the write. The connection's QueryClient owns both the authoritative deployment and pending or uncertain writes across route transitions. Leaving Sandbox configuration cannot cancel or forget a submitted reset, and a cached node snapshot cannot replace a newer reset or completion learned on Overview. Returning to Nodes or Sandbox configuration reads the shared deployment immediately and refreshes node evidence separately. Only a successful authoritative read begun after the write settles can release the mutation block; an earlier or still-pending read cannot. Submitting consumes the reset confirmation even if its outcome is uncertain; recovery uses the separate read-and-review dialog. Observation retries preserve applicable non-secret configuration drafts. A changed installation, owner epoch, backend, mode or generation discards the prior draft and confirmation. Logout clears this connection-scoped state. ### System page -Four sections, each saying where it changes. Installation: the public address, API -base URL, installation ID and source commit as a fact card. Default model configuration, the one -section changed here: one card per harness in an auto-fill grid, its header holding -the harness name and outline actions (Set, or Replace and Clear); fact rows give the -harness's read-only startup state (a status dot and a Default pill, its source behind -a help tip), then the default model ID, provider protocol, base URL, whether a key is configured, -token limits when set and the update time, or Not set. Set and Replace open one form -dialog. The model ID is required; advanced settings disclose an optional JSON object -editor with formatting and inline syntax errors, plus token limits. The existing -harness discovery response supplies supported protocols, native protocols, JSON -support and required limits from one adapter declaration. The form uses those -fields without harness-specific branches. Nonempty JSON requires a native protocol; -the form explains an incompatible selection beside the editor. Help tips explain -the scope of native settings. Changing the model ID, provider URL or protocol -clears the native JSON so settings cannot follow an unrelated model by accident. -Re-entering the required write-only API key alone does not change model identity. The key field is a required password input, never prefilled or shown and -forgotten when the form closes. Core's rejection stays in red inside the form; Clear -is a ConfirmDialog. Usage details opens Core’s observations in a separate dialog. -Sandboxes: one navigation row to the Sandbox configuration secondary page; do not -repeat its configuration facts on System. Startup settings: a line naming -the config file and the apply command as copyable chips, with when they were last -applied, over a table of each setting, its value and the services a change restarts. -Sensitive settings show only Configured or Not set; Default and Fixed after install -are neutral pills beside the value. +Four sections, each saying where it changes. Installation: the public address, API base URL, installation ID and source commit as a fact card, with an outline action that opens the Domain and HTTPS secondary page. Default model configuration, the one section changed here: one card per harness in an auto-fill grid, its header holding the harness name and outline actions (Set, or Replace and Clear); fact rows give the harness's read-only startup state (a status dot and a Default pill, its source behind a help tip), then the default model ID, provider protocol, base URL, whether a key is configured, token limits when set and the update time, or Not set. Set and Replace open one form dialog. The model ID is required; advanced settings disclose an optional JSON object editor with formatting and inline syntax errors, plus token limits. The harness list supplies supported protocols, native protocols, JSON support and required limits from one adapter declaration. The form uses those fields without harness-specific branches. Nonempty JSON requires a native protocol; the form explains an incompatible selection beside the editor. Help tips explain the scope of native settings. Changing the model ID, provider URL or protocol clears the native JSON so settings cannot follow an unrelated model by accident. Re-entering the required write-only API key alone does not change model identity. The key field is a required password input, never prefilled or shown and forgotten when the form closes. Core's rejection stays in red inside the form; Clear is a ConfirmDialog. Usage details opens Core’s observations in a separate dialog. Sandboxes: one navigation row to the Sandbox configuration secondary page; do not repeat its configuration facts on System. Startup settings: a line naming the config file and the apply command as copyable chips, with when they were last applied, over a table of each setting, its value and the services a change restarts. Sensitive settings show only Configured or Not set; Default and Fixed after install are neutral pills beside the value. ### One place for each task -A configuration or operation has one home. Other pages link to it instead of -repeating the same panel. System links to the Sandbox configuration secondary -page; Nodes contains node management. Keep the configuration page flat: the -resource editor is a dialog, and rollout is one status row with a details -action. Put low-frequency counts and generation metadata in that dialog. -Explanatory prose belongs in help tips, not rows of small print. Keep actionable -errors and unresolved state visible without duplicating the whole workflow. +A configuration or operation has one home. Other pages link to it instead of repeating the same panel. System links to the Sandbox configuration secondary page; Nodes contains node management. Keep the configuration page flat: the resource editor is a dialog, and rollout is one status row with a details action. Put low-frequency counts and generation metadata in that dialog. Explanatory prose belongs in help tips, not rows of small print. Keep actionable errors and unresolved state visible without duplicating the whole workflow. ### Diagnostic observations -Failure reasons belong beside the failed Session or Turn status. Their first -read uses a skeleton; an unavailable reason names that state and puts the read -retry beside its help tip. Technical classifications are -translated through one catalogue, not shown as raw error codes. Trace Timing -labels Core receipt times separately from tool-reported duration; explanations -of batched delivery, clock differences and historical gaps live in help tips. +Failure reasons belong beside the failed Session or Turn status. Their first read uses a skeleton; an unavailable reason names that state and puts the read retry beside its help tip. Technical classifications are translated through one catalogue, not shown as raw error codes. Trace Timing labels Core receipt times separately from tool-reported duration; explanations of batched delivery, clock differences and historical gaps live in help tips. -The self-hosted connection panel names Core's observed state, the bound key and -last heartbeat. The bound-key action follows the existing rotation confirmation -and one-time credential flow. Stale observations cannot complete Run on host. +The self-hosted connection panel names Core's observed state, the bound key and last heartbeat. The bound-key action uses the rotation confirmation and the one-time credential flow. Stale observations cannot complete Run on host. ### Loading and motion -The console has no spinners and no "Loading…" lines. Reads are cached (TanStack -Query) and prefetched on navigation hover, so revisits show data at once and -refreshes keep the last data on screen. Only a first read shows a skeleton in the -final layout's cards: table rows, a headline strip with chart panels, or a facts -card with a table, swept once under a second. Work in progress is the Agent's -shimmering "Working…" line in the conversation and the breathing pending dot. - -Motion reports state and never makes anyone wait: the navigation chip and segmented -thumbs glide (Motion, one 320ms spring without bounce), figures roll, charts draw in -once per range, new conversation messages settle 6px upward in 260ms, pages fade in -160ms, popovers and dialogs scale from 98%. Reduced motion makes all of it instant. +The console has no spinners and no "Loading…" lines. Reads are cached (TanStack Query) and prefetched on navigation hover, so revisits show data at once and refreshes keep the last data on screen. Only a first read shows a skeleton in the final layout's cards: table rows, a headline strip with chart panels, or a facts card with a table, with a sweep that repeats every 900ms and stops under reduced motion. Work in progress is the Agent's shimmering "Working…" line in the conversation and the breathing pending dot. + +Motion reports state and never makes anyone wait: the navigation chip and segmented thumbs glide (Motion, one 320ms spring without bounce), figures roll, charts draw in once per range, new conversation messages settle 6px upward in 260ms, pages fade in 160ms, anchored popovers scale from 98% and dialogs from 96%. Reduced motion makes all of it instant. ## Do's and Don'ts ### Do: -- **Do** put every explanation of a figure, column, section or page behind a - circled "?" help tip; report errors in a dialog or a toast; keep warnings and - safety notices (deletion consequences, a key shown once) visible. -- **Do** start every project-scoped toolbar with the project filter, then search, - with the count on the right. +- **Do** put every explanation of a figure, column, section or page behind a circled "?" help tip; report errors in a dialog or a toast; keep warnings and safety notices (deletion consequences, a key shown once) visible. +- **Do** start every project-scoped toolbar with the project filter, then search, with the count on the right. - **Do** end every resource table with the Creator column and then the row actions. - **Do** confirm every deletion in ConfirmDialog. -- **Do** keep meters in neutral ink and let amber and red mean a threshold was - crossed. -- **Do** reserve OpenAgentCore Indigo for selection, focus, primary actions and the single - `--data` series. +- **Do** keep meters in neutral ink and let amber and red mean a threshold was crossed. +- **Do** reserve OpenAgentCore Indigo for selection, focus, links and the single `--data` series. - **Do** place figures, charts and tables in one card divided by 1px internal rules. - **Do** render missing data as "—", a chart gap, "Unavailable" or "Unknown". -- **Do** show status as a 7px dot plus a plain label. -- **Do** use tabular numerals for every aligned figure and right-align numeric - columns. +- **Do** show status as a 6px dot plus a plain label. +- **Do** use tabular numerals for every aligned figure and right-align numeric columns. - **Do** build every page from PageHeader, PageBody and Section. ### Don't: -- **Don't** add lines of small explanatory print under headings, KPIs, fields or - charts. +- **Don't** add lines of small explanatory print under headings, KPIs, fields or charts. - **Don't** colour healthy meters, bars or states; colour is for problems and data. - **Don't** colour multi-series data with the indigo accent. - **Don't** nest cards inside cards or draw a dashed empty state. @@ -910,7 +463,5 @@ once per range, new conversation messages settle 6px upward in 260ms, pages fade - **Don't** use coloured status pills or colour-only status. - **Don't** reassign a categorical colour by rank when data re-sorts. - **Don't** show full IDs in list columns; show the compact ID with its copy button. -- **Don't** add uppercase letter-spaced micro-labels or eyebrow lines above - headings; a section is named by its title alone. -- **Don't** mix synonyms in zh-CN copy (for example alternating 沙盒 with 沙箱, or - API 密钥 with API key). +- **Don't** add uppercase letter-spaced micro-labels or eyebrow lines above headings; a section is named by its title alone. +- **Don't** mix synonyms in zh-CN copy (for example alternating 沙盒 with 沙箱, or API 密钥 with API key). diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index fda356583..1d17230e8 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -8,285 +8,81 @@ web ## Users -The primary user is the administrator who deployed OpenAgentCore: a self-hosted, -OpenAI Agents API compatible execution service. After signing in to the paired -console they need to answer quickly: is the service healthy, is there enough -sandbox capacity, how much is each project using, and where is work failing. -They also create projects and issue their keys, enroll execution nodes, and clean -up or redistribute assets between projects. +The primary user is the administrator who deployed OpenAgentCore: a self-hosted, OpenAI Agents API compatible execution service. After signing in to the paired console they need to answer quickly: is the service healthy, is there enough sandbox capacity, how much is each project using, and where is work failing. They also create projects and issue their keys, enroll execution nodes, and clean up project assets. -API callers (application developers, and Parsar itself) use the Agents API from -their own code with the keys of their project, not this console. The console is -the administrator's management tool, comparable to what the provider of a hosted -API runs internally: it manages the service and its projects, it does not build or -run things on a caller's behalf. +API callers (application developers, and Parsar itself) use the Agents API from their own code with the keys of their project, not this console. The console is the administrator's management tool, comparable to what the provider of a hosted API runs internally: it manages the service and its projects, it does not build or run things on a caller's behalf. ## Product Purpose -A management console for one OpenAgentCore deployment. Success: the administrator -lands on health, capacity, usage and failures across every project; inspects any -project's Agents, Environment templates, Skills, Files, Vaults and Session history -together with the API key that created each of them; deletes assets (for example a -leaked Credential); manages projects and their named keys; and administers sandbox -nodes. +A management console for one OpenAgentCore deployment. Success: the administrator lands on health, capacity, usage and failures across every project; inspects any project's Agents, Environment templates, Skills, Files, Vaults and Session history together with the API key that created each of them; deletes assets (for example a leaked Credential); manages projects and their named keys; and administers sandbox nodes. ## Positioning -The console runs beside the administrator's own Core, with execution, files and -credentials on infrastructure they control. It shows only evidence Core actually -reports and never invents readiness, traffic or zero values for missing data. It -is not a playground: there is no Agent builder, Session composer or request -workbench. +The console runs beside the administrator's own Core, with execution, files and credentials on infrastructure they control. It shows only evidence Core actually reports and never invents readiness, traffic or zero values for missing data. It is not a playground: there is no Agent builder, Session composer or request workbench. ## Operating Context -- Paired console (`services/core-console`): the administrator signs in with the - deployment's Core key, the administration credential the installer writes to - `secrets/core.key` under the installation directory (by default - `~/.oac/core/secrets/core.key`; keeping and rotating it is described in - [Core key](../../docs/getting-started/operations.md#core-key)). There are no - console accounts or usernames. Sign-in shows the default file location and a - copyable `cat ~/.oac/core/secrets/core.key` command for the Core host, with a - reminder to substitute a custom installation directory. The browser sends the key only to sign in and - keeps only the session cookie; the console server holds the Core key and forwards - the Web API (`/core/v1/**`, including sandbox administration under - `/core/v1/sandbox/**`). The console never calls `/v1`. -- The Core key is not an Agents API identity and cannot call `/v1`. An administrator - who wants to call the Agents API issues a project API key like any other caller. -- `/console/config` reports the node installer (`node_installer`, - `node_installer_sha256`), offered only with a 64-hex digest. Native self-hosted - installation does not depend on this endpoint. It also lists the providers it has node files - for (`node_artifacts`); without the deployment's provider, Add node says so and - issues no command. Signing in grants administration, so sandbox - administration is available unless the console explicitly reports - `sandbox_admin: false`; then the Nodes page explains that it is not configured - and the fleet figures show as unavailable. +- Paired console (`services/core-console`): the administrator signs in with the deployment's Core key, the administration credential the installer writes to `secrets/core.key` under the installation directory (by default `~/.oac/core/secrets/core.key`; keeping and rotating it is described in [Core key](../../docs/getting-started/operations.md#core-key)). There are no console accounts or usernames. Sign-in shows the default file location and a copyable `cat ~/.oac/core/secrets/core.key` command for the Core host, with a reminder to substitute a custom installation directory. The browser sends the key only to sign in and keeps only the session cookie; the console server holds the Core key and forwards the Web API (`/core/v1/**`, including sandbox administration under `/core/v1/sandbox/**`). The console never calls `/v1`. +- The Core key is not an Agents API identity and cannot call `/v1`. An administrator who wants to call the Agents API issues a project API key like any other caller. +- `/console/config` reports the node installer (`node_installer`, `node_installer_sha256`), offered only with a 64-hex digest. Native self-hosted installation does not depend on this endpoint. It also lists the providers it has node files for (`node_artifacts`); without the deployment's provider, Add node says so and issues no command. Signing in grants administration, sandbox administration included. - Chinese and English UI; light and dark themes; reduced motion honored. ## Information Architecture -- **Monitor**: Overview (service status, running Sessions, sandbox slots, Sessions - needing attention, 24-hour Session activity, the topology of Core and its nodes - with a popover glance at each, the attention table, usage by project), 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), 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). Agent - metrics' By Agent table opens an Agent's page and, from its failed Turns, its - 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. -- **Platform**: Projects and keys (projects, their assets and usage, named keys, - write history), Nodes (the node list, capacity, host figures, allocations and - individual node operations). Add node asks for limits before issuing its - one-time command; installers use Core's public URL and require supported node - artifacts. Removal offers the host's uninstall command. System owns installation - facts, each harness's default model configuration, startup settings, and a link to - the Sandbox configuration secondary page. That page owns setup, resource edits, - rollout details and reset. Setup selects a backend, size and Runtime, then asks - for a deliberate save; own-machine setup continues to Add node. -- A node whose provider is not ready names the reason (Docker unreachable, no Docker - limits, missing Runtime image, no KVM, missing microsandbox components, a host too - small) and its fix in the help tip beside its status, wherever that status shows. -- A node enrolled with an earlier Core address gets no new sandboxes, so on the Nodes - list and its page its status is Old address, with "Remove and add again", never - Available. -- **Sandbox reset** is an explicit administrator operation in System → Sandbox configuration. Auto clear is - the default, with a one-hour deadline (5 minutes–24 hours); Force clear requires - destructive confirmation. Reset stops new hosted Session admission, clears idle, - suspended and pending hosted work, and waits for busy Turns and file writes until - Core forces the remaining work. It does not affect self-hosted execution. - Histories and persisted Files/Artifacts remain; archived Sessions cannot resume, - and unpersisted workspace contents may be lost. Cancel stops further clearing - without undoing archives. Core alone reports progress and completion, including - resources blocked on named offline nodes; force does not bypass their cleanup. - Completion clears the backend configuration and retires old nodes/enrollment - credentials. A new configuration is then a separate deliberate save. -- **Online sandbox configuration** changes the same backend's resources, Runtime - or E2B template without retiring existing nodes or changing existing Sessions' - resource ownership. New placement follows Core's qualified capacity; saving a - target does not promise immediate placement on it. Configuration rollout shows - Core's target preparation and retained previous-generation sandbox count. A - settled rollout can still have failed, update-required or unknown nodes and old - resources. An offline node stays offline even when it has a recorded serving - generation. Node and allocation detail distinguish the serving pin, target - preparation and each resource's configuration generation. -- **E2B credential replacement** uses the same configuration form. Setup requires - a key; leaving it blank during an update keeps the saved key. An explicit key, - even the same value, is verified as a replacement and advances the target generation - after successful verification. - Another backend or E2B team requires a deliberate reset. A rejected or uncertain - replacement never clears the committed configuration or replays the write. -- **E2B deployments** have no machines: Nodes offers a link to System's sandbox configuration. - Overview and Sandbox metrics show the sandboxes Core holds in E2B's cloud - (running, starting, size, template build) instead of node capacity, with no node column - or Add node action; a sandbox's dialog adds its disk use. -- **microsandbox** suspends idle sandboxes into snapshots, so its nodes show how - many sleep (Core's retained minus active) on the Nodes list, a node's page, Sandbox - metrics and Overview; a node's allocations show how long each has been suspended and - about when Core reclaims it. Docker never suspends and shows none of it. -- **Getting started**: signing in opens the console on the Overview; nothing is - forced first. While a step is to do, a Getting started checklist on the Overview - shows four steps, in any order, each with its state and one action: sandboxes - ready (a saved deployment and a node online and ready, or a saved E2B deployment - whose template build is not reported as not ready), a default model provider on the default - harness (on any enabled harness when none is default), - a project with an active key, and a first Session, whose action opens the call - samples of the newest active project, preferring one with an active key. - Completion comes from reads the - console already makes. It can be hidden; Show Getting started in the sidebar - opens it again, and it ends with a brief "You're set". While it is open, Add node - ends with the next step once its node is ready: the default model provider while that is to - do, otherwise back to the checklist. The optional - three-chapter tour of the console (Monitor, Resources, Platform) opens from it, - on the sign-in stage. -- Terminology: API terms stay in English in the Chinese UI (Agent, Session, Turn, - Skill, Vault, Credential, API key). The sign-in credential is the Core key - ("Core Key"); keys issued in a project for applications are project API keys - ("项目 API Key"). Provider readiness is "Provider not ready / 提供方未就绪"; - revoked credentials and keys use "Revoked / 已撤销". A default model provider is - a service address and write-only key; the application's Agent `model` chooses - the provider-supported model name. A sandbox is Core-managed compute; Runtime - names Core's execution observations, and Environment is the Session's API - execution environment. These are distinct counts and resources, not synonyms. +- **Monitor**: Overview (service status, running Sessions, sandbox slots, Sessions needing attention, 24-hour Session activity, the topology of Core and its nodes 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), 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). Agent metrics' By Agent table opens an Agent's page and, from its failed Turns, its 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. +- **Platform**: Projects and keys (projects, their assets and usage, named keys, write history), Nodes (the node list, capacity, host figures, allocations and individual node operations). Add node asks for limits before issuing its one-time command; installers use Core's public URL and require supported node artifacts. Removal offers the host's uninstall command. System owns installation facts, the Domain and HTTPS secondary page, each harness's default model configuration, startup settings, and a link to the Sandbox configuration secondary page. That page owns setup, resource edits, rollout details and reset. Setup selects a backend, size and Runtime, then asks for a deliberate save; own-machine setup continues to Add node. +- A node whose provider is not ready names the reason (Docker unreachable, no Docker limits, missing Runtime image, no KVM, missing microsandbox components, a host too small) and its fix in the help tip beside its status, wherever that status shows. +- A node enrolled with an earlier Core address gets no new sandboxes, so on the Nodes list and its page its status is Old address, with "Remove and add again", never Available. +- **Sandbox reset** is an explicit administrator operation in System → Sandbox configuration. Auto clear is the default, with a one-hour deadline (5 minutes–24 hours); Force clear requires destructive confirmation. Reset stops new hosted Session admission, clears idle, suspended and pending hosted work, and waits for busy Turns and file writes until Core forces the remaining work. It does not affect self-hosted execution. Histories and persisted Files/Artifacts remain; archived Sessions cannot resume, and unpersisted workspace contents may be lost. Cancel stops further clearing without undoing archives. Core alone reports progress and completion, including resources blocked on named offline nodes; force does not bypass their cleanup. Completion clears the backend configuration and retires old nodes/enrollment credentials. A new configuration is then a separate deliberate save. +- **Online sandbox configuration** changes the same backend's resources, Runtime or E2B template without retiring existing nodes or changing existing Sessions' resource ownership. New placement follows Core's qualified capacity; saving a target does not promise immediate placement on it. Configuration rollout shows Core's target preparation and retained previous-generation sandbox count. A settled rollout can still have failed, update-required or unknown nodes and old resources. An offline node stays offline even when it has a recorded serving generation. Node and allocation detail distinguish the serving pin, target preparation and each resource's configuration generation. +- **E2B credential replacement** uses the same configuration form. Setup requires a key; leaving it blank during an update keeps the saved key. An explicit key, even the same value, is verified as a replacement and advances the target generation after successful verification. Another backend or E2B team requires a deliberate reset. A rejected or uncertain replacement never clears the committed configuration or replays the write. +- **E2B deployments** have no machines: Nodes offers a link to System's sandbox configuration. Overview and Sandbox metrics show the sandboxes Core holds in E2B's cloud (running, starting, size, template build) instead of node capacity, with no node column or Add node action; a sandbox's dialog adds its disk use. +- **microsandbox** suspends idle sandboxes into snapshots, so its nodes show how many sleep (Core's retained minus active) on the Nodes list, a node's page, Sandbox metrics and Overview; a node's allocations show how long each has been suspended and about when Core reclaims it. Docker never suspends and shows none of it. +- **Getting started**: signing in opens the console on the Overview; nothing is forced first. While a step is to do, a Getting started checklist on the Overview shows four steps, in any order, each with its state and one action: sandboxes ready (a saved deployment and a node online and ready, or a saved E2B deployment whose template build is not reported as not ready), a default model provider on the default harness (on any enabled harness when none is default), a project with an active key, and a first Session, whose action opens the call samples of the newest active project, preferring one with an active key. Completion comes from reads the console already makes. It can be hidden; Show Getting started in the sidebar opens it again, and it ends with a brief "You're set". While it is open, Add node ends with the next step once its node is ready: the default model provider while that is to do, otherwise back to the checklist. The optional three-chapter tour of the console (Monitor, Resources, Platform) opens from it, on the sign-in stage. +- Terminology: API terms stay in English in the Chinese UI (Agent, Session, Turn, Skill, Vault, Credential, API key). The sign-in credential is the Core key ("Core Key"); keys issued in a project for applications are project API keys ("项目 API Key"). Provider readiness is "Provider not ready / 提供方未就绪"; revoked credentials and keys use "Revoked / 已撤销". A default model provider is a service address and write-only key; the application's Agent `model` chooses the provider-supported model name. A sandbox is Core-managed compute; Runtime names Core's execution observations, and Environment is the Session's API execution environment. These are distinct counts and resources, not synonyms. ## Capabilities and Constraints -- **Projects and keys.** A project owns an isolated set of assets shared by all of - its named API keys; projects do not see each other's assets. Issuing or revoking - a key never touches assets. Archiving a project revokes every key and keeps its - assets viewable and deletable; it can't be undone, so its confirmation says so, - counts the active keys it revokes and, when there are any, asks for the - project's name. Key plaintext is shown once, at issuance, and never - stored by the console. Beside it, and without the key on an active project's - page, the console tells developers to set `OPENAI_BASE_URL` (the installation's - API base URL) and `OPENAI_API_KEY` (a key of the project), with curl and Python - samples that list Agents and create a Session. -- **One home for each setting.** System owns sandbox configuration through its - Sandbox configuration secondary page. This is the only place to set up, - update, reset or inspect deployment rollout. Nodes owns the node list and - individual node operations. Overview and metrics link to these owners instead - of repeating their configuration or rollout panels. Resource editing uses a - dialog; rollout counts and generations appear in its details dialog. -- **Web API only.** Every read and write goes through `/core/v1/**`. The console - holds no API key and sends nothing to `/v1`. -- **No asset writes except delete.** Assets are created and changed only by - a project's keys through the Agents API. The console does not create or edit - Agents or Templates, upload Skills or Files, create or replace Credentials, start - Sessions, send input or cancel work. Deletion follows the public deletion rules; - a busy Session is not deletable and the console never cancels work to make it so. -- **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 an - administrator copied in an earlier release shows as Admin copy and an asset - without a record 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 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. -- **Executor credentials.** Only Core issues the credential file a self-hosted - executor needs, 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. -- **Connect a host.** Native Linux/macOS and PowerShell installation instructions - depend on the Session's remote URL, Environment ID and workspace, not console - installer flags or served Python assets. Users privately save the issued JSON, - obtain a matching native distribution through the linked guide, and run the - interactive install command from its root. Installation asks for the credential - file path and does not automatically start the daemon. Run the installed binary - in the installation's bin directory with `start`. No model readiness is implied. - Credential rotation requires stopping the installed daemon, replacing the - configured file and starting that same daemon again. A disconnected daemon may - still be running; `start` alone does not replace it. A new key cannot reconnect an already-bound Environment. - Accept wss or loopback ws; withhold commands for missing or invalid facts. -- **Typed write errors.** Known Core codes use shared bilingual copy and safe - typed details. Exact Core field paths attach definite refusals to the relevant - input. Unknown codes retain Core's fallback message; uncertain write outcomes - stay form-level and are never retried automatically. -- **Read failures.** Overview and Session log distinguish unavailable reads from - successful empty results. Failed reads have a visible retry; retained or partial - data says it may be incomplete or out of date, and Session filter totals stay - missing while any required read has failed. Only successful empty reads show zero. -- **Local-only address.** Overview, Nodes and System warn when Core reports - `local_only`, with the configuration path and apply command Core supplies as - copyable instructions. Without a configuration snapshot they state what is - missing. Add node is unavailable with a reason; Getting started keeps the first - step to do until the public address is fixed. An unread installation address - cannot complete that step, and a failed read offers Retry. -- **Figures.** Project, Agent and key usage comes from Core's summary; Agent run, - tool and activity figures are still assembled in the browser from bounded reads - and state their coverage. Metrics that need new Core endpoints are recorded as - backend requirements, not simulated. Usage is cumulative per Session and is not - billing. +- **Projects and keys.** A project owns an isolated set of assets shared by all of its named API keys; projects do not see each other's assets. Issuing or revoking a key never touches assets. Archiving a project revokes every key and keeps its assets viewable and deletable; it can't be undone, so its confirmation says so, counts the active keys it revokes and, when there are any, asks for the project's name. Key plaintext is shown once, at issuance, and never stored by the console. Beside it, and without the key on an active project's page, the console tells developers to set `OPENAI_BASE_URL` (the installation's API base URL) and `OPENAI_API_KEY` (a key of the project), with curl and Python samples that list Agents and create a Session. +- **One home for each setting.** System owns sandbox configuration through its Sandbox configuration secondary page. This is the only place to set up, update, reset or inspect deployment rollout. Nodes owns the node list and individual node operations. Overview and metrics link to these owners instead of repeating their configuration or rollout panels. Resource editing uses a dialog; rollout counts and generations appear in its details dialog. +- **Web API only.** Every read and write goes through `/core/v1/**`. The console holds no API key and sends nothing to `/v1`. +- **No asset writes except delete.** Assets are created and changed only by a project's keys through the Agents API. The console does not create or edit Agents or Templates, upload Skills or Files, create or replace Credentials, start Sessions, send input or cancel work. Deletion follows the public deletion rules; a busy Session is not deletable and the console never cancels work to make it so. +- **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 Core records as an administrator copy (`admin_copy`) shows as Admin copy and an asset without a record 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 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. +- **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. +- **Connect a host.** The Linux/macOS and PowerShell commands come from Core's installation read for the Session's environment; the console shows them as they are, with a link to the native installation guide, and never builds one itself. A command downloads the matching installer, installs the chosen Harnesses, starts the daemon and checks its connection. Its authorization expires after 30 minutes; the console reads a fresh one every 20 minutes, and says the command is unavailable when Core has none. No model readiness is implied. Rotating a credential requires stopping the installed daemon, replacing the configured file and starting that same daemon again. A disconnected daemon may still be running; `start` alone does not replace it. +- **Typed write errors.** Known Core codes use shared bilingual copy and safe typed details. Exact Core field paths attach definite refusals to the relevant input. Unknown codes retain Core's fallback message; uncertain write outcomes stay form-level and are never retried automatically. +- **Read failures.** Overview and Session log distinguish unavailable reads from successful empty results. Failed reads have a visible retry; retained or partial data says it may be incomplete or out of date, and Session filter totals stay missing while any required read has failed. Only successful empty reads show zero. +- **Local-only address.** Overview, Nodes and System warn when Core reports `local_only`, with the configuration path and apply command Core supplies as copyable instructions. Without a configuration snapshot they state what is missing. Add node is unavailable with a reason; Getting started keeps the first step to do until the public address is fixed. An unread installation address cannot complete that step, and a failed read offers Retry. +- **Figures.** Project, Agent and key usage comes from Core's summary; Agent run, tool and activity figures are still assembled in the browser from bounded reads and state their coverage. Metrics that would need new Core endpoints are not simulated. Usage is cumulative per Session and is not billing. - Runtime CPU and memory exist only for Core-managed hosted sandboxes. -- Preserve workflow safety: confirmed deletion, no automatic retry of uncertain - writes, no secrets in browser storage. +- Preserve workflow safety: confirmed deletion, no automatic retry of uncertain writes, no secrets in browser storage. ## Brand Commitments - Product name: OpenAgentCore. OpenAgentCore mark assets in `apps/web/public/`. -- Keep the OpenAgentCore visual identity shared with the public landing (`site/`): - neutral grays and a quiet indigo accent. The console uses Inter and Geist Mono - on Beautiful UI's foundation tokens and structure; `DESIGN.md` records the system. - -Development and build settings use `OAC_WEB_*`. The Vite proxy uses the -server-only `OAC_WEB_DEV_PROXY_TARGET`, `OAC_WEB_DEV_PROXY_TOKEN` and -`OAC_WEB_DEV_PROXY_TOKEN_FILE`, defaulting to `~/.oac/dev/web-token` for its private -token file. Retired `AGENTS_CORE_WEB_*` and the three `AGENTS_API_PROXY_*` -settings stop startup or build with replacement names, without logging values -or falling back to the old token path. Browser definitions contain only the -existing capability flags and validated, non-secret Docker guide profiles. +- Keep the OpenAgentCore visual identity shared with the public landing (`site/`): neutral grays and a quiet indigo accent. The console uses Inter and Geist Mono on Beautiful UI's foundation tokens and structure; `DESIGN.md` records the system. ## Evidence on Hand -- Browser acceptance in `apps/web/e2e/`: one test per acceptance behavior against - `fixture-console.mjs`, a synthetic console service with deterministic data. +- Browser acceptance in `apps/web/e2e/`: one test per acceptance behavior against `fixture-console.mjs`, a synthetic console service with deterministic data. - No customer data, benchmarks or usage claims exist; do not fabricate them. ## Product Principles 1. Operations first: health, capacity, usage and failures lead. -2. Manage, don't operate: the administrator views, deletes and manages projects - and keys; assets belong to the projects' keys. +2. Manage, don't operate: the administrator views, deletes and manages projects and keys; assets belong to the projects' keys. 3. Report evidence, not assumptions: missing data stays visibly missing. -4. One page grammar everywhere: the same header, toolbar, tables, metrics and - states on every screen. -5. Projects are the unit: every asset shows the project that owns it and the key - that created it. +4. One page grammar everywhere: the same header, toolbar, tables, metrics and states on every screen. +5. Projects are the unit: every asset shows the project that owns it and the key that created it. 6. Deployment-level truth (nodes, configuration) is distinct from project assets. ## Accessibility & Inclusion -Keyboard navigation with visible focus, reduced-motion support, and bilingual -Chinese/English copy through the existing i18n modules. +Keyboard navigation with visible focus, reduced-motion support, and bilingual Chinese/English copy through the existing i18n modules. diff --git a/apps/web/README.md b/apps/web/README.md index ffd3edb82..e91129cef 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,190 +1,46 @@ -# Core administrator Web frontend - -This package contains the React application served by `services/core-console`: -the administrator console for monitoring Core, inspecting Project resources, and -managing Projects, keys and sandbox nodes. Its design system is -described in [DESIGN.md](DESIGN.md) and its product scope in [PRODUCT.md](PRODUCT.md). - -## Integration contract - -Browser management requests use same-origin `/core/v1` through `AdminClient`, -`CoreMetricsClient` (`/core/v1/metrics`) and the sandbox management client -(`/core/v1/sandbox`). -The administrator signs in with the deployment's Core key; the console keeps the key -server-side and gives the browser only a session cookie. -Applications use their own Project keys directly against Core's public `/v1` API. -The production console returns 404 for `/v1`, even with an explicit Bearer token. - -Management covers Projects and keys, resource inspection and permitted deletion, -monitoring and audit. It does not create or edit arbitrary -application resources or execute Sessions. Do not add application keys or deployment -credentials to browser configuration, `VITE_*`, storage or logs. - -See the [Core Web guide](../../docs/web/README.md), -[connection contract](../../docs/web/core-connection.md), -[frontend handoff](../../docs/web/roadmap.md) and -[administrator API](../../contracts/agents-api/admin-api.md). - -The Core Web is an administrator console. Web calls only `/core/v1`, with the Core -key held on its server, and never `/v1` or `/api/v1`. Applications use an API key issued inside a Project. One Project owns one execution -tenant and principal; all its keys share assets and permissions while writes retain -individual key provenance. Projects and keys are database-owned, with no static -business keys or configuration synchronization. Revocation affects one key; -archiving a Project revokes all its keys, retaining assets and admitted execution. -Do not add Core users, roles, memberships or cross-Project sharing. Management -provides safe reads, public deletion preconditions, explicit hosted Session archive, -Project and key operations and credential issuance; it cannot copy, execute or edit -arbitrary assets. -Keep administrator target scope separate from caller principals. See -[design principles](../../docs/design-principles.md) and the -[administrator contract](../../contracts/agents-api/admin-api.md). - -### Console structure - -Core Web leads with operations: Monitor (Overview, Core metrics, Agent metrics, -Sandbox metrics, Session log), Resources and Platform. Pages use the shared -components in `apps/web/src/components` and the tokens in `apps/web/src/styles`, -described in `apps/web/DESIGN.md`. Keep explanations behind help tips, but keep -errors, warnings and safety notices visible. Browser-derived metrics state their -coverage, keep missing values missing, bound their fan-out and time, report a failed -read as failed and never imply deployment-wide or billing totals. -Sandbox deployment setup, configuration, reset and progress belong to the System -secondary page (`#system?id=sandbox`). Nodes owns node management; Overview and -metrics pages link to these owners instead of repeating their controls or details. -Keep uncommon resource edits and rollout details in dialogs, and avoid repeating -the same information within or across pages. Core responses remain the source of -truth for deployment and connection state. - -### Console server and sign-in - -`services/core-console` serves the production Web build and, after console login -and same-origin checks, forwards every `/core/v1` request with the Core key; Core -decides whether the route exists. It requires the private Core key file named by -`OAC_WEB_CORE_KEY_FILE` and holds no project caller credential. Every `/v1` -and `/api/v1` request returns 404, including explicit Bearer and WebSocket -requests; Web forwards no node or daemon transport. The installer mounts only the -Core key into Web and only its digest (`OAC_CORE_KEY_DIGESTS_FILE`) into -Core. The browser receives safe configuration, never that key. The deployment's -TLS reverse proxy routes `/v1` (applications) and `/api/v1` (nodes and Runtime -daemons, with their own credentials) directly to Core and everything else, -including `/core/v1`, to Web. Operator scripts call `/core/v1` on Core's loopback -port. -Nodes and Core come from one distribution. Runtime generation rollout has a -separate resource lifecycle; see the -[installation version policy](../../docs/getting-started/operations.md#installation-version-policy). - -The Web manager offers no manual Core key entry outside sign-in, and Web refuses -to start without its Core key file. It holds no Project API key and never calls -`/v1`. -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. The sandbox manager centers node readiness and capacity in a desktop topology, -with Core surrounded by actual node buttons. Connection animation represents -liveness only, never invented traffic or work; offline/stale connections are -static and reduced-motion preferences disable decorative animation. Node selection -reveals inspection details. Installation identifiers, provider metadata and -allocation records are secondary content. Node enrollment is an explicit Add node action in a focused -dialog, using the deployment's `core_url` (the installation public URL). -Do not expose routine network wiring or manual runtime setup as the primary flow. -Generate a one-time command only on user intent, never retry enrollment writes -automatically, and discard credentials and late responses when the dialog closes -or the Core connection changes. Core reports the command's `enrollment_id` on the -node it registered (null for nodes enrolled before Core recorded it), and Web follows -the added node by an exact match on it; an existing node reconnecting is not a new -enrollment. -The command verifies the installer checksum before execution, retains normal TLS -verification, and passes the enrollment credential only to the installer process, -on standard input. - - -The `/core/v1` proxy retains fixed-origin, cross-site, safe-path, redirect and Upgrade -restrictions through the standard Go reverse proxy with streaming/cancellation; -literal or encoded dot segments can never move a request out of `/core/v1`. -During managed HTTP bootstrap, the console accepts a literal IP host and requires -writes to match that request's origin; domain hosts still require the configured -origin. -The console implements no product identity, resource semantics, Runtime discovery -or execution loop. Signing in with the Core key grants the complete console -surface; do not introduce Web accounts, roles, invitations or per-project Web -identities. Agent API caller keys remain independent of the Core key and cookie. - -Web signs in only with the Core key (`POST /console/auth/login` with -`{"core_key":"…"}`), compared in constant time with the console's configured key -and never logged or echoed. There are no accounts, passwords, first-run setup or -Basic authentication. The retired authentication-mode, state-directory, -password-file and admin-token-file settings fail startup; their names are listed -in [`config.go`](../../services/core-console/config.go). Cookie sessions are in memory, bounded, HttpOnly, SameSite Strict and -Secure for HTTPS origins; a restart or Core key rotation requires sign-in again. -Unauthenticated access is limited to the static login UI, finite console -authentication routes and the static node installation payload. Sign-in uses -same-origin JSON POSTs with bounded bodies and bounded concurrent work. Only -failed attempts are rate limited, so the correct key always signs in; Web and the -installer therefore require Core keys of at least 32 characters. See the -[Core key operations guide](../../docs/getting-started/operations.md#core-key). - -Projects and application API keys live in Core PostgreSQL. Project creation owns -its scope and shared principal; key issuance, revocation and Project archive share -a transaction with audit. Issuance stores only a digest and metadata and returns -plaintext once. Keys cannot be read back or reset in place; rotate by issuing a -new key in the same Project and revoking the old key. Authentication checks the -key and Project on every request, without a credential cache, and fails closed on -database errors. Deployment credentials cannot authenticate to the public API. -Configuration defines no Projects or business API keys. Fresh installation starts -with no Projects; an administrator creates a Project and then issues a key. - -Administrator onboarding covers console login, Project creation, key issuance and -optional node enrollment. Model execution belongs in an external API example using an issued -key. Keep secrets out of browser persistence, generated examples and URLs. Observe -confirmed resources through the management API; do not infer Agent-to-node ownership -or execution readiness from a host connection. Preserve keyboard focus, reduced -motion and the existing node enrollment/topology contract. -The console has neither KVM nor Docker authority; its static root contains no -secrets. A default container installation uses a managed gateway with its Web -bootstrap port bound to `0.0.0.0`, so operators can sign in through the server's -IP address and configure a domain under System → Domain and HTTPS. Core's direct -host port remains on loopback and PostgreSQL stays on the private container -network. After HTTPS setup, the gateway routes application and node traffic to -Core and redirects the bootstrap Web entry to the configured HTTPS address. -Native, Core-only, Web-only and explicit external-ingress installations keep an -operator-managed HTTPS boundary. Web-only mode can connect to a loopback existing -Core on the same Linux host or a remote HTTPS Core. - -## Local checks - -From the repository root, with Node 22 and pnpm 10.30.3: +# Web console package + +`apps/web` (`@agents-core-web/web`) is the React application of the OpenAgentCore administrator console. The [console server](../../docs/web/console-server.md) serves its production build. [DESIGN.md](DESIGN.md) records the visual system and [PRODUCT.md](PRODUCT.md) the product scope and behavior; the [operator guide](../../docs/web/README.md) describes the console for administrators. + +## Rules for console code + +- Call Core only through `AdminClient`, `SandboxAdminClient` and `CoreMetricsClient` from [`packages/agents-client`](../../packages/agents-client/README.md). The browser calls same-origin `/console/*` and `/core/v1/*` routes and never `/v1` or `/api/v1`. [Console API usage](../../docs/web/console-api-usage.md) lists each page's routes and read bounds; update it with any change to them. +- Keep API keys, the Core key and provider credentials out of `VITE_*` variables, browser storage, URLs, logs and source files. +- Build pages from the shared components in `src/components` and the design tokens: the Beautiful UI base tokens in `src/app/beautifui/foundation.css` and the console's own in `src/styles`, as [DESIGN.md](DESIGN.md) describes. +- Put copy in the i18n resources; see [Web internationalization](src/i18n/README.md). + +## Run the console locally + +Set up the checkout as described in the [development guide](../../docs/development.md), then start the fixture console and the development server in separate terminals from the repository root: ```sh -pnpm --filter @agents-core-web/web typecheck -pnpm --filter @agents-core-web/web test -pnpm --filter @agents-core-web/web build +node apps/web/e2e/fixture-console.mjs +``` + +```sh +OAC_WEB_DEV_PROXY_TARGET=http://127.0.0.1:18092 pnpm dev:web ``` -These checks cover the application source. Browser acceptance through -`services/core-console` is tracked in the [frontend roadmap](../../docs/web/roadmap.md). -Required repository checks are documented in [CONTRIBUTING.md](../../CONTRIBUTING.md). +Open `http://127.0.0.1:4173` and sign in with the fixture-only key `fixture-core-key-3f9a2c71`. + +`pnpm dev:web` runs Vite on `127.0.0.1:4173` and proxies `/console`, `/node-install` and `/core/v1` to `OAC_WEB_DEV_PROXY_TARGET` (default `http://127.0.0.1:8091`). Vite reads the setting from the environment or the repository's `.env` file; it never reaches browser code. The target must serve the console routes. The development server also forwards `/v1` to the same target for local tooling such as `scripts/core-doctor.mjs`, adding a bearer token from `OAC_WEB_DEV_PROXY_TOKEN` or from the private file `OAC_WEB_DEV_PROXY_TOKEN_FILE` (default `~/.oac/dev/web-token`, used when it exists); the console itself never calls `/v1`. `apps/web/e2e/fixture-console.mjs` is a synthetic console service with deterministic data; `AGENTS_FIXTURE_PORT` changes its port (default 18092). -### Domain setup +## Checks -System → Domain and HTTPS uses the authenticated, same-origin -`/console/installation/domain` installation manager. It is not a Core API route. -The form accepts one hostname, submits once, and polls backend-reported status. -A changed public address requires explicit confirmation when requested by the -manager. Failed or interrupted writes are not retried automatically; refresh -status before retrying. During a console restart the new HTTPS address remains -available as a sign-in link, including when the old session ends. Only the -manager's `ready` state confirms HTTPS; the browser does not probe another origin. +From the repository root: + +```sh +pnpm --filter @agents-core-web/web typecheck +pnpm --filter @agents-core-web/web test +pnpm --filter @agents-core-web/web build +pnpm test:web:acceptance +``` -The Web bootstrap listener and Core's machine-facing public address are separate. -A `local_only` Core address requires HTTPS setup for external clients; it does -not mean the Web console is restricted to the local machine. Domain settings -belong to their System subpage, not the read-only startup settings table. +`pnpm test:web:acceptance` runs the Playwright tests in `apps/web/e2e` in Chrome against the fixture console. After each test, every spec except `domain.spec.ts` checks that the browser sent nothing to `/v1` and no `Authorization` header. The tests do not exercise `services/core-console` or a real Core; the console server has its own Go tests. `make check-web` runs all of these; [CONTRIBUTING.md](../../CONTRIBUTING.md) lists the repository's required checks. -### README screenshots +## README screenshots -The browser fixture has an opt-in scene for Overview and Agent metrics, including -five available nodes. From the repository root, start these in separate terminals: +The fixture has an opt-in scene for Overview and Agent metrics, including five available nodes. From the repository root, start these in separate terminals: ```sh OAC_WEB_SCREENSHOT_DEMO=1 AGENTS_FIXTURE_PORT=18394 node apps/web/e2e/fixture-console.mjs @@ -194,12 +50,4 @@ OAC_WEB_SCREENSHOT_DEMO=1 AGENTS_FIXTURE_PORT=18394 node apps/web/e2e/fixture-co OAC_WEB_DEV_PROXY_TARGET=http://127.0.0.1:18394 pnpm --filter @agents-core-web/web exec vite --host 127.0.0.1 --mode test --port 4394 ``` -Open `http://127.0.0.1:4394` in Chrome and sign in with the fixture-only key -`fixture-core-key-3f9a2c71`. Capture Overview and Agent metrics in light mode, -once in English and once in Chinese using the console language menu. Check that -all five nodes load and metrics have no partial-data warning before capturing. -For a remote preview, forward port 4394 over SSH and capture in local Chrome. -Keep the original resolution, crop browser chrome and add a plain macOS-style -window bar. The four WebP images in `docs/assets/console-*.webp` are linked by the matching -README and included in the distribution manifest. Normal acceptance data and -production builds do not enable this scene. +Open `http://127.0.0.1:4394` in Chrome and sign in with the fixture key `fixture-core-key-3f9a2c71`. Capture Overview and Agent metrics in light mode, once in English and once in Chinese using the console language menu. Check that all five nodes load and metrics have no partial-data warning before capturing. For a remote preview, forward port 4394 over SSH and capture in local Chrome. Keep the original resolution, crop browser chrome and add a plain macOS-style window bar. Save the four images as `docs/assets/console-*.webp`; the READMEs link them and the distribution manifest includes them. Normal acceptance data and production builds do not enable this scene. diff --git a/apps/web/src/components/magicui/README.md b/apps/web/src/components/magicui/README.md index 325aaa17c..9818c33ee 100644 --- a/apps/web/src/components/magicui/README.md +++ b/apps/web/src/components/magicui/README.md @@ -1,14 +1,9 @@ # Magic UI components -Copied from the Magic UI registry (, MIT License, -Copyright (c) Magic UI) and used by the onboarding stage. Their animation -keyframes live in `src/styles/magicui-theme.css`. +Copied from the Magic UI registry (, MIT License, Copyright (c) Magic UI). The onboarding stage uses the flickering grid, light rays, border beam and orbiting circles. Their animation keyframes live in `src/styles/magicui-theme.css`. Local changes: -- `motion.*` components are Motion's lazy `m.*` components, as the console - renders inside a strict `LazyMotion`. -- `flickering-grid.tsx` draws one still frame with reduced motion, redrawn on - resize, instead of flickering. -- `orbiting-circles.tsx` starts each orbit at its wall-clock phase, so a - remounted orbit continues instead of jumping, and ignores the unused `delay`. +- `motion.*` components are Motion's lazy `m.*` components, as the console renders inside a strict `LazyMotion`. +- `flickering-grid.tsx` draws one still frame with reduced motion, redrawn on resize, instead of flickering. +- `orbiting-circles.tsx` starts each orbit at its wall-clock phase, so a remounted orbit continues instead of jumping, and ignores the unused `delay`. diff --git a/apps/web/src/features/sandbox/standard-sizes.md b/apps/web/src/features/sandbox/standard-sizes.md index 830f6fe28..24f1d1f80 100644 --- a/apps/web/src/features/sandbox/standard-sizes.md +++ b/apps/web/src/features/sandbox/standard-sizes.md @@ -1,8 +1,6 @@ # Standard sandbox sizes -`standard-sizes.json` is the single source of truth for the default Standard -size of each sandbox on a self-hosted backend. The console setup wizard offers it -as Standard and derives Small (half) and Large (double) from it. +`standard-sizes.json` is the single source of truth for the default Standard size of each sandbox on a self-hosted backend. The console setup wizard offers it as Standard and derives Small (half) and Large (double) from it. ## Structure and units @@ -18,22 +16,14 @@ as Standard and derives Small (half) and Large (double) from it. - `docker` has exactly `cpus` and `memory_mib`; it has no disk fields. - `microsandbox` has exactly all four fields. -The values must stay within the bounds that Core and `validSandboxResources` -accept. +The values must stay within the bounds that Core and `validSandboxResources` accept. ## Readers -- The Web setup wizard, through `defaultSandboxResources` in - `deployment-specification.ts`. -- The release bundle: `build-core-distribution.sh` copies this file to - `/standard-sizes.json`. -- The Core installer: `install.sh --sandbox` reads the bundled copy to create - the default deployment. +- The Web setup wizard, through `defaultSandboxResources` in `deployment-specification.ts`. +- The release bundle: `scripts/build-core-distribution.sh` copies this file to `/standard-sizes.json`. +- The Core installer: `deploy/install/sandbox_setup.py` reads the bundled copy when `install.sh` saves the initial Docker or microsandbox deployment (`--sandbox`, microsandbox by default). ## Contract -The keys and structure are a contract with the Core installer. Changing a value -is fine. Renaming, removing or adding keys, or restructuring the file, must be -coordinated with the backend first, because `install.sh` parses the bundled copy. -`deployment-specification.test.ts` pins the structure so that an accidental -change fails. +The keys and structure are a contract with the Core installer. Changing a value is fine. Renaming, removing or adding keys, or restructuring the file, needs a matching change to `deploy/install/sandbox_setup.py`, which rejects a bundled copy whose fields differ. `deployment-specification.test.ts` pins the structure so that an accidental change fails. diff --git a/apps/web/src/i18n/README.md b/apps/web/src/i18n/README.md index c17a1e9e2..4fba2e457 100644 --- a/apps/web/src/i18n/README.md +++ b/apps/web/src/i18n/README.md @@ -1,23 +1,20 @@ # Web internationalization -The Core Web uses `i18next` and `react-i18next`. English is the fallback language -and the source for TypeScript key inference. Simplified Chinese uses the BCP 47 -tag `zh-CN`. +The console uses `i18next` and `react-i18next`. English is the fallback language and the source for TypeScript key inference. Simplified Chinese uses the BCP 47 tag `zh-CN`. -Translations are split by feature namespace. Keep reusable actions in `common`, -shell navigation in `navigation`, connection workflow copy in `connection`, and -page-level copy in `pages`. Add a dedicated namespace when a feature grows beyond -page-level labels; do not grow one application-wide translation object. +Translations are split by namespace, registered in `resources.ts`: + +- `locales/en/*.ts` and `locales/zh-CN/*.ts` hold one file per feature namespace: `common` (reusable actions and labels, with the Core error messages of `core-errors.ts` nested inside it), `navigation` (the shell), `pages`, `agents`, `templates`, `vaults`, `files`, `skills`, `keys`, `sessions`, `diagnostics`, `dashboard`, `overview`, `metrics`, `system`, `sandbox-navigation` (registered as `sandboxNavigation`) and `onboarding`. +- The `sandbox` and `firstRun` namespaces come from `src/lib/locale-strings.ts` and `src/lib/console-auth-strings.ts`. Their keys are the English text and their values the Chinese translation. Sandbox status and node diagnostic formatting in `src/lib/sandbox-labels.ts` and `src/lib/sandbox-diagnostic.ts` reads the same strings. + +Add a namespace when a feature grows beyond page-level labels; do not grow one application-wide translation object. When adding or changing copy: 1. Add the English key and the `zh-CN` translation in matching namespace files. 2. Consume the key with `useTranslation(namespace)` in React components. 3. Use interpolation for dynamic values instead of concatenating translated text. -4. Keep API values, identifiers, paths, commands, and user-provided content out of - translation resources. -5. Run the Web tests. The resource parity test rejects missing keys. +4. Keep API values, identifiers, paths, commands and user-provided content out of translation resources. +5. Run the Web tests. The resource parity test rejects keys missing from either language. -The initial language follows the browser preference (`zh*` selects `zh-CN`) unless -the user has made an explicit choice. Explicit choices are stored in local storage; -the application still works when browser storage is unavailable. +The initial language follows the browser preference (`zh*` selects `zh-CN`) unless the user has chosen a language in the console menu. The choice is stored in local storage; the console still works when browser storage is unavailable. diff --git a/contracts/agents-api/message-input.md b/contracts/agents-api/message-input.md index 076daba7c..fd34a9f95 100644 --- a/contracts/agents-api/message-input.md +++ b/contracts/agents-api/message-input.md @@ -48,7 +48,7 @@ as observed officially. The shared daemon validator, daemon dispatch and the TypeScript client apply the same rule. Core Web keeps local UI rules: its composer trims leading and trailing whitespace from every message it sends and does not send blank text, and its Start Session form omits whitespace-only simple -text ([Web architecture](../../docs/web/architecture.md)). Other clients' text is +text ([console API usage](../../docs/web/console-api-usage.md#not-consumed)). Other clients' text is never trimmed. Harness profiles declare whether whitespace-only text is qualified, through the diff --git a/contracts/agents-api/model-execution.md b/contracts/agents-api/model-execution.md index 0df238d43..09394ddc1 100644 --- a/contracts/agents-api/model-execution.md +++ b/contracts/agents-api/model-execution.md @@ -100,7 +100,7 @@ historical rows keep their documented retry limitations; this change does not rewrite them. Omitted and explicit fields retain the existing local intent-hash semantics rather than promising upstream equivalence. -See the [TypeScript client example](../../packages/agents-client/saved-agent-defaults.md). +See the [TypeScript client example](../../packages/agents-client/README.md#saved-agent-and-deployment-defaults). ## Session override example diff --git a/contracts/agents-api/subagents.md b/contracts/agents-api/subagents.md index 65a5e0923..6c4e84ef4 100644 --- a/contracts/agents-api/subagents.md +++ b/contracts/agents-api/subagents.md @@ -103,7 +103,7 @@ cancellation uses a protected immutable effect receipt because native abort can leave no terminal record. The receipt preserves the confirmed effect time across reads without rewriting native history. This profile has no qualified close operation, and completed or cancelled children remain active. See the -[adapter contract](../../packages/claude-sdk-adapter/SUBAGENTS.md) for restrictions. +[adapter contract](../../packages/claude-sdk-adapter/README.md#subagents) for restrictions. MiniMax's fixed ACP supplies native delegation operations. Its Session-private SQLite records supply original child identity, accepted inputs, terminal times diff --git a/docs/api/README.md b/docs/api/README.md index c99f90beb..18b2a89ea 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -133,4 +133,4 @@ owns expiry, retry and credential ownership; the The console-local `GET`/`POST /console/installation/domain` surface uses the signed-in browser session and same-origin checks. It delegates only domain setup to the installer, with the server-held Core key over a private Unix socket; it is not part -of the Agents API or Core management API. See [Web request boundaries](../web/architecture.md#request-boundaries). +of the Agents API or Core management API. See [console domain setup](../web/console-server.md#domain-setup). diff --git a/docs/architecture.md b/docs/architecture.md index c39096673..83355612d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -90,4 +90,4 @@ The `none` profile shares the execution protocol without workspace preparation. - **Isolation belongs to the outer Environment.** The daemon is not a sandbox ([Runtime and outer isolation](design-principles.md#runtime-and-outer-isolation)). - **Execution and compute have separate lifetimes.** Closing an executor does not release its allocation, destroy its Environment or delete its workspace. Reclamation is an explicit Sandbox Provider operation. - **Model keys stay with the compute that owns them.** A self-hosted Session brings its own model provider ([why](user-guide.md#which-model-provider-a-session-uses)). -- **Core Web is an administrator console.** It calls only `/core/v1` and cannot start Sessions or send input ([Web architecture](web/architecture.md)). +- **Core Web is an administrator console.** It calls only `/core/v1` and cannot start Sessions or send input ([console API usage](web/console-api-usage.md#not-consumed)). diff --git a/docs/configuration.md b/docs/configuration.md index c5c5fe801..d56c56a2c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -155,4 +155,4 @@ Core reads only its environment. The installer renders `generated/core.env` from Core logs the file paths it loads, never environment values or file contents. -A Web you run without the installer reads the variables in [Connecting the administrator console to Core](web/core-connection.md#server-configuration-and-login), plus `OAC_WEB_NODE_PAYLOAD_DIR`: the absolute path of the matched distribution's node payload (the installer's `node-payload/`). Without it, Add node is unavailable. +A Web you run without the installer reads the variables in [Console server settings](web/console-server.md#settings), plus `OAC_WEB_NODE_PAYLOAD_DIR`: the absolute path of the matched distribution's node payload (the installer's `node-payload/`). Without it, Add node is unavailable. diff --git a/docs/development.md b/docs/development.md index d793fcdc7..3f209c139 100644 --- a/docs/development.md +++ b/docs/development.md @@ -90,7 +90,7 @@ site; `pnpm dev:docs` starts its development server. | `apps/parsar-daemon/internal/dispatch` | Runtime preparation, Executor reuse, Turn and cleanup ownership | [Harness lifecycle](../contracts/agents-api/harness-onboarding.md#required-adapter-interfaces) | | `apps/parsar-daemon/internal/agent` | Native harness adapters | [Native references](../contracts/agents-api/harness-onboarding.md#native-references) | | `services/agents-api/internal/sandbox` | Provider interfaces and managed compute lifecycle | [Provider onboarding](sandbox-provider.md) | -| `services/core-console` | Console login and the server-side management proxy | [Web architecture](web/architecture.md) | +| `services/core-console` | Console login and the server-side management proxy | [Console server](web/console-server.md) | | `apps/web` and `packages/agents-client` | Console UI and typed clients | [Web guide](../apps/web/README.md) | | `deploy/install` and `scripts` | Distribution, installation and validation tools | [Maintainers](maintainers.md) | | `contracts/agents-api` | Pinned schema, local semantic contracts and qualification evidence | [Coverage ledger](../contracts/agents-api/README.md) | diff --git a/docs/web/README.md b/docs/web/README.md index e4a3d2c44..20d4439e2 100644 --- a/docs/web/README.md +++ b/docs/web/README.md @@ -1,62 +1,54 @@ # OpenAgentCore Web -Core Web is the administrator console for a Core deployment. Its Go service -provides Core key login and forwards signed-in, same-origin `/core/v1` requests to -Core. Applications use Core's public Agents API directly with their own Project API -keys. +Web is the administrator console of one OpenAgentCore deployment. Administrators use it to watch health, capacity, usage and failures, inspect each Project's resources and execution history, and manage Projects, keys, nodes and deployment settings. Applications do not use Web; they call Core's Agents API (`/v1`) with their own Project API keys. -The React console (`apps/web`) uses this contract: every browser request goes through -the console's same-origin management routes with `AdminClient` and the sandbox -management client, and it sends nothing to `/v1`. +![OpenAgentCore Web overview](../assets/console-overview-en.webp) -![Core Web overview](images/overview.png) +## Sign in + +[Sign in](../getting-started/install.md#sign-in-to-web) with the deployment's [Core key](../getting-started/operations.md#core-key); the console has no user accounts. The browser keeps only a session cookie, and the [console server](console-server.md) sends the Core key to Core on its behalf; [sign-in](console-server.md#sign-in) describes how long a session lasts. + +Signing in opens the Overview. While any step is still to do, its **Getting started** checklist leads through four steps in any order: sandboxes ready, a default model provider, a Project with an active key, and a first Session. An optional tour of the console opens from it. ## Console pages | Group | Page | Purpose | | --- | --- | --- | | Monitor | Overview | Service status, running Sessions, sandbox slots and work needing attention; 24-hour Session activity; Core and its nodes as a topology, each with a popover glance; Sessions needing attention; usage by Project | -| Monitor | Core metrics | The Core process: execution slots, the Turn queue, connected daemons, database latency and pool, background jobs | +| Monitor | Core metrics | The Core process: CPU and resident memory against their limits, execution slots and the Turn queue, connected daemons, database latency and pool, background jobs | | Monitor | Agent metrics | Requests, errors, duration, tokens, models, tools, Agents and API keys over 1 h, 6 h, 24 h or 7 d | | Monitor | Sandbox metrics | Node capacity and hosted Runtime CPU and memory across Projects | -| Monitor | Session log | Every Session, opening one Session's read-only conversation, trace and Turns; a self-hosted Session's page also manages its executor credentials and gives the command that connects a host | -| Resources | Agents, Environment templates, Skills, Files, Vaults | Inspection and permitted deletion | -| Platform | Projects and keys, Nodes, System | Project and key lifecycle; sandbox deployment and nodes; System: the installation's public address, API base URL, ID and source commit (read-only), each harness's default model provider (write-only key) beside its read-only startup state, the sandbox configuration every Project shares: provider, sandbox size, Runtime or E2B template build, idle suspension and reset, and Core's config.json startup settings with where to change them | - -Missing data is shown as missing (—), never as zero. How each figure is read and -bounded is recorded in [management interface coverage](protocol-coverage.md). - -## Management scope - -Administrators can create, rename and archive Projects; issue and revoke their -keys; inspect resources and execution history; delete supported resources; and -issue, rotate and revoke the executor credentials of a self-hosted Session's -environment on its Session page. -They can also read summaries, Runtime observations and audit history, and manage -deployment sandbox nodes. Deployment sandbox management selects E2B, Docker or -microsandbox; caller-managed `self_hosted` Runtimes remain a separate application -path. - -How Projects and keys behave, and what administrators can and cannot do, is in -the [design principles](../design-principles.md#projects-own-assets). - -## Connect and develop - -Follow the [installation guide](../getting-started/install.md) for Core, Web and -PostgreSQL with zero execution nodes. Installation creates no Project or application -key; an administrator creates them on the console's **Projects and keys** page or -through the management API. The browser signs in to the console with the Core key; -only the console server sends it to Core. - -- [Connection and authentication](core-connection.md) -- [Architecture and ownership](architecture.md) -- [Management interface coverage](protocol-coverage.md) -- [Frontend handoff and acceptance](roadmap.md) -- [React application](../../apps/web/README.md) - -The [administrator API contract](../../contracts/agents-api/admin-api.md) defines -management routes and resource behavior. The [public API contracts](../../contracts/agents-api/README.md) -define the separate application interface. See the [design principles](../design-principles.md) -for ownership and [contributor guide](../../CONTRIBUTING.md) for required checks. +| Monitor | Session log | Every Session, and each Session's read-only conversation, trace and Turns with the classified reason of a failure; a self-hosted Session's page also manages its executor credentials and gives the command that connects a host | +| Resources | Agents, Environment templates, Skills, Files, Vaults | Inspection and permitted deletion, with the Project and the creating key of each resource | +| Platform | Projects and keys | Create, rename and archive Projects; issue and revoke keys; each Project's usage, write history and how to call the API | +| Platform | Nodes | Add, edit and remove Docker or microsandbox nodes; each node's readiness, capacity and allocations | +| Platform | System | The installation's public address, API base URL, ID and source commit; **Domain and HTTPS**; each harness's default model; **Sandbox configuration**; Core's `config.json` startup settings, read-only, with where to change them | + +Missing data is shown as missing (—), never as zero. [Console API usage](console-api-usage.md) lists what each page reads and how its figures are bounded. + +## What administrators do here + +| Task | Where | +| --- | --- | +| Give the installation an HTTPS address | **System → Domain and HTTPS**, on an installation with managed ingress; see [Make Core reachable](../getting-started/install.md#configure-the-domain-and-https) | +| Choose the sandbox backend (Docker, microsandbox or E2B), the sandbox size and Runtime, or reset the backend | **System → Sandbox configuration**; see [change the sandbox configuration](../getting-started/nodes.md#change-the-sandbox-configuration) | +| Add or remove execution nodes | **Nodes**; see the [nodes guide](../getting-started/nodes.md) | +| Set the default model of a harness | **System → Default model configuration**; see [default models](../configuration.md#default-models) | +| Create a Project and issue its API keys | **Projects and keys**; see [Projects and API keys](../getting-started/operations.md#projects-and-api-keys) | +| Issue, rotate or revoke a self-hosted executor's credential, or copy its install command | The Session's page in the **Session log**; see [self-hosted executors](../getting-started/self-hosted.md) | +| Delete a resource, for example a leaked Credential | The resource's row in its list, or its page; Files are deleted from the Files list. The public deletion rules apply | + +Installation creates no Project or key. Opening the console neither allocates compute nor calls a model, and an installation may have zero nodes. Web never starts a Session, sends input or cancels work; the [design principles](../design-principles.md#what-administrators-can-and-cannot-do) state what administrators can and cannot do. + +The deployment's sandbox backend serves hosted Sessions. An application's `self_hosted` Runtime, including one in its own E2B account, is a separate path that the sandbox configuration does not change. + +A loopback public address (`local_only`) keeps nodes and remote applications from reaching Core. The console stays reachable at its own address and [warns about it](console-api-usage.md#provenance-and-monitoring). + +## More + +- [Console server](console-server.md): request boundary, sign-in, settings and verification. +- [Console API usage](console-api-usage.md): the Core routes each page uses. +- [Web package](../../apps/web/README.md): developing the console. +- [Administrator API](../../contracts/agents-api/admin-api.md): the `/core/v1` routes behind the console. OpenAgentCore Web is available under the [MIT License](../../LICENSE). diff --git a/docs/web/admin-metrics-backend-requirements.md b/docs/web/admin-metrics-backend-requirements.md deleted file mode 100644 index 7f4a7b6a3..000000000 --- a/docs/web/admin-metrics-backend-requirements.md +++ /dev/null @@ -1,138 +0,0 @@ -# Administrator metrics: backend requirements - -Status reviewed 2026-09-28: P0 Agent/Tool aggregates (BE-8) remain unimplemented -proposals. Core process metrics and node host history have landed through their -own contracts, linked below. Other P1/P2 ideas are proposals, not an accepted -implementation backlog or a statement that all observability is missing. - -The console is a management tool: Monitor (Overview, Agent metrics, Sandbox -metrics, Session log) leads. It reads only the Web API (`/core/v1/**`, -including the sandbox administration routes under `/core/v1/sandbox/**`); it -never calls `/v1`. Some figures come from Web API aggregates, others are still assembled -in the browser from bounded reads of each project. This document records what -that costs, where it is incomplete, and which Web API endpoints would replace -the browser work. - -## Two classes of Core interface - -1. **Public Agents API** (`/v1/**`): serves only the pinned official OpenAI - Agents API routes; Core additions appear only as `x_agents_core` fields. - Metrics work must not add fields, routes or behavior here. -2. **Web API** (`/core/v1/**`, including `/core/v1/sandbox/**` for sandbox - administration): called by the console server and operator scripts with the - Core key. Every endpoint proposed below belongs here; `services/core-console` - forwards `/core/v1/*` by prefix, so a new endpoint needs no proxy change. - -## What the console computes today - -| Page | Source | Limit | -| --- | --- | --- | -| Overview | `GET /summary` (per project: asset counts, Sessions by status, usage, coverage, last activity); `/core/v1/sandbox` deployment and nodes; each project's Session list for the 24-hour activity chart and the Sessions needing attention | Session lists stop once they pass the 24-hour window and have found every Session the summary counts as needing attention, at most 1,000 per project; projects idle since before the window are not read | -| Agent metrics | `GET /summary` to skip idle projects; each project's Session list, then Turns and Items of the most recently active Sessions through the project's scope; `GET /summary?group_by=key` for usage by API key | 2,000 Sessions listed per project; 200 Sessions read per load, 10 Turn pages and 5 Item pages per Session, 15 s per Session and 45 s per load | -| Sandbox metrics | `/core/v1/sandbox` nodes and allocations; `GET /core/v1/sandbox/runtime-observations` (every project); each hosted Session read by ID through its project; Runtime history per hosted Session | 100 hosted Sessions read per refresh; history covers at most 24 hosted Sessions; node host observations and history are available through the separate node-detail contract below | - -Consequences the console states in its help tips and warnings: - -- A "request" is an Agent Turn. HTTP-level request counts, status codes and API - latency are not available anywhere. -- Model attribution uses the Session's Agent snapshot, not the effective - execution configuration. -- Busy projects exceed the Session caps, so long ranges and old Sessions that - need attention can be partial; the page says which projects were cut short. -- Usage by API key counts Sessions created in the range by their creating key - (#87 provenance); Sessions without a record are shown as "unknown". - -Other limits: browser and Core clocks can differ (the console tolerates 15 -minutes of skew without saying so), and Subagent Turns and deleted Sessions are -not counted. - -## Proposed endpoints - -All endpoints are read-only, deployment-scoped with an optional `project_id` -filter, bounded like Runtime history (`start`, `end`, `step`, a maximum range and -point count), and return `null` for unavailable values instead of zero. They are -operational evidence, not billing. - -### P0 — Agent run aggregates - -`GET /core/v1/metrics/agent-runs?start=&end=&step=&group_by=model|agent|harness|project|key&project_id=` - -Per bucket, and per group when `group_by` is set: - -- Turns created, completed, failed, cancelled, still running. -- Duration from `started_at` to `completed_at`: average, p50, p95. -- Queue wait from `created_at` to `started_at`: average, p95. -- Tokens: input, output, cached input, reasoning. -- The effective model and harness from the execution configuration. -- Top-N groups plus an `other` bucket, with the total group count. - -This replaces the Turn fan-out on Agent metrics and makes long ranges complete. - -### P0 — Tool call aggregates - -`GET /core/v1/metrics/tool-calls?start=&end=&step=&group_by=tool|kind|agent&project_id=` - -Per bucket and tool (`function` name, MCP `server_label` + name, shell command, -web search, Subagent): calls, failures, and duration where the Item reports one. -This replaces the Item fan-out. - -### Delivered — Core process metrics - -`GET /core/v1/metrics?range=1h|6h|24h|7d` reports Core process CPU/RSS and -limits, execution queue/slots, PostgreSQL and background-job measurements. -See [Core metrics](../../contracts/agents-api/core-metrics.md) for exact units, -nulls and retention. The earlier proposed `/core/v1/core-status` route was not -adopted. Whole-host CPU is not a Core process metric; additional host fields are -separate proposals rather than missing parts of BE-8. - -### P1 — Session activity and attention - -`GET /summary` already gives Session counts by status per project, Agent and -key. Two Overview parts still read Session lists: - -- Sessions created and failed per bucket: - `GET /core/v1/metrics/sessions?start=&end=&step=&project_id=`. -- Sessions needing attention across projects: - `GET /core/v1/sessions?status=failed,requires_action&order=last_active_desc&limit=` - (each entry labelled with its project), or a `status` filter on the per-project - Session list. - -### P1 — Agents API request metrics - -`GET /core/v1/metrics/api-requests?start=&end=&step=&group_by=route|status_class|project|key` - -Recorded by the API router middleware: request count, 4xx/5xx counts and latency -percentiles per route family (Sessions, events stream, Turns, Items, files …). -This is what hosted consoles call "requests" and "error rate". Store rollups in -the existing PostgreSQL database with bounded retention, following the Runtime -history pattern, with the optional OTLP exporter as a secondary sink. - -### Delivered — Node host observations and history - -`GET /core/v1/sandbox/nodes/{node_id}?range=1h|6h|24h` includes node host -history. See [node host history](../../contracts/agents-api/node-host-history.md) -for available fields, nulls, freshness and aggregation. The earlier proposed -`/nodes/{id}/history` route was not adopted. Further list fields and allocation -series require a separate agreed scope; they are not prerequisites for BE-8. - -### P2 — Hosted Runtime rows with their Sessions - -`GET /core/v1/sandbox/runtime-observations` labels each observation with its -project but not its Session's title, Agent, status or usage, so Sandbox metrics -reads every hosted Session by ID (bounded at 100 per refresh). An -`expand=session` option returning those fields would remove the reads. - -### P2 — Keys - -- API key `last_used_at` and per-key request counts (with the API request - metrics above). - -## Open questions - -1. Should aggregates be computed on read from existing tables (simpler, slower on - large deployments) or from periodic rollups (like Runtime history)? -2. Retention and maximum range for each family; is 7 days enough for the console? -3. Which percentile method and bucket alignment should all metric families share? -4. Should API request metrics exclude the console's own polling traffic? -5. Should Core's own status include the executor gateway and model endpoint - reachability, or stay limited to the process and host? diff --git a/docs/web/architecture.md b/docs/web/architecture.md deleted file mode 100644 index ca076951e..000000000 --- a/docs/web/architecture.md +++ /dev/null @@ -1,105 +0,0 @@ -# Core Web architecture - -Core Web manages a Core deployment. Applications, including Parsar, use the public -Agents API independently with their own Project keys. The management backend, -`AdminClient` and the React console built on them are implemented. - -The [design principles](../design-principles.md) define identity and authority. -The [administrator contract](../../contracts/agents-api/admin-api.md) defines exact -routes, payloads, pagination and audit records. - -## Request boundaries - -```mermaid -flowchart LR - browser["Administrator browser"] - console["Core console service"] - core["Core API"] - database[("PostgreSQL")] - application["Application / official SDK"] - runtime["Runtime and native adapters"] - - browser -->|"Same-origin management requests; console login"| console - console -->|"/core/v1/* by prefix, including sandbox; Core key"| core - application -->|"/v1; Project API key"| core - core <--> database - core <--> runtime -``` - -React management code must use the Core clients from `packages/agents-client`: -`AdminClient` for `/core/v1`, `CoreMetricsClient` for `/core/v1/metrics` and the -sandbox management client for `/core/v1/sandbox`. The console service -returns 404 for `/v1` and `/api/v1`, including requests with an explicit Bearer -token. It has no application key and does not impersonate the selected Project. - -The console signs the browser in with the Core key, checks the host and origin, and -forwards every signed-in `/core/v1/*` request to Core by prefix; Core alone decides -whether the route exists. It strips the browser's Authorization, Cookie, Origin and -Referer headers and supplies the Core key as its private upstream credential. Core -rejects application keys on management routes and the Core key on `/v1`. -The audit actor label is declared by the caller and is display only, never Core authorization: the console server declares `console`, and operator scripts calling Core with the Core key directly leave it empty. - -`GET` and `POST /console/installation/domain` are the scoped installation-management -exception: the console authenticates the same browser session and origin, then -calls the installer's private Unix socket with its server-held Core key. This is -not a Core `/core/v1` route and does not use `AdminClient`. It can configure only -the managed domain; it cannot submit shell commands or arbitrary process settings. -The [installer rules](../../deploy/install/README.md#managed-https) own application, -certificates and recovery. Before a domain is configured, the console accepts -same-origin HTTP requests at literal IP addresses; after apply, only the configured -HTTPS origin is accepted. - -Node and daemon connections use `/api/v1` with their own credentials. The reverse -proxy sends them directly to Core; the console never forwards them, and they do not -grant a browser execution authority. - -## Ownership - -| Component | Responsibility | -| --- | --- | -| React frontend | Project selection, permitted management actions and operational views; cached reads (TanStack Query) that keep the last data on screen while refreshing | -| `AdminClient` | Typed management requests and validation, sharing resource parsers with the public client | -| `services/core-console` | Core key login, host/origin checks, and prefix forwarding of `/core/v1/*` with the Core key as the private upstream credential | -| Core API and PostgreSQL | Project isolation, resource state, deletion preconditions, audit and scheduling | -| Runtime and native adapters | Existing allocation, process lifecycle and execution protocols | - -Projects, keys and administrator authority follow the -[design principles](../design-principles.md#projects-own-assets). The console adds -no execution path: a deletion conflict is never resolved by an implicit cancellation. - -Secret fields remain write-only; Skill source and Artifact content have explicit -read routes, while Source File content does not have an administrator download -route. - -## Deployment and application Runtime paths - -Deployment sandbox management selects one provider at a time: E2B, Docker or -microsandbox. E2B uses the deployment's provider integration; Docker and microsandbox -use operator-managed machines. Provider setup, reset and node administration -belong to the existing sandbox management surface. - -An application's `self_hosted` Runtime, including one it provisions in its own E2B -account, is a separate caller-managed path. It does not choose or reconfigure the -deployment provider. This console contract changes neither native Runtime protocols -nor application Session creation semantics. - -## Frontend state and validation - -Session inspection uses paginated durable history and bounded polling. There is no -management Session SSE endpoint. Project changes must discard stale reads and -pending operation state before displaying results in another Project. - -The client sends each write once per explicit action. An uncertain result stays -visible until the administrator checks state and decides how to proceed. Issued -key plaintext must not enter browser storage or logs. Key issuance recovery -follows the administrator contract. - -The sandbox deployment read (`GET /core/v1/sandbox/deployment`) describes the saved -selection. It does not prove a reachable model, valid provider credentials or -execution readiness. Runtime observations, usage coverage and audit history must -retain the distinctions defined by Core. In E2B views, the running sandbox count -comes from the deployment's allocations; the hosted Runtime total counts hosted -observation records across projects and reported lifecycle states. These sources -have different coverage and refresh independently, so the console does not infer -resource retention or cleanup from their difference. -Native execution ownership remains governed by [CONTRIBUTING.md](../../CONTRIBUTING.md). diff --git a/docs/web/console-api-usage.md b/docs/web/console-api-usage.md new file mode 100644 index 000000000..926c7d1f1 --- /dev/null +++ b/docs/web/console-api-usage.md @@ -0,0 +1,144 @@ +# Console API usage + +This page lists the Core routes each console page reads and writes, and how the console bounds its reads. The [administrator API contract](../../contracts/agents-api/admin-api.md) defines the routes, response shapes, pagination and audit records; [API namespaces and credentials](../api/README.md) defines the terms used here. + +## Interfaces + +| Interface | Paths | Authentication | Console use | +| --- | --- | --- | --- | +| Console server | `/console/auth`, `/console/auth/{login,logout}`, `/console/config`, `/console/installation/domain`, `/node-install/manifest.json` | The Core key at sign-in, then the console session cookie; `/node-install/manifest.json` needs no sign-in | Sign-in and sign-out; the node installer and node artifacts for Add node; domain setup on **System → Domain and HTTPS**; the distribution's Runtime release for Docker and microsandbox setup. See [console server](console-server.md) | +| Administrator API | `/core/v1/**` outside `/core/v1/sandbox` | The Core key, added by the console server | Projects, keys, resource reads and deletion, diagnostics, executor credentials and installation commands, provenance, summaries, Core metrics, the installation, default models | +| Sandbox administration | `/core/v1/sandbox/**` | The Core key, added by the console server | Sandbox configuration, Nodes, fleet and capacity figures on Overview and Sandbox metrics, Runtime observations of every project | +| Agents API | `/v1/**` | Project API key | Not used. The console shows developers how to call it (see [Provenance and monitoring](#provenance-and-monitoring)) | + +Browser requests are same-origin and carry only the console session cookie. The browser sends the Core key once, in the sign-in request body, and never stores it; it never holds or sends an API key or an `OpenAI-Beta` header. The console reads through `AdminClient`, `SandboxAdminClient` and `CoreMetricsClient` from [`packages/agents-client`](../../packages/agents-client/README.md), which validate every response: a malformed value is reported as a failure, or marked as unrecognised where noted below, and never replaced by a guessed or zero value. + +## Projects and keys + +| Operation | Route | Console use | +| --- | --- | --- | +| List projects | `GET /core/v1/projects` | Project filter on every project-scoped page; Projects and keys list; the Overview's Getting started (a project with an active key, and the newest active project, preferring one with an active key, whose call samples the first-Session step opens); `active_key_count` in the archive confirmation, which counts more when the project's key list shows more | +| Create project | `POST /core/v1/projects` | **Create project**, also from Getting started | +| Rename project | `POST /core/v1/projects/{project_id}` | **Rename** on an active project; the ID stays the same | +| Archive project | `POST /core/v1/projects/{project_id}/archive` | **Archive**: revokes every key; the project's assets stay readable and deletable | +| List keys | `GET /core/v1/projects/{project_id}/keys` | Key table of a project: name, prefix, status, creation and revocation time; the active keys the archive confirmation counts | +| Issue key | `POST /core/v1/projects/{project_id}/keys` | **Issue key** on an active project, also from Getting started; the plaintext is shown once | +| Revoke key | `DELETE /core/v1/projects/{project_id}/keys/{key_id}` | **Revoke**, with a warning when it is the project's last active key | + +Names are checked for length (projects 1–128 characters, keys 1–80) and control characters before sending. An issued key's plaintext stays in component memory until the administrator confirms it was saved and is never written to browser storage, URLs or logs. There is no project deletion and no plaintext recovery. + +## Project resources + +Routes are relative to `/core/v1/projects/{project_id}` and return the same objects as the corresponding public `/v1` operations, so the console applies the public client's strict projections. Archived projects remain readable. + +| Resource | Reads used | Deletion | Creator | Console surface | +| --- | --- | --- | --- | --- | +| Agents | `/agents`, `/agents/{agent_id}` | Agent | `agent` | Agents list with usage per Agent; Agent page with instructions, tools, the saved model provider (never its key), generation settings and metadata | +| Environment templates | `/environment-templates`, `/environment-templates/{id}` | Template | `environment_template` | Templates list; Template page with every safe section | +| Skills | `/skills`, `/skills/{skill_id}`, `/skills/{skill_id}/versions`, Skill and version `/content` | Skill and Skill version | `skill` (list) | Skills list; Skill page with versions and archive downloads | +| Files | `/files` | File | `file` | Files list (metadata only) | +| Vaults | `/vaults`, `/vaults/{vault_id}`, `/vaults/{vault_id}/credentials` | Vault and Credential | `vault`, `credential` | Vaults list; Vault page with Credential metadata | +| Sessions | `/sessions`, `/sessions/{session_id}`, `/sessions/{session_id}/items`, `/sessions/{session_id}/turns`, `/sessions/{session_id}/runtime-observation`, `/sessions/{session_id}/runtime-history` | Session | `session` | Session log; Session page; Agent metrics; hosted Runtime rows | +| Diagnostics | `/sessions/{session_id}/diagnostics`, `/sessions/{session_id}/turns/{turn_id}/diagnostics` | — | — | The classified reason under a failed Session's or Turn's status on Overview, the Session log and the Session page; Core's receipt time of each Item in the trace | + +Resource-specific rules: + +- **Environment templates.** `env` and setup commands are write-only and never returned, so the console cannot tell whether a Template has them. Inline files report only their size. A Template with a section or field the client does not recognise is marked; its recognised sections are still shown and nothing else is guessed. +- **Skills.** A version upload, a default-pointer change and every other Skill write belong to the project's keys. The console downloads the default or an exact version as a ZIP, deletes versions (the default version is blocked while others remain; deleting the only version deletes the Skill) and deletes a Skill after its name is typed. +- **Files.** The list is read 100 per page, newest or oldest first. The administrator API has no File content route, so the console offers no download. +- **Vaults.** Credential tokens are never returned. The console shows each Credential's name, MCP server URL, authentication type and update time. +- **Sessions.** A malformed Session fails the read of its project instead of being skipped. +- **Diagnostics.** The console translates Core's classified reason and never infers a cause from raw logs. An unavailable or mismatched diagnostic offers an explicit read retry; a retry never replays execution. + +## Executor credentials and host connection + +The **Executor credentials** section of a Session page appears only when the Session's environment is `self_hosted`, for that Session's `project_id` and `environment.id`. The [executor credential contract](../../contracts/agents-api/environment-executor-credentials.md) defines the routes, their 404 and 409 responses and the credential file. + +| Operation | Route | Console use | +| --- | --- | --- | +| List credentials | `GET /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials` | Read every 5 seconds while visible. The table shows each credential's short `key_id` with its copy button, creation time (`created_at`, which rotation does not change) and status (Active, or Revoked with its time), active first; the credential itself is never listed. The same read's `connection` observation drives the host connection panel: never connected, connected, disconnected, bound credential revoked, and unknown stay distinct, and only a fresh connected read marks the host connected | +| Issue or rotate | `POST …/executor-credentials` with `{"key_id", "rotate"}` | Issue keeps the generated key ID before submission. Rotation confirms that the old credential stops working; rotating a revoked credential restores it with a new secret. The private JSON is shown once for download or copy, never stored in browser storage or the query cache, and forgotten on Done | +| Revoke | `DELETE …/executor-credentials/{key_id}` | **Revoke**, confirmed (the executor disconnects and does not retry; its daemon stays parked until the operator stops it), then the list is read again and shows the credential as Revoked | +| Installation commands | `GET /core/v1/projects/{project_id}/environments/{environment_id}/installation` | **Connect a host**: Core's short-lived Linux/macOS and PowerShell commands, shown as Core returned them with a platform selector and a link to the [native installation guide](../getting-started/self-hosted.md). The commands install the daemon and its Harnesses, start it and check its connection; their authorization expires after 30 minutes, and the console reads them again every 20 minutes. Without an available, unexpired answer the section says the command is unavailable. Archived projects do not read it | + +In an archived project the section hides **Issue credential** and **Rotate** behind a note and keeps the list and **Revoke**, which Core still allows. + +## Provenance and monitoring + +| Operation | Route | Console use | +| --- | --- | --- | +| Resource owners | `GET /core/v1/projects/{project_id}/resource-owners` | The Creator column of every resource list and the creator fact of detail pages, in batches of up to 100 IDs: the creating key's name, **Admin copy** for an owner with source `admin_copy`, or **Unknown** when Core has no record | +| Write operations | `GET /core/v1/projects/{project_id}/write-operations` | A project's write history, newest first, filtered by key and resource type, 50 per page | +| Summary | `GET /core/v1/summary` | Overview (per project), the Agents list (`group_by=agent`), a project's page (per project and `group_by=key`), Agent metrics (to skip idle projects, and usage by creating key since the start of the range), the Projects list (last activity) | +| Installation | `GET /core/v1/installation` | System's Installation facts (`public_url`, `api_base_url`, `installation_id`, `source_commit`) and read-only Startup settings (`configuration.settings` under its `path`, `apply_command` and `applied_at`; a sensitive setting shows only whether it is `configured`); `api_base_url` in the call samples; `public_url` as the download origin and `--source-url` of the node install and uninstall commands (and the install command's `--core-url`); `path` and `apply_command` beside a sandbox configuration Core rejected. A sensitive setting with a value, or an unknown member, fails the read; `configuration: null` shows a note | +| Core metrics | `GET /core/v1/metrics?range=` | Core metrics page; the Core popover on Overview. A Core without the route (404) is shown as not reporting, and the popover then shows only Core's status. The [Core metrics contract](../../contracts/agents-api/core-metrics.md) defines every measurement | + +`local_only`, or a `public_url` that is not an HTTPS origin, stops Add node from issuing a command and Clean up the host from giving one. Overview, Nodes and System then show a visible warning with Core's configuration path and apply command as copyable values; when `configuration` is null, they state that the path and command are unavailable. Nodes disables Add node with a visible reason, and Getting started leaves its sandbox step to do. + +Wherever a new key is shown, and without any key on an active project's page, the console gives shell exports of `OPENAI_BASE_URL` (the installation's `api_base_url`) and `OPENAI_API_KEY` (the new key, or a placeholder for a key of the project), with `curl` and Python examples for `GET /v1/agents` and `POST /v1/agents/sessions`, and sends none of them. When the installation is `local_only` it says the API is reachable only on the Core machine, and without an `api_base_url` it says to set `public_url`. + +Summary figures are cumulative per Session and are not billing records. Sessions without reported usage count toward coverage but not toward token sums, and the console shows missing values as missing, never as zero. + +## Default models + +| Operation | Route | Console use | +| --- | --- | --- | +| List harnesses | `GET /core/v1/harnesses` | System's Default model cards: each harness's read-only `enabled` and `default`, its model configuration without the key, and Usage details from the configuration's `last_used_at`, `last_error_code` and `last_error_at`; the Overview's Getting started (a default model on the default harness, or on any enabled harness when none is default) | +| Set or replace | `PUT /core/v1/harnesses/{harness}/model-configuration` | **Set** or **Replace**: the complete model configuration with its write-only provider key, never prefilled and never retried; a 400 shows Core's message in the form, and a 503 `credential_storage_unavailable` says Core has no credential encryption key; then the list is read again | +| Clear | `DELETE /core/v1/harnesses/{harness}/model-configuration` | **Clear**, confirmed, then the list is read again | + +The list carries each harness's configuration, so the console does not read `GET /core/v1/harnesses/{harness}/model-configuration`. + +## Sandbox administration + +| Operation | Route | Console use | +| --- | --- | --- | +| Deployment | `GET`, `POST`, `PUT /core/v1/sandbox/deployment` | Read the provider, the read-only `core_url` (config.json's `public_url`, shown in the setup review and never sent), reset state, installation ID and specification; a 409 `sandbox_configuration_error` (E2B with a loopback `public_url`) shows the shared client's fixed safe address-configuration message in the setup wizard, with the installation's config file and apply command, and leaves nothing to confirm; initialize the deployment with `resources` and the Docker or microsandbox `runtime` release, or with the E2B account and no `resources` (Core adopts the template build's CPU and memory); change its settings with the expected generation. E2B's `e2b.template_build` (status, CPU, memory, disk) shows on System, the Sandbox configuration summary and Sandbox metrics, and sizes each sandbox when `specification.resources` is missing; microsandbox's `suspension` (idle and retention seconds) shows on System and the Nodes summary | +| E2B discovery | `POST /core/v1/sandbox/e2b/templates`, `POST /core/v1/sandbox/e2b/templates/{template_id}/builds` | The setup wizard lists the templates the entered E2B key can see, then the selected template's ready builds. The key travels only in these request bodies and the deployment write | +| Reset | `POST`, `DELETE /core/v1/sandbox/deployment/reset` | Explicitly clear hosted resources, or cancel the remaining clear at the observed generation; show Core's remaining and offline projection | +| Nodes | `GET /core/v1/sandbox/nodes` | Nodes page; fleet on Overview; node capacity on Sandbox metrics. An online node's `diagnostic` (`docker_unavailable`, `docker_limits_unsupported`, `runtime_image_unavailable`, `kvm_unavailable`, `microsandbox_artifacts_unavailable`, `capacity_insufficient`, `provider_unavailable`; any other value reads as `provider_unavailable`) marks it degraded and names the reason and fix in the help tip beside its status on each of these and on the node's page. A node whose `core_url` (the address it enrolled with) differs from the deployment's `core_url` is named on the Nodes page as bound to an old address, to be removed and added again, and its status there and on its page reads Old address instead of its health; an empty `core_url` (a node Core did not enroll) is unknown, not old. **Add node** follows only the node whose `enrollment_id` equals its command's | +| Node detail | `GET /core/v1/sandbox/nodes/{node_id}?range=1h\|6h\|24h` | Sandbox metrics node dialog: the host's CPU busy share and memory from its last heartbeat, and their history over the page's range. **Edit node** reads `host.effective_cpu_cores` and `host.total_memory_bytes` to show the host beside each sandbox's size and at most how many of those fit | +| Allocations | `GET /core/v1/sandbox/nodes/{node_id}/allocations` | Nodes page; Sandbox metrics. Under microsandbox, a node's page shows from `compute_phase_changed_at` how long each allocation has been in its compute phase and, while suspended, about when Core reclaims it (that time plus the deployment's `suspension.retention_seconds`); a null time shows a dash | +| Enrollment | `POST /core/v1/sandbox/enrollment-tokens` | **Add node**: the administrator sets the node's sandbox limits (`max_active`; `max_retained` only for microsandbox, equal to `max_active` for Docker) before Core issues a single-use token inside a command that verifies the installer checksum, with the command's `enrollment_id`, which the node it registers reports. The command runs the installer with sudo (a system service) and passes the token on standard input; root runs it directly. No ordinary-user installation or removal entry is exposed, and the log hint always names the system service. The command downloads the installer from the installation's `public_url`. No token is requested until the installation is read, when it cannot be read, when it is `local_only` (or its `public_url` is not an HTTPS origin), or when `/console/config` lists `node_artifacts` without the deployment's provider. The dialog reads both again on opening and when the window regains focus | +| Update node | `PATCH /core/v1/sandbox/nodes/{node_id}` | **Edit node**: the name and sandbox limits together (the retained limit only for microsandbox; under Docker, Core sets it to the active limit) | +| Remove node | `DELETE /core/v1/sandbox/nodes/{node_id}` | Confirmed node removal; the row goes only after Core acknowledges the deletion, and a Clean up the host dialog then gives the host's uninstall command (requiring root or sudo; for a node enrolled with another address than the deployment's, also with `--force`, which skips the installer's confirmation with Core) | +| Runtime observations | `GET /core/v1/sandbox/runtime-observations` | Sandbox metrics: hosted Runtimes of every project, each labelled with its project; an E2B sandbox's dialog adds its `observation.disk` as used / limit (null elsewhere) | + +An E2B deployment has no nodes; its API key is write-only. Overview and Sandbox metrics count its running and starting sandboxes from the deployment's `resources.allocations` and `resources.pending`, while the hosted Runtime rows come from Runtime observations. The two sources refresh independently, so the console does not infer retention or cleanup from their difference. The Runtime release sent for Docker and microsandbox comes from the console's own `GET /node-install/manifest.json`; without it the administrator enters the release under advanced settings. + +## Writes + +- Deletion uses the administrator API with the same preconditions as the public delete operation. Every deletion is confirmed. A 4xx keeps the dialog open with Core's reason, a 404 counts as already deleted, and any other failure is reported as uncertain and followed by a fresh read. +- The console offers Session deletion only for idle or failed Sessions without required actions and never cancels work to make a Session deletable. +- Project, key, executor credential, deletion and sandbox writes are sent once per explicit action and never retried automatically. An uncertain result stays visible until the administrator reads the state again and decides. +- An executor credential issuance with an unknown outcome (no answer, a 30-second timeout, a 5xx) opens an error dialog whose next step is **Refresh list**. If the kept `key_id` is then listed, it was issued and its secret lost: the console offers to rotate it (`rotate: true`) for a fresh secret, shown once. If it is not listed, the next Issue sends the same `key_id` with `rotate: false`; should that return 409 because the first request was issued after all, the console reads the list again and offers the same rotation only if the credential is listed as active in an active project, and otherwise reports the issuance as rejected. A kept `key_id` that is already listed is never sent again, and rotating or revoking it from its row forgets it: the next Issue generates a new `key_id`. + +## Read bounds + +The console assembles several figures in the browser from bounded reads of each project. Session history is read in pages and polled; there is no management event stream. + +| Page | Reads | Bound | +| --- | --- | --- | +| Resource lists | Every page of the selected project, or of every project in parallel | 10,000 entries per project; a failed project is named and the rest still show | +| Session log | Every Session page of the selected projects, newest first | 10,000 per project; refreshed on request | +| Session page | Session, Items and Turns | 10,000 Items and Turns; polled every 5 s while the Session is in progress or waiting, backing off to 60 s on failures | +| Overview | Summary; Session lists for the 24-hour activity and the Sessions needing attention | 1,000 Sessions per project; idle projects are skipped; refreshed every 30 s while visible. Failed project or Session reads show Retry instead of a synthesized empty result; any retained or partial data is visibly qualified | +| Agent metrics | Summary; Session lists; Turns and Items of the most recently active Sessions | 2,000 Sessions listed per project; 200 Sessions read per load, 10 Turn and 5 Item pages each, 15 s per Session and 45 s per load | +| Sandbox metrics | Nodes and allocations; Runtime observations; hosted Sessions by ID; Runtime history | 100 hosted Sessions read per refresh; history for at most 24; refreshed every 30 s while visible | + +Agent metrics has these limits: + +- A request is one root Agent Turn; Subagent Turns and deleted Sessions are not counted. HTTP request counts, status codes and API latency are not available. +- The model of a request comes from the Session's Agent snapshot, not from the Session's execution configuration. +- Busy projects exceed the Session caps, so long ranges can be partial. +- Usage by API key counts Sessions created in the range by their creating key; Sessions without a creation record count as Unknown. +- Turn times up to 15 minutes after the end of the range are accepted, to allow for clock differences between the browser and Core. + +Its help tips and coverage notes state what a request is and what is not counted, where the model comes from, which projects were cut short or Sessions skipped, and how usage by key is grouped. They do not mention HTTP request metrics or the clock allowance. + +## Not consumed + +- Any `/v1/**` route, including Session creation, Session events and their stream, message input, function results and cancellation. +- Creation or update of Agents, Environment templates, Skills, Files, Vaults or Credentials, including uploads and Credential token replacement. +- Single Turn reads, Artifacts, Session execution configuration, Environment Files and administrative Session archive. +- The administrator audit log (`GET /core/v1/audit-log`). System shows the installation, each harness's default model and the sandbox deployment instead. diff --git a/docs/web/console-server.md b/docs/web/console-server.md new file mode 100644 index 000000000..499ec9996 --- /dev/null +++ b/docs/web/console-server.md @@ -0,0 +1,140 @@ +# Console server + +The console server (`services/core-console`, the `oac-web` process) serves the built console, signs the administrator in with the Core key and forwards the signed-in browser's `/core/v1` requests to Core with that key. The browser never holds the Core key or any API key. Applications, nodes and self-hosted executors call Core directly; the console forwards none of their traffic. + +## Request boundary + +```mermaid +flowchart LR + browser["Administrator browser"] + console["Console server"] + core["Core"] + database[("PostgreSQL")] + application["Application / official SDK"] + machine["Nodes and Runtime daemons"] + installer["Installer domain service"] + + browser -->|"same origin: /console/*, /core/v1/*; session cookie"| console + console -->|"/core/v1/* with the Core key"| core + console -->|"domain setup, Unix socket"| installer + application -->|"/v1 with a Project API key"| core + machine -->|"/api/v1 with machine credentials"| core + core <--> database +``` + +The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation options](../getting-started/install-options.md#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: + +| Path | Sign-in | Handling | +| --- | --- | --- | +| `/healthz` | No | `GET` or `HEAD` answers `200 ok` | +| `/v1`, `/api/v1` and below | — | 404, whatever credential the request carries | +| `/node-install/*` | No | The node installation payload (see [Node installation payload](#node-installation-payload)) | +| `/console/auth`, `/console/auth/login`, `/console/auth/logout` | No | [Sign-in](#sign-in) | +| `/`, `/index.html`, `/favicon.svg`, `/oac-mark.svg`, `/assets/*` | No | Static console assets | +| `/console/config` | Yes | [Console configuration](#console-configuration) | +| `/console/installation/domain` | Yes | [Domain setup](#domain-setup) | +| `/core/v1/*` | Yes | [Forwarded to Core](#forwarding-to-core) | +| `/core` and other paths under `/core/` | Yes | 404 | +| Any other path | Yes | Static assets; a path without a file extension falls back to `index.html` | + +Every request except `/healthz`, `/v1` and `/api/v1` must pass these checks first: + +1. **Host and origin.** The `Host` header must equal the host of `OAC_WEB_ORIGIN`. An `Origin` header, when present, must equal that origin, and `Sec-Fetch-Site` must be `same-origin` or `none`. A write that carries neither `Origin` nor `Sec-Fetch-Site: same-origin` needs a same-origin `Referer`. Otherwise the console answers 403. `/node-install/*` checks only the host and the path. +2. **Safe request.** The path must start with `/` and contain no `%`, backslash, NUL, dot segment or empty segment. Absolute-form request targets, `CONNECT`, `TRACE` and any request with an `Upgrade` header get 400. A request can therefore never leave `/core/v1` on Core, and the console carries no WebSocket. +3. **Sign-in.** Paths that need sign-in answer 401 without a valid session cookie. + +Under `/core`, these failures use the Core error envelope with the codes in [console-generated failures](../../contracts/agents-api/core-errors.md#console-generated-failures); elsewhere they return `{"error": "…"}`, or plain text for an unsafe request. Every response carries `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer` and `Content-Security-Policy: frame-ancestors 'none'`. + +## Forwarding to Core + +The console forwards each signed-in `/core/v1/*` request by prefix to `OAC_WEB_UPSTREAM`, with its path and query unchanged. Core alone decides whether the route exists, and its responses and errors pass through unchanged. The console therefore needs no change when Core adds a `/core/v1` route. + +On the way to Core, the console: + +- removes the browser's `Authorization`, `Proxy-Authorization`, `Cookie`, `Origin` and `Referer` headers; +- sends `Authorization: Bearer `; +- sets `X-Core-Console-Actor: console`, replacing any value the browser sent. Core records it as a display-only audit label ([administrator API](../../contracts/agents-api/admin-api.md)); +- ignores ambient HTTP proxy settings, so the Core key reaches only the configured Core; +- streams responses without buffering. + +On the way back, it removes `Set-Cookie`, `WWW-Authenticate`, `Location`, `Refresh` and every `Access-Control-*` header. A redirect from Core, or a failed connection to Core, becomes 502 `core_unreachable`. + +The console never retries a request. Browser code calls `/core/v1` through the typed clients in [`packages/agents-client`](../../packages/agents-client/README.md); [console API usage](console-api-usage.md) lists what each page reads and writes. + +## Sign-in + +| Method and route | Request | Result | +| --- | --- | --- | +| `GET /console/auth` | No body | `200 {"mode":"login"}` or `200 {"mode":"authenticated"}` | +| `POST /console/auth/login` | `Content-Type: application/json`; body `{"core_key":"…"}` with no other member, at most 4 KiB | `200 {"mode":"authenticated"}` and the session cookie | +| `POST /console/auth/logout` | No body | `200 {"mode":"login"}`; ends the session and clears the cookie | + +The administrator signs in with the deployment's [Core key](../getting-started/operations.md#core-key). There are no console accounts, usernames or setup step, and signing in grants the whole console. + +- The console compares SHA-256 digests of the submitted and configured keys in constant time. It never logs or returns the key. +- The session cookie `core_console_session` is HttpOnly, `SameSite=Strict`, and `Secure` when `OAC_WEB_ORIGIN` is HTTPS. It lasts 12 hours. +- Sessions live only in the console's memory, at most 64 at a time; the oldest is dropped first. A console restart or a Core key rotation signs everyone out. +- At most two sign-in checks run at once; another attempt gets 429 with `Retry-After: 1`. +- Failed attempts share a budget of 10 per minute; beyond it, a wrong key gets 429 with `Retry-After: 60`. The correct key always signs in, which is why the console refuses to start with a Core key shorter than 32 characters. + +Sign-in errors: 400 for a malformed body, 401 `Invalid Core key`, 405 for a method other than `POST`, 415 for a body that is not JSON, 429 as above, and 503 when the console cannot create a session. + +## Console configuration + +`GET /console/config` returns what the signed-in browser needs to add nodes: + +| Field | Meaning | +| --- | --- | +| `node_installer` | Whether the console serves a node installation payload | +| `node_installer_sha256` | SHA-256 of that payload's `node-install.pyz`; Add node commands verify it before running the installer | +| `node_artifacts` | The providers (`docker`, `microsandbox`) whose node artifacts the payload holds, locally or as a pinned release download. Read on every request, so artifacts added by rerunning the installer appear without a restart | + +## Node installation payload + +With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution's node payload at `/node-install/` without sign-in: `node-install.pyz`, `manifest.json`, `SHA256SUMS`, `runtime/seccomp.json`, and the node artifacts the manifest declares under `artifacts/`. An artifact missing locally redirects (307) to its pinned release download. Node install and uninstall commands download from `/node-install/`, so the reverse proxy must send that path to the console. Nodes verify every checksum themselves. + +## Domain setup + +`GET` and `POST /console/installation/domain` let **System → Domain and HTTPS** configure a managed installation's domain. They are console routes, not Core routes. After the same origin and sign-in checks, the console passes the request body (at most 2 KiB) to the installer's Unix socket at `OAC_WEB_INSTALLATION_SOCKET`, authenticated with the Core key, and returns the installer's JSON answer and status. The request times out after 20 seconds. + +| Method | Request | Result | +| --- | --- | --- | +| `GET` | No body | The domain status | +| `POST` | `{"hostname":"core.example.com"}`, optionally with `"confirm_public_url_change":"https://core.example.com"` | 202 and the status; the installer checks and applies the domain in the background | + +The status has `supported`, `state` (`unconfigured`, `checking`, `applying`, `ready` or `failed`), and nullable `public_url`, `target_url` and `message`. Installer errors use `{"error":{"code":"…","message":"…"}}`. Changing an address that nodes or executors already use returns 409 `public_url_confirmation_required` until the request confirms the new URL; pending `config.json` edits, an installation that is not applied or not running, hand-edited generated files, and another installation operation holding the lock (`installation_busy`) also return 409. + +Without `OAC_WEB_INSTALLATION_SOCKET` (external reverse proxy installations), `GET` reports `supported: false` and `POST` returns 400 `domain_setup_unavailable`. An unreachable installer or an invalid answer returns 502 `installation_unreachable`. + +The System page submits a hostname once, polls the status every 2 seconds while it is `checking` or `applying`, and asks for confirmation when the installer requires it. It never retries a write. Applying the domain restarts the console, which ends every session; the page keeps a sign-in link to the new HTTPS address. Only the `ready` state confirms HTTPS; the browser does not probe the new origin. The installer owns certificates, locking and recovery ([managed HTTPS](../../deploy/install/README.md#managed-https)). + +`OAC_WEB_BOOTSTRAP=1`, which the installer sets while no public URL is configured, lets the console also accept plain HTTP requests addressed to a literal IP address, treating `http://` as the origin, so an operator can sign in through the server's IP address. Host names still require `OAC_WEB_ORIGIN`, so DNS rebinding cannot reach the console. + +## Settings + +The installer sets these variables from `config.json`; set them yourself only when you run the console without the installer. Of the installation's secrets, the installer gives the console only `secrets/core.key`. + +| Variable | Default | Meaning | +| --- | --- | --- | +| `OAC_WEB_ADDR` | `:8080` | Listener address | +| `OAC_WEB_ORIGIN` | `http://127.0.0.1:8080` | The exact browser-facing origin, HTTP or HTTPS, without a path. Host and origin checks use it; HTTPS makes the session cookie `Secure` | +| `OAC_WEB_UPSTREAM` | `http://core:8091` | Core's origin, HTTP or HTTPS, without credentials, query or path | +| `OAC_WEB_CORE_KEY_FILE` | `/admin/core.key` | Absolute path of a regular file with no group or other permissions, holding the Core key: at least 32 characters, no whitespace, at most 4 KiB | +| `OAC_WEB_DIST` | `/www` | Absolute directory of the built console; must contain `index.html` | +| `OAC_WEB_NODE_PAYLOAD_DIR` | unset | Absolute path of the matched distribution's node payload (the installer's `node-payload/`). Unset, `/node-install/*` is not served and Add node is unavailable | +| `OAC_WEB_INSTALLATION_SOCKET` | unset | Absolute path of the installer's domain socket. Unset, domain setup reports unsupported | +| `OAC_WEB_BOOTSTRAP` | `0` | `1` accepts literal-IP hosts before a domain is configured. Requires an `http://` origin and `OAC_WEB_INSTALLATION_SOCKET` | + +An invalid `OAC_WEB_*` value stops the console at startup with a message naming the variable. The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](../configuration.md#appendix-core-environment-without-the-installer)); unknown values fall back to their defaults. Use HTTPS for any browser that is not on the same machine. + +## Verification + +After installing or changing the console, check: + +1. `GET /healthz` on the console and on Core. Each proves only that the process answers. +2. Sign in, then read `GET /core/v1/projects` in the browser. This proves the browser-to-console and console-to-Core path and the console's Core key. +3. A Project API key works on `/v1` and fails on `/core/v1`. The Core key fails on `/v1`, and `/v1` sent to the console answers 404. +4. A cross-origin write to the console is rejected, and a forged `X-Core-Console-Actor` header does not change the audit label. +5. Neither sign-in nor the sandbox deployment read (`GET /core/v1/sandbox/deployment`) proves that a model or a sandbox is ready. Runtime observations and history report execution separately. + +A sign-in failure belongs to the console. A 401 from Core on a signed-in request means the console's Core key does not match Core's digest, or the console reaches the wrong Core. The [troubleshooting table](../getting-started/operations.md#troubleshooting) covers the common symptoms. diff --git a/docs/web/core-connection.md b/docs/web/core-connection.md deleted file mode 100644 index 80d23ec1c..000000000 --- a/docs/web/core-connection.md +++ /dev/null @@ -1,87 +0,0 @@ -# Connecting the administrator console to Core - -`services/core-console` serves built Web assets, signs administrators in with the -Core key and forwards every signed-in, same-origin `/core/v1/*` request to Core. The -backend contract and the React screens that use it are implemented; the console has -no execution controls. - -## Connection model - -The browser calls same-origin `/core/v1` through `AdminClient` and -`CoreMetricsClient`, and the `/core/v1/sandbox` management routes through the -sandbox client. The console server forwards each signed-in `/core/v1/*` request by -prefix to its configured Core upstream, with the Core key (`OAC_WEB_CORE_KEY_FILE`) -as the upstream credential; Core alone decides whether the route exists. Browser -code must never receive that credential. - -Applications call Core's `/v1` directly with their own Project API keys and the -public API's route-specific headers. Nodes and Runtime daemons call Core's `/api/v1` -directly with their own machine credentials. The console returns 404 for `/v1` and -`/api/v1`, even with an explicit Bearer token. Deployment routing must send both to Core. -The console endpoint and the public application endpoint serve different purposes, -even if they share a host. - -Use the [installation guide](../getting-started/install.md) for deployment and the -[operations guide](../getting-started/operations.md) for storage, same-release repair and node -management. A Core, Web and PostgreSQL installation may have zero execution nodes. -Opening the console neither allocates compute nor invokes a model. Deployment -sandbox management selects E2B, Docker or microsandbox independently of an -application's caller-managed `self_hosted` Runtime, including its own E2B setup. - -## Server configuration and login - -The installer generates these from its `config.json` and `secrets/`; set them -yourself only for a Web you run without the installer. - -| Setting | Purpose | -| --- | --- | -| `OAC_WEB_ADDR` | Console listener address | -| `OAC_WEB_ORIGIN` | Exact browser-facing origin used for host and origin checks | -| `OAC_WEB_UPSTREAM` | Core HTTP(S) origin, without credentials, query or resource path | -| `OAC_WEB_CORE_KEY_FILE` | Absolute path to the private regular file containing the Core key | -| `OAC_WEB_DIST` | Absolute directory containing the built Web assets | - -The console exposes `GET /console/auth` and `POST /console/auth/login` and -`/logout`. The administrator signs in with the deployment's Core key, which the -installer writes to `secrets/core.key` under the installation directory (by default -`~/.oac/core/secrets/core.key`; see [Core key](../getting-started/operations.md#core-key)). -The server compares it in constant time and answers with a same-origin session -cookie held only in its memory; the key is never logged or returned, and the -browser does not store it. A console restart or a Core key rotation requires -signing in again. There are no console accounts, usernames or setup step, and the -Core key cannot call `/v1`. `GET /console/config` provides safe console -configuration to an authenticated browser. - -Use TLS for remote browser access and loopback listeners for local development. -Preserve the host/origin checks and the forwarding rules. The console refuses -requests with an `Upgrade` header, CONNECT and TRACE, and any path that `safePath` -rejects: an encoded `%`, dot segments, empty segments or backslashes. Before -forwarding, it strips the browser's Authorization, Cookie, Origin and Referer headers -and overwrites the actor header (`X-Core-Console-Actor`) with `console`, so a browser -cannot choose the audit actor label the service reports. The label is caller-declared -and display only. Keep deployment, application, node and provider credentials out of -`VITE_*`, browser storage, source files, URLs and logs. - -## Projects and application keys - -Installation creates no Project or key. Create them on **Projects and keys** or -through the [administrator API](../../contracts/agents-api/admin-api.md); see -[Projects and API keys](../getting-started/operations.md#projects-and-api-keys). - -## Verification and diagnosis - -1. Core `/healthz` proves process liveness only. -2. Console login followed by `GET /core/v1/projects` proves the authenticated - browser-to-console and console-to-Core path. -3. A Project key must work on its public resources and fail on management routes. - The Core key must fail on `/v1`; `/v1` through the console stays 404. -4. Cross-origin management writes must be rejected. Audit actor labels must ignore - a forged browser header. -5. Runtime observations and history report execution state separately from the - sandbox deployment read (`GET /core/v1/sandbox/deployment`). Neither a login nor - a successful deployment read proves model or sandbox readiness. - -A console login failure belongs to console authentication. An upstream 401 on a -management request points to the console's Core key or Core connection. A resource -deletion conflict must remain visible; it does not authorize an execution call. -Node and daemon `/api/v1` routes and native Runtime interfaces retain their own authentication. diff --git a/docs/web/core-process-metrics-requirements.md b/docs/web/core-process-metrics-requirements.md deleted file mode 100644 index fae811816..000000000 --- a/docs/web/core-process-metrics-requirements.md +++ /dev/null @@ -1,63 +0,0 @@ -# Core process CPU and memory: backend requirements - -Status: implemented by the Core backend for Monitor > Core metrics, Process. -The page and typed client (`packages/agents-client/src/core-metrics.ts`) consume -the fields below. Unavailable measurements show as missing ("—", "No data"). - -## Why - -Core is one `oac-core` process. Operators size and alert on its CPU and -resident memory, and today the response carries only the Go heap in use -(`process.memory_bytes`, `runtime.MemStats.Alloc`) and the goroutine count, read -when requested. The heap is neither what the operating system -charges the process nor what a container limit is compared against. - -## Contract - -Reuse `GET /core/v1/metrics?range=1h|6h|24h|7d`: add fields to -`process`; change nothing else. `memory_bytes` and `goroutines` keep their meaning. -Every figure Core cannot measure is `null`, never `0`. -Measured zero remains zero. New current values expire after 60 seconds without -a sample. Go heap and goroutine values continue to be read when requested. - -```json -"process": { - "memory_bytes": 190840832, - "goroutines": 214, - "cpu_cores": 0.35, - "cpu_limit_cores": 2, - "rss_bytes": 312475648, - "memory_limit_bytes": 1073741824, - "series": [ { "start": "RFC 3339", "cpu_cores": 0.41, "rss_bytes": 318767104 } ] -} -``` - -| Field | Meaning and source | -| --- | --- | -| `cpu_cores` | CPU the process used over the last sample interval (30 s), in cores: the increase in its user plus system CPU time (`getrusage(RUSAGE_SELF)` or `/proc/self/stat`) divided by the elapsed wall time. Null until the first interval completes | -| `cpu_limit_cores` | CPU available to the process: the cgroup v2 `cpu.max` quota divided by its period; without a quota, the CPUs the process may run on (`GOMAXPROCS`) | -| `rss_bytes` | Resident memory (`VmRSS` in `/proc/self/status`) | -| `memory_limit_bytes` | The cgroup v2 `memory.max`; null when it is `max` or unreadable | -| `series` | Per bucket of the response's range, the highest `cpu_cores` and `rss_bytes` observed, recorded by the existing 30-second sampler in the same in-process ring as the queue and database samples. Missing observations stay null; a restart does not backfill | - -CPU uses Linux `getrusage(RUSAGE_SELF)`. Missing or invalid readings, counter -resets, nonpositive elapsed time and sampling gaps over 60 seconds reset its -baseline. The next valid interval supplies CPU again. RSS and limits are -independent readings, so a missing CPU interval does not hide measured memory. - -Linux resolves cgroup v2 membership using `/proc/self/cgroup` and -`/proc/self/mountinfo`; it reads the process's own cgroup rather than assuming -the mount root. The actual cgroup v2 root has no quota interface and uses -`GOMAXPROCS`; an unreadable interface remains unknown. Limits describe that cgroup's configuration; ancestor-limit -discovery is outside this contract. Unreadable and malformed values stay null. -All procfs and cgroup reads have byte bounds. Non-Linux builds return null for -CPU usage, RSS and memory limit, with `GOMAXPROCS` as the CPU capacity fallback. -Unsupported process measurements do not mark execution or database health as -degraded. - -All four existing ranges retain their bucket counts and exclude the active -partial bucket. The process's partial first bucket stays null. Series report -independent observed maxima, so they need not come from the same sample. - -Not requested: whole-host CPU, memory or disk (Core may share its host; those -belong to host monitoring), per-request CPU profiles, or garbage-collector detail. diff --git a/docs/web/images/overview.png b/docs/web/images/overview.png deleted file mode 100644 index ffee4c4b0..000000000 Binary files a/docs/web/images/overview.png and /dev/null differ diff --git a/docs/web/protocol-coverage.md b/docs/web/protocol-coverage.md deleted file mode 100644 index 1f4d15698..000000000 --- a/docs/web/protocol-coverage.md +++ /dev/null @@ -1,233 +0,0 @@ -# Protocol coverage - -This matrix records which Core interfaces the OpenAgentCore console (`apps/web`) -consumes and for what. It is not a statement of public Agents API compatibility; -that inventory, its pinned baseline and its evidence live in the -[Agents API contract](../../contracts/agents-api/README.md). - -The console is a management tool. It reads and deletes each project's assets and -manages projects and keys through the administrator API (`/core/v1/**`), and it -administers sandbox nodes through `/core/v1/sandbox/**`. It sends no request to -the Agents API (`/v1/**`). Routes, response shapes, pagination and audit records of -the administrator API are defined by the [administrator API contract](../../contracts/agents-api/admin-api.md). - -## Interfaces - -| Interface | Paths | Authentication | Console use | -| --- | --- | --- | --- | -| Console server | `/console/auth`, `/console/auth/{login,logout}`, `/console/config` | Core key at sign-in, then the console session cookie | Sign-in with the Core key and sign-out; the node installer (`node_installer`, `node_installer_sha256`) and the providers whose node assets it holds (`node_artifacts`) | -| Administrator API | `/core/v1/**` outside `/core/v1/sandbox` | Core key, added by the console server | Projects, keys, resource reads and deletion, executor credentials, provenance, summaries, Core metrics, the installation | -| Sandbox administration | `/core/v1/sandbox/**` | Core key, added by the console server | Nodes page; fleet and capacity figures on Overview and Sandbox metrics; Runtime observations of every project | -| Agents API | `/v1/**` | Project API key | Not used. Wherever a new key is shown, and without any key on an active project's page, the console gives shell exports of `OPENAI_BASE_URL` (the installation's `api_base_url`) and `OPENAI_API_KEY` (the new key, or a placeholder for a key of the project) with `curl` and Python examples for `GET /v1/agents` and `POST /v1/agents/sessions`, and sends none of them; when the installation is `local_only` it says the API is reachable only on the Core machine, and without an `api_base_url` it says to set `public_url` | - -Browser requests are same-origin and carry only the console session. The browser -sends the Core key once, in the sign-in request body, and never stores it; it never -holds or sends an API key or an `OpenAI-Beta` header. Responses are validated: a malformed value is reported as a failure, or -marked as unrecognised where noted below, and never replaced by a guessed or zero -value. - -## Projects and keys - -| Operation | Route | Console use | -| --- | --- | --- | -| List projects | `GET /core/v1/projects` | Project filter on every project-scoped page; Projects and keys list; the Overview's Getting started (a project with an active key, and the newest active project, preferring one with an active key, whose call samples the first-Session step opens); `active_key_count` in the archive confirmation, which counts more when the project's key list shows more | -| Create project | `POST /core/v1/projects` | **Create project**, also from Getting started | -| Rename project | `POST /core/v1/projects/{project_id}` | **Rename** on an active project; the ID stays the same | -| Archive project | `POST /core/v1/projects/{project_id}/archive` | **Archive**: revokes every key; the project's assets stay readable and deletable | -| List keys | `GET /core/v1/projects/{project_id}/keys` | Key table of a project: name, prefix, status, creation and revocation time; the active keys the archive confirmation counts | -| Issue key | `POST /core/v1/projects/{project_id}/keys` | **Issue key** on an active project, also from Getting started; the plaintext is shown once | -| Revoke key | `DELETE /core/v1/projects/{project_id}/keys/{key_id}` | **Revoke**, with a warning when it is the project's last active key | - -Names are checked for length (projects 1–128 characters, keys 1–80) and control -characters before sending. An issued key's plaintext stays in component memory -until the administrator confirms it was saved and is never written to browser -storage, URLs or logs. There is no project deletion and no plaintext recovery. - -## Project resources - -Routes are relative to `/core/v1/projects/{project_id}` and return the same -objects as the corresponding public `/v1` operations, so the console applies the -public client's strict projections. Runtime observation and history exist only -here. Archived projects remain readable. - -| Resource | Reads used | Deletion | Creator | Console surface | -| --- | --- | --- | --- | --- | -| Agents | `/agents`, `/agents/{agent_id}` | Agent | `agent` | Agents list with usage per Agent; Agent page with instructions, tools, the saved model provider (never its key), generation settings and metadata | -| Environment templates | `/environment-templates`, `/environment-templates/{id}` | Template | `environment_template` | Templates list; Template page with every safe section | -| Skills | `/skills`, `/skills/{skill_id}`, `/skills/{skill_id}/versions`, Skill and version `/content` | Skill and Skill version | `skill` (list) | Skills list; Skill page with versions and archive downloads | -| Files | `/files` | File | `file` | Files list (metadata only) | -| Vaults | `/vaults`, `/vaults/{vault_id}`, `/vaults/{vault_id}/credentials` | Vault and Credential | `vault`, `credential` | Vaults list; Vault page with Credential metadata | -| Sessions | `/sessions`, `/sessions/{session_id}`, `/sessions/{session_id}/items`, `/sessions/{session_id}/turns`, `/sessions/{session_id}/runtime-observation`, `/sessions/{session_id}/runtime-history` | Session | `session` | Session log; Session page; Agent metrics; hosted Runtime rows | - -Resource-specific boundaries: - -- **Environment templates.** `env` and setup commands are write-only and never - returned, so the console cannot tell whether a Template has them. Inline files - report only their size. A Template with a section or field the client does not - recognise is marked; its recognised sections are still shown and nothing else is - guessed. -- **Skills.** A version upload, a default-pointer change and every other Skill write - belong to the project's keys. The console downloads the default or an exact - version as a ZIP, deletes versions (the default version is blocked while others - remain; deleting the only version deletes the Skill) and deletes a Skill after - its name is typed. -- **Files.** The list is read 100 per page, newest or oldest first. The - administrator API has no File content route, so the console offers no download. -- **Vaults.** Credential tokens are never returned. The console shows each - Credential's name, MCP server URL, authentication type and update time. -- **Sessions.** A malformed Session fails the read of its project instead of being - skipped. The console does not read single Turns, Artifacts, execution - configuration or Environment resources. - -## Executor credentials - -The credential operations below also serve the native daemon on Linux, macOS and -Windows. The existing **Connect a host** download command is the Linux container -installer; use the [self-hosted guide](../getting-started/self-hosted.md) for -`oac-daemon install` and lifecycle commands. Native credential rotation replaces -the configured credential file and restarts the daemon, without rerunning install. - -Core issues the credentials of a self_hosted executor, and the console is -where the administrator does it: the **Executor credentials** section of a -Session page, shown only when the Session's environment is `self_hosted`, for -that Session's `project_id` and `environment.id`. The routes accept only the -`self_hosted` environment of an existing (not deleted) Session in that project; -anything else returns 404. Writes have two conflicts, both 409: -`project_archived` (issuing or rotating in an archived project) and -`executor_credential_exists` (issuing an existing `key_id` without -`rotate: true`). In an archived project the section hides **Issue credential** -and **Rotate** behind a note and keeps the list and **Revoke**, which Core still -allows. - -| Operation | Route | Console use | -| --- | --- | --- | -| List credentials | `GET /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials` | The section's table, through the query cache: each credential's short `key_id` with its copy button, creation time (`created_at`, which rotation does not change) and status (Active, or Revoked with its time), active first. The credential itself is never listed | -| Issue or rotate | `POST /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials` with `{"key_id", "rotate"}` | Issue retains the generated key ID before submission. Rotation confirms that the old credential stops working. The private JSON is shown once for download or copy, never stored in browser storage or the query cache, and forgotten on Done. Save it as the credential file. Rotation keeps the same key ID: stop the daemon, replace that file, then start again. An existing key without rotation returns 409 `executor_credential_exists` | -| Revoke | `DELETE /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials/{key_id}` | **Revoke**, confirmed (the executor disconnects and won't retry; its daemon remains parked until the operator stops it), then the list is read again and shows the credential as Revoked; revoking again returns 204 | - -**Connect a host** offers Linux/macOS or PowerShell commands for the native -installer, prefilled with the Session's Environment ID, remote URL and workspace. -It links the native installation guide rather than inventing a release URL. -The command contains no credential: download the one-time JSON and supply its -absolute path to the installer. `wss` and loopback `ws` are accepted; missing or -invalid connection facts suppress the command. Installation does not start the -daemon; use the installed `bin/oac-daemon start` afterward. - -## Provenance and monitoring - -| Operation | Route | Console use | -| --- | --- | --- | -| Resource owners | `GET /core/v1/projects/{project_id}/resource-owners` | The Creator column of every resource list and the creator fact of detail pages, in batches of up to 100 IDs. An asset an administrator copied in an earlier release shows **Admin copy**; a resource without a record shows **Unknown** | -| Write operations | `GET /core/v1/projects/{project_id}/write-operations` | A project's write history, newest first, filtered by key and resource type, 50 per page | -| Summary | `GET /core/v1/summary` | Overview (per project), the Agents list (`group_by=agent`), a project's page (per project and `group_by=key`), Agent metrics (to skip idle projects, and usage by creating key since the start of the range), the Projects list (last activity) | -| Installation | `GET /core/v1/installation` | System's Installation facts (`public_url`, `api_base_url`, `installation_id`, `source_commit`) and read-only Startup settings (`configuration.settings` under its `path`, `apply_command` and `applied_at`; a sensitive setting shows only whether it is `configured`); `api_base_url` in the how-to-call samples under a new key and on an active project's page; `public_url` as the download origin and `--source-url` of the node install and uninstall commands (and the install command's `--core-url`) (the reverse proxy sends `/node-install/*` to the console); `local_only`, or a `public_url` that is not an HTTPS origin, stops Add node from issuing a command and Clean up the host from giving one; `path` and `apply_command` beside a sandbox configuration Core rejected. Overview, Nodes and System show a visible `local_only` warning with those repair instructions as copyable values; when `configuration` is null, they state that the path and command are unavailable. Nodes disables Add node with a visible reason, and Getting started leaves its sandbox step to do. A sensitive setting with a value, or an unknown member, fails the read; `configuration: null` shows a note | -| Core metrics | `GET /core/v1/metrics?range=` | Core metrics page; the Core popover on Overview. A Core without the route (404) is shown as not reporting; the popover then shows only Core's status. Measurements are defined in the [Core metrics contract](../../contracts/agents-api/core-metrics.md); the Process section's CPU and resident memory are a [requested extension](core-process-metrics-requirements.md) and show as missing until Core reports them | - -Summary figures are cumulative per Session and are not billing records. Sessions -without reported usage count toward coverage but not toward token sums, and the -console shows missing values as missing, never as zero. The administrator audit -log (`GET /core/v1/audit-log`) is not consumed; System shows the installation, -each harness's default model and the sandbox deployment. - -## Default models - -| Operation | Route | Console use | -| --- | --- | --- | -| List harnesses | `GET /core/v1/harnesses` | System's Default model cards (each harness's read-only `enabled` and `default`, and its `model_provider` view without the key); the Overview's Getting started (a default model on the default harness, or on any enabled harness when none is default) | -| Set or replace | `PUT /core/v1/harnesses/{harness}/model-configuration` | **Set** or **Replace**: the complete model configuration with its write-only provider key, never prefilled and never retried; a 400 shows Core's message in the form, and a 503 `credential_storage_unavailable` says Core has no credential encryption key; then the list is read again | -| Clear | `DELETE /core/v1/harnesses/{harness}/model-configuration` | **Clear**, confirmed, then the list is read again | - -The single-provider read (`GET /core/v1/harnesses/{harness}/model-configuration`) is not -consumed; the list carries each provider. - -## Sandbox administration - -| Operation | Route | Console use | -| --- | --- | --- | -| Deployment | `GET`, `POST`, `PUT /core/v1/sandbox/deployment` | Read the provider, the read-only `core_url` (config.json's `public_url`, shown in the setup review and never sent), reset state, installation ID and specification; a 409 `sandbox_configuration_error` (E2B with a loopback `public_url`) shows the shared client's fixed safe address-configuration message in the setup wizard, with the installation's config file and apply command, and leaves nothing to confirm; initialize the deployment with `resources` and the Docker or microsandbox `runtime` release, or with the E2B account and no `resources` (Core adopts the template build's CPU and memory); change its settings with the expected generation. E2B's `e2b.template_build` (status, CPU, memory, disk) shows on System, the Sandbox backend summary and Sandbox metrics, and sizes each sandbox when `specification.resources` is missing; microsandbox's `suspension` (idle and retention seconds) shows on System and the Nodes summary | -| Reset | `POST/DELETE /core/v1/sandbox/deployment/reset` | Explicitly clear hosted resources or cancel the remaining clear at the observed generation; consume Core’s remaining/offline projection | -| Nodes | `GET /core/v1/sandbox/nodes` | Nodes page; fleet on Overview; node capacity on Sandbox metrics. An online node's `diagnostic` (`docker_unavailable`, `docker_limits_unsupported`, `runtime_image_unavailable`, `kvm_unavailable`, `microsandbox_artifacts_unavailable`, `capacity_insufficient`, `provider_unavailable`; any other value reads as `provider_unavailable`) marks it degraded and names the reason and fix in the help tip beside its status on each of these and on the node's page. A node whose `core_url` (the address it enrolled with) differs from the deployment's `core_url` is named on the Nodes page as bound to an old address, to be removed and added again, and its status there and on its page reads Old address instead of its health; an empty `core_url` (a node Core did not enroll) is unknown, not old. **Add node** follows only the node whose `enrollment_id` equals its command's; a node enrolled before Core recorded it reports null and never matches | -| Node detail | `GET /core/v1/sandbox/nodes/{node_id}?range=1h\|6h\|24h` | Sandbox metrics node dialog: the host's CPU busy share and memory from its last heartbeat, and their history over the page's range. **Edit node** reads `host.effective_cpu_cores` and `host.total_memory_bytes` to show the host beside each sandbox's size and at most how many of those fit | -| Allocations | `GET /core/v1/sandbox/nodes/{node_id}/allocations` | Nodes page; Sandbox metrics. Under microsandbox, a node's page shows from `compute_phase_changed_at` how long each allocation has been in its compute phase and, while suspended, about when Core reclaims it (that time plus the deployment's `suspension.retention_seconds`); a null time shows a dash | -| Enrollment | `POST /core/v1/sandbox/enrollment-tokens` | **Add node**: the administrator sets the node's sandbox limits (`max_active`; `max_retained` only for microsandbox, equal to `max_active` for Docker) before Core issues a single-use token inside a command that verifies the installer checksum, with the command's `enrollment_id`, which the node it registers reports. The command runs the installer with sudo (a system service) and passes the token on standard input; root runs it directly. No ordinary-user installation or removal entry is exposed, and the log hint always names the system service. The command downloads the installer from the installation's `public_url`. Until the installation is read, when it can't be read, when it is `local_only` (or its `public_url` is not an HTTPS origin), or when `/console/config` lists `node_artifacts` without the deployment's provider (null reads as none; an absent field blocks nothing), no token is requested; Nodes disables Add node when the installation reports `local_only`, and the dialog retains its guards for pending or failed reads and the other blockers. It reads both again on opening and when the window regains focus | -| Update node | `PATCH /core/v1/sandbox/nodes/{node_id}` | **Edit node**: the name and sandbox limits together (the retained limit only for microsandbox; under Docker, Core sets it to the active limit) | -| Remove node | `DELETE /core/v1/sandbox/nodes/{node_id}` | Confirmed node removal; the row goes only after Core acknowledges the deletion, and a Clean up the host dialog then gives the host's uninstall command (requiring root or sudo; for a node enrolled with another address than the deployment's, also with `--force`, which skips the installer's confirmation with Core) | -| Runtime observations | `GET /core/v1/sandbox/runtime-observations` | Sandbox metrics: hosted Runtimes of every project, each labelled with its project; an E2B sandbox's dialog adds its `observation.disk` as used / limit (null elsewhere) | - -Signing in grants administration, so `/console/config` reports only the node -installer (`node_installer`, `node_installer_sha256`) and the providers -whose node assets the console holds (`node_artifacts`). These pages appear unless -the console has no `/console/config` (404) or reports `sandbox_admin: false`. An E2B -deployment has no nodes; its API key is write-only. The Runtime release sent for -Docker and microsandbox comes from the console's own `GET /node-install/manifest.json` -(the distribution manifest the node installer uses); without it the administrator -enters the release under advanced settings. - -## Writes - -- Deletion uses the administrator API with the same preconditions as the public - delete operation. Every deletion is confirmed. A 4xx keeps the dialog open with - Core's reason, a 404 counts as already deleted, and any other failure is reported - as uncertain and followed by a fresh read. -- The console offers Session deletion only for idle or failed Sessions without - required actions and never cancels work to make a Session deletable. -- Project, key, executor credential, deletion and sandbox writes are never - retried automatically. An executor credential issuance with an unknown - outcome (no answer, a 30-second timeout, a 5xx) opens an error dialog whose - next step is **Refresh list**. If the kept `key_id` is then listed, it was - issued and its secret lost: the console offers to rotate it (`rotate: true`) - for a fresh secret, shown once. If it is not listed, the next Issue sends the - same `key_id` with `rotate: false`; should that return 409 because the first - request was issued after all, the console reads the list again and offers the - same rotation only if the credential is listed as active in an active project, - and otherwise reports the issuance as rejected. A kept `key_id` that is - already listed is never sent again, and rotating or revoking it from its row - forgets it: the next Issue generates a new `key_id`. - -## Read bounds - -| Page | Reads | Bound | -| --- | --- | --- | -| Resource lists | Every page of the selected project, or of every project in parallel | 10,000 entries per project; a failed project is named and the rest still show | -| Session log | Every Session page of the selected projects, newest first | 10,000 per project; refreshed on request | -| Session page | Session, Items and Turns | 10,000 Items and Turns; polled every 5 s while the Session is in progress or waiting, backing off to 60 s on failures | -| Overview | Summary; Session lists for the 24-hour activity and the Sessions needing attention | 1,000 Sessions per project; idle projects are skipped; refreshed every 30 s while visible. Failed project or Session reads show Retry instead of a synthesized empty result; any retained or partial data is visibly qualified | -| Agent metrics | Summary; Session lists; Turns and Items of the most recently active Sessions | 2,000 Sessions listed per project; 200 Sessions read per load, 10 Turn and 5 Item pages each, 15 s per Session and 45 s per load | -| Sandbox metrics | Nodes and allocations; Runtime observations; hosted Sessions by ID; Runtime history | 100 hosted Sessions read per refresh; history for at most 24; refreshed every 30 s while visible | - -The aggregate endpoints that would replace these browser reads are proposed in -[Administrator metrics: backend requirements](admin-metrics-backend-requirements.md). - -## Not consumed - -- Any `/v1/**` route, including Session creation, Session events and their SSE - stream, message input, function results and cancellation. -- Creation or update of Agents, Environment templates, Skills, Files, Vaults or - Credentials, including uploads and Credential token replacement. -- Environment resources, Environment Files and Artifacts. - -## Terminology - -- **OpenAI Agents API** is the managed-harness API described in the official - [Agents guide](https://developers.openai.com/api/docs/guides/agents). OpenAgentCore - implements part of its pinned beta resource shape under `/v1`. -- **Administrator API** (also called the Web API) is OpenAgentCore's management - extension under `/core/v1`. It is not part of the public Agents API. -- **OpenAI Agents SDK** and **Responses API** are different interfaces and are not - used by the console. - -Any change to a consumed route, field, error or bound must update this matrix, the -client tests and the console's fixtures in the same change. - -## Evidence and changes - -The console's `/core/v1/*` prefix forwarding lives in -[core_routes.go](../../services/core-console/core_routes.go); authentication, origin -and path checks and header handling live in [server.go](../../services/core-console/server.go), -with Core key sign-in in [auth.go](../../services/core-console/auth.go). -Update this matrix when those boundaries or the console's reads change, and keep -detailed wire semantics in the administrator contract. - -Backend HTTP tests, console login and proxy checks and Project isolation -acceptance establish backend behavior. The console's unit tests and fixture-backed -browser tests cover its screens; they do not prove execution readiness. diff --git a/docs/web/roadmap.md b/docs/web/roadmap.md deleted file mode 100644 index 018340bad..000000000 --- a/docs/web/roadmap.md +++ /dev/null @@ -1,57 +0,0 @@ -# Frontend handoff and acceptance - -The backend management service, `AdminClient` and the React console that uses them -are implemented (PR #96). The console signs in through the console service, sends -same-origin management requests only and never calls `/v1`. - -Use the [architecture](architecture.md), [connection guide](core-connection.md) and -[administrator API contract](../../contracts/agents-api/admin-api.md) as the -contract. Public Agents API compatibility work is tracked in the -[public contract documentation](../../contracts/agents-api/README.md). - -## Delivered - -- Console login and same-origin `AdminClient` and sandbox management requests; the - browser holds no application key, and only the console server sends the Core key - to Core. -- Sign-in with the deployment's Core key; the browser keeps only the session - cookie. -- Getting started: signing in opens the Overview, whose checklist leads to - sandboxes, a default model provider, a project and its key, and a first Session; an - optional tour of the console. -- Projects and keys: create, rename, archive, issue with one-time display, revoke; - uncertain writes are reported, never replayed. -- Resource inspection and permitted deletion; no execution, resource editors, - Session input or cancellation. -- Monitoring: Overview, Core metrics, Agent metrics, Sandbox metrics and the Session - log, keeping missing data unknown and summaries distinct from billing. - -## Remaining frontend work - -- The Vite development proxy still forwards `/v1` with a local bearer for older - tooling (`scripts/core-doctor.mjs`, `.env.example`). The console no longer sends - `/v1`; remove the path together with that tooling. -- Run the browser acceptance below against the production console service and a - real Core; today it runs against a fixture. - -## Acceptance before calling the UI complete - -Verify login, Project isolation, shared access across a Project's keys, revocation, -archive retention, deletion conflicts and audit attribution through the -production console service. Browser acceptance must also cover denied cross-origin -writes, absent `/v1` proxying, secret handling and uncertain write outcomes. - -`apps/web/e2e` covers the browser side against `fixture-console.mjs`, a synthetic -console service: Core key sign-in, a refused key and repeated attempts, and sign-out, -with no credential in browser storage; a fresh install from sign-in to Getting started, empty pages and its actions; a harness's default model provider set, replaced and cleared with its key kept out of the browser, and a Core without a credential key; Project creation, one-time key display, revocation and archive; an -unconfirmed key issue that is reported and never replayed; a refused deletion that -keeps Core's reason; the monitor pages and a read-only Session conversation; node -enrollment and removal. -Every test also asserts that the browser sent nothing to `/v1` and no -Authorization header. Project isolation, shared key access, audit attribution and -cross-origin write denial are enforced by Core and the console service and are -covered by their backend tests. - -A backend test pass is evidence for the service it exercises. UI completion requires -separate browser evidence for the migrated screens; successful rendering alone is -insufficient. This handoff does not change native Runtime or application API ownership. diff --git a/packages/agents-client/README.md b/packages/agents-client/README.md index 55d1bafb4..cb71a0b52 100644 --- a/packages/agents-client/README.md +++ b/packages/agents-client/README.md @@ -1,10 +1,106 @@ -# Agents API Go client +# Agents client -`v1` configures the [official openai-go SDK](https://github.com/openai/openai-go/tree/v3.61.0), -pinned in the root `go.mod`. It returns the SDK's Session service directly. Request -types, response parsing, cursor pagination, events and errors remain SDK-owned. -The external protocol baseline remains the pinned Python SDK in -[`contracts/agents-api/upstream.json`](../../contracts/agents-api/upstream.json). +This package holds the typed clients that OpenAgentCore code uses to call Core: + +- a TypeScript client, `@agents-core-web/agents-client` (`src/index.ts`), for the Agents API (`/v1`) and the Core API (`/core/v1`). The console (`apps/web`) and the example application under `example/` use it; it is a private workspace package; +- a Go client, `v1`, that configures the official openai-go SDK for the Agents API. + +[API namespaces and credentials](../../docs/api/README.md) explains which credential each namespace takes. + +## TypeScript client + +| Class | Calls | Default base URL | Credential option | +| --- | --- | --- | --- | +| `OpenAIAgentsClient` | The Agents API: Agents, Sessions, Items, Turns, input, event streams, Environments and Environment templates, Files, Skills, Vaults | `/v1` | `token`: a Project API key | +| `AdminClient` | The Core API: installation, Projects and keys, summaries, each Project's resources, Session archive, diagnostics, execution configuration, Runtime observations and history, resource owners, audit log, write operations, executor credentials and installation commands, harness default models | `/core/v1` | `adminToken`: the Core key | +| `SandboxAdminClient` | Sandbox administration: deployment, reset, E2B discovery, nodes, allocations, enrollment | `/core/v1/sandbox` | `token`: the Core key | +| `CoreMetricsClient` | `GET /core/v1/metrics` | `/core/v1` | `token`: the Core key | + +Every constructor also takes `baseUrl` and `fetch`. A token may be a string or a function that returns one. Without a token the clients send no `Authorization` header: the console constructs `AdminClient`, `SandboxAdminClient` and `CoreMetricsClient` without one, and the console server adds the Core key. The Core API clients send same-origin credentials and refuse redirects; `OpenAIAgentsClient` sends `OpenAI-Beta: agents=v1` on the Agents routes that require it. + +Behavior shared by the clients: + +- **Strict responses.** Session, history, event, Environment and Core API responses are checked against their pinned shapes before they are returned. A malformed one throws `AgentCoreError` with status 502 and a code such as `invalid_session_resource` or `invalid_admin_response` (`CoreMetricsClient`: status 0, `invalid_response`) instead of passing on a guessed value. `listSessionsTolerant` reports Sessions it cannot recognise in `unrecognized` instead of failing. Agent responses are typed but not checked at run time. +- **Errors.** A non-2xx response throws `AgentCoreError` with `status`, `code`, `param`, `errorType` and, from the Core API, the optional `details` of the [Core error envelope](../../contracts/agents-api/core-errors.md). Invalid caller input throws `TypeError` before any request. +- **No retries or timeouts.** No client retries a request. Pass `signal` to cancel one. +- **Idempotency.** `createSession` takes an idempotency key and generates one when omitted; pass your own to retry a creation safely. `sendMessage`, `submitEvents`, `cancelTurn` and `submitFunctionResult` require a key of at most 128 bytes. `createIdempotencyKey()` makes one. +- **Streams.** `streamEvents` and `createSessionStream` decode the live event stream with `createSSEDecoder` and validate each event. Recover missed events with ordinary reads; the decoder does not resume with `Last-Event-ID`. + +### Saved Agent and deployment defaults + +A saved Agent can carry a harness, native harness parameters and a complete model provider. Keep the provider key in private application configuration: + +```ts +import { OpenAIAgentsClient } from "@agents-core-web/agents-client"; + +// apiBaseURL is the installation's API base URL, ending in /v1. +const client = new OpenAIAgentsClient({ baseUrl: apiBaseURL, token: projectAPIKey }); +const agent = await client.createAgent({ + model: "requested-model", + x_agents_core: { + harness: "codex", + harness_config: { model_reasoning_effort: "high" }, + model_provider: { + protocol: "responses", + base_url: modelBaseURL, + api_key: modelAPIKey, + }, + }, +}); + +// Later Sessions inherit the saved defaults; saving does not execute anything. +const session = await client.createSession({ + agent_id: agent.id, + environment: { type: "openai_hosted" }, + input: "Follow the saved Agent instructions.", +}); + +// Change only the model; the saved harness and provider stay. +await client.updateAgent(agent.id, { model: "another-model" }); + +// Clear only the provider; the harness stays. +await client.updateAgent(agent.id, { x_agents_core: { model_provider: null } }); +``` + +Reads return `ModelProviderView`, which has `api_key_configured` and never `api_key`; writes take `ModelProviderInput`, so a read cannot be resubmitted as an update. [Model execution](../../contracts/agents-api/model-execution.md#saved-defaults-and-precedence) defines what omission and `null` mean on each field, which provider a Session uses and which protocols each harness accepts. [Harness selection](../../contracts/agents-api/harness-selection.md) defines the `harness` field. + +With the Core key, `AdminClient` reads the configuration a Session froze at creation and sets each harness's deployment default: + +```ts +import { AdminClient } from "@agents-core-web/agents-client"; + +// On the Core host; keep the Core key out of application code. +const admin = new AdminClient({ baseUrl: "http://127.0.0.1:8091/core/v1", adminToken: coreKey }); + +const frozen = await admin.retrieveSessionExecutionConfiguration(projectId, session.id); +console.log(frozen.model.value, frozen.model.source, frozen.harness.value); +if (frozen.model_provider.status === "available") { + console.log(frozen.model_provider.configuration?.protocol); +} + +await admin.setHarnessModelConfiguration("codex", { + model_provider: { protocol: "responses", base_url: modelBaseURL, api_key: modelAPIKey }, + model: "requested-model", + harness_config: { model_reasoning_effort: "high" }, +}); +const defaults = await admin.retrieveHarnessModelConfiguration("codex"); +console.log(defaults.model, defaults.harness_config, defaults.model_provider.api_key_configured); +``` + +[Execution configuration queries](../../contracts/agents-api/execution-configuration.md) and [deployment defaults](../../contracts/agents-api/model-execution.md#deployment-defaults) define these reads and writes. + +### Checks + +```sh +pnpm --filter @agents-core-web/agents-client typecheck +pnpm --filter @agents-core-web/agents-client test +``` + +`pnpm test:web` and `make check-web` include them. + +## Go client + +`v1` configures the [official openai-go SDK](https://github.com/openai/openai-go/tree/v3.61.0), pinned in the root `go.mod`. `New` returns the SDK's Session service and `NewAgents` its complete Agents service. Request types, response parsing, cursor pagination, events and errors stay SDK-owned. ```go import ( @@ -15,14 +111,14 @@ import ( sessions, err := agentsclient.New(agentsclient.Config{ BaseURL: serviceBaseURL, // Includes /v1; use TLS for remote connections. - APIKey: serviceKey, // Execution tenant identity, not a product login token. + APIKey: projectAPIKey, }) if err != nil { return err } session, err := sessions.New(ctx, openai.BetaAgentSessionNewParams{ Agent: openai.BetaAgentSessionNewParamsAgent{ - Model: openai.String("requested-model"), + Model: openai.String("requested-model"), Instructions: openai.String("Follow the supplied instructions."), }, Environment: openai.EnvironmentParamUnion{ @@ -31,29 +127,12 @@ session, err := sessions.New(ctx, openai.BetaAgentSessionNewParams{ }, option.WithHeader("Idempotency-Key", operationID)) ``` -Use a stable, non-secret operation ID for a creation retry, with the same request. -Omitting the key creates a new Session on each call. SDK retries are disabled by -default. Requests honor the caller's context; the default HTTP timeout is 30 seconds. -An optional trusted HTTP client can configure the transport/timeout. Its cookie jar -is ignored and redirects are rejected. No OpenAI environment credentials or product -session cookies are inherited. Per-request SDK options are trusted application code; -do not accept them from end users. - -Use `sessions.Get`, `sessions.List` and the returned page's `GetNextPage` directly. -Errors can be inspected using `errors.As(err, &apiErr)` with `*openai.Error`. -SDK errors retain the request and response: log selected status/code fields, not -raw errors, request dumps or credentials. Constructing this client does not switch -Parsar's current execution flow or grant workspace/user permissions. - -The SDK includes more methods than the server currently supports. Only the -[documented Session subset](../../contracts/agents-api/README.md) is implemented; -other methods receive explicit service errors. Team orchestration belongs in Parsar -and depends on `openai-agents-python`, not this client package. - -`make check-go` includes configuration tests. The real-service harness in -`services/agents-api/tests/official_client.py` runs `TestService` with fresh tenants -and a dedicated PostgreSQL database. It validates Go-created Sessions through the -official Python SDK as well. No product database or model calls are involved. - -The TypeScript client also supports [saved Agent execution defaults](saved-agent-defaults.md), -with separate write-only provider inputs and safe read types. +- `BaseURL` must be an absolute HTTP(S) URL without credentials, query or fragment; `APIKey` must be non-empty and contain no whitespace. +- SDK retries are disabled. To retry a creation, send the same request with the same stable, non-secret `Idempotency-Key`; without one, each call creates a new Session. +- Requests honor the caller's context. The default HTTP timeout is 30 seconds; an optional trusted `HTTPClient` sets the transport and timeout. The client ignores its cookie jar and rejects redirects, and reads no `OPENAI_*` environment credentials. +- Per-request SDK options are trusted application code; do not accept them from end users. +- Inspect errors with `errors.As(err, &apiErr)` and `*openai.Error`. They retain the request and response, so log selected status and code fields, not raw errors, request dumps or credentials. + +The SDK has more methods than Core supports. The [Agents API contract](../../contracts/agents-api/README.md) lists the implemented routes; other methods receive explicit errors. + +`make check-agents-api` runs the Go client's tests. The real-service harness, `services/agents-api/tests/official_client.py`, also runs `TestService` against a Core with fresh Projects and a dedicated PostgreSQL database, and checks the Go-created Sessions through the official Python SDK. It calls no model. diff --git a/packages/agents-client/saved-agent-defaults.md b/packages/agents-client/saved-agent-defaults.md deleted file mode 100644 index b5f5b819b..000000000 --- a/packages/agents-client/saved-agent-defaults.md +++ /dev/null @@ -1,88 +0,0 @@ -# Saved Agent execution defaults - -The TypeScript client accepts a complete provider bundle when creating or updating -a saved Agent. Keep its key in private application configuration: - -```ts -import { OpenAIAgentsClient } from "@agents-core-web/agents-client"; - -const client = new OpenAIAgentsClient({ baseUrl: coreURL, token: tenantToken }); -const agent = await client.createAgent({ - model: "requested-model", - x_agents_core: { - harness: "codex", - harness_config: { model_reasoning_effort: "high" }, - model_provider: { - protocol: "responses", - base_url: modelBaseURL, - api_key: modelAPIKey, - }, - }, -}); - -// Future Sessions inherit the saved defaults; saving itself does not execute. -const session = await client.createSession({ - agent_id: agent.id, - environment: { type: "openai_hosted" }, - input: "Follow the saved Agent instructions.", -}); - -// Change only the model; the saved harness and provider remain configured. -await client.updateAgent(agent.id, { model: "another-model" }); - -// Clear only the provider, retaining the harness. -await client.updateAgent(agent.id, { x_agents_core: { model_provider: null } }); -``` - -Reads return `ModelProviderView`, containing safe endpoint/limit fields and -`api_key_configured`, never `api_key`. It is distinct from `ModelProviderInput`: -do not submit a read response as an update. Replacing a provider requires its full -protocol, endpoint and key; MiniMax Code also requires both token limits. -Defaults may be absent from a read: an Agent saved with only a provider has -no `harness`, and one saved with an empty extension reads `x_agents_core: {}`. - -On update, omitted fields follow the [extension contract](../../contracts/agents-api/harness-selection.md). -A null provider clears its saved bundle, while -`x_agents_core: null` clears the extension and its secret. An omitted harness -defers protocol compatibility to Session admission. Existing Sessions retain their -configuration snapshots. Session inline `agent.x_agents_core` accepts the harness -and its native `harness_config`; one-off provider overrides belong in the Session's -top-level `x_agents_core`. The model remains the ordinary `agent.model` field. -Use `{}` to clear native parameters; parameter names and validation belong to the -selected harness. See [the extension contract](../../contracts/agents-api/harness-selection.md) -for resolution and inheritance rules. - -A Session read preserves its explicit harness and native parameters in -`agent.x_agents_core`; it does not invent an explicit harness selection. -Administrators can inspect the configuration committed for one Session, including -the provider selection, without reading credentials: - -```ts -const frozen = await admin.retrieveSessionExecutionConfiguration(projectId, session.id); -console.log(frozen.model.value, frozen.model.source, frozen.harness.value); -// Historical provider snapshots may be unavailable. -if (frozen.model_provider.status === "available") { - console.log(frozen.model_provider.configuration?.protocol); -} -``` - -Configuration reads do not execute or wake Sessions. See -[the query contract](../../contracts/agents-api/execution-configuration.md). - -Deployment defaults use the same provider, model and native-parameter fields through -`AdminClient`: - -```ts -await admin.setHarnessModelConfiguration("codex", { - model_provider: { protocol: "responses", base_url: modelBaseURL, api_key: modelAPIKey }, - model: "requested-model", - harness_config: { model_reasoning_effort: "high" }, -}); -const defaults = await admin.retrieveHarnessModelConfiguration("codex"); -console.log(defaults.model, defaults.harness_config, defaults.model_provider.api_key_configured); -``` - -`ModelConfigurationInput` carries the write-only key. `ModelConfigurationView` -contains a safe provider view, and `CoreHarness.model_configuration` exposes the -same deployment resource with observation timestamps. Existing Sessions keep their -frozen configuration when defaults change. diff --git a/packages/agents-client/src/core-metrics.ts b/packages/agents-client/src/core-metrics.ts index 502e07d6f..9a3bf5fca 100644 --- a/packages/agents-client/src/core-metrics.ts +++ b/packages/agents-client/src/core-metrics.ts @@ -95,10 +95,9 @@ export interface CoreMetrics { memory_bytes: number | null; goroutines: number | null; /** - * Requested extension (docs/web/core-process-metrics-requirements.md): * CPU used over the last sample interval, in cores; the CPU available to - * the process; resident memory; its memory limit; and a series. Null until - * Core reports them. + * the process; resident memory; its memory limit; and a series + * (contracts/agents-api/core-metrics.md). Null when Core cannot measure them. */ cpu_cores: number | null; cpu_limit_cores: number | null; diff --git a/packages/claude-sdk-adapter/README.md b/packages/claude-sdk-adapter/README.md index 8fe0891f2..b67233dd4 100644 --- a/packages/claude-sdk-adapter/README.md +++ b/packages/claude-sdk-adapter/README.md @@ -1,15 +1,8 @@ # Claude SDK adapter -This package translates the pinned native Claude Agent SDK into OpenAgentCore's -common Executor and Turn lifecycle. It owns the private TypeScript bridge and -native SDK configuration. The [Go adapter](../../apps/parsar-daemon/internal/agent/claudesdk) -owns its subprocess and translates bridge frames into the shared Runtime protocol. +This package translates the pinned native Claude Agent SDK into OpenAgentCore's common Executor and Turn lifecycle. It owns the private TypeScript bridge and the native SDK configuration. The [Go adapter](../../apps/parsar-daemon/internal/agent/claudesdk) owns the bridge subprocess and translates bridge frames into the shared Runtime protocol. -Start with [Harness onboarding](../../contracts/agents-api/harness-onboarding.md) -for shared interfaces, registration and acceptance. This document owns the -Claude-specific bridge and package rules. Public operation qualification stays in -[the harness contract](../../contracts/agents-api/harnesses.md) and its linked -operation contracts; local readiness cannot expand that qualification. +[Harness onboarding](../../contracts/agents-api/harness-onboarding.md) defines the shared interfaces, registration and acceptance. Public qualification belongs to [the harness contract](../../contracts/agents-api/harnesses.md) and its linked operation contracts; local readiness cannot expand it. ## Develop and verify @@ -21,430 +14,104 @@ pnpm --filter @parsar/claude-sdk-adapter test make check-claude-sdk ``` -The package test compiles TypeScript before running its tests. The Make target -also builds and checks the relocatable Runtime artifact. Changes to native -execution require real-provider acceptance through Core and Runtime, including -continuation and cancellation, followed by the repository's required `make check`. -Use [the deployment guide](../../services/agents-api/deploy/claude/README.md) -for the qualified environment and Runtime build. +The package test compiles TypeScript before running its tests. The Make target also builds and checks the relocatable Runtime artifact. Changes to native execution require real-provider acceptance through Core and Runtime, including continuation and cancellation, followed by the repository's required `make check`. Use [the deployment guide](../../services/agents-api/deploy/claude/README.md) for the qualified environment and Runtime build. ## Bridge and native lifecycle -`packages/claude-sdk-adapter` privately owns the pinned official TypeScript SDK -and native message translation. The Go `claudesdk.NewExecutorFactory` uses the shared -owned process runner and emits the existing daemon delta/error/Done frames. -The SDK owns the model loop. Its narrow stdio protocol carries Executor preparation and identified Turn starts, -text deltas, function calls/results/receipts, active input/receipts, usage snapshots -and terminal result/error plus settlement; native translation stays inside the adapter. -The private `native_model_options` input contains only the native options compiled -by Go after shared Harness validation. The bridge checks object/field structure -and maps those fields explicitly to SDK options; enum membership, budget ranges -and thinking combinations belong solely to the Go adapter declaration. The -public `harness_config` object does not cross this private boundary. - -With `observe_messages`, it also emits the existing neutral `output_message` -start/completion snapshots and tags deltas with the native Messages API message -ID, not the SDK event UUID. Text blocks in one native message share that identity. -The SDK's per-block assistant snapshots replace draft block text; only native -`message_stop` completes the message, without replaying its text as another delta. -Thinking/tool-only messages produce no text Items; interrupted messages retain -their streamed partial text. No phase is inferred from the final result. -Turn-owned native work and output draining precede reuse. Executor close releases -the SDK Query and native process. The private `turn_settled` frame requires -`confirmed` independently of `reusable`: confirmed native Turn/cancellation -settlement, confirmed resource cleanup, and reuse eligibility are separate facts. -Unknown or nonempty interrupt receipts and unsettled input/function/tool work -remain unconfirmed even after successful teardown. Go rejects cancellation and -AwaitSettlement when native confirmation is missing or false, or its own receipt -ledger remains unsettled. A confirmed Turn may be non-reusable after cleanup; -that state alone does not turn a verified cancellation into an error. -Confirmed native cancellation may settle unanswered function calls after result -admission closes and callbacks drain. A submitted function result still requires -its native application receipt, including when the MCP request aborts. - -`claudesdk.Config.Workspace` is an operator binding for the selected workspace and -native state. It enables native Bash/Read/Edit and admitted host functions in the -existing SDK loop. Native tools run with the launching user's permissions on all -platforms; there is no inner sandbox, protected-root deny policy or managed shell -wrapper. Managed isolation belongs to the outer Environment, which must exclude -other tenants' and broader application credentials. An ordinary native install -provides no such boundary. Directory selection is not tenant authorization. -The adapter's existing tool callback authorizes unattended execution in native -`default` permission mode. It does not use the CLI permission-bypass flag, which -Claude rejects for root accounts. - -`Config.Env` selects readiness and native process variables. Explicit tool env is -applied to tool execution, but this is not a guarantee that same-user tools cannot -read credentials or history from local files. Workspace hooks retain their event, -identity and lifecycle responsibilities, not security enforcement. Process groups -and Windows Jobs provide cancellation and descendant cleanup, not isolation. -Supported MCP and subagent combinations require their own qualification. The -existing `none` profile keeps its tool inventory. Packaged `workspace_tools` -establishes bridge support, not outer host isolation or public API admission. -The dedicated Runtime composes public preparation, placement quotas, command Items -and Files ownership. Real-provider acceptance verifies effects, cancellation and -same-history continuation for the actual platform and outer deployment. - -The private bridge accepts `executor_prepare` without model input. It freezes -validated configuration and resume identity, checks required history, and retains -one native process and SDK Query across Turns. Preparation requires initialization -and acknowledgement of required hooks while the input iterator remains empty. -An `executor_ready` receipt permits later `turn_start` messages containing only -Turn identity and ordered input; configuration replacement and concurrent starts -are rejected. Every Turn event carries its originating `turn_id`. Native Session -identity and actual tool inventory are checked before `input_ready`. - -Each Turn ends with a result/error and `turn_settled`, independently of process -exit. The outer input iterator remains open for later Turns. `turn_cancel` invokes -the native interrupt control for that exact Turn. Unconfirmed input, native child -work or queue state invalidates the Executor and requires close before replacement. -EOF, owner signals and invalid control input close owned resources. Preparation -may write native metadata and perform startup traffic; readiness does not prove -provider authentication, complete sandbox health or placement authorization. - -`claudesdk.NewExecutorFactory` binds this bridge to `agent.Executor`. Direct-call -and read-only preparation wrappers delegate to the same implementation. Runtime -execution uses the Executor registry for both none and workspace configurations. -Its owner context spans all Turns; a Turn's caller cannot replace fixed resources. -A failed preparation returns its Executor when cleanup remains unconfirmed. -Installed runtime checks are cached by package/file identity, while capability -and request validation still run for each Executor configuration. - -The optional private `agent.WorkspaceReader` on this Executor and its delegated wrappers -requires the packaged `workspace_read` feature. It sends bounded relative -paths to that same SDK Query's native `readFile` control. Only the adapter combines -the path with the frozen workspace root; callers cannot replace the placement. -The pinned native read handler awaits file-handle close before its successful -base64 response. The adapter validates bytes and truncation, bounds each result -to 1 MiB and each request to 8 KiB, and admits one read at a time. Its continuous -bridge output consumer retains read receipts during preparation and across Turns. -Caller cancellation detaches observation without cancelling the Run or discarding -an admitted waiter; its original deadline still applies. Owner closure stops -admission. Native null, malformed receipts, timeout and interrupted delivery remain -uncertain and stop the owner; local reap is not a successful read settlement. -The SDK's nullable result catches all native/control errors, so it cannot distinguish -missing files from denial or transport failure. This does not provide a public -Files endpoint, snapshot consistency, placement registration or idle owner policy. -The qualified live workspace fixture also checks binary, empty and bounded reads -before input and during real execution, plus effects before cancellation and reads -on fresh-process history continuation. - -The optional private `agent.WorkspaceDirectoryLister` uses the portable -`workspace_directory` bridge feature. Node reads directory metadata under the -selected workspace; there is no Linux `/proc/self/fd` dependency. Public daemon -Files operations share the Go Binding implementation on all platforms. Their -relative path and result bounds define the Files API, not Harness permissions. - -Directory requests are bounded to 8 KiB and 1,000 immediate entries, with explicit -truncation, literal names, kinds, and sizes only for regular files. They do not promise -ordering, snapshots, recursion, or public pagination. Missing and permission errors -are returned only from distinguishable filesystem outcomes; unknown results stop the -owner. Each operation closes its directory before a successful receipt. -Caller cancellation, Turn transitions and owner shutdown retain the existing workspace -read settlement rules. This adapter gap fill alone does not enable public Claude Files; the dedicated -Runtime integration supplies public placement and ownership. - -With `ObserveToolObservations`, private workspace execution requires the packaged -`workspace_command_observations` feature and emits the existing neutral command -snapshots. Match root, current-query native Bash call/result identities after input; -ignore historical replay, synthetic and child work. Preserve exact command text and -the native per-call textual result, including native rendering or truncation. This -is final native output, not incremental stdout/stderr or reconstructed interleaving. -Native error results are failed; unambiguous structured interruption is incomplete. -Missing results close as incomplete after the observation drain; query cancellation -does not overwrite an already observed native failure. Do not infer an -exit code from rendered text or supply cwd/duration without qualified native fields. -Preparation alone emits no command. Cold continuation must not reissue historical -observations. This private translation does not enable public workspace admission, -Read/Edit Items or Files ownership. - -The private adapter also accepts typed anonymous HTTP and static-bearer HTTPS MCP -declarations on the trusted `environment:none` harness host. The packaged readiness -report must include `mcp_http_tools`; discovery advertises that feature only when -present, and execution -rechecks the installed bundle before dispatching an MCP request. An unchanged SDK -version alone cannot qualify an older bridge. Authenticated private requests also -require the packaged `mcp_http_bearer_auth` feature at discovery and dispatch. -The daemon generates a separate environment reference for each server and launch; -only those references enter the bridge request and native SDK configuration. -The native HTTP client expands them from its owned process environment. Literal -bearers must never enter SDK MCP headers because that configuration enters argv. -Readiness probes receive no per-request bearer environment. Token validation is -shared with the Codex adapter; credential storage remains an opaque-string contract. -Public Claude MCP admission reuses the shared resolver, immutable Session snapshots -and neutral Item/event projection. -The API checks the supported profile before persistence, during device selection -and again before claiming execution; a missing runtime capability leaves work queued. - -MCP queries use the SDK's main-thread Agent definition to restrict model-visible -tools, in addition to empty built-ins, strict MCP configuration, empty setting -sources and default-deny permissions. Permission allowlists alone do not restrict -the native model inventory. Null selects all tools from a declared server; an empty -list selects none. Host functions compose with those selections. Native server -status supplies original tool identities; map their normalized native aliases while -preserving the original names in observations. Native status deduplicates aliases, -so it does not prove a complete original server inventory. A native PreToolUse hook -waits for inventory verification before admitting root calls and denies unverified, -mismatched or cancelled calls. The native Agent restriction controls model-visible -tools; inventory verification is not a barrier before the model request. -Anonymous HTTP declarations explicitly set an empty Authorization header to disable -native OAuth and automatic credential injection. Preserve that header; do not erase -native history or credentials to enforce this boundary. Servers that reject a blank -Authorization header, normalized name collisions and inventory changes during a -query require separate validation; this profile covers static inventories. -Private SDK status/control objects can contain expanded authentication headers. -Read only connection and tool identity fields; never retain, log or publish raw -status/configuration or control responses. Diagnostic projections must whitelist -safe fields. This does not permit filtering actual model/tool output to hide a leak. -The bounded adapter profile currently requires connected servers, reserves the -`functions` label, accepts alphanumeric/underscore/hyphen server labels and -alphanumeric/underscore/hyphen/dot selected tool names, and excludes remote -environments. Required startup is separately qualified by `mcp_http_required`. -All HTTP MCP queries use native SDK startup and an empty input iterator to -confirm initialization hooks. Required declarations additionally check connected -server status before the initial prompt is released exactly once. Pending, failed, -missing or ambiguous required status rejects before input; native startup timeouts are retained without -an adapter retry loop. Normal system/init still verifies Session identity and the -complete inventory before input readiness/tool authority. Optional servers retain -their existing inventory checks without a new pre-input connection requirement. -A Runtime must advertise the concrete required-initialization capability; there -is no fallback to an older execution path. - -Public Claude static-bearer HTTPS MCP reuses the shared -Vault attachment, frozen selection and scoped decryption path. Selection and final -preclaim require the existing bearer capability; shared authentication dispatch -uses capability/placement checks rather than a Codex-name restriction. Missing keys -or failed lookup/decryption never fall back to anonymous execution; an attached -Vault with no matching credential may remain anonymous. These are execution limits, -not saved-Agent schema restrictions or changes to the official protocol. - -Root assistant tool calls and live root user results produce the existing neutral -MCP observations. Correlate actual Session/call identities; exclude replay, -synthetic and subagent work and keep host function receipts separate. Preserve -the exact native `tool_use_result` when one result is unambiguous, otherwise the -per-call result content. Native errors remain observed native errors. The SDK can -replace annotated MCP content with rendered structuredContent and flatten MCP -errors; these observations do not claim original MCP envelope fidelity or hosted -output parity. Do not reconstruct lost fields or infer output from model prose. -Unfinished observed calls become incomplete on shutdown, without claiming that -remote tool effects were cancelled. Rich content, native truncation and asynchronous -MCP task results remain unverified. - -For unmanaged bootstrap, daemon `connect` optionally registers this factory as -`claude_sdk` when the operator sets `OAC_RUNTIME_CLAUDE_SDK_ENTRYPOINT` to the absolute packaged `dist/main.js`. -`OAC_RUNTIME_CLAUDE_SDK_NODE` selects Node (default: `node` on PATH). Discovery resolves -Node once and checks that exact configuration before pairing; the SDK's bounded -runtime check is independent of legacy CLI version probes. A ready SDK alone is -sufficient to start the daemon. No configuration means no SDK probe or descriptor; -failed readiness reports an unavailable descriptor with a rejecting factory. -Runtime checks establish local readiness, not provider authentication. Installed -daemons use `start` and their verified installation manifest for adapter selection -and activation; ambient activation variables cannot extend that selection. See -[the native installation contract](../../deploy/install/README.md#native-daemon-installer). - -SDK state lives under `paths.ProfileDir(profile)/runtime/claude-sdk`, independently -of the replaceable runtime bundle. Both the entrypoint and managed state root must -be absolute. Background re-execution inherits operator configuration; it does not -persist provider credentials in pairing profiles. Product `claude_code` remains -unchanged. Product registration explicitly opts existing engines into -`WorkspaceAuthoring`; the authoring registry wraps only that opt-in. SDK registration -bypasses product capability-download, skill-upload and workspace-authoring wrappers. -It does not accept caller-supplied environment variables or business write authority. - -The SDK descriptor advertises the validated daemon subset, including durable -Turns/input receipts, text observations, function tools, raw usage and restrictive -execution controls. It does not advertise permissions, product authoring, legacy -raw tool Items, general web-search control or text-verbosity levels. Router admission -for `environment:none` uses the available engine capability, not an engine name. -The independent API selects new Session engines through `OAC_DEFAULT_HARNESS` -(`codex` by default, `claude_sdk` or `mcode`); existing Sessions keep their stored engine. -This remains the deployment default; the optional Core harness extension selects -an enabled engine for one saved or inline Agent configuration. API admission, -device selection and the final preclaim check share the execution service's narrow -engine policy without importing native adapters. Selected engines require the common -durable execution capabilities. Codex retains its general search/verbosity checks; -Claude uses its restrictive profile without claiming those general capabilities. -Idle and initial-input Session creation qualify the resolved configuration before -persistence; saved Agent resources remain independent of engine restrictions. -Claude additionally requires medium verbosity and explicit object-root function -schemas. Function-result batches normalize through the existing shared parser. Claude accepts -text results and, on `none` and Core-managed Docker `openai_hosted`, successful -ordered inline PNG/JPEG results. Unqualified placements, failed image results and -invalid/remote references reject before any batch write, preserving pending calls -and retry identity. Public qualification receives the full neutral result so -success-dependent limitations remain in the profile. Image-bearing delivery alone -requires Runtime function-result image support; text results and function -declarations do not acquire that requirement. These are implementation limits, not changes to the upstream contract. -Do not bypass them by dropping fields, changing model identity or fabricating usage. -Operators may configure the daemon provider environment or the deployment default -model provider for `claude_sdk` (HTTPS `base_url` and a write-only key), which Core -freezes in the Session's encrypted snapshot and delivers as the adapter-owned -`model_provider`; it never enters public Session configuration. The adapter exclusively selects the -provider environment and removes credentials from native tool environments. Product `claude_code` and product execution are unchanged. -The `none` public profile accepts only -text, explicit model/system instructions, managed state, exact native resume and -declared functions with ordered text or successful inline PNG/JPEG results, and the HTTP MCP subset -described above. It rejects unsupported request -options and disables built-in tools and undeclared MCP discovery. -`DisableExecutionEnvironment` and `DisableSubagents` are accepted assertions about -the single-Agent restrictive profile. Omission does not enable built-in tools. -Explicit Subagent observation enables only its qualified native delegation tools, -with admission before start and verified child identity before workspace authority. -Public function/MCP combinations remain unqualified with Subagents. Single-Agent -new and resumed queries use the SDK's empty built-in tool set, explicit function MCP -configuration and allowlist, strict MCP configuration and empty user/project/local -setting sources. Without HTTP MCP declarations, native initialization and real -provider request inventories must contain only the declared host functions. Managed operator policy may further -restrict execution; it must not widen the profile. This limits model tool access, -not native state files or filesystem access by an explicitly supplied host function; -it is not sandbox/file isolation. The private factory accepts typed execution -controls only for disabled search and medium text verbosity. Search remains excluded -by the native tool inventory; medium retains the SDK's default text generation, -without adding instructions or changing caller input. The pinned SDK has no native -verbosity-level option: low/high and enabled search remain explicit implementation -gaps. Missing/invalid fields in a supplied control block fail before native setup; -omitting the block keeps the same restrictive profile. Public engine admission is -qualified separately by the API policy described above. -Use the SDK's history lookup before explicit resume; never fall back to a new -Session. Native files remain device-affine under a caller-selected managed -runtime directory. The launch configuration supplies trusted provider environment; -request options cannot supply environment variables or business write authority. -Omitted, null and empty `system_prompt` map to empty SDK instructions only at this -adapter boundary; null model values and unsupported options remain rejected. - -The internal SDK function-server helper uses the maintained MCP server's public -request handlers and standard Tool/CallToolResult types. It snapshots definitions -and forwards JSON Schema without a JSON Schema-to-Zod conversion; supplied tools -are always loaded. Native call identity comes from the pinned harness's -`claudecode/toolUseId` MCP metadata, independently of request IDs, names or arrival -order. Missing identities and undeclared tools fail before invoking the host. -Return content/error fields unchanged over MCP and forward its per-request abort -signal. The private Go factory connects declared functions through this helper and -reuses the daemon function-call/result interface and opt-in neutral observations. -The native function-server registry must contain exactly those functions. SDK allowlisting admits -only these host callbacks; the host still owns result decisions and any business -permission checks. It grants no runtime-token business authority. - -Function results remain pending after stdin/MCP delivery. A matching live, root -native user tool_result confirms application only when its Session/call identity, -error flag and ordered content match the submission. Text matches exactly; each -submitted image position must remain a valid native base64 image. Native resizing -or re-encoding may change image bytes. This acknowledges incorporation into native -history, not byte/pixel fidelity or completed provider consumption. Public Items -retain the original caller content; real image-dependent model responses separately -qualify usability. Ignore replayed, synthetic and -subagent messages. Native error text joins the submitted text parts with newlines; -neutral observations retain their original order and separate failure status. -Missing/mismatched receipts fail the execution; do not replay unknown delivery. -Result submission waits at most ten seconds for a receipt and cancels uncertain -execution on timeout. Invalid or unsupported image results fail before consuming -a pending call. Function state belongs to one live Run and ends with it; the -existing router owns receipt retry/conflict handling. This does not establish -crash recovery or exactly-once effects. Public schemas outside MCP's object-root -contract, failed image results and remote image references remain admission/execution gaps. - -Each SDK result supplies one native usage snapshot, including reported failures. -`Usage.Raw.claude_sdk_result` holds the latest; queries with multiple native results -also retain all snapshots in order under `claude_sdk_results`. Main-loop `usage` -is per native turn, while query-pipeline `modelUsage` and estimated `total_cost_usd` -are cumulative within the query. Retain subtype/error provenance and earlier -snapshots even when a later failure reports zero counters. Reuse the latest full -snapshot set in Usage and Done; never sum cumulative measurements. Each Executor owns one SDK query, including cold resume. Raw snapshots explicitly -identify per-native-turn usage versus query-cumulative model usage and cost. A new -Turn has a fresh snapshot list, but query totals may include earlier Turns; never -represent those totals as consumption by the current Turn. Missing native results do not imply zero consumption. SDK estimates stay -in raw evidence, outside the billed cost field; do not select an arbitrary model -or invent missing public token breakdowns. The API does not parse native counters. -Precise public usage projection, unreported costs and crash/partial accounting -remain gaps; the native snapshot alone is not complete protocol Usage compatibility. - -Active text uses the SDK's `AsyncIterable` input, with a fresh -native UUID mapped to each daemon input ID. A native query may fold text into its -current native turn or queue another; one daemon Run can therefore contain several -native turns. Never promise Codex's same-native-turn semantics. Writes, queued -notifications and user-message echoes do not confirm consumption. Only matching -root assistant/partial/result `user_message_uuids` (or the singular fallback) -confirm applied input. Typed mid-turn folds may appear only on the native result. -Preserve that receipt even when the result reports failure. Check pending functions -after the query drains: the SDK may dispatch later-turn callbacks before the -earlier result handler finishes. -Keep Turn input admission open until every submitted input has a consuming result, -even when an earlier result reports an empty native queue. Close admission before -releasing final receipt waiters. The outer SDK iterator remains open across Turns. -Cancellation resolves unconfirmed pending receipts as unknown and interrupts the -exact native Turn. Successful Cancel requires confirmed Turn settlement; process -exit alone cannot establish a successful cancellation. Reuse also requires an -empty confirmed native queue and settled child work. Otherwise close the Executor. -The settled CancellationOutcome retains verified native identity, partial text -and observed Usage; a requested resume identity alone is not evidence. Caller -wait expiry reports failure/unknown while cleanup retains ownership. Output -backpressure cannot turn missing native facts into confirmed settlement. Closed -Turn output precedes successful AwaitSettlement; the settled outcome remains -readable when connection loss prevents publication. -A receipt timeout after a full write preserves the process and pending identity -without redelivery; a blocked write is cancelled and released. -The private adapter permits one input awaiting consumption and at most 63 extra -inputs per Run, preserving the native 64-UUID receipt bound. Durable receipt opt-in -separates bounded writes from native consumption waits; calls without it retain -the router's ten-second deadline. Larger input capacity and interrupted-input -recovery remain separate work. Daemon registration alone does not establish public acceptance. - -Current public qualification is recorded in the -[harness contract](../../contracts/agents-api/harnesses.md) and its linked operation -contracts. Keep native execution evidence separate from local registration and -packaging checks. Live adapter acceptance uses a real provider with private -credentials; fixture tests cannot substitute for it. +### Bridge protocol + +`packages/claude-sdk-adapter` privately owns the pinned official TypeScript SDK and native message translation. The Go `claudesdk.NewExecutorFactory` uses the shared owned process runner and emits the daemon's delta, error and Done frames. The SDK owns the model loop. Its narrow stdio protocol carries Executor preparation and identified Turn starts, text deltas, function calls/results/receipts, active input/receipts, usage snapshots and terminal result/error plus settlement; native translation stays inside the adapter. The private `native_model_options` input contains only the native options compiled by Go after shared Harness validation. The bridge checks object/field structure and maps those fields explicitly to SDK options; enum membership, budget ranges and thinking combinations belong solely to the Go adapter declaration. The public `harness_config` object does not cross this private boundary. + +With `observe_messages`, it also emits the neutral `output_message` start/completion snapshots and tags deltas with the native Messages API message ID, not the SDK event UUID. Text blocks in one native message share that identity. The SDK's per-block assistant snapshots replace draft block text; only native `message_stop` completes the message, without replaying its text as another delta. Thinking/tool-only messages produce no text Items; interrupted messages retain their streamed partial text. No phase is inferred from the final result. Turn-owned native work and output draining precede reuse. Executor close releases the SDK Query and native process. The private `turn_settled` frame requires `confirmed` independently of `reusable`: confirmed native Turn/cancellation settlement, confirmed resource cleanup, and reuse eligibility are separate facts. Unknown or nonempty interrupt receipts and unsettled input/function/tool work remain unconfirmed even after successful teardown. Go rejects cancellation and AwaitSettlement when native confirmation is missing or false, or its own receipt ledger remains unsettled. A confirmed Turn may be non-reusable after cleanup; that state alone does not turn a verified cancellation into an error. Confirmed native cancellation may settle unanswered function calls after result admission closes and callbacks drain. A submitted function result still requires its native application receipt, including when the MCP request aborts. + +### Workspace execution + +`claudesdk.Config.Workspace` is an operator binding for the selected workspace and native state. It enables native Bash/Read/Edit and admitted host functions in the SDK loop. Native tools run with the launching user's permissions on all platforms; the adapter adds no inner sandbox, protected-root deny policy or managed shell wrapper. Isolation belongs to the outer Environment ([Runtime and outer isolation](../../docs/design-principles.md#runtime-and-outer-isolation)), which must exclude other tenants' and broader application credentials; an ordinary native install provides no such boundary, and directory selection is not tenant authorization. The adapter's tool callback authorizes unattended execution in native `default` permission mode. It does not use the CLI permission-bypass flag, which Claude rejects for root accounts. + +`Config.Env` selects readiness and native process variables. Explicit tool env is applied to tool execution, but same-user tools can still read credentials or history from local files. Workspace hooks keep their event, identity and lifecycle responsibilities; they are not security enforcement. Process groups and Windows Jobs provide cancellation and descendant cleanup, not isolation. Supported MCP and subagent combinations require their own qualification. The `none` profile keeps its tool inventory. Packaged `workspace_tools` establishes bridge support, not outer host isolation or public API admission. The dedicated Runtime composes public preparation, placement quotas, command Items and Files ownership. Real-provider acceptance verifies effects, cancellation and same-history continuation for the actual platform and outer deployment. + +### Executor preparation and Turns + +The private bridge accepts `executor_prepare` without model input. It freezes validated configuration and resume identity, checks required history, and retains one native process and SDK Query across Turns. Preparation requires initialization and acknowledgement of required hooks while the input iterator remains empty. An `executor_ready` receipt permits later `turn_start` messages containing only Turn identity and ordered input; configuration replacement and concurrent starts are rejected. Every Turn event carries its originating `turn_id`. Native Session identity and actual tool inventory are checked before `input_ready`. + +Each Turn ends with a result/error and `turn_settled`, independently of process exit. The outer input iterator remains open for later Turns. `turn_cancel` invokes the native interrupt control for that exact Turn. Unconfirmed input, native child work or queue state invalidates the Executor and requires close before replacement. EOF, owner signals and invalid control input close owned resources. Preparation may write native metadata and perform startup traffic; readiness does not prove provider authentication, complete sandbox health or placement authorization. + +`claudesdk.NewExecutorFactory` binds this bridge to `agent.Executor`. Direct-call and read-only preparation wrappers delegate to the same implementation. Runtime execution uses the Executor registry for both none and workspace configurations. Its owner context spans all Turns; a Turn's caller cannot replace fixed resources. A failed preparation returns its Executor when cleanup remains unconfirmed. Installed runtime checks are cached by package/file identity, while capability and request validation still run for each Executor configuration. + +### Workspace reads and directory listing + +The optional private `agent.WorkspaceReader` on this Executor and its delegated wrappers requires the packaged `workspace_read` feature. It sends bounded relative paths to that same SDK Query's native `readFile` control. Only the adapter combines the path with the frozen workspace root; callers cannot replace the placement. The pinned native read handler awaits file-handle close before its successful base64 response. The adapter validates bytes and truncation, bounds each result to 1 MiB and each request to 8 KiB, and admits one read at a time. Its continuous bridge output consumer retains read receipts during preparation and across Turns. Caller cancellation detaches observation without cancelling the Run or discarding an admitted waiter; its original deadline still applies. Owner closure stops admission. Native null, malformed receipts, timeout and interrupted delivery remain uncertain and stop the owner; local reap is not a successful read settlement. The SDK's nullable result catches all native/control errors, so it cannot distinguish missing files from denial or transport failure. The reader provides no public Files endpoint, snapshot consistency, placement registration or idle owner policy. The qualified live workspace fixture also checks binary, empty and bounded reads before input and during real execution, plus effects before cancellation and reads on fresh-process history continuation. + +The optional private `agent.WorkspaceDirectoryLister` uses the portable `workspace_directory` bridge feature. Node reads directory metadata under the selected workspace; there is no Linux `/proc/self/fd` dependency. Public daemon Files operations share the Go Binding implementation on all platforms. Their relative path and result bounds define the Files API, not Harness permissions. + +Directory requests are bounded to 8 KiB and 1,000 immediate entries, with explicit truncation, literal names, kinds, and sizes only for regular files. They do not promise ordering, snapshots, recursion, or public pagination. Missing and permission errors are returned only from distinguishable filesystem outcomes; unknown results stop the owner. Each operation closes its directory before a successful receipt. Caller cancellation, Turn transitions and owner shutdown follow the workspace read settlement rules. The lister alone does not enable public Claude Files; the dedicated Runtime integration supplies public placement and ownership. + +### Command observations + +With `ObserveToolObservations`, private workspace execution requires the packaged `workspace_command_observations` feature and emits the neutral command snapshots. Match root, current-query native Bash call/result identities after input; ignore historical replay, synthetic and child work. Preserve exact command text and the native per-call textual result, including native rendering or truncation. This is final native output, not incremental stdout/stderr or reconstructed interleaving. Native error results are failed; unambiguous structured interruption is incomplete. Missing results close as incomplete after the observation drain; query cancellation does not overwrite an already observed native failure. Do not infer an exit code from rendered text or supply cwd/duration without qualified native fields. Preparation alone emits no command. Cold continuation must not reissue historical observations. This private translation does not enable public workspace admission, Read/Edit Items or Files ownership. + +### HTTP MCP + +The private adapter accepts typed anonymous HTTP and static-bearer HTTPS MCP declarations on the trusted `environment:none` harness host. The packaged readiness report must include `mcp_http_tools`; discovery advertises that feature only when present, and execution rechecks the installed bundle before dispatching an MCP request. An unchanged SDK version alone cannot qualify an older bridge. Authenticated private requests also require the packaged `mcp_http_bearer_auth` feature at discovery and dispatch. The daemon generates a separate environment reference for each server and launch; only those references enter the bridge request and native SDK configuration. The native HTTP client expands them from its owned process environment. Literal bearers must never enter SDK MCP headers because that configuration enters argv. Readiness probes receive no per-request bearer environment. Token validation is shared with the Codex adapter; credential storage remains an opaque-string contract. Public MCP admission, Vault credential selection and their failure rules belong to [public MCP connection origin](../../contracts/agents-api/environments.md#public-mcp-connection-origin) and [HTTP MCP execution](../../services/agents-api/README.md#http-mcp-execution). + +MCP queries use the SDK's main-thread Agent definition to restrict model-visible tools, in addition to empty built-ins, strict MCP configuration, empty setting sources and default-deny permissions. Permission allowlists alone do not restrict the native model inventory. Null selects all tools from a declared server; an empty list selects none. Host functions compose with those selections. Native server status supplies original tool identities; map their normalized native aliases while preserving the original names in observations. Native status deduplicates aliases, so it does not prove a complete original server inventory. A native PreToolUse hook waits for inventory verification before admitting root calls and denies unverified, mismatched or cancelled calls. The native Agent restriction controls model-visible tools; inventory verification is not a barrier before the model request. Anonymous HTTP declarations explicitly set an empty Authorization header to disable native OAuth and automatic credential injection. Preserve that header; do not erase native history or credentials to enforce this boundary. Servers that reject a blank Authorization header, normalized name collisions and inventory changes during a query require separate validation; this profile covers static inventories. Private SDK status/control objects can contain expanded authentication headers. Read only connection and tool identity fields; never retain, log or publish raw status/configuration or control responses. Diagnostic projections must whitelist safe fields; filtering actual model or tool output does not fix a leak. The adapter profile requires connected servers, reserves the `functions` label, accepts alphanumeric/underscore/hyphen server labels and alphanumeric/underscore/hyphen/dot selected tool names, and excludes remote environments. Required startup is separately qualified by `mcp_http_required`. All HTTP MCP queries use native SDK startup and an empty input iterator to confirm initialization hooks. Required declarations additionally check connected server status before the initial prompt is released exactly once. Pending, failed, missing or ambiguous required status rejects before input; native startup timeouts are retained without an adapter retry loop. Normal system/init still verifies Session identity and the complete inventory before input readiness/tool authority. Optional servers keep their inventory checks without a pre-input connection requirement. A Runtime must advertise the concrete required-initialization capability; there is no fallback to another execution path. + +Root assistant tool calls and live root user results produce the neutral MCP observations. Correlate actual Session/call identities; exclude replay, synthetic and subagent work and keep host function receipts separate. Preserve the exact native `tool_use_result` when one result is unambiguous, otherwise the per-call result content. Native errors remain observed native errors. The SDK can replace annotated MCP content with rendered structuredContent and flatten MCP errors; these observations do not claim original MCP envelope fidelity or hosted output parity. Do not reconstruct lost fields or infer output from model prose. Unfinished observed calls become incomplete on shutdown, without claiming that remote tool effects were cancelled. Rich content, native truncation and asynchronous MCP task results remain unverified. + +### Deferred function discovery + +Workspace deferred-function discovery uses native ToolSearch alongside the normal workspace tool profile. Its readiness feature is `workspace_tool_search`, in addition to `tool_search` and the workspace/function features. Qualification, combination limits and model-policy limitations are owned by [Deferred function discovery](../../contracts/agents-api/tool-search.md). + +### Registration and state + +For unmanaged bootstrap, daemon `connect` optionally registers this factory as `claude_sdk` when the operator sets `OAC_RUNTIME_CLAUDE_SDK_ENTRYPOINT` to the absolute packaged `dist/main.js`. `OAC_RUNTIME_CLAUDE_SDK_NODE` selects Node (default: `node` on PATH). Discovery resolves Node once and checks that exact configuration before pairing; the SDK's bounded runtime check is independent of CLI version probes. A ready SDK alone is sufficient to start the daemon. No configuration means no SDK probe or descriptor; failed readiness reports an unavailable descriptor with a rejecting factory. Runtime checks establish local readiness, not provider authentication. Installed daemons use `start` and their verified installation manifest for adapter selection and activation; ambient activation variables cannot extend that selection. See [the native installation contract](../../deploy/install/README.md#native-daemon-installer). + +SDK state lives under `paths.ProfileDir(profile)/runtime/claude-sdk`, independently of the replaceable runtime bundle. Both the entrypoint and managed state root must be absolute. Background re-execution inherits operator configuration; it does not persist provider credentials in pairing profiles. The daemon registers `claude_sdk` directly, without the capability-download, skill-upload and `WorkspaceAuthoring` wrappers of its product agent kinds. It accepts no caller-supplied environment variables or business write authority. + +### Descriptor and execution profile + +The SDK descriptor advertises the validated daemon subset, including durable Turns/input receipts, text observations, function tools, raw usage and restrictive execution controls. It does not advertise permissions, product authoring, raw tool Items, general web-search control or text-verbosity levels. Router admission for `environment:none` uses the available engine capability, not an engine name. + +Core owns public admission for Claude: [harness selection](../../contracts/agents-api/harness-selection.md) chooses the engine for each Session; one [engine policy](../../contracts/agents-api/harness-onboarding.md#add-the-engine-to-core) serves API admission, device selection and the final claim; the [qualified operations](../../contracts/agents-api/harnesses.md#current-qualified-operations) table records Claude's medium-only verbosity and object-root function schemas; [function result images](../../contracts/agents-api/function-result-images.md) defines which placements accept image results; and [deployment defaults](../../contracts/agents-api/model-execution.md#deployment-defaults) define the model provider Core freezes for a Session. The adapter receives that provider as the adapter-owned `model_provider`, never in public Session configuration. Without one, a `none` host uses the daemon's own provider environment. The adapter alone selects the provider environment and removes credentials from native tool environments. + +The `none` profile accepts only text, explicit model/system instructions, managed state, exact native resume and declared functions with ordered text or successful inline PNG/JPEG results, and the HTTP MCP subset described above. It rejects unsupported request options and disables built-in tools and undeclared MCP discovery. `DisableExecutionEnvironment` and `DisableSubagents` are accepted assertions about the single-Agent restrictive profile. Omission does not enable built-in tools. Explicit Subagent observation enables only the native delegation tools described under [Subagents](#subagents). Single-Agent new and resumed queries use the SDK's empty built-in tool set, explicit function MCP configuration and allowlist, strict MCP configuration and empty user/project/local setting sources. Without HTTP MCP declarations, native initialization and real provider request inventories must contain only the declared host functions. Managed operator policy may further restrict execution; it must not widen the profile. The profile limits model tool access; it does not isolate native state files or filesystem access by an explicitly supplied host function. The private factory accepts typed execution controls only for disabled search and medium text verbosity. Search remains excluded by the native tool inventory; medium retains the SDK's default text generation, without adding instructions or changing caller input. The pinned SDK has no native verbosity-level option, so low and high verbosity and enabled search are unsupported. Missing/invalid fields in a supplied control block fail before native setup; omitting the block keeps the same restrictive profile. Use the SDK's history lookup before explicit resume; never fall back to a new Session. Native files remain device-affine under a caller-selected managed runtime directory. The launch configuration supplies trusted provider environment; request options cannot supply environment variables or business write authority. Omitted, null and empty `system_prompt` map to empty SDK instructions only at this adapter boundary; null model values and unsupported options remain rejected. + +### Function server and results + +The internal SDK function-server helper uses the maintained MCP server's public request handlers and standard Tool/CallToolResult types. It snapshots definitions and forwards JSON Schema without a JSON Schema-to-Zod conversion; supplied tools are always loaded. Native call identity comes from the pinned harness's `claudecode/toolUseId` MCP metadata, independently of request IDs, names or arrival order. Missing identities and undeclared tools fail before invoking the host. Return content/error fields unchanged over MCP and forward its per-request abort signal. The private Go factory connects declared functions through this helper and reuses the daemon function-call/result interface and opt-in neutral observations. The native function-server registry must contain exactly those functions. SDK allowlisting admits only these host callbacks; the host still owns result decisions and any business permission checks. It grants no runtime-token business authority. + +Function results remain pending after stdin/MCP delivery. A matching live, root native user tool_result confirms application only when its Session/call identity, error flag and ordered content match the submission. Text matches exactly; each submitted image position must remain a valid native base64 image. Native resizing or re-encoding may change image bytes. This acknowledges incorporation into native history, not byte/pixel fidelity or completed provider consumption. Public Items retain the original caller content; real image-dependent model responses separately qualify usability. Ignore replayed, synthetic and subagent messages. Native error text joins the submitted text parts with newlines; neutral observations retain their original order and separate failure status. Missing/mismatched receipts fail the execution; do not replay unknown delivery. Result submission waits at most ten seconds for a receipt and cancels uncertain execution on timeout. Invalid or unsupported image results fail before consuming a pending call. Function state belongs to one live Run and ends with it; the router owns receipt retry/conflict handling. This does not establish crash recovery or exactly-once effects. + +### Usage + +Each SDK result supplies one native usage snapshot, including reported failures. `Usage.Raw.claude_sdk_result` holds the latest; queries with multiple native results also retain all snapshots in order under `claude_sdk_results`. Main-loop `usage` is per native turn, while query-pipeline `modelUsage` and estimated `total_cost_usd` are cumulative within the query. Retain subtype/error provenance and earlier snapshots even when a later failure reports zero counters. Reuse the latest full snapshot set in Usage and Done; never sum cumulative measurements. Each Executor owns one SDK query, including cold resume. Raw snapshots explicitly identify per-native-turn usage versus query-cumulative model usage and cost. A new Turn has a fresh snapshot list, but query totals may include earlier Turns; never represent those totals as consumption by the current Turn. Missing native results do not imply zero consumption. SDK estimates stay in raw evidence, outside the billed cost field; do not select an arbitrary model or invent missing public token breakdowns. The API does not parse native counters. The native snapshot is not a complete public Usage: precise public usage projection, unreported costs and crash or partial accounting are not provided. + +### Active input + +Active text uses the SDK's `AsyncIterable` input, with a fresh native UUID mapped to each daemon input ID. A native query may fold text into its current native turn or queue another; one daemon Run can therefore contain several native turns. Never promise Codex's same-native-turn semantics. Writes, queued notifications and user-message echoes do not confirm consumption. Only matching root assistant/partial/result `user_message_uuids` (or the singular fallback) confirm applied input. Typed mid-turn folds may appear only on the native result. Preserve that receipt even when the result reports failure. Check pending functions after the query drains: the SDK may dispatch later-turn callbacks before the earlier result handler finishes. Keep Turn input admission open until every submitted input has a consuming result, even when an earlier result reports an empty native queue. Close admission before releasing final receipt waiters. The outer SDK iterator remains open across Turns. Cancellation resolves unconfirmed pending receipts as unknown and interrupts the exact native Turn. Successful Cancel requires confirmed Turn settlement; process exit alone cannot establish a successful cancellation. Reuse also requires an empty confirmed native queue and settled child work. Otherwise close the Executor. The settled CancellationOutcome retains verified native identity, partial text and observed Usage; a requested resume identity alone is not evidence. Caller wait expiry reports failure/unknown while cleanup retains ownership. Output backpressure cannot turn missing native facts into confirmed settlement. Closed Turn output precedes successful AwaitSettlement; the settled outcome remains readable when connection loss prevents publication. A receipt timeout after a full write preserves the process and pending identity without redelivery; a blocked write is cancelled and released. The private adapter permits one input awaiting consumption and at most 63 extra inputs per Run, preserving the native 64-UUID receipt bound. Durable receipt opt-in separates bounded writes from native consumption waits; calls without it keep the router's ten-second deadline. Larger input capacity and recovery of interrupted input are not supported. Daemon registration alone does not establish public acceptance. + +The [harness contract](../../contracts/agents-api/harnesses.md) and its linked operation contracts record current public qualification. Keep native execution evidence separate from local registration and packaging checks. Live adapter acceptance uses a real provider with private credentials; fixture tests cannot substitute for it. + +## Subagents + +The adapter uses the pinned Claude Agent SDK's native Agent and SendMessage execution; it implements no model loop. An explicit Runtime request enables the `oac_worker` agent; ordinary requests keep their tools. The packaged `subagent_resources` readiness feature gates this request. + +A child identity comes from native task admission and persisted child metadata. The metadata's `toolUseId` must identify the parent's original Agent call; `parentAgentId` identifies a nested parent, and root children require native `spawnDepth: 1`. The child history must belong to the same root Session and child ID. The SDK resolves its persisted conversation chain; the adapter reads the corresponding private original records for ownership and timestamps that the SDK's public TypeScript message shape omits. + +The first own native user record has a null `parentUuid`. Its timestamp supplies `opened_at`, in seconds, and the first Turn's creation and start time, in milliseconds: the original input time, not discovery time or a copied parent record. The fixed native metadata has no separate creation timestamp. Reopening the Runtime preserves these values. Each later own input starts a child Turn; native end-turn assistant records supply completion time. Own messages, reasoning and native Bash receipts keep their native IDs and ordering. Completion leaves the Subagent active and idle: the adapter emits no closed state because this native profile has no qualified close operation. + +The native query owns child execution and cleanup. PreToolUse admission reserves each native Agent or idle-child SendMessage call before execution. Native `task_started` associates the call and child ID; completion releases that reservation. The frozen limit applies across the child tree, excludes the root, and defaults to six. Unknown call associations fail closed. SendMessage to a running child is rejected; only idle continuation is qualified. Native background execution, alternate agent types, worktree isolation and per-call model overrides are rejected. + +Workspace children use native Bash with the same launching-user permissions as the parent, and the daemon and adapter add no filesystem, permission or network sandbox. A child's workspace tool calls are denied until native task admission has verified its identity. Workspace hooks keep their execution and event responsibilities but are not a private-file boundary: tools can access Runtime state that the host user can access. Isolation belongs to the outer Environment. Functions and MCP combined with Subagents are not qualified and are rejected explicitly ([subagents contract](../../contracts/agents-api/subagents.md)); functions and MCP without Subagents are unaffected. Claude on Windows requires Git Bash. + +Cancellation uses an adapter-owned effect receipt only after the query owner confirms native process exit, because the fixed native history can end at a tool call without a cancellation result or timestamp. Each receipt is linked into the native history directory atomically, without overwriting an earlier receipt, and records the child, own Turn, spawn call and confirmed effect time. Native records stay unchanged. Replay uses that same timestamp; it never takes a new cancellation time from an unfinished history. Child Items and the cancelled Turn precede the root cancellation event. Missing native exit confirmation or a missing receipt does not imply a terminal child state. ## Runtime artifact -`make build-claude-sdk-runtime` exports the compiled bridge and pinned production -SDK/MCP dependencies, including the native package for the build host, into a -platform/architecture/libc-specific `.tar.gz` and SHA256 file under -`${OAC_DEV_HOME:-$HOME/.oac}/build/claude-sdk-runtime`. `CLAUDE_SDK_BUILD_DIR` -may select another absolute output directory. The production dependency closure -requires Node20 or newer; Node22 is the tested version. This standalone archive -requires operator-supplied Node and is independent of product sources, services -and databases. -It does not add Node or SDK assets to the Agents API binaries/image. - -The build validates source manifests with the repository-pinned pnpm frozen -install and compiles into fresh managed staging, never exporting incremental -checkout output. It then uses modern `pnpm deploy` with command-scoped workspace injection -and its dedicated frozen lock. The adapter has no workspace dependencies; keep -that boundary explicit. Do not enable injection globally or replace this with a -custom dependency copier. Export only compiled `dist` and production dependencies; -retain their package metadata, lockfile and licenses. Check dependency links stay -inside the export, pinned SDK/MCP/native versions, native `--version`, and bridge -startup before publishing the archive. Startup with stdin EOF is an import check, -not model execution acceptance. `make check-claude-sdk` includes this artifact check. - -Extract the archive into a fresh managed runtime directory on a matching host -and use its absolute `dist/main.js` as the private factory entrypoint. Validate -relocation and real provider cancellation/continuation before accepting an -artifact. Linux x64/glibc with Node22 is the currently exercised platform; -other hosts require their own native acceptance. Do not reuse a bundle across -platforms or libc variants. The native installer bundles Node and owns -user-managed activation; release publication remains separate. Operator-configured -discovery and registration remain available for unmanaged bootstrap as specified -above. - -The exported `dist/runtime_check.js` companion is the local readiness contract. -It checks Node20+, installed SDK/MCP/native versions against the package manifest, -contained dependency resolution, native startup, and the exact `dist/main.js` bridge -with stdin EOF. It emits one versioned JSON report without calling a model or -creating Session state. The artifact check reuses this companion and separately -checks all exported links, the lockfile and source pins. `claudesdk.CheckRuntime` -uses the same Node, entrypoint and environment as execution, -with shared process-group ownership, bounded output and a 15-second deadline -plus bounded cleanup. Both native and bridge probes have five-second limits. -Return unavailable on failed or malformed probes; never forward native diagnostics -or treat local readiness as provider authentication, public capability acceptance -or filesystem isolation. The native installer reuses this readiness check after -copying its release components. - - -Workspace deferred-function discovery uses native ToolSearch alongside the normal -workspace tool profile. Its readiness feature is `workspace_tool_search`, in -addition to `tool_search` and the existing workspace/function features. Qualification, -combination limits and model-policy limitations are owned by -[Deferred function discovery](../../contracts/agents-api/tool-search.md). +`make build-claude-sdk-runtime` exports the compiled bridge and pinned production SDK/MCP dependencies, including the native package for the build host, into a platform/architecture/libc-specific `.tar.gz` and SHA256 file under `${OAC_DEV_HOME:-$HOME/.oac}/build/claude-sdk-runtime`. `CLAUDE_SDK_BUILD_DIR` may select another absolute output directory. The production dependency closure requires Node20 or newer; Node22 is the tested version. This standalone archive requires operator-supplied Node and is independent of product sources, services and databases. It does not add Node or SDK assets to the Agents API binaries/image. + +The build validates source manifests with the repository-pinned pnpm frozen install and compiles into fresh managed staging, never exporting incremental checkout output. It then uses `pnpm deploy` with command-scoped workspace injection and its dedicated frozen lock. The adapter has no workspace dependencies; keep that boundary explicit. Do not enable injection globally or replace this with a custom dependency copier. Export only compiled `dist` and production dependencies; retain their package metadata, lockfile and licenses. Check dependency links stay inside the export, pinned SDK/MCP/native versions, native `--version`, and bridge startup before publishing the archive. Startup with stdin EOF is an import check, not model execution acceptance. `make check-claude-sdk` includes this artifact check. + +Extract the archive into a fresh managed runtime directory on a matching host and use its absolute `dist/main.js` as the private factory entrypoint. Validate relocation and real provider cancellation/continuation before accepting an artifact. Linux x64/glibc with Node22 is the currently exercised platform; other hosts require their own native acceptance. Do not reuse a bundle across platforms or libc variants. The native installer bundles Node and owns user-managed activation; release publication remains separate. Operator-configured discovery and registration remain available for unmanaged bootstrap as specified above. + +The exported `dist/runtime_check.js` companion is the local readiness contract. It checks Node20+, installed SDK/MCP/native versions against the package manifest, contained dependency resolution, native startup, and the exact `dist/main.js` bridge with stdin EOF. It emits one versioned JSON report without calling a model or creating Session state. The artifact check reuses this companion and separately checks all exported links, the lockfile and source pins. `claudesdk.CheckRuntime` uses the same Node, entrypoint and environment as execution, with shared process-group ownership, bounded output and a 15-second deadline plus bounded cleanup. Both native and bridge probes have five-second limits. Return unavailable on failed or malformed probes; never forward native diagnostics or treat local readiness as provider authentication, public capability acceptance or filesystem isolation. The native installer reuses this readiness check after copying its release components. diff --git a/packages/claude-sdk-adapter/SUBAGENTS.md b/packages/claude-sdk-adapter/SUBAGENTS.md deleted file mode 100644 index 3514af3ab..000000000 --- a/packages/claude-sdk-adapter/SUBAGENTS.md +++ /dev/null @@ -1,60 +0,0 @@ -# Native subagent observations - -The adapter uses the pinned Claude Agent SDK 0.3.269 and its native Agent and -SendMessage execution. It does not implement a model loop. An explicit Runtime -request enables `oac_worker`; ordinary requests retain their previous tools. -The packaged `subagent_resources` readiness feature gates this request. - -A child identity comes from native task admission and persisted child metadata. -The metadata's `toolUseId` must identify the parent's original Agent call; -`parentAgentId` identifies a nested parent, and root children require native -`spawnDepth: 1`. The child history must belong to the same root Session and child -ID. The SDK resolves its persisted conversation chain; the adapter reads the -corresponding private original records for ownership and timestamps that the -SDK's public TypeScript message shape omits. - -The first own native user record has a null `parentUuid`. Its timestamp supplies -`opened_at`, in seconds, and the first Turn's creation and start time, in -milliseconds. This is the original input timestamp, not discovery time or a -copied parent record. The fixed native metadata has no separate creation -timestamp. Reopening the Runtime preserves these values. Each subsequent own -input starts a child Turn; native end-turn assistant records supply completion -time. Own messages, reasoning and native Bash receipts retain native IDs and -ordering. Completion leaves the Subagent active and idle: the adapter emits no -closed state because this native profile has no qualified close operation. - -The existing native query owns child execution and cleanup. PreToolUse admission -reserves each native Agent or idle-child SendMessage call before execution. -Native `task_started` associates the call and child ID; completion releases that -reservation. The frozen limit applies across the child tree, excludes the root, -and defaults to six. Unknown call associations fail closed. SendMessage to a running child is rejected; only idle continuation is qualified. -Native background -execution, alternate agent types, worktree isolation and per-call model overrides -are not admitted in this profile. - -Workspace children use native Bash with the same launching-user permissions as -the parent. The daemon and adapter add no inner filesystem, permission or network -sandbox. Workspace hooks retain their execution and event responsibilities, but -are not a private-file boundary. Tools can access Runtime state that the host user -can access. Managed isolation belongs to the outer Environment. Functions and MCP -with subagents are not qualified combinations and are rejected explicitly; this -does not affect existing functions/MCP paths without subagents. Claude on Windows -requires Git Bash; native Windows validation remains pending. - -The earlier adapter mechanism qualification is historical evidence for its tested -Docker Runtime and binaries. It used real Kimi calls for two child identities, -their histories, workspace writes, private credential/history/proc-read denial, -strict concurrent admission and same-ID continuation from a new process. Its -private-file denial results describe the former inner sandbox and are not current -behavior. They do not qualify the current bypass execution or additional native -platforms. Core public resource and pagination acceptance remain separate checks. - -Cancellation uses an adapter-owned effect receipt only after the existing query -owner confirms native process exit. The fixed native history can end at a tool -call without a cancellation result or timestamp. Each receipt is linked into -the native history directory atomically, without overwriting an -earlier receipt, and records the child, own Turn, spawn call and confirmed effect -time. Native records remain unchanged. Replay uses that same timestamp; it -never obtains a new cancellation time by observing an unfinished history. Child -Items and the cancelled Turn precede the root cancellation event. Missing native -exit confirmation or a missing receipt does not imply a terminal child state. diff --git a/packages/mcode-harness/README.md b/packages/mcode-harness/README.md index bcb2cd161..00c23b067 100644 --- a/packages/mcode-harness/README.md +++ b/packages/mcode-harness/README.md @@ -1,71 +1,40 @@ # MiniMax Code workspace bridge -This adapter companion keeps MiniMax Code ACP, model loop and history. Its trusted -MCP server exposes six original native tools inside the upstream-vendored Linux -sandbox. The pinned native CLI has one source patch: the original SQLite task -admission transaction enforces the daemon's Subagent concurrency limit before -child work starts. Foreground, background, nested and idle-child append admissions -share that transaction; terminal native tasks release capacity. No second model -or scheduling loop is introduced. -Hosted public execution is not qualified by this package alone. - -The harness process and ACP Session use a private control directory. Builtin file -tools are disabled. Only the adapter registers this bridge; callers cannot supply -its command, profile, working directory or environment. Workspace project files -are read through sandboxed tools rather than imported by the privileged harness. -Native diff/undo capture is not provided by this path. Common Files/Artifacts use -the bound public workspace independently of the native control directory. - -`source.json` pins the CLI, native tool and sandbox source. The pinned npm package -supplies native runtime dependencies; the CLI is built from source into the same -qualified artifact. On Linux x86_64: +This companion package lets the daemon run MiniMax Code with OpenAgentCore's workspace. MiniMax Code keeps its own ACP Session, model loop and history. The package supplies a trusted MCP server (`bridge.mjs`), which the daemon registers as `oac_workspace` (its MCP server info names it `oac-workspace`). It exposes six native MiniMax Code tools rooted at the Session's workspace: `workspace_read`, `workspace_write`, `workspace_edit`, `workspace_bash`, `workspace_grep` and `workspace_glob`. The tools run as the daemon's user with ordinary permissions; the package adds no inner sandbox. The [MiniMax Code Runtime](../../services/agents-api/deploy/mcode/README.md) guide owns the Runtime image, configuration and qualified deployment. + +One patch script (`patch-native.mjs`) makes three edits to the pinned native CLI source. The SQLite task-admission transaction enforces the daemon's Subagent concurrency limit before child work starts; foreground, background, nested and idle-child append admissions share that transaction, and terminal native tasks release capacity. ACP initialization reports `oac/subagents` metadata: its version, the applied workspace tool policy and the admission limit. The native tool catalog applies the `protected-mcp-v1` tool gate described under [Subagents and cancellation](#subagents-and-cancellation). No second model or scheduling loop is introduced. Hosted public execution is not qualified by this package alone. + +## Workspace tools + +The native process, its ACP Session and the workspace tools share the Session's workspace as their working directory; native configuration, Skills and history stay in the private Session data directory. Builtin file tools are disabled, so project files are read and written through the bridge's tools. Only the adapter registers this bridge; callers cannot supply its command, profile, working directory or environment. Native diff/undo capture is not provided by this path. Common Files and Artifacts use the same bound workspace. + +For each tool call, the bridge starts `launch.mjs` with the Session's private profile (`workspace-profile.json`, written by the daemon). The launcher checks that the profile's `workspace` is a canonical absolute path and that `network` is `enabled`, creates the `scratch` directory, and runs the worker in the workspace. An optional `toolEnvFile` supplies the Runtime's frozen tool environment, read only at launch. A present `capabilityRoot` must be a canonical absolute path, and `skills` requires one. A mismatched or invalid profile rejects the call. + +## Build + +`source.json` pins the CLI and native tool source. The pinned npm package supplies native runtime dependencies; the CLI is built from source into the same artifact. On Linux x86_64 or macOS arm64: ```sh MCODE_NATIVE_SOURCE=/absolute/upstream/checkout MCODE_CLI_DIR=/absolute/pinned/package bash scripts/build-mcode-harness.sh ``` -This standalone companion uses its own npm lock and is excluded from the root -pnpm workspace. The build archives the exact source revision, bundles its native tools and sandbox -and installs pinned MCP dependencies. The same source archive builds the CLI with -its upstream build script and lockfile; the pinned package supplies only native -runtime dependencies. `native-patch.json` records the upstream revision and exact -patch hashes. Runtime packaging uses this single CLI artifact. Install -the artifact immutably at `/opt/mcode-harness`. The private profile supplies -`workspace`, `scratch`, `protectedDirs` and `network`. Only the -isolated worker receives the real workspace as its tool root. Missing or mismatched -profiles reject; there is no unsandboxed fallback. - -`subagent-snapshot.mjs` is a daemon-only reader of the private native SQLite -history. It opens a read-only transaction, scopes recursive descendants to the -bound root, and fails explicitly if a complete snapshot exceeds its bounds. -It never executes a model or exposes native history to workspace tools. The -adapter freezes root output and retains the same ACP owner for bounded child -settlement. A completed/idle task remains an active public Subagent; native abort -is a Turn cancellation and never implies a closed Subagent. - -The bridge owns each launcher until exit. MCP cancellation and transport shutdown -stop all owned workers before releasing the bridge. The outer Runtime owns the -native process group. Both boundaries require real Docker cancellation tests. - -The daemon sets the protected `protected-mcp-v1` tool policy independently of -the concurrency limit. The native catalog applies it to root and child profiles, -withholding direct native filesystem and process tools. The Session-private -`oac_workspace` MCP server supplies the already authorized workspace tools to -workers. Other MCP servers retain their existing native selection rules. Native -Explore/Verifier profiles retain their stricter native capability ceiling. ACP -initialization reports the applied policy and admission limit; enabled Subagents -reject an unpatched CLI before accepting model input. - -Native tool schemas are retained. Text and image results use standard MCP content; -video results reject explicitly. The pinned native CLI may add task/skill utility tools; -qualification must inspect the actual inventory rather than assume exactly six. - -Qualify changes with the -[Harness acceptance checklist](../../contracts/agents-api/harnesses.md#acceptance-checklist). Synthetic isolation probes and native -model runs do not complete public Files/Artifacts or independent Core acceptance. - -For the packaged Linux regression, provide an operator-owned private profile and -artifact directory, then run `native.test.mjs` inside the qualified Docker Runtime: +This standalone companion uses its own npm lock and is excluded from the root pnpm workspace. The build archives the exact source revision, bundles its native tools and installs pinned MCP dependencies. The same source archive builds the CLI with its upstream build script and lockfile; the pinned package supplies only native runtime dependencies. `native-patch.json` in the artifact records the upstream revision and exact patch hashes, and `provenance.json` the hash of every artifact file. The artifact lands in `MCODE_HARNESS_BUILD_DIR`, or a new `${OAC_DEV_HOME:-$HOME/.oac}/build/mcode-harness-` directory. Runtime packaging uses this single CLI artifact and installs it immutably at `/opt/mcode-harness`. + +## Subagents and cancellation + +`subagent-snapshot.mjs` is a daemon-only reader of the private native SQLite history. It opens a read-only transaction, scopes recursive descendants to the bound root, and fails explicitly if a complete snapshot exceeds its bounds. It never executes a model or exposes native history to workspace tools. The adapter freezes root output and keeps the same ACP owner for bounded child settlement. A completed or idle task remains an active public Subagent; native abort is a Turn cancellation and never implies a closed Subagent. + +The bridge owns each launcher until exit. MCP cancellation and transport shutdown stop all owned workers before releasing the bridge. The outer Runtime owns the native process group. Both boundaries require real Docker cancellation tests. + +The daemon sets the protected `protected-mcp-v1` tool policy independently of the concurrency limit. The native catalog applies it to root and child profiles, withholding direct native filesystem and process tools. The Session-private `oac_workspace` MCP server supplies the authorized workspace tools to workers. Other MCP servers keep their native selection rules. Native Explore and Verifier profiles keep their stricter native capability ceiling. ACP initialization reports the applied policy and admission limit; enabled Subagents reject an unpatched CLI before accepting model input. + +Native tool schemas are retained. Text and image results use standard MCP content; video results are rejected. The pinned native CLI may add task and Skill utility tools, so qualification must inspect the actual inventory rather than assume exactly six. + +## Tests + +`make check-mcode-harness` runs the package's Node tests and syntax checks. Qualify changes with the [Harness acceptance checklist](../../contracts/agents-api/harnesses.md#acceptance-checklist); synthetic probes and native model runs do not complete public Files/Artifacts or independent Core acceptance. + +For the packaged Linux regression, provide an operator-owned private profile and artifact directory, then run `native.test.mjs` inside the qualified Docker Runtime: ```sh OAC_TEST_MCODE_NATIVE_PROFILE=/absolute/private-profile.json \ @@ -73,7 +42,4 @@ OAC_TEST_MCODE_NATIVE_ARTIFACT=/opt/mcode-harness \ node --test packages/mcode-harness/native.test.mjs ``` -Run once for each supported network policy. It verifies writable native TMPDIR, -large Bash output retention and subsequent native Read. The ordinary repository -gate skips this case without those explicit inputs; it cannot replace Docker -isolation, cancellation or real-model acceptance. +It verifies a writable native TMPDIR, large Bash output retention and a later native Read. The repository gate skips this case without those inputs; it does not replace Docker cancellation or real-model acceptance. diff --git a/scripts/name-allowlist.json b/scripts/name-allowlist.json index 387b32e61..4062e6067 100644 --- a/scripts/name-allowlist.json +++ b/scripts/name-allowlist.json @@ -44,11 +44,6 @@ "regex": "Parsar [0-9a-f]{8}\\b", "reason": "The test profile labels cite the historical source revision, not the current Core brand." }, - { - "path": "packages/agents-client/README.md", - "regex": "Parsar's current execution flow|Team orchestration belongs in Parsar", - "reason": "Client documentation distinguishes the separate product execution and team orchestration ownership." - }, { "path": "*", "regex": "Parsar (?:product|repository|service|checkout)\\b", @@ -464,11 +459,6 @@ "regex": "(?:AGENTS_API_|AGENTS_CORE_WEB_)[A-Z0-9_]*\\*?", "reason": "These Web settings occur only in explicit retirement tables, diagnostics and rejection fixtures." }, - { - "path": "apps/web/PRODUCT.md", - "regex": "(?:AGENTS_API_|AGENTS_CORE_WEB_)[A-Z0-9_]*\\*?", - "reason": "These Web settings occur only in explicit retirement tables, diagnostics and rejection fixtures." - }, { "path": "scripts/core-doctor.test.mjs", "regex": "\"\\.parsar\"|\"agents-api\"", @@ -544,11 +534,6 @@ "regex": "Parsar is an ordinary API-key holder", "reason": "These exact phrases refer to the separate Parsar product, its ownership or historical source, not the OpenAgentCore brand." }, - { - "path": "docs/web/architecture.md", - "regex": "including Parsar", - "reason": "These exact phrases refer to the separate Parsar product, its ownership or historical source, not the OpenAgentCore brand." - }, { "path": "apps/web/PRODUCT.md", "regex": "and Parsar itself", @@ -629,11 +614,6 @@ "regex": "'PARSAR_'", "reason": "The external-client credential-leak test rejects product-secret prefixes in observed model tool output; this is a leak detector, not a runtime setting." }, - { - "path": "apps/web/.impeccable/design.json", - "regex": "Parsar [Ii]ndigo|Parsar\\n", - "reason": "The design records explicitly attribute the copied indigo palette to its original product; it is not a rendered Core product label." - }, { "path": "apps/web/.impeccable/surfaces/src-app-tsx.md", "regex": "Parsar [Ii]ndigo|Parsar\\n", @@ -659,11 +639,6 @@ "regex": "Parsar is an ordinary API-key holder", "reason": "Generated copy of docs/design-principles.md: These exact phrases refer to the separate Parsar product, its ownership or historical source, not the OpenAgentCore brand." }, - { - "path": "apps/docs/content/docs/execution-model.mdx", - "regex": "including Parsar", - "reason": "Generated copy of docs/web/architecture.md: These exact phrases refer to the separate Parsar product, its ownership or historical source, not the OpenAgentCore brand." - }, { "path": "apps/docs/content/docs/troubleshooting.mdx", "regex": "~/\\.parsar/core", diff --git a/services/agents-api/README.md b/services/agents-api/README.md index 20177ec9a..c46b4a3cb 100644 --- a/services/agents-api/README.md +++ b/services/agents-api/README.md @@ -379,7 +379,7 @@ The test uses `OAC_TEST_DATABASE_URL`, temporary service keys and fresh tenant IDs. The suite checks upstream and generated response schemas, retries, ordering, tenant isolation, unsupported options and reads after a process restart, without a model provider. It also runs the -[official Go client integration](../../packages/agents-client/README.md), using two +[official Go client integration](../../packages/agents-client/README.md#go-client), using two fresh tenants, and validates its created Sessions through the Python SDK. ## Checks @@ -619,7 +619,7 @@ Anonymous requests suppress native OAuth/credential injection with a blank Authorization header, without deleting native state. Servers rejecting that header, normalized name collisions, changing inventories and original MCP metadata fidelity remain gaps. Items retain the observed native JSON, which may differ from the original -MCP envelope. See the [Claude SDK profile](../../packages/claude-sdk-adapter/README.md#bridge-and-native-lifecycle). +MCP envelope. See the [Claude SDK profile](../../packages/claude-sdk-adapter/README.md#http-mcp). The current subset rejects native OAuth login, inline authorization, nonempty headers or request metadata, URL userinfo/query/fragment, the `environment` origin, stdio