diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index 6f1f31bd3..4c14c1539 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -35,7 +35,7 @@ The console runs beside the administrator's own Core, with execution, files and - A node whose provider is not ready names the reason as one Provider-neutral readiness class (provider unavailable, host unsupported, provider files or Runtime image missing, Runtime download failed, a host too small) and its fix in the help tip beside its status, wherever that status shows. - 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 Settings → Sandbox and scheduling. 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. +- **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. Changes keep the saved Runtime. When it differs from the Runtime this console serves, Sandbox and scheduling adds a Runtime row with both source commits and Use this installation's Runtime, which opens Change resources on its review with that Runtime; a console without a Runtime manifest leaves the release to Advanced settings. 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 Settings → Sandbox and scheduling. Overview and Sandbox metrics show the sandboxes Core holds in E2B's cloud (running, starting, size, template build) instead of node capacity, with no Add node action; a sandbox's dialog adds its disk use. - **Automatic suspension** appears when the deployment declares a suspension policy. Nodes shows each node's suspended count and retained limit, the most sandboxes it keeps running and suspended together, with Edit node for its limits; Overview adds the suspended total of all registered nodes under its sandbox slots. Settings → Sandbox and scheduling owns **Automatic suspension**: deployment-wide idle seconds are editable there; snapshot retention remains read-only. Necessary policy explanations belong in help tips, not persistent helper paragraphs. These are Core records, not measured CPU or memory savings. Node allocations show the recorded compute phase and elapsed phase time, never an inferred snapshot-expiry countdown. Sandbox metrics' Hosted Runtimes has one Status column: Core's lifecycle record of each sandbox (Active, Suspended, Changing state, Waiting for allocation or Stopped), which Core derives from the allocation's recorded phase. The Session page's Runtime state reads the same way. When an active sandbox has no sample, its CPU and Memory cells name why. Independent workspace persistence is explained only when the deployment declares it. Nodes refreshes its inventory every 30 seconds while visible; reset and active preparation retain their faster polling. diff --git a/apps/web/e2e/sandbox-generation.spec.ts b/apps/web/e2e/sandbox-generation.spec.ts index 74d3cc846..5f6fd2aef 100644 --- a/apps/web/e2e/sandbox-generation.spec.ts +++ b/apps/web/e2e/sandbox-generation.spec.ts @@ -105,6 +105,22 @@ test("online configuration retains existing resources and stops rapid polling wh expect(await writes(request)).toEqual([`PUT ${deploymentPath}`]); }); +test("a saved Runtime moves to the one this installation serves through the reviewed update", async ({ page, request }) => { + await openConsole(page, request, "settings?id=sandbox"); + const served = await (await page.request.get(new URL("/node-install/manifest.json", page.url()).href)).json() as { source_commit: string; images: { runtime: string } }; + const hex = (c: string) => c.repeat(64); + const saved = { source_commit: "a".repeat(40), image_id: `sha256:${hex("a")}`, image_manifest_digest: `sha256:${hex("b")}`, microsandbox_ref: `oac-runtime@sha256:${hex("c")}`, runtime_sha256: hex("d"), firmware_sha256: hex("e") }; + await setDeployment(request, { specification: { resources: { cpus: 2, memory_mib: 4096 }, runtime: saved } }); + await page.getByRole("button", { name: "Refresh sandbox state", exact: true }).click(); + await expect(page.getByText(`Saved ${saved.source_commit.slice(0, 12)} · this installation serves ${served.source_commit.slice(0, 12)}`)).toBeVisible(); + const use = page.getByRole("button", { name: "Use this installation's Runtime", exact: true }); + await use.click(); + const sent = await saveConfiguration(page); + expect(sent.input.runtime).toMatchObject({ source_commit: served.source_commit, image_id: served.images.runtime }); + await expect(use).toHaveCount(0); + await inspectRollout(page, { "Core preparation": "Preparing configuration", "Target generation": "2" }); +}); + test("unknown target preparation preserves live old-generation service while an offline pin stays offline", async ({ page, request }, info) => { await page.setViewportSize({ width: 1280, height: 1000 }); await page.emulateMedia({ colorScheme: "light", reducedMotion: "reduce" }); diff --git a/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx b/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx index 6b3c6400c..3b1affcaf 100644 --- a/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx +++ b/apps/web/src/features/sandbox/SandboxDeploymentSettings.tsx @@ -1,13 +1,15 @@ import { useState, type ReactNode } from "react"; -import type { UpdateSandboxDeployment, SandboxDeployment } from "@oac/agents-client"; -import { useTranslation } from "react-i18next"; +import type { UpdateSandboxDeployment, SandboxDeployment, SandboxRuntimeRelease } from "@oac/agents-client"; +import { useQuery } from "@tanstack/react-query"; +import { Trans, useTranslation } from "react-i18next"; import { Modal } from "../../components/Modal"; import { HelpTip, RefreshButton } from "../../components/console-ui"; import { formatBytes, formatPeriod, MISSING } from "../../lib/format"; import type { ParseKeys } from "i18next"; import { sandboxProviderLabel } from "../../lib/sandbox-labels"; import { Fact } from "../system/Fact"; -import { sandboxSize, templateBuildSize, templateBuildStatus } from "./deployment-specification"; +import { RUNTIME_RELEASE_FIELDS, sandboxSize, templateBuildSize, templateBuildStatus } from "./deployment-specification"; +import { distributionRuntimeQuery } from "./sandbox-queries"; import { SandboxRolloutSummary } from "./SandboxRolloutSummary"; import { SandboxSetupWizard } from "./SandboxSetupWizard"; @@ -29,13 +31,21 @@ export function SandboxDeploymentSettings({ deployment, disabled, fresh, onUpdat const locale = i18n.resolvedLanguage?.startsWith("zh") ? "zh-CN" : "en"; const [changing, setChanging] = useState(false); const [editKey, setEditKey] = useState(0); + const [proposedRuntime, setProposedRuntime] = useState(); const spec = deployment.specification; + // Only Docker and microsandbox save a Runtime. Changes keep it; moving to the + // one this console serves is an explicit change of its own. + const savedRuntime = spec?.runtime; + const served = useQuery({ ...distributionRuntimeQuery, enabled: Boolean(savedRuntime) }).data; + const offered = savedRuntime && served && !deployment.reset && RUNTIME_RELEASE_FIELDS.some((field) => savedRuntime[field] !== served[field]) ? served : null; // An E2B selection may adopt its template build's size instead of saving one. const size = sandboxSize(deployment); const build = deployment.metadata?.template_build; const buildSize = templateBuildSize(deployment); const sizeLabel = (value: { cpus: number; memory_mib: number }) => t("{{cpus}} CPU · {{memory}}", { cpus: value.cpus, memory: formatBytes(value.memory_mib * MIB) }); const canEdit = fresh && !deployment.reset; + const change = (runtime?: SandboxRuntimeRelease) => { setEditKey((key) => key + 1); setProposedRuntime(runtime); setChanging(true); }; + const short = (release: SandboxRuntimeRelease) => {release.source_commit.slice(0, 12)}; return
@@ -53,12 +63,15 @@ export function SandboxDeploymentSettings({ deployment, disabled, fresh, onUpdat
{!deployment.reset ?
{t("Changes keep this backend and existing Sessions and nodes. Core prepares the new target for future work; placement may continue on qualified earlier generations.")} - +
: null}
{sandboxProviderLabel(deployment.provider, locale)} {size ? `${sizeLabel(size)}${size.root_disk_mib ? ` · ${spec?.workspace ? t("Root disk {{root}} · external workspace", { root: formatBytes(size.root_disk_mib * MIB) }) : t("Root disk {{root}} · data disk {{data}}", { root: formatBytes(size.root_disk_mib * MIB), data: formatBytes((size.environment_disk_mib ?? 0) * MIB) })}` : ""}` : MISSING} + {savedRuntime && offered ? change(offered)}>{t("Use this installation's Runtime")}}> + + : null} {deployment.provider === "e2b" ? <> {deployment.configuration?.api_url} {deployment.configuration?.domain} @@ -79,6 +92,7 @@ export function SandboxDeploymentSettings({ deployment, disabled, fresh, onUpdat current={deployment.provider ? { provider: deployment.provider, specification: deployment.specification, e2bTemplate: deployment.configuration?.template, e2bAPIURL: deployment.configuration?.api_url, e2bDomain: deployment.configuration?.domain } : undefined} disabled={disabled || !canEdit} editing + proposedRuntime={proposedRuntime} onSubmit={async (input) => { if (await onUpdate(input)) setChanging(false); }} /> {writeFailure} diff --git a/apps/web/src/features/sandbox/SandboxRolloutSummary.test.tsx b/apps/web/src/features/sandbox/SandboxRolloutSummary.test.tsx index 3fa0d84d1..a6d724701 100644 --- a/apps/web/src/features/sandbox/SandboxRolloutSummary.test.tsx +++ b/apps/web/src/features/sandbox/SandboxRolloutSummary.test.tsx @@ -1,4 +1,5 @@ import type { SandboxDeployment } from "@oac/agents-client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import { node } from "../overview/test-fixtures"; @@ -51,7 +52,7 @@ describe("authoritative configuration rollout", () => { }); it("allows same-provider online editing with held resources but keeps reset active as a boundary", () => { - const render = (value: SandboxDeployment) => renderToStaticMarkup( true} onRefresh={() => undefined} refreshing={false} pending={false} />); + const render = (value: SandboxDeployment) => renderToStaticMarkup( true} onRefresh={() => undefined} refreshing={false} pending={false} />); const html = render(deployment); expect(html).toMatch(/]*class="button outline"[^>]*>Change resources<\/button>/); expect(html).not.toMatch(/]*disabled[^>]*>Change resources<\/button>/); diff --git a/apps/web/src/features/sandbox/SandboxSetupWizard.tsx b/apps/web/src/features/sandbox/SandboxSetupWizard.tsx index 4fdf6d6df..7644af628 100644 --- a/apps/web/src/features/sandbox/SandboxSetupWizard.tsx +++ b/apps/web/src/features/sandbox/SandboxSetupWizard.tsx @@ -15,9 +15,9 @@ import { formatBytes } from "../../lib/format"; import { installationQuery } from "../../lib/installation"; import type { ParseKeys } from "i18next"; import { sandboxConfigurationRejection, sandboxProviderLabel } from "../../lib/sandbox-labels"; -import { defaultSandboxResources, distributionRuntime, isRuntimeRelease, isRuntimeReleaseField, RUNTIME_RELEASE_FIELDS, savedSpecification, validSandboxResources } from "./deployment-specification"; +import { defaultSandboxResources, isRuntimeRelease, isRuntimeReleaseField, RUNTIME_RELEASE_FIELDS, savedSpecification, validSandboxResources } from "./deployment-specification"; import { e2bKeyReady, e2bUpdateSelection } from "./sandbox-update"; -import { sandboxAdmin } from "./sandbox-queries"; +import { distributionRuntimeQuery, sandboxAdmin } from "./sandbox-queries"; import "./sandbox-wizard.css"; type Where = "nodes" | "direct"; @@ -118,16 +118,18 @@ type WizardProps = { expectedGeneration: number; current?: { provider: SandboxProvider; specification?: SandboxSpecification; e2bTemplate?: string; e2bAPIURL?: string; e2bDomain?: string }; disabled: boolean; + /** A Runtime release an update proposes; the wizard opens on its review. */ + proposedRuntime?: SandboxRuntimeRelease; } & ({ editing: true; onSubmit: (input: UpdateSandboxDeployment) => Promise } | { editing?: false; onSubmit: (input: InitializeSandboxDeployment) => Promise }); -export function SandboxSetupWizard({ coreUrl, expectedGeneration, current, disabled, editing, onSubmit }: WizardProps) { +export function SandboxSetupWizard({ coreUrl, expectedGeneration, current, disabled, editing, proposedRuntime, onSubmit }: WizardProps) { const { t, i18n } = useTranslation("sandbox"); const { t: tCommon } = useTranslation("common"); const id = useId(); const locale = i18n.resolvedLanguage?.startsWith("zh") ? "zh-CN" : "en"; const currentMode: Where | null = current ? deploymentContract.providers[current.provider].mode : null; - const [step, setStep] = useState(editing ? currentMode === "direct" ? "e2b" : "size" : "where"); + const [step, setStep] = useState(proposedRuntime ? "review" : editing ? currentMode === "direct" ? "e2b" : "size" : "where"); const [where, setWhere] = useState(currentMode); const [provider, setProvider] = useState(current?.provider ?? null); const policy = provider ? deploymentContract.providers[provider] : null; @@ -149,7 +151,7 @@ export function SandboxSetupWizard({ coreUrl, expectedGeneration, current, disab const [discovery, setDiscovery] = useState<"idle" | "loading" | "ready" | "error">("idle"); const [buildDiscovery, setBuildDiscovery] = useState<"idle" | "loading" | "ready" | "error">("idle"); const [discoveryRetry, setDiscoveryRetry] = useState(0); - const [runtime, setRuntime] = useState>({}); + const [runtime, setRuntime] = useState>(proposedRuntime ?? {}); const [busy, setBusy] = useState(false); const [dockerConfirmed, setDockerConfirmed] = useState(current?.provider === "docker"); const [confirmingDocker, setConfirmingDocker] = useState(false); @@ -193,9 +195,9 @@ export function SandboxSetupWizard({ coreUrl, expectedGeneration, current, disab setTemplate(""); setSelectedTemplate(""); setTemplates([]); setBuilds([]); } - // A release the administrator entered comes first, then the saved one of the same backend, + // A release the administrator entered or an update proposes comes first, then the saved one of the same backend, // then the one this console distributes (the release its node installer verifies). - const matched = useQuery({ queryKey: ["sandbox-runtime-release"], queryFn: ({ signal }) => distributionRuntime(signal).catch(() => null), staleTime: Infinity, retry: false }); + const matched = useQuery(distributionRuntimeQuery); const release: Partial = Object.keys(runtime).length ? runtime : saved?.runtime ?? matched.data ?? {}; const needsRuntime = policy?.runtime ?? false; diff --git a/apps/web/src/features/sandbox/sandbox-queries.ts b/apps/web/src/features/sandbox/sandbox-queries.ts index 81029f64e..5bf2fe68d 100644 --- a/apps/web/src/features/sandbox/sandbox-queries.ts +++ b/apps/web/src/features/sandbox/sandbox-queries.ts @@ -3,6 +3,7 @@ import { queryOptions, useQuery } from "@tanstack/react-query"; import { confirmSandboxRead, startSandboxRead } from "./sandbox-write-ownership"; import { sandboxConsoleConfig } from "./console-config"; +import { distributionRuntime } from "./deployment-specification"; /** * Cache entries for the sandbox deployment (`/core/v1/sandbox`). Every read @@ -24,6 +25,14 @@ export const sandboxConsoleConfigQuery = queryOptions({ }, }); +/** The Runtime release of this console's distribution manifest, the release its node installer verifies; null when it serves none. */ +export const distributionRuntimeQuery = queryOptions({ + queryKey: ["sandbox-runtime-release"], + queryFn: ({ signal }) => distributionRuntime(signal).catch(() => null), + staleTime: Infinity, + retry: false, +}); + /** The deployment alone, for pages that only describe it. */ export const sandboxDeploymentQuery = queryOptions({ queryKey: [...sandboxScope, "deployment"], diff --git a/apps/web/src/features/settings/settings.css b/apps/web/src/features/settings/settings.css index 8f89cc477..4a99469c1 100644 --- a/apps/web/src/features/settings/settings.css +++ b/apps/web/src/features/settings/settings.css @@ -105,7 +105,7 @@ overflow-wrap: anywhere; } -.settings-row-content { min-width: 0; flex: 1; } +.settings-row-content { min-width: 0; flex: 1 1 auto; } .settings-row-action { display: flex; flex: none; gap: 8px; margin-left: auto; } .settings-row pre { diff --git a/apps/web/src/i18n/locales/zh-CN/sandbox.ts b/apps/web/src/i18n/locales/zh-CN/sandbox.ts index 882058a1c..a6c368a89 100644 --- a/apps/web/src/i18n/locales/zh-CN/sandbox.ts +++ b/apps/web/src/i18n/locales/zh-CN/sandbox.ts @@ -331,6 +331,9 @@ export const sandbox = { "Root disk (MiB)": "根盘(MiB)", "Root disk {{root}} · data disk {{data}}": "根盘 {{root}} · 数据盘 {{data}}", "Runtime": "Runtime", + "Saved · this installation serves ": "已保存 · 本安装提供 ", + "Other changes keep the saved Runtime. Existing sandboxes keep their Runtime while nodes prepare the new one.": "其他修改会保留已保存的 Runtime。节点准备新 Runtime 期间,现有沙箱继续使用原来的 Runtime。", + "Use this installation's Runtime": "使用本安装的 Runtime", "Runtime SHA-256": "Runtime SHA-256", "Runtime release": "Runtime 版本", "Runtime release needed": "需要 Runtime 版本", diff --git a/docs/getting-started/nodes.md b/docs/getting-started/nodes.md index daa6510f2..179f3d3f9 100644 --- a/docs/getting-started/nodes.md +++ b/docs/getting-started/nodes.md @@ -102,6 +102,7 @@ Run `sudo journalctl -u oac-node-.service`. In a root shell, om Sandbox settings apply to the whole installation; nodes follow them. - **Size, Runtime release, E2B key or template build.** Open **Settings** → **Sandbox and scheduling** → **Change resources**, edit and save. Existing sandboxes keep their configuration. Each node prepares the new one while it keeps serving the old one, and **Nodes** shows its progress: **Preparing target**, **Ready for target** or **Preparation failed** with the [reason](#readiness-codes). Core places new Sessions on nodes ready for the new configuration first, and on nodes still serving an older one when those have no room. E2B changes apply at once. +- **The installation's Runtime.** When the saved Runtime differs from the one the installation serves, **Settings** → **Sandbox and scheduling** shows a **Runtime** row with both source commits. **Use this installation's Runtime** opens **Change resources** on its review with that Runtime, and **Save configuration** starts the same node preparation. Other changes keep the saved Runtime. When the console serves no Runtime manifest, enter the release under **Advanced settings**. - **Capacity and idle suspension.** On **Nodes**, use **Edit node** for a node's active and retained limits. In **Settings** → **Sandbox and scheduling**, use **Automatic suspension** → **Idle before suspension (seconds)** → **Save** for the deployment-wide [idle policy](../../contracts/agents-api/sandbox-deployment.md#suspension-policy). - **Backend.** On the same page, choose **Reset deployment**. Auto reset archives idle hosted Sessions at once and lets running work finish until the deadline you set; force cancels it now. Offline nodes must come back so Core can confirm their cleanup. When the reset completes, Core has retired every node and unused command: set up the new backend, then add nodes again. **Cancel reset** stops the remaining work; archived Sessions stay archived. diff --git a/docs/web/console-api-usage.md b/docs/web/console-api-usage.md index 879018c5d..57f0f41bb 100644 --- a/docs/web/console-api-usage.md +++ b/docs/web/console-api-usage.md @@ -8,7 +8,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`, `/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; 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`, `/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; the distribution's Runtime release for Docker and microsandbox setup and for the Runtime row in Settings → Sandbox and scheduling. 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, the fleet and capacity summary on Overview, Runtime observations of every project on Sandbox metrics | | Agents API | `/v1/**` | Project API key | Not used. The console shows developers how to call it (see [Provenance and monitoring](#provenance-and-monitoring)) | @@ -114,7 +114,7 @@ The list carries each Harness's configuration, so the console does not read `GET | 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. Settings → Sandbox and scheduling reads the manifest only for a deployment with a saved `specification.runtime` and shows the Runtime row only when the two releases differ; Use this installation's Runtime sends the manifest's release through the same `PUT`. ## Writes diff --git a/docs/zh/getting-started/nodes.md b/docs/zh/getting-started/nodes.md index 988eca509..768c9947b 100644 --- a/docs/zh/getting-started/nodes.md +++ b/docs/zh/getting-started/nodes.md @@ -1,7 +1,7 @@ --- title: "添加和管理节点" source: docs/getting-started/nodes.md -source_hash: 197acaddceb84ff46a833b883a8751c24d3f4a2ae49e077280e0c677a083424e +source_hash: 1a87d17a7d38d808c8d60c616b439bfb4d50c549412e2366dc8c4d7cc01c28d5 --- 节点是一台 Linux 主机,在沙箱提供商为 microsandbox(推荐)或 Docker 时,为 Core 托管 Session 运行沙箱。Core 将新 Session 分配给有空余容量的节点;节点创建沙箱,沙箱回连 Core。E2B 不需要节点。应用为自己的 Session 连接的机器是[自托管执行器](self-hosted.md),而不是节点。 @@ -104,6 +104,7 @@ root 只准备账号、组和服务单元;其他操作(包括 Docker 网络 沙箱设置适用于整个安装,节点跟随这些设置。 - **规格、Runtime 版本、E2B 密钥或模板构建。** 打开 **设置** → **沙箱与调度** → **修改资源**,修改并保存。已有沙箱保持其配置。每个节点继续服务旧配置,同时准备新配置;**节点** 展示进度:**正在准备目标配置**、**目标配置已就绪**,或带[原因](#readiness-codes)的 **目标配置准备失败**。Core 优先在已准备好新配置的节点上放置新 Session;这些节点没有容量时,会使用仍服务旧配置的节点。E2B 变更立即生效。 +- **安装提供的 Runtime。** 已保存的 Runtime 与安装提供的不同时,**设置** → **沙箱与调度** 会显示 **Runtime** 行,列出两者的源码提交。**使用本安装的 Runtime** 以该 Runtime 打开 **修改资源** 的确认页,**保存配置** 后节点按上面的方式准备。其他修改保留已保存的 Runtime。控制台没有提供 Runtime 清单时,在 **高级设置** 中填写版本。 - **容量与空闲挂起。** 在 **节点** 中通过 **编辑节点** 修改节点的活跃和保留上限。在 **设置** → **沙箱与调度** 中,通过 **自动挂起** → **空闲多久后挂起(秒)** → **保存** 修改整个部署的[空闲策略](../../../contracts/agents-api/zh/sandbox-deployment.md#suspension-policy)。 - **沙箱提供商。** 同一页面选择 **重置部署**。自动重置立即归档空闲托管 Session,并让运行中的工作在设定截止时间前完成;强制重置立即取消工作。离线节点必须恢复连接,让 Core 确认清理。重置完成时,Core 已撤销所有节点和未使用命令:配置新的沙箱提供商后重新添加节点。**取消重置** 停止剩余工作;已归档 Session 仍保持归档。 diff --git a/docs/zh/web/console-api-usage.md b/docs/zh/web/console-api-usage.md index 1a4d68106..7a2e82c98 100644 --- a/docs/zh/web/console-api-usage.md +++ b/docs/zh/web/console-api-usage.md @@ -1,7 +1,7 @@ --- title: "控制台 API 使用" source: docs/web/console-api-usage.md -source_hash: 6eb50333e1da66b50f70b0f07835b76fb8717f80a22a307b1b4dda21ec191b44 +source_hash: 77a5047c028ea755622714bf89f92066f9b331b7832f0295dec85390e13b182e --- 本页列出各控制台页面读取和写入的 Core 路由,以及控制台如何限定读取范围。[administrator API contract](../../../contracts/agents-api/zh/admin-api.md) 定义了路由、响应结构、分页和审计记录;[API namespaces and credentials](../api/index.md) 定义了本文使用的术语。 @@ -10,7 +10,7 @@ source_hash: 6eb50333e1da66b50f70b0f07835b76fb8717f80a22a307b1b4dda21ec191b44 | 接口 | 路径 | 身份验证 | 控制台用途 | | --- | --- | --- | --- | -| Console server | `/console/auth`、`/console/auth/{login,logout}`、`/console/config`、`/node-install/manifest.json` | 登录时使用 Core 密钥,随后使用控制台会话 Cookie;`/node-install/manifest.json` 无需登录 | 登录和退出;**添加节点** 所用的节点安装程序和节点构件;用于 Docker 和 microsandbox 设置的发行版 Runtime release。参见 [console server](console-server.md) | +| Console server | `/console/auth`、`/console/auth/{login,logout}`、`/console/config`、`/node-install/manifest.json` | 登录时使用 Core 密钥,随后使用控制台会话 Cookie;`/node-install/manifest.json` 无需登录 | 登录和退出;**添加节点** 所用的节点安装程序和节点构件;用于 Docker 和 microsandbox 设置以及 **设置** → **沙箱与调度** 中 Runtime 行的发行版 Runtime release。参见 [console server](console-server.md) | | Administrator API | `/core/v1/**`,不包括 `/core/v1/sandbox` | Core 密钥,由控制台服务器添加 | 项目、密钥、资源读取和删除、诊断、执行器凭据和安装命令、来源信息、汇总、Core 指标、安装信息、默认模型 | | Sandbox administration | `/core/v1/sandbox/**` | Core 密钥,由控制台服务器添加 | 沙箱配置;**节点**;**概览** 上的机群与容量摘要;**沙箱监控** 中每个项目的 Runtime 观测 | | Agents API | `/v1/**` | 项目 API 密钥 | 不使用。控制台会向开发者说明如何调用它(参见 [Provenance and monitoring](#provenance-and-monitoring)) | @@ -116,7 +116,7 @@ source_hash: 6eb50333e1da66b50f70b0f07835b76fb8717f80a22a307b1b4dda21ec191b44 | 移除节点 | `DELETE /core/v1/sandbox/nodes/{node_id}` | 确认后移除节点;只有 Core 确认删除后该行才会消失,随后 **清理主机** 对话框会提供主机的卸载命令(需要 root 或 sudo;对于使用不同于部署地址的地址注册的节点,还需要使用 `--force`,以跳过安装程序与 Core 的确认) | | Runtime 观测 | `GET /core/v1/sandbox/runtime-observations` | **沙箱监控**:每个项目的托管 Runtime,每项均以所属项目为标签;E2B Sandbox 对话框还会将 `observation.disk` 显示为已用量/限制值(其他位置为 null) | -E2B 部署没有节点;其 API 密钥为只写。**概览** 和 **沙箱监控** 根据部署的 `resources.allocations` 和 `resources.pending` 统计其正在运行和正在启动的 Sandbox,而托管 Runtime 行来自 Runtime 观测。两个来源独立刷新,因此控制台不会根据两者之间的差异推断保留或清理状态。用于 Docker 和 microsandbox 的 Runtime 版本来自控制台自身的 `GET /node-install/manifest.json`;如果无法获取该信息,管理员需在高级设置中输入 release。 +E2B 部署没有节点;其 API 密钥为只写。**概览** 和 **沙箱监控** 根据部署的 `resources.allocations` 和 `resources.pending` 统计其正在运行和正在启动的 Sandbox,而托管 Runtime 行来自 Runtime 观测。两个来源独立刷新,因此控制台不会根据两者之间的差异推断保留或清理状态。用于 Docker 和 microsandbox 的 Runtime 版本来自控制台自身的 `GET /node-install/manifest.json`;如果无法获取该信息,管理员需在高级设置中输入 release。**设置** → **沙箱与调度** 只为已保存 `specification.runtime` 的部署读取该清单,并且只在两个 release 不同时显示 Runtime 行;**使用本安装的 Runtime** 通过同一个 `PUT` 发送清单中的 release。 ## 写入 {#writes}