diff --git a/apps/aevatar-console-web/config/routes.ts b/apps/aevatar-console-web/config/routes.ts
index 03bda1e44..b96611069 100644
--- a/apps/aevatar-console-web/config/routes.ts
+++ b/apps/aevatar-console-web/config/routes.ts
@@ -99,10 +99,6 @@ export default [
path: "/actors",
redirect: "/runtime/explorer",
},
- {
- path: "/observability",
- redirect: "/runtime/observability",
- },
{
name: "Runtime",
icon: "deploymentUnit",
@@ -132,22 +128,13 @@ export default [
name: "Explorer",
component: "./actors",
},
- {
- path: "observability",
- name: "Observability",
- component: "./observability",
- },
],
},
{
path: "/settings",
name: "Settings",
icon: "setting",
- redirect: "/settings/console",
- },
- {
- path: "/settings/console",
- component: "./settings/console",
+ component: "./settings/account",
},
{
path: "/",
diff --git a/apps/aevatar-console-web/docs/2026-03-25-runs-page-restructure-wireframe.md b/apps/aevatar-console-web/docs/2026-03-25-runs-page-restructure-wireframe.md
index 01f610b5b..3c906481d 100644
--- a/apps/aevatar-console-web/docs/2026-03-25-runs-page-restructure-wireframe.md
+++ b/apps/aevatar-console-web/docs/2026-03-25-runs-page-restructure-wireframe.md
@@ -32,7 +32,7 @@
```text
+-----------------------------------------------------------------------------------------------------------+
| Runtime Run Console |
-| Start run | Open workflows | Open actor | Open observability |
+| Start run | Open workflows | Open actor | Open settings |
+-----------------------------------------------------------------------------------------------------------+
| STATUS STRIP |
| [Running] [RunId: xxx] [Elapsed: 02:14] [Workflow: human_input_manual_triage] [WS] [Pending Interaction] |
diff --git a/apps/aevatar-console-web/src/pages/actors/index.test.tsx b/apps/aevatar-console-web/src/pages/actors/index.test.tsx
index cdb557968..a8cdb25dc 100644
--- a/apps/aevatar-console-web/src/pages/actors/index.test.tsx
+++ b/apps/aevatar-console-web/src/pages/actors/index.test.tsx
@@ -41,9 +41,7 @@ describe('ActorsPage', () => {
expect(
screen.getByRole('button', { name: 'Open Runtime Workflows' }),
).toBeTruthy();
- expect(
- screen.getByRole('button', { name: 'Open observability' }),
- ).toBeTruthy();
+ expect(screen.queryByRole('button', { name: 'Open observability' })).toBeNull();
expect(container.textContent).toContain('Runtime actor query');
expect(container.textContent).toContain('No recent runs yet');
expect(container.textContent).toContain(
diff --git a/apps/aevatar-console-web/src/pages/actors/index.tsx b/apps/aevatar-console-web/src/pages/actors/index.tsx
index 89c3b00fa..c795d6291 100644
--- a/apps/aevatar-console-web/src/pages/actors/index.tsx
+++ b/apps/aevatar-console-web/src/pages/actors/index.tsx
@@ -12,7 +12,6 @@ import {
import { useQuery } from "@tanstack/react-query";
import { history } from "@/shared/navigation/history";
import {
- buildRuntimeObservabilityHref,
buildRuntimeRunsHref,
buildRuntimeWorkflowsHref,
} from "@/shared/navigation/runtimeRoutes";
@@ -30,7 +29,10 @@ import {
Typography,
} from "antd";
import React, { useEffect, useMemo, useRef, useState } from "react";
-import { runtimeActorsApi } from "@/shared/api/runtimeActorsApi";
+import {
+ runtimeActorsApi,
+ type ActorGraphDirection,
+} from "@/shared/api/runtimeActorsApi";
import type {
WorkflowActorGraphEdge,
WorkflowActorGraphNode,
@@ -40,10 +42,6 @@ import type {
import { formatDateTime } from "@/shared/datetime/dateTime";
import { buildActorGraphElements } from "@/shared/graphs/buildGraphElements";
import GraphCanvas from "@/shared/graphs/GraphCanvas";
-import {
- type ActorGraphDirection,
- loadConsolePreferences,
-} from "@/shared/preferences/consolePreferences";
import { loadRecentRuns } from "@/shared/runs/recentRuns";
import {
codeBlockStyle,
@@ -62,6 +60,7 @@ import {
summaryMetricValueStyle,
stretchColumnStyle,
} from "@/shared/ui/proComponents";
+import { describeError } from "@/shared/ui/errorText";
import {
type ActorTimelineFilters,
type ActorTimelineRow,
@@ -113,6 +112,11 @@ type GraphControlValues = {
graphViewMode: ActorGraphViewMode;
};
+const defaultActorTimelineTake = 50;
+const defaultActorGraphDepth = 3;
+const defaultActorGraphTake = 100;
+const defaultActorGraphDirection: ActorGraphDirection = "Both";
+
type SummaryFieldProps = {
copyable?: boolean;
label: string;
@@ -373,14 +377,13 @@ function parseGraphViewMode(value: string | null): ActorGraphViewMode {
}
function readStateFromUrl(): ActorPageState {
- const preferences = loadConsolePreferences();
if (typeof window === "undefined") {
return {
actorId: "",
- timelineTake: preferences.actorTimelineTake,
- graphDepth: preferences.actorGraphDepth,
- graphTake: preferences.actorGraphTake,
- graphDirection: preferences.actorGraphDirection,
+ timelineTake: defaultActorTimelineTake,
+ graphDepth: defaultActorGraphDepth,
+ graphTake: defaultActorGraphTake,
+ graphDirection: defaultActorGraphDirection,
edgeTypes: [],
};
}
@@ -390,19 +393,19 @@ function readStateFromUrl(): ActorPageState {
actorId: params.get("actorId") ?? "",
timelineTake: parsePositiveInt(
params.get("timelineTake"),
- preferences.actorTimelineTake
+ defaultActorTimelineTake
),
graphDepth: parsePositiveInt(
params.get("graphDepth"),
- preferences.actorGraphDepth
+ defaultActorGraphDepth
),
graphTake: parsePositiveInt(
params.get("graphTake"),
- preferences.actorGraphTake
+ defaultActorGraphTake
),
graphDirection: parseDirection(
params.get("graphDirection"),
- preferences.actorGraphDirection
+ defaultActorGraphDirection
),
edgeTypes: params
.getAll("edgeTypes")
@@ -848,17 +851,6 @@ const ActorsPage: React.FC = () => {
-
}
>
@@ -1149,7 +1141,7 @@ const ActorsPage: React.FC = () => {
showIcon
type="error"
title="Failed to load timeline"
- description={String(timelineQuery.error)}
+ description={describeError(timelineQuery.error)}
/>
) : null}
@@ -1274,7 +1266,7 @@ const ActorsPage: React.FC = () => {
showIcon
type="error"
title="Failed to load graph topology"
- description={String(currentGraphError)}
+ description={describeError(currentGraphError)}
/>
) : currentGraph && currentGraph.nodes.length > 0 ? (
@@ -1337,7 +1329,7 @@ const ActorsPage: React.FC = () => {
showIcon
type="error"
title="Failed to load actor"
- description={String(snapshotQuery.error)}
+ description={describeError(snapshotQuery.error)}
/>
) : snapshotRecord ? (
<>
@@ -1561,7 +1553,7 @@ const ActorsPage: React.FC = () => {
showIcon
type="error"
title="Failed to load graph view"
- description={String(currentGraphError)}
+ description={describeError(currentGraphError)}
/>
) : graphSummary ? (
<>
diff --git a/apps/aevatar-console-web/src/pages/auth/callback/index.tsx b/apps/aevatar-console-web/src/pages/auth/callback/index.tsx
index 4d03f3a53..622bca18c 100644
--- a/apps/aevatar-console-web/src/pages/auth/callback/index.tsx
+++ b/apps/aevatar-console-web/src/pages/auth/callback/index.tsx
@@ -4,6 +4,7 @@ import React, { useEffect, useMemo, useState } from 'react';
import { NyxIDAuthClient } from '@/shared/auth/client';
import { getNyxIDRuntimeConfig } from '@/shared/auth/config';
import { loadStoredAuthSession } from '@/shared/auth/session';
+import { describeError } from '@/shared/ui/errorText';
const CallbackPage: React.FC = () => {
const [errorText, setErrorText] = useState
(undefined);
@@ -26,7 +27,7 @@ const CallbackPage: React.FC = () => {
return;
}
- setErrorText(error instanceof Error ? error.message : String(error));
+ setErrorText(describeError(error));
}
};
diff --git a/apps/aevatar-console-web/src/pages/login/index.tsx b/apps/aevatar-console-web/src/pages/login/index.tsx
index 0e6a7c88e..b27325f0d 100644
--- a/apps/aevatar-console-web/src/pages/login/index.tsx
+++ b/apps/aevatar-console-web/src/pages/login/index.tsx
@@ -11,6 +11,7 @@ import { getNyxIDRuntimeConfig } from '@/shared/auth/config';
import {
sanitizeReturnTo,
} from '@/shared/auth/session';
+import { describeError } from '@/shared/ui/errorText';
const pageStyle: React.CSSProperties = {
minHeight: '100vh',
@@ -79,7 +80,7 @@ const LoginPage: React.FC = () => {
});
} catch (error) {
setPending(false);
- setErrorText(error instanceof Error ? error.message : String(error));
+ setErrorText(describeError(error));
}
};
diff --git a/apps/aevatar-console-web/src/pages/observability/index.test.tsx b/apps/aevatar-console-web/src/pages/observability/index.test.tsx
deleted file mode 100644
index 699c838b2..000000000
--- a/apps/aevatar-console-web/src/pages/observability/index.test.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { screen } from "@testing-library/react";
-import React from "react";
-import { renderWithQueryClient } from "../../../tests/reactQueryTestUtils";
-import ObservabilityPage from "./index";
-
-describe("ObservabilityPage", () => {
- beforeEach(() => {
- window.history.replaceState(
- null,
- "",
- "/observability?workflow=direct&actorId=Workflow:19fe1b04&commandId=cmd-123"
- );
- });
-
- it("renders platform-oriented observability jumps", async () => {
- const { container } = renderWithQueryClient(
- React.createElement(ObservabilityPage)
- );
-
- expect(container.textContent).toContain("Observability");
- expect(container.textContent).toContain(
- "Use configured external tools as the jump hub for runtime, scopes, raw platform services, platform governance, and local settings without adding new backend APIs."
- );
- expect(container.textContent).toContain("Console surfaces");
- expect(container.textContent).toContain("Open Runtime Explorer");
- expect(container.textContent).toContain("Open Console Settings");
- expect(container.textContent).toContain("Open Scopes");
- expect(container.textContent).toContain("Open Platform Services");
- expect(container.textContent).toContain("Open Platform Governance");
- expect(screen.getByText("direct")).toBeTruthy();
- expect(screen.getByText("Workflow:19fe1b04")).toBeTruthy();
- expect(screen.getByText("cmd-123")).toBeTruthy();
- });
-});
diff --git a/apps/aevatar-console-web/src/pages/observability/index.tsx b/apps/aevatar-console-web/src/pages/observability/index.tsx
deleted file mode 100644
index c15a67be4..000000000
--- a/apps/aevatar-console-web/src/pages/observability/index.tsx
+++ /dev/null
@@ -1,501 +0,0 @@
-import {
- PageContainer,
- ProCard,
- ProDescriptions,
- ProForm,
- ProFormText,
-} from "@ant-design/pro-components";
-import type {
- ProDescriptionsItemProps,
- ProFormInstance,
-} from "@ant-design/pro-components";
-import { history } from "@/shared/navigation/history";
-import {
- buildRuntimeExplorerHref,
- buildRuntimeRunsHref,
- buildRuntimeWorkflowsHref,
-} from "@/shared/navigation/runtimeRoutes";
-import { Alert, Button, Col, Empty, Row, Space, Tag, Typography } from "antd";
-import React, { useEffect, useMemo, useRef, useState } from "react";
-import {
- buildObservabilityTargets,
- type ObservabilityContext,
- type ObservabilityTarget,
-} from "@/shared/observability/observabilityLinks";
-import { loadConsolePreferences } from "@/shared/preferences/consolePreferences";
-import {
- cardListActionStyle,
- cardListHeaderStyle,
- cardListItemStyle,
- cardListMainStyle,
- cardListStyle,
- cardListUrlStyle,
- compactPanelHeight,
- fillCardStyle,
- moduleCardProps,
- scrollViewportBodyStyle,
- scrollViewportStyle,
- stretchColumnStyle,
-} from "@/shared/ui/proComponents";
-
-type ObservabilityContextForm = ObservabilityContext;
-
-type ObservabilitySummaryRecord = {
- configuredCount: number;
- missingCount: number;
- workflow: string;
- actorId: string;
- commandId: string;
-};
-
-type InternalJumpItem = {
- id: string;
- title: string;
- description: string;
- href: string;
- enabled: boolean;
-};
-
-const consoleSurfacesCardStyle = {
- ...fillCardStyle,
- height: compactPanelHeight,
-} as const;
-
-const configuredTargetsCardStyle = {
- ...fillCardStyle,
- height: compactPanelHeight,
-} as const;
-
-const consoleSurfacesBodyStyle = scrollViewportBodyStyle;
-
-const configuredTargetsBodyStyle = scrollViewportBodyStyle;
-
-const consoleSurfacesViewportStyle = scrollViewportStyle;
-
-const configuredTargetsViewportStyle = scrollViewportStyle;
-
-const cardListMetaWrapStyle: React.CSSProperties = {
- display: "flex",
- flexWrap: "wrap",
- gap: 8,
-};
-
-const summaryColumns: ProDescriptionsItemProps[] = [
- {
- title: "Configured targets",
- dataIndex: "configuredCount",
- valueType: "digit",
- },
- {
- title: "Missing targets",
- dataIndex: "missingCount",
- valueType: "digit",
- },
- {
- title: "Workflow context",
- dataIndex: "workflow",
- render: (_, record) => record.workflow || "n/a",
- },
- {
- title: "Actor context",
- dataIndex: "actorId",
- render: (_, record) => record.actorId || "n/a",
- },
- {
- title: "Command context",
- dataIndex: "commandId",
- render: (_, record) => record.commandId || "n/a",
- },
-];
-
-function readContextFromUrl(): ObservabilityContext {
- if (typeof window === "undefined") {
- return {
- workflow: "",
- actorId: "",
- commandId: "",
- runId: "",
- stepId: "",
- };
- }
-
- const params = new URLSearchParams(window.location.search);
- return {
- workflow: params.get("workflow") ?? "",
- actorId: params.get("actorId") ?? "",
- commandId: params.get("commandId") ?? "",
- runId: params.get("runId") ?? "",
- stepId: params.get("stepId") ?? "",
- };
-}
-
-function renderConfiguredTargetCards(
- targets: ObservabilityTarget[]
-): React.ReactNode {
- if (targets.length === 0) {
- return (
-
- );
- }
-
- return (
-
- {targets.map((record) => (
-
-
-
-
- {record.label}
-
- {record.status}
-
-
-
- {record.description}
-
-
-
-
-
- {record.contextSummary}
-
- {record.status === "configured"
- ? "External target ready"
- : "No URL configured"}
-
-
-
- {record.homeUrl ? (
-
- {record.homeUrl}
-
- ) : (
-
No URL configured.
- )}
-
-
-
-
-
-
- ))}
-
- );
-}
-
-function renderInternalJumpCards(
- internalJumps: InternalJumpItem[]
-): React.ReactNode {
- if (internalJumps.length === 0) {
- return (
-
- );
- }
-
- return (
-
- {internalJumps.map((record) => (
-
-
-
-
- {record.title}
-
- {record.enabled ? "Ready" : "Missing context"}
-
-
-
- {record.description}
-
-
-
-
-
-
-
-
- ))}
-
- );
-}
-
-const ObservabilityPage: React.FC = () => {
- const preferences = useMemo(() => loadConsolePreferences(), []);
- const initialContext = useMemo(() => readContextFromUrl(), []);
- const formRef = useRef | undefined>(
- undefined
- );
- const [context, setContext] = useState(initialContext);
-
- const targets = useMemo(
- () => buildObservabilityTargets(preferences, context),
- [context, preferences]
- );
-
- const summaryRecord = useMemo(
- () => ({
- configuredCount: targets.filter(
- (target) => target.status === "configured"
- ).length,
- missingCount: targets.filter((target) => target.status === "missing")
- .length,
- workflow: context.workflow,
- actorId: context.actorId,
- commandId: context.commandId,
- }),
- [context.actorId, context.commandId, context.workflow, targets]
- );
-
- const internalJumps = useMemo(
- () => [
- {
- id: "jump-runs",
- title: "Open Runtime Runs",
- description: context.workflow
- ? `Open Runtime Runs with workflow=${context.workflow}.`
- : "Open Runtime Runs and keep the current workflow selection manual.",
- href: buildRuntimeRunsHref({
- workflow: context.workflow || undefined,
- }),
- enabled: true,
- },
- {
- id: "jump-actors",
- title: "Open Runtime Explorer",
- description: context.actorId
- ? `Open Runtime Explorer with actorId=${context.actorId}.`
- : "Provide actorId first to jump directly to Runtime Explorer.",
- href: buildRuntimeExplorerHref({
- actorId: context.actorId || undefined,
- }),
- enabled: Boolean(context.actorId),
- },
- {
- id: "jump-workflows",
- title: "Open Runtime Workflows",
- description: context.workflow
- ? `Open Runtime Workflows with workflow=${context.workflow}.`
- : "Provide workflow first to jump directly to Runtime Workflows.",
- href: buildRuntimeWorkflowsHref({
- workflow: context.workflow || undefined,
- }),
- enabled: Boolean(context.workflow),
- },
- {
- id: "jump-settings",
- title: "Open Console Settings",
- description:
- "Manage observability endpoint URLs and console preferences.",
- href: "/settings/console",
- enabled: true,
- },
- {
- id: "jump-scopes",
- title: "Open Scopes",
- description:
- "Inspect published workflow and script assets owned by GAgentService scopes without exposing platform tenant/app identity.",
- href: "/scopes",
- enabled: true,
- },
- {
- id: "jump-services",
- title: "Open Platform Services",
- description:
- "Inspect raw platform services, revisions, serving targets, rollouts, and traffic exposure.",
- href: "/services",
- enabled: true,
- },
- {
- id: "jump-governance",
- title: "Open Platform Governance",
- description:
- "Inspect raw bindings, policies, endpoint exposure, and activation capability views.",
- href: "/governance",
- enabled: true,
- },
- ],
- [context.actorId, context.workflow]
- );
-
- useEffect(() => {
- if (typeof window === "undefined") {
- return;
- }
-
- const url = new URL(window.location.href);
- const entries = Object.entries(context) as Array<
- [keyof ObservabilityContext, string]
- >;
- for (const [key, value] of entries) {
- if (value) {
- url.searchParams.set(key, value);
- } else {
- url.searchParams.delete(key);
- }
- }
- window.history.replaceState(null, "", `${url.pathname}${url.search}`);
- }, [context]);
-
- return (
-
-
-
-
-
-
-
- formRef={formRef}
- layout="vertical"
- initialValues={initialContext}
- onFinish={async (values) => {
- setContext({
- workflow: values.workflow?.trim() ?? "",
- actorId: values.actorId?.trim() ?? "",
- commandId: values.commandId?.trim() ?? "",
- runId: values.runId?.trim() ?? "",
- stepId: values.stepId?.trim() ?? "",
- });
- return true;
- }}
- submitter={{
- render: (props) => (
-
-
-
-
- ),
- }}
- >
-
-
-
-
-
-
-
-
-
-
-
-
- column={2}
- dataSource={summaryRecord}
- columns={summaryColumns}
- />
-
-
-
-
-
-
-
-
- {renderConfiguredTargetCards(targets)}
-
-
-
-
-
-
-
- {renderInternalJumpCards(internalJumps)}
-
-
-
-
-
- );
-};
-
-export default ObservabilityPage;
diff --git a/apps/aevatar-console-web/src/pages/overview/index.test.tsx b/apps/aevatar-console-web/src/pages/overview/index.test.tsx
index b0476429c..3c3eb564a 100644
--- a/apps/aevatar-console-web/src/pages/overview/index.test.tsx
+++ b/apps/aevatar-console-web/src/pages/overview/index.test.tsx
@@ -33,7 +33,7 @@ describe("OverviewPage", () => {
expect(container.textContent).toContain("Overview");
expect(container.textContent).toContain(
- "Overview of runtime workflows, scope assets, raw platform services, platform governance, actors, and observability."
+ "Overview of runtime workflows, scope assets, raw platform services, platform governance, and actors."
);
expect(container.textContent).toContain("Quick actions");
expect(container.textContent).toContain("Platform entry points");
@@ -42,7 +42,10 @@ describe("OverviewPage", () => {
expect(container.textContent).toContain("Platform services");
expect(container.textContent).toContain("Platform governance");
expect(container.textContent).toContain("Open Runtime Explorer");
- expect(container.textContent).toContain("Open Runtime Observability");
+ expect(container.textContent).toContain("Start direct workflow");
+ expect(container.textContent).not.toContain("Start preferred workflow");
+ expect(container.textContent).not.toContain("Preferred workflow");
+ expect(container.textContent).not.toContain("Open Runtime Observability");
expect(container.textContent).not.toContain("Open Studio");
await waitFor(() => {
expect(runtimeCatalogApi.listWorkflowNames).toHaveBeenCalled();
diff --git a/apps/aevatar-console-web/src/pages/overview/index.tsx b/apps/aevatar-console-web/src/pages/overview/index.tsx
index 5f19e78e6..3b000e019 100644
--- a/apps/aevatar-console-web/src/pages/overview/index.tsx
+++ b/apps/aevatar-console-web/src/pages/overview/index.tsx
@@ -13,7 +13,6 @@ import React, { useMemo } from "react";
import { history } from "@/shared/navigation/history";
import {
buildRuntimeExplorerHref,
- buildRuntimeObservabilityHref,
buildRuntimePrimitivesHref,
buildRuntimeRunsHref,
buildRuntimeWorkflowsHref,
@@ -24,8 +23,6 @@ import {
cardListHeaderStyle,
cardListItemStyle,
cardListMainStyle,
- cardListStyle,
- cardListUrlStyle,
cardStackStyle,
embeddedPanelStyle,
fillCardStyle,
@@ -38,7 +35,7 @@ import {
summaryMetricValueStyle,
stretchColumnStyle,
} from "@/shared/ui/proComponents";
-import type { ObservabilityOverviewItem } from "./useOverviewData";
+import { describeError } from "@/shared/ui/errorText";
import { useOverviewData } from "./useOverviewData";
type CapabilitySurfaceItem = {
@@ -111,89 +108,12 @@ const SummaryMetric: React.FC = ({ label, value }) => (
);
-function renderObservabilityTargetCards(
- observabilityTargets: ObservabilityOverviewItem[],
- preferredWorkflow: string,
-): React.ReactNode {
- if (observabilityTargets.length === 0) {
- return (
-
- No observability targets configured.
-
- );
- }
-
- return (
-
- {observabilityTargets.map((record) => (
-
-
-
-
- {record.label}
-
- {record.status}
-
-
-
- {record.description}
-
-
-
-
- {record.homeUrl ? (
-
- {record.homeUrl}
-
- ) : (
-
No URL configured.
- )}
-
-
-
-
-
-
- ))}
-
- );
-}
-
const OverviewPage: React.FC = () => {
const {
agentsQuery,
capabilitiesQuery,
- configuredObservabilityCount,
- grafanaBaseUrl,
humanFocusedWorkflows,
liveActors,
- observabilityTargets,
- preferences,
- profileData,
visibleCatalogItems,
workflowsQuery,
capabilityConnectorSummary,
@@ -240,20 +160,6 @@ const OverviewPage: React.FC = () => {
actionLabel: "Open Runtime Explorer",
onOpen: () => history.push(buildRuntimeExplorerHref()),
},
- {
- id: "surface-observability",
- title: "Observability",
- summary: `${configuredObservabilityCount}/${observabilityTargets.length} targets configured`,
- description:
- "Drive Grafana, Jaeger, Loki, and other external tools with the current runtime context.",
- actionLabel: "Open Runtime Observability",
- onOpen: () =>
- history.push(
- buildRuntimeObservabilityHref({
- workflow: preferences.preferredWorkflow,
- })
- ),
- },
{
id: "surface-scopes",
title: "Scope assets",
@@ -284,9 +190,6 @@ const OverviewPage: React.FC = () => {
],
[
agentsQuery.data?.length,
- configuredObservabilityCount,
- observabilityTargets.length,
- preferences.preferredWorkflow,
capabilitiesQuery.data?.primitives.length,
visibleCatalogItems.length,
]
@@ -294,15 +197,10 @@ const OverviewPage: React.FC = () => {
const platformQuickActions = useMemo(
() => [
{
- id: "quick-start-preferred",
- label: "Start preferred workflow",
+ id: "quick-start-direct",
+ label: "Start direct workflow",
primary: true,
- onOpen: () =>
- history.push(
- buildRuntimeRunsHref({
- workflow: preferences.preferredWorkflow,
- })
- ),
+ onOpen: () => history.push(buildRuntimeRunsHref({ workflow: "direct" })),
},
{
id: "quick-workflows",
@@ -324,16 +222,6 @@ const OverviewPage: React.FC = () => {
label: "Open Runtime Explorer",
onOpen: () => history.push(buildRuntimeExplorerHref()),
},
- {
- id: "quick-observability",
- label: "Open Runtime Observability",
- onOpen: () =>
- history.push(
- buildRuntimeObservabilityHref({
- workflow: preferences.preferredWorkflow,
- })
- ),
- },
{
id: "quick-scopes",
label: "Open scopes",
@@ -350,33 +238,23 @@ const OverviewPage: React.FC = () => {
onOpen: () => history.push("/governance"),
},
],
- [preferences.preferredWorkflow]
+ []
);
const localQuickActions = useMemo(
- () =>
- [
- {
- id: "quick-console-settings",
- label: "Open console settings",
- onOpen: () => history.push("/settings/console"),
- },
- grafanaBaseUrl
- ? {
- id: "quick-grafana-explore",
- label: "Open Grafana Explore",
- href: `${grafanaBaseUrl}/explore`,
- target: "_blank",
- rel: "noreferrer",
- }
- : null,
- ].filter(Boolean) as QuickActionItem[],
- [grafanaBaseUrl]
+ () => [
+ {
+ id: "quick-console-settings",
+ label: "Open settings",
+ onOpen: () => history.push("/settings"),
+ },
+ ],
+ []
);
return (
@@ -411,34 +289,6 @@ const OverviewPage: React.FC = () => {
/>
-
-
-
- Preferred workflow
- {preferences.preferredWorkflow}
-
-
-
-
-
-
- Observability
- {grafanaBaseUrl ? (
-
- ) : (
- Not configured
- )}
-
-
-
@@ -476,8 +326,7 @@ const OverviewPage: React.FC = () => {
type="secondary"
style={{ display: "block", marginTop: 4 }}
>
- Jump into browser-level preferences, local runtime
- configuration, and external observability tools.
+ Jump into account settings and local console entry points.
@@ -543,39 +392,21 @@ const OverviewPage: React.FC = () => {
-
- {profileData.preferredWorkflow}
- {profileData.observability}
-
-
-
-
+
-
@@ -664,7 +495,7 @@ const OverviewPage: React.FC = () => {
showIcon
type="error"
title="Failed to load capability digest"
- description={String(capabilitiesQuery.error)}
+ description={describeError(capabilitiesQuery.error)}
/>
) : (
@@ -744,20 +575,6 @@ const OverviewPage: React.FC = () => {
-
-
-
- {renderObservabilityTargetCards(
- observabilityTargets,
- preferences.preferredWorkflow,
- )}
-
-
-
);
};
diff --git a/apps/aevatar-console-web/src/pages/overview/useOverviewData.ts b/apps/aevatar-console-web/src/pages/overview/useOverviewData.ts
index b17198cce..7616c064e 100644
--- a/apps/aevatar-console-web/src/pages/overview/useOverviewData.ts
+++ b/apps/aevatar-console-web/src/pages/overview/useOverviewData.ts
@@ -2,29 +2,9 @@ import { useQuery } from "@tanstack/react-query";
import React, { useEffect, useMemo, useState } from "react";
import { runtimeCatalogApi } from "@/shared/api/runtimeCatalogApi";
import { runtimeQueryApi } from "@/shared/api/runtimeQueryApi";
-import { buildObservabilityTargets } from "@/shared/observability/observabilityLinks";
-import { loadConsolePreferences } from "@/shared/preferences/consolePreferences";
import { listVisibleWorkflowCatalogItems } from "@/shared/workflows/catalogVisibility";
-export type ConsoleProfileItem = {
- preferredWorkflow: string;
- observability: string;
-};
-
-export type ObservabilityOverviewItem = {
- id: string;
- label: string;
- description: string;
- status: "configured" | "missing";
- homeUrl: string;
-};
-
-function normalizeBaseUrl(value: string): string {
- return value.trim().replace(/\/+$/, "");
-}
-
export function useOverviewData() {
- const preferences = useMemo(() => loadConsolePreferences(), []);
const [deferredDetailsEnabled, setDeferredDetailsEnabled] = useState(false);
useEffect(() => {
@@ -132,52 +112,12 @@ export function useOverviewData() {
[agentsQuery.data]
);
- const grafanaBaseUrl = normalizeBaseUrl(preferences.grafanaBaseUrl);
-
- const profileData = useMemo
(
- () => ({
- preferredWorkflow: preferences.preferredWorkflow,
- observability: grafanaBaseUrl ? "Configured" : "Not configured",
- }),
- [grafanaBaseUrl, preferences.preferredWorkflow]
- );
-
- const observabilityTargets = useMemo(
- () =>
- buildObservabilityTargets(preferences, {
- workflow: preferences.preferredWorkflow,
- actorId: "",
- commandId: "",
- runId: "",
- stepId: "",
- }).map((target) => ({
- id: target.id,
- label: target.label,
- description: target.description,
- status: target.status,
- homeUrl: target.homeUrl,
- })),
- [preferences]
- );
-
- const configuredObservabilityCount = useMemo(
- () =>
- observabilityTargets.filter((target) => target.status === "configured")
- .length,
- [observabilityTargets]
- );
-
return {
agentsQuery,
capabilitiesQuery,
catalogQuery,
- configuredObservabilityCount,
- grafanaBaseUrl,
humanFocusedWorkflows,
liveActors,
- observabilityTargets,
- preferences,
- profileData,
visibleCatalogItems,
workflowsQuery,
capabilityConnectorSummary,
diff --git a/apps/aevatar-console-web/src/pages/primitives/index.tsx b/apps/aevatar-console-web/src/pages/primitives/index.tsx
index 2c14e4b86..9bc02da4d 100644
--- a/apps/aevatar-console-web/src/pages/primitives/index.tsx
+++ b/apps/aevatar-console-web/src/pages/primitives/index.tsx
@@ -48,6 +48,7 @@ import {
summaryMetricValueStyle,
stretchColumnStyle,
} from "@/shared/ui/proComponents";
+import { describeError } from "@/shared/ui/errorText";
type PrimitiveLibraryRow = WorkflowPrimitiveDescriptor & {
key: string;
@@ -369,7 +370,7 @@ const PrimitivesPage: React.FC = () => {
showIcon
type="error"
title="Failed to load primitive library"
- description={String(primitivesQuery.error)}
+ description={describeError(primitivesQuery.error)}
/>
) : !selectedPrimitive ? (
{
screen.getByRole("button", { name: "Open Runtime Explorer" })
).toBeTruthy();
expect(
- screen.getByRole("button", { name: "Open observability hub" })
- ).toBeTruthy();
+ screen.queryByRole("button", { name: "Open observability hub" })
+ ).toBeNull();
expect(screen.getByRole("button", { name: "Inspector" })).toBeTruthy();
expect(container.textContent).toContain("Launch rail");
expect(container.textContent).toContain("Run trace");
diff --git a/apps/aevatar-console-web/src/pages/runs/index.tsx b/apps/aevatar-console-web/src/pages/runs/index.tsx
index d8fc1b736..c8189b997 100644
--- a/apps/aevatar-console-web/src/pages/runs/index.tsx
+++ b/apps/aevatar-console-web/src/pages/runs/index.tsx
@@ -23,7 +23,6 @@ import { useQuery } from "@tanstack/react-query";
import { history } from "@/shared/navigation/history";
import {
buildRuntimeExplorerHref,
- buildRuntimeObservabilityHref,
buildRuntimeWorkflowsHref,
} from "@/shared/navigation/runtimeRoutes";
import {
@@ -53,7 +52,6 @@ import { runtimeActorsApi } from "@/shared/api/runtimeActorsApi";
import { runtimeCatalogApi } from "@/shared/api/runtimeCatalogApi";
import { runtimeRunsApi } from "@/shared/api/runtimeRunsApi";
import { formatDateTime } from "@/shared/datetime/dateTime";
-import { loadConsolePreferences } from "@/shared/preferences/consolePreferences";
import {
clearRecentRuns,
loadRecentRuns,
@@ -96,6 +94,7 @@ import {
composerRailDefaultWidth,
composerRailKeyboardStep,
type ConsoleViewKey,
+ defaultRunRouteName,
formatElapsedDuration,
humanInputColumns,
type HumanInputRecord,
@@ -173,12 +172,8 @@ function resolveRequestedServiceId(
}
const RunsPage: React.FC = () => {
- const preferences = useMemo(() => loadConsolePreferences(), []);
const [messageApi, messageContextHolder] = message.useMessage();
- const urlInitialFormValues = useMemo(
- () => readInitialRunFormValues(preferences.preferredWorkflow),
- [preferences.preferredWorkflow]
- );
+ const urlInitialFormValues = useMemo(() => readInitialRunFormValues(), []);
const draftRunKey = useMemo(() => {
if (typeof window === "undefined") {
return "";
@@ -240,7 +235,7 @@ const RunsPage: React.FC = () => {
scopeDraftPayload?.bundleName ??
(endpointInvocationDraftPayload ? "" : undefined) ??
initialFormValues.routeName ??
- preferences.preferredWorkflow
+ defaultRunRouteName
);
const [recentRuns, setRecentRuns] = useState(() =>
loadRecentRuns()
@@ -1356,20 +1351,6 @@ const RunsPage: React.FC = () => {
>
Open Runtime Explorer
-
- history.push(
- buildRuntimeObservabilityHref({
- workflow: routeName || undefined,
- actorId: actorId ?? undefined,
- commandId: commandId || undefined,
- runId: session.runId ?? undefined,
- })
- )
- }
- >
- Open observability hub
-
{
+ beforeEach(() => {
+ window.localStorage.clear();
+ jest.clearAllMocks();
+ });
+
+ it("renders signed-in account details", async () => {
+ persistAuthSession({
+ tokens: {
+ accessToken: "token",
+ tokenType: "Bearer",
+ expiresIn: 3600,
+ expiresAt: Date.now() + 60_000,
+ },
+ user: {
+ sub: "user-123",
+ email: "ada@example.com",
+ email_verified: true,
+ name: "Ada Lovelace",
+ roles: ["admin", "operator"],
+ groups: ["platform"],
+ },
+ });
+
+ renderWithQueryClient(React.createElement(AccountSettingsPage));
+
+ expect(await screen.findByText("Settings")).toBeTruthy();
+ expect(screen.getByText("Account profile")).toBeTruthy();
+ expect(screen.getAllByText("Ada Lovelace")).toHaveLength(2);
+ expect(screen.getAllByText("ada@example.com")).toHaveLength(2);
+ expect(screen.getByText("Session summary")).toBeTruthy();
+ expect(screen.getByText("Access notes")).toBeTruthy();
+ expect(screen.getByRole("button", { name: "Sign out" })).toBeTruthy();
+ });
+});
diff --git a/apps/aevatar-console-web/src/pages/settings/account.tsx b/apps/aevatar-console-web/src/pages/settings/account.tsx
new file mode 100644
index 000000000..41b92adc5
--- /dev/null
+++ b/apps/aevatar-console-web/src/pages/settings/account.tsx
@@ -0,0 +1,246 @@
+import { UserOutlined } from "@ant-design/icons";
+import { history } from "@/shared/navigation/history";
+import { buildRuntimeRunsHref } from "@/shared/navigation/runtimeRoutes";
+import { clearStoredAuthSession, loadRestorableAuthSession } from "@/shared/auth/session";
+import {
+ cardStackStyle,
+ fillCardStyle,
+ moduleCardProps,
+ summaryFieldGridStyle,
+ summaryMetricGridStyle,
+ stretchColumnStyle,
+} from "@/shared/ui/proComponents";
+import { Avatar, Button, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
+import React, { useMemo } from "react";
+import { ProCard } from "@ant-design/pro-components";
+import { SettingsPageShell, SummaryField, SummaryMetric } from "./shared";
+
+const accountUsageNotes = [
+ {
+ id: "account-session",
+ text: "This page reflects the active NyxID session restored from the browser before authenticated console requests are sent.",
+ },
+ {
+ id: "account-signout",
+ text: "Signing out clears the stored browser session and returns the console to the login screen.",
+ },
+];
+
+const compactIdentityStyle: React.CSSProperties = {
+ margin: 0,
+ maxWidth: "100%",
+};
+
+function formatCompactIdentifier(
+ value: string,
+ leading = 8,
+ trailing = 6
+): string {
+ if (value.length <= leading + trailing + 3) {
+ return value;
+ }
+
+ return `${value.slice(0, leading)}...${value.slice(-trailing)}`;
+}
+
+const AccountSettingsPage: React.FC = () => {
+ const authSession = useMemo(() => loadRestorableAuthSession(), []);
+
+ const accountDisplayName = useMemo(
+ () =>
+ authSession?.user.name ||
+ authSession?.user.email ||
+ authSession?.user.sub ||
+ "No active session",
+ [authSession]
+ );
+ const accountSecondaryText = useMemo(() => {
+ if (!authSession) {
+ return "No signed-in user information is available in this browser session.";
+ }
+
+ return authSession.user.email || authSession.user.sub;
+ }, [authSession]);
+ const rolesLabel = useMemo(() => {
+ const roles = authSession?.user.roles ?? [];
+ return roles.length > 0 ? roles.join(", ") : "n/a";
+ }, [authSession]);
+ const groupsLabel = useMemo(() => {
+ const groups = authSession?.user.groups ?? [];
+ return groups.length > 0 ? groups.join(", ") : "n/a";
+ }, [authSession]);
+ const compactUserId = useMemo(
+ () =>
+ authSession?.user.sub
+ ? formatCompactIdentifier(authSession.user.sub)
+ : "n/a",
+ [authSession]
+ );
+
+ const handleSignOut = () => {
+ clearStoredAuthSession();
+ window.location.replace("/login");
+ };
+
+ return (
+
+
+
+
+
+ {authSession ? (
+ <>
+
+ }
+ size={56}
+ src={authSession.user.picture}
+ />
+
+
+ {accountDisplayName}
+
+
+ {accountSecondaryText}
+
+
+
+
+
+ Signed in
+ {authSession.user.email_verified ? (
+ Email verified
+ ) : (
+ Email unverified
+ )}
+ {authSession.user.roles?.length ? (
+ {`${authSession.user.roles.length} roles`}
+ ) : null}
+ {authSession.user.groups?.length ? (
+ {`${authSession.user.groups.length} groups`}
+ ) : null}
+
+
+
+
+
+
+ {compactUserId}
+
+
+ }
+ />
+
+
+
+
+
+
+ history.push(
+ buildRuntimeRunsHref({
+ workflow: "direct",
+ })
+ )
+ }
+ >
+ Open runtime runs
+
+
+ Sign out
+
+
+ >
+ ) : (
+
+
+ {accountSecondaryText}
+
+
+ window.location.replace("/login")}
+ >
+ Sign in
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {accountUsageNotes.map((item) => (
+ {item.text}
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default AccountSettingsPage;
diff --git a/apps/aevatar-console-web/src/pages/settings/console.test.tsx b/apps/aevatar-console-web/src/pages/settings/console.test.tsx
deleted file mode 100644
index 90cf129f4..000000000
--- a/apps/aevatar-console-web/src/pages/settings/console.test.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { screen } from "@testing-library/react";
-import React from "react";
-import { runtimeCatalogApi } from "@/shared/api/runtimeCatalogApi";
-import { renderWithQueryClient } from "../../../tests/reactQueryTestUtils";
-import ConsoleSettingsPage from "./console";
-
-jest.mock("@/shared/api/runtimeCatalogApi", () => ({
- runtimeCatalogApi: {
- listWorkflowCatalog: jest.fn(async () => [
- {
- name: "incident_triage",
- description: "Incident triage",
- category: "ops",
- group: "starter",
- groupLabel: "Starter",
- sortOrder: 1,
- source: "home",
- sourceLabel: "Saved",
- showInLibrary: true,
- isPrimitiveExample: false,
- requiresLlmProvider: true,
- primitives: ["llm_call"],
- },
- ]),
- },
-}));
-
-describe("ConsoleSettingsPage", () => {
- beforeEach(() => {
- window.localStorage.clear();
- jest.clearAllMocks();
- });
-
- it("renders console preference sections on the dedicated settings page", async () => {
- renderWithQueryClient(React.createElement(ConsoleSettingsPage));
-
- expect(await screen.findByText("Console preferences")).toBeTruthy();
- expect(screen.getByText("Workflow defaults")).toBeTruthy();
- expect(screen.getByText("Observability URLs")).toBeTruthy();
- expect(screen.getByText("Runtime explorer defaults")).toBeTruthy();
- expect(runtimeCatalogApi.listWorkflowCatalog).toHaveBeenCalled();
- });
-});
diff --git a/apps/aevatar-console-web/src/pages/settings/console.tsx b/apps/aevatar-console-web/src/pages/settings/console.tsx
deleted file mode 100644
index 3d8595dd9..000000000
--- a/apps/aevatar-console-web/src/pages/settings/console.tsx
+++ /dev/null
@@ -1,476 +0,0 @@
-import type { ProFormInstance } from "@ant-design/pro-components";
-import {
- PageContainer,
- ProCard,
- ProForm,
- ProFormDigit,
- ProFormSelect,
- ProFormText,
-} from "@ant-design/pro-components";
-import { useQuery } from "@tanstack/react-query";
-import { history } from "@/shared/navigation/history";
-import {
- buildRuntimeObservabilityHref,
- buildRuntimeRunsHref,
-} from "@/shared/navigation/runtimeRoutes";
-import { Button, Col, Empty, Row, Space, Tag, Typography, message } from "antd";
-import React, { useMemo, useRef, useState } from "react";
-import { runtimeCatalogApi } from "@/shared/api/runtimeCatalogApi";
-import {
- buildObservabilityTargets,
- type ObservabilityTarget,
-} from "@/shared/observability/observabilityLinks";
-import {
- type ActorGraphDirection,
- type ConsolePreferences,
- loadConsolePreferences,
- resetConsolePreferences,
- saveConsolePreferences,
-} from "@/shared/preferences/consolePreferences";
-import { buildWorkflowCatalogOptions } from "@/shared/workflows/catalogVisibility";
-import {
- cardListActionStyle,
- cardListHeaderStyle,
- cardListItemStyle,
- cardListMainStyle,
- cardListStyle,
- cardListUrlStyle,
- cardStackStyle,
- fillCardStyle,
- moduleCardProps,
- summaryFieldGridStyle,
- summaryFieldLabelStyle,
- summaryFieldStyle,
- summaryMetricGridStyle,
- summaryMetricStyle,
- summaryMetricValueStyle,
- stretchColumnStyle,
-} from "@/shared/ui/proComponents";
-
-type ConsoleSettingsSummaryRecord = {
- preferredWorkflow: string;
- graphDirection: ActorGraphDirection;
- observabilityTargetsConfigured: number;
-};
-
-const consoleUsageNotes = [
- {
- id: "console-defaults",
- text: "These settings are stored locally in the browser and apply to console navigation, runtime explorer defaults, and outbound observability links.",
- },
- {
- id: "console-observability",
- text: "Grafana, Jaeger, and Loki URLs are not proxied. The console only builds outbound links and preserves current workflow context.",
- },
-];
-
-type SummaryFieldProps = {
- label: string;
- value: React.ReactNode;
-};
-
-type SummaryMetricProps = {
- label: string;
- value: React.ReactNode;
-};
-
-const SummaryField: React.FC = ({ label, value }) => (
-
- {label}
- {value}
-
-);
-
-const SummaryMetric: React.FC = ({ label, value }) => (
-
- {label}
- {value}
-
-);
-
-function renderObservabilityEndpointCards(
- observabilityTargets: ObservabilityTarget[],
-): React.ReactNode {
- if (observabilityTargets.length === 0) {
- return (
-
- No observability targets configured.
-
- );
- }
-
- return (
-
- {observabilityTargets.map((record) => (
-
-
-
-
- {record.label}
-
- {record.status}
-
-
-
- {record.description}
-
-
-
-
- {record.homeUrl ? (
-
- {record.homeUrl}
-
- ) : (
-
No URL configured.
- )}
-
-
-
- Open
-
-
- Explore
-
-
-
- ))}
-
- );
-}
-
-const ConsoleSettingsPage: React.FC = () => {
- const formRef = useRef | undefined>(
- undefined
- );
- const [messageApi, messageContextHolder] = message.useMessage();
- const [preferences, setPreferences] = useState(
- loadConsolePreferences()
- );
-
- const workflowCatalogQuery = useQuery({
- queryKey: ["settings-console", "workflow-catalog"],
- queryFn: () => runtimeCatalogApi.listWorkflowCatalog(),
- });
-
- const workflowOptions = useMemo(
- () =>
- buildWorkflowCatalogOptions(
- workflowCatalogQuery.data ?? [],
- preferences.preferredWorkflow
- ),
- [preferences.preferredWorkflow, workflowCatalogQuery.data]
- );
-
- const observabilityTargets = useMemo(
- () =>
- buildObservabilityTargets(preferences, {
- workflow: preferences.preferredWorkflow,
- actorId: "",
- commandId: "",
- runId: "",
- stepId: "",
- }),
- [preferences]
- );
-
- const summaryRecord = useMemo(
- () => ({
- preferredWorkflow: preferences.preferredWorkflow,
- graphDirection: preferences.actorGraphDirection,
- observabilityTargetsConfigured: observabilityTargets.filter(
- (target) => target.status === "configured"
- ).length,
- }),
- [observabilityTargets, preferences]
- );
-
- const handleSavePreferences = async (values: ConsolePreferences) => {
- const next = saveConsolePreferences(values);
- setPreferences(next);
- messageApi.success("Console preferences saved.");
- return true;
- };
-
- const handleResetPreferences = () => {
- const next = resetConsolePreferences();
- setPreferences(next);
- formRef.current?.setFieldsValue(next);
- messageApi.success("Console preferences reset to defaults.");
- };
-
- return (
- history.push("/overview")}
- extra={[
- history.push(buildRuntimeObservabilityHref())}
- >
- Open observability hub
- ,
- ]}
- >
- {messageContextHolder}
-
-
-
-
- formRef={formRef}
- layout="vertical"
- initialValues={preferences}
- onFinish={handleSavePreferences}
- submitter={{
- render: (props) => (
-
- props.form?.submit?.()}
- >
- Save preferences
-
-
- Reset defaults
-
-
- history.push(
- buildRuntimeRunsHref({
- workflow:
- formRef.current?.getFieldValue(
- "preferredWorkflow"
- ) ?? preferences.preferredWorkflow,
- })
- )
- }
- >
- Open preferred workflow
-
-
- ),
- }}
- >
-
-
-
-
-
- Loading workflows...
-
- ) : (
-
- ),
- }}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- name="actorGraphDirection"
- label="Actor graph direction"
- options={[
- { label: "Both", value: "Both" },
- { label: "Outbound", value: "Outbound" },
- { label: "Inbound", value: "Inbound" },
- ]}
- rules={[
- {
- required: true,
- message: "Graph direction is required.",
- },
- ]}
- />
-
-
-
-
-
-
-
-
-
-
-
-
-
- {summaryRecord.preferredWorkflow}
- {summaryRecord.graphDirection}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {renderObservabilityEndpointCards(observabilityTargets)}
-
-
-
- {consoleUsageNotes.map((item) => (
- {item.text}
- ))}
-
-
-
-
-
-
- );
-};
-
-export default ConsoleSettingsPage;
diff --git a/apps/aevatar-console-web/src/pages/settings/shared.tsx b/apps/aevatar-console-web/src/pages/settings/shared.tsx
new file mode 100644
index 000000000..ff40eb590
--- /dev/null
+++ b/apps/aevatar-console-web/src/pages/settings/shared.tsx
@@ -0,0 +1,60 @@
+import { PageContainer } from "@ant-design/pro-components";
+import { history } from "@/shared/navigation/history";
+import { Typography } from "antd";
+import React from "react";
+import {
+ summaryFieldLabelStyle,
+ summaryFieldStyle,
+ summaryMetricStyle,
+ summaryMetricValueStyle,
+} from "@/shared/ui/proComponents";
+
+type SettingsPageShellProps = {
+ children: React.ReactNode;
+ content: string;
+};
+
+type SummaryFieldProps = {
+ label: string;
+ value: React.ReactNode;
+};
+
+type SummaryMetricProps = {
+ label: string;
+ value: React.ReactNode;
+};
+
+function renderSummaryFieldValue(value: React.ReactNode): React.ReactNode {
+ if (typeof value === "string" || typeof value === "number") {
+ return {value};
+ }
+
+ return value;
+}
+
+export const SummaryField: React.FC = ({ label, value }) => (
+
+
{label}
+
{renderSummaryFieldValue(value)}
+
+);
+
+export const SummaryMetric: React.FC = ({ label, value }) => (
+
+ {label}
+ {value}
+
+);
+
+export const SettingsPageShell: React.FC = ({
+ children,
+ content,
+}) => (
+ history.push("/overview")}
+ >
+ {children}
+
+);
diff --git a/apps/aevatar-console-web/src/pages/studio/components/StudioBootstrapGate.tsx b/apps/aevatar-console-web/src/pages/studio/components/StudioBootstrapGate.tsx
index fe1d5ff65..3b9a58ad3 100644
--- a/apps/aevatar-console-web/src/pages/studio/components/StudioBootstrapGate.tsx
+++ b/apps/aevatar-console-web/src/pages/studio/components/StudioBootstrapGate.tsx
@@ -1,5 +1,6 @@
import { Tag, Typography } from 'antd';
import React from 'react';
+import { describeError } from '@/shared/ui/errorText';
import { embeddedPanelStyle } from '@/shared/ui/proComponents';
type StudioBootstrapGateProps = {
@@ -34,6 +35,15 @@ const studioBootstrapNoticeCardStyle: React.CSSProperties = {
padding: '12px 14px',
};
+const studioBootstrapNoticeDescriptionStyle: React.CSSProperties = {
+ margin: 0,
+ display: '-webkit-box',
+ overflow: 'hidden',
+ wordBreak: 'break-word',
+ WebkitBoxOrient: 'vertical',
+ WebkitLineClamp: 2,
+};
+
function getStudioBootstrapNoticeAccent(
type: StudioBootstrapNoticeProps['type'],
): { background: string; borderColor: string; label: string } {
@@ -76,7 +86,11 @@ const StudioBootstrapNotice: React.FC = ({
>
{accent.label}
{title}
-
+
{description}
@@ -84,7 +98,7 @@ const StudioBootstrapNotice: React.FC = ({
};
function renderErrorMessage(error: unknown): string {
- return error instanceof Error ? error.message : String(error);
+ return describeError(error);
}
const StudioBootstrapGate: React.FC = ({
diff --git a/apps/aevatar-console-web/src/pages/studio/components/StudioWorkbenchSections.tsx b/apps/aevatar-console-web/src/pages/studio/components/StudioWorkbenchSections.tsx
index e9305769e..aa544ec4c 100644
--- a/apps/aevatar-console-web/src/pages/studio/components/StudioWorkbenchSections.tsx
+++ b/apps/aevatar-console-web/src/pages/studio/components/StudioWorkbenchSections.tsx
@@ -104,6 +104,7 @@ import {
summaryMetricStyle,
summaryMetricValueStyle,
} from '@/shared/ui/proComponents';
+import { describeError } from '@/shared/ui/errorText';
type QueryState = {
readonly isLoading: boolean;
@@ -951,7 +952,7 @@ const StudioScopeBindingPanel: React.FC = ({
) : !binding?.available ? (
= ({
) : (
@@ -1695,7 +1696,7 @@ export const StudioWorkflowsPage: React.FC = ({
) : filteredWorkflows.length > 0 ? (
workflowLayout === 'grid' ? (
@@ -2587,7 +2588,7 @@ export const StudioExecutionPage: React.FC = ({
) : selectedExecution.data ? (
renderExecutionLogsSection({ fullscreen: true })
@@ -2609,14 +2610,14 @@ export const StudioExecutionPage: React.FC = ({
) : null}
{selectedExecution.isError ? (
) : null}
{executionNotice ? (
@@ -3524,14 +3525,14 @@ export const StudioEditorPage: React.FC = ({
key="selected-workflow-error"
type="error"
title="Failed to load Studio workflow"
- description={String(selectedWorkflow.error)}
+ description={describeError(selectedWorkflow.error)}
/>
) : templateWorkflow.isError ? (
) : null;
@@ -4529,7 +4530,7 @@ export const StudioRolesPage: React.FC = ({
{roles.isError ? (
- {String(roles.error)}
+ {describeError(roles.error)}
) : roles.isLoading ? (
Loading roles...
) : (
@@ -5064,7 +5065,7 @@ export const StudioConnectorsPage: React.FC = ({
{connectors.isError ? (
- {String(connectors.error)}
+ {describeError(connectors.error)}
) : connectors.isLoading ? (
Loading connectors...
) : (
@@ -5677,7 +5678,7 @@ export const StudioSettingsPage: React.FC = ({
) : settingsDraft ? (
@@ -6187,13 +6188,13 @@ export const StudioSettingsPage: React.FC
= ({
) : settings.isError ? (
) : settingsDraft ? (
@@ -6254,7 +6255,7 @@ export const StudioSettingsPage: React.FC
= ({
) : workspaceSettings.data ? (
diff --git a/apps/aevatar-console-web/src/pages/studio/index.tsx b/apps/aevatar-console-web/src/pages/studio/index.tsx
index b92bd5195..1655724a8 100644
--- a/apps/aevatar-console-web/src/pages/studio/index.tsx
+++ b/apps/aevatar-console-web/src/pages/studio/index.tsx
@@ -22,12 +22,6 @@ import React, {
import { ensureActiveAuthSession } from '@/shared/auth/client';
import { getNyxIDRuntimeConfig } from '@/shared/auth/config';
import { sanitizeReturnTo } from '@/shared/auth/session';
-import {
- CONSOLE_PREFERENCES_UPDATED_EVENT,
- loadConsolePreferences,
- type StudioAppearanceTheme,
- type StudioColorMode,
-} from '@/shared/preferences/consolePreferences';
import {
clearPlaygroundPromptHistory,
loadPlaygroundPromptHistory,
@@ -163,11 +157,19 @@ type StudioSettingsDraft = {
readonly providers: StudioProviderSettings[];
};
+type StudioAppearanceTheme = 'blue' | 'coral' | 'forest';
+type StudioColorMode = 'light' | 'dark';
+
type StudioAppearancePreferences = {
readonly appearanceTheme: StudioAppearanceTheme;
readonly colorMode: StudioColorMode;
};
+const defaultStudioAppearance: StudioAppearancePreferences = {
+ appearanceTheme: 'blue',
+ colorMode: 'light',
+};
+
let studioLocalKeyCounter = 0;
const STUDIO_AUTO_RELOGIN_ATTEMPT_KEY =
'aevatar-console:studio:auto-relogin:';
@@ -790,14 +792,6 @@ function createProviderDraft(
};
}
-function readStudioAppearancePreferences(): StudioAppearancePreferences {
- const preferences = loadConsolePreferences();
- return {
- appearanceTheme: preferences.studioAppearanceTheme,
- colorMode: preferences.studioColorMode,
- };
-}
-
function isExecutionStopAllowed(status: string | undefined): boolean {
const normalized = status?.trim().toLowerCase() ?? '';
return !['completed', 'failed', 'stopped', 'cancelled'].includes(normalized);
@@ -1001,8 +995,6 @@ const StudioPage: React.FC = () => {
const [selectedProviderName, setSelectedProviderName] = useState('');
const [settingsPending, setSettingsPending] = useState(false);
const [settingsNotice, setSettingsNotice] = useState
(null);
- const [studioAppearance, setStudioAppearance] =
- useState(() => readStudioAppearancePreferences());
const [runtimeTestPending, setRuntimeTestPending] = useState(false);
const [runtimeTestResult, setRuntimeTestResult] =
useState(null);
@@ -1034,6 +1026,7 @@ const StudioPage: React.FC = () => {
Boolean(authSessionQuery.data?.authenticated);
const studioHostReady =
studioHostAccessResolved && studioHostAuthenticated;
+ const studioAppearance = defaultStudioAppearance;
useEffect(() => {
if (typeof window === 'undefined') {
@@ -1281,30 +1274,6 @@ const StudioPage: React.FC = () => {
}),
});
- useEffect(() => {
- if (typeof window === 'undefined') {
- return undefined;
- }
-
- const syncStudioAppearance = () => {
- setStudioAppearance(readStudioAppearancePreferences());
- };
-
- window.addEventListener(
- CONSOLE_PREFERENCES_UPDATED_EVENT,
- syncStudioAppearance,
- );
- window.addEventListener('storage', syncStudioAppearance);
-
- return () => {
- window.removeEventListener(
- CONSOLE_PREFERENCES_UPDATED_EVENT,
- syncStudioAppearance,
- );
- window.removeEventListener('storage', syncStudioAppearance);
- };
- }, []);
-
useEffect(() => {
if (
selectedWorkflowId ||
diff --git a/apps/aevatar-console-web/src/pages/workflows/index.tsx b/apps/aevatar-console-web/src/pages/workflows/index.tsx
index cd21dd6bf..4e25d5a40 100644
--- a/apps/aevatar-console-web/src/pages/workflows/index.tsx
+++ b/apps/aevatar-console-web/src/pages/workflows/index.tsx
@@ -65,6 +65,7 @@ import {
tallScrollPanelStyle,
stretchColumnStyle,
} from "@/shared/ui/proComponents";
+import { describeError } from "@/shared/ui/errorText";
import WorkflowYamlViewer from "./WorkflowYamlViewer";
import {
buildStepRows,
@@ -1602,7 +1603,7 @@ const WorkflowsPage: React.FC = () => {
showIcon
type="error"
title="Failed to load workflow catalog"
- description={String(catalogQuery.error)}
+ description={describeError(catalogQuery.error)}
/>
) : null}
@@ -1658,7 +1659,7 @@ const WorkflowsPage: React.FC = () => {
showIcon
type="error"
title="Failed to load workflow detail"
- description={String(detailQuery.error)}
+ description={describeError(detailQuery.error)}
/>
) : detailQuery.data ? (
diff --git a/apps/aevatar-console-web/src/shared/api/http/client.ts b/apps/aevatar-console-web/src/shared/api/http/client.ts
index 8d2bf9053..f92a2f41b 100644
--- a/apps/aevatar-console-web/src/shared/api/http/client.ts
+++ b/apps/aevatar-console-web/src/shared/api/http/client.ts
@@ -1,5 +1,6 @@
import { authFetch } from "@/shared/auth/fetch";
import type { Decoder } from "../decodeUtils";
+import { readResponseError } from "./error";
export type QueryValue =
| string
@@ -14,24 +15,6 @@ const JSON_HEADERS = {
"Content-Type": "application/json",
};
-async function readError(response: Response): Promise {
- const text = await response.text();
- if (!text) {
- return `HTTP ${response.status}`;
- }
-
- try {
- const payload = JSON.parse(text) as {
- message?: string;
- error?: string;
- code?: string;
- };
- return payload.message || payload.error || payload.code || text;
- } catch {
- return text;
- }
-}
-
export function withQuery(
path: string,
query?: Record
@@ -70,7 +53,7 @@ export async function requestJson(
): Promise {
const response = await authFetch(input, init);
if (!response.ok) {
- throw new Error(await readError(response));
+ throw new Error(await readResponseError(response));
}
return decoder(await response.json());
diff --git a/apps/aevatar-console-web/src/shared/api/http/error.ts b/apps/aevatar-console-web/src/shared/api/http/error.ts
new file mode 100644
index 000000000..ff23b0bef
--- /dev/null
+++ b/apps/aevatar-console-web/src/shared/api/http/error.ts
@@ -0,0 +1,73 @@
+function normalizeWhitespace(value: string | null | undefined): string {
+ return String(value ?? "")
+ .replace(/\s+/g, " ")
+ .trim();
+}
+
+function formatHttpError(status: number, statusText: string): string {
+ const normalizedStatusText = normalizeWhitespace(statusText);
+ return normalizedStatusText ? `HTTP ${status} ${normalizedStatusText}` : `HTTP ${status}`;
+}
+
+function stripHtmlTags(value: string): string {
+ return value
+ .replace(/