zz-meridian 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/migrations.js +24 -0
- package/dist/ownership.js +2 -2
- package/package.json +1 -1
- package/payload/CHANGELOG.md +23 -0
- package/payload/CONTRIBUTING.md +2 -1
- package/payload/app/(dashboard)/(overview)/README.md +18 -1
- package/payload/app/(dashboard)/(overview)/page.tsx +13 -6
- package/payload/app/(dashboard)/analytics/README.md +13 -1
- package/payload/app/(dashboard)/analytics/page.tsx +10 -4
- package/payload/app/(dashboard)/customers/README.md +12 -1
- package/payload/app/(dashboard)/health/README.md +9 -1
- package/payload/app/(dashboard)/health/page.tsx +6 -5
- package/payload/app/(dashboard)/keys/README.md +8 -1
- package/payload/app/(dashboard)/members/README.md +12 -1
- package/payload/app/(dashboard)/requests/README.md +15 -1
- package/payload/app/(dashboard)/requests/[id]/README.md +12 -1
- package/payload/app/(dashboard)/requests/[id]/page.tsx +3 -1
- package/payload/app/(dashboard)/requests/page.tsx +2 -2
- package/payload/app/(dashboard)/settings/README.md +7 -0
- package/payload/app/api/assistant/route.ts +23 -3
- package/payload/app/embed/health/README.md +6 -1
- package/payload/app/embed/health/page.tsx +14 -9
- package/payload/app/embed/health/view.tsx +4 -11
- package/payload/app/embed/overview/README.md +6 -1
- package/payload/app/embed/overview/page.tsx +13 -17
- package/payload/app/embed/overview/view.tsx +15 -30
- package/payload/app/embed/proposal/README.md +8 -1
- package/payload/app/embed/proposal/page.tsx +2 -1
- package/payload/app/embed/proposal/view.tsx +28 -16
- package/payload/app/embed/requests/README.md +6 -1
- package/payload/app/embed/requests/page.tsx +9 -7
- package/payload/app/embed/requests/view.tsx +9 -15
- package/payload/app/system/(atlas)/[section]/[id]/page.tsx +23 -1
- package/payload/decisions/0011-one-context-two-agents.md +36 -0
- package/payload/docs/agents.md +36 -13
- package/payload/docs/assistant.md +8 -6
- package/payload/docs/benchmark.md +18 -0
- package/payload/docs/surfaces.md +5 -4
- package/payload/docs/voice.md +1 -1
- package/payload/package.json +1 -1
- package/payload/scripts/assistant.ts +50 -5
- package/payload/scripts/check.ts +2 -0
- package/payload/scripts/fake-llm.ts +9 -0
- package/payload/skills/zz-meridian/references/agents.md +36 -13
- package/payload/skills/zz-meridian/references/cache.md +16 -3
- package/payload/skills/zz-meridian/references/customize.md +8 -2
- package/payload/skills/zz-meridian/references/ownership.md +1 -1
- package/payload/skills/zz-meridian/references/update.md +15 -0
- package/payload/skills/zz-meridian/references/voice.md +1 -1
- package/payload/src/components/base/shell/index.tsx +21 -5
- package/payload/src/components/base/surface/README.md +2 -1
- package/payload/src/components/base/surface/index.tsx +40 -7
- package/payload/src/components/base/use-share-view.ts +12 -8
- package/payload/src/components/charts/meridian/README.md +1 -0
- package/payload/src/components/charts/meridian/index.tsx +12 -2
- package/payload/src/components/charts/trend-chart/README.md +2 -0
- package/payload/src/components/charts/trend-chart/index.tsx +12 -2
- package/payload/src/components/charts/trend-chart/preview.tsx +12 -0
- package/payload/src/components/patterns/ask-about/README.md +5 -2
- package/payload/src/components/patterns/ask-about/index.tsx +9 -3
- package/payload/src/components/patterns/assistant/index.tsx +17 -3
- package/payload/src/components/patterns/filter-bar/README.md +2 -1
- package/payload/src/components/patterns/filter-bar/index.tsx +4 -0
- package/payload/src/components/patterns/metric-tile/README.md +5 -3
- package/payload/src/components/patterns/metric-tile/index.tsx +34 -2
- package/payload/src/components/patterns/metric-tile/preview.tsx +12 -0
- package/payload/src/data/access.ts +16 -3
- package/payload/src/data/collections.ts +85 -2
- package/payload/src/data/metrics.ts +64 -0
- package/payload/src/data/request-methods.ts +2 -0
- package/payload/src/data/requests.ts +22 -4
- package/payload/src/data/sample.ts +3 -3
- package/payload/src/lib/agent-guidance.ts +11 -0
- package/payload/src/lib/assistant/prompt.ts +25 -5
- package/payload/src/lib/assistant/respond.ts +21 -3
- package/payload/src/lib/assistant/tools.ts +40 -6
- package/payload/src/lib/host.ts +1 -1
- package/payload/src/lib/insight.ts +81 -0
- package/payload/src/lib/shared-context.ts +104 -0
- package/payload/src/system/page-stage.tsx +24 -8
- package/payload/src/views/analytics-context.ts +79 -0
- package/payload/src/views/analytics.tsx +23 -17
- package/payload/src/views/customers-context.ts +72 -0
- package/payload/src/views/customers.tsx +6 -3
- package/payload/src/views/health-context.ts +53 -0
- package/payload/src/views/health.tsx +14 -3
- package/payload/src/views/keys-context.ts +38 -0
- package/payload/src/views/keys.tsx +3 -0
- package/payload/src/views/members-context.ts +40 -0
- package/payload/src/views/members.tsx +4 -0
- package/payload/src/views/overview-context.ts +179 -0
- package/payload/src/views/overview.tsx +44 -29
- package/payload/src/views/request-context.ts +46 -0
- package/payload/src/views/request.tsx +4 -1
- package/payload/src/views/requests-context.ts +85 -0
- package/payload/src/views/requests.tsx +12 -6
- package/payload/src/views/sample-footer.tsx +20 -7
- package/payload/src/views/settings.tsx +40 -2
- package/payload/src/views/tools.ts +142 -0
- package/payload/tests/agents.test.tsx +12 -0
- package/payload/tests/assistant-guard.test.ts +12 -0
- package/payload/tests/embed-requests-provenance.test.tsx +4 -2
- package/payload/tests/empty-views.test.tsx +3 -3
- package/payload/tests/request-view-replay.test.tsx +1 -1
- package/payload/tests/shared-context.test.tsx +167 -0
package/dist/migrations.js
CHANGED
|
@@ -205,7 +205,31 @@ export const RELEASE_MIGRATIONS = [
|
|
|
205
205
|
instructions: 'Read "Writes authorize, then invalidate" and "The assistant route" in references/cache.md. Call `resolveAccess()` first in the route and in each Server Action, answer 401 or an error result when there is no session, and hand the assistant only the collections the caller may read. The route passes a guard that authorizes each approved change again when it runs, so `src/lib/assistant/tools.ts` and `src/lib/assistant/respond.ts` take that guard too: bring them over from the release with the route.',
|
|
206
206
|
checks: GATE_AND_BUILD,
|
|
207
207
|
},
|
|
208
|
+
{
|
|
209
|
+
id: 'share-view-context',
|
|
210
|
+
since: '0.9.0',
|
|
211
|
+
applies: (root) => matching(root, (_, src) => callArity(src, 'useShareView').some((n) => n === 2)),
|
|
212
|
+
summary: 'useShareView takes one shared context, not a sentence and an object.',
|
|
213
|
+
instructions: 'Read "Legible" in references/agents.md and "A shared context" in references/customize.md. Build the view\'s context with the fields of `SharedContext` from `src/lib/shared-context.ts` (scope, freshness, each figure with its unit, change and definition, what code finds, what the data cannot say) in a pure function beside the view, and pass it alone: `useShareView(context)`. Both agents read it: an MCP host and the console\'s assistant.',
|
|
214
|
+
checks: GATE_AND_BUILD,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'agent-reads-section',
|
|
218
|
+
since: '0.9.0',
|
|
219
|
+
applies: pagesWithoutAgentReads,
|
|
220
|
+
summary: 'A page under app/(dashboard)/ or app/embed/ says what the agent reads.',
|
|
221
|
+
instructions: 'Read "A page spec" in references/customize.md. Under `## Agents` in the page\'s README.md, add `### What the agent reads`: what the page\'s shared context tells each agent, with a real line of it, and what the MCP App and the console\'s assistant each receive. `scripts/check.ts` requires it.',
|
|
222
|
+
checks: ['gate'],
|
|
223
|
+
},
|
|
208
224
|
];
|
|
225
|
+
/** The page specifications under the console and embed folders that lack the part the 0.9.0 check requires. */
|
|
226
|
+
function pagesWithoutAgentReads(root) {
|
|
227
|
+
const files = [];
|
|
228
|
+
for (const d of ['app/(dashboard)', 'app/embed'])
|
|
229
|
+
walk(root, d, files, /^README\.md$/);
|
|
230
|
+
const hits = files.filter((rel) => !/^## Agents\n[\s\S]*?^### What the agent reads$/m.test(text(root, rel) ?? ''));
|
|
231
|
+
return hits.length > 0 ? hits : null;
|
|
232
|
+
}
|
|
209
233
|
/** The registry entries that apply to a project moving from `source` to `target`, as journal migrations. */
|
|
210
234
|
export function releaseMigrations(root, source, target) {
|
|
211
235
|
const out = [];
|
package/dist/ownership.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const HASH = /^sha256-[0-9a-f]{64}$/;
|
|
2
2
|
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
3
3
|
/** The library modules Meridian's components and gates import; the rest of src/lib belongs to the template's pages. */
|
|
4
|
-
export const LIB = ['cn', 'format', 'format-date', 'period', 'color', 'host', 'preferences', 'csv', 'safe-markdown', 'logo', 'collection', 'live'].map((n) => `src/lib/${n}.ts`);
|
|
4
|
+
export const LIB = ['cn', 'format', 'format-date', 'period', 'color', 'host', 'preferences', 'csv', 'safe-markdown', 'logo', 'collection', 'live', 'shared-context', 'agent-guidance', 'insight'].map((n) => `src/lib/${n}.ts`);
|
|
5
5
|
/** The single files adopt copies besides the folders and the library: all of them must be in a payload that is whole. */
|
|
6
6
|
export const FIXED = [...LIB, 'src/lib/assistant/prompt.ts', 'src/views/console-chrome.tsx', 'tests/setup.ts'];
|
|
7
7
|
/** The one rule, a plain-English line per clause. `references/ownership.md` renders it, so the guidance cannot drift. */
|
|
8
8
|
export const OWNERSHIP = [
|
|
9
9
|
'Managed: the payload files under `tokens/`, `src/styles/` and `src/components/`, except each component\'s `README.md` and `preview.tsx`.',
|
|
10
10
|
'Managed: the distributed scripts under `scripts/`, except `scripts/verify.config.ts`, the first-load baseline `optional:scripts/verify.baseline.json` and a team-local `optional:scripts/check.local.ts`.',
|
|
11
|
-
'Managed: the library helpers `src/lib/cn.ts`, `src/lib/format.ts`, `src/lib/format-date.ts`, `src/lib/period.ts`, `src/lib/color.ts`, `src/lib/host.ts`, `src/lib/preferences.ts`, `src/lib/csv.ts`, `src/lib/safe-markdown.ts`, `src/lib/logo.ts`, `src/lib/collection.ts` and `src/lib/
|
|
11
|
+
'Managed: the library helpers `src/lib/cn.ts`, `src/lib/format.ts`, `src/lib/format-date.ts`, `src/lib/period.ts`, `src/lib/color.ts`, `src/lib/host.ts`, `src/lib/preferences.ts`, `src/lib/csv.ts`, `src/lib/safe-markdown.ts`, `src/lib/logo.ts`, `src/lib/collection.ts`, `src/lib/live.ts`, `src/lib/shared-context.ts`, `src/lib/agent-guidance.ts` and `src/lib/insight.ts`, and the assistant prompt `src/lib/assistant/prompt.ts`.',
|
|
12
12
|
'Managed: `src/views/console-chrome.tsx` and `tests/setup.ts`.',
|
|
13
13
|
'Managed: `optional:scripts/package.json`, only when the adopt or create that set the project up generated it for this project shape.',
|
|
14
14
|
'Managed: both installed skill trees, `optional:.agents/skills/zz-meridian/` and `optional:.claude/skills/zz-meridian/`, taken from the release\'s skill payload.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zz-meridian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Bring ZZ Meridian, a dashboard design system, into a Next.js project, or start a new dashboard on it. Copies the files in; nothing depends on this package at runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/payload/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of ZZ Meridian, newest first. Versions follow semver: a removed or renamed token, prop or card is major; a new card, token or variant is minor; a corrected value is a patch. Each entry says what breaks and what to do instead.
|
|
4
4
|
|
|
5
|
+
## [0.9.0] · 2026-10-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Every page tells both agents the same thing (decision 0011).** A view builds one shared context (`src/lib/shared-context.ts`, one producer per page in `src/views/*-context.ts`): its scope and freshness, what the person points at, every figure with its unit, its change and its definition, what code computed from them (`src/lib/insight.ts`: a day against its median, a share, a ranking, a run of days, what nothing recorded explains), and what the data cannot say. An MCP host receives it through `ui/update-model-context`, inline and in fullscreen; the console's assistant receives it in its prompt, with the page's address and query. All nine console pages and the four embed views share one.
|
|
10
|
+
- **Every view has a tool.** `src/views/tools.ts` holds each view's contract: its name, its address as input, its `ui://` resource, and one read that returns what it renders and what the agents are told. The console's assistant offers each as a read-only `view_<name>`; `docs/agents.md` registers the same list on an MCP server, with the apply tool of a change registered view-only. The embed routes render from their tool's read.
|
|
11
|
+
- **The assistant is told its limits.** Its prompt lists what it cannot do or see, from each collection's `pageOnly`, `hidden` and missing operations, so it can say why.
|
|
12
|
+
- **Ask hands a card to the console's assistant.** With the assistant on, Ask appears on the featured card of Overview, Requests, Health and Analytics; a press opens the panel and sends the card's question with the page's context (`ConsoleSurface`). Ask renders only after hydration.
|
|
13
|
+
- **An MCP view says when the host refuses.** A refused context is sent once more, and Ask then carries the view's address; a refused Ask tells the person in a toast with the question's words.
|
|
14
|
+
- **The person sees the findings.** `MetricTile` takes `finding` (a line, with a day it points the Meridian there) and `baseline` ("2.7× usual" beside a day being read); `TrendChart` takes `baseline`, a dashed median line. The Overview uses all three.
|
|
15
|
+
- **An agent's change leaves an Activity line.** The assistant's guard takes an optional `record`; the template writes to a new `activity` collection, and the Overview's Activity is live.
|
|
16
|
+
- **The Overview, Analytics and Health read through `read()`.** Daily totals, endpoints, responses, services, incidents and activity are read-only collections (`src/data/metrics.ts`).
|
|
17
|
+
- **An address can name a day.** `?day=2026-09-22` opens the Overview pointed at it (`Meridian day`).
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
`update` reports the two that touch the team's own files as migrations, `share-view-context` and `agent-reads-section`, with what to change (`references/update.md`, "Resolving 0.9.0's migrations"); `src/lib/shared-context.ts`, `src/lib/agent-guidance.ts` and `src/lib/insight.ts` are Meridian's now and arrive with the update.
|
|
22
|
+
|
|
23
|
+
- `useShareView(text, structured)` is `useShareView(context)`. Build a context with the fields in `SharedContext`; `contextText` writes the text both agents read.
|
|
24
|
+
- `scripts/check.ts` requires `### What the agent reads` under `## Agents` in the README of every page under `app/(dashboard)/` and `app/embed/`. Add it to a product's own pages, saying what the page's context tells each agent.
|
|
25
|
+
- `src/data/sample.ts` no longer exports `ENDPOINTS`, `STATUS_MIX`, `ACTIVITY`, `INCIDENTS`, `PAST_INCIDENTS`, `SERVICES` or `demoTotals`: read them through `src/data/metrics.ts`. `RequestView` takes `routeP95`.
|
|
26
|
+
- `OverviewBody`, `HealthBody` and `AnalyticsBody` take the period key, `updatedAt` and, for the first and last, the incidents and activity beside the figures; `RequestsView` takes `updatedAt` and `now`.
|
|
27
|
+
|
|
5
28
|
## [0.8.0] · 2026-10-06
|
|
6
29
|
|
|
7
30
|
### Added
|
package/payload/CONTRIBUTING.md
CHANGED
|
@@ -39,7 +39,7 @@ Every card's README follows the same order, so a reader always knows where to lo
|
|
|
39
39
|
4. **States**: rest, hover, pressed, focus, selected, disabled, loading, empty, error, each with its transition (property, duration token, curve).
|
|
40
40
|
5. **Behaviour**: interaction, keyboard, timing.
|
|
41
41
|
6. **Surfaces**: console, mobile (under 1024px) and embed (an MCP App): what changes on each, or "same", and why.
|
|
42
|
-
7. **Agents**: what an agent may read here, what it may do (and through which proposal), and how its work is marked. Write "Not applicable" for purely presentational parts.
|
|
42
|
+
7. **Agents**: what an agent may read here, what it may do (and through which proposal), and how its work is marked. Write "Not applicable" for purely presentational parts. A page or a view that shares a context (`useShareView`, decision 0011) adds **What the agent reads** under it: what its context says, with a real line of it, and what each consumer receives (the MCP App through `ui/update-model-context`, the console's assistant in its prompt).
|
|
43
43
|
8. **Accessibility**: roles, names, keyboard, and contrast quoted from `node scripts/contrast.ts --all` (the dark theme and the indigo accent, the defaults; add light where it differs in kind).
|
|
44
44
|
9. **Content**: copy rules with real strings.
|
|
45
45
|
10. **Do and do not.**
|
|
@@ -75,6 +75,7 @@ Write the status on the line under the title: `Status: beta`.
|
|
|
75
75
|
- Under reduced motion it shows its final state with nothing moving.
|
|
76
76
|
- Keyboard: every control reachable, operable and visibly focused; focus order follows reading order.
|
|
77
77
|
- An agent's work in it is marked (Agent mark, "via" attribution, or a Proposal); no agent write happens without one.
|
|
78
|
+
- A page shares a context both agents read: its scope and freshness, every figure with its unit, change and definition, what code derived from them, and what the data cannot say, within 2,400 characters. A figure an agent would otherwise recompute is computed in the context.
|
|
78
79
|
|
|
79
80
|
## Tools
|
|
80
81
|
|
|
@@ -41,7 +41,24 @@ From `src/system/fixtures/sample.ts`: `demoSeries(period)` (one point per day, a
|
|
|
41
41
|
|
|
42
42
|
## Agents
|
|
43
43
|
|
|
44
|
-
The
|
|
44
|
+
The page shares one context with both agents (decision 0011), built by `overviewContext` in `src/views/overview-context.ts` from the figures the page draws. The Activity feed marks the agent's own changes ("Claude … · for Jonas Weber").
|
|
45
|
+
|
|
46
|
+
### What the agent reads
|
|
47
|
+
|
|
48
|
+
The period with its dates, freshness, the four figures, each with its change against the previous period and its definition (the tiles' `hint`s come from the same `OVERVIEW_METRICS`), the day the Meridian points at against the same weekday and the period's median, and what code computed:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
- Error rate was 2.6× to 2.7× the period's median of 0.81% on 21 and 22 Sept (2.13%, 2.18%); no other day passed 1.2×.
|
|
52
|
+
- Latency p95 rose on the same days: 525ms and 505ms against a median of 296ms.
|
|
53
|
+
Unknown:
|
|
54
|
+
- Nothing is recorded between 20 Sept and 23 Sept: no incident and no activity event, so nothing on this page explains the rise on 21 and 22 Sept.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It also says which endpoint contributes most errors, that requests follow the week, what the status classes mean against the error rate's definition, which incident sits beside a spike when one does (in 90 days: "Upload failures for files over 50 MB"), and that today is partial.
|
|
58
|
+
|
|
59
|
+
- **MCP App:** the text above as `ui/update-model-context`, with the same context as its structured part, from the inline view and from fullscreen alike, on load and on every change.
|
|
60
|
+
- **Console assistant:** the same text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
61
|
+
- **Handoff:** Ask on the featured card hands the period, or the day the Meridian points at, to the assistant: "What stands out in requests, errors and latency over the last 30 days, and why?"
|
|
45
62
|
|
|
46
63
|
## Embed view
|
|
47
64
|
|
|
@@ -5,15 +5,20 @@ import { Freshness } from '@/components/patterns/freshness';
|
|
|
5
5
|
import { PeriodSelect } from '@/components/patterns/period-select';
|
|
6
6
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
7
7
|
import { OverviewBody } from '@/views/overview';
|
|
8
|
-
import { DEMO_NOW, DEMO_UPDATED_AT
|
|
9
|
-
import {
|
|
8
|
+
import { DEMO_NOW, DEMO_UPDATED_AT } from '@/data/sample';
|
|
9
|
+
import { overviewTool } from '@/views/tools';
|
|
10
|
+
import { cache } from 'react';
|
|
11
|
+
import { parsePeriod } from '@/lib/period';
|
|
10
12
|
import { app } from '@/app.config';
|
|
11
13
|
import { Busy } from '../_loading';
|
|
12
14
|
import { OverviewSkeleton } from './loading';
|
|
13
15
|
|
|
14
16
|
export const metadata = { title: 'Overview' };
|
|
15
17
|
|
|
16
|
-
type SearchParams = Promise<{ period?: string }>;
|
|
18
|
+
type SearchParams = Promise<{ period?: string; day?: string }>;
|
|
19
|
+
|
|
20
|
+
/** The page's one read, through the Overview tool (`src/views/tools.ts`), shared by the export and the body. */
|
|
21
|
+
const readPeriod = cache((period: ReturnType<typeof parsePeriod>, day?: string) => overviewTool.read({ period, day: /^\d{4}-\d{2}-\d{2}$/.test(day ?? '') ? day : undefined }));
|
|
17
22
|
|
|
18
23
|
/** The masthead renders at once; the period select, the export and the body read the address inside their own boundaries. */
|
|
19
24
|
export default function OverviewPage({ searchParams }: { searchParams: SearchParams }) {
|
|
@@ -38,15 +43,17 @@ export default function OverviewPage({ searchParams }: { searchParams: SearchPar
|
|
|
38
43
|
|
|
39
44
|
async function Actions({ searchParams }: { searchParams: SearchParams }) {
|
|
40
45
|
const period = parsePeriod((await searchParams).period);
|
|
46
|
+
const { data } = await readPeriod(period);
|
|
41
47
|
return (
|
|
42
48
|
<>
|
|
43
49
|
<Suspense><PeriodSelect value={period} /></Suspense>
|
|
44
|
-
<ExportButton rows={
|
|
50
|
+
<ExportButton rows={data.series} filename={`overview-${period}.csv`} noun="days" className="max-sm:hidden" />
|
|
45
51
|
</>
|
|
46
52
|
);
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
async function Body({ searchParams }: { searchParams: SearchParams }) {
|
|
50
|
-
const period =
|
|
51
|
-
|
|
56
|
+
const { period, day } = await searchParams;
|
|
57
|
+
const { data } = await readPeriod(parsePeriod(period), day);
|
|
58
|
+
return <OverviewBody {...data} />;
|
|
52
59
|
}
|
|
@@ -39,7 +39,19 @@ Status: beta
|
|
|
39
39
|
|
|
40
40
|
## Agents
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
An agent drives nothing here; it reads the page through its shared context (decision 0011), built by `analyticsContext` in `src/views/analytics-context.ts`.
|
|
43
|
+
|
|
44
|
+
### What the agent reads
|
|
45
|
+
|
|
46
|
+
The period, freshness, the busiest hour of the week and of the day, each region's share with its p50, every endpoint in the table's current order, the day the Meridian points at, and what code found: the same error spike as the Overview (by the same code), the slowest and the most failing endpoint against the median, the slowest region, and the weekend's share of a weekday. It says that the weekday-and-hour pattern, the regions and the endpoints are sums over the period, not a day.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
- POST /v1/files fails most: 1.88% of its requests, 5.0× the 0.37% across endpoints. Evidence: /requests?q=%2Fv1%2Ffiles
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Console assistant:** this text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
53
|
+
- **MCP App:** no embed view yet; an MCP server can return this context as a tool's text (`docs/agents.md`).
|
|
54
|
+
- **Handoff:** Ask on Errors per day hands the pointed day, or the period, to the assistant: "Why did errors change on 22 Sept 2026?"
|
|
43
55
|
|
|
44
56
|
## Accessibility
|
|
45
57
|
|
|
@@ -6,7 +6,9 @@ import { Freshness } from '@/components/patterns/freshness';
|
|
|
6
6
|
import { PeriodSelect } from '@/components/patterns/period-select';
|
|
7
7
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
8
8
|
import { AnalyticsBody } from '@/views/analytics';
|
|
9
|
-
import { DEMO_NOW, DEMO_UPDATED_AT
|
|
9
|
+
import { DEMO_NOW, DEMO_UPDATED_AT } from '@/data/sample';
|
|
10
|
+
import { analyticsTool } from '@/views/tools';
|
|
11
|
+
import { cache } from 'react';
|
|
10
12
|
import { parsePeriod } from '@/lib/period';
|
|
11
13
|
import { Busy } from '../_loading';
|
|
12
14
|
import { AnalyticsSkeleton } from './loading';
|
|
@@ -36,17 +38,21 @@ export default function AnalyticsPage({ searchParams }: { searchParams: SearchPa
|
|
|
36
38
|
);
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/** The page's one read, through the Analytics tool (`src/views/tools.ts`), shared by the export and the body. */
|
|
42
|
+
const readPeriod = cache((period: ReturnType<typeof parsePeriod>) => analyticsTool.read({ period }));
|
|
43
|
+
|
|
39
44
|
async function Actions({ searchParams }: { searchParams: SearchParams }) {
|
|
40
45
|
const period = parsePeriod((await searchParams).period);
|
|
46
|
+
const { data } = await readPeriod(period);
|
|
41
47
|
return (
|
|
42
48
|
<>
|
|
43
49
|
<Suspense><PeriodSelect value={period} /></Suspense>
|
|
44
|
-
<ExportButton rows={
|
|
50
|
+
<ExportButton rows={data.series} filename={`analytics-${period}.csv`} noun="days" className="max-sm:hidden" />
|
|
45
51
|
</>
|
|
46
52
|
);
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
async function Body({ searchParams }: { searchParams: SearchParams }) {
|
|
50
|
-
const
|
|
51
|
-
return <AnalyticsBody
|
|
56
|
+
const { data } = await readPeriod(parsePeriod((await searchParams).period));
|
|
57
|
+
return <AnalyticsBody {...data} />;
|
|
52
58
|
}
|
|
@@ -39,7 +39,18 @@ None yet: a `zz_meridian_customer { name }` view would show one customer's card
|
|
|
39
39
|
|
|
40
40
|
## Agents
|
|
41
41
|
|
|
42
|
-
An agent reads customers through their requests view; it never changes a plan or invites from here.
|
|
42
|
+
An agent reads customers through this page's shared context and their requests view; it never changes a plan or invites from here. The context is built by `customersContext` in `src/views/customers-context.ts`, and the tiles' `hint`s come from its `CUSTOMER_METRICS`.
|
|
43
|
+
|
|
44
|
+
### What the agent reads
|
|
45
|
+
|
|
46
|
+
The three tiles with their definitions, every customer in the table's filter and order (with the columns a narrow screen drops: the error rate and the trend are still the person's to read), and what code found: how concentrated spend is, how combined requests moved over the last 7 days against the 7 before and who departs from that by 10 points or more, and which customers fail at twice the median rate. Lists stop at 25 and say how many more there are.
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
- Combined requests are up 15% over the last 7 days against the 7 before. Apart from that: Fernway Bank up 36%.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Console assistant:** this text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
53
|
+
- **MCP App:** no embed view yet; an MCP server can return this context as a tool's text (`docs/agents.md`).
|
|
43
54
|
|
|
44
55
|
## Content
|
|
45
56
|
|
|
@@ -42,7 +42,15 @@ Without a live incident row 1 becomes one full-width featured card.
|
|
|
42
42
|
|
|
43
43
|
## Agents
|
|
44
44
|
|
|
45
|
-
The page itself takes no agent action.
|
|
45
|
+
The page itself takes no agent action. It shares one context with both agents (decision 0011), built by `healthContext` in `src/views/health-context.ts`.
|
|
46
|
+
|
|
47
|
+
### What the agent reads
|
|
48
|
+
|
|
49
|
+
Every service's state, latency now and uptime; the average uptime with its definition; which services carry the days below operational ("Inference API accounts for 3 of them"); the open incident with its latest update by time, not by position; the longest incident resolved in the period. Unknowns: that a monitoring incident is not yet confirmed fixed, and that latency is one reading with no history to call it unusual.
|
|
50
|
+
|
|
51
|
+
- **MCP App:** the text above as `ui/update-model-context`, with the same context as its structured part, from the inline view and from fullscreen alike, on load and on every change.
|
|
52
|
+
- **Console assistant:** the same text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
53
|
+
- **Handoff:** Ask on the uptime card hands the open incident to the assistant: "Which service is hurting uptime, and is … over?"
|
|
46
54
|
|
|
47
55
|
## Embed view
|
|
48
56
|
|
|
@@ -2,22 +2,23 @@ import { app } from '@/app.config';
|
|
|
2
2
|
import { PageFrame } from '@/components/base/shell';
|
|
3
3
|
import { Freshness } from '@/components/patterns/freshness';
|
|
4
4
|
import { PackedHealthBody, SubscribeButton } from '@/views/health';
|
|
5
|
+
import { healthTool } from '@/views/tools';
|
|
5
6
|
import { packServices } from '@/components/patterns/status-list/summarise';
|
|
6
|
-
import { DEMO_NOW, DEMO_UPDATED_AT, INCIDENTS, SERVICES, PAST_INCIDENTS } from '@/data/sample';
|
|
7
7
|
|
|
8
8
|
export const metadata = { title: 'Health' };
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/** Rendered from the Health tool's own read (`src/views/tools.ts`): the services and incidents the caller may read. */
|
|
11
|
+
export default async function HealthPage() {
|
|
12
|
+
const { data } = await healthTool.read({});
|
|
12
13
|
return (
|
|
13
14
|
<PageFrame
|
|
14
15
|
kicker={<>{app.name} · {app.workspace}</>}
|
|
15
16
|
title="Health"
|
|
16
17
|
description="Every service, how it is right now, and what happened in the last 90 days."
|
|
17
|
-
meta={<Freshness updatedAt={
|
|
18
|
+
meta={<Freshness updatedAt={new Date(data.updatedAt)} now={new Date(data.now)} />}
|
|
18
19
|
actions={<SubscribeButton />}
|
|
19
20
|
>
|
|
20
|
-
<PackedHealthBody services={packServices(
|
|
21
|
+
<PackedHealthBody {...data} services={packServices(data.services)} />
|
|
21
22
|
</PageFrame>
|
|
22
23
|
);
|
|
23
24
|
}
|
|
@@ -41,7 +41,14 @@ Not offered. Keys are secrets, and creating one is a page-only operation. In the
|
|
|
41
41
|
|
|
42
42
|
## Agents
|
|
43
43
|
|
|
44
|
-
An agent may
|
|
44
|
+
An agent may propose revoking a key (a critical Proposal); it never creates one, because a key's secret is shown once, on this page (`pageOnly`). The proposal waits for approval, and its approval removes the key through the same collection. The page shares its context through `keysContext` in `src/views/keys-context.ts`.
|
|
45
|
+
|
|
46
|
+
### What the agent reads
|
|
47
|
+
|
|
48
|
+
Every key with its id, environment, hint (the first and last characters, never the secret), owner, scopes, when it was created and last used, and what code found: live keys unused for 30 days, live keys with every scope, and an owner holding several live keys. The secret and its hash are never in it (`secretHash` is `hidden`), and the context says so, with the rule that creating a key is the person's.
|
|
49
|
+
|
|
50
|
+
- **Console assistant:** this text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
51
|
+
- **MCP App:** no embed view yet; an MCP server can return this context as a tool's text (`docs/agents.md`).
|
|
45
52
|
|
|
46
53
|
## Content
|
|
47
54
|
|
|
@@ -42,7 +42,18 @@ Not offered. Inviting and removing people are decisions for the console, not a c
|
|
|
42
42
|
|
|
43
43
|
## Agents
|
|
44
44
|
|
|
45
|
-
The assistant reads and proposes changes here through Proposals: it adds, changes and removes members only after a person approves, and what it changed shows on this page after a refresh. A change made here is what the assistant reads next.
|
|
45
|
+
The assistant reads and proposes changes here through Proposals: it adds, changes and removes members only after a person approves, and what it changed shows on this page after a refresh. A change made here is what the assistant reads next. The page shares its context through `membersContext` in `src/views/members-context.ts`.
|
|
46
|
+
|
|
47
|
+
### What the agent reads
|
|
48
|
+
|
|
49
|
+
Counts by status, role (with what each role may do) and team, everyone with their role, team, status and when they were last seen (25 at most; `query_members` finds the rest), and what code found: active members unseen for 60 days or more, unanswered invitations, and who can change the workspace. It says that "last active" is a console sign-in, not use of the API.
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
- 3 active members have not been seen for 60 days or more: Alice Moreno (Viewer, Support, 94 days), Felix Andersson (Viewer, Engineering, 75 days), Ravi Patel (Viewer, Support, 71 days).
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **Console assistant:** this text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
56
|
+
- **MCP App:** no embed view yet; an MCP server can return this context as a tool's text (`docs/agents.md`).
|
|
46
57
|
|
|
47
58
|
## Content
|
|
48
59
|
|
|
@@ -51,7 +51,21 @@ Export CSV is a link to `/api/export/requests` with the same filters and sort: t
|
|
|
51
51
|
|
|
52
52
|
## Agents
|
|
53
53
|
|
|
54
|
-
An agent opens this page's views through `zz_meridian_requests`; it
|
|
54
|
+
An agent opens this page's views through `zz_meridian_requests`; it never exports or blocks from here. Its filters are marked until a person changes one. The page shares one context with both agents (decision 0011), built by `requestsContext` in `src/views/requests-context.ts` from the page's read: the rows on screen and the summary of the whole filtered set.
|
|
55
|
+
|
|
56
|
+
### What the agent reads
|
|
57
|
+
|
|
58
|
+
The filters in words and the span of time the log covers; freshness; the three figures with their change against the half hour before and their definitions (the tiles' `hint`s come from `REQUEST_METRICS`); the page, the sort and the rows on screen by id; and what the matching requests and their errors have in common:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
- 5 of the 9 errors and limits are in eu-west-1 (56%). Evidence: /requests?region=eu-west-1
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When a half hour holds fewer than 30 requests it says the change is too few to read as a trend, and when the summary reads only the newest 500 it says so.
|
|
65
|
+
|
|
66
|
+
- **MCP App:** the text above as `ui/update-model-context`, with the same context as its structured part, from the inline view and from fullscreen alike, on load and on every change.
|
|
67
|
+
- **Console assistant:** the same text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
68
|
+
- **Handoff:** Ask in the filter bar hands the filtered set to the assistant: "What do these requests with status 5xx have in common, and why?"
|
|
55
69
|
|
|
56
70
|
## Accessibility
|
|
57
71
|
|
|
@@ -41,7 +41,18 @@ None of its own. An agent shows a request through `zz_meridian_requests` (inline
|
|
|
41
41
|
|
|
42
42
|
## Agents
|
|
43
43
|
|
|
44
|
-
Replay
|
|
44
|
+
Replay is a person's action here: it is `pageOnly`, so no agent can run or propose it. The page shares its context through `requestContext` in `src/views/request-context.ts`.
|
|
45
|
+
|
|
46
|
+
### What the agent reads
|
|
47
|
+
|
|
48
|
+
The request, its status, latency (with its definition), customer and region, every phase with its start and duration, the payloads' sizes (their text is in the page text), and what code found: where most of the time went, the latency against the route's p95, and, for a failed request, that it counts in the error rate. It says that one request is one sample, and that replaying it is the person's.
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
- Most of its time went to Rate limit (Over 1,200 requests a minute): 159ms, 93% of 171ms.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- **Console assistant:** this text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
55
|
+
- **MCP App:** no embed view yet; an MCP server can return this context as a tool's text (`docs/agents.md`).
|
|
45
56
|
|
|
46
57
|
## Accessibility
|
|
47
58
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { read } from '@/data/read';
|
|
2
|
+
import { readEndpoints } from '@/data/metrics';
|
|
2
3
|
import { FEATURED_REQUEST_IDS, traceOf, payloadsOf, type RequestRow } from '@/data/sample';
|
|
3
4
|
import { MissingPage } from '@/views/missing-page';
|
|
4
5
|
import { RequestView } from '@/views/request';
|
|
@@ -23,5 +24,6 @@ export default async function RequestPage({ params }: { params: Promise<{ id: st
|
|
|
23
24
|
const { request: r, observedAt } = await byId(id);
|
|
24
25
|
// Rendered here rather than thrown, so the screen is in the first HTML (see MissingPage).
|
|
25
26
|
if (!r) return <MissingPage />;
|
|
26
|
-
|
|
27
|
+
const route = (await readEndpoints()).find((e) => e.method === r.method && e.route === r.route);
|
|
28
|
+
return <RequestView request={r} trace={traceOf(r)} payloads={payloadsOf(r)} routeP95={route?.p95 ?? null} now={observedAt} replay={replayRequest} />;
|
|
27
29
|
}
|
|
@@ -65,6 +65,6 @@ async function RequestsExport({ searchParams }: { searchParams: SearchParams })
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
async function Requests({ searchParams }: { searchParams: SearchParams }) {
|
|
68
|
-
const { rows, total, summary, state } = await readPage(await searchParams);
|
|
69
|
-
return <RequestsView rows={rows} total={total} summary={summary} state={state} pageSize={REQUEST_PAGE} />;
|
|
68
|
+
const { rows, total, summary, state, observedAt } = await readPage(await searchParams);
|
|
69
|
+
return <RequestsView rows={rows} total={total} summary={summary} state={state} pageSize={REQUEST_PAGE} updatedAt={DEMO_UPDATED_AT.toISOString()} now={observedAt} />;
|
|
70
70
|
}
|
|
@@ -42,6 +42,13 @@ Appearance reads and writes `usePreferences()` (stored on the device). Time zone
|
|
|
42
42
|
|
|
43
43
|
This is where people govern agents: whether assistants may read, which are connected, and the rule (locked on) that nothing an agent proposes runs without approval, a removal included. The assistant section, shown only where the product has an assistant, holds the person's switch for its panel.
|
|
44
44
|
|
|
45
|
+
### What the agent reads
|
|
46
|
+
|
|
47
|
+
Every section's current values, which neither an input's nor a switch's text carries: the workspace's name, address and time zone (and an unsaved edit, when there is one), each notification switch, this device's theme, accent and density, whether the assistant is shown, whether assistants may read dashboards and which are connected with what they may do, and that deleting the workspace is typed, never proposed. Each section reports its own line; `SettingsBody` shares them as one context. None is offered to an agent as a change, and the context says so.
|
|
48
|
+
|
|
49
|
+
- **Console assistant:** this text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
50
|
+
- **MCP App:** no embed view yet; an MCP server can return this context as a tool's text (`docs/agents.md`).
|
|
51
|
+
|
|
45
52
|
## Accessibility
|
|
46
53
|
|
|
47
54
|
- Each section is a form with its own submit; switches carry their label and description.
|
|
@@ -2,9 +2,11 @@ import { safeValidateUIMessages } from 'ai';
|
|
|
2
2
|
import { revalidateTag } from 'next/cache';
|
|
3
3
|
import { assistantConfig } from '@/lib/assistant/config';
|
|
4
4
|
import { clock, collections } from '@/data/collections';
|
|
5
|
-
import { Unauthenticated, can, collectionFor, resolveAccess } from '@/data/access';
|
|
5
|
+
import { Unauthenticated, can, collectionFor, nameOf, resolveAccess } from '@/data/access';
|
|
6
6
|
import { collectionTag } from '@/data/read';
|
|
7
7
|
import { respond } from '@/lib/assistant/respond';
|
|
8
|
+
import type { AgentChange } from '@/lib/assistant/tools';
|
|
9
|
+
import { viewTools } from '@/views/tools';
|
|
8
10
|
|
|
9
11
|
/** The panel sends at most the last 100 messages; a larger body than 2 MB is refused before it is parsed. */
|
|
10
12
|
const MAX_MESSAGES = 100;
|
|
@@ -20,7 +22,7 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
20
22
|
const raw = await request.text();
|
|
21
23
|
if (raw.length > MAX_BODY) return new Response(null, { status: 413 });
|
|
22
24
|
const body: unknown = (() => { try { return JSON.parse(raw); } catch { return null; } })();
|
|
23
|
-
const { messages, page } = (body ?? {}) as { messages?: unknown; page?: { path?: unknown; title?: unknown; text?: unknown } };
|
|
25
|
+
const { messages, page } = (body ?? {}) as { messages?: unknown; page?: { path?: unknown; title?: unknown; context?: unknown; text?: unknown } };
|
|
24
26
|
if (!Array.isArray(messages) || messages.length === 0 || messages.length > MAX_MESSAGES) return new Response(null, { status: 400 });
|
|
25
27
|
const valid = await safeValidateUIMessages({ messages });
|
|
26
28
|
if (!valid.success) return new Response(null, { status: 400 });
|
|
@@ -35,14 +37,32 @@ export async function POST(request: Request): Promise<Response> {
|
|
|
35
37
|
return now !== null && now.tenantId === scope.tenantId && now.subjectId === scope.subjectId && can(now, name, op, ids);
|
|
36
38
|
},
|
|
37
39
|
invalidate: (name: string) => revalidateTag(collectionTag(scope.tenantId, name), { expire: 0 }),
|
|
40
|
+
// Provenance (decision 0011): an approved change leaves an Activity line naming the agent and the person it acted
|
|
41
|
+
// for. Only where the product keeps activity and the person may add to it; the change stands either way.
|
|
42
|
+
record: async (change: AgentChange) => {
|
|
43
|
+
if (!(await can(scope, 'activity', 'create'))) return;
|
|
44
|
+
await collectionFor(scope, 'activity').create!({ at: clock().toISOString(), actor: (await nameOf(scope)) ?? 'You', via: 'Assistant', ...activityLine(change) });
|
|
45
|
+
revalidateTag(collectionTag(scope.tenantId, 'activity'), { expire: 0 });
|
|
46
|
+
},
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
return respond({
|
|
41
50
|
...config,
|
|
42
51
|
collections: readable,
|
|
52
|
+
// Every view's read-only tool; each reads through `read()`, so it sees only what this caller may read.
|
|
53
|
+
views: viewTools,
|
|
43
54
|
guard,
|
|
44
55
|
now: clock(),
|
|
45
56
|
messages: valid.data,
|
|
46
|
-
page: { path: String(page?.path ?? ''), title: String(page?.title ?? ''), text: String(page?.text ?? '') },
|
|
57
|
+
page: { path: String(page?.path ?? ''), title: String(page?.title ?? ''), context: String(page?.context ?? ''), text: String(page?.text ?? '') },
|
|
47
58
|
});
|
|
48
59
|
}
|
|
60
|
+
|
|
61
|
+
/** An agent's change as an Activity line: "changed status to Suspended for Alice Moreno and Ravi Patel". */
|
|
62
|
+
function activityLine({ label, op, titles, set }: AgentChange): { verb: string; object: string } {
|
|
63
|
+
const names = titles.length <= 3 ? titles.join(titles.length === 2 ? ' and ' : ', ') : `${titles.length} ${label.toLowerCase()}`;
|
|
64
|
+
if (op === 'create') return { verb: 'added', object: `${names} to ${label.toLowerCase()}` };
|
|
65
|
+
if (op === 'remove') return { verb: 'removed', object: `${names} from ${label.toLowerCase()}` };
|
|
66
|
+
const fields = Object.entries(set ?? {}).map(([k, v]) => `${k} to ${Array.isArray(v) ? v.join(', ') : String(v)}`).join(', ');
|
|
67
|
+
return { verb: 'changed', object: `${fields} for ${names}` };
|
|
68
|
+
}
|
|
@@ -29,7 +29,12 @@ Embed only, at 360 to 760px inline and the host's full panel in fullscreen. The
|
|
|
29
29
|
|
|
30
30
|
## Agents
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
The Health page's context (`app/(dashboard)/health/README.md` lists it), inline and in fullscreen. Ask on the banner posts "What is the impact of … on my traffic?".
|
|
33
|
+
|
|
34
|
+
### What the agent reads
|
|
35
|
+
|
|
36
|
+
- **MCP App:** the text above as `ui/update-model-context`, with the same context as its structured part, from the inline view and from fullscreen alike, on load and on every change.
|
|
37
|
+
- **Console assistant:** the same text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|
|
33
38
|
|
|
34
39
|
## Accessibility
|
|
35
40
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Suspense } from 'react';
|
|
2
|
+
import { healthTool } from '@/views/tools';
|
|
2
3
|
import { EmbedHealth } from './view';
|
|
3
4
|
|
|
4
5
|
export const metadata = { title: 'Health' };
|
|
5
6
|
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Tool: `zz_meridian_health {}` (`healthTool` in `src/views/tools.ts`), rendered from the tool's own read, at request
|
|
9
|
+
* time for whoever is asking. Inline: the status list and the live incident. Fullscreen: the console's Health rows.
|
|
10
|
+
*/
|
|
7
11
|
export default function Page() {
|
|
8
12
|
return (
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
past={PAST_INCIDENTS}
|
|
13
|
-
updatedAt={DEMO_UPDATED_AT.toISOString()}
|
|
14
|
-
now={DEMO_NOW.toISOString()}
|
|
15
|
-
/>
|
|
13
|
+
<Suspense>
|
|
14
|
+
<Health />
|
|
15
|
+
</Suspense>
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
async function Health() {
|
|
20
|
+
const { data } = await healthTool.read({});
|
|
21
|
+
return <EmbedHealth {...data} />;
|
|
22
|
+
}
|
|
@@ -1,35 +1,28 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useSurface } from '@/components/base/surface';
|
|
4
|
-
import { useShareView } from '@/components/base/use-share-view';
|
|
5
4
|
import { Banner } from '@/components/ui/banner';
|
|
6
5
|
import { Card } from '@/components/ui/card';
|
|
7
6
|
import { EmbedFrame } from '@/components/patterns/embed-frame';
|
|
8
7
|
import { Freshness } from '@/components/patterns/freshness';
|
|
9
8
|
import { AskAbout } from '@/components/patterns/ask-about';
|
|
10
|
-
import { StatusList
|
|
11
|
-
import { HealthBody } from '@/views/health';
|
|
9
|
+
import { StatusList } from '@/components/patterns/status-list';
|
|
10
|
+
import { HealthBody, ShareHealth } from '@/views/health';
|
|
12
11
|
import { formatRelative } from '@/lib/format-date';
|
|
13
12
|
import type { Incident } from '@/components/patterns/incident-card';
|
|
14
13
|
import type { Service } from '@/components/patterns/status-list';
|
|
15
|
-
import { app } from '@/app.config';
|
|
16
14
|
|
|
17
15
|
type Props = { services: Service[]; current: Incident | null; past: Incident[]; updatedAt: string; now: string };
|
|
18
16
|
|
|
19
17
|
export function EmbedHealth(p: Props) {
|
|
20
18
|
const s = useSurface();
|
|
21
|
-
const sum = summarise(p.services);
|
|
22
|
-
const affected = p.services.filter((x) => x.status !== 'operational');
|
|
23
|
-
useShareView(
|
|
24
|
-
`${app.name} health: ${sum.text}${affected.length ? ` (${affected.map((x) => `${x.name} ${x.status}`).join(', ')})` : ''}.${p.current ? ` Open incident: ${p.current.title}, ${p.current.state}.` : ''}`,
|
|
25
|
-
{ view: 'health', state: sum.status, affected: affected.map((x) => x.name), incident: p.current?.id ?? null },
|
|
26
|
-
);
|
|
27
19
|
return (
|
|
28
20
|
<EmbedFrame title="Health" meta={<Freshness updatedAt={new Date(p.updatedAt)} now={new Date(p.now)} />} consolePath="/health">
|
|
29
21
|
{s.mode === 'fullscreen' ? (
|
|
30
|
-
<HealthBody
|
|
22
|
+
<HealthBody {...p} />
|
|
31
23
|
) : (
|
|
32
24
|
<div className="flex flex-col gap-3">
|
|
25
|
+
<ShareHealth data={p} />
|
|
33
26
|
{p.current ? (
|
|
34
27
|
<Banner
|
|
35
28
|
tone="warning"
|
|
@@ -23,4 +23,9 @@ Status: beta
|
|
|
23
23
|
|
|
24
24
|
## Agents
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
The Overview page's context, unchanged (`app/(dashboard)/(overview)/README.md` lists it): the view renders the console's figures and shares the console's context, so an MCP host and the console's assistant are told the same thing. Ask on the trend posts "Why did requests change on <day>?" or "What drove the trend in requests this period?".
|
|
27
|
+
|
|
28
|
+
### What the agent reads
|
|
29
|
+
|
|
30
|
+
- **MCP App:** the text above as `ui/update-model-context`, with the same context as its structured part, from the inline view and from fullscreen alike, on load and on every change.
|
|
31
|
+
- **Console assistant:** the same text in its prompt's `<view-context>` block, with the page's address and query, and the page text after it for anything the context does not cover.
|