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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/web/DESIGN.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/web/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ The console runs beside the administrator's own Core, with execution, files and
- **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.
- **Local-only address.** Overview, Nodes and System warn when Core reports `local_only` and lead to System to review the public address. 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.
Expand Down
3 changes: 1 addition & 2 deletions apps/web/e2e/fixture-console.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const publicUrl = () => (state.installation === "local" ? LOCAL_URL : PUBLIC_URL
/** Core reports one installation ID, a canonical UUID, in the installation and the deployment. */
const INSTALLATION_ID = "7f3c2a90-5b1e-4c2d-9e3f-0a1b2c3d4e5f";

/** GET /core/v1/installation: the address, the startup settings from config.json and what is bound to the address. */
/** GET /core/v1/installation: the address, the startup settings Core loaded and what is bound to the address. */
function installation() {
const local = state.installation === "local";
const setting = (key, value, fallback, restarts, extra = {}) => ({ key, value, default: fallback, changeable: true, sensitive: false, restarts, ...extra });
Expand All @@ -51,7 +51,6 @@ function installation() {
object: "core.installation", installation_id: INSTALLATION_ID, public_url: publicUrl(), api_base_url: `${publicUrl()}/v1`,
local_only: local, source_commit: release.source_commit,
configuration: {
path: "/opt/oac/config.json", apply_command: "sudo oac apply", applied_at: "2026-09-24T09:30:00Z",
settings: [
setting("public_url", publicUrl(), LOCAL_URL, ["core", "web"]),
setting("listen_address", "127.0.0.1:8091", "127.0.0.1:8091", ["core"]),
Expand Down
3 changes: 1 addition & 2 deletions apps/web/e2e/public-url.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,11 @@ test("explains an E2B rejection in the wizard, with a link to domain setup", asy
await expect(page.getByRole("heading", { name: "Connect E2B" })).toBeVisible();
});

test("lists the startup settings on System with where to change them", async ({ page, request }) => {
test("lists the startup settings on System", async ({ page, request }) => {
await openConsole(page, request, "system");
const installation = page.getByRole("region", { name: "Installation" });
await expect(installation).toContainText("https://core.example.com/v1");
const startup = page.getByRole("region", { name: "Startup settings" });
await expect(startup).toContainText("Change these in /opt/oac/config.json, then run sudo oac apply");
const settings = startup.getByRole("table", { name: "Startup settings" });
await expect(settings.getByRole("row", { name: /^log_level/ })).toContainText("debug");
await expect(settings.getByRole("row", { name: /^listen_address/ })).toContainText("Default");
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/InstallationNotice.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { InstallationNotice } from "./InstallationNotice";

const installation: CoreInstallation = {
object: "core.installation", installation_id: null, public_url: "http://127.0.0.1:8091", api_base_url: "http://127.0.0.1:8091/v1",
source_commit: null, local_only: true, configuration: null,
source_commit: null, local_only: true, configuration: { settings: [] },
address_bindings: { nodes: 0, nodes_on_other_address: 0, hosted_sandboxes: 0, self_hosted_executors: 0 },
};

Expand Down
93 changes: 37 additions & 56 deletions apps/web/src/features/system/StartupSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { CoreInstallationConfiguration, CoreInstallationSetting } from "@oac/agents-client";
import { Trans, useTranslation } from "react-i18next";
import { useTranslation } from "react-i18next";

import { ValuePill } from "../../components/atoms/ValuePill";
import { Section } from "../../components/console-ui";
import { CopyableId } from "../../components/list-ui";
import { formatDateTime } from "../../lib/format";

function display(value: unknown): string | null {
if (value === null || value === undefined) return null;
Expand All @@ -16,62 +14,45 @@ function isDefault(setting: CoreInstallationSetting): boolean {
}

/**
* Platform › System: Core's startup settings, read-only. They live in
* config.json and take effect with the apply command; the console only says
* where to change them. A sensitive setting shows whether it is set, never
* its value.
* Platform › System: the process settings Core loaded, read-only. A
* sensitive setting shows whether it is set, never its value.
*/
export function StartupSettings({ configuration }: { configuration: CoreInstallationConfiguration | null }) {
const { t, i18n } = useTranslation("system");
const locale = i18n.resolvedLanguage;
export function StartupSettings({ configuration }: { configuration: CoreInstallationConfiguration }) {
const { t } = useTranslation("system");
return (
<Section headingId="system-startup-heading" title={t("startup.title")} help={t("startup.help")}>
{configuration === null ? <p className="system-note">{t("startup.none")}</p> : <>
<p className="system-where">
{configuration.path ? <span>
<Trans
t={t}
i18nKey="startup.where"
components={{
path: <CopyableId id={configuration.path} label={t("startup.copyPath")} />,
command: <CopyableId id={configuration.apply_command} label={t("startup.copyCommand")} />,
}}
/>
</span> : <span>{t("startup.effective")}</span>}
{configuration.applied_at ? <span className="system-applied">{t("startup.appliedAt", { time: formatDateTime(Date.parse(configuration.applied_at) / 1000, locale) })}</span> : null}
</p>
<div className="table-frame">
<table className="data-table system-settings" aria-label={t("startup.title")}>
<thead>
<tr>
<th scope="col">{t("startup.columns.key")}</th>
<th scope="col">{t("startup.columns.value")}</th>
<th scope="col">{t("startup.columns.restarts")}</th>
</tr>
</thead>
<tbody>
{configuration.settings.filter((setting) => setting.key !== "public_url").map((setting) => {
const value = setting.sensitive ? null : display(setting.value);
return (
<tr key={setting.key}>
<th scope="row"><code className="system-code">{setting.key}</code></th>
<td>
<span className="system-setting-value">
{setting.sensitive
? t(setting.configured ? "startup.configured" : "startup.notSet")
: value === null ? <span className="system-muted">{t("startup.notSet")}</span> : <code className="system-code">{value}</code>}
{isDefault(setting) ? <ValuePill>{t("startup.default")}</ValuePill> : null}
{setting.changeable ? null : <ValuePill>{t("startup.fixed")}</ValuePill>}
</span>
</td>
<td>{setting.restarts.length ? setting.restarts.join(", ") : <span className="system-muted">{t("startup.noRestart")}</span>}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>}
<p className="system-note">{t("startup.effective")}</p>
<div className="table-frame">
<table className="data-table system-settings" aria-label={t("startup.title")}>
<thead>
<tr>
<th scope="col">{t("startup.columns.key")}</th>
<th scope="col">{t("startup.columns.value")}</th>
<th scope="col">{t("startup.columns.restarts")}</th>
</tr>
</thead>
<tbody>
{configuration.settings.filter((setting) => setting.key !== "public_url").map((setting) => {
const value = setting.sensitive ? null : display(setting.value);
return (
<tr key={setting.key}>
<th scope="row"><code className="system-code">{setting.key}</code></th>
<td>
<span className="system-setting-value">
{setting.sensitive
? t(setting.configured ? "startup.configured" : "startup.notSet")
: value === null ? <span className="system-muted">{t("startup.notSet")}</span> : <code className="system-code">{value}</code>}
{isDefault(setting) ? <ValuePill>{t("startup.default")}</ValuePill> : null}
{setting.changeable ? null : <ValuePill>{t("startup.fixed")}</ValuePill>}
</span>
</td>
<td>{setting.restarts.length ? setting.restarts.join(", ") : <span className="system-muted">{t("startup.noRestart")}</span>}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</Section>
);
}
33 changes: 0 additions & 33 deletions apps/web/src/features/system/system.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,45 +59,12 @@
color: var(--ink-3);
}

/* Where startup settings are changed: the file and the apply command, both copyable. */
.system-where {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 4px 12px;
margin: 0;
color: var(--ink-2);
font-size: 13px;
line-height: 24px;
}

/* The file and the command read as chips that carry their own copy button. */
.system-where .copyable-id {
margin: 0 2px;
padding-left: 8px;
vertical-align: middle;
background: var(--surface);
border-radius: var(--radius-chip);
box-shadow: var(--shadow-hairline);
}

.system-where .copyable-id code {
color: var(--fg);
font-family: var(--font-mono);
font-size: 12px;
}

/* Setting names and values are the table's data, not secondary IDs. */
.data-table.system-settings code {
color: var(--ink);
font-size: 12px;
}

.system-applied {
color: var(--ink-3);
font-size: 12.5px;
}

.system-note {
margin: 0;
color: var(--ink-2);
Expand Down
5 changes: 0 additions & 5 deletions apps/web/src/i18n/locales/en/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ export const system = {
startup: {
title: "Startup settings",
help: "Core reports the process settings it loaded. A sensitive setting shows only whether it is set.",
none: "Core did not report startup settings.",
effective: "These are the settings this Core process loaded.",
where: "Change these in <path/>, then run <command/>",
copyPath: "Copy path",
copyCommand: "Copy command",
appliedAt: "Last applied {{time}}",
columns: {
key: "Setting",
value: "Value",
Expand Down
5 changes: 0 additions & 5 deletions apps/web/src/i18n/locales/zh-CN/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@ export const system: TranslationShape<typeof english> = {
startup: {
title: "启动设置",
help: "Core 报告它加载的进程设置。敏感设置只显示是否已设置。",
none: "Core 没有报告启动设置。",
effective: "这些是这个 Core 进程加载的设置。",
where: "在 <path/> 中修改,然后运行 <command/>",
copyPath: "复制路径",
copyCommand: "复制命令",
appliedAt: "上次应用于 {{time}}",
columns: {
key: "设置",
value: "值",
Expand Down
3 changes: 0 additions & 3 deletions apps/web/src/lib/locale-strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,6 @@ export const chinese = {
"Change the sandbox configuration": "修改沙箱配置",
"The address nodes and sandboxes use to reach Core.": "节点和沙箱访问 Core 使用的地址。",
"Managed in System": "在系统中管理",
"Config file": "配置文件",
"Then run": "然后运行",
"Copy path": "复制路径",
"Set a public address before connecting remote nodes; E2B sandboxes need an HTTPS one.": "连接远程节点前,请先设置公开地址;E2B 沙箱需要 HTTPS 地址。",
"Enter the E2B key again to save.": "请重新输入 E2B key 后再保存。",
"Enter the key": "输入 key",
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Core writes this record in the same transaction that creates the Session. Later
| `api_base_url` | `public_url` followed by `/v1`, the `OPENAI_BASE_URL` for Project API keys. Null when `public_url` is null |
| `local_only` | True when `public_url` names a loopback host, which only the Core host reaches |
| `source_commit` | The full source commit Core was built from; null for development builds |
| `configuration` | The process settings Core loaded from its environment. `path` and `apply_command` are empty, and `applied_at` is null |
| `configuration` | The process settings Core loaded from its environment, under `settings` |
| `address_bindings` | What a change of `public_url` affects, counted on each read |

`configuration.settings` has one entry per setting Core loaded, with its dotted `key`, effective `value`, `default`, whether it is `changeable`, whether it is `sensitive`, and the services it `restarts` (`core`, `web`, `database`).
Expand Down
13 changes: 1 addition & 12 deletions contracts/agents-api/core.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -437,8 +437,7 @@ definitions:
configuration:
allOf:
- $ref: '#/definitions/api.InstallationConfiguration'
description: The process settings Core loaded. path and apply_command are empty, and applied_at is null, because Core reports its environment rather than an installer file.
x-nullable: true
description: The process settings Core loaded.
installation_id:
description: The ID in OAC_INSTALLATION_ID_FILE; null when Core runs without the sandbox manager.
type: string
Expand All @@ -461,16 +460,6 @@ definitions:
type: object
api.InstallationConfiguration:
properties:
applied_at:
description: Null when Core reports its own environment.
type: string
x-nullable: true
apply_command:
description: Command that applies config.json changes. Empty when Core reports its own environment.
type: string
path:
description: Absolute host path of config.json. Empty when Core reports its own environment.
type: string
settings:
items:
$ref: '#/definitions/api.InstallationSetting'
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/zh/admin-api.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Core 管理 API"
source: contracts/agents-api/admin-api.md
source_hash: 3fc6573b19b9c78ca8a3b275122793b1a99e31f739d83ff25ff56624013dc428
source_hash: 22ac83d8596bcee671a4f94c81d2fe65e109ab9c0b3759e17cf30626bd5d41df
---

Core 管理 API(`/core/v1`)用于管理安装实例:Project 及其 API 密钥、Project 资源的读取和删除、执行器凭据、部署默认模型、沙箱部署及其节点、监控和审计。Web 的[控制台服务器](../../../docs/zh/web/console-server.md#forwarding-to-core)会为已登录的管理员调用它;运维人员则从 Core 主机上的脚本调用它([编写 Core API 脚本](../../../docs/zh/getting-started/operations.md#script-the-core-api))。生成的架构是 [core.openapi.yaml](../core.openapi.yaml),所有错误都使用 [Core 错误封装](core-errors.md)。
Expand Down Expand Up @@ -139,7 +139,7 @@ Core 会在创建 Session 的同一事务中写入此记录。之后的 Agent
| `api_base_url` | 在 `public_url` 后附加 `/v1`,即 Project API 密钥使用的 `OPENAI_BASE_URL`。当 `public_url` 为 null 时为 null |
| `local_only` | 当 `public_url` 指向回环主机时为 True,该主机只能由 Core 主机访问 |
| `source_commit` | Core 构建所依据的完整源代码提交;开发构建为 null |
| `configuration` | Core 从环境加载的进程设置。`path` 和 `apply_command` 为空,`applied_at` 为 null |
| `configuration` | Core 从环境加载的进程设置,位于 `settings` 中 |
| `address_bindings` | 更改 `public_url` 所影响的内容,每次读取都会重新统计 |

`configuration.settings` 为 Core 加载的每项设置一条记录,包含以点分隔的 `key`、生效的 `value`、`default`、是否 `changeable`、是否 `sensitive`,以及会 `restarts` 的服务(`core`、`web`、`database`)。
Expand Down
Loading
Loading