From fb21d3bb2ecf327c36acc96232df46e96c4156e6 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 30 Sep 2026 06:51:06 +0000 Subject: [PATCH 1/6] docs: merge the Web console server docs and rename console API usage Merge docs/web/architecture.md and core-connection.md into console-server.md, rename protocol-coverage.md to console-api-usage.md and fix its stale rows, rewrite the operator console guide, and delete the roadmap, the metrics requirement proposals and the old overview image. Repoint inbound links and regenerate the docs site. --- .env.example | 2 +- CONTRIBUTING.md | 4 +- .../content/docs/bootstrap-projects-keys.mdx | 304 +++++++++++++----- apps/docs/content/docs/configure.mdx | 2 +- apps/docs/content/docs/console.mdx | 111 ++++--- apps/docs/content/docs/development.mdx | 2 +- apps/docs/content/docs/execution-model.mdx | 302 +++++++++++------ apps/docs/content/docs/public-api.mdx | 2 +- apps/docs/content/guide-sources.json | 29 +- .../web/public/onboarding/monitor-en.webp | Bin 43958 -> 0 bytes .../docs/assets/console-overview-en.webp | Bin 0 -> 171520 bytes apps/docs/scripts/guides.json | 9 +- contracts/agents-api/message-input.md | 2 +- docs/api/README.md | 2 +- docs/architecture.md | 2 +- docs/configuration.md | 2 +- docs/development.md | 2 +- docs/web/README.md | 111 ++++--- .../web/admin-metrics-backend-requirements.md | 138 -------- docs/web/architecture.md | 105 ------ ...tocol-coverage.md => console-api-usage.md} | 229 +++++++------ docs/web/console-server.md | 219 +++++++++++++ docs/web/core-connection.md | 87 ----- docs/web/core-process-metrics-requirements.md | 63 ---- docs/web/images/overview.png | Bin 118825 -> 0 bytes docs/web/roadmap.md | 57 ---- scripts/name-allowlist.json | 20 -- 27 files changed, 907 insertions(+), 899 deletions(-) delete mode 100644 apps/docs/public/images/source/apps/web/public/onboarding/monitor-en.webp create mode 100644 apps/docs/public/images/source/docs/assets/console-overview-en.webp delete mode 100644 docs/web/admin-metrics-backend-requirements.md delete mode 100644 docs/web/architecture.md rename docs/web/{protocol-coverage.md => console-api-usage.md} (51%) create mode 100644 docs/web/console-server.md delete mode 100644 docs/web/core-connection.md delete mode 100644 docs/web/core-process-metrics-requirements.md delete mode 100644 docs/web/images/overview.png delete mode 100644 docs/web/roadmap.md 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..1d0043df6 100644 --- a/apps/docs/content/docs/bootstrap-projects-keys.mdx +++ b/apps/docs/content/docs/bootstrap-projects-keys.mdx @@ -3,90 +3,222 @@ 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 guide](/install#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) | +| 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, and hand-edited generated files 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` | + +The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` +([configuration](/configure#appendix-core-environment-without-the-installer)). +An invalid value stops the console at startup with a message naming the variable. +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..1816aa36b 100644 --- a/apps/docs/content/docs/console.mdx +++ b/apps/docs/content/docs/console.mdx @@ -3,64 +3,79 @@ 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. A +console restart or a Core key rotation signs everyone out. + +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#make-core-reachable) | +| Choose the sandbox backend (Docker, microsandbox or E2B), the sandbox size and Runtime, or reset the backend | **System → Sandbox configuration**; see [configuration](/configure#sandbox-deployment) | +| 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 page, under the public deletion rules | + +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. + +When Core's public address is a loopback address (`local_only`), Overview, Nodes +and System warn that other machines, including nodes and remote applications, +cannot reach Core, and show the configuration file and apply command that change +it. The console itself stays reachable at its own address. + +## 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..2aac6efb3 100644 --- a/apps/docs/content/docs/execution-model.mdx +++ b/apps/docs/content/docs/execution-model.mdx @@ -5,108 +5,222 @@ 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 guide](/install#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) | +| 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, and hand-edited generated files 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` | + +The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` +([configuration](/configure#appendix-core-environment-without-the-installer)). +An invalid value stops the console at startup with a message naming the variable. +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..91f01ed0a 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": "2b1e914e2013559fdd35d48d64c6a1b68ef57e2485a46545adfcb2eadf622de0", "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": "08667cb82aec5495d1d3a09d588b37fa00601e0b18d26f246113042dcdd114c5", "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": "926f1952fa41bc52e1a7723b9cd1e83ce1555fbec86906fe0524df70bf7299b1", "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": "8fb8a53b383c48a8fcbb838627648b9c8a6663af0d2ce9c4e1e53755b548e54b", "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": "58abcddf241dfbdb43ef93410e979552aa9f65381bbd286e4db7dff20edd3b9e", "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 70aab640be883b11fc895540d9284bd8325c4ee5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43958 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

32z zifKrQTR*DKk0FP4kf}x?kA-Op>Ot50hc+TlSZnVVEVwDy zuct$@RpkO}<{q_Gm9T5ALZ6PGpbUHZNF5zx5#h}+H3sK^EHORG=uah)i!2%n61WQa zDIQ2wlJ7rYHwBRZ07{BZ5KaMyjFMt35B0fZs_ry2$l(8HT=^o?+ zz*zi1%Dcfh;G}=$n+Zi?KwZEEa*b@6pcS~8Fr&qSC0dqUWyteYB}Qe1yjQOOxu=X` zmX7e}Kw`Vfif48v6R@%RZlnN01Py%Z9y8aLf`QO5WEwSVrG1U=o3&q@edYVPAD}nq z515e^1g!1FQ>AKXslwm1ey}6s9}+b1R~Y_}F#DP4mvxG0V0y%SCx4M0(ynU9ns-SA zo?rXOBWYL{I>F6f%t~D0^gPm+MuC^O?D3~Y!#(FfWsthL#mjgg5E30FZ<%2Mr_gE2 zp_PUM!t#u1;BtL26@*T3it4YMJTG@{HS~e*zWqR8aZE?;;fq?JF6^2^J7Y)|S+qiI zFKe1*)8fHC8(gYm+ND8JYlwz}r<@Zo6pqH6BV2cw;7X(qtLV*&ZrOu)4?pL2RbYX; zWTS<=J|b*MOJ2OkVU6Xfha2sn4S_K~gQ;0f@NNR1Mj@=;l)ttZq>&Bch0is<$lsnx zMItjxe%Z5T&k{L}n2%{({N!&zL75f>7O0q$2867kx;$_Sy~h)wdWOZx^ptZRb;tp% zn0JQQTqYPj$aagd@#bh@4fJ*B=eh@G?f2CZa%>!5P-4T^dU6h?!wBwfUDj=CV=MGh z*adF~<8qXEbn#r!ti{vgxq40z%MXjQ;qHHG8!K_#pcEaB0+jiSMQGnWr}sJKuPogSUGw zt^oM%h1G`ijSGo3QWFmJa8CN6U#)oJYHvO+WA9d|(>=B&s9FolH=UH6aIG(Nj;6y# z&5kfLBaOYERtp^?ulhE8xW^zwo6~aq*dndL%d~BM$vW-O!70aVcO`3x`C_nXyF7^b zk6fJhJu}T+!zD3J{U0dPWBI%VL(cM9khIs(+zqm*g>wOE!;N-1WV7rYEb*2qnN!Ml z?B$uy)bsZL-jj7j(6dRo#yY9y6H51my{wHWJsuVPGTkJxpR=&%kM&MY87ii1k^JmM zfT)j>R5Xf1g5{7R1jK{C9P!J};VNrXOzc4<$o!*!p>B>dubg0qmUctS<|aGceoz?8 zkIf}Gihz*$Lo{3q zAP(NC86`SbYe_a##B$|iLomkvgh_hc0~gi6IfDfLunkixr;GT|@iYSdEke_tlqVy( z&M7$F()-EiUjt>l*@tU?AWC6}zlENm3B`-tozyePHb|vmtrOFIDGYI+> zXv@CRV(OU;?5RZ(W*0|Iw?SQ#m6`>3HY(dOdvME&io}WIHpapakG%sBU_wQLNz8Hg zq5n`KEUfVYj=9kv`oUua(St?xpK(&oWad=ajvDpr#bg*kHx7NsPkfm{P@mx8;@j2! zB%8cb<3ff*??DQdsI6-twwwC(Dshi{jiHTvUsuy|bG~q}9i7fNNTQW3p|~7aoor;E z(Ye%JAWnqa8KAw1`X-54dk|Z|Sge!*jbn=bbh|$ekVj&ttpxgu|Mk99#E$01st{wp1Vz}~lG&3;BQ}Nljo}vwH5g@42A>#a^}3A5mz(O?koEeqeSH`oL~TJr(}Dzrrg!icrt!sFQH6!|$A}$C zVnVQDwK!6wtc&lmLI2z$h~LwN4i$s$`g+oH#D0?^>eN(=lZm`+8LGsFCiX;K|_vhE_(v7!Pb?_I5Y!(``N^WC4#?zq7?tAW2&fP6U&-9p(kqI?Oxk zkp3NW9FsTD8{Qe1XimRBSS8EFuG_^roMQliVD}b9`3wzQJOP=tum~7#klU1?15-{e*Qh6!g@o8dx0e-xUl^(A7o6-l8Gt9}w?uC!UQ9;f~B*Wu@R z_8aLcG3RkZWPwx5#P)0Q$h4dap;yxdsj-dc6MZaRep(6BsH4VGl_|!^#hsY?`WC-7 zP|U2Im5L3?Aw6NEtj-A>JY_zonT1YTJV08x%#jagDO&oF)n5*3M>}5AB9;5cpTv=Y zBy*egYNEyJ!V5>sa*lUtkyTSQ^*Ajsxi^(*lWb-Z1M=q5Nk4Hnj$UYScFStyRI9=ZhT^#kuy_t4$FpwSTWp#Os_Pbgp`Yi2bScEjzv!j z1H1Frchza@;P=X-i(J-^&%h7+&iTzu*u&}XQVMjg+P`rq8{BX|6-Ip{tCH4IY`gFg zpX==vUowpYRjQ?8gnv=4&`d?SQ8dkv2H}R-e@x*d{iJ_Npv=f5oe#UGnb^=S$dXXA zKz!BYJbp~jTFXf{@Is&^39&2F1%<(kM7dCwf!91cs8ooz3Z9MMXr@bJZa5r-XXO8Z zE=VWnO_POp#mPP6(0GZ;4lD@GfC6V=PUtK^ycii&+2rBNMK*a)<#kImPddF;P|~c3 z1q?GI1zE&^52Rj}OogwU4(f0(l(cZX^(gsfJB#i_lwuNiEj!=B!3x5%&7&Ap#!&fv z1^!X=vB5uwr;aZjbgBU94wHaTuy_vZNw542)({cl_jK3IjtGw7AvKqq!47TmV^01# zg#>`IEi#9=6J=@{9!UziKq77)8;EK~W11 z#JUFhg3jg%)dK zYHc9Wj}F9%rL{eJWsZ?z8YvMoF@4roFYG~JQMX;?iJ<|oSWutQLM&x1!%)SqGX6FX z?vDIj?a2tE#cP9<>FtVbb(Pk24%!Ty1@?v?m8@;fG=>MhmjE-e4H@>TLXtV$z!QOKsi_w-pRbw>UJpg+F;5ds6uR z7(T7EP8&GdbfDGhtGGQ961I^|oOo1=q}*~etT}C(-s1ZWT^}5J@XoR(&7yAbs(UU2Q8?wPn!yqICg~q zU|io$Z7#gnGaaqFpbT6dKHjRuoqr*vSp395UCc0J=bZWA#3D(HBkI~4jntk2V9+Gx zLnB*L9RGGle_*O?2^ie&I%97FHa9ocy24}VPhR-OvrEYtvXfkMq# z4Wa!7;S`Jy7OPAgwT96*K?;N<)#c@0xJN`S=+7p}oX+o$XR%0m#Rtgh|1ey2rn><5 z!okZ9&^L!XfH8nPFaX_gjp2<{6KPgX9})!o*p6(2<543OIOgLl*cnX{L2pj-H`}ui z$|cQgD)f9!MqKvqAW<*vGD|ENaQ{kh#;!R`Ad&n)$Vk^Kkci@T2FKJZq|RUh%a=wF z|Bd$4E94`CuGZE2q3q@rc7lQCn}5NdD@|qpfVksUoobhF42W>{whuZ(0~K3!98!xf zt-UWN=P<8hh$`r(qX-ouxG~Ozb{JRwsv(}bHht{K?4yWkEo7=MHqloaITi9I9=!OE z^!mFT;swi8R+H18*+~pNs-lc)s;BBf&;v+>btmsbC|mm}nf}=wIshQxqJkAg4dYTf z#pVR6wZQ|s;*XdPAmY^;SF!z_4p!ofh5uKG(i$)*L8Zh1@v7veS%Rj(EN3)A=q@)X zD))|0I3^N!+`P6bQ6}xbr#dp3e%F!#)4xw7P!4N%d+aybeNnjfHH3?7xJgP1@SD!H zpj1OJbnmZ6)e}FkQoo1>l`iDA5c>du)!_K}8p2R^Y->0lRntC3Pph{}{`^t-_W z9!<@2KoQqU$+!$b_J8;b&^H!7d?=?tm_}48VhxymBv3F!YtN8^*!Y>DP21K|;aQ-( zt5L@op)H`{J`A%V57BgnE|HTu1%&6M5JToL!5o4#=dY`8@NtGf0BI-%7%BF;R9)8A zw@gKv4{pU#snzQ0j5U-t#E7*m0Y5bX*sbW-gxiSR;^eK5n z>j(~(4sb#h4S@XSL_Vso8^$1=*}cf$+OB@B%=vDaJNpSfJM=tC zTc_9)j9z*}ou4X@brqmUJa#F}&XPI)#&Q%6kS}z#P;y%%L5K|q&bwYwsDNbLVo4fy znMYM&Pn!D~kK1NmX;Q7#%gOhwYpL0U`k49>nR9Uk7=)akBawrPjOheoMV zgiIdVOfhnd!s8!!G?=p&B0Bx<&~jq24h^q4t>fcR;?pbR@xj_BiS@SfaOyPjq??v@ zLw7XcNWAKloK_#<#a#?wva1qrd-uDk0*{;%;Ehdw46C4^uDrRP9J_MLb~mt=u^cm) zW?DL^h9#%8(C4_T4ben$T9P(kYjTBy>3^Bq+f$V%^^EB|w~exy1e{zK?9D8=SQwD| z8A*UCCtD{6!qa8F-p^k;jLC8F`1=HbADUV&v}Ey3Ngo*1%|+nS^@75ih`5lSe4RvR zA~M^d;H^FZN7*Wpo`c<&_+Had8Xl8OL30JE#@rvUTmoNA%C#xrT4pX0AUzSHUr+Ki z5x?n-Or73;XhUwEL!yaiGKwESzzR1X9Pu}@!6G9*lRv+MYzS$6nTgE*AXsEPdL*pr~vTh=2rZUO$nLKaf(_vkwEP> zk`n4fyemkH&dR;MC2em%9l+$=RiA-||K5QAPA{o%FVcoQW9i(_1&q8P?lZ(|U+Y7~ z83n7j)2;~}ocKD#c=SUl`W8OMvuMKdQ#XG_BOJH>nRYV$+WYJIib?OG>%Z^4XJ#vf z*FotdofY#G6Nd0ZRQj{@V;U9vd|Q3+KJRt|$A6+)r#Q=pW{k3$`ysUaR*k+oZ&n7gJX5d6rXtfCCR3`!}lI0I*si zk(<9!_PMN@%sq|d{(A`zGdbFkDKVDS>)e?~Ywb&cI?YJ+akNk9PelX zT^p30esKg9SfH&odUl{~UZ1SOaH!Sw%I@Az(q<>3Eha4UByi8NbB#u38@mx`-SpH> zI^*|Cs47+~sJJHJsqp3F8*F>x!*vS)hM}!_8K@5w%IFzi(RJev zlYpdiX}GUS5y3jgl|#9 z^R%z+>Ncv`<{GDLQ0!gJysZ$6@N;phc*>oIk zYnFm|0nE;dH2nv*6mjODED|0hJ(HBJko9jWC9rp(Cx8Z#xy99xZ(SaYi@V^{!tsUB zzv5QlF!ZW7`9RMSLc8;CxfdodVHA7G+OfJ_g@-jA>!?6zC)vlMcc`9fQ0?&C*She5 zWnVEG|0#0C$M{#oJ`etbAj?Z4swB6KkLTk+O4BV^*EB&gY9_2mvKr@ znyMCFN&4(ix?9q#ob1{HC5GooeLVHxkiYt5HQ~zV6GaL?G zlPSu2=P7ToqzSx9>HzrGDW1|m#k{jkdIkicGt|UOnZSE3j`euu>}evqeb|UAcKK}n zE+~U7{=>SviV4%w$cQqChwjZVWw3j{YCWn^=M=Q; z=Ct;b_S3-$C2~v`2=!Q4N@*Z+QqFu{d%@wYa%hY%*wNHvDf@LDpE?AJYd#*q$7JN7 zb7*NXByL`Q7z@+Zg@qS|EQF`eJ6TbT3e|ad0X?iI?{=E33TvLi0T$+3aiyYK6JhaB z#CYl@E7%UgeL*m+V0JxNDlesn{h*1y_EUlYl}!!GXsj>&zcc5*=&Am*El+Mm<9pI9b6q38E7J1N@?^Z&Fsh*irdE%C?b7Z%Soc*YX=q5a_UM{m+r`*5OWw30o!8Bv8g~*8t zFA7+b(KA-*6HO(iy5rPbl?Jn1Q{|VS7gz8KkUL2KY_{1^vuA#rq~inn*N`V5nS~1p z*+Z>c+5O}wvltk2x$HL3KU7yZ-fJxewmr;5HYLtcFka!?ReUr^DbBN`E*M>$2#W-G z?4&@X3T2&!$YgmP=l+fE28USUZ?ccB%pP}Vtc(a3HcRU*%IZKe#5WQ(HYww1MlSPc zWr-!KFLx7utDBY3cv-lx3?nZbH!&ho$O=Td){dIY5jWsd*bNvInJ%ulZWFN3uD47| z=GXTtUL-(U`bJ~1DxnIAW1QQUocz>wT}2Em@=y;HJ-)o*gnVtb#i|}o3C5RmXYfbY z@HeEr@JG(k7N3)eBy#Q#Zu!OrG;IPqJ5Ft^0V2`F&VT|=k`8}f~*p9qz=sSxX0H3lsGnSnc_XHiXhMk3fwxy2SmIKLXcq6KKr)tfP~y&8gJ*{Qp2A4 zmGnxZ_V}W8_}F^`+28PO0t*xQa`i3~PUwbgc*+)uDx32VCUvxH2ak^zBVNjmSc#1F+I))^|6(rsdJ zm-emwVYz;u+%cmk=sk?F47iQ% zW8AMDukst#J{Xwu(6j1sJ}h?=Ja7?`XI0ipr(D9KTFsH6ZjHRP@UouHn?)f2KYGSE z8>{MTJ-_`@+N)RI&6Z$a<{}QorDunnR`j^I0_3l2+&E>yvDDi@-L+y%@D%wF$ z3}mI-K0DNDbz>O`;7n-{mBukU=|&6s{VzH|PO=`ylZKCir)V427vaWhWKCqt{)~tp z#tVg$-1vNgBg;~xY8(RUDVa{K)X!Y~Kl{$09f$C!>ToyeMX|<}L=tk4d~w}+qGIe# zV!!v-QjbO}EV3*sdp5o*hW@JbN|&({RloGXV*fp&%(f3x2_?p^3Uv)AWs;p!s8`AU zrN8_l2}cjy3iw{{*DU$3)clnOWdycfudc`6_ndVy_cR%GEcC6-J0azJE;0Nr zoXi90H`S1_ISAmAL8?;mU@5Bqokux-&u^+j`QVH!I5FXt{k|Q`#P4hSjES%7d;SgL z9O!nxt`O0XWs8*Eyv`!I`J#CkZp1~(BwcifDh1`rR+8ecERk<2e%RmV7^o67u96E4 z-0h-i9p2(_WyvYj!8MkVB2ZGbY%h`VeF@P|84vMq-f!ZhjFS{l>KsG&V0shOdW~d) z5dO}RESMLK2;tB1JSK1fdda4}-TC$6#JJf*2U3nB6=;TdA7u^t>a2De0SSXsNi zO-Zmt=K^*_1;jUpFAFstD;M?aWR0>nE;fXbmG&3J%|!vKSN=-IAi__eBOVps(c1oc0t2rSRQ!fTwu>HpWk3_ zySPsWVcnz%$?Wmz6t*frEquvQQINSKZK=dX+Jiy;5Z$p2cWwL*-l$3aV{et0Pl}_6 z0ud_l%4<9Cd~nwz;X$E&x`ZR7yROR}0VK-`f(9}B&CDly)Du3>g=KP!GU-6X>5PQs zO6r(F_s$~fgojWc8j8h*_cSdPJt2e0!@2CD$V~Q3u^AJUuO<9r<8EWP*_M;X_waOi zEkRAI<(M(u#J=C@5U3&P^6;Q|am@c;v0iFpPepEYOwX?=KGq|>Xdi6~*tYUVtLJ-i za(f}0aCl5wJWq1U3F}oXMOaOz^IbIAiaZ5ng^#lIU)3%2vWU?LW#`5GN}^LaTyhk}OcU(CMo1zlRE5P@0)roI@W zgGk~0OM5(~7IMo+o{idvWBs6W^YQ_%Ydj{QSkfl!v7st7T;RoOM2Ep* zC2vDJ&T5zI{X})_GNU5+KYprqnGkpH-if}+dCE2QQzH&Eacg#^)HQA3Dls*<(3dM? zW)v_!7VaIlmjE^}&CeFNER2BOQfKA*N}3^CTbZ$e z7{=aYSw*(M5OXvAeb3@5LT)5`h@ltCCmjpb<9ui9E3>%p94mD|hT;(8|75=5qHh$D#}Z8ubL=>HFOfxJFacn=3#3&IPcuH6$P z+z*8UK^=3X*$al_^W{=8ce^a6YeMYRa#-0 z{-X>xe9J;re#kpI=Mo9TU=o_-I837%4yfzRJKh732_L7^ggypI!kuP_=fJ7B0=YzIcGdcyH7EUjBPys=^=`PPrgB$#aqGv`) z%<)Z}aqgmwX^}KkjtEwjUV?oTsDRJMw&u6vA{3=f2o#zon@3yatX)$T?ia*F)DtNYvHu0h}={;8B6Yd577lcLqwq zul9iQj%aFS(7h?f>4`URv=5qaak3F7$wEvc1z`s0Y1X=jC<8ifWa+LRHi-B){>wpA zUk7e0f+#LuNCfV%IXsMy-StEWqeZ}v*$?K`0O{~SGe^4wJ+I*IzP9_2({>6Q=O$pT zc;}gD=KGd=P2#cab>64yM{3*)C3bp>BDA-DZL0NVY{qrk#mmdK_SX=vEp>l;{2}(( zh>UJHW8zMu-FZ9&K8v>(q0J?vMT*hpx|fs77>CP5HB*T({L{ZGVPdX4<|SX7f_a(G zE^WC033J&zql2!&oxmRgUqi zZx`U9)rLXHJu7j*adGndk0^hBW+7V=GiYDLamncO*8xNXUX0ft&u=?{J~ER^ix>H``|q z4kN@$2bT7n?NOxh=5OgAkpBen-y?JdGoPe|N$nH?Q0+4O#6F!xaz7dfJG!!tgr~Ep zpm+#$r})rnIv#0Yg30x9+$6hbj#Kv=X3Ll& z%#l?{iwX|CNq;$-=QSsv_VH6h;s@`uY5w%>Fpg?EXJ{qn#VlM>|+qGjV zB+6G8{x&Nr`92$;zc+EU%7pHK#7eSo5HTH4AKuB}NbwER1E-Mrzs$!^o~IK-kWBcV z*aD!8e(pjADiE-_Kv*yxKZI zaMv>Rvo~*90%s#v+rZP0-YY>tAByo!Fvtm*qRt#jSEen&;7j!;#@bb!J>WS@d{wqC36xY>wENpM zpNLt)h8|4*m*Aqs?sH&0T1L?o$$?+M{Q-tmIdL+@`2()!MnymhBBe(Nx(zyzJzlP} zlrYxcWF!r0tk$5JIM#RrD!!1%$t~aJ2I}nomzxzwknS-=YL80teSp8#P_g^E!|Gt! zH{8SJ9Vld7&2NQhhW6s|Ud)EH#|D%*lTv(yNQGgeP4|FDVlGzQGnwF}5O}1VzGW<^ z_>nbplZQ`#3(zNptqkW#k(Y)z!iM~yH0Tzu-cPn*zAx^t6hz9ukh_LBk20Tla)&`s zDsK94Ol~C9TR7xF{p++on=bM4F!H{G0lx~4Qi1{7iv}&Wqzq&My{)tsf*DNe&>#;2 z6XZr4>JF}*zf=I%s@^${?OmhKr)Wvqlo)X9o!52umWB@zc9b~6`E*tY4Q1&K&aPH`b5ZY*!y&F)&&$IbsV|Av zXP5BquU4A~b^S89>e?BPSGJ*c@JLIhWUbd}@lJHMA-3kj|se0c?*;u5?CLD#EwljwlD z_5KWKXzOwyhs1`polQ*PjVh9?+ABzp4DQVZ0xPby7WmT&F`^7V`kxSN3%X^(I-evB zTqjp>itQdaxzaD-=fEn)n&DT6E8|i38-S7!;kUES+p@*iLc}{ZvDx!@u-c5WAnW8C zbx>!sx6I_?xVEKh6d>KtIVAf&^p&Dv`uH}Jf%;;Ldg;D#F;9g*1%>H>69!gM6+19~ z-rBKXYaKVG5CHr?jUU0oZH+3(bQ$V8*&OrkkO3?L*5Gx~99g0axl&`21xTXf(gDr2 z)$|RBNJljHjVqFkibj5kaW55E<_3aWKki>7gF!QmSUW=VDqW9AY9f+prE!PYxnqW- zKaReOsa>5f`b-F+-3`qd2*cp7e>0OxvL7rAm|Fc#W|JdN8(4P$*{Z{@g(%C5-Um2A;~dhE;_r&cZt zK#OJ;qV{8Dlxy(|g21SDy~PoPwk}2I7iSR<1vMd2%e11euF9X3_3MMrI=|hQ76*TH zy1GH+{URb9c=i>==H=93a4r0qDHRpeiG|X`A8sSmLckJ0j*F|VKHsvQRpmy2lFrXVgM9FFPA#m?kvj9mMZ*6VtdT=@mlhS}Um8jLwG z(>UMNXCh!vH3&ttQoc5|(1INTErlhNn80-y2tm!r!9a zIB8DMH2HEP?QAQzO{f-7nW95i1;vpM5vA{&SU-jV(tG=3Du}1#$y(sdD*RjehrNrt zNd_}MqJl1MzXs2h!R`(Jwn|nE_RvqR?T9c$4VA=FgO|8f!clTOA^BvuNq#TSX^D5va z5*5#%hCjSnk4~Ra3+DO%6-Sl_F#H)@5*IBuzZpMJkn4(iG)+22nHn{;;!MNScP2#v zJxmzFRE2m8`o1=v5BEh6*;U=ISW?n^m_AiND)Zacfa7!VEA1k(OYqmtjwAVLg}}J> z849o_3!67gwjTdaD0DB7zh7ZaZ1}q4{+7YfS4!&7?4_bk9D^&nnhyHYFs1OML zJaqq>IRiPrxI2V4ul<=<3a@IONEj{uuhsswBm!8hxj~H4P=Q3}&01Ql>Nl<5%NdCf%{;Y475JgqT+CO zYa+wX^?UKaRq_vyd58cGXt4Hg=yaL>q`Y)#A@fdSWCSeL)2(ksFVu?)+}7$GTc0@M@g1N1eS3A0JUbsqm9?Kw%q6qN4?#=07WzN z7pbC2DAfn4_z^{2wI^G#WP^wHu_!p$hxT3o18ouLbysx^8z0x+mgQ*Z|C(rA1VJLE z*BZDSb+v1qsoe+-oAXNL*atjs_%26wi+u-uyy84TCml?LrVVV2ks%G1D}5BhyuUt2P}0>L zmAlj`E=i0lGGx*>*r5lTovsM<%B_;-RZe4)aCnM@rvT)4C2`RFy1!1p+GvNp4zdY1BGMez-Q2)(g;!^?@XuBDqe5}iU*DqDFYy(pOnuCC0Y8^S%Gg|z{NP`{sjV)9}*JRHK zv&=0nHpQ@M?o3YKs|p_a8E{Q&7(=78<^!NXs*Ls!(B{i;ZMvx z`{_UH?IyJrbHOvO+A*~8Qs+kHL^%wPs!v1P04H zWjh5Yn@fP{g}J&OHGpy>Fz}nlaMe|}X@Z(UXfF=t>Q~0ciT3^c8ey-nyav5+NXTOJ zZ-m0tEAZ`=Cdk981iN*vzwmT1P=lRW`XhT&>r1#$VifI|$4H#8p<*Tlq_-iZ(yvX? zq0XnRbVMtRur#PhKBDI1RRmT8G!fw6bP=inX@Ky2hg|bssipa9ECg)v7Yf1vT;KfM zYS$zd;2Xz`oCYOL{gXPW#6_Rfh-M9LE!*6kG@OB=3XKg3pr63tlBZAp`qJqLNgA!T z-Y?;v4ef0Va1oG$9B1fYmdZmqyuJWN&JplPt z@U6f!T3YGqwE1V^QNPRpH(U%)KrH)97^tp9*u$A2{?WDbWJlI~B zAppiZt_NpHDp{#LGZ|UfJmkV&fe$fqf5Rd=UvgT2}X|T?7Xi9llZ4k$B{43-;;Zmcm~~#Ah@xMUj6iL+>;9vdZlC- zzvrruHoRuaD;$p+RgU0NMRRCIes1A!rt1?3OUEo0yzA`e!*qYU1T31_eYBKJ6Su?FW|{5xg2wz-CbU#7Wf{(QSOSqFfm0m? z493haV(iofSFq-;bIPBPhP3n;=lD&g-^m#TdaN=e+Z2576Te-%#v)YGD|ly<#V}Hh zS?t@rP>QcG47dA?fd@e#E?O1+Cx@j#p_A>fiu(X@qh^SF>yx45s%sItZTc#sx7AY+ ztK?HRUKs74RGR_+3G6jy08hN8%aUA3dW%GuBjH?~`E-YPb`BS0qj9yDh9&;6*6ua= zopd+MJ{V=l5S>PXDs1pq{a>>y_XO)U_717I5cnUj&j-wLVinsyYFyG;1>ZJ7@qk$X zfsd@4x-dj;aP0J;ot7l*U2GuzSqd>2-~o-&KzyDj4X)(U0&iVo$n_6S?ADm0ta|(Ob7%Zj8|{AV|+8B+=LCm9xR7 zk_E1upt%M73!5Or%)BYZCU=nqcAaTyOEwW$$%^{D`XeyZh6fTPE#-?ZpdD8i~#COT!Yuu((3!@;i%2l5vv2IsjH&I z`O?H7YVBNa3J(CBKeLkD94DIdFSV-32C7@>b*3ow^d>%jp!4od&0Q0Y2y2Y3_rX3c zR*nJ`#%6xUTtmQ%&491sA>{si%^*Mq!6&~HDU57C@=)HhS#Tn=I zw`-~}qF3^L&cL6)bF%$Lrzw`sgc{*=6BlJS9?Ap8Kx70+7^1DQaOX-x(xZy}%^wF? zQsQ-CC@__J5|FH#B4FqLNg2Lnt?l#G_8B6HK)Y&Wj~Do8^4ayNi{tWL!i(Im5mI zFaf$}5kxn+1Y@3s1@_MfK}h@HVLt`Q)^IV^GfSAdw6|7lN3}CqUg)Lxwe%~2Kl?MS z#iS;+4Sl5$3_2A>OORA>gWxCT>@Ulji0YUl_40Z?Q6`sh#qt`&ZHY6#uiJOmrZ`XB zu)M*d9S}Kcdop&Dt_i#q(e|-uO7Pw#`1B6dj zp_VfN3fDxo^m>8vcf$DozY;w$5QDxIcYK}bX@&8%GrM96ahMLoI)MAs9q)7$S+Jgg zdJFPt^k{kH=eaUjUv&3Lf}2~NF2G4`>2ArxipCeBpsqi=*0;m|RlOzTx$>yR#Sk)S zPy{vWl@lQ*C;c-34y}5F#giUjulBAq1t2n7Wn>BO+t?riESRViC~Ru?JTrx;N8@E; zzMU6mhV`l23@?AL0wo9rFW>5mbWiJquL-fd{H@`QpgRd59yNED3A_A|`yIh%tx3EbrWSV^ zn0m^amc;03Gjlw46Ay!h&L!KWNe_YEkOctIucD*6bX>;SwiR=4$w}C*0ApoDu_!#d zznLB$9cRgkeHmigz&JuvcmDnB67MNFiWvA+UP$nxhoCY-{EdXqw$&XJEeUNJBn?bj za+*F_@gpv*^@mWW(K15CN1C)h-eRcONO2`>w^GyoGiasRNvIP@*lZPY(B5IXVM}(- z$v0h8cH{fEmoP+@N;~0;vQVK@g^OZrptXRuYc@ukQgiBhs73=-4J+ysg?pVtVlma3 z@XI|bSrKI>+cC7`4-J?G80jk`_{eh-*XQTw=jZ3==jZ3^60aPC5L*WGBPqUGBPqUGBPskAVO9!^DlYW0kUy_DzVZH92~aI6ADn8kO~>Yvtv)& z*qn#5COE^4IKx%RGJVoy`=rVDNt5oHye3b&OrLa_KIt-j(6$U2kQf3B3hwy_8__q6 z`Uw20z9zZlQ^`P3}(p@Vg5G$35(_hf`Z&J?6Ivj?a2a^!s>AV`{p}Z6{~(%fZfs)4_O3O1v(f0{c&zFF)T3 z40A$l9LSk@eZ0FfOa8HpJj8&uw&b&E12yJ8oUJfqr8HyGO|BG4TozQYwu0CBPhCCf zVrB_hSr<$?yx3~3Wf| z120B)K-zW#`><7|pF*R87C7}N7(yqM;dgLYU1+}$pKz))ETa}W4J->!X3Qx=>fe$? ztJ*%OBP=3`b&Q4d7S+el03Sgz87ey~@BE*zYIido!{^L$9}s_bEDPe7{c(7(ErM)4 zwuD>n_)S$5|5^-`famJ)WqD1lk@#^!0aX0i?ak2j>E`O{>=r*LoY$H~8vEXlnN5Bn zp06-2cddb@5u2Eht#w}u#=tRa%L<8k-Mar9LUljvd&@QH${=VkvT$QXE0`uR zKF`X)i$J6nz(XMCJ7Gxt>ff&HArGWNlu zzb1dgqy3t~=c(a_E4;i@h_&klP&qX)-{Q!XLFrzVeNFUfYhc4qmI9d~%}6y-b)x1! zR*F!FELJgtW`-wYC`>!{d7nND+Je6&>D{#}mEdJQlDGPi6oiIrFBB`xb3691ikK(r z8tVu4tIE>^g+)OcIYw8R?*ZkHxZTeP4Ff^=ezCTEfy@S~fahnw8v)ASvqF0^Wp6f? zof4dTFy=9gq@cnd=5yv^qbC8NDPPvER{26xl=!6)kU&z>z#M2jCq@y63y>f@nL4X3 z*EvDDy9OhvRK~cCFBRwr;p(Mb=VwfY;XxaXEX#LY#aMT|!(U0GWgdIju)5IjrF_eP zq6;+Y{b^pVzYV4{f(etsdhSqT-}$M)&|lAC!M~_$w+Bxe>r^dI=+hS}Q#rg@qcYGRbpKM@Q~x z4R<(`U89r9XCOXstN;fHk-RV@XKHOi)7jtDpM1Pl2ZT5YhP=JAXd^oS}Mi z$uHwI#HUu6fLR=DMf)pJl--4HmNA;&=)(LKD=L6X+H68&ese=s_&Usx_|Qu4uV<1M zY@4}ytFZIvjP&Mvveb8q>UiG)WbDF96~jBP_Y%PkS)GXA@q9pcMH4U1e8U1%%@fPw z&osv^V3)!(Ow(_%QO)J{Tg6d{+|XUBalc>OFvtFRZKsTW4fuvT-Ww60qAY-kfc-8E z*^%24*$bbAHlTkjrv{*Xgk6cX8UH&&Pqrwat?bK**+DyncT%`ZUdWk(b*cF8)pJdu#00GsIJ0y<{6B2N)&Q!uJq4sk>Ot^YT;q6;) zT-_!SxgIGVW%t@&7GJ~Kh}dOqMUww0-~HjxZkCg=PLHCgxzy`nn>Mutu%eTI+<1lz zW!#go~j%oCBEgt$s@|X=_(;H*KlL<0WbCTQ zyI>UuT+B_LW5C7ui8q{DUcM`pK^KUY*`$n701v?v_I`&`x1#PwLx)O<3N7E~vokPZ zl|3wr;ba5n8ua5=1XXmAqwAv_+HsJ%erTctMd%*|H?9B_mXFEKl-0SWWZQ+u0Ktkw zEk|P8v+5yTK8*l5jGeyaM4!=bcmqu})aXHu*hMcJ1vaPCohy#xqoEXWN(a4N{H9-I zDt8zDf)S(R-eLd|6|VU8BUgF)d$bl>gZqxuQ~WDdA($&&3Pi7kYCFT1hdrjK<=d2- z^n7yV=94SnMl=n1qNWA~B53-E9@EKayPfAFsGLhE5RhKpawi%pXNbMg{1ZHw0_O>u z0m1nHhh)mAJI<(ZxV@^-13rpZEQ<3ozA!}uRTD$?)yy0{Uqy-7mlp7Q*V*$^PNZei z@SuRjfS|kNPM5uqaE&4gVSJQubArFJ*Iv;M)y(DE+Peax2sF2MKfItcmpN`z;V%K@ zT@?$(MQCpBUIPeE^@yDv47moK_F^raPkRMrWAKRh_Zf&VctV#MCN;*f65P*Mf=7uY zPJMR0QUc;?3k}Ym2qzj0L~rk=(He4V0zOzMe_aw~T(4~`;B$+o{6p9RIJeR-heH8+ z+;&vFoy&nlTj5tW*0DIK(gBfA&Sx>oH(b)I$~`$R#g%SD0;~poEq_;=?51{=^&ogA z4gmo9l_WZibxB>I3n|@`f;D)xGyHH?U@5)X@(0x*gyh5EJY)C2{@oyeTE~Hb*v7y< zlBcOTCEK-nLHHeaTZDg21n`gYacF|69KHE3|G-Jj67tuAvnUZ8XJ*D{bkSavz}R(i z0Od<H6{c+lkUz$*~@rF66gFM+c@x3AJ-itSq2GPHdY zkXi>Uf9odB`z(n>sS9q4DO~$HygO4gE!}lSF}QG`4Ax3D}44{yf5Z-O+S+?mDkC$v|GS+K}ZQw=#quQ7G?t ztpY4wQGP;;Pp-uEX2B`$nh;C7I$f`R{HdD!(q-UK$(5Zo-f;Fh-8*nfskY=@FbI^>DurW9qeQws)4mP1>+3S9`Yw@C zN&2xl6#ZuY6fA0RNMI8k9;$%`!b(2W>3=VCYJ{ys1SSVA9ZABhz2fNcF9ifT*5Hf7y?BL)w{W=|)eyoREf65^(FSgG6Q!J~;SCjm8+v`$XI?Y^Xzk(76NKDbwx4 zR|&IcfhL$lzIn)fkYfdjVZyR37IC@G5td4{VK?^a&)&l}#h>NMo~XntqW5zTtOjyr z+CQhL(%f26tk;6G$QAIBZ;P;eWX+QwIGr8~VBUq5#aHE8oBqh=gnQur{!yu6X5&nn z^*#qPE$sel4jgZyJH7pjP}S5`=odA1H*lVMoml%wi(>L>b9}T)6O7gY!WT>az|drCVCg{OsEpp|DGGAbDCM}{z5SQ#nFd)w zwu<)+?ti(CTQK(_=LIsTymXl7OO`jx#sz8+i_QbCFLbrGJE;|~y`s&wcF6#l)+Hbw zPfahC^+6Fl-f5hSpRn$9%!lM4!luUH7PzQk^ZIJ$j?7o}5gH^9&j?52CS(%PzI-}S zFGOs)0l5&rvSiS;sIEsua!a16Tc`ROPP37FQs3u0;4}4RGkkJQo^&S!iN*Y@kSlXF zz+>f4EuGfJXl|BUJX#aVMe-AobQQ^`YfUO+cl~Eaw{Gn2x_3^~CKrF9qDv9Mn*6Bi zf=Ie{&nfn>?m)eH76VhVTSR%8uDA6f|C!*ZDuHdBQd#wj3MGo5>eIy}K=s6JgjZ}= ztM}{{jEni#Avc%nea&S96)oMcIow+5ZMsBi{lxT`rVsHTE@a5@J0q{uvD4+0*QcWz zF;=uM#1i1w(63JZfQNP}$TsrtES2~5@dAuoU#kRC+@*bTnd^te(UsG=3hw@P;%f~R z(Z)d>Ffpm6z3_}iYQ(mHLjQd*G>=##Kj2cNaD5KmovLPR^s94jc;ObUD-!+LL7_{(R>fK0P-OE#G z+6QNyt*vr(PVcjk^teiA?5vERy>i5<+P<^GfHfMI;7(cuFxG@bStW| z6lwEa4NCV7J~N?t&@x67;CLIslbO4lUTPJl-P2^d!{b|Is-$}UyqpIpnat&%f_e)o z?Am(thnjrX-$Tg_TE%`mP6#wBhU>WHi0)iTMGJ0`C_bLHCD3=t-0jN@hmRW5h zMj1_I6ojM1TEy~<3!2e2b)cDY@TA+@M1n%FxPDFDsq8l6I1rN)Z|>`l0RN9>NM3() zyF-qxq7cmkW!(>ck-J^-0}SiJfX@+$-9TNjYZHnx6C-lA9Uf*(!fsjy+U-3-_zvRic0=#^X_b^y+?aU}Z>Xc;$-%ho$8T zyXN4llpX?V=YqoyY6(|t!6c?IfnFo^+AS=urd_YBfhQ_Y0b(CV6)SP2EomW~c!c{G zRlnC}#jl8hU?m$3wQl6>#q1T6U{Yp*m6&j`$-$^~hA z55clgwGY8Y+n6|2g5c7gr+2pa$KeKqKBEO0RE_SD-&{BPfP-dsq}&jO!m8^iCh)fS zrR9IpsC+NVZMm*F$!9pD)pqF|Bv z*h~-MIZMWoZQs!9U-+ac@p$EF6K8xhpXg0ExZ0I)Ga&_|c-=-4gFsVHEn6d+TdV_h zxUhbLWT~GE`>)cCDXNyjoL~QJLzIBPWhSjDx!95(rvq$N8R~F}MNId>k}~00>?e9E z7vTh6RQuu&n<~g<&VjmXEGyhDx#bVU#zs7giwd5SV$7o9GHbzk>4l%o1Vb3pGh20Y zjjw9DW(FAO4|B5B9-z1={RKPGbH|wb^5$odeLyMC zyik2%4RL-WocJBFA@V02n(y+ZeB!lHk25($|EH+jKJbqC6Kd7P0q`K?yL+$WqN3uV zZa2~^?heuFoJgkHP$0=Q9NFAN3#s;~E08-_+JE~pD4+z_g!w`g=hbfMi5RMOhNS9w zsjZ2#HDVYkyG?vvq0Uw3>XM;4@o79-O0~`t?~g~W`Rj8OYqAzt9`y!L@41!7uh>m@ zJr%vnmd28`yrwr(&-b&wmHwh~$#u(Y4BA6f5`SB(U2wMKTJLjxA?)eQ3LT=}-m!H? zVxd%KuLeF!+>S{Vex^&^;)L97?+?n3LbKWd3uMabcWK#2aMvW&Ypb^nj|#ouBEDmu z_8ZY1G*JB^bD3U_kyk*m-i0W}>n-NpWECCFPLrDd4pe?j zb*0u_1YPTcKy2<)3CR=U<1rIGKIQO7yg$6E?un}q!A8fzO7IaPL8W|gt_;dVyAB9R*cm+W3!Ma=_R z@xsU6f*|3Jfj4%l!rrV-Yr^eDXFV|_^~k3J_3W&rs#lTAtM*bgZHmvEY8mL#oYIv=FOW4ze-gj2$?`S^H+!6gdN%X zb!boL^j|m0Ztz-pa;W;V@x$qt;@;oS@eor>;oMbiCH29_tW)j@L3qs}JAc>r2h8NE z=h&EI+Big8Aef6*Y$R^H-GX^W3vcjiDEz*DXxagpH+CzV342k)V;w^mcOqLUp@E$N z6eDSOz|;{L*XsK*`8js3v+7iyF@g(;J3pXd;63DIdc&H zO(goHaHNll-YaQBa$lP^Lk@NzLHz*RXLiiT06+!Pn%4ZInO5=qXonjlRwLw(-*AV< z@?1*?PRAH3be#@j6iRk!!x}9f@1l(C$E%;{kV!YmmbZHb%^VHygQk8lu5|rdqe2CL z!ZKC2nzx8`<^|HB=XS*0kx@9tc!4A5EH7<|)SSf)woNq*wvFnsS(>D5P6pY+QaJIn z*qA0b-TgeThGa1`WYCza@OsxeEC{uT6w-KB<#^DqYZ`^IVyPz6JZ| zP2;gH9fJT43(}vllX=g>G9?9TiwdV9q2ai`ohBl$#wVv#65g&66GGQ!i1%;PH;%Xhh~+y0@!=iWwyLMMD@P*qXzkq}}2%H#dit77@Z3_2r{N$tS2s1m_YoLEq6k zAJi>A(F{)2pqtf8t_}FlU>!NB6>Nrse^a7CfW*uzjXGXa0d@t>h-X7h;nx&E(F+eq>R zH`LbYPe6^cYL6m4%5iUihJPsE&n;c8JwkaF3JDIJ^!kjqxE z2b^D@3)l(x(u%!k*kQ#uw8-u5)}W}eZHS&du^tXNK43N>I|v`EeZvkMC54y4Wz{i@ zMo1{0dA!HRpfgyiDSgzD&(FIr#}2-3;5Zwl{e{f#<05Hk%HJK+c-C@f5qe`2Ggj8d_R}pEkUDR%iM^qf>G& zf4kesTFYi2>qSHAl(kNYp@~Q@Ye7=x(?iLQoF&g#UGo@Dm}e7HO&r;uv`}L@TM|1g z531Nv(oV>FQHSQ20E5cDbI_9b9BDIl}`3sKF@As znlYE+DOVGtxz>y*A)-fioZ~h}FO2GnQpqaGgHs>?bU1#w#i{oF(!t+cDQk8ZFD{g**Vwi6oHF7C`CHext4rbwi zrNuDfDTE=Wu%4dcnz{MFDIKO;6?Ih#lLqOnzNe$Y>`V_y@{j zr6xc_wPHrBOdb6RW#1Cgs37-p3^fwk-?6vV^^W)XYroXng||_Nd&*sk8&YiYTOy1H zGDBmjDE>1M#EP#vq=mZ+d-K2B)MPD2I7Cn)_VbZ`r{hIqcy2qRNLwD2Y24*{n2U)7 zS%}P>%qr%w=#d%2R`D9}onQNZj7& z>pWg^zZPm-yhc&l3=eDAmlGx*FtM>T4X4f1E*_*}#qWwouBt>Lv|Vi}<*nBTqpnM` zK_vU#D8+ZbJ@L5tmMI)|`yX!ECcmptQsWv8oOGDo1sNk%zAd1WIyYr+KLn^rs?b|0 z@M#U6SH?evFa}82b(MSIH)#Sap%0L}Kn((OPWjXq>8_q8BajHiQylFRC)I^Q%*mgR ztmaV1WL1KIU)&ziJX6f5L<*z@ic^p(h+JkmcsmT!>sjtgqkd%O#`Bx0ap42+3Ir{r z+GCP2>EOWZrnF(N5(5sbxTb8KQ#?$y+NXZQt&y=Yt8Y8y9g9^`6tPYehz`wvFroqg z0007I)ZtyxvRZtKmpF?s_ryN+3maPw$c?!Aib5!I<^ zIGz6gJ`fzkB|cXu$9D<2H!3FKf zfrsu=_TVo7(;fNPoTNX15aaSvMlW%J+rjau$tFDQ-&j8zh3?`Bxmht&LweJ!5-jhG z6MT3Ci}DpbLt4^TySA+~NyYW#?t^JCk{$WCFPn?mXu(VX?2=ICt3}BV+4((ZFUp5W z#Nr#omUQ4!-PhYIh{s)yCF#F+(9NCd7yLv;JDJLA&0Pw^k=}N&zx)X0?H1nunv$LQ z8>D%#fwV#{zWI^cT&DDYH2M#CLwScO*_;)NCtbf64U5~-co6~6PfPhgx)GTk`;k0g z%~=+tnwSY@B(jYagmAb%@M3kCPt;EDyK57pMo)1vWqs{PI(_G)HH|=^p4*_SkkC1S zqw{HXr3DV#A>` z&X0NiH!hbKz&@!iAjasG$mTq;$B@ z>Oxh?T`DomVbH$SHO?*{6ilfdpv0%%xt^I}?rInLUgz!Q(2MKNnWbTDE)N zO5L#K+(18FC#Z?*>$4o_F@re)=6cAOxyexM?R^7Hhic%+q+e}?wo=8D zogh}TA@rH{3%}~mV~lvCWh(9nmWyPmLsT84uJ5U;suwi*N>mOm>2ycyvr*Ai6EvfR z%OQkQ%3>dl;Yy<5ADQ*UGATF%Xg4NzTCNc)?@SFeedOm2e5BpC2TB_D@>=G=r8Iv41N zT<+JtXegcD_1y4T{c@oVb^lq=UvXDx8?a>%gM^Cwiw|sw$j1qVsK9qG)zGi9%woE9JOa|k!~gxC99IO z>>S;%9#l^YpHIUr6s1)OK8AdJ9R@xQh3+tMNkZdONNC1NwlG(yvz^^7!mqzZcR)cE zIe~78#2yPgan19p{=;=05+8}9l#IMy%o(E zY=gPE@!C2xJCKjv`mmQ=!xbEt6}drZqZisyROaTU;vNG_J@x!Ajdp-J=(r%^NcEaV z3rd~JQuZhX^K;2pUsoG{oJxRj#&f%n9`mm7xTsmBb9n3N!(HZQzI45@#zJ``#*pNz z`-F#(DN|w{oz79a+Kld^%Dp=O@rd^6nKc-H+fipKv2yBN%gsjeDryRs04i~B9|r-p ziewG1V|ti+w&-Lq{2DqV{~L&V+Ho}f4QLVVjtfw@x}Dm0|$MSoyHNQY>`OG8g_5B?K@yOlffaH{X6tIpP$|4 z=51*Iq7{ASnY%<{Ach`IZ16w-xFz(!5)L=>lR_(vk7)ea3tSD4oYgdRpd+9@OS}m+3m#nm|M>1Xffp{;n21fif*5%Enj%Ic&!6I!N%v?IuEGVsXYHXpg!8*pjjjRQ!Cy^z2 zBqX?(LDV-4IEz!g2>+qzhu8RP4%4rYuk{=9s#s1lyYwPM6Jvk$rUZ+_%J025DRN zx&gsv0e1Azz@}}C(SZg%{8;YFu?b|d;xw1#ynbpukJzSSalBwdgrRP%ZyXW=-DTLr zPkNgj&~+L!Am*OyO)%FB#Xs)~`-W1>Amxez8k-Q#BZ7W~tTeNEsx{iBqjed_eYxIu zN#ySxvIcmqh;|-prD${b&sqzK>YnRcd+4#chE3-a$oO=}E|rL!)K=|fpJq^^!}OY!<}dyjLB}l2hc#90z_Av2r}RB94^yG53y^dd&eQ{ z7vVS3l^LjV_~dMTXtk3V@pS8Q`;>M4a+F0eHYtIx5L35 zaGSw}vV58w@sQzrixvTk&wT=ube?mmOQsIX(l&7)V|!!6V=hqV+kEo=?RG?gsiH9d zo$^jUrP%kayiuH8A32Et_xI;xj;oHnp^cCXAXf|o-V9D z48D<$=zs0jH#nu~1V9!%SUi`R-J22G!J)+9)-l<9+dJlK?kL__uo|2N-h;)rG!|bc zbq8?|<4(kHiswAzwuaOl27gA+OlN_%p%^mJtbid!GC?CPlAK#1s5H~3vK zO`+zf9ujVSBsqEBu?GORQ3N;1BFoRiY^!gN{YM-$z zB!nC;ZArCFPE4H2aTt|2yZu4-7?&nD9m?nM#$XQH<)h9@ncFHjgD-V%lfYl?aQ~<#ZDLn*H6K+tx~6Nn!A5 z72n)QTsbMxYRn58gL*}WH1FYQH7hlHy2Y1`-hl&6Zr;|*x+V=_`d$Z6LJVPg!~Oau)rpz)hvH1uaGdY zE&`$Pz%=Ia3J&DrnWFXo`(EiCt2bq40-S;!Sh!Pc6Lt}QX(_>Ujt`G>R#>Mss zobzII-otAVNqAvO5|yeK?1LB&cUz5cnTGSB;^^l9XmUoG!AL`jhRH0qF78#|wUrwqGZA)!F^4ImWeu{=(<-br+o WO$lrxZ=&BJ#tXEI(Z<9a@Bjc5cn|Xc 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 0000000000000000000000000000000000000000..b12e17ca69c4019393d68549fa4ceb505b19f7b9 GIT binary patch literal 171520 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�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"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/protocol-coverage.md b/docs/web/console-api-usage.md similarity index 51% rename from docs/web/protocol-coverage.md rename to docs/web/console-api-usage.md index 1f4d15698..27e88d574 100644 --- a/docs/web/protocol-coverage.md +++ b/docs/web/console-api-usage.md @@ -1,30 +1,26 @@ -# Protocol coverage +# Console API usage -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). +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` | 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. +| 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 | 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 @@ -47,8 +43,7 @@ storage, URLs or logs. There is no project deletion and no plaintext recovery. 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. +public client's strict projections. Archived projects remain readable. | Resource | Reads used | Deletion | Creator | Console surface | | --- | --- | --- | --- | --- | @@ -58,8 +53,9 @@ here. Archived projects remain readable. | 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 boundaries: +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 @@ -76,92 +72,91 @@ Resource-specific boundaries: - **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. + 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` | 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. +| 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. An asset an administrator copied in an earlier release shows **Admin copy**; a resource without a record shows **Unknown** | +| 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 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 | +| 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. 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. +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`, 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) | +| 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 single-provider read (`GET /core/v1/harnesses/{harness}/model-configuration`) is not -consumed; the list carries each provider. +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 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 | +| 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`. 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 | +| 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) | -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. +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 @@ -171,21 +166,26 @@ enters the release under advanced settings. 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`. +- 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 | @@ -195,39 +195,26 @@ enters the release under advanced settings. | 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). +Agent metrics states these limits in its help tips: + +- A request is one root Agent Turn. 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. +- Subagent Turns and deleted Sessions are not counted. Busy projects exceed the + Session caps, so long ranges can be partial; the page names the projects that were + cut short. +- Usage by API key counts Sessions created in the range by their creating key. +- The console accepts Turn times up to 15 minutes after the end of the range, to + allow for clock differences between the browser and Core. ## Not consumed -- Any `/v1/**` route, including Session creation, Session events and their SSE - stream, message input, function results and cancellation. +- 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. -- 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. +- 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..4e76e95ed --- /dev/null +++ b/docs/web/console-server.md @@ -0,0 +1,219 @@ +# 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 guide](../getting-started/install.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) | +| 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, and hand-edited generated files 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` | + +The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` +([configuration](../configuration.md#appendix-core-environment-without-the-installer)). +An invalid value stops the console at startup with a message naming the variable. +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 ffee4c4b0ae29d29fcd63db00f86971ed9b25e2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118825 zcmbTdby!qw^fn5jASn1EB`qRd(hbtm4bmbYUD7!y(j^_z&CuN~jnvTH-JQdEeD(dE z^Ig|D*LTjDf7}i;d-i_zv(~!TecvlUK~4f4jQ|Y+0Rdg|ov0E5!lP6K1f*||AAolz znGk&t5dJ}s6cti&PTZY)!BIXkE0Plqe~vIf~H=Piv=1w&L-1vYh7lrW82 zV12zCpU}tGUurO&E^e%StuB0Di$4D6a^5&)ZxM0NhEGN!OYcI zkonKsLnMzMKOSvj{rceRZ0mHGEcm>@^I7eTm~S;)e&nyHFQJr=*~+EiNj;|6pO_idX!(_Cw~hxQz6&vYI$I_AWvyajJ}=CRJ56Y5$gjEkQgKcP_Q0vGf3qL*Xv%!==~7k=&fe?GEEPC?t7#yZ_WkrkrX^@WZr zwGoDe?4M^E}N`HYj+FQi4g<25zRE<=lztz0>`_~i%TU%zHrxu0}9uf^P zs;b+i#DDeV{Bv>vOnZcPeqZe0Ob$KV^hTPd)7K|M{`)8VSFf-?vNPX_|tJOuOmtE%&5(xBBiYx$q)NxVz7e=E1UMlAq(@^&wR7 z|IcSo)4f}f85fnOyY4!6!54^Pk(TdtX=EAcI)$Gu&qrsD%#T05|{q{Aqgik0U0?%$DjLd$)Td- ztvAw56)5O4p=eyE>^DYc-%%4bMwYAGs8! zUi=;rMa1hUBW7ge{JzF7C#TluDqI9*K^iY&!SiM=t1s5HWn`o*u!`!Hu%7(E;gZOR zC4@dH@&_IXoR5{RzN}12L$Xu^#ehOgEZZ_FDoVG(eXi0?om~|tdrtUoL>!u$Q7@V- zG0;XwM=R&Zp+7}~ZyfMLtnMOZIpe1HWgv5xJ53%pU%v`>h7wChHrgs4qaE9=_a_S; z1U192%cWx(^#|L8g@xms_IGRTqO9I)Y3SA3A8m$?@LsL>yUqUYC z@5F*|2b1}Cj#j$9d=Y9A&=qJB@WUVpa3&$6p)oZuDF58Sd3=0ak?U%FzgI1b@{Cyb z=+{J7D6zVN%4D-Oll7SKU}Db2Sbp=w`0yvQ8>1vvxASu2CH(mK-G{om^EvY1LRa&$ zV?IqVCgsTgproLPPDxq#)lsR}-1O?zE2o3`nbR3@bMqbk``eYBjo7Ft3j-r|2%OLE z1S8~rOdd%@l>4KHtJ8)J*VAfJtVXTs0}0WF&FL%yup>6h{eP~;vX6~v5)i1Vt+%xa z!x81BrKNFjaMBGvK6>(At|W{$e53h57}UwvNB9eObi|UR7Q5l=Xa(CuL`6n+tDrj3<`Cd)wQ)fUYieF z5m{&1gavX*JRTQ!(7T&+V?)C?vf?g?jna}*KHt94Yc{;*WU}6l>bZ7xg&fS6L+%mh z4JAT}q9Y=bIV`1wgj$c+7QRV{K7H|~Wqy8X{g;+tv5o{5o4`$IQvL4U9<|Ss-dRvL zp62wlxVUe_^FTt!{mY%1XgZTPHRg*Tm$bn)Ukv<$%-qw9h5Or*Yl-=&n%$=^^^(HljZ8B8vI%7@t46Bj<)t`HE{9iACu^Kp z4alXKpGQXCk@D;3^z`?}L}5GaOzCJQzzf^rrwQdr1kuyKf81Pz(?X@l()3l{ohXrs z5r&>~h>3MvkUA@VY}2xKa5(dLmU4WIhQdo|$?z+6(O9yF=I_pZ%R3rD$F+>LEG;bs zcG1YCp*J$dwe6YwV2Yr+n%bv?m$bCk6jT0uTxw@pNsMqA=BBUKZMW5|2P|?yRL`1mm ztQUenEGg~w$847fC6GN~)IBv4kuEAINS~C{R@psM#KDO^LJc5J+MRsDCMwFMJMU@a z$iYU>$k>USKtL#Pc$gNFBN!Q@tfZ;={=J2z>A?DX#bMiP*SZ+oS?PD9D$xUa4e(FVy<;^GBq<( zR7Cb{p;=KyRJ5if*B{RYJJu!^a=lA2Pk@8HivMRNyv|bxbZ8k+xx_0oJ&=s17=;3h}N=&@ATGKU|GnlATzHY2ToFs5}ZDM9(G2h?= z!G+gbz;Eq#LVj?H2n#oQxQ7x8?z`9SgziDu1$dPgPRtn?3aQKe0|VFRvXpdnn-2Rr z%*<%>A(v_AdLgEd4@fKO>Jok@we{WIenL7bx;{23F^vGXJf9*Ai6}y?p*pL%CiyR7 z5>#%QES$p%>*CKJ`OKP#q;st2s-v)Cu_~`5?#}vm=Td!X<>Ap>{ZPwe-%5`{cG z)0znhiPPRbtFbg+A3WIJHtW9`kMQyib**!-b6GL``t>U#Bjat7z-W+1pj47@nBTK4 zuKUqEtn~DBZf@?Ki94l(27G*clH_DuT-^5AxxU^$D%r%{wY4NYg`uBQSLiQ>!XAX4 z?Lej~&HYMB-u!AZzp%2lww9tZDe%b>`+2hA=?UKzBYH;S`oeP~oA zBhEWXOh-0a3EGmXphcL!n@9w?&!Ox@?(9VFr#k2e>h|`-5OlreA|ngmF3j+5kLrn( zkqM4>3fdmH42U!(gwK{u<((6rVeo|oXIp!;DfWPWXT_q(mc`)F#Ns zvh`d;rR!N(MS5oD>p<_fIrnQm{Ze~COKRd-1?asXl#o!<#TWW39A7fWdAlM#P!$oHhl~f$=es`~L znUjWXX#cqk)MSv?aH6A`OOJef#K1N4YbTjAjXUGm48^8OTokpY>Eki!Wf++9>sa#h zTdqest*jSPNXM2}KM42t-#F!Qg15y8a-8jt1-baq($Wfxq~PG;9+3gb|lb&@B&2YZ_p=9F~sZw$1cEcZ`6NuF=A`y^kz==RP;+r3Tj27}-ARz+Gm zl6<6%s-j8f*StT>crZ#mDkmogMZja)xdjs!Hzp=V&ZXBU2=51>P5-sUdf#fSJ?`Cl zp_YMoSPy71oLG>MycZw~6>bwCP2Oj>JAi+i@0aSc2X(vZDg~YPXSbGpf`q;ZwY9Z* zBcms9S{Fq`JP#Qe9=qBp7Hrlz|cSMY@g9{ETwf zvkP3@b!+X)1ZbINN^)_mGKs#qxg-<3EmY32LEng?qcp>>v|SCCxPX3JAY%My_t4MH zd%5PIprE5q;ZaeWBU{8e1VfAuAMzzMFG@?{`aUIL`@H(t&8@zrr3IEVsM}!g0@7Vq zC`tD7bsbDBta{tM&*GnA8j~1c=gAV{%5_V#9Ra@OcAv?~4W6Q(MPcLAIc#li?cKj( z>3pbu@$Uk=yKWjbREOQm3%b)WFm&k28IMzilG>e3%UYA_Z4@QNKl1`@fl&FU_tlPb zp*p-qnMMPN+!;khjG%LBFX4fKP5|KL^|f^j0zNJ#ChOi&8c>1b5uM!(;mu}^49Ypb zoDYY*M?wV6Nn!2fAC}UkV~bC>=5`L|c&TI(WpJEZZ(XEg=|6ULc2U)w?~3)r7FYX$ zmd!6`<7kD-`|H;yL4p0hcxB@G@*vGkPcIU(FG;K#*Vfjakxt*wWdC|b>bg7IWI3K# z`~3NHM9=vKIm;M&9nbUGs{pLd;=3!V*nNJi$O!89TO+ktAyB6t3JQvl(;%EaSs*z6O7hmp?JI3@{p)6i4D z;4}13Dx6yvHq!@>h%WX8b8~ZF2)Ms^M6|)Az50j< zkI6Pg6xLqJ`@>2mo+l$ayYsD{o``to%na4bMNF}0YHG!d?P+`;*NyE&zxnVw?T=i-Nogq_g>&D%D-Dq z;L(>at@aJ~u607$a>*_3FP!IW9r)~*}&3J*Xfek@zC zSN9;5IIl4yuy3ejzwt$HjF_9Px!}CIz8-e_^hxc#GZ*^MX8=*#=nm%kLQLO#dVT>F zgcqRmw_D;rPjdSU=;`{@tJ464Tiw88`S)%QX?g~F#$Yg0_XLoE5>G>~(Ji#|A7G%k z-T0A^_+gL|aDUEpL9t_nVYJWs?JYDf^!34pG5L90x+H~#KOUwE^*85e+$6sI`QoaCK1!f@bja3gmro( zwa3ymCcqW1w<5i-N@!COYr*Gy`Q0~1c1@QA_n&{>9>C$V7rG(m-)ZT{BO@YO^)KFn<30$-~us5*8R7nC_&nukYaC;Cq9Rxzq)^QLmMm=mqUs*AOYt)zAYlo)j(H z4)DAyvO@}YL~?kZA}^CNVfbO8%2;K6-yO=cVx8uF0sHQ|n`b5ru)S>%C(5PejCWidOZghBFE)Y!+;`3Eq!C zZLC^s_jvr;Y3P^^W^$@W%yo3wnpUSRcZ7yJGbH3m_+4j*dlY45L#ocNLw;C5h#F^I z$9tHz|5@|j+%|E&y5x1*>W=wIMM*=~JpJ?;lOU1@G3M!;f7{f<0B-x$pRksf=Y}O| zU*Nfz`wo=OxVYhow@O-CL01SB+qgJhVg0)rDZ@8o`4hPG+Mt&ODDUk7n|tLsOGrGc z@nJR*f1q17H75McY9;~ z@uNjq*&4!%O79u6hwVW z!#91aA=xvtLw2Bv3h(;KQD*44oZNW*v$J!!C0ZJfp1%L&_;{C?7cdY3pSk2y9v&{> z!Tj;UnM+Xv*j2}(hO@J?P*KtFH+Npn{n=P-Ndj(ZW$CY&Y2wqpr&x2gH%(5&ANld! z7%!w1>F|US^M7@zE-SP1`Sx&UHeHz1HVCcdKK|w^k%V76jEMVu^JwMUH&JLyC9AoKeBEE;cIkHLVsi`IA6AMkAo~KIc>KPGP(zu*WBbe=e zrHFp@H{X5tAG)kWxLtg{aU0YH9ElX?W=26lL1YHG?9{I;Ei4$iuN$%7m*iweU0qL4 z&$O8Agv5fU`!fGhgqKmnn61ZIdH``j=6n~Tv2lV!(CO&%bw$OaV`6?K3QSG+e!cpz zh=ySjJH3zgmq7j?CN8FI^})jMZTs39PJpB&DyrmzuMb@7cuqggW1XX6WS>LPb;;1l z0SAkU_@@lC=ETA4f90JY68(7>VQ8Oh@$aFR<1?@q{`|-5yf8Z(S^fW> zwElni^55SDG>_Z=Z$AA0)2XpOAxx(g>}vmISr_#lbEWDiseCv1a}SF!*bu7qZ2I2? zK=^;b$2Wd*V$oDpbpV|+!3(z3Fu-kSfjVn*w@!?R_=P!R_DA`f&cOp)RR`4xMq&`k zAe?{6g#2x=5xhQ`E#VNQrzc)siq3akUOI<9_A?b=XU9QDXD8_H?F|SFHTt{Q2noeL zVF9XYO2N|a%1YiC2zF9Y($UalW@eX?g62iI{gsk3Y|YR}MmB_fv=*~7tB(_G6!3E+ ztSmUIYf1Hv&oMDQ;}g_$R8=)piCmm%^=O-;BBg6H)De{*X3y_A$xFfo7dNJw{g zT#5fX4}Kj&^Cy~@8B||(4MBU(GBPrk#2XYO{kQo>K)5&4Zy_UNEJ{zuCX9XCeq@W9 zKwy@hs9v4hAR|e~K({k8C8?zq`5aB`kxyKlvtv}YZ(VQ&b|dIx4L4TVDh@X%xyVKM zm9fbAb%=S*`+ok61<|O&`1-Y>seU#z`xQO?&;M5YHh)cCad>P@PDTolP%<)dY5KY@ zwvx<#PXo_KLTe`gb+_9#l zY74(?sc&VtUsxu<* ze#BC~mMMLwX;zmWk)GD%`R@I=(ul`qwLH(V-TdjDK6_1v~qd&<80ineY;eVR|bo)rLo*dHT z78KAkFfcGN6&FCEMMXti9WeQgsC_4f}B?w_%&Q0QC4 z^3y~f8dvB;n4ad%HTw0n>!)!2Xm(gY)@%|jRmY){l2XL;=T8yd$SB&{>g%V~)YL>} zW~P2Hx3(5#KyoHF!+)zK^7|^Rt7F*H)4G1X0tq&6?*RtdOS_Zf+~n@Q9x@M9Kcmnq&;n!5!drj#KhFc z45ys0852iD8S4ae3x*##!1C^}d3nz<-#N#f`DZc`2d$X6*v|gOy(cx*m!m7Tel;C8P|MSoa zJ1Ps<37&HO4xYdu_R%qH>{VMm{07!jBW0EE>gvi_#iS=zIJme{l2R-PLl0Fna(mzW z`Uakt{r@aqWkF1(xHqDNkbc^?ni^`qc7z7IkLl^BXLQbf(P<404N$0*ib@os7iH@2 zE1;Ew84H2Oh5P%ie1AWAO@%(qJh+#)mIEE#mM9O>Ve-&KaeBjTlB#dn@n_}=RfJzc zs@VnH?AYLG)&8H0Y{g)nb&V97zWfRw-*h(q!2zbnouQFY;KEo4Eq1nlKoBd!km1LV zA8~$}7J+Xi{C?_%X<$Js*T>7$pn{9JP-thg`r*7SS0dEu?EUg~0zs(%V;0R*vzIS7PGqt&5(2;Ac=Ts_!cKIXPp|R8^wz`cp1CeSnk)F#6_kbpFxN(fIgy zBxNw^n^Zwqr{nId!)kYFxv?BHH}}7keyC{DgF_T$g^h#5%QXp;R#N(WCU2g5n8Pj@ z1j?Kbn`7U&We8JdYO2ldWk9pVBsC+WfZ1yI>gsCk32woa3o)#djh!9jhS#rOzxbQg zF5g$IqJ^HGK9yqjt$miYBoRh(b9DoID`iF-&Be_W16YCvq?HmpwQ~%=vc6ut}(?pPAL4Rdiudr^d9q;!3pQ{6N zZoH2B*%L4-`C!>#!uXnkg5CajI}tyOPcZcH5sIKOP(+diMB1^auE1PK{qYbrmiI*iGq!Di`|HR?VP zMu_-hkm1u!k>aIyHGno&f(rMgWeu}{MoY@4-tk@*%h2%Ch>fmpa#LE*tjm*0AB(AL zDv$o&-cNtmW>MsL{6Q#%G;UrVbbOSBg(aECMTs1Xs@YvbMP;;Bnd)~5AfuA#%I&5m zAmMSgF*F=4H`d?HHJ>QThe92lws48DSY5X5A3gF};D9gh3#=Xh1+mHHi8U$;22sKr z{n=@DL5HnbNiFG}Y0yczSXgK)C>-wKSplsqr%%0Tn7+9{PfJ5pOgdXT!!MsMEGQpx-N2ad9vtfLO6QcH7pryu7^Ivf6|7$B(YPZ2QB-FAMi2*VAV^fb9eIozrTbfYb3Q!|i10N&*3aIRqZy z>vpjn4J6;=(LC!W-GKoC51utztf{3m5tM_e^6HWj1u3cR?aG}B7a*3c>ZvPx9I~IG$Zc(L_=crFnTjzzpP=^~0$Be5UE_{4G3O|7}Z#&y)FC z7g~hFsOadmHM~_k?A~{hijOvIL|-6Weou{fz=q-QaIsN@{ad!VWDB!HZ)R3svu^2U z9P}S?FN$y1%?X^3SGVfU2m#O^Dap&*$_^p4x33`7{MwMn*SLX)JxY_)A`Bo0GN@}e z=X-Wr$I(X8v9pa%&AGWI-QR^&NU(__q7p4kEII}82xI?2^6=1II@I5XST4Bij3+I% z&lNA!47ZP|N=k-9ioOpSgOWqnd>5+Q(02Llq3Z$o)69(Dl5Xn~ujO{sWDS=fNlZ64UfS7u>-%0Dy>cH|j_e%d@B_K@(8!1i*eMs!7MVbmM{_>y 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 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/scripts/name-allowlist.json b/scripts/name-allowlist.json index 387b32e61..b902e4b81 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", @@ -659,11 +644,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", From c22579860e3018b14d4f88506c9c559a178dc8f0 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 30 Sep 2026 06:51:25 +0000 Subject: [PATCH 2/6] docs: make apps/web a package guide and fix its design inputs Reduce apps/web/README.md to the web-developer guide (rules, local run with the fixture console, checks including Playwright, screenshots). Fix PRODUCT.md, DESIGN.md and the impeccable surface against the shipped console and its stylesheets, and correct the i18n, Magic UI and standard-size notes. --- apps/web/.impeccable/surfaces/src-app-tsx.md | 10 +- apps/web/DESIGN.md | 455 +++++++++--------- apps/web/PRODUCT.md | 59 +-- apps/web/README.md | 242 +++------- apps/web/src/components/magicui/README.md | 5 +- .../src/features/sandbox/standard-sizes.md | 15 +- apps/web/src/i18n/README.md | 31 +- 7 files changed, 359 insertions(+), 458 deletions(-) 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..312e842bb 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -1,88 +1,89 @@ --- 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)" + card-border: "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)" + status-idle: "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)" + meter-track: "oklch(0.961 0.001 286.375)" 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" + control: "8px" + control-inner: "6px" + segment: "8px" popover: "8px" frame: "12px" + window: "14px" pill: "999px" spacing: xs: "4px" @@ -96,70 +97,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 +167,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 +193,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 +203,11 @@ components: modal: backgroundColor: "{colors.surface}" textColor: "{colors.ink}" - rounded: "{rounded.popover}" + rounded: "{rounded.window}" meter: backgroundColor: "{colors.meter-track}" rounded: "{rounded.pill}" - height: "6px" + height: "5px" --- # Design System: OpenAgentCore Console @@ -217,14 +217,15 @@ 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, +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 @@ -236,9 +237,11 @@ interface shows absence honestly: an em dash, a gap in a line, the word 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`). +- 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 @@ -257,13 +260,13 @@ 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 brand colour shared + with the public OpenAgentCore landing. +- **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. @@ -271,18 +274,23 @@ separate categorical palette that belongs to multi-series data alone. 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** (card-border): 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): @@ -293,7 +301,7 @@ separate categorical palette that belongs to multi-series data alone. uses Series 1. ### Data (categorical) -- **Series 1–6** (Cobalt, Persimmon, Jade, Saffron, Rose, Forest) and **Series +- **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. @@ -305,8 +313,8 @@ separate categorical palette that belongs to multi-series data alone. 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 +**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, @@ -316,29 +324,30 @@ 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 (-apple-system, Segoe UI, +with PingFang SC / Microsoft YaHei / Noto Sans SC for Chinese) as fallback +**Body Font:** the same stack, with Inter's `cv11` and `ss01` alternates +**Label/Mono Font:** Geist Mono Variable, then ui-monospace / SF Mono / Menlo, for +identifiers and code -**Character:** One quiet system sans in several weights, sized for dense reading; +**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. +- **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 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. + 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 Graphite; axis ticks 11px. + Status labels are 13px. +- **Mono** (400, 11.5px): IDs and code in tables and name cells, in Pencil. ### Named Rules **The Columns Line Up Rule.** Every figure that can share a column uses tabular @@ -358,12 +367,13 @@ Skill, Vault, Credential, API key). Time ranges read "1 小时 / 6 小时 / 24 ## 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. +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), @@ -376,8 +386,8 @@ 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. +Controls are 30px tall, segmented options 26px, table rows 44px (32px compact), +table headers 36px. **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 @@ -386,16 +396,19 @@ 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 +- **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 `card-border` 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 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. +- **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 @@ -404,61 +417,66 @@ 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, +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. +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. 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 +- **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 +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 +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 control lift and Ledger Ink text, and the thumb glides to a new +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 +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). + 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 @@ -467,23 +485,24 @@ Every resource list, the Session log and the project list share one grammar: 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 + "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, 1px Hairline, 12px, 40px 24px padding) with - an optional 20px outline icon, a 14px/600 title, an optional one-line description +- **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, +- 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 (danger). + 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 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 @@ -496,21 +515,19 @@ Every resource list, the Session log and the project list share one grammar: - 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 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 +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 @@ -532,8 +549,8 @@ request runs. 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. + native installer reads the unchanged JSON file 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 @@ -593,9 +610,9 @@ request runs. 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) +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 @@ -613,13 +630,13 @@ 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 +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, +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 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 @@ -630,7 +647,8 @@ 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% +A 5px pill rail in Meter Track (Well Gray with an inset hairline) 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. @@ -639,15 +657,15 @@ identity colour and then ignores thresholds. 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 +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 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. ### 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 +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 @@ -673,7 +691,7 @@ 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. +(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 @@ -778,9 +796,7 @@ 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. +late reads, explicit retries and login isolation. ### Sandbox reset The deployment section offers explicit reset rather than maintenance/resume. Reuse @@ -821,7 +837,8 @@ 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 +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 @@ -891,7 +908,7 @@ once per range, new conversation messages settle 6px upward in 260ms, pages fade - **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 +- **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". diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index fda356583..5f10d11c2 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -13,7 +13,7 @@ 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. +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 @@ -57,17 +57,17 @@ workbench. `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. + 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, + 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 @@ -85,8 +85,9 @@ workbench. 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, + 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 @@ -184,8 +185,8 @@ workbench. 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 + 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 @@ -199,8 +200,8 @@ workbench. 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 +- **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; @@ -219,17 +220,16 @@ workbench. 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. +- **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 @@ -246,9 +246,8 @@ workbench. 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. + 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. @@ -260,14 +259,6 @@ workbench. 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. - ## Evidence on Hand - Browser acceptance in `apps/web/e2e/`: one test per acceptance behavior against diff --git a/apps/web/README.md b/apps/web/README.md index ffd3edb82..438522381 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,190 +1,70 @@ -# 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 tokens 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. `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. Each test also 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 +74,12 @@ 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 +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. 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 +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..7e2d7ba58 100644 --- a/apps/web/src/components/magicui/README.md +++ b/apps/web/src/components/magicui/README.md @@ -1,8 +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`. +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: diff --git a/apps/web/src/features/sandbox/standard-sizes.md b/apps/web/src/features/sandbox/standard-sizes.md index 830f6fe28..0dd45bce4 100644 --- a/apps/web/src/features/sandbox/standard-sizes.md +++ b/apps/web/src/features/sandbox/standard-sizes.md @@ -25,15 +25,16 @@ accept. - The Web setup wizard, through `defaultSandboxResources` in `deployment-specification.ts`. -- The release bundle: `build-core-distribution.sh` copies this file to +- The release bundle: `scripts/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 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. +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..a5efffd5e 100644 --- a/apps/web/src/i18n/README.md +++ b/apps/web/src/i18n/README.md @@ -1,23 +1,36 @@ # Web internationalization -The Core Web uses `i18next` and `react-i18next`. English is the fallback language +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 +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. +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 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. From ad33dd3e372f6e6b3e9a3d1fb2616a7de18d796e Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 30 Sep 2026 06:51:25 +0000 Subject: [PATCH 3/6] docs: document the agents client and trim the harness package guides Document the TypeScript client and fold the saved-default examples into the agents-client README; replace public admission rules in the Claude adapter README with links to their contracts and fold in SUBAGENTS.md; remove stale sandbox claims from the MiniMax Code bridge guide. --- contracts/agents-api/model-execution.md | 2 +- contracts/agents-api/subagents.md | 2 +- packages/agents-client/README.md | 196 +++++++++--- .../agents-client/saved-agent-defaults.md | 88 ------ packages/agents-client/src/core-metrics.ts | 5 +- packages/claude-sdk-adapter/README.md | 279 +++++++++++------- packages/claude-sdk-adapter/SUBAGENTS.md | 60 ---- packages/mcode-harness/README.md | 100 ++++--- services/agents-api/README.md | 4 +- 9 files changed, 397 insertions(+), 339 deletions(-) delete mode 100644 packages/agents-client/saved-agent-defaults.md delete mode 100644 packages/claude-sdk-adapter/SUBAGENTS.md 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/packages/agents-client/README.md b/packages/agents-client/README.md index 55d1bafb4..c28bb0f8c 100644 --- a/packages/agents-client/README.md +++ b/packages/agents-client/README.md @@ -1,10 +1,143 @@ -# Agents API Go client +# Agents client + +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`. 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). +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 +148,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 +164,26 @@ 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..bb3019376 100644 --- a/packages/claude-sdk-adapter/README.md +++ b/packages/claude-sdk-adapter/README.md @@ -1,15 +1,15 @@ # 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 +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 its subprocess and translates bridge frames into the shared Runtime protocol. +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 +[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 that qualification. +operation contracts; local readiness cannot expand it. ## Develop and verify @@ -30,9 +30,11 @@ for the qualified environment and Runtime build. ## Bridge and native lifecycle +### 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 existing daemon delta/error/Done frames. +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. @@ -42,7 +44,7 @@ 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` +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 @@ -62,29 +64,34 @@ 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 -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. +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 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. +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 -existing `none` profile keeps its tool inventory. Packaged `workspace_tools` -establishes bridge support, not outer host isolation or public API admission. +`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 @@ -110,6 +117,8 @@ 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 @@ -123,7 +132,7 @@ 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 +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 @@ -140,12 +149,14 @@ truncation, literal names, kinds, and sizes only for regular files. They do not 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. +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 existing neutral command +`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 @@ -158,7 +169,9 @@ Preparation alone emits no command. Cold continuation must not reissue historica 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 +### 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 @@ -171,10 +184,9 @@ 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. +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 @@ -195,8 +207,8 @@ 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 +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`. @@ -205,20 +217,12 @@ 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. +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 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. +is no fallback to another execution path. -Root assistant tool calls and live root user results produce the existing neutral +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 @@ -230,11 +234,21 @@ 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 legacy CLI version probes. A ready SDK alone is +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 @@ -245,66 +259,55 @@ and activation; ambient activation variables cannot extend that selection. See 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. +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, legacy -raw tool Items, general web-search control or text-verbosity levels. Router admission +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. -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 + +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 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 +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. 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 +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: 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. +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; @@ -312,6 +315,8 @@ 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 @@ -339,9 +344,10 @@ 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. +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 @@ -355,8 +361,10 @@ Turn has a fresh snapshot list, but query totals may include earlier Turns; neve 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. +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 @@ -385,15 +393,69 @@ 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. +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. 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 @@ -409,7 +471,7 @@ 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 +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; @@ -441,10 +503,3 @@ Return unavailable on failed or malformed probes; never forward native diagnosti 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). 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..cae02e900 100644 --- a/packages/mcode-harness/README.md +++ b/packages/mcode-harness/README.md @@ -1,47 +1,66 @@ # 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. +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`, server name `oac-workspace`) +that exposes six native MiniMax Code tools (Read, Write, Edit, Bash, Grep and Glob, +each prefixed `workspace_`) rooted at the Session's workspace. 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. + +The pinned native CLI carries one source patch: its 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. + +## Workspace tools 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. +are read through the bridge's tools rather than imported by the harness. Native +diff/undo capture is not provided by this path. Common Files and Artifacts use the +bound public workspace independently of the native control directory. + +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. -`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: +## 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 +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` 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. +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 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. +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 @@ -50,19 +69,23 @@ 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. +`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 reject explicitly. The pinned native CLI may add task/skill utility tools; -qualification must inspect the actual inventory rather than assume exactly six. +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 -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. +`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: @@ -73,7 +96,6 @@ 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/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 From 1e4d2cf0a34a84de92a64f70cb5a903b4b513472 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 30 Sep 2026 06:52:26 +0000 Subject: [PATCH 4/6] docs: unwrap prose in Web and package docs Put every paragraph, list item and blockquote of the Markdown files PR4 owns on one line (unwrap_md.py; render_equal.mjs reports identical rendering) and regenerate the docs site. The impeccable surface keeps its one-field-per-line format; files edited only for link fixes stay wrapped for their owners. --- .../content/docs/bootstrap-projects-keys.mdx | 156 +--- apps/docs/content/docs/console.mdx | 41 +- apps/docs/content/docs/execution-model.mdx | 156 +--- apps/docs/content/guide-sources.json | 10 +- apps/web/DESIGN.md | 707 +++--------------- apps/web/PRODUCT.md | 281 ++----- apps/web/README.md | 52 +- apps/web/src/components/magicui/README.md | 14 +- .../src/features/sandbox/standard-sizes.md | 23 +- apps/web/src/i18n/README.md | 30 +- docs/web/README.md | 41 +- docs/web/console-api-usage.md | 142 +--- docs/web/console-server.md | 156 +--- packages/agents-client/README.md | 95 +-- packages/claude-sdk-adapter/README.md | 506 ++----------- packages/mcode-harness/README.md | 92 +-- 16 files changed, 457 insertions(+), 2045 deletions(-) diff --git a/apps/docs/content/docs/bootstrap-projects-keys.mdx b/apps/docs/content/docs/bootstrap-projects-keys.mdx index 1d0043df6..966a439b1 100644 --- a/apps/docs/content/docs/bootstrap-projects-keys.mdx +++ b/apps/docs/content/docs/bootstrap-projects-keys.mdx @@ -3,11 +3,7 @@ title: "Sign in and issue application keys" description: "Keep the Core key on the management side and issue Project API keys through Web." --- -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 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 @@ -29,9 +25,7 @@ flowchart LR core <--> database ``` -The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other -path to the console; the [installation guide](/install#https-and-the-reverse-proxy) -gives the routes. The console handles each path as follows: +The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation guide](/install#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: | Path | Sign-in | Handling | | --- | --- | --- | @@ -47,49 +41,27 @@ gives the routes. The console handles each path as follows: 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. +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'`. +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. +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; +- 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; +- 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`. +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. +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 @@ -99,25 +71,15 @@ clients in [`packages/agents-client`](https://github.com/MiniMax-AI/parsar-core/ | `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. +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 @@ -131,58 +93,28 @@ console cannot create a session. ## 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. +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. +`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, and hand-edited generated files 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. +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, and hand-edited generated files 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`. +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 | | --- | --- | --- | @@ -195,30 +127,18 @@ installer gives the console only `secrets/core.key`. | `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` | -The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` -([configuration](/configure#appendix-core-environment-without-the-installer)). -An invalid value stops the console at startup with a message naming the variable. -Use HTTPS for any browser that is not on the same machine. +The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](/configure#appendix-core-environment-without-the-installer)). An invalid value stops the console at startup with a message naming the variable. 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. +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/console.mdx b/apps/docs/content/docs/console.mdx index 1816aa36b..cb7060e2f 100644 --- a/apps/docs/content/docs/console.mdx +++ b/apps/docs/content/docs/console.mdx @@ -3,26 +3,15 @@ title: "Administrator console" description: "Monitor Core, inspect resources and manage deployment settings through Web." --- -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. +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. ![OpenAgentCore Web overview](/images/source/docs/assets/console-overview-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. A -console restart or a Core key rotation signs everyone out. +[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. A console restart or a Core key rotation signs everyone out. -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. +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 @@ -38,8 +27,7 @@ optional tour of the console opens from it. | 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. +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 @@ -53,29 +41,18 @@ lists what each page reads and how its figures are bounded. | 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 page, under the public deletion rules | -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. +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. +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. -When Core's public address is a loopback address (`local_only`), Overview, Nodes -and System warn that other machines, including nodes and remote applications, -cannot reach Core, and show the configuration file and apply command that change -it. The console itself stays reachable at its own address. +When Core's public address is a loopback address (`local_only`), Overview, Nodes and System warn that other machines, including nodes and remote applications, cannot reach Core, and show the configuration file and apply command that change it. The console itself stays reachable at its own address. ## More -- [Console server](/bootstrap-projects-keys): request boundary, sign-in, settings and - verification. +- [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. +- [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/execution-model.mdx b/apps/docs/content/docs/execution-model.mdx index 2aac6efb3..7845e54e9 100644 --- a/apps/docs/content/docs/execution-model.mdx +++ b/apps/docs/content/docs/execution-model.mdx @@ -5,11 +5,7 @@ description: "Applications call the public API; the administrator browser calls ![Application, administration and machine credential boundaries](/images/architecture.svg) -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 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 @@ -31,9 +27,7 @@ flowchart LR core <--> database ``` -The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other -path to the console; the [installation guide](/install#https-and-the-reverse-proxy) -gives the routes. The console handles each path as follows: +The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation guide](/install#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: | Path | Sign-in | Handling | | --- | --- | --- | @@ -49,49 +43,27 @@ gives the routes. The console handles each path as follows: 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. +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'`. +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. +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; +- 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; +- 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`. +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. +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 @@ -101,25 +73,15 @@ clients in [`packages/agents-client`](https://github.com/MiniMax-AI/parsar-core/ | `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. +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 @@ -133,58 +95,28 @@ console cannot create a session. ## 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. +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. +`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, and hand-edited generated files 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. +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, and hand-edited generated files 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`. +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 | | --- | --- | --- | @@ -197,30 +129,18 @@ installer gives the console only `secrets/core.key`. | `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` | -The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` -([configuration](/configure#appendix-core-environment-without-the-installer)). -An invalid value stops the console at startup with a message naming the variable. -Use HTTPS for any browser that is not on the same machine. +The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](/configure#appendix-core-environment-without-the-installer)). An invalid value stops the console at startup with a message naming the variable. 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. +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/guide-sources.json b/apps/docs/content/guide-sources.json index 91f01ed0a..556bac000 100644 --- a/apps/docs/content/guide-sources.json +++ b/apps/docs/content/guide-sources.json @@ -3,7 +3,7 @@ "sources": { "docs/getting-started/README.md": "d93c270e208fdf84edaf71827ed635a017be8cf67e0a4afef7279e3945d53173", "docs/design-principles.md": "334e0d477c255552f874f3ca8a2db603d0d07892ad7d58930bf8e3d06b86c636", - "docs/web/console-server.md": "2b1e914e2013559fdd35d48d64c6a1b68ef57e2485a46545adfcb2eadf622de0", + "docs/web/console-server.md": "f7796ed8a5c370c862a35b221e31a76a01643c8d125d55c03eab514991b88b42", "docs/getting-started/install.md": "16a77eeeefeb608e329df63318edcc9e111c37273c27c0994054ab5c659fa1bc", "docs/getting-started/install-options.md": "51fb99f87310f137ee3842137f69d181d15547f1121b0ba43765f990ef1cbd25", "docs/configuration.md": "ff24b556096841bb89df3747da04e57331346020f349d7208f708e6bbfab5589", @@ -16,7 +16,7 @@ "docs/getting-started/nodes.md": "395d04a29b95a9299a2474d76955d65e66edebdfd5bf3d8ce798e1bbda223148", "docs/getting-started/self-hosted.md": "653a9132ea6bbc39186f7917fa1596027d091071172e6a6afd9f618fc1dab725", "docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", - "docs/web/README.md": "08667cb82aec5495d1d3a09d588b37fa00601e0b18d26f246113042dcdd114c5", + "docs/web/README.md": "04c17f7f440a962a123727ae0e40922934346156f6b8a7fb867057481cecc66d", "docs/api/web-management.md": "efba58e8d38734e167e767d2c75d5991202d97f519ec1829d2e531b0e3ea4af2", "contracts/agents-api/runtime-observability-api.md": "cd46e777fe716a7e76374f4655c96dfbad9d8be1bfca6203ed6118ab87874c2b", "docs/getting-started/operations.md": "5cf3f4c6fa26b6445645c501ed53f78f450e9d9a31667166afebfaf02131f9a7", @@ -32,11 +32,11 @@ "outputs": { "content/docs/index.mdx": "55c6f5320b163a46dc74c705581cc34416766132b9fb10b0f5a1cf5832b46f2f", "content/docs/concepts.mdx": "b9aceda2f72f3f1239e5518c3cecff8c9c0aa11a3eb622ddf9db6af2984a1abc", - "content/docs/execution-model.mdx": "926f1952fa41bc52e1a7723b9cd1e83ce1555fbec86906fe0524df70bf7299b1", + "content/docs/execution-model.mdx": "3f36a85d3273b82c4551d83899f69e65c86436aecb6bd45d3c10cc241ff8673c", "content/docs/install.mdx": "90cd9f76a0ef5116363c2d20ad21465171bd0dca55f9d5d9b0df443cc3c844b3", "content/docs/install-options.mdx": "090f8840f5c164f41d6438b7b403c406ea1b7c0d695adbca3c3f06ba2193a5aa", "content/docs/configure.mdx": "1613d7a2f5c57b832cf6336a4ab51852b76ac59b8be2ca7b2b3054c32e627c7c", - "content/docs/bootstrap-projects-keys.mdx": "8fb8a53b383c48a8fcbb838627648b9c8a6663af0d2ce9c4e1e53755b548e54b", + "content/docs/bootstrap-projects-keys.mdx": "a0db9694ee663b4386a0d8d4202d9469308635852729d917f953044eba960fba", "content/docs/quickstart.mdx": "e389d12e7417456494b67047fa5de922678634539154bd6486ff424b08344126", "content/docs/public-api.mdx": "5caac0e2c857c6f887b903c7a9b6112320dce8081ca920f6ed2ccddaac91c403", "content/docs/agents-and-tools.mdx": "3ca16d2e2ff1c759251fbdf6e071ff09a93d5efc7acaca36a48854597d4f9576", @@ -46,7 +46,7 @@ "content/docs/hosted-providers.mdx": "da86d65fe1fa44f27600c3a0ea61e4954f894339724310be972d5e2bc03163e5", "content/docs/self-hosted-execution.mdx": "a8e6500e41c666eacb2c75882b2b8ee0cf122fe19ea36f39ef89f5dcf17b68e1", "public/images/source/docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", - "content/docs/console.mdx": "58abcddf241dfbdb43ef93410e979552aa9f65381bbd286e4db7dff20edd3b9e", + "content/docs/console.mdx": "8e3855f02b002788e212489c0a1fbf1480ee3d2418af56ba05ec8aa77ab2a9be", "content/docs/admin-api.mdx": "52bfea0ffc4d1e3bd1b9e476c32250e787e82b1d167c2343e662d7550b6a64e9", "content/docs/observability.mdx": "c81214e1865517a9163c48c7ce7c396f5490c9d162080dfa05fa5ebc147f6c8b", "content/docs/troubleshooting.mdx": "cf5b9e9d28fe9b56146bccc8e43d1804b3c6c5cc8ff2bb345aa4a3961329e838", diff --git a/apps/web/DESIGN.md b/apps/web/DESIGN.md index 312e842bb..671317d2a 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -216,710 +216,243 @@ 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 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. +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:** -- 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. +- 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, 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 brand colour shared - with the public OpenAgentCore landing. -- **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. +- **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 brand colour shared with the public OpenAgentCore landing. +- **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 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. +- **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** (card-border): 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. +- **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): 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. +- **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) is the pending KPI tone; **Idle Gray** (status-idle) marks idle and neutral status dots. A running or pending status dot uses Series 1. ### Data (categorical) -- **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. +- **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. ### 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, links 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:** Inter Variable, with the system UI sans (-apple-system, Segoe UI, -with PingFang SC / Microsoft YaHei / Noto Sans SC for Chinese) as fallback -**Body Font:** the same stack, with Inter's `cv11` and `ss01` alternates -**Label/Mono Font:** Geist Mono Variable, then ui-monospace / SF Mono / Menlo, for -identifiers and code +**Display Font:** Inter Variable, with the system UI sans (-apple-system, Segoe UI, with PingFang SC / Microsoft YaHei / Noto Sans SC for Chinese) as fallback **Body Font:** the same stack, with Inter's `cv11` and `ss01` alternates **Label/Mono Font:** Geist Mono Variable, then ui-monospace / SF Mono / Menlo, for identifiers and code -**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. +**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** (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 Graphite; axis ticks 11px. - Status labels are 13px. +- **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 Graphite; axis ticks 11px. Status labels are 13px. - **Mono** (400, 11.5px): IDs and code in tables and name cells, 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 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 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 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-panel step, not from stacked shadows. ### Shadow Vocabulary -- **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 `card-border` 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. +- **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 `card-border` 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 -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. +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 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. +- **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. +- **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 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. +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 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. +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 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". +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 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. +- **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** (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 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 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. - 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**: 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. +- 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 (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 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. +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 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 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". +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 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. +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 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 -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 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. ### Meter -A 5px pill rail in Meter Track (Well Gray with an inset hairline) 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 Meter Track (Well Gray with an inset hairline) 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. ### 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 dark 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 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. ### Tables -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). +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 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. +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 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. ### 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. +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. ### 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. +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. ### 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, 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 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 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. ### 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 follows the existing rotation confirmation and 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, 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. ## 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, links 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** 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. @@ -927,7 +460,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 5f10d11c2..1d17230e8 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -8,276 +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 project assets. +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, sandbox administration - included. +- 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), 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. +- **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 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. +- **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. +- 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 438522381..828a360d4 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -1,29 +1,17 @@ # 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. +`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 tokens in - `src/styles`, as [DESIGN.md](DESIGN.md) describes. +- 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 tokens 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: +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 node apps/web/e2e/fixture-console.mjs @@ -33,15 +21,9 @@ node apps/web/e2e/fixture-console.mjs OAC_WEB_DEV_PROXY_TARGET=http://127.0.0.1:18092 pnpm dev:web ``` -Open `http://127.0.0.1:4173` and sign in with the fixture-only key -`fixture-core-key-3f9a2c71`. +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. `apps/web/e2e/fixture-console.mjs` is a synthetic console service -with deterministic data; `AGENTS_FIXTURE_PORT` changes its port (default 18092). +`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. `apps/web/e2e/fixture-console.mjs` is a synthetic console service with deterministic data; `AGENTS_FIXTURE_PORT` changes its port (default 18092). ## Checks @@ -54,17 +36,11 @@ pnpm --filter @agents-core-web/web build pnpm test:web:acceptance ``` -`pnpm test:web:acceptance` runs the Playwright tests in `apps/web/e2e` in Chrome -against the fixture console. Each test also 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. +`pnpm test:web:acceptance` runs the Playwright tests in `apps/web/e2e` in Chrome against the fixture console. Each test also 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 -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: +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 @@ -74,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 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. +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 7e2d7ba58..9818c33ee 100644 --- a/apps/web/src/components/magicui/README.md +++ b/apps/web/src/components/magicui/README.md @@ -1,15 +1,9 @@ # Magic UI components -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`. +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 0dd45bce4..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,23 +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: `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). +- 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, 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. +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 a5efffd5e..4fba2e457 100644 --- a/apps/web/src/i18n/README.md +++ b/apps/web/src/i18n/README.md @@ -1,36 +1,20 @@ # Web internationalization -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`. +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 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. +- `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. +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 keys missing from either - language. +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 chosen a language in the console menu. The choice is stored in local -storage; the console 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/docs/web/README.md b/docs/web/README.md index c9971a6a2..4868f8e99 100644 --- a/docs/web/README.md +++ b/docs/web/README.md @@ -1,25 +1,14 @@ # OpenAgentCore Web -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. +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. ![OpenAgentCore Web overview](../assets/console-overview-en.webp) ## 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. A -console restart or a Core key rotation signs everyone out. +[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. A console restart or a Core key rotation signs everyone out. -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. +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 @@ -35,8 +24,7 @@ optional tour of the console opens from it. | 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. +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 @@ -50,28 +38,17 @@ lists what each page reads and how its figures are bounded. | 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 page, under the public deletion rules | -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. +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. +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. -When Core's public address is a loopback address (`local_only`), Overview, Nodes -and System warn that other machines, including nodes and remote applications, -cannot reach Core, and show the configuration file and apply command that change -it. The console itself stays reachable at its own address. +When Core's public address is a loopback address (`local_only`), Overview, Nodes and System warn that other machines, including nodes and remote applications, cannot reach Core, and show the configuration file and apply command that change it. The console itself stays reachable at its own address. ## More -- [Console server](console-server.md): request boundary, sign-in, settings and - verification. +- [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. +- [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/console-api-usage.md b/docs/web/console-api-usage.md index 27e88d574..6446a2829 100644 --- a/docs/web/console-api-usage.md +++ b/docs/web/console-api-usage.md @@ -1,9 +1,6 @@ # 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. +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 @@ -14,13 +11,7 @@ defines the routes, response shapes, pagination and audit records; | 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. +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 @@ -34,16 +25,11 @@ unrecognised where noted below, and never replaced by a guessed or zero value. | 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. +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. +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 | | --- | --- | --- | --- | --- | @@ -57,32 +43,16 @@ public client's strict projections. Archived projects remain readable. 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. +- **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. +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 | | --- | --- | --- | @@ -91,8 +61,7 @@ defines the routes, their 404 and 409 responses and the credential file. | 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. +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 @@ -104,24 +73,11 @@ a note and keeps the list and **Revoke**, which Core still allows. | 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. +`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 @@ -131,8 +87,7 @@ console shows missing values as missing, never as zero. | 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`. +The list carries each harness's configuration, so the console does not read `GET /core/v1/harnesses/{harness}/model-configuration`. ## Sandbox administration @@ -149,42 +104,18 @@ The list carries each harness's configuration, so the console does not read | 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. +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`. +- 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. +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 | | --- | --- | --- | @@ -197,24 +128,15 @@ stream. Agent metrics states these limits in its help tips: -- A request is one root Agent Turn. 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. -- Subagent Turns and deleted Sessions are not counted. Busy projects exceed the - Session caps, so long ranges can be partial; the page names the projects that were - cut short. +- A request is one root Agent Turn. 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. +- Subagent Turns and deleted Sessions are not counted. Busy projects exceed the Session caps, so long ranges can be partial; the page names the projects that were cut short. - Usage by API key counts Sessions created in the range by their creating key. -- The console accepts Turn times up to 15 minutes after the end of the range, to - allow for clock differences between the browser and Core. +- The console accepts Turn times up to 15 minutes after the end of the range, to allow for clock differences between the browser and Core. ## 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. +- 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 index 4e76e95ed..28e4cc1cb 100644 --- a/docs/web/console-server.md +++ b/docs/web/console-server.md @@ -1,10 +1,6 @@ # 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. +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 @@ -26,9 +22,7 @@ flowchart LR core <--> database ``` -The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other -path to the console; the [installation guide](../getting-started/install.md#https-and-the-reverse-proxy) -gives the routes. The console handles each path as follows: +The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation guide](../getting-started/install.md#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: | Path | Sign-in | Handling | | --- | --- | --- | @@ -44,49 +38,27 @@ gives the routes. The console handles each path as follows: 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. +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'`. +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. +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; +- 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; +- 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`. +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. +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 @@ -96,25 +68,15 @@ clients in [`packages/agents-client`](../../packages/agents-client/README.md); | `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. +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 @@ -128,58 +90,28 @@ console cannot create a session. ## 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. +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. +`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, and hand-edited generated files 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. +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, and hand-edited generated files 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`. +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 | | --- | --- | --- | @@ -192,28 +124,16 @@ installer gives the console only `secrets/core.key`. | `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` | -The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` -([configuration](../configuration.md#appendix-core-environment-without-the-installer)). -An invalid value stops the console at startup with a message naming the variable. -Use HTTPS for any browser that is not on the same machine. +The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](../configuration.md#appendix-core-environment-without-the-installer)). An invalid value stops the console at startup with a message naming the variable. 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. +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/packages/agents-client/README.md b/packages/agents-client/README.md index c28bb0f8c..cb71a0b52 100644 --- a/packages/agents-client/README.md +++ b/packages/agents-client/README.md @@ -2,14 +2,10 @@ 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 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. +[API namespaces and credentials](../../docs/api/README.md) explains which credential each namespace takes. ## TypeScript client @@ -20,41 +16,19 @@ each namespace takes. | `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. +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`. +- **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: +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"; @@ -88,15 +62,9 @@ await client.updateAgent(agent.id, { model: "another-model" }); 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. +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: +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"; @@ -119,9 +87,7 @@ 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. +[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 @@ -134,10 +100,7 @@ pnpm --filter @agents-core-web/agents-client test ## 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. +`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 ( @@ -164,26 +127,12 @@ session, err := sessions.New(ctx, openai.BetaAgentSessionNewParams{ }, option.WithHeader("Idempotency-Key", operationID)) ``` -- `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. +- `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/claude-sdk-adapter/README.md b/packages/claude-sdk-adapter/README.md index bb3019376..d2c92ef89 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 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. +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. -[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. +[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,485 +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 ### 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. +`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. +`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. +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. +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. +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. +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). +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. +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. +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. +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. +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. +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. 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. +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. 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 `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. +`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/mcode-harness/README.md b/packages/mcode-harness/README.md index cae02e900..534e1f8fd 100644 --- a/packages/mcode-harness/README.md +++ b/packages/mcode-harness/README.md @@ -1,94 +1,40 @@ # MiniMax Code workspace bridge -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`, server name `oac-workspace`) -that exposes six native MiniMax Code tools (Read, Write, Edit, Bash, Grep and Glob, -each prefixed `workspace_`) rooted at the Session's workspace. 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. - -The pinned native CLI carries one source patch: its 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. +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`, server name `oac-workspace`) that exposes six native MiniMax Code tools (Read, Write, Edit, Bash, Grep and Glob, each prefixed `workspace_`) rooted at the Session's workspace. 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. + +The pinned native CLI carries one source patch: its 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. ## Workspace tools -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 the bridge's tools rather than imported by the harness. Native -diff/undo capture is not provided by this path. Common Files and Artifacts use the -bound public workspace independently of the native control directory. - -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. +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 the bridge's tools rather than imported by the harness. Native diff/undo capture is not provided by this path. Common Files and Artifacts use the bound public workspace independently of the native control directory. + +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: +`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 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`. +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. +`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. +`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: +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 \ @@ -96,6 +42,4 @@ OAC_TEST_MCODE_NATIVE_ARTIFACT=/opt/mcode-harness \ node --test packages/mcode-harness/native.test.mjs ``` -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. +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. From 234d6dbb8ac1e04351bd2f2720431435e4ee7507 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 30 Sep 2026 07:34:40 +0000 Subject: [PATCH 5/6] docs: address review of the Web and package docs Correct console-server, console API usage, the operator guide and the apps/web README against the code; fix DESIGN.md tokens, component facts and the topology liveness rule; regenerate the impeccable sidecar from DESIGN.md and the stylesheets; correct the MiniMax Code bridge and Claude subagent notes; and regenerate the docs site. --- .../content/docs/bootstrap-projects-keys.mdx | 5 +- apps/docs/content/docs/console.mdx | 6 +- apps/docs/content/docs/execution-model.mdx | 5 +- apps/docs/content/guide-sources.json | 10 +- apps/web/.impeccable/design.json | 857 ++++++++++-------- apps/web/DESIGN.md | 59 +- apps/web/README.md | 6 +- docs/web/README.md | 6 +- docs/web/console-api-usage.md | 14 +- docs/web/console-server.md | 5 +- packages/claude-sdk-adapter/README.md | 2 +- packages/mcode-harness/README.md | 6 +- scripts/name-allowlist.json | 5 - 13 files changed, 542 insertions(+), 444 deletions(-) diff --git a/apps/docs/content/docs/bootstrap-projects-keys.mdx b/apps/docs/content/docs/bootstrap-projects-keys.mdx index 966a439b1..6ac9b2e45 100644 --- a/apps/docs/content/docs/bootstrap-projects-keys.mdx +++ b/apps/docs/content/docs/bootstrap-projects-keys.mdx @@ -37,6 +37,7 @@ The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every othe | `/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: @@ -104,7 +105,7 @@ With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution | `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, and hand-edited generated files also return 409. +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`. @@ -127,7 +128,7 @@ The installer sets these variables from `config.json`; set them yourself only wh | `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` | -The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](/configure#appendix-core-environment-without-the-installer)). An invalid value stops the console at startup with a message naming the variable. Use HTTPS for any browser that is not on the same machine. +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 diff --git a/apps/docs/content/docs/console.mdx b/apps/docs/content/docs/console.mdx index cb7060e2f..ce3028bb5 100644 --- a/apps/docs/content/docs/console.mdx +++ b/apps/docs/content/docs/console.mdx @@ -9,7 +9,7 @@ Web is the administrator console of one OpenAgentCore deployment. Administrators ## 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. A console restart or a Core key rotation signs everyone out. +[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. @@ -39,13 +39,13 @@ Missing data is shown as missing (—), never as zero. [Console API usage](https | 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 page, under the public deletion rules | +| 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. -When Core's public address is a loopback address (`local_only`), Overview, Nodes and System warn that other machines, including nodes and remote applications, cannot reach Core, and show the configuration file and apply command that change it. The console itself stays reachable at its own address. +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 diff --git a/apps/docs/content/docs/execution-model.mdx b/apps/docs/content/docs/execution-model.mdx index 7845e54e9..b578e3c29 100644 --- a/apps/docs/content/docs/execution-model.mdx +++ b/apps/docs/content/docs/execution-model.mdx @@ -39,6 +39,7 @@ The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every othe | `/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: @@ -106,7 +107,7 @@ With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution | `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, and hand-edited generated files also return 409. +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`. @@ -129,7 +130,7 @@ The installer sets these variables from `config.json`; set them yourself only wh | `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` | -The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](/configure#appendix-core-environment-without-the-installer)). An invalid value stops the console at startup with a message naming the variable. Use HTTPS for any browser that is not on the same machine. +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 diff --git a/apps/docs/content/guide-sources.json b/apps/docs/content/guide-sources.json index 556bac000..6cd593f98 100644 --- a/apps/docs/content/guide-sources.json +++ b/apps/docs/content/guide-sources.json @@ -3,7 +3,7 @@ "sources": { "docs/getting-started/README.md": "d93c270e208fdf84edaf71827ed635a017be8cf67e0a4afef7279e3945d53173", "docs/design-principles.md": "334e0d477c255552f874f3ca8a2db603d0d07892ad7d58930bf8e3d06b86c636", - "docs/web/console-server.md": "f7796ed8a5c370c862a35b221e31a76a01643c8d125d55c03eab514991b88b42", + "docs/web/console-server.md": "983dcb9f12a68ebf7d40c9eb1a8f352fb1da6f123c3e34a3d754fc2574bb4c87", "docs/getting-started/install.md": "16a77eeeefeb608e329df63318edcc9e111c37273c27c0994054ab5c659fa1bc", "docs/getting-started/install-options.md": "51fb99f87310f137ee3842137f69d181d15547f1121b0ba43765f990ef1cbd25", "docs/configuration.md": "ff24b556096841bb89df3747da04e57331346020f349d7208f708e6bbfab5589", @@ -16,7 +16,7 @@ "docs/getting-started/nodes.md": "395d04a29b95a9299a2474d76955d65e66edebdfd5bf3d8ce798e1bbda223148", "docs/getting-started/self-hosted.md": "653a9132ea6bbc39186f7917fa1596027d091071172e6a6afd9f618fc1dab725", "docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", - "docs/web/README.md": "04c17f7f440a962a123727ae0e40922934346156f6b8a7fb867057481cecc66d", + "docs/web/README.md": "01d0df8d50ac56bc13c5574911814441525aa8cd376cff0498867002c60ff7ec", "docs/api/web-management.md": "efba58e8d38734e167e767d2c75d5991202d97f519ec1829d2e531b0e3ea4af2", "contracts/agents-api/runtime-observability-api.md": "cd46e777fe716a7e76374f4655c96dfbad9d8be1bfca6203ed6118ab87874c2b", "docs/getting-started/operations.md": "5cf3f4c6fa26b6445645c501ed53f78f450e9d9a31667166afebfaf02131f9a7", @@ -32,11 +32,11 @@ "outputs": { "content/docs/index.mdx": "55c6f5320b163a46dc74c705581cc34416766132b9fb10b0f5a1cf5832b46f2f", "content/docs/concepts.mdx": "b9aceda2f72f3f1239e5518c3cecff8c9c0aa11a3eb622ddf9db6af2984a1abc", - "content/docs/execution-model.mdx": "3f36a85d3273b82c4551d83899f69e65c86436aecb6bd45d3c10cc241ff8673c", + "content/docs/execution-model.mdx": "06538f5b839b590a62af9f57b2cfe40dfb7133ab711957c1cb12ac382fbc54a8", "content/docs/install.mdx": "90cd9f76a0ef5116363c2d20ad21465171bd0dca55f9d5d9b0df443cc3c844b3", "content/docs/install-options.mdx": "090f8840f5c164f41d6438b7b403c406ea1b7c0d695adbca3c3f06ba2193a5aa", "content/docs/configure.mdx": "1613d7a2f5c57b832cf6336a4ab51852b76ac59b8be2ca7b2b3054c32e627c7c", - "content/docs/bootstrap-projects-keys.mdx": "a0db9694ee663b4386a0d8d4202d9469308635852729d917f953044eba960fba", + "content/docs/bootstrap-projects-keys.mdx": "efc6afdc90027b39503fdf984af90aa739caf4ee7632fa8ad096a058bd6ed08b", "content/docs/quickstart.mdx": "e389d12e7417456494b67047fa5de922678634539154bd6486ff424b08344126", "content/docs/public-api.mdx": "5caac0e2c857c6f887b903c7a9b6112320dce8081ca920f6ed2ccddaac91c403", "content/docs/agents-and-tools.mdx": "3ca16d2e2ff1c759251fbdf6e071ff09a93d5efc7acaca36a48854597d4f9576", @@ -46,7 +46,7 @@ "content/docs/hosted-providers.mdx": "da86d65fe1fa44f27600c3a0ea61e4954f894339724310be972d5e2bc03163e5", "content/docs/self-hosted-execution.mdx": "a8e6500e41c666eacb2c75882b2b8ee0cf122fe19ea36f39ef89f5dcf17b68e1", "public/images/source/docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", - "content/docs/console.mdx": "8e3855f02b002788e212489c0a1fbf1480ee3d2418af56ba05ec8aa77ab2a9be", + "content/docs/console.mdx": "9d97d2d832778460128528b69ee8c44fd3eac584444ca2cac38249dc540bb9a4", "content/docs/admin-api.mdx": "52bfea0ffc4d1e3bd1b9e476c32250e787e82b1d167c2343e662d7550b6a64e9", "content/docs/observability.mdx": "c81214e1865517a9163c48c7ce7c396f5490c9d162080dfa05fa5ebc147f6c8b", "content/docs/troubleshooting.mdx": "cf5b9e9d28fe9b56146bccc8e43d1804b3c6c5cc8ff2bb345aa4a3961329e838", 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/DESIGN.md b/apps/web/DESIGN.md index 671317d2a..8efa23278 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -10,7 +10,7 @@ colors: 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)" - card-border: "color-mix(in oklch, oklch(0.247 0.006 258.361) 11%, transparent)" + 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)" @@ -25,7 +25,6 @@ colors: warning: "oklch(0.68 0.135 62)" danger: "oklch(0.585 0.17 25)" status-queued: "oklch(0.695 0.009 264.505)" - status-idle: "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)" @@ -34,7 +33,6 @@ colors: 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)" - meter-track: "oklch(0.961 0.001 286.375)" typography: metric: fontFamily: "\"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Hiragino Sans GB\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", \"Helvetica Neue\", Helvetica, Arial, sans-serif" @@ -81,7 +79,8 @@ rounded: control: "8px" control-inner: "6px" segment: "8px" - popover: "8px" + tooltip: "8px" + popover: "14px" frame: "12px" window: "14px" pill: "999px" @@ -205,7 +204,7 @@ components: textColor: "{colors.ink}" rounded: "{rounded.window}" meter: - backgroundColor: "{colors.meter-track}" + backgroundColor: "{colors.surface-muted}" rounded: "{rounded.pill}" height: "5px" --- @@ -237,7 +236,7 @@ The data contract is part of the look. Core reports only what it observes, so th 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, 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 brand colour shared with the public OpenAgentCore landing. +- **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 @@ -247,7 +246,7 @@ A restrained neutral ledger with one indigo voice, three signal colours and a se - **Sidebar Ink** (sidebar-ink): navigation text. - **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** (card-border): the 1px ring of cards, ink at 11%. +- **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. @@ -257,12 +256,12 @@ A restrained neutral ledger with one indigo voice, three signal colours and a se ### 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. +- **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** (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. @@ -273,7 +272,9 @@ A restrained neutral ledger with one indigo voice, three signal colours and a se ## Typography -**Display Font:** Inter Variable, with the system UI sans (-apple-system, Segoe UI, with PingFang SC / Microsoft YaHei / Noto Sans SC for Chinese) as fallback **Body Font:** the same stack, with Inter's `cv11` and `ss01` alternates **Label/Mono Font:** Geist Mono Variable, then 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 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. @@ -283,8 +284,8 @@ A restrained neutral ledger with one indigo voice, three signal colours and a se - **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 Graphite; axis ticks 11px. Status labels are 13px. -- **Mono** (400, 11.5px): IDs and code in tables and name cells, in Pencil. +- **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. @@ -301,6 +302,8 @@ The recurring shapes in the body are the KPI strip (auto-fit columns, min 158px; 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 @@ -309,7 +312,7 @@ Depth comes from the canvas-to-panel step, not from stacked shadows. ### Shadow Vocabulary - **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 `card-border` 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. +- **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. @@ -354,7 +357,7 @@ Every resource list, the Session log and the project list share one grammar: ### Detail pages - 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 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. +- 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. @@ -364,7 +367,7 @@ Every resource list, the Session log and the project list share one grammar: 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 with `--credential-file`; tokens never enter command arguments. +- **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. @@ -381,13 +384,13 @@ A KPI strip is one card of equal cells separated by inset rules. Each cell: a 12 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 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 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 5px pill rail in Meter Track (Well Gray with an inset hairline) 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 dark 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 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). @@ -403,7 +406,7 @@ A local-only installation has the same amber notice on Overview, Nodes and Syste 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. @@ -411,17 +414,17 @@ Setting up hosted sandboxes is a set of pages inside System’s Sandbox configur ### 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. +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. +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 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. +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 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. +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, 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 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. @@ -429,12 +432,12 @@ A configuration or operation has one home. Other pages link to it instead of rep ### 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. -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. +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, popovers and dialogs scale from 98%. Reduced motion makes all of it instant. +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 @@ -447,7 +450,7 @@ Motion reports state and never makes anyone wait: the navigation chip and segmen - **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** 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. diff --git a/apps/web/README.md b/apps/web/README.md index 828a360d4..e91129cef 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -6,7 +6,7 @@ - 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 tokens in `src/styles`, as [DESIGN.md](DESIGN.md) describes. +- 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 @@ -23,7 +23,7 @@ OAC_WEB_DEV_PROXY_TARGET=http://127.0.0.1:18092 pnpm dev:web 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. `apps/web/e2e/fixture-console.mjs` is a synthetic console service with deterministic data; `AGENTS_FIXTURE_PORT` changes its port (default 18092). +`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). ## Checks @@ -36,7 +36,7 @@ pnpm --filter @agents-core-web/web build pnpm test:web:acceptance ``` -`pnpm test:web:acceptance` runs the Playwright tests in `apps/web/e2e` in Chrome against the fixture console. Each test also 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. +`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 diff --git a/docs/web/README.md b/docs/web/README.md index 4868f8e99..2679da299 100644 --- a/docs/web/README.md +++ b/docs/web/README.md @@ -6,7 +6,7 @@ Web is the administrator console of one OpenAgentCore deployment. Administrators ## 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. A console restart or a Core key rotation signs everyone out. +[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. @@ -36,13 +36,13 @@ Missing data is shown as missing (—), never as zero. [Console API usage](conso | 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 page, under the public deletion rules | +| 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. -When Core's public address is a loopback address (`local_only`), Overview, Nodes and System warn that other machines, including nodes and remote applications, cannot reach Core, and show the configuration file and apply command that change it. The console itself stays reachable at its own address. +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 diff --git a/docs/web/console-api-usage.md b/docs/web/console-api-usage.md index 6446a2829..926c7d1f1 100644 --- a/docs/web/console-api-usage.md +++ b/docs/web/console-api-usage.md @@ -6,7 +6,7 @@ This page lists the Core routes each console page reads and writes, and how the | 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 | 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) | +| 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)) | @@ -126,13 +126,15 @@ The console assembles several figures in the browser from bounded reads of each | 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 states these limits in its help tips: +Agent metrics has these limits: -- A request is one root Agent Turn. HTTP request counts, status codes and API latency are not available. +- 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. -- Subagent Turns and deleted Sessions are not counted. Busy projects exceed the Session caps, so long ranges can be partial; the page names the projects that were cut short. -- Usage by API key counts Sessions created in the range by their creating key. -- The console accepts Turn times up to 15 minutes after the end of the range, to allow for clock differences between the browser and Core. +- 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 diff --git a/docs/web/console-server.md b/docs/web/console-server.md index 28e4cc1cb..c6e9d9390 100644 --- a/docs/web/console-server.md +++ b/docs/web/console-server.md @@ -34,6 +34,7 @@ The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every othe | `/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: @@ -101,7 +102,7 @@ With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution | `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, and hand-edited generated files also return 409. +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`. @@ -124,7 +125,7 @@ The installer sets these variables from `config.json`; set them yourself only wh | `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` | -The console also reads `OAC_LOG_LEVEL`, `OAC_LOG_FORMAT` and `OAC_LOG_ADD_SOURCE` ([configuration](../configuration.md#appendix-core-environment-without-the-installer)). An invalid value stops the console at startup with a message naming the variable. Use HTTPS for any browser that is not on the same machine. +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 diff --git a/packages/claude-sdk-adapter/README.md b/packages/claude-sdk-adapter/README.md index d2c92ef89..b67233dd4 100644 --- a/packages/claude-sdk-adapter/README.md +++ b/packages/claude-sdk-adapter/README.md @@ -102,7 +102,7 @@ The first own native user record has a null `parentUuid`. Its timestamp supplies 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. 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. +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. diff --git a/packages/mcode-harness/README.md b/packages/mcode-harness/README.md index 534e1f8fd..00c23b067 100644 --- a/packages/mcode-harness/README.md +++ b/packages/mcode-harness/README.md @@ -1,12 +1,12 @@ # MiniMax Code workspace bridge -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`, server name `oac-workspace`) that exposes six native MiniMax Code tools (Read, Write, Edit, Bash, Grep and Glob, each prefixed `workspace_`) rooted at the Session's workspace. 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. +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. -The pinned native CLI carries one source patch: its 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. +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 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 the bridge's tools rather than imported by the harness. Native diff/undo capture is not provided by this path. Common Files and Artifacts use the bound public workspace independently of the native control directory. +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. diff --git a/scripts/name-allowlist.json b/scripts/name-allowlist.json index b902e4b81..4062e6067 100644 --- a/scripts/name-allowlist.json +++ b/scripts/name-allowlist.json @@ -614,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", From 1f397942fa3932a3005dcb1ada1bae9c8c794524 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 30 Sep 2026 07:56:10 +0000 Subject: [PATCH 6/6] docs: repoint Web links to the rebuilt operator guides Point the console guide and console server doc at the domain setup, reverse proxy and sandbox configuration sections that the operator guide rebuild moved, and regenerate the docs site. --- apps/docs/content/docs/bootstrap-projects-keys.mdx | 2 +- apps/docs/content/docs/console.mdx | 4 ++-- apps/docs/content/docs/execution-model.mdx | 2 +- apps/docs/content/guide-sources.json | 10 +++++----- docs/web/README.md | 4 ++-- docs/web/console-server.md | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/docs/content/docs/bootstrap-projects-keys.mdx b/apps/docs/content/docs/bootstrap-projects-keys.mdx index 6ac9b2e45..a75a05ace 100644 --- a/apps/docs/content/docs/bootstrap-projects-keys.mdx +++ b/apps/docs/content/docs/bootstrap-projects-keys.mdx @@ -25,7 +25,7 @@ flowchart LR core <--> database ``` -The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation guide](/install#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: +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 | | --- | --- | --- | diff --git a/apps/docs/content/docs/console.mdx b/apps/docs/content/docs/console.mdx index ce3028bb5..3e4f607cf 100644 --- a/apps/docs/content/docs/console.mdx +++ b/apps/docs/content/docs/console.mdx @@ -33,8 +33,8 @@ Missing data is shown as missing (—), never as zero. [Console API usage](https | Task | Where | | --- | --- | -| Give the installation an HTTPS address | **System → Domain and HTTPS**, on an installation with managed ingress; see [Make Core reachable](/install#make-core-reachable) | -| Choose the sandbox backend (Docker, microsandbox or E2B), the sandbox size and Runtime, or reset the backend | **System → Sandbox configuration**; see [configuration](/configure#sandbox-deployment) | +| 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) | diff --git a/apps/docs/content/docs/execution-model.mdx b/apps/docs/content/docs/execution-model.mdx index b578e3c29..2474c102d 100644 --- a/apps/docs/content/docs/execution-model.mdx +++ b/apps/docs/content/docs/execution-model.mdx @@ -27,7 +27,7 @@ flowchart LR core <--> database ``` -The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation guide](/install#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: +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 | | --- | --- | --- | diff --git a/apps/docs/content/guide-sources.json b/apps/docs/content/guide-sources.json index 6cd593f98..1d586a695 100644 --- a/apps/docs/content/guide-sources.json +++ b/apps/docs/content/guide-sources.json @@ -3,7 +3,7 @@ "sources": { "docs/getting-started/README.md": "d93c270e208fdf84edaf71827ed635a017be8cf67e0a4afef7279e3945d53173", "docs/design-principles.md": "334e0d477c255552f874f3ca8a2db603d0d07892ad7d58930bf8e3d06b86c636", - "docs/web/console-server.md": "983dcb9f12a68ebf7d40c9eb1a8f352fb1da6f123c3e34a3d754fc2574bb4c87", + "docs/web/console-server.md": "e995515b80450f2964bbd8e0c850d05b9935ba65512f8817368ca87628eafb37", "docs/getting-started/install.md": "16a77eeeefeb608e329df63318edcc9e111c37273c27c0994054ab5c659fa1bc", "docs/getting-started/install-options.md": "51fb99f87310f137ee3842137f69d181d15547f1121b0ba43765f990ef1cbd25", "docs/configuration.md": "ff24b556096841bb89df3747da04e57331346020f349d7208f708e6bbfab5589", @@ -16,7 +16,7 @@ "docs/getting-started/nodes.md": "395d04a29b95a9299a2474d76955d65e66edebdfd5bf3d8ce798e1bbda223148", "docs/getting-started/self-hosted.md": "653a9132ea6bbc39186f7917fa1596027d091071172e6a6afd9f618fc1dab725", "docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", - "docs/web/README.md": "01d0df8d50ac56bc13c5574911814441525aa8cd376cff0498867002c60ff7ec", + "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", @@ -32,11 +32,11 @@ "outputs": { "content/docs/index.mdx": "55c6f5320b163a46dc74c705581cc34416766132b9fb10b0f5a1cf5832b46f2f", "content/docs/concepts.mdx": "b9aceda2f72f3f1239e5518c3cecff8c9c0aa11a3eb622ddf9db6af2984a1abc", - "content/docs/execution-model.mdx": "06538f5b839b590a62af9f57b2cfe40dfb7133ab711957c1cb12ac382fbc54a8", + "content/docs/execution-model.mdx": "faa01d7499f34fd1bacdf04860e63df0015084ab941ee9e157060c831e18266e", "content/docs/install.mdx": "90cd9f76a0ef5116363c2d20ad21465171bd0dca55f9d5d9b0df443cc3c844b3", "content/docs/install-options.mdx": "090f8840f5c164f41d6438b7b403c406ea1b7c0d695adbca3c3f06ba2193a5aa", "content/docs/configure.mdx": "1613d7a2f5c57b832cf6336a4ab51852b76ac59b8be2ca7b2b3054c32e627c7c", - "content/docs/bootstrap-projects-keys.mdx": "efc6afdc90027b39503fdf984af90aa739caf4ee7632fa8ad096a058bd6ed08b", + "content/docs/bootstrap-projects-keys.mdx": "5aa4b230ac8b8608d0e454bbc3badd128789ceb7acb9dd7996534a649caa6655", "content/docs/quickstart.mdx": "e389d12e7417456494b67047fa5de922678634539154bd6486ff424b08344126", "content/docs/public-api.mdx": "5caac0e2c857c6f887b903c7a9b6112320dce8081ca920f6ed2ccddaac91c403", "content/docs/agents-and-tools.mdx": "3ca16d2e2ff1c759251fbdf6e071ff09a93d5efc7acaca36a48854597d4f9576", @@ -46,7 +46,7 @@ "content/docs/hosted-providers.mdx": "da86d65fe1fa44f27600c3a0ea61e4954f894339724310be972d5e2bc03163e5", "content/docs/self-hosted-execution.mdx": "a8e6500e41c666eacb2c75882b2b8ee0cf122fe19ea36f39ef89f5dcf17b68e1", "public/images/source/docs/assets/console-overview-en.webp": "86c663ffb214b3e428eb09fc998f55c22a3d3706164216dc4386031728a7352c", - "content/docs/console.mdx": "9d97d2d832778460128528b69ee8c44fd3eac584444ca2cac38249dc540bb9a4", + "content/docs/console.mdx": "74a676fe64513cb1c76a2fc2c5400c520109efcec352fc354e9b3d64edb4e6a4", "content/docs/admin-api.mdx": "52bfea0ffc4d1e3bd1b9e476c32250e787e82b1d167c2343e662d7550b6a64e9", "content/docs/observability.mdx": "c81214e1865517a9163c48c7ce7c396f5490c9d162080dfa05fa5ebc147f6c8b", "content/docs/troubleshooting.mdx": "cf5b9e9d28fe9b56146bccc8e43d1804b3c6c5cc8ff2bb345aa4a3961329e838", diff --git a/docs/web/README.md b/docs/web/README.md index 2679da299..20d4439e2 100644 --- a/docs/web/README.md +++ b/docs/web/README.md @@ -30,8 +30,8 @@ Missing data is shown as missing (—), never as zero. [Console API usage](conso | 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#make-core-reachable) | -| Choose the sandbox backend (Docker, microsandbox or E2B), the sandbox size and Runtime, or reset the backend | **System → Sandbox configuration**; see [configuration](../configuration.md#sandbox-deployment) | +| 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) | diff --git a/docs/web/console-server.md b/docs/web/console-server.md index c6e9d9390..499ec9996 100644 --- a/docs/web/console-server.md +++ b/docs/web/console-server.md @@ -22,7 +22,7 @@ flowchart LR core <--> database ``` -The deployment's reverse proxy routes `/v1` and `/api/v1` to Core and every other path to the console; the [installation guide](../getting-started/install.md#https-and-the-reverse-proxy) gives the routes. The console handles each path as follows: +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 | | --- | --- | --- |