Skip to content

Commit 472d97a

Browse files
bloveclaude
andauthored
docs(cockpit): bring the Mastra runtime example to content parity (#912)
The mastra topic's cockpit page rendered an empty docs pane and no backend code: its descriptor declared no docsAssetPaths or backendAssetPaths and only one prompt, versus the aws-strands and microsoft-agent-framework siblings (guide.md + two backend files + a backend-build prompt). - Add cockpit/runtimes/mastra/angular/docs/guide.md matching the siblings' structure: what the example demonstrates (camping planner over @ag-ui/mastra), the suspend/approval interrupt flow and its command.interruptEvent resume shape, working memory as STATE_SNAPSHOT/STATE_DELTA, the hand-written Node hosting service at deployments/ag-ui-mastra/, and the honest subagents note (upstream reserves ACTIVITY for background tasks). - Add prompts/mastra-backend.md — the backend-build prompt siblings keep in their python lane; mastra has no python lane so both prompts live beside the Angular app. - Descriptor: declare docsAssetPaths, backendAssetPaths (deployments/ag-ui-mastra/{agents,server}.mjs — the topic's real backend lives outside cockpit/ by design), runtimeUrl, devPort. - apps/cockpit: trace deployments/ag-ui-mastra/*.mjs into the deployed bundle, map .mjs to javascript highlighting, and extract JSDoc sections from .mjs backend files. - Extend the #910 wiring-guard spec: assert the new mastra fields and that every declared mastra asset exists on disk (the content bundle degrades to 'File not found' silently otherwise). Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent c20ab69 commit 472d97a

6 files changed

Lines changed: 150 additions & 1 deletion

File tree

apps/cockpit/next.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export const nextConfig: WithNxOptions = {
1313
'../../cockpit/**/*.md',
1414
'../../cockpit/**/*.py',
1515
'../../cockpit/**/*.ts',
16+
// The mastra runtime's backend assets live outside cockpit/ — the
17+
// Node hosting service IS that topic's backend (no python lane).
18+
'../../deployments/ag-ui-mastra/*.mjs',
1619
'../../nx.json',
1720
],
1821
},

apps/cockpit/src/lib/content-bundle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const LANG_MAP: Record<string, string> = {
6363
ts: 'typescript',
6464
tsx: 'tsx',
6565
js: 'javascript',
66+
mjs: 'javascript',
6667
jsx: 'jsx',
6768
py: 'python',
6869
md: 'markdown',
@@ -129,7 +130,7 @@ export async function getContentBundle(
129130

130131
// Extract doc sections
131132
const fileName = path.split('/').pop() ?? path;
132-
if (path.endsWith('.ts') || path.endsWith('.tsx')) {
133+
if (path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.mjs')) {
133134
docSections.push(...extractTsDocSections(source, fileName));
134135
} else if (path.endsWith('.py')) {
135136
docSections.push(...extractPyDocSections(source, fileName));

apps/cockpit/src/lib/route-resolution.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { existsSync } from 'node:fs';
2+
import { join } from 'node:path';
13
import { describe, expect, it } from 'vitest';
24
import { cockpitManifest } from '@threadplane/cockpit-registry';
35
import {
@@ -117,8 +119,39 @@ describe('runtimes capability presentation', () => {
117119
'cockpit/runtimes/mastra/angular/src/app/mastra.component.ts'
118120
);
119121
expect(presentation.promptAssetPaths).toEqual([
122+
'cockpit/runtimes/mastra/angular/prompts/mastra-backend.md',
120123
'cockpit/runtimes/mastra/angular/prompts/mastra.md',
121124
]);
125+
// Backend assets deliberately point outside cockpit/: the topic's
126+
// backend is the Node hosting service, not a cockpit/ Python lane.
127+
expect(presentation.backendAssetPaths).toEqual([
128+
'deployments/ag-ui-mastra/agents.mjs',
129+
'deployments/ag-ui-mastra/server.mjs',
130+
]);
131+
expect(presentation.docsAssetPaths).toEqual([
132+
'cockpit/runtimes/mastra/angular/docs/guide.md',
133+
]);
134+
expect(presentation.runtimeUrl).toBe('runtimes/mastra');
135+
expect(presentation.devPort).toBe(4332);
136+
137+
// Every declared asset must exist on disk — the content bundle renders
138+
// "File not found" instead of failing, so a typo here is silent in CI.
139+
// Same workspace-root discovery as content-bundle.ts: walk up from CWD
140+
// until nx.json (vitest may run from the app dir or the workspace root).
141+
let workspaceRoot = process.cwd();
142+
while (!existsSync(join(workspaceRoot, 'nx.json'))) {
143+
const parent = join(workspaceRoot, '..');
144+
if (parent === workspaceRoot) break;
145+
workspaceRoot = parent;
146+
}
147+
for (const path of [
148+
...presentation.promptAssetPaths,
149+
...presentation.codeAssetPaths,
150+
...presentation.backendAssetPaths,
151+
...presentation.docsAssetPaths,
152+
]) {
153+
expect(existsSync(join(workspaceRoot, path)), `missing asset: ${path}`).toBe(true);
154+
}
122155
});
123156
});
124157

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Runtimes — Mastra
2+
3+
Third entry on the one-capability-many-runtimes axis
4+
(`cockpit/runtimes/<runtime>/`): the same neutral `Agent` contract and the
5+
same `@threadplane/chat` UI primitives as every other AG-UI example, over a
6+
backend that is genuinely not LangGraph — and, uniquely on this axis, not
7+
Python either.
8+
9+
## What it demonstrates
10+
11+
| Surface | How |
12+
| --- | --- |
13+
| Messages | Streamed assistant text (`TEXT_MESSAGE_CHUNK`) from a Mastra `Agent` via the `@ag-ui/mastra` bridge. |
14+
| Tool calls | `check_conditions` executes server-side, no pause. |
15+
| Shared state | Working memory bridged honestly: the agent's `packing_list` working-memory schema streams as a `STATE_SNAPSHOT` plus real JSON-Patch `STATE_DELTA` events while the agent updates the list (measured in the spike's 04a capture) — the only runtime on this axis that emits deltas. |
16+
| Interrupts | `reserve_campsite` suspends the run via its `suspendSchema`/`resumeSchema` pair; the bridge emits a `CUSTOM on_interrupt` payload (`{ toolCallId, toolName, suspendPayload, runId }`) followed by the protocol-standard `RUN_FINISHED.outcome = { type: 'interrupt', interrupts: [...] }`. The adapter resumes with the Mastra wire shape `forwardedProps.command = { resume, interruptEvent: { toolCallId, runId } }`. |
17+
| Subagents | Not demonstrated — upstream reserves `ACTIVITY_*` events for background tasks, so delegation has no per-subagent stream (measured red in the 2026-08-31 runtime matrix). |
18+
19+
Unlike the Python-lane runtimes, the interrupt path here does use a `CUSTOM
20+
on_interrupt` event — it is the one convention the Mastra bridge shares with
21+
the LangGraph bridge — but the run still finishes with the outcome-provenance
22+
`RUN_FINISHED` shape added in #888/#889/#891, so the reducer treats all three
23+
runtimes identically.
24+
25+
Suspend/resume REQUIRES persistent storage: Mastra writes suspended-run
26+
snapshots to LibSQL file storage and resume loads them back, so an in-memory
27+
store would orphan every pending approval across HTTP requests.
28+
29+
## The hosting service (Node lane)
30+
31+
Upstream `@ag-ui/mastra` ships no plain AG-UI HTTP endpoint — only the
32+
in-process `MastraAgent` bridge and a CopilotKit runtime mount. The backend
33+
is therefore the hand-written Node service `deployments/ag-ui-mastra/`:
34+
`server.mjs` subscribes to `MastraAgent.run(input)` (the raw AG-UI event
35+
Observable) and encodes each event as one SSE `data:` frame — exactly what
36+
`@ag-ui/client`'s `HttpAgent` consumes. It mirrors the Python lane's
37+
behavior contract (`GET /ok` unauthenticated, `X-Internal-Token` on every
38+
other route, topics at `POST /agent/<topic>`, Observable errors mapped to a
39+
`RUN_ERROR` frame). The agent itself lives in `agents.mjs`, next to the
40+
shim, because there is no per-example Python module to stage into a
41+
generated deployment. This is why `cockpit/runtimes/mastra/` has no
42+
`python/` directory and its assets live in the `angular` lane.
43+
44+
## Model client
45+
46+
Mastra's model router resolves the plain string `openai/gpt-4o-mini` on
47+
`OPENAI_API_KEY` — no provider SDK wiring. `OPENAI_BASE_URL` is honored,
48+
which is how the aimock e2e harness intercepts model calls without a code
49+
fork.
50+
51+
## Running locally
52+
53+
```sh
54+
npx tsx apps/cockpit/scripts/serve-example.ts --capability=rt-mastra
55+
```
56+
57+
Angular dev server on :4332. The serve script only auto-starts Python
58+
backends, so start the Node service manually (see
59+
`deployments/ag-ui-mastra/README.md`):
60+
61+
```sh
62+
cd deployments/ag-ui-mastra && npm ci
63+
AG_UI_INTERNAL_TOKEN=dev-local-token OPENAI_API_KEY=sk-... PORT=5332 node server.mjs
64+
```
65+
66+
The example's dev proxy rewrites `/agent` to
67+
`http://localhost:5332/agent/mastra` and injects the dev token.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Prompt: camping trip planner on Mastra
2+
3+
Build a Mastra agent exposed over AG-UI:
4+
5+
- Resolve the model through Mastra's model router with the plain string
6+
`openai/gpt-4o-mini` (honors `OPENAI_API_KEY` and `OPENAI_BASE_URL`) — no
7+
provider SDK wiring.
8+
- Add a plain backend tool (`check_conditions`, via `createTool`) that
9+
executes server-side without pausing. Keep it deterministic so e2e
10+
fixtures stay stable.
11+
- Make `reserve_campsite` a human-in-the-loop tool: give it a
12+
`suspendSchema`/`resumeSchema` pair and call `suspend(...)` on the first
13+
invocation; the AG-UI bridge signals it as a `CUSTOM on_interrupt` payload
14+
followed by the protocol-standard `RUN_FINISHED` interrupt outcome, and
15+
the resume arrives as `forwardedProps.command = { resume,
16+
interruptEvent: { toolCallId, runId } }`.
17+
- Give the agent `Memory` with a `workingMemory` schema (`packing_list`)
18+
backed by file-based `LibSQLStore` storage. Working memory bridges to
19+
AG-UI shared state as a `STATE_SNAPSHOT` plus real JSON-Patch
20+
`STATE_DELTA` events. File-backed storage is REQUIRED: suspended-run
21+
snapshots persist there, and resume loads them back across HTTP requests.
22+
- Upstream `@ag-ui/mastra` ships no plain AG-UI HTTP endpoint, so
23+
hand-write the hosting service: for each `POST /agent/<topic>` request,
24+
construct a fresh `MastraAgent` bridge (`resourceId` keyed by the AG-UI
25+
`threadId`), subscribe to `run(input)`, and write one SSE `data:` frame
26+
per event. Map Observable errors to a `RUN_ERROR` frame, never a dropped
27+
socket.
28+
- Do not add a multi-agent surface — Mastra reserves `ACTIVITY_*` events
29+
for background tasks, so delegation has no per-subagent stream.

cockpit/runtimes/mastra/angular/src/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export interface CockpitCapabilityModule {
1111
docsPath: string;
1212
promptAssetPaths: string[];
1313
codeAssetPaths: string[];
14+
backendAssetPaths: string[];
15+
docsAssetPaths: string[];
16+
runtimeUrl?: string;
17+
devPort?: number;
1418
}
1519

1620
export const runtimesMastraAngularModule: CockpitCapabilityModule = {
@@ -25,10 +29,22 @@ export const runtimesMastraAngularModule: CockpitCapabilityModule = {
2529
title: 'Runtimes — Mastra (Angular)',
2630
docsPath: '/docs/runtimes/core-capabilities/mastra/overview/angular',
2731
promptAssetPaths: [
32+
'cockpit/runtimes/mastra/angular/prompts/mastra-backend.md',
2833
'cockpit/runtimes/mastra/angular/prompts/mastra.md',
2934
],
3035
codeAssetPaths: [
3136
'cockpit/runtimes/mastra/angular/src/app/mastra.component.ts',
3237
'cockpit/runtimes/mastra/angular/src/app/app.config.ts',
3338
],
39+
// The Mastra backend is the Node AG-UI service, not a cockpit/ Python
40+
// lane — these paths intentionally point outside cockpit/ (the cockpit
41+
// app reads workspace-root-relative paths and its file tracing stages
42+
// this directory explicitly).
43+
backendAssetPaths: [
44+
'deployments/ag-ui-mastra/agents.mjs',
45+
'deployments/ag-ui-mastra/server.mjs',
46+
],
47+
docsAssetPaths: ['cockpit/runtimes/mastra/angular/docs/guide.md'],
48+
runtimeUrl: 'runtimes/mastra',
49+
devPort: 4332,
3450
};

0 commit comments

Comments
 (0)