From d4beaafea68ff1976077bf27107c4ce2c3f159e7 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 14 Oct 2026 06:08:14 +0000 Subject: [PATCH 1/3] Make failed Sessions actionable --- apps/web/PRODUCT.md | 5 +- apps/web/e2e/diagnostics.spec.ts | 2 +- .../features/metrics/SandboxMetricsPage.tsx | 4 +- .../src/features/metrics/sandbox-runtime.ts | 8 +-- .../src/features/overview/OverviewPage.tsx | 53 +++++++++---------- .../features/overview/overview-loader.test.ts | 4 +- .../src/features/overview/overview-loader.ts | 7 ++- .../features/overview/overview-model.test.ts | 5 +- .../src/features/overview/overview-model.ts | 13 +++-- apps/web/src/features/overview/overview.css | 4 +- .../src/features/sessions/SessionLogPage.tsx | 4 +- .../web/src/features/sessions/SessionPage.tsx | 9 ++-- .../sessions/session-diagnostics.test.tsx | 22 ++++++-- .../features/sessions/session-diagnostics.tsx | 13 +++-- apps/web/src/i18n/locales/en/common.ts | 1 + apps/web/src/i18n/locales/en/diagnostics.ts | 33 +++++++++--- apps/web/src/i18n/locales/en/metrics.ts | 1 - apps/web/src/i18n/locales/en/overview.ts | 9 ++-- apps/web/src/i18n/locales/zh-CN/common.ts | 1 + .../web/src/i18n/locales/zh-CN/diagnostics.ts | 33 +++++++++--- apps/web/src/i18n/locales/zh-CN/metrics.ts | 1 - apps/web/src/i18n/locales/zh-CN/overview.ts | 11 ++-- apps/web/src/lib/diagnostic-failure.ts | 16 ++++-- apps/web/src/lib/session-title.ts | 11 ++++ docs/web/console-api-usage.md | 4 +- docs/web/index.md | 4 +- docs/zh/web/console-api-usage.md | 6 +-- docs/zh/web/index.md | 6 +-- 28 files changed, 179 insertions(+), 111 deletions(-) create mode 100644 apps/web/src/lib/session-title.ts diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index 23b0a3e0a..82c39f76d 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -29,7 +29,7 @@ The console runs beside the administrator's own Core, with execution, files and ## Information Architecture -- **Monitor**: Overview (service status, running Sessions, sandbox slots, Sessions needing attention, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, with a popover glance at each, the attention table, usage by project), Core metrics (the Core process's CPU and memory, execution slots and the Turn queue, connected daemons, the database and background jobs), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (node capacity and hosted Runtimes across projects; a node or a sandbox opens in a dialog with its figures and CPU and memory charts), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log. +- **Monitor**: Overview (service status, running Sessions, sandbox slots, Sessions needing attention in the last 24 hours, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, with a popover glance at each, the attention table, whose rows name each Session, its project, its one-line reason and last activity and open it, with older ones left to the Session log, usage by project), Core metrics (the Core process's CPU and memory, execution slots and the Turn queue, connected daemons, the database and background jobs), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (node capacity and hosted Runtimes across projects; a node or a sandbox opens in a dialog with its figures and CPU and memory charts), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log. - **Resources**: Agents, Environment templates, Skills, Files, Vaults. Each list shows one project or all projects, with a Project column when all are shown and a Creator column naming the creating key. Detail pages show the resource's facts and offer Delete. An empty list says, in one sentence, that applications create these resources through the Agents API, and links to that resource's section of the API guide; Agent metrics does the same for Sessions. - **Platform**: Projects and keys (projects, their assets and usage, named keys, write history), Nodes (the node list, capacity observations, host figures, allocations and individual removal operations), and Settings, the single entry for platform configuration. Settings has four groups: Sandbox and scheduling, Workspace storage, Models and credentials, and Service and deployment. Sandbox and scheduling owns setup, resource edits, rollout, reset, idle policy, per-node capacity and enrollment. Add node asks for limits before issuing its one-time command; installers use Core's public URL and require supported node artifacts. Nodes links to these forms; removal stays on the node page and offers the host's uninstall command. Models and credentials owns each harness's default model and shows whether each harness is ready: Ready when it is enabled and has a default model provider, otherwise the missing step, Model provider not set (with Set as the primary action) or Disabled (with a link to the startup settings). Getting started uses the same readiness. Service and deployment owns installation facts (the public and API base URLs, with the installation ID and source commit behind Installation details), Domain and HTTPS actions and read-only startup settings, each named by its environment variable, with a link to the configuration guide's section on changing them. - A node whose provider is not ready names the reason as one Provider-neutral readiness class (provider unavailable, host unsupported, provider files or Runtime image missing, Runtime download failed, a host too small) and its fix in the help tip beside its status, wherever that status shows. @@ -52,8 +52,9 @@ The console runs beside the administrator's own Core, with execution, files and - **Secrets stay write-only.** Credential tokens, Template environment variables and setup commands are never returned, to the administrator included. An Agent's saved model provider shows its protocol, base URL, limits and whether a key is configured, never the key. - **Creators.** Core records the key behind every write. The console shows the creating key of each asset and a project's write history; an asset without a record shows as Unknown. - **Waiting for results.** Overview, Session log and Session details name the function whose result the calling application must submit. The console cannot submit that result; environment connection waits stay distinct from function waits. +- **Session names.** Wherever Sessions are listed, and on the Session page, a Session is named by its metadata `title`, else its Agent's name, else Untitled Session. - **Session history is read-only.** A Session page reads the Session, its Items and Turns and polls while work is in flight; there is no live event stream. -- **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible. +- **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. The Session page adds one next step where the reason has one, such as checking the model ID on the Agent or in Settings → Models and credentials; lists show the reason alone, with the next step in its tooltip. Core never returns the Harness's or provider's own message, so an unclassified failure says so. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible. - **Executor credentials.** Core issues executor credentials; the console does so with the deployment's Core key. A Session page whose environment is self-hosted has an Executor credentials section: issue a credential (shown once as one line of JSON, to copy or download, never stored), rotate it (the old one stops working immediately) or revoke it (the executor disconnects; installed Runtime state and workspace contents are not deleted). The file lets one executor connect for that environment only; it cannot call the Agents API. - **Default provider observations.** Each configured harness offers Usage details for Core's last successful use and any newer classified provider error. Missing records remain unknown; an error at or before the last success is no longer actionable. These are best-effort observations, not readiness checks. Failed refreshes qualify retained records, and replacing the provider starts a new observation history. - **Host connection.** Core's connection observation and credential metadata share one five-second read while visible. Never connected, connected, disconnected, bound credential revoked, and unknown are distinct; a recent heartbeat alone never proves connectivity. Only a fresh connected read marks Host connected. Stale or failed reads withhold completion. Recovery rotates the bound key, stops the installed daemon, replaces the host credential file and starts the daemon again. diff --git a/apps/web/e2e/diagnostics.spec.ts b/apps/web/e2e/diagnostics.spec.ts index 962380260..a7a01680f 100644 --- a/apps/web/e2e/diagnostics.spec.ts +++ b/apps/web/e2e/diagnostics.spec.ts @@ -22,7 +22,7 @@ test("reads classified failures and separate Item receipts without execution wri await page.getByRole("radio", { name: /^Failed/ }).click(); const row = page.getByRole("row").filter({ hasText: "The model provider rejected authentication" }).first(); await row.getByRole("button", { name: /^Open Session / }).click(); - await expect(page.getByLabel("Session facts")).toContainText("Check its API key"); + await expect(page.getByLabel("Session facts")).toContainText("Check the model service's API key"); await page.getByRole("radio", { name: "Trace", exact: true }).click(); await page.locator(".trace-ledger-row-tools").first().click(); await page.getByRole("tab", { name: "Timing", exact: true }).click(); diff --git a/apps/web/src/features/metrics/SandboxMetricsPage.tsx b/apps/web/src/features/metrics/SandboxMetricsPage.tsx index a937f2af0..c79c62e4a 100644 --- a/apps/web/src/features/metrics/SandboxMetricsPage.tsx +++ b/apps/web/src/features/metrics/SandboxMetricsPage.tsx @@ -47,7 +47,6 @@ import { matchesRuntime, runtimeSnapshot, runtimeSnapshotWhere, - sessionTitle, type HostedRuntimeLoad, type HostedRuntimeRow, } from "./sandbox-runtime"; @@ -55,6 +54,7 @@ import "./MetricsView.css"; import { RuntimeCharts } from "./RuntimeCharts"; import { hostedRuntimesQuery } from "./metrics-queries"; import { SessionRuntimeSection } from "../sessions/SessionRuntimeSection"; +import { sessionTitle } from "../../lib/session-title"; import type { SandboxDeployment, SandboxNode } from "@oac/agents-client"; const healthTone: Record = { available: "ok", degraded: "warning", offline: "danger" }; @@ -410,7 +410,7 @@ function RuntimeTable({ rows, showProject, showNode, showCompute, onOpen }: { ro return ( - + {showProject ? : null} {showNode ? {row.node ? row.node.name || row.node.id : {MISSING}} : null} diff --git a/apps/web/src/features/metrics/sandbox-runtime.ts b/apps/web/src/features/metrics/sandbox-runtime.ts index bc29f4589..b03e28756 100644 --- a/apps/web/src/features/metrics/sandbox-runtime.ts +++ b/apps/web/src/features/metrics/sandbox-runtime.ts @@ -1,6 +1,7 @@ import type { AgentSession, SandboxAllocation, SandboxNode } from "@oac/agents-client"; import type { ProjectClient } from "../../lib/projects"; +import { sessionTitle } from "../../lib/session-title"; import type { RuntimeDashboardSnapshot } from "../dashboard/runtime-snapshot"; import { type OwnedRuntimeObservation } from "../../lib/admin-view"; @@ -163,13 +164,6 @@ export function hostedRuntimeRows( }); } -export function sessionTitle(session: AgentSession | null): string | null { - if (!session) return null; - const title = session.metadata?.title; - if (typeof title === "string" && title.trim()) return title; - return session.agent?.name?.trim() ? session.agent.name : null; -} - /** Local search over a Runtime row's Session, Agent, host and IDs. */ export function matchesRuntime(row: HostedRuntimeRow, query: string): boolean { const needle = query.trim().toLocaleLowerCase(); diff --git a/apps/web/src/features/overview/OverviewPage.tsx b/apps/web/src/features/overview/OverviewPage.tsx index 54c505fd9..07783f3b7 100644 --- a/apps/web/src/features/overview/OverviewPage.tsx +++ b/apps/web/src/features/overview/OverviewPage.tsx @@ -25,6 +25,7 @@ import { NameCell } from "../../components/list-ui"; import { useConsoleNavigation } from "../../lib/console-navigation"; import { formatClock, formatCompact, formatInteger, formatPercent, formatRelative, MISSING } from "../../lib/format"; import { ProjectName, useProjects } from "../../lib/projects"; +import { sessionTitle } from "../../lib/session-title"; import { capacitySummary, coreStatus, type CoreStatus } from "../fleet/fleet-model"; import { FleetReadNotice, fleetObservationStale } from "../fleet/FleetReadNotice"; import { SandboxResetNotice } from "../fleet/SandboxResetNotice"; @@ -37,6 +38,7 @@ import { GettingStarted } from "./GettingStarted"; import { type OverviewData } from "./overview-loader"; import { overviewQuery } from "./overview-queries"; import { + activityStart, attentionCount, attentionSessions, projectUsageRows, @@ -114,7 +116,7 @@ export function OverviewPage() { const usageRows = useMemo(() => (summaryRows ? projectUsageRows(projects, summaryRows) : null), [projects, summaryRows]); const sessions = data && (data.sessions.sessions.length || !sessionReadsFailed) ? data.sessions.sessions : null; const activity = useMemo(() => (sessions ? sessionActivity(sessions.map((entry) => entry.value), now) : null), [now, sessions]); - const attention = useMemo(() => (sessions ? attentionSessions(sessions, ATTENTION_LIMIT) : null), [sessions]); + const attention = useMemo(() => (sessions ? attentionSessions(sessions, activityStart(now)) : null), [now, sessions]); const capacity = fleet ? capacitySummary(fleet.nodes) : null; // An E2B deployment has no machines: capacity is what Core holds in E2B's cloud. const cloud = cloudHost(fleet); @@ -149,7 +151,9 @@ export function OverviewPage() { const loading = refreshing || projectsState.state.status === "loading" || (fleetState.status === "ready" && fleetState.refreshing); const updatedAt = data?.loadedAt ?? fleet?.loadedAt ?? null; - const attentionTotal = totals ? attentionCount(totals) : null; + const attentionFailed = attention ? attention.filter((entry) => entry.value.status === "failed").length : null; + // Core's summary counts every Session needing attention; the rest of them are older than the window. + const attentionOlder = attention && totals ? Math.max(0, attentionCount(totals) - attention.length) : null; const truncated = data?.sessions.truncated ?? []; // Whether any Session exists: Core's summary counts them all; without it, any Session read counts. const sessionCount = totals ? totals.total @@ -214,8 +218,8 @@ export function OverviewPage() { } - sub={totals ? t("tiles.attentionSplit", { failed: totals.failed, waiting: totals.requires_action }) : t("kpi.summaryUnavailable")} + value={} + sub={attention && attentionFailed !== null ? t("tiles.attentionSplit", { failed: attentionFailed, waiting: attention.length - attentionFailed }) : sessionReadsFailed ? t("kpi.readsFailed") : null} /> @@ -267,17 +271,18 @@ export function OverviewPage() {

{t("attention.title")}

{t("attention.subtitle")} - {attention && attentionTotal !== null && attention.length < attentionTotal ? ( + {attention && attention.length > ATTENTION_LIMIT ? ( <> - {t("attention.shown", { shown: formatInteger(attention.length, locale), total: formatInteger(attentionTotal, locale) })} - {attention.length < Math.min(attentionTotal, ATTENTION_LIMIT) ? t("attention.partialHelp") : t("attention.limitHelp", { count: ATTENTION_LIMIT })} + {t("attention.shown", { shown: formatInteger(ATTENTION_LIMIT, locale), total: formatInteger(attention.length, locale) })} + {t("attention.limitHelp", { count: ATTENTION_LIMIT })} ) : null} + {attention?.length && truncated.length ? {t("attention.partialHelp")} : null}
{sessionReadsFailed || summaryError !== null ? : null} - {attention?.length || (!sessionReadsFailed && summaryError === null) ? failure.project.name)} truncated={truncated.length > 0} now={now} onOpen={openSession} /> : null} + {attention?.length || (!sessionReadsFailed && summaryError === null) ? failure.project.name)} partial={truncated.length > 0 || summaryError !== null} now={now} onOpen={openSession} /> : null}
@@ -435,24 +440,19 @@ function ProjectUsageTable({ rows, failed, now, onOpen }: { rows: ProjectUsageRo ); } -function sessionTitle(session: AgentSession): string | null { - const title = session.metadata?.title; - if (typeof title === "string" && title.trim()) return title; - return session.agent?.name?.trim() ? session.agent.name : null; -} - /** - * Sessions needing attention. "Nothing needs attention" is claimed only when - * every Session list was read and Core's summary agrees; otherwise the empty - * table says the Sessions could not be listed. + * Sessions needing attention in the activity window. "Nothing needs attention" + * is claimed only when every Session list was read in full; otherwise the + * empty table says the Sessions could not be listed. */ -function AttentionTable({ sessions, expected, unread, truncated, now, onOpen }: { +function AttentionTable({ sessions, older, unread, partial, now, onOpen }: { sessions: InProject[] | null; - /** Sessions needing attention by Core's summary, when it was read. */ - expected: number | null; + /** Sessions needing attention before the window, when Core's summary was read. */ + older: number | null; /** Projects whose Session list could not be read. */ unread: string[]; - truncated: boolean; + /** A read stopped at its cap, or no summary bounded it, so some Sessions may be missing. */ + partial: boolean; now: number; onOpen: (entry: InProject) => void; }) { @@ -464,12 +464,10 @@ function AttentionTable({ sessions, expected, unread, truncated, now, onOpen }: if (!sessions.length) { const description = unread.length ? t("attention.unreadDescription", { names: unread.join(", ") }) - : expected - ? t("attention.unlistedDescription", { count: expected }) - : truncated || expected === null ? t("attention.partialHelp") : null; + : partial ? t("attention.partialHelp") : null; return (
- {description ? : } + {description ? : }
); } @@ -488,15 +486,16 @@ function AttentionTable({ sessions, expected, unread, truncated, now, onOpen }: {sessions.map((entry) => { const session = entry.value; const failed = session.status === "failed"; + const waiting = failed ? null : waitingFor(session).join(" · "); return ( onOpen(entry)}> - onOpen(entry)} /> + onOpen(entry)} /> event.stopPropagation()}> -
{failed ? : waitingFor(session).join(" · ")}
+
{failed ? : waiting}
{formatRelative(session.last_active_at, now, locale)} diff --git a/apps/web/src/features/overview/overview-loader.test.ts b/apps/web/src/features/overview/overview-loader.test.ts index c9c80a131..8d0404d02 100644 --- a/apps/web/src/features/overview/overview-loader.test.ts +++ b/apps/web/src/features/overview/overview-loader.test.ts @@ -15,13 +15,13 @@ function hourly(prefix: string, count: number, overrides: (index: number) => Par } describe("needsSessionRead", () => { - it("skips projects without Sessions, or idle since before the window with nothing needing attention", () => { + it("skips projects without Sessions or idle since before the window", () => { expect(needsSessionRead(undefined, SINCE)).toBe(true); expect(needsSessionRead(summary("a"), SINCE)).toBe(false); const idle = { total: 4, idle: 4, in_progress: 0, requires_action: 0, failed: 0 }; expect(needsSessionRead(summary("a", { sessions: idle, last_active_at: SINCE - 1 }), SINCE)).toBe(false); expect(needsSessionRead(summary("a", { sessions: idle, last_active_at: SINCE + 1 }), SINCE)).toBe(true); - expect(needsSessionRead(summary("a", { sessions: { ...idle, failed: 1 }, last_active_at: SINCE - 1 }), SINCE)).toBe(true); + expect(needsSessionRead(summary("a", { sessions: { ...idle, failed: 1 }, last_active_at: SINCE - 1 }), SINCE)).toBe(false); }); }); diff --git a/apps/web/src/features/overview/overview-loader.ts b/apps/web/src/features/overview/overview-loader.ts index 5c3dffaae..ad5f1d3cc 100644 --- a/apps/web/src/features/overview/overview-loader.ts +++ b/apps/web/src/features/overview/overview-loader.ts @@ -36,15 +36,14 @@ export interface OverviewSource { /** * Whether a project's Sessions must be read at all. The summary already - * counts every Session; the list is read only for Sessions created in the - * activity window and for those needing attention. A project whose last - * activity precedes the window and that has nothing needing attention is + * counts every Session; the list is read only for Sessions active in the + * activity window. A project whose last activity precedes the window is * skipped. */ export function needsSessionRead(row: ProjectSummary | undefined, since: number): boolean { if (!row) return true; if (row.sessions.total === 0) return false; - return attentionCount(row.sessions) > 0 || (row.last_active_at ?? Number.POSITIVE_INFINITY) >= since; + return (row.last_active_at ?? Number.POSITIVE_INFINITY) >= since; } export async function loadOverview( diff --git a/apps/web/src/features/overview/overview-model.test.ts b/apps/web/src/features/overview/overview-model.test.ts index 4834d0bf1..82b77fedc 100644 --- a/apps/web/src/features/overview/overview-model.test.ts +++ b/apps/web/src/features/overview/overview-model.test.ts @@ -46,14 +46,15 @@ describe("project usage rows", () => { }); describe("Sessions needing attention", () => { - it("lists failed and waiting Sessions of every project by recent activity", () => { + it("lists failed and waiting Sessions of every project active in the window, by recent activity", () => { const a = project("a"); const b = project("b"); const listed = attentionSessions([ { project: a, value: session("s1", { status: "failed", last_active_at: 300 }) }, { project: b, value: session("s2", { status: "requires_action", last_active_at: 400 }) }, { project: b, value: session("s3", { status: "in_progress", last_active_at: 500 }) }, - ]); + { project: a, value: session("old", { status: "failed", created_at: 10, last_active_at: 299 }) }, + ], 300); expect(listed.map((entry) => [entry.project.id, entry.value.id])).toEqual([["b", "s2"], ["a", "s1"]]); }); diff --git a/apps/web/src/features/overview/overview-model.ts b/apps/web/src/features/overview/overview-model.ts index 338f58a72..04eff409e 100644 --- a/apps/web/src/features/overview/overview-model.ts +++ b/apps/web/src/features/overview/overview-model.ts @@ -55,12 +55,15 @@ function needsAttention(session: AgentSession): boolean { return session.status === "failed" || session.status === "requires_action"; } -/** Sessions that need an operator (failed or waiting for a required action), newest activity first. */ -export function attentionSessions(sessions: readonly InProject[], limit = 8): InProject[] { +/** + * Sessions that need an operator (failed or waiting for a required action) and + * were active since `since`, newest activity first. Older ones stay in the + * Session log. + */ +export function attentionSessions(sessions: readonly InProject[], since: number): InProject[] { return sessions - .filter((entry) => needsAttention(entry.value)) - .sort((a, b) => b.value.last_active_at - a.value.last_active_at || a.value.id.localeCompare(b.value.id)) - .slice(0, limit); + .filter((entry) => needsAttention(entry.value) && entry.value.last_active_at >= since) + .sort((a, b) => b.value.last_active_at - a.value.last_active_at || a.value.id.localeCompare(b.value.id)); } /** diff --git a/apps/web/src/features/overview/overview.css b/apps/web/src/features/overview/overview.css index e3be7a63d..fc36d43e7 100644 --- a/apps/web/src/features/overview/overview.css +++ b/apps/web/src/features/overview/overview.css @@ -286,7 +286,9 @@ color: var(--danger); } -.overview-attention-reason { max-width: 32ch; margin-top: 4px; color: var(--ink-2); font-size: 12px; line-height: 18px; white-space: normal; overflow-wrap: anywhere; } +/* One line per row; the full reason and next step are in the title and on the Session page. */ +.overview-attention-reason { max-width: 56ch; margin-top: 4px; overflow: hidden; color: var(--ink-2); font-size: 12px; line-height: 18px; text-overflow: ellipsis; white-space: nowrap; } +.overview-attention-reason .session-status-reason { padding-left: 0; white-space: nowrap; } /* Tables inside cards ----------------------------------------------------- */ diff --git a/apps/web/src/features/sessions/SessionLogPage.tsx b/apps/web/src/features/sessions/SessionLogPage.tsx index f4ecc097e..71416af9d 100644 --- a/apps/web/src/features/sessions/SessionLogPage.tsx +++ b/apps/web/src/features/sessions/SessionLogPage.tsx @@ -9,6 +9,7 @@ import { EmptyState, PageBody, PageHeader, RefreshButton, SegmentedControl } fro import { ListToolbar, listSummary, NameCell, RowActions, SearchField } from "../../components/list-ui"; import { useConsoleIntent, useConsoleNavigation } from "../../lib/console-navigation"; import { formatClock, formatCompact, formatDateTime, formatInteger, formatRelative, MISSING } from "../../lib/format"; +import { sessionTitle } from "../../lib/session-title"; import { CreatorCell, CreatorHeading, ProjectFilter, ProjectName, useCreators, useProjectCollection, useProjects, type Creators, type Owned, type ProjectFilterValue } from "../../lib/projects"; import { SessionDeleteDialog, type SessionDeleteTarget } from "./SessionDeleteDialog"; import { SessionStatus } from "./SessionStatus"; @@ -240,13 +241,14 @@ function SessionLogRow({ onDelete: (target: { project: AdminProject; sessionId: string }) => void; }) { const { t } = useTranslation("sessions"); + const { t: tCommon } = useTranslation("common"); const projectCell = allProjects ? : null; const session = row.value; const open = () => onOpen(row.project.id, session.id); return ( - + {projectCell} event.stopPropagation()}> diff --git a/apps/web/src/features/sessions/SessionPage.tsx b/apps/web/src/features/sessions/SessionPage.tsx index 2ffefe85a..b23ff8ed8 100644 --- a/apps/web/src/features/sessions/SessionPage.tsx +++ b/apps/web/src/features/sessions/SessionPage.tsx @@ -1,4 +1,3 @@ -import type { AgentSession } from "@oac/agents-client"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { ArrowLeft, Trash2 } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; @@ -12,6 +11,7 @@ import { useConsoleNavigation } from "../../lib/console-navigation"; import { formatClock, formatDateTime, formatInteger, MISSING } from "../../lib/format"; import { CreatorCell, ProjectName, useCreators, useProjects } from "../../lib/projects"; import { collectionQuery, collections } from "../../lib/queries"; +import { sessionTitle } from "../../lib/session-title"; import { forgetDeleted } from "../resources/detail-queries"; import { ExecutorCredentialsSection } from "./ExecutorCredentialsSection"; import { SessionDeleteDialog, type SessionDeleteTarget } from "./SessionDeleteDialog"; @@ -30,10 +30,6 @@ import "./sessions.css"; type HistoryView = "conversation" | "trace" | "turns"; -function sessionTitle(session: AgentSession, fallback: string): string { - return session.metadata.title?.trim() || session.agent.name?.trim() || fallback; -} - function errorText(error: unknown): string { return error instanceof Error ? error.message : String(error ?? ""); } @@ -46,6 +42,7 @@ function errorText(error: unknown): string { */ export function SessionPage() { const { t, i18n } = useTranslation("sessions"); + const { t: tCommon } = useTranslation("common"); const locale = i18n.resolvedLanguage; const { params, navigate, back: goBack } = useConsoleNavigation(); const projectId = params.project; @@ -232,7 +229,7 @@ export function SessionPage() { ); } - const title = session ? sessionTitle(session, t("common.untitledAgent")) : t("common.session"); + const title = session ? sessionTitle(session) ?? tCommon("untitledSession") : t("common.session"); return (
{ void i18n.changeLanguage("en"); }); -function render(kind: "session" | "turn", data: SessionDiagnostics | TurnDiagnostics, error = false) { +function render(kind: "session" | "turn", data: SessionDiagnostics | TurnDiagnostics, error = false, truncate = false) { const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const key = kind === "session" ? sessionDiagnosticsQuery(scope.projectId, session).queryKey : turnDiagnosticsQuery(scope, turn).queryKey; client.setQueryData(key, data); if (error) client.getQueryCache().find({ queryKey: key })!.setState({ status: "error", error: new Error("PRIVATE_READ_SENTINEL") }); const html = renderToStaticMarkup( - {kind === "session" ? : } + {kind === "session" ? : } ); client.clear(); return html; @@ -31,8 +31,8 @@ function render(kind: "session" | "turn", data: SessionDiagnostics | TurnDiagnos describe("Core diagnostics in the console", () => { it("renders safe classified Session and root Turn failures in both languages", async () => { - expect(render("session", sessionData)).toContain("Check its API key"); - expect(render("turn", turnData)).toContain("Check its API key"); + expect(render("session", sessionData)).toContain("API key in Settings → Models and credentials"); + expect(render("turn", turnData)).toContain("API key in Settings → Models and credentials"); await i18n.changeLanguage("zh-CN"); expect(render("session", sessionData)).toContain("模型服务认证失败"); expect(render("turn", turnData)).not.toContain("PRIVATE_NATIVE_SENTINEL"); @@ -40,7 +40,7 @@ describe("Core diagnostics in the console", () => { it("withholds old causes after a failed read or mismatched status", () => { expect(render("session", sessionData, true)).toContain("Failure details unavailable"); - expect(render("turn", { ...turnData, status: "completed", failure: null })).not.toContain("Check its API key"); + expect(render("turn", { ...turnData, status: "completed", failure: null })).not.toContain("rejected authentication"); expect(render("turn", turnData, true)).not.toContain("PRIVATE_READ_SENTINEL"); }); @@ -61,6 +61,18 @@ describe("Core diagnostics in the console", () => { expect(diagnosticFailure({ code: "connection_failed", params: { http_status: 429 }, failed_at: null }, t)).toContain("HTTP 429"); }); + it("names a model rejection with its next step, and keeps the unclassified cause for unclassified failures", async () => { + const t = i18n.getFixedT("en", "diagnostics"); + expect(diagnosticFailure({ code: "invalid_request", params: {}, failed_at: null }, t)).toBe("The model provider rejected the request. Check the model ID on the Agent or in Settings → Models and credentials."); + expect(diagnosticFailure({ code: "connection_failed", params: { http_status: 502 }, failed_at: null }, t)).toBe("The connection to the model provider failed. HTTP 502. Check the base URL in Settings → Models and credentials."); + expect(diagnosticFailure({ code: "harness_error", params: {}, failed_at: null }, t)).toContain("Core does not show the Harness's own message"); + expect(diagnosticFailure({ code: "invalid_request", params: {}, failed_at: null }, i18n.getFixedT("zh-CN", "diagnostics"))).toBe("模型服务拒绝了请求。请检查 Agent 或“设置 → 模型与凭据”中的模型 ID。"); + // Lists show the reason alone; the next step stays in the title. + const row = render("session", { ...sessionData, failure: { ...sessionData.failure!, code: "invalid_request" } }, false, true); + expect(row).toContain(">The model provider rejected the request.<"); + expect(row).toContain('title="The model provider rejected the request. Check the model ID'); + }); + it("keeps call/result receipt intervals distinct and historical settlement missing", () => { const client = new QueryClient(); client.setQueryData(turnDiagnosticsQuery(scope, turn).queryKey, { ...turnData, items: [ diff --git a/apps/web/src/features/sessions/session-diagnostics.tsx b/apps/web/src/features/sessions/session-diagnostics.tsx index d880c297c..cfd9b9ac5 100644 --- a/apps/web/src/features/sessions/session-diagnostics.tsx +++ b/apps/web/src/features/sessions/session-diagnostics.tsx @@ -4,7 +4,7 @@ import { createContext, useContext } from "react"; import { useTranslation } from "react-i18next"; import { HelpTip } from "../../components/console-ui"; -import { diagnosticFailure } from "../../lib/diagnostic-failure"; +import { diagnosticFailure, diagnosticReason } from "../../lib/diagnostic-failure"; import { admin } from "../../lib/projects"; import { sessionKey } from "./session-queries"; @@ -39,14 +39,17 @@ export function DiagnosticsUnavailable({ pending, retry }: { pending: boolean; r ; } -/** Mounted for failed rows on the current page only; no fan-out over hidden Sessions. */ +/** + * Mounted for failed rows on the current page only; no fan-out over hidden + * Sessions. Lists show the reason alone and keep its next step in the title. + */ export function SessionFailure({ projectId, session, truncate }: { projectId: string; session: AgentSession; truncate: boolean }) { const { t } = useTranslation("diagnostics"); const query = useQuery(sessionDiagnosticsQuery(projectId, session)); const failure = !query.isError && query.data?.status === session.status ? query.data.failure : null; - const reason = failure ? diagnosticFailure(failure, t) : null; - return - {reason ?? void query.refetch()} />} + const full = failure ? diagnosticFailure(failure, t) : null; + return + {failure ? (truncate ? diagnosticReason(failure, t) : full) : void query.refetch()} />} ; } diff --git a/apps/web/src/i18n/locales/en/common.ts b/apps/web/src/i18n/locales/en/common.ts index 8fcb5f959..12698b620 100644 --- a/apps/web/src/i18n/locales/en/common.ts +++ b/apps/web/src/i18n/locales/en/common.ts @@ -53,6 +53,7 @@ export const common = { partial: "Could not load {{names}}. Other projects are shown.", failed: "Could not load {{names}}.", }, + untitledSession: "Untitled Session", creator: { column: "Creator", help: "The API key that created this asset, as recorded by Core for every write.", diff --git a/apps/web/src/i18n/locales/en/diagnostics.ts b/apps/web/src/i18n/locales/en/diagnostics.ts index 03d741940..26de5a030 100644 --- a/apps/web/src/i18n/locales/en/diagnostics.ts +++ b/apps/web/src/i18n/locales/en/diagnostics.ts @@ -10,24 +10,24 @@ export const diagnostics = { providerHelp: "Core records outcomes from hosted Turns using this deployment default. Records are best effort and may lag; missing records do not prove the provider is ready or unused. Replacing the default starts a new observation history.", providerStaleHelp: "The latest read failed. These are the last confirmed observations.", failure: { - authentication_error: "The model provider rejected authentication. Check its API key.", + authentication_error: "The model provider rejected authentication.", rate_limit_exceeded: "The model provider's rate limit was reached.", - usage_limit_exceeded: "The model provider's usage limit was reached. Check its quota.", + usage_limit_exceeded: "The model provider's usage limit was reached.", server_overloaded: "The model provider is overloaded.", server_error: "The model provider reported a server error.", - invalid_request: "The model provider rejected the request. Check the model and input.", - resource_not_found: "The model provider could not find the requested resource. Check the model.", + invalid_request: "The model provider rejected the request.", + resource_not_found: "The model provider could not find the model.", request_timeout: "The model provider request timed out.", context_length_exceeded: "The input exceeds the model's context limit.", cyber_policy: "The model provider rejected the input under its safety policy.", connection_failed: "The connection to the model provider failed.", - harness_error: "The harness could not complete execution. No specific cause was reported.", + harness_error: "The Harness failed without a cause Core recognizes.", model_provider_required: "No model provider is configured for this Session.", - runtime_unavailable: "The Runtime is unavailable. Check the execution host.", + runtime_unavailable: "The Runtime is unavailable.", runtime_disconnected: "The Runtime connection was lost during execution.", runtime_preparation_failed: "The Runtime could not prepare execution.", execution_interrupted: "Execution was interrupted by Core recovery.", - delivery_unconfirmed: "Core could not confirm delivery. Check the result before sending it again.", + delivery_unconfirmed: "Core could not confirm delivery.", input_rejected: "The executor did not accept the input.", executor_protocol_error: "The executor returned an unsupported or invalid response.", core_storage_failed: "Core could not persist execution data.", @@ -36,6 +36,25 @@ export const diagnostics = { environment_unavailable: "The Session's environment is unavailable.", environment_provisioning_failed: "The environment could not be prepared.", }, + next: { + authentication_error: "Check the model service's API key in Settings → Models and credentials.", + rate_limit_exceeded: "Retry later, or raise the limit with the provider.", + usage_limit_exceeded: "Check the account's quota with the provider.", + server_overloaded: "Retry later.", + server_error: "Retry later.", + invalid_request: "Check the model ID on the Agent or in Settings → Models and credentials.", + resource_not_found: "Check the model ID on the Agent or in Settings → Models and credentials.", + request_timeout: "Retry later.", + context_length_exceeded: "Start a new Session or shorten the input.", + connection_failed: "Check the base URL in Settings → Models and credentials.", + harness_error: "Core does not show the Harness's own message. Check the model ID and API key on the Agent or in Settings → Models and credentials.", + model_provider_required: "Set a default model configuration in Settings → Models and credentials.", + runtime_unavailable: "Check the node on the Nodes page.", + runtime_disconnected: "Check the node on the Nodes page.", + delivery_unconfirmed: "Check the result before sending it again.", + }, + /** Between sentences of one reason. */ + sentenceGap: " ", provisioningStep: "Environment preparation failed at {{step}}.", setupIndex: "Setup command {{number}}.", exitCode: "Exit code {{code}}.", diff --git a/apps/web/src/i18n/locales/en/metrics.ts b/apps/web/src/i18n/locales/en/metrics.ts index cb00d33dd..27574c56e 100644 --- a/apps/web/src/i18n/locales/en/metrics.ts +++ b/apps/web/src/i18n/locales/en/metrics.ts @@ -284,7 +284,6 @@ export const metrics = { uptime: "Uptime", tokens: "Tokens", tokenSplit: "Input {{input}} · output {{output}}", - untitled: "Untitled Session", recordedCompute: "Recorded compute", recordedComputeHelp: "Core’s recorded compute phase, read separately from the Runtime observation. Refreshes can briefly differ.", lifecycle: { diff --git a/apps/web/src/i18n/locales/en/overview.ts b/apps/web/src/i18n/locales/en/overview.ts index 069569469..bce836f97 100644 --- a/apps/web/src/i18n/locales/en/overview.ts +++ b/apps/web/src/i18n/locales/en/overview.ts @@ -78,7 +78,7 @@ export const overview = { nodesOnline: "{{online}} / {{total}} nodes online", nodesOnlineSuspended: "{{online}} / {{total}} nodes online · {{suspended}} suspended", cloudPending: "{{count}} starting or changing", - attentionSplit: "{{failed}} failed, {{waiting}} waiting for caller", + attentionSplit: "Last 24 hours: {{failed}} failed, {{waiting}} waiting for caller", }, errors: { load: "The overview could not be loaded: {{reason}}", @@ -194,21 +194,20 @@ export const overview = { attention: { unlistedTitle: "Sessions needing attention could not be listed", unreadDescription: "The Session lists of {{names}} could not be read. Refresh, or open the Session log.", - unlistedDescription: "Core counts {{count}} Sessions that need attention, but they were not among the Sessions read. Open the Session log to see them.", title: "Needs attention", - subtitle: "Failed Sessions and Sessions waiting for their caller, across every project. Applications submit function results; the console cannot submit them.", + subtitle: "Failed Sessions and Sessions waiting for their caller, active in the last 24 hours across every project. Applications submit function results; the console cannot submit them.", viewLog: "Session log", shown: "{{shown}} of {{total}}", limitHelp: "The {{count}} most recently active are shown. The Session log lists the rest.", partialHelp: "Only the newest Sessions of each project were read, so some Sessions that need attention are not listed. The Session log lists them all.", loading: "Loading Sessions…", emptyTitle: "Nothing needs attention", - emptyDescription: "No Session has failed or is waiting for a required action.", + emptyDescription: "No Session active in the last 24 hours failed or is waiting for its caller.", + emptyOlder: "No Session active in the last 24 hours failed or is waiting for its caller. {{count}} older Sessions need attention in the Session log.", emptyOlder_one: "No Session active in the last 24 hours failed or is waiting for its caller. {{count}} older Session needs attention in the Session log.", emptyOlder_other: "No Session active in the last 24 hours failed or is waiting for its caller. {{count}} older Sessions need attention in the Session log.", session: "Session", status: "Status", reason: "Reason", lastActive: "Last active", - untitled: "Untitled Session", noError: "No error reported", requiredActions: "Required actions: {{count}}", }, diff --git a/apps/web/src/i18n/locales/zh-CN/common.ts b/apps/web/src/i18n/locales/zh-CN/common.ts index dab3bcdf6..19b551883 100644 --- a/apps/web/src/i18n/locales/zh-CN/common.ts +++ b/apps/web/src/i18n/locales/zh-CN/common.ts @@ -53,6 +53,7 @@ export const common = { partial: "无法加载 {{names}} 的数据,其余项目照常显示。", failed: "无法加载 {{names}} 的数据。", }, + untitledSession: "未命名 Session", creator: { column: "创建者", help: "创建这个资产的 API 密钥,由 Core 在每次写入时记录。", diff --git a/apps/web/src/i18n/locales/zh-CN/diagnostics.ts b/apps/web/src/i18n/locales/zh-CN/diagnostics.ts index e0a5e1769..ddbf34286 100644 --- a/apps/web/src/i18n/locales/zh-CN/diagnostics.ts +++ b/apps/web/src/i18n/locales/zh-CN/diagnostics.ts @@ -10,24 +10,24 @@ export const diagnostics = { providerHelp: "Core 记录使用此部署默认配置的托管 Turn 结果。记录可能延迟或缺失;暂无记录不代表服务已就绪或从未使用。替换默认配置后会重新记录。", providerStaleHelp: "最新读取失败,当前显示上次确认的记录。", failure: { - authentication_error: "模型服务认证失败,请检查 API key。", + authentication_error: "模型服务认证失败。", rate_limit_exceeded: "已达到模型服务的请求频率上限。", - usage_limit_exceeded: "已达到模型服务的用量上限,请检查额度。", + usage_limit_exceeded: "已达到模型服务的用量上限。", server_overloaded: "模型服务当前负载过高。", server_error: "模型服务发生了服务器错误。", - invalid_request: "模型服务拒绝了请求,请检查模型和输入。", - resource_not_found: "模型服务未找到请求的资源,请检查模型。", + invalid_request: "模型服务拒绝了请求。", + resource_not_found: "模型服务找不到该模型。", request_timeout: "模型服务请求超时。", context_length_exceeded: "输入超出模型的上下文上限。", cyber_policy: "模型服务因安全策略拒绝了输入。", connection_failed: "与模型服务的连接失败。", - harness_error: "Harness 未能完成执行,未报告具体原因。", + harness_error: "Harness 执行失败,Core 无法识别原因。", model_provider_required: "此 Session 未配置模型服务。", - runtime_unavailable: "Runtime 不可用,请检查执行主机。", + runtime_unavailable: "Runtime 不可用。", runtime_disconnected: "执行过程中 Runtime 连接中断。", runtime_preparation_failed: "Runtime 未能完成执行准备。", execution_interrupted: "Core 恢复过程中执行被中断。", - delivery_unconfirmed: "Core 无法确认是否送达,请先检查结果再决定是否重发。", + delivery_unconfirmed: "Core 无法确认是否送达。", input_rejected: "执行器未接受输入。", executor_protocol_error: "执行器返回了不支持或无效的响应。", core_storage_failed: "Core 未能保存执行数据。", @@ -36,6 +36,25 @@ export const diagnostics = { environment_unavailable: "此 Session 的环境不可用。", environment_provisioning_failed: "环境准备失败。", }, + next: { + authentication_error: "请在“设置 → 模型与凭据”中检查模型服务的 API 密钥。", + rate_limit_exceeded: "请稍后重试,或向模型服务申请提高限额。", + usage_limit_exceeded: "请在模型服务中检查账户额度。", + server_overloaded: "请稍后重试。", + server_error: "请稍后重试。", + invalid_request: "请检查 Agent 或“设置 → 模型与凭据”中的模型 ID。", + resource_not_found: "请检查 Agent 或“设置 → 模型与凭据”中的模型 ID。", + request_timeout: "请稍后重试。", + context_length_exceeded: "请新建 Session 或缩短输入。", + connection_failed: "请检查“设置 → 模型与凭据”中的 Base URL。", + harness_error: "Core 不显示 Harness 的原始消息。请检查 Agent 或“设置 → 模型与凭据”中的模型 ID 和 API 密钥。", + model_provider_required: "请在“设置 → 模型与凭据”中设置默认模型配置。", + runtime_unavailable: "请在“节点”页检查节点。", + runtime_disconnected: "请在“节点”页检查节点。", + delivery_unconfirmed: "请先检查结果,再决定是否重新发送。", + }, + /** Between sentences of one reason. */ + sentenceGap: "", provisioningStep: "环境准备在{{step}}阶段失败。", setupIndex: "第 {{number}} 条设置命令。", exitCode: "退出码 {{code}}。", diff --git a/apps/web/src/i18n/locales/zh-CN/metrics.ts b/apps/web/src/i18n/locales/zh-CN/metrics.ts index 94f914ad6..8b47d06d0 100644 --- a/apps/web/src/i18n/locales/zh-CN/metrics.ts +++ b/apps/web/src/i18n/locales/zh-CN/metrics.ts @@ -284,7 +284,6 @@ export const metrics = { uptime: "运行时长", tokens: "Token", tokenSplit: "输入 {{input}} · 输出 {{output}}", - untitled: "未命名 Session", recordedCompute: "记录的计算状态", recordedComputeHelp: "Core 记录的计算阶段,与 Runtime 观测分别读取,刷新时可能短暂不同。", lifecycle: { diff --git a/apps/web/src/i18n/locales/zh-CN/overview.ts b/apps/web/src/i18n/locales/zh-CN/overview.ts index e7cf99936..2f5eed558 100644 --- a/apps/web/src/i18n/locales/zh-CN/overview.ts +++ b/apps/web/src/i18n/locales/zh-CN/overview.ts @@ -78,7 +78,7 @@ export const overview = { nodesOnline: "{{online}} / {{total}} 个节点在线", nodesOnlineSuspended: "{{online}} / {{total}} 个节点在线 · {{suspended}} 个挂起", cloudPending: "{{count}} 个启动或切换中", - attentionSplit: "失败 {{failed}},等待调用方 {{waiting}}", + attentionSplit: "过去 24 小时:失败 {{failed}},等待调用方 {{waiting}}", }, errors: { load: "无法加载概览:{{reason}}", @@ -194,21 +194,20 @@ export const overview = { attention: { unlistedTitle: "无法列出需要处理的 Session", unreadDescription: "{{names}} 的 Session 列表读取失败。请刷新,或打开 Session 日志查看。", - unlistedDescription: "Core 统计有 {{count}} 个 Session 需要处理,但不在已读取的 Session 中。请打开 Session 日志查看。", title: "需要处理", - subtitle: "所有项目中失败或等待调用方的 Session。函数结果须由应用提交,控制台无法代为提交。", + subtitle: "过去 24 小时内所有项目中失败或等待调用方的 Session。函数结果须由应用提交,控制台无法代为提交。", viewLog: "Session 日志", shown: "{{shown}} / {{total}}", limitHelp: "只显示最近活跃的 {{count}} 个,其余请在 Session 日志中查看。", partialHelp: "每个项目只读取了最近创建的 Session,部分需要处理的 Session 没有列出。Session 日志中可以看到全部。", loading: "正在加载 Session…", - emptyTitle: "暂无需要处理的 Session", - emptyDescription: "没有失败或等待必要操作的 Session。", + emptyTitle: "没有需要处理的 Session", + emptyDescription: "过去 24 小时内活跃的 Session 都没有失败或等待调用方。", + emptyOlder: "过去 24 小时内活跃的 Session 都没有失败或等待调用方。Session 日志中还有 {{count}} 个更早的 Session 需要处理。", emptyOlder_one: "过去 24 小时内活跃的 Session 都没有失败或等待调用方。Session 日志中还有 {{count}} 个更早的 Session 需要处理。", emptyOlder_other: "过去 24 小时内活跃的 Session 都没有失败或等待调用方。Session 日志中还有 {{count}} 个更早的 Session 需要处理。", session: "Session", status: "状态", reason: "原因", lastActive: "最近活跃", - untitled: "未命名 Session", noError: "未上报错误信息", requiredActions: "待完成操作:{{count}}", }, diff --git a/apps/web/src/lib/diagnostic-failure.ts b/apps/web/src/lib/diagnostic-failure.ts index 9921c7f5a..ab13edbf6 100644 --- a/apps/web/src/lib/diagnostic-failure.ts +++ b/apps/web/src/lib/diagnostic-failure.ts @@ -2,15 +2,23 @@ import type { DiagnosticFailure } from "@oac/agents-client"; import type { TFunction } from "i18next"; /** Translate the catalog, never native error text or guessed message categories. */ -export function diagnosticFailure(failure: DiagnosticFailure, t: TFunction<"diagnostics">): string { +export function diagnosticReason(failure: DiagnosticFailure, t: TFunction<"diagnostics">): string { + const gap = t("sentenceGap"); let message: string = t(`failure.${failure.code}`, { defaultValue: t("failure.internal_error") }); if (failure.code === "environment_provisioning_failed" && "step" in failure.params && failure.params.step) { message = t("provisioningStep", { step: t(`step.${failure.params.step}`) }); - if (failure.params.index !== null) message += ` ${t("setupIndex", { number: failure.params.index + 1 })}`; - if (failure.params.exit_code !== null) message += ` ${t("exitCode", { code: failure.params.exit_code })}`; + if (failure.params.index !== null) message += gap + t("setupIndex", { number: failure.params.index + 1 }); + if (failure.params.exit_code !== null) message += gap + t("exitCode", { code: failure.params.exit_code }); } if (failure.code === "connection_failed" && "http_status" in failure.params && failure.params.http_status !== null) { - message += ` ${t("httpStatus", { status: failure.params.http_status })}`; + message += gap + t("httpStatus", { status: failure.params.http_status }); } return message; } + +/** The classified reason, then its next step when the catalog has one. */ +export function diagnosticFailure(failure: DiagnosticFailure, t: TFunction<"diagnostics">): string { + const reason = diagnosticReason(failure, t); + const next: string = t(`next.${failure.code}`, { defaultValue: "" }); + return next ? reason + t("sentenceGap") + next : reason; +} diff --git a/apps/web/src/lib/session-title.ts b/apps/web/src/lib/session-title.ts new file mode 100644 index 000000000..2fc7b832e --- /dev/null +++ b/apps/web/src/lib/session-title.ts @@ -0,0 +1,11 @@ +import type { AgentSession } from "@oac/agents-client"; + +/** + * A Session's name wherever Sessions are listed: its metadata title, else its + * Agent's name. Null means untitled; callers show `common:untitledSession`. + */ +export function sessionTitle(session: AgentSession | null): string | null { + const title = session?.metadata?.title; + if (typeof title === "string" && title.trim()) return title; + return session?.agent?.name?.trim() ? session.agent.name : null; +} diff --git a/docs/web/console-api-usage.md b/docs/web/console-api-usage.md index 06651e795..81ed11660 100644 --- a/docs/web/console-api-usage.md +++ b/docs/web/console-api-usage.md @@ -50,7 +50,7 @@ Resource-specific rules: - **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. +- **Diagnostics.** The console translates Core's classified reason and never infers a cause from raw logs. The Session page adds the reason's next step, such as checking the model ID on the Agent or in Settings → Models and credentials; lists show the reason alone, with the next step in its tooltip. Core returns no Harness or provider message, so an unclassified failure says so. An unavailable or mismatched diagnostic offers an explicit read retry; a retry never replays execution. ## Executor credentials and host connection @@ -132,7 +132,7 @@ The console assembles several figures in the browser from bounded reads of each | Resource lists | Every page of the selected project, or of every project in parallel | 10,000 entries per project; a failed project is named and the rest still show | | Session log | Every Session page of the selected projects, newest first | 10,000 per project; refreshed on request | | Session page | Session, Items and Turns | 10,000 Items and Turns; polled every 5 s while the Session is in progress or waiting, backing off to 60 s on failures | -| Overview | Summary; Session lists for the 24-hour activity and the Sessions needing attention | 1,000 Sessions per project; idle projects are skipped; refreshed every 30 s while visible. Failed project or Session reads show Retry instead of a synthesized empty result; any retained or partial data is visibly qualified | +| Overview | Summary; Session lists for the 24-hour activity and the Sessions needing attention that were active in the same 24 hours | 1,000 Sessions per project; projects idle for the whole window are skipped; refreshed every 30 s while visible. Failed project or Session reads show Retry instead of a synthesized empty result; any retained or partial data is visibly qualified | | Agent metrics | Summary; Session lists; Turns and Items of the most recently active Sessions | 2,000 Sessions listed per project; 200 Sessions read per load, 10 Turn and 5 Item pages each, 15 s per Session and 45 s per load | | Sandbox metrics | Nodes and allocations; Runtime observations; hosted Sessions by ID; Runtime history | 100 hosted Sessions read per refresh; history for at most 24; refreshed every 30 s while visible | diff --git a/docs/web/index.md b/docs/web/index.md index 2fc4aac7c..7a3399330 100644 --- a/docs/web/index.md +++ b/docs/web/index.md @@ -16,12 +16,12 @@ Signing in opens the Overview. While any step is still to do, its **Getting star | 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 | 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 in the last 24 hours, each with its one-line reason; usage by Project | | 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; **Automatic suspension** when supported; **Recorded compute** lifecycle state shown separately from hosted Runtime CPU and memory observations across Projects | | Monitor | Request log | Recent HTTP requests to Core, their status, elapsed time and safe error codes; [scope and retention](../../contracts/agents-api/admin-api.md#request-log) | -| 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 | +| Monitor | Session log | Every Session, and each Session's read-only conversation, trace and Turns with the classified reason of a failure and its next step; 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 | Inspect and remove microsandbox or Docker nodes; each node's readiness, capacity and allocations, with the automatic suspension policy when supported, active and retained slots, and elapsed time in each compute phase | diff --git a/docs/zh/web/console-api-usage.md b/docs/zh/web/console-api-usage.md index e5f9a3657..48adbb1d4 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: e96e3146dd30a88416e920f66b4071d217baedf3471e3d6f9334085645191587 +source_hash: f4fb9af958ffb16a4bff1f8d9c369c5df0bcf68c2e4a7072a0cead694b882307 --- 本页列出各控制台页面读取和写入的 Core 路由,以及控制台如何限定读取范围。[administrator API contract](../../../contracts/agents-api/zh/admin-api.md) 定义了路由、响应结构、分页和审计记录;[API namespaces and credentials](../api/index.md) 定义了本文使用的术语。 @@ -52,7 +52,7 @@ source_hash: e96e3146dd30a88416e920f66b4071d217baedf3471e3d6f9334085645191587 - **Files.** 列表每页读取 100 条,可按最新或最早顺序排列。管理员 API 没有 File 内容路由,因此控制台不提供下载。 - **Vaults.** Credential 令牌绝不返回。控制台显示每个 Credential 的名称、MCP 服务器 URL、身份验证类型和更新时间。 - **Sessions.** 如果某个 Session 格式错误,其所属项目的读取会失败,而不会跳过该 Session。 -- **Diagnostics.** 控制台会翻译 Core 分类的原因,绝不从原始日志推断原因。诊断信息不可用或不匹配时,会提供显式的读取重试;重试绝不会重新执行操作。 +- **Diagnostics.** 控制台会翻译 Core 分类的原因,绝不从原始日志推断原因。Session 页面会补充该原因的下一步操作,例如检查 Agent 或“设置 → 模型与凭据”中的模型 ID;列表只显示原因,下一步操作放在悬停提示中。Core 不返回 Harness 或模型服务的原始消息,因此无法分类的失败会如实说明。诊断信息不可用或不匹配时,会提供显式的读取重试;重试绝不会重新执行操作。 ## 执行器凭据和主机连接 {#executor-credentials-and-host-connection} @@ -134,7 +134,7 @@ E2B 部署没有节点;其 API 密钥为只写。Overview 和 Sandbox metrics | 资源列表 | 所选项目的所有页面,或并行读取所有项目的所有页面 | 每个项目最多 10,000 个条目;读取失败的项目会被指明,其他项目仍会显示 | | Session log | 所选项目的所有 Session 页面,按最新优先 | 每个项目最多 10,000 个;按请求刷新 | | Session 页面 | Session、Items 和 Turns | 最多 10,000 个 Items 和 Turns;Session 进行中或等待时每 5 秒轮询一次,失败后退避至 60 秒 | -| Overview | 汇总;24 小时活动和需要关注的 Sessions 所对应的 Session 列表 | 每个项目最多 1,000 个 Sessions;跳过空闲项目;可见时每 30 秒刷新一次。项目或 Session 读取失败时显示 Retry,而不是合成空结果;任何保留的数据或不完整数据都会附带明确的限定说明 | +| Overview | 汇总;24 小时活动及同一 24 小时内活跃且需要处理的 Sessions 所对应的 Session 列表 | 每个项目最多 1,000 个 Sessions;跳过整个时段内都空闲的项目;可见时每 30 秒刷新一次。项目或 Session 读取失败时显示 Retry,而不是合成空结果;任何保留的数据或不完整数据都会附带明确的限定说明 | | Agent 指标 | 汇总;Session 列表;最近活动的 Sessions 的 Turns 和 Items | 每个项目列出最多 2,000 个 Sessions;每次加载读取 200 个 Sessions,每个 Session 读取 10 个 Turn 页面和 5 个 Item 页面;每个 Session 为 15 秒,每次加载为 45 秒 | | Sandbox metrics | Nodes 和 allocations;Runtime observations;按 ID 标识的托管 Sessions;Runtime 历史记录 | 每次刷新读取 100 个托管 Sessions;最多读取 24 个 Session 的历史记录;可见时每 30 秒刷新一次 | diff --git a/docs/zh/web/index.md b/docs/zh/web/index.md index b0daef4bd..4e7ef0fdf 100644 --- a/docs/zh/web/index.md +++ b/docs/zh/web/index.md @@ -1,7 +1,7 @@ --- title: "OpenAgentCore Web" source: docs/web/index.md -source_hash: d855a260ed8979564ee94e18e3261611baba479f0406e8ca9424d69fb754f2e8 +source_hash: 69608c288990ae3bf383510b253ef344810bee095ff574464cd3fd6a6ca04669 --- Web 是单个 OpenAgentCore 部署的管理员控制台。管理员用它查看健康状态、容量、用量和失败情况,检查各 Project 的资源与执行历史,并管理 Project、密钥、节点和部署设置。应用不使用 Web;它们通过自己的 Project API 密钥调用 Core 的 Agents API(`/v1`)。 @@ -18,12 +18,12 @@ Web 是单个 OpenAgentCore 部署的管理员控制台。管理员用它查看 | 分组 | 页面 | 用途 | | --- | --- | --- | -| Monitor | Overview | 服务状态、运行中的 Session、沙箱槽位和待处理工作;24 小时 Session 活动;以拓扑展示 Core 与节点,各自提供弹出概览;需要关注的 Session;按 Project 统计的用量 | +| Monitor | Overview | 服务状态、运行中的 Session、沙箱槽位和待处理工作;24 小时 Session 活动;以拓扑展示 Core 与节点,各自提供弹出概览;过去 24 小时内需要处理的 Session,每行附一行原因;按 Project 统计的用量 | | Monitor | Core metrics | Core 进程:CPU 与常驻内存及其限制、执行槽位与 Turn 队列、已连接守护进程、数据库延迟与连接池、后台任务 | | Monitor | Agent metrics | 1 小时、6 小时、24 小时或 7 天的请求、错误、耗时、token、模型、工具、Agent 和 API 密钥 | | Monitor | Sandbox metrics | 节点容量;支持时显示 **Automatic suspension**;**Recorded compute** 生命周期状态与各 Project 的托管 Runtime CPU、内存观测分开展示 | | Monitor | Request log | 到达 Core 的近期 HTTP 请求、状态、耗时和安全错误码;[范围及保留方式](../../../contracts/agents-api/zh/admin-api.md#request-log) | -| Monitor | Session log | 全部 Session;各 Session 的只读对话、跟踪、Turn 和失败分类原因;自托管 Session 页面还管理执行器凭据并提供主机连接命令 | +| Monitor | Session log | 全部 Session;各 Session 的只读对话、跟踪、Turn、失败分类原因及下一步操作;自托管 Session 页面还管理执行器凭据并提供主机连接命令 | | Resources | Agents、Environment templates、Skills、Files、Vaults | 检查和允许的删除操作,显示各资源所属 Project 及创建密钥 | | Platform | Projects and keys | 创建、重命名和归档 Project;签发与撤销密钥;各 Project 的用量、写入历史和 API 调用方法 | | Platform | Nodes | 查看和移除 microsandbox 或 Docker 节点;各节点的就绪状态、容量和分配,以及支持时的自动挂起策略、活跃与保留名额、各计算阶段的已用时间 | From 48d850c74001beefd4c528f7fca767551323baf9 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 14 Oct 2026 06:56:40 +0000 Subject: [PATCH 2/3] Give failed Sessions next steps for every Session kind --- apps/web/PRODUCT.md | 4 +- apps/web/e2e/diagnostics.spec.ts | 2 +- apps/web/e2e/overview-readiness.spec.ts | 3 ++ .../src/features/overview/OverviewPage.tsx | 52 ++++++++++++------- .../sessions/session-diagnostics.test.tsx | 16 +++--- .../features/system/ProviderObservations.tsx | 4 +- apps/web/src/i18n/locales/en/diagnostics.ts | 17 +++--- apps/web/src/i18n/locales/en/overview.ts | 2 +- .../web/src/i18n/locales/zh-CN/diagnostics.ts | 17 +++--- apps/web/src/i18n/locales/zh-CN/overview.ts | 2 +- docs/web/console-api-usage.md | 4 +- docs/web/index.md | 2 +- docs/zh/web/console-api-usage.md | 6 +-- docs/zh/web/index.md | 4 +- 14 files changed, 78 insertions(+), 57 deletions(-) diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index 82c39f76d..1f3b5c4b5 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -29,7 +29,7 @@ The console runs beside the administrator's own Core, with execution, files and ## Information Architecture -- **Monitor**: Overview (service status, running Sessions, sandbox slots, Sessions needing attention in the last 24 hours, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, with a popover glance at each, the attention table, whose rows name each Session, its project, its one-line reason and last activity and open it, with older ones left to the Session log, usage by project), Core metrics (the Core process's CPU and memory, execution slots and the Turn queue, connected daemons, the database and background jobs), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (node capacity and hosted Runtimes across projects; a node or a sandbox opens in a dialog with its figures and CPU and memory charts), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log. +- **Monitor**: Overview (service status, running Sessions, sandbox slots, the Needs attention count for the last 24 hours, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, with a popover glance at each, the Needs attention table, whose rows name each Session, its project, its one-line reason and last activity and open it, with older ones left to the Session log, usage by project), Core metrics (the Core process's CPU and memory, execution slots and the Turn queue, connected daemons, the database and background jobs), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (node capacity and hosted Runtimes across projects; a node or a sandbox opens in a dialog with its figures and CPU and memory charts), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log. - **Resources**: Agents, Environment templates, Skills, Files, Vaults. Each list shows one project or all projects, with a Project column when all are shown and a Creator column naming the creating key. Detail pages show the resource's facts and offer Delete. An empty list says, in one sentence, that applications create these resources through the Agents API, and links to that resource's section of the API guide; Agent metrics does the same for Sessions. - **Platform**: Projects and keys (projects, their assets and usage, named keys, write history), Nodes (the node list, capacity observations, host figures, allocations and individual removal operations), and Settings, the single entry for platform configuration. Settings has four groups: Sandbox and scheduling, Workspace storage, Models and credentials, and Service and deployment. Sandbox and scheduling owns setup, resource edits, rollout, reset, idle policy, per-node capacity and enrollment. Add node asks for limits before issuing its one-time command; installers use Core's public URL and require supported node artifacts. Nodes links to these forms; removal stays on the node page and offers the host's uninstall command. Models and credentials owns each harness's default model and shows whether each harness is ready: Ready when it is enabled and has a default model provider, otherwise the missing step, Model provider not set (with Set as the primary action) or Disabled (with a link to the startup settings). Getting started uses the same readiness. Service and deployment owns installation facts (the public and API base URLs, with the installation ID and source commit behind Installation details), Domain and HTTPS actions and read-only startup settings, each named by its environment variable, with a link to the configuration guide's section on changing them. - A node whose provider is not ready names the reason as one Provider-neutral readiness class (provider unavailable, host unsupported, provider files or Runtime image missing, Runtime download failed, a host too small) and its fix in the help tip beside its status, wherever that status shows. @@ -54,7 +54,7 @@ The console runs beside the administrator's own Core, with execution, files and - **Waiting for results.** Overview, Session log and Session details name the function whose result the calling application must submit. The console cannot submit that result; environment connection waits stay distinct from function waits. - **Session names.** Wherever Sessions are listed, and on the Session page, a Session is named by its metadata `title`, else its Agent's name, else Untitled Session. - **Session history is read-only.** A Session page reads the Session, its Items and Turns and polls while work is in flight; there is no live event stream. -- **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. The Session page adds one next step where the reason has one, such as checking the model ID on the Agent or in Settings → Models and credentials; lists show the reason alone, with the next step in its tooltip. Core never returns the Harness's or provider's own message, so an unclassified failure says so. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible. +- **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. The Session page adds one next step where the reason has one, such as checking the model shown on the Session page and the Session's model provider; lists show the reason alone, with the next step in its tooltip. A next step must hold for every Session kind and deployment: a self-hosted Session never uses the default model provider, and an E2B deployment has no nodes. Provider observations in Settings show the reason alone. Core never returns the Harness's or provider's own message, so an unclassified failure says so. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible. - **Executor credentials.** Core issues executor credentials; the console does so with the deployment's Core key. A Session page whose environment is self-hosted has an Executor credentials section: issue a credential (shown once as one line of JSON, to copy or download, never stored), rotate it (the old one stops working immediately) or revoke it (the executor disconnects; installed Runtime state and workspace contents are not deleted). The file lets one executor connect for that environment only; it cannot call the Agents API. - **Default provider observations.** Each configured harness offers Usage details for Core's last successful use and any newer classified provider error. Missing records remain unknown; an error at or before the last success is no longer actionable. These are best-effort observations, not readiness checks. Failed refreshes qualify retained records, and replacing the provider starts a new observation history. - **Host connection.** Core's connection observation and credential metadata share one five-second read while visible. Never connected, connected, disconnected, bound credential revoked, and unknown are distinct; a recent heartbeat alone never proves connectivity. Only a fresh connected read marks Host connected. Stale or failed reads withhold completion. Recovery rotates the bound key, stops the installed daemon, replaces the host credential file and starts the daemon again. diff --git a/apps/web/e2e/diagnostics.spec.ts b/apps/web/e2e/diagnostics.spec.ts index a7a01680f..8c48c9613 100644 --- a/apps/web/e2e/diagnostics.spec.ts +++ b/apps/web/e2e/diagnostics.spec.ts @@ -22,7 +22,7 @@ test("reads classified failures and separate Item receipts without execution wri await page.getByRole("radio", { name: /^Failed/ }).click(); const row = page.getByRole("row").filter({ hasText: "The model provider rejected authentication" }).first(); await row.getByRole("button", { name: /^Open Session / }).click(); - await expect(page.getByLabel("Session facts")).toContainText("Check the model service's API key"); + await expect(page.getByLabel("Session facts")).toContainText("Check the API key of the Session's model provider"); await page.getByRole("radio", { name: "Trace", exact: true }).click(); await page.locator(".trace-ledger-row-tools").first().click(); await page.getByRole("tab", { name: "Timing", exact: true }).click(); diff --git a/apps/web/e2e/overview-readiness.spec.ts b/apps/web/e2e/overview-readiness.spec.ts index 434dcec35..4c134f9e7 100644 --- a/apps/web/e2e/overview-readiness.spec.ts +++ b/apps/web/e2e/overview-readiness.spec.ts @@ -124,6 +124,9 @@ test("Overview preserves partial activity and visibly qualifies a failed project const activity = page.getByRole("region", { name: "Session activity", exact: true }); await expect(activity.locator(".ts-chart-plot")).toBeVisible(); await expect(activity).toContainText("may be incomplete or out of date"); + // Needs attention counts from the same Session reads, so its tile and section are qualified too. + await expect(page.locator(".metric-tile").filter({ hasText: "Needs attention" })).toContainText("Partial"); + await expect(page.getByRole("region", { name: "Needs attention", exact: true })).toContainText("Partial"); await page.unroute(sessionLists); await activity.getByRole("button", { name: "Retry", exact: true }).click(); await expect(activity.locator(".error-state")).toHaveCount(0); diff --git a/apps/web/src/features/overview/OverviewPage.tsx b/apps/web/src/features/overview/OverviewPage.tsx index 07783f3b7..1ff44edad 100644 --- a/apps/web/src/features/overview/OverviewPage.tsx +++ b/apps/web/src/features/overview/OverviewPage.tsx @@ -155,6 +155,13 @@ export function OverviewPage() { // Core's summary counts every Session needing attention; the rest of them are older than the window. const attentionOlder = attention && totals ? Math.max(0, attentionCount(totals) - attention.length) : null; const truncated = data?.sessions.truncated ?? []; + const unreadNames = readFailures.map((failure) => failure.project.name).join(", "); + const truncatedNames = truncated.map((project) => project.name).join(", "); + // Attention comes from the browser's Session reads, so an unread or capped project leaves it partial. + const attentionPartialHelp = [ + readFailures.length ? t("activity.unreadHelp", { names: unreadNames }) : null, + truncated.length ? t("attention.partialHelp", { names: truncatedNames }) : null, + ].filter(Boolean).join(" "); // Whether any Session exists: Core's summary counts them all; without it, any Session read counts. const sessionCount = totals ? totals.total : sessions?.length ? sessions.length @@ -218,6 +225,7 @@ export function OverviewPage() { {attentionPartialHelp} : null} value={} sub={attention && attentionFailed !== null ? t("tiles.attentionSplit", { failed: attentionFailed, waiting: attention.length - attentionFailed }) : sessionReadsFailed ? t("kpi.readsFailed") : null} /> @@ -232,14 +240,9 @@ export function OverviewPage() { {t("activity.help")} {truncated.length || readFailures.length ? ( - - - - {readFailures.length ? t("activity.unreadHelp", { names: readFailures.map((failure) => failure.project.name).join(", ") }) : null} - {readFailures.length && truncated.length ? " " : null} - {truncated.length ? t("activity.partialHelp", { names: truncated.map((project) => project.name).join(", ") }) : null} - - + + {[readFailures.length ? t("activity.unreadHelp", { names: unreadNames }) : null, truncated.length ? t("activity.partialHelp", { names: truncatedNames }) : null].filter(Boolean).join(" ")} + ) : null} {sessionReadsFailed ? : null} @@ -277,12 +280,12 @@ export function OverviewPage() { {t("attention.limitHelp", { count: ATTENTION_LIMIT })} ) : null} - {attention?.length && truncated.length ? {t("attention.partialHelp")} : null} + {attention?.length && attentionPartialHelp ? {attentionPartialHelp} : null} {sessionReadsFailed || summaryError !== null ? : null} - {attention?.length || (!sessionReadsFailed && summaryError === null) ? failure.project.name)} partial={truncated.length > 0 || summaryError !== null} now={now} onOpen={openSession} /> : null} + {attention?.length || (!sessionReadsFailed && summaryError === null) ? : null}
@@ -301,16 +304,27 @@ export function OverviewPage() { ); } -function MetricTile({ label, help, value, sub }: { label: string; help?: ReactNode; value: ReactNode; sub?: ReactNode }) { +function MetricTile({ label, help, note, value, sub }: { label: string; help?: ReactNode; note?: ReactNode; value: ReactNode; sub?: ReactNode }) { return (
-
{label}{help ? {help} : null}
+
{label}{help ? {help} : null}{note}
{value}
{sub ?

{sub}

: null}
); } +/** Marks figures read from only part of the Session lists; the help says which. */ +function PartialChip({ children }: { children: ReactNode }) { + const { t } = useTranslation("overview"); + return ( + + + {children} + + ); +} + function fleetDetail(state: FleetState, t: TFunction<"overview">): string { if (state.status === "failed") return t("fleet.failed"); return t("fleet.loading"); @@ -445,14 +459,14 @@ function ProjectUsageTable({ rows, failed, now, onOpen }: { rows: ProjectUsageRo * is claimed only when every Session list was read in full; otherwise the * empty table says the Sessions could not be listed. */ -function AttentionTable({ sessions, older, unread, partial, now, onOpen }: { +function AttentionTable({ sessions, older, unreadNames, truncatedNames, now, onOpen }: { sessions: InProject[] | null; /** Sessions needing attention before the window, when Core's summary was read. */ older: number | null; /** Projects whose Session list could not be read. */ - unread: string[]; - /** A read stopped at its cap, or no summary bounded it, so some Sessions may be missing. */ - partial: boolean; + unreadNames: string; + /** Projects whose Session read stopped at its cap, so some Sessions may be missing. */ + truncatedNames: string; now: number; onOpen: (entry: InProject) => void; }) { @@ -462,9 +476,9 @@ function AttentionTable({ sessions, older, unread, partial, now, onOpen }: { const waitingFor = useWaitingFor(); if (sessions === null) return ; if (!sessions.length) { - const description = unread.length - ? t("attention.unreadDescription", { names: unread.join(", ") }) - : partial ? t("attention.partialHelp") : null; + const description = unreadNames + ? t("attention.unreadDescription", { names: unreadNames }) + : truncatedNames ? t("attention.partialHelp", { names: truncatedNames }) : null; return (
{description ? : } diff --git a/apps/web/src/features/sessions/session-diagnostics.test.tsx b/apps/web/src/features/sessions/session-diagnostics.test.tsx index 79d56eb95..330fd1d41 100644 --- a/apps/web/src/features/sessions/session-diagnostics.test.tsx +++ b/apps/web/src/features/sessions/session-diagnostics.test.tsx @@ -31,8 +31,8 @@ function render(kind: "session" | "turn", data: SessionDiagnostics | TurnDiagnos describe("Core diagnostics in the console", () => { it("renders safe classified Session and root Turn failures in both languages", async () => { - expect(render("session", sessionData)).toContain("API key in Settings → Models and credentials"); - expect(render("turn", turnData)).toContain("API key in Settings → Models and credentials"); + expect(render("session", sessionData)).toContain("Check the API key of the Session's model provider."); + expect(render("turn", turnData)).toContain("Check the API key of the Session's model provider."); await i18n.changeLanguage("zh-CN"); expect(render("session", sessionData)).toContain("模型服务认证失败"); expect(render("turn", turnData)).not.toContain("PRIVATE_NATIVE_SENTINEL"); @@ -61,16 +61,18 @@ describe("Core diagnostics in the console", () => { expect(diagnosticFailure({ code: "connection_failed", params: { http_status: 429 }, failed_at: null }, t)).toContain("HTTP 429"); }); - it("names a model rejection with its next step, and keeps the unclassified cause for unclassified failures", async () => { + it("gives each classified failure a next step that holds for every Session kind", () => { const t = i18n.getFixedT("en", "diagnostics"); - expect(diagnosticFailure({ code: "invalid_request", params: {}, failed_at: null }, t)).toBe("The model provider rejected the request. Check the model ID on the Agent or in Settings → Models and credentials."); - expect(diagnosticFailure({ code: "connection_failed", params: { http_status: 502 }, failed_at: null }, t)).toBe("The connection to the model provider failed. HTTP 502. Check the base URL in Settings → Models and credentials."); + const providerDefault = "Without a model provider set on the Session or its saved Agent, a hosted sandbox Session uses the default in Settings → Models and credentials."; + expect(diagnosticFailure({ code: "invalid_request", params: {}, failed_at: null }, t)).toBe(`The model provider rejected the request. Check the model and parameters, starting with the model on the Session page. ${providerDefault}`); + expect(diagnosticFailure({ code: "connection_failed", params: { http_status: 502 }, failed_at: null }, t)).toContain("HTTP 502. Check the Base URL of the Session's model provider."); + expect(diagnosticFailure({ code: "runtime_unavailable", params: {}, failed_at: null }, t)).toBe("The Runtime is unavailable. Retry later. For a self-hosted Session, check Host connection on the Session page; for a hosted sandbox Session, check Settings → Sandbox and scheduling."); expect(diagnosticFailure({ code: "harness_error", params: {}, failed_at: null }, t)).toContain("Core does not show the Harness's own message"); - expect(diagnosticFailure({ code: "invalid_request", params: {}, failed_at: null }, i18n.getFixedT("zh-CN", "diagnostics"))).toBe("模型服务拒绝了请求。请检查 Agent 或“设置 → 模型与凭据”中的模型 ID。"); + expect(diagnosticFailure({ code: "invalid_request", params: {}, failed_at: null }, i18n.getFixedT("zh-CN", "diagnostics"))).toBe("模型服务拒绝了请求。请检查模型和参数,先看 Session 页显示的模型。Session 及其保存的 Agent 都未设置模型服务时,托管沙箱 Session 使用“设置 → 模型与凭据”中的默认配置。"); // Lists show the reason alone; the next step stays in the title. const row = render("session", { ...sessionData, failure: { ...sessionData.failure!, code: "invalid_request" } }, false, true); expect(row).toContain(">The model provider rejected the request.<"); - expect(row).toContain('title="The model provider rejected the request. Check the model ID'); + expect(row).toContain('title="The model provider rejected the request. Check the model and parameters'); }); it("keeps call/result receipt intervals distinct and historical settlement missing", () => { diff --git a/apps/web/src/features/system/ProviderObservations.tsx b/apps/web/src/features/system/ProviderObservations.tsx index 6a7b9b4d5..e66a8dbb9 100644 --- a/apps/web/src/features/system/ProviderObservations.tsx +++ b/apps/web/src/features/system/ProviderObservations.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { HelpTip, StatusDot } from "../../components/console-ui"; import { Modal } from "../../components/Modal"; -import { diagnosticFailure } from "../../lib/diagnostic-failure"; +import { diagnosticReason } from "../../lib/diagnostic-failure"; import { formatDateTime } from "../../lib/format"; import { Fact } from "./Fact"; @@ -27,7 +27,7 @@ export function ProviderObservations({ provider, name, stale }: { provider: Harn
{date(provider.last_used_at)} - {activeError && provider.last_error_code ? <>{diagnosticFailure({ code: provider.last_error_code, params: {}, failed_at: provider.last_error_at }, t)} {date(provider.last_error_at)} : t("providerNoError")} + {activeError && provider.last_error_code ? <>{diagnosticReason({ code: provider.last_error_code, params: {}, failed_at: provider.last_error_at }, t)} {date(provider.last_error_at)} : t("providerNoError")}
{stale ?

{t("providerStaleHelp")}

: null} diff --git a/apps/web/src/i18n/locales/en/diagnostics.ts b/apps/web/src/i18n/locales/en/diagnostics.ts index 26de5a030..191df7ada 100644 --- a/apps/web/src/i18n/locales/en/diagnostics.ts +++ b/apps/web/src/i18n/locales/en/diagnostics.ts @@ -37,20 +37,21 @@ export const diagnostics = { environment_provisioning_failed: "The environment could not be prepared.", }, next: { - authentication_error: "Check the model service's API key in Settings → Models and credentials.", + authentication_error: "Check the API key of the Session's model provider. Without one set on the Session or its saved Agent, a hosted sandbox Session uses the default in Settings → Models and credentials.", rate_limit_exceeded: "Retry later, or raise the limit with the provider.", usage_limit_exceeded: "Check the account's quota with the provider.", server_overloaded: "Retry later.", server_error: "Retry later.", - invalid_request: "Check the model ID on the Agent or in Settings → Models and credentials.", - resource_not_found: "Check the model ID on the Agent or in Settings → Models and credentials.", + invalid_request: "Check the model and parameters, starting with the model on the Session page. Without a model provider set on the Session or its saved Agent, a hosted sandbox Session uses the default in Settings → Models and credentials.", + resource_not_found: "Check the model on the Session page. Without a model provider set on the Session or its saved Agent, a hosted sandbox Session uses the default in Settings → Models and credentials.", request_timeout: "Retry later.", context_length_exceeded: "Start a new Session or shorten the input.", - connection_failed: "Check the base URL in Settings → Models and credentials.", - harness_error: "Core does not show the Harness's own message. Check the model ID and API key on the Agent or in Settings → Models and credentials.", - model_provider_required: "Set a default model configuration in Settings → Models and credentials.", - runtime_unavailable: "Check the node on the Nodes page.", - runtime_disconnected: "Check the node on the Nodes page.", + cyber_policy: "Change the input before sending it again, or start a new Session.", + connection_failed: "Check the Base URL of the Session's model provider. Without one set on the Session or its saved Agent, a hosted sandbox Session uses the default in Settings → Models and credentials.", + harness_error: "Core does not show the Harness's own message. Check the model and Harness on the Session page, then the Session's model provider. Without one set on the Session or its saved Agent, a hosted sandbox Session uses the default in Settings → Models and credentials.", + model_provider_required: "Set a model provider on the Session or its saved Agent. A hosted sandbox Session can also use the default in Settings → Models and credentials.", + runtime_unavailable: "Retry later. For a self-hosted Session, check Host connection on the Session page; for a hosted sandbox Session, check Settings → Sandbox and scheduling.", + runtime_disconnected: "Retry later. For a self-hosted Session, check Host connection on the Session page; for a hosted sandbox Session, check Settings → Sandbox and scheduling.", delivery_unconfirmed: "Check the result before sending it again.", }, /** Between sentences of one reason. */ diff --git a/apps/web/src/i18n/locales/en/overview.ts b/apps/web/src/i18n/locales/en/overview.ts index bce836f97..ddac3f0f8 100644 --- a/apps/web/src/i18n/locales/en/overview.ts +++ b/apps/web/src/i18n/locales/en/overview.ts @@ -199,7 +199,7 @@ export const overview = { viewLog: "Session log", shown: "{{shown}} of {{total}}", limitHelp: "The {{count}} most recently active are shown. The Session log lists the rest.", - partialHelp: "Only the newest Sessions of each project were read, so some Sessions that need attention are not listed. The Session log lists them all.", + partialHelp: "Only the newest Sessions of {{names}} were read, so some Sessions that need attention may not be listed. The Session log lists them all.", loading: "Loading Sessions…", emptyTitle: "Nothing needs attention", emptyDescription: "No Session active in the last 24 hours failed or is waiting for its caller.", diff --git a/apps/web/src/i18n/locales/zh-CN/diagnostics.ts b/apps/web/src/i18n/locales/zh-CN/diagnostics.ts index ddbf34286..f7e530a2c 100644 --- a/apps/web/src/i18n/locales/zh-CN/diagnostics.ts +++ b/apps/web/src/i18n/locales/zh-CN/diagnostics.ts @@ -37,20 +37,21 @@ export const diagnostics = { environment_provisioning_failed: "环境准备失败。", }, next: { - authentication_error: "请在“设置 → 模型与凭据”中检查模型服务的 API 密钥。", + authentication_error: "请检查该 Session 所用模型服务的 API 密钥。Session 及其保存的 Agent 都未设置模型服务时,托管沙箱 Session 使用“设置 → 模型与凭据”中的默认配置。", rate_limit_exceeded: "请稍后重试,或向模型服务申请提高限额。", usage_limit_exceeded: "请在模型服务中检查账户额度。", server_overloaded: "请稍后重试。", server_error: "请稍后重试。", - invalid_request: "请检查 Agent 或“设置 → 模型与凭据”中的模型 ID。", - resource_not_found: "请检查 Agent 或“设置 → 模型与凭据”中的模型 ID。", + invalid_request: "请检查模型和参数,先看 Session 页显示的模型。Session 及其保存的 Agent 都未设置模型服务时,托管沙箱 Session 使用“设置 → 模型与凭据”中的默认配置。", + resource_not_found: "请检查 Session 页显示的模型。Session 及其保存的 Agent 都未设置模型服务时,托管沙箱 Session 使用“设置 → 模型与凭据”中的默认配置。", request_timeout: "请稍后重试。", context_length_exceeded: "请新建 Session 或缩短输入。", - connection_failed: "请检查“设置 → 模型与凭据”中的 Base URL。", - harness_error: "Core 不显示 Harness 的原始消息。请检查 Agent 或“设置 → 模型与凭据”中的模型 ID 和 API 密钥。", - model_provider_required: "请在“设置 → 模型与凭据”中设置默认模型配置。", - runtime_unavailable: "请在“节点”页检查节点。", - runtime_disconnected: "请在“节点”页检查节点。", + cyber_policy: "请修改输入后再发送,或新建 Session。", + connection_failed: "请检查该 Session 所用模型服务的 Base URL。Session 及其保存的 Agent 都未设置模型服务时,托管沙箱 Session 使用“设置 → 模型与凭据”中的默认配置。", + harness_error: "Core 不显示 Harness 的原始消息。请检查 Session 页显示的模型和 Harness,再检查该 Session 所用的模型服务。Session 及其保存的 Agent 都未设置模型服务时,托管沙箱 Session 使用“设置 → 模型与凭据”中的默认配置。", + model_provider_required: "请在 Session 或其保存的 Agent 上设置模型服务。托管沙箱 Session 也可以使用“设置 → 模型与凭据”中的默认配置。", + runtime_unavailable: "请稍后重试。自托管 Session 请检查 Session 页的“主机连接”;托管沙箱 Session 请检查“设置 → 沙箱与调度”。", + runtime_disconnected: "请稍后重试。自托管 Session 请检查 Session 页的“主机连接”;托管沙箱 Session 请检查“设置 → 沙箱与调度”。", delivery_unconfirmed: "请先检查结果,再决定是否重新发送。", }, /** Between sentences of one reason. */ diff --git a/apps/web/src/i18n/locales/zh-CN/overview.ts b/apps/web/src/i18n/locales/zh-CN/overview.ts index 2f5eed558..8a88ec4ee 100644 --- a/apps/web/src/i18n/locales/zh-CN/overview.ts +++ b/apps/web/src/i18n/locales/zh-CN/overview.ts @@ -199,7 +199,7 @@ export const overview = { viewLog: "Session 日志", shown: "{{shown}} / {{total}}", limitHelp: "只显示最近活跃的 {{count}} 个,其余请在 Session 日志中查看。", - partialHelp: "每个项目只读取了最近创建的 Session,部分需要处理的 Session 没有列出。Session 日志中可以看到全部。", + partialHelp: "{{names}} 只读取了最近创建的 Session,部分需要处理的 Session 可能没有列出。Session 日志中可以看到全部。", loading: "正在加载 Session…", emptyTitle: "没有需要处理的 Session", emptyDescription: "过去 24 小时内活跃的 Session 都没有失败或等待调用方。", diff --git a/docs/web/console-api-usage.md b/docs/web/console-api-usage.md index 81ed11660..0a25afd9b 100644 --- a/docs/web/console-api-usage.md +++ b/docs/web/console-api-usage.md @@ -50,7 +50,7 @@ Resource-specific rules: - **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. The Session page adds the reason's next step, such as checking the model ID on the Agent or in Settings → Models and credentials; lists show the reason alone, with the next step in its tooltip. Core returns no Harness or provider message, so an unclassified failure says so. An unavailable or mismatched diagnostic offers an explicit read retry; a retry never replays execution. +- **Diagnostics.** The console translates Core's classified reason and never infers a cause from raw logs. The Session page adds the reason's next step, such as checking the model shown on the Session page and the Session's model provider; lists show the reason alone, with the next step in its tooltip. Core returns no Harness or provider message, so an unclassified failure says so. An unavailable or mismatched diagnostic offers an explicit read retry; a retry never replays execution. ## Executor credentials and host connection @@ -132,7 +132,7 @@ The console assembles several figures in the browser from bounded reads of each | Resource lists | Every page of the selected project, or of every project in parallel | 10,000 entries per project; a failed project is named and the rest still show | | Session log | Every Session page of the selected projects, newest first | 10,000 per project; refreshed on request | | Session page | Session, Items and Turns | 10,000 Items and Turns; polled every 5 s while the Session is in progress or waiting, backing off to 60 s on failures | -| Overview | Summary; Session lists for the 24-hour activity and the Sessions needing attention that were active in the same 24 hours | 1,000 Sessions per project; projects idle for the whole window are skipped; refreshed every 30 s while visible. Failed project or Session reads show Retry instead of a synthesized empty result; any retained or partial data is visibly qualified | +| Overview | Summary; Session lists for the 24-hour activity and Needs attention, which lists Sessions active in the same 24 hours | 1,000 Sessions per project; projects idle for the whole window are skipped; refreshed every 30 s while visible. Failed project or Session reads show Retry instead of a synthesized empty result; any retained or partial data is visibly qualified | | Agent metrics | Summary; Session lists; Turns and Items of the most recently active Sessions | 2,000 Sessions listed per project; 200 Sessions read per load, 10 Turn and 5 Item pages each, 15 s per Session and 45 s per load | | Sandbox metrics | Nodes and allocations; Runtime observations; hosted Sessions by ID; Runtime history | 100 hosted Sessions read per refresh; history for at most 24; refreshed every 30 s while visible | diff --git a/docs/web/index.md b/docs/web/index.md index 7a3399330..d4dd4bf39 100644 --- a/docs/web/index.md +++ b/docs/web/index.md @@ -16,7 +16,7 @@ Signing in opens the Overview. While any step is still to do, its **Getting star | 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 in the last 24 hours, each with its one-line reason; usage by Project | +| 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; Needs attention: failed Sessions and Sessions waiting for their caller, active in the last 24 hours, each with its one-line reason; usage by Project | | 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; **Automatic suspension** when supported; **Recorded compute** lifecycle state shown separately from hosted Runtime CPU and memory observations across Projects | diff --git a/docs/zh/web/console-api-usage.md b/docs/zh/web/console-api-usage.md index 48adbb1d4..26f3afc1b 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: f4fb9af958ffb16a4bff1f8d9c369c5df0bcf68c2e4a7072a0cead694b882307 +source_hash: 74cbd391bcc6aed0df2aa3b6272689eb75874f88f0c0cf6bc45e22a9b708b905 --- 本页列出各控制台页面读取和写入的 Core 路由,以及控制台如何限定读取范围。[administrator API contract](../../../contracts/agents-api/zh/admin-api.md) 定义了路由、响应结构、分页和审计记录;[API namespaces and credentials](../api/index.md) 定义了本文使用的术语。 @@ -52,7 +52,7 @@ source_hash: f4fb9af958ffb16a4bff1f8d9c369c5df0bcf68c2e4a7072a0cead694b882307 - **Files.** 列表每页读取 100 条,可按最新或最早顺序排列。管理员 API 没有 File 内容路由,因此控制台不提供下载。 - **Vaults.** Credential 令牌绝不返回。控制台显示每个 Credential 的名称、MCP 服务器 URL、身份验证类型和更新时间。 - **Sessions.** 如果某个 Session 格式错误,其所属项目的读取会失败,而不会跳过该 Session。 -- **Diagnostics.** 控制台会翻译 Core 分类的原因,绝不从原始日志推断原因。Session 页面会补充该原因的下一步操作,例如检查 Agent 或“设置 → 模型与凭据”中的模型 ID;列表只显示原因,下一步操作放在悬停提示中。Core 不返回 Harness 或模型服务的原始消息,因此无法分类的失败会如实说明。诊断信息不可用或不匹配时,会提供显式的读取重试;重试绝不会重新执行操作。 +- **Diagnostics.** 控制台会翻译 Core 分类的原因,绝不从原始日志推断原因。Session 页面会补充该原因的下一步操作,例如检查 Session 页显示的模型以及该 Session 所用的模型服务;列表只显示原因,下一步操作放在悬停提示中。Core 不返回 Harness 或模型服务的原始消息,因此无法分类的失败会如实说明。诊断信息不可用或不匹配时,会提供显式的读取重试;重试绝不会重新执行操作。 ## 执行器凭据和主机连接 {#executor-credentials-and-host-connection} @@ -134,7 +134,7 @@ E2B 部署没有节点;其 API 密钥为只写。Overview 和 Sandbox metrics | 资源列表 | 所选项目的所有页面,或并行读取所有项目的所有页面 | 每个项目最多 10,000 个条目;读取失败的项目会被指明,其他项目仍会显示 | | Session log | 所选项目的所有 Session 页面,按最新优先 | 每个项目最多 10,000 个;按请求刷新 | | Session 页面 | Session、Items 和 Turns | 最多 10,000 个 Items 和 Turns;Session 进行中或等待时每 5 秒轮询一次,失败后退避至 60 秒 | -| Overview | 汇总;24 小时活动及同一 24 小时内活跃且需要处理的 Sessions 所对应的 Session 列表 | 每个项目最多 1,000 个 Sessions;跳过整个时段内都空闲的项目;可见时每 30 秒刷新一次。项目或 Session 读取失败时显示 Retry,而不是合成空结果;任何保留的数据或不完整数据都会附带明确的限定说明 | +| Overview | 汇总;24 小时活动及“需要处理”所用的 Session 列表,后者列出同一 24 小时内活跃的 Session | 每个项目最多 1,000 个 Sessions;跳过整个时段内都空闲的项目;可见时每 30 秒刷新一次。项目或 Session 读取失败时显示 Retry,而不是合成空结果;任何保留的数据或不完整数据都会附带明确的限定说明 | | Agent 指标 | 汇总;Session 列表;最近活动的 Sessions 的 Turns 和 Items | 每个项目列出最多 2,000 个 Sessions;每次加载读取 200 个 Sessions,每个 Session 读取 10 个 Turn 页面和 5 个 Item 页面;每个 Session 为 15 秒,每次加载为 45 秒 | | Sandbox metrics | Nodes 和 allocations;Runtime observations;按 ID 标识的托管 Sessions;Runtime 历史记录 | 每次刷新读取 100 个托管 Sessions;最多读取 24 个 Session 的历史记录;可见时每 30 秒刷新一次 | diff --git a/docs/zh/web/index.md b/docs/zh/web/index.md index 4e7ef0fdf..afc15f1c0 100644 --- a/docs/zh/web/index.md +++ b/docs/zh/web/index.md @@ -1,7 +1,7 @@ --- title: "OpenAgentCore Web" source: docs/web/index.md -source_hash: 69608c288990ae3bf383510b253ef344810bee095ff574464cd3fd6a6ca04669 +source_hash: 17ec9dbffb34ae89966259c3e32ea168a20289de42bae1f50d3ceec1ded1c956 --- Web 是单个 OpenAgentCore 部署的管理员控制台。管理员用它查看健康状态、容量、用量和失败情况,检查各 Project 的资源与执行历史,并管理 Project、密钥、节点和部署设置。应用不使用 Web;它们通过自己的 Project API 密钥调用 Core 的 Agents API(`/v1`)。 @@ -18,7 +18,7 @@ Web 是单个 OpenAgentCore 部署的管理员控制台。管理员用它查看 | 分组 | 页面 | 用途 | | --- | --- | --- | -| Monitor | Overview | 服务状态、运行中的 Session、沙箱槽位和待处理工作;24 小时 Session 活动;以拓扑展示 Core 与节点,各自提供弹出概览;过去 24 小时内需要处理的 Session,每行附一行原因;按 Project 统计的用量 | +| Monitor | Overview | 服务状态、运行中的 Session、沙箱槽位和待处理工作;24 小时 Session 活动;以拓扑展示 Core 与节点,各自提供弹出概览;需要处理:过去 24 小时内活跃的失败或等待调用方的 Session,每行附一行原因;按 Project 统计的用量 | | Monitor | Core metrics | Core 进程:CPU 与常驻内存及其限制、执行槽位与 Turn 队列、已连接守护进程、数据库延迟与连接池、后台任务 | | Monitor | Agent metrics | 1 小时、6 小时、24 小时或 7 天的请求、错误、耗时、token、模型、工具、Agent 和 API 密钥 | | Monitor | Sandbox metrics | 节点容量;支持时显示 **Automatic suspension**;**Recorded compute** 生命周期状态与各 Project 的托管 Runtime CPU、内存观测分开展示 | From 2c3e78dbae6f714971c529197f12663e924c43b9 Mon Sep 17 00:00:00 2001 From: SaladDay <1203511142@qq.com> Date: Wed, 14 Oct 2026 07:04:14 +0000 Subject: [PATCH 3/3] Wrap the Partial chip on narrow metric tiles --- apps/web/src/features/overview/overview.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/src/features/overview/overview.css b/apps/web/src/features/overview/overview.css index fc36d43e7..c013c0b3a 100644 --- a/apps/web/src/features/overview/overview.css +++ b/apps/web/src/features/overview/overview.css @@ -19,6 +19,9 @@ .metric-tile:first-child { padding-left: 0; border-left: 0; } .metric-tile:last-child { padding-right: 0; } +/* A Partial chip wraps under the label on narrow tiles instead of being cut off. */ +.metric-tile > header { flex-wrap: wrap; } + .metric-tile-label { color: var(--fg-muted); font-size: 13px;