@mastra/core@1.37.0
mastra-ai/mastra@mastra/core@1.37.0May 27, 2026by PaulieScanlon
AI Summary
This release introduces a new Google Cloud Spanner storage adapter and major UX improvements to agent channels, including opt-in streaming text, adaptive typing indicators, and a new `toolDisplay` system for controlling tool rendering. It also adds client-side tool observability and auto-registration for Agent Builder routes.
Key Highlights
- New Google Cloud Spanner storage adapter (`@mastra/spanner@1.0.0`)
- Agent Channels UX upgrades: opt-in streaming text, adaptive typing indicators, and `toolDisplay` modes (`cards`, `text`, `timeline`, `grouped`, `hidden`, function)
- Client-side tool observability end-to-end via `CLIENT_TOOL_CALL` spans
- Agent Builder routes auto-registered in server adapters
- New Workspace Filesystem Provider via FilesSDK (`@mastra/files-sdk@0.2.0`)
Breaking Changes
- `@mastra/slack`: Removed `cards: boolean` and `formatToolCall`; migrate to `toolDisplay: 'text'` or `toolDisplay` function
New Features
- Google Cloud Spanner storage backend
- Streaming text deltas for agent channels
- Adaptive typing indicators
- Tool display system (cards, text, timeline, grouped, hidden, function)
- Client-side tool observability
- Auto-registration of Agent Builder routes
- Workspace Filesystem Provider
Full Release Notes
## Highlights
### Google Cloud Spanner Storage Adapter (`@mastra/spanner@1.0.0`)
New first-party storage backend for Google Cloud Spanner (GoogleSQL) covering most Mastra storage domains (memory, workflows, schedules, observability, etc.), including persisting tracing spans for Studio and supporting the local Spanner emulator.
### Agent Channels Streaming UX + Tool Rendering Controls (Core + Slack)
Major upgrades to channel adapters: opt-in streaming text deltas, adaptive typing indicators, and a new `toolDisplay` system (`cards`/`text`/`timeline`/`grouped`/`hidden`/function) to control how tool execution renders (including plan/timeline widgets and stable `toolCallId` correlation).
### Client-Side Tool Observability End-to-End (`@mastra/client-js` + `@mastra/observability`)
Client-executed tools now show up in server traces via `CLIENT_TOOL_CALL` spans, with a shared `observe` helper for child spans/logs inside `execute`; observability exporters can ingest and forward this client telemetry (including duration metrics labeled `toolType: 'client'`).
### Agent Builder Routes Now Auto-Registered in Server Adapters
`/agent-builder/*` endpoints are now served automatically by any `@mastra/server`-based adapter (Express/Fastify/Hono/Koa/etc.) without manual wiring, while preserving lazy-loading to keep worker bundles small; new EE permissions gate access.
### New Workspace Filesystem Provider via FilesSDK (`@mastra/files-sdk@0.2.0`)
Adds a unified `WorkspaceFilesystem` implementation backed by FilesSDK, letting you swap storage providers (S3/R2/GCS/Azure Blob/Vercel Blob/local FS, etc.) by changing the FilesSDK adapter without changing Mastra code.
### Breaking Changes
- `@mastra/slack`: removed `cards: boolean` and `formatToolCall` from Slack provider/channel config; migrate to `toolDisplay: 'text'` (for `cards: false`) or `toolDisplay` function form (for `formatToolCall`).
## Changelog
### [@mastra/core@1.37.0](https://github.com/mastra-ai/mastra/blob/@mastra/core@1.37.0/packages/core/CHANGELOG.md)
#### Minor Changes
- Improved agent channels UX: ([#16937](https://github.com/mastra-ai/mastra/pull/16937))
- **Streaming text** — opt-in per-adapter `streaming` flag (`boolean | { updateIntervalMs?: number }`) that pushes the agent's text deltas into the platform message progressively via the Chat SDK.
- **Adaptive typing indicator** — the platform's typing status now reflects what the agent is doing (`is working…` at run start, `is thinking…` during reasoning, `is typing…` while generating text, `is calling {toolName}…` while a tool runs, `is saving to memory…`/`is recalling memory…` during memory work, `is requesting approval for {toolName}…` while a tool is suspended), coalesced so the platform API isn't called on every delta. Skipped while a streaming session is open since the widget itself conveys progress.
- **`toolDisplay` modes** — new `ChannelAdapterConfig.toolDisplay` controls how tool calls render:
- `'cards'` (default) — per-tool running/result cards in rich Block Kit form.
- `'text'` — per-tool running/result messages as plain text (replaces the old `cards: false` flag).
- `'timeline'` — every tool gets its own task row in a streaming widget with status icons and args.
- `'grouped'` — all tools in the run collapse into a single streaming widget; args fold inline into the title and successful results are suppressed for an at-a-glance summary (errors keep their full text).
- `'hidden'` — tools run silently; only the typing indicator shows work.
- **Function form (`ToolDisplayFn`)** — pass a function `(event, ctx) => { kind: 'post', message } | { kind: 'stream', chunk } | undefined` to fully control how every tool event renders. `'post'` results post a discrete message (closing/reopening the streaming session when needed); `'stream'` results push a `task_update`/`plan_update` into the active streaming widget; `undefined` skips the event. Every `ToolDisplayEvent` variant (`running`/`result`/`error`/`approval`) carries a stable `toolCallId` so user code can correlate events for the same tool invocation (e.g. as the `id` on a streamed `task_update` so the SDK updates a row in place rather than appending a new one).
`'timeline'` and `'grouped'` require `streaming: true` and fall back to `'cards'` with a one-time warn if not enabled. `'cards'`/`'text'` work under both streaming modes — with `streaming: true`, the driver closes the streaming session around each card, posts it, and reopens on the next chunk. Approve/deny prompts always render as a separate Block Kit card regardless of mode, since inline task entries and plain text can't carry interactive buttons.
**Deprecation (no breaking changes)**: `ChannelAdapterConfig.cards: boolean` and `ChannelAdapterConfig.formatToolCall` are now `@deprecated` and surface in IDEs with a strikethrough. Both still work at runtime — they're mutually exclusive with `toolDisplay` at the type level. When `toolDisplay` is not set: `cards: true` resolves to `toolDisplay: 'cards'`, `cards: false` resolves to `toolDisplay: 'text'`, and `formatToolCall` is shimmed into an equivalent `ToolDisplayFn` that only fires on `result`/`error` events. Migrate at your leisure: `cards: false` → `toolDisplay: 'text'`, and `formatToolCall: ({ toolName, result }) => msg` → `toolDisplay: event => event.kind === 'result' ? { kind: 'post', message: msg } : undefined`.
- **`typingStatus` customization** — new `ChannelAdapterConfig.typingStatus` (`boolean | (chunk, ctx) => string | false`, default `true`). Set to `false` to suppress all typing indicators (useful when a live streaming widget already conveys progress), or pass a function to set custom copy per chunk. Compose with the exported `defaultTypingStatus` helper to fall back to built-in defaults for chunks you don't handle.
- **Signal-aware message boundaries** — when a `data-user-message` signal echoes into the stream mid-reply, any in-flight text is flushed first so the agent's response renders as a new message after the user's signal instead of streaming into the prior reply.
- **Stronger stay-silent prompt** — the channel input processor's non-DM system message now explicitly calls out anti-patterns (bracketed status notes, "Got it"/"Noted" acknowledgments, apologizing for silence) and points the model at `add_reaction` for silent acknowledgments. Empty responses are framed as a first-class action rather than a fallback.
- **Slack DM thread routing** — each Slack thread (including top-level DMs) now maps to its own Mastra thread. Previously, replies and tool-approval clicks in a top-level DM could be routed into a sub-thread keyed by the bot's last message, causing follow-ups to thread under that message and tool approvals to fail to find the pending approval.
- **Parallel same-tool approval** — fixed a bug where two parallel calls to the same tool with `requireApproval: true` clobbered each other's pending entry, so only the most recent could be approved.
- **Tool error rendering** — failing tools now emit a closing task update in `'timeline'`/`'grouped'` modes (previously the row stayed `in_progress` and rendered as ⚠ at session close) and edit their card in `'cards'`/`'hidden'` modes. The error text is inlined into the task `details` (with a ⚠ glyph) while the task itself stays `status: 'complete'` so a single tool failure doesn't flip the overall plan header to an error state.
- **Observational-memory lifecycle in streaming widgets** — `data-om-buffering-*` and `data-om-activation` chunks are routed into the active streaming session in `'timeline'`/`'grouped'` modes as their own task rows (e.g. `Saved to memory (10x)` with `12.4k → 1.2k tokens`), so memory work is visible alongside tool calls. Consecutive observation activations within a session coalesce into a single `Recalled memory (Nx)` row with running totals instead of stacking — reflection runs often fire several activations back-to-back. The plan title is set to `Updating memory` on the first OM event so memory-only runs don't show the chat-SDK default of `Thinking completed`. OM buffering runs async in the background, so any still-`in_progress` OM task is optimistically marked `complete` when the streaming session closes — without this, the chat-SDK plan widget would flip the "Saving to memory…" row to an error icon when the stream ends before the buffer flush resolves. In non-plan modes (`'cards'`/`'text'`/`'hidden'`/`ToolDisplayFn`) OM events are skipped entirely — a phantom Plan widget showing only memory rows would be inconsistent with the mode contract. Approval `plan_update`/`task_update` rows are also gated by `'timeline'`/`'grouped'` for the same reason; non-plan modes now post the approval card directly without flashing a single-row Plan widget that closes immediately after.
- **Logger propagation** — the Mastra logger is now propagated into `AgentChannels` on register so channel-level logs flow through the configured logger.
- **Internal refactor (no public API change)** — `consumeAgentStream` now dispatches to one of two focused drivers (streaming vs static) instead of switching on `toolDisplay` inside a single 700-line loop. Tool-call correlation moved into a `ToolTracker` helper and observational-memory rendering into a dedicated `renderOmTaskUpdate` helper, both shared between drivers. Invalid combinations now warn and downgrade: `streaming: false` + `'timeline'`/`'grouped'` falls back to `'cards'`. `streaming: true` + `'cards'`/`'text'` is now valid and uses the streaming driver's close/post/reopen lifecycle.
```ts
import { Agent } from '@mastra/core/agent';
import { defaultTypingStatus } from '@mastra/core/channels';
import { createDiscordAdapter } from '@mastra/discord';
const agent = new Agent({
name: 'support-bot',
channels: {
adapters: {
discord: {
adapter: createDiscordAdapter(),
streaming: true,
toolDisplay: 'grouped', // 'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn
typingStatus: false, // suppress typing indicator when the widget already shows progress
// Custom typing status per chunk; fall back to defaults for everything else.
// typingStatus: (chunk, ctx) => {
// if (chunk.type === 'tool-call' && chunk.payload.toolName === 'searchDocs') {
// return 'is searching docs…';
// }
// return defaultTypingStatus(chunk, ctx);
// },
// Custom tool rendering via the function form: skip the running message,
// post a single line on result. `undefined` skips rendering that event.
// toolDisplay: event => {
// if (event.kind !== 'result') return undefined;
// return { kind: 'post', message: `🛠 ${event.toolName} → ${event.resultText}` };
// },
},
},
},
});
```
- Client-side tools now appear in your traces when observability is configured. When an agent calls a tool that executes in the browser via `@mastra/client-js`, a `CLIENT_TOOL_CALL` span is recorded on the server trace so you can see which client tools were invoked, what arguments they received, and how they relate to the rest of the agent run. ([#16425](https://github.com/mastra-ai/mastra/pull/16425))
Tools also gain an `observe` helper on their execution context for recording child spans and logs from inside `execute`:
```ts
execute: async ({ userId }, { observe }) => {
observe.log('info', 'fetching user', { userId });
return observe.span('fetch user', () => fetch(`/api/users/${userId}`));
};
```
- Added signal delivery option attributes API that conditionally merges branch `attributes` based on whether a signal is delivered to an active agent run (`ifActive.attributes`) or an idle run (`ifIdle.attributes`). This enables contextual signal delivery — for example, tagging user messages as `while-active` when the agent is actively working. ([#16923](https://github.com/mastra-ai/mastra/pull/16923))
- Add per-provider capability files and `auto` mode for `observeAttachments` ([#16922](https://github.com/mastra-ai/mastra/pull/16922))
- Generate per-provider capability files (e.g. `capabilities/openai.json`) alongside the model router registry, sourced from models.dev API
- Export `modelSupportsAttachments(modelRouterId)` from `@mastra/core/llm` to check whether a model supports image/file attachments
- Extend `observeAttachments` config to accept `'auto'` in addition to `boolean | string[]`
- When set to `'auto'`, the observer resolves the model (including function-based models) and checks the capability registry before deciding to forward or drop attachment parts
#### Patch Changes
- Update provider registry and model documentation with latest models and providers ([`cfa2e3a`](https://github.com/mastra-ai/mastra/commit/cfa2e3a5292322f48bb28b4d257d631da7f9d3cc))
- Added sub-agent token usage to `onDelegationComplete` results, so apps can track per-sub-agent token costs ([#15825](https://github.com/mastra-ai/mastra/pull/15825))
- Fixed thread subscription streams stalling or deadlocking when multiple consumers observe the same active run. Thread streams are now multicast to subscribers so each subscriber receives the run without competing for the underlying stream, and follow-up messages can continue while a subscription is active. ([#16946](https://github.com/mastra-ai/mastra/pull/16946))
- Connecting or disconnecting an agent through a channel (e.g. Slack) now requires the same write permission as editing the underlying stored agent. The check runs on `POST /channels/:platform/connect` and `POST /channels/:platform/:agentId/disconnect` whenever the target agent has a record in the stored-agents store. Callers without write access receive a `404 Not found`, matching the behavior of the stored-agent edit routes. Agents defined in code (no stored-agents record) are unaffected and continue to honor only the route's existing auth requirement. ([#16949](https://github.com/mastra-ai/mastra/pull/16949))
The caller must either own the stored agent, have admin bypass, or hold `agents:edit` (or a scoped `agents:edit:<agentId>`).
```http
POST /channels/slack/connect
Authorization: Bearer <token-with-agents:edit>
Content-Type: application/json
{ "agentId": "support-bot" }
```
```http
POST /channels/slack/support-bot/disconnect
Authorization: Bearer <token-with-agents:edit>
```
`@mastra/core` is bumped as a patch to ship the regenerated permission definitions that back this check.
- Made provider capabilities loading lazy instead of bulk-syncing on every registry access. Removed subscription setup from harness switchMode() — subscriptions are now lazily ensured at send time. Added 10s TTL cache and invalidation method for listAvailableModels(). ([#17008](https://github.com/mastra-ai/mastra/pull/17008))
- Preserve `this` when resolving permissions for the "View as role" picker in `buildCapabilities`. Class-based RBAC providers (e.g. `@mastra/auth-workos`) read state from `this` inside `getPermissionsForRole`, but the method was being detached to a bare variable before invocation. This caused a `TypeError: Cannot read properties of undefined (reading 'options')` to be logged on every authenticated request and silently emptied the available roles list. The error was swallowed by a surrounding try/catch so admins saw an empty picker instead of a crash. ([#17112](https://github.com/mastra-ai/mastra/pull/17112))
- Fixed `workflow.parallel()` type-checking for steps that declare `requestContextSchema`. ([#16989](https://github.com/mastra-ai/mastra/pull/16989))
Steps with matching request context now type-check correctly.
Steps with mismatched request context still fail with a type error.
Fixes #16975.
- Fixed split-brain broker election race in UnixSocketPubSub. When a broker process dies and multiple clients recover concurrently, an exclusive lock file now serializes the election so exactly one process becomes the new broker. ([#16955](https://github.com/mastra-ai/mastra/pull/16955))
- Fixed a crash that could occur when background execution is enabled for tools with Zod v3 input schemas. ([#16915](https://github.com/mastra-ai/mastra/pull/16915))
Tools with Zod v3, Zod v4, and JSON Schema input definitions now work consistently with background execution.
- Fixed processor workflow steps so `sendSignal` is available when processors inject Agent signals, and updated Observational Memory temporal gap markers to use Agent signals. ([#16927](https://github.com/mastra-ai/mastra/pull/16927))
- Fixed an issue where thread subscriptions could appear idle after a run finished. Subsequent runs now stream promptly, and post-finish signals correctly start from an idle state. ([#16928](https://github.com/mastra-ai/mastra/pull/16928))
- Fixed agent responses being ordered before the user message that triggered them in long conversations. This prevents duplicate tool calls in the next step. This regression started in 1.35.0. Fixes https://github.com/mastra-ai/mastra/issues/16893. ([#16913](https://github.com/mastra-ai/mastra/pull/16913))
- Fixed agent crash when `backgroundTasks.enabled: true` is combined with a tool whose `inputSchema` uses Zod's `.refine()` or `.superRefine()`. On Zod v4, such agents threw `Cannot overwrite keys on object schemas containing refinements` on repeated invocations and became unusable. Tools with refined input schemas now work with background tasks. ([#16966](https://github.com/mastra-ai/mastra/pull/16966))
- UnixSocketPubSub: skip serialization when broker has 0 remote clients, lazily build ServerFrame only when a subscribed client exists, and automatically elect a new broker with resubscription when the active broker disconnects. ([#16939](https://github.com/mastra-ai/mastra/pull/16939))
- Fixed v1 agent tools to preserve legacy tool result output while still allowing usage data in delegation hooks. ([#17070](https://github.com/mastra-ai/mastra/pull/17070))
- Fixed thread stream subscriptions so streamed agent responses are saved to memory while still supporting multiple subscribers. ([#16982](https://github.com/mastra-ai/mastra/pull/16982))
- Gate stored-workspace handlers by author. Previously any authenticated caller within a tenant could list, read, update, or delete another user's workspace. ([#16974](https://github.com/mastra-ai/mastra/pull/16974))
**Behavior changes**
- `POST /stored/workspaces` — server stamps `authorId` from the authenticated caller; any body-provided `authorId` is ignored.
- `GET /stored/workspaces/:id`, `PATCH /stored/workspaces/:id`, `DELETE /stored/workspaces/:id` — return `404 Not found` unless the caller is the owner, an admin (`*`), or holds `stored-workspaces:<action>[:<id>]`.
- `GET /stored/workspaces` — filters to the caller's own rows plus legacy unowned records; admins still see every row.
- Legacy workspaces created before this change (no `authorId`) remain accessible to any authenticated caller for backwards compatibility.
**Example**
```ts
// Client POST body — authorId is ignored if sent
await fetch('/stored/workspaces', {
method: 'POST',
body: JSON.stringify({ name: 'My workspace', authorId: 'someone-else' }),
});
// Stored row — authorId is stamped from the authenticated caller
// {
// id: 'my-workspace',
// name: 'My workspace',
// authorId: 'user_abc123', // from requestContext, NOT from body
// ...
// }
```
**Migration**
- Existing rows with `authorId === null/undefined` remain readable/writable by any authenticated caller — no action required for backwards compatibility.
- To lock down legacy rows, backfill `authorId` directly in the `workspaces` table with the original creator's id.
- For service accounts or tooling that need cross-user access, grant `stored-workspaces:*` (or per-id `stored-workspaces:<action>:<id>`) instead of relying on the legacy unowned bypass.
- Admins (callers with `*`) continue to see and mutate every row regardless of `authorId`.
The `@mastra/core` patch regenerates `permissions.generated.ts` to include the `auth` and `infrastructure` resources that already had routes on `main`.
- Agent Builder action routes (`/agent-builder/*`) are now registered automatically through the standard server route pipeline. Any adapter built on `@mastra/server` (Hono, Express, Fastify, Koa, etc.) serves the 15 `/agent-builder/*` endpoints without consumers wiring them manually. ([#17085](https://github.com/mastra-ai/mastra/pull/17085))
**Example**
```ts
import { MastraClient } from '@mastra/client-js';
const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
// `/agent-builder/*` routes are now reachable out-of-the-box
const actions = await client.getAgentBuilderActions();
const action = client.getAgentBuilderAction('generate-agent');
const { runId } = await action.createRun();
const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId);
```
**Why**
Previously, `AGENT_BUILDER_ROUTES` was a type-only entry in the route registry to keep `@mastra/agent-builder` out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of `@mastra/agent-builder` is preserved — handlers still resolve the workflow module on first request via dynamic `import()`, so Cloudflare bundles are unaffected.
**New EE permissions**
The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes:
- `agent-builder:read`
- `agent-builder:write`
- `agent-builder:execute`
Two legacy stream routes (`STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`, `OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`) are now registered through the standard pipeline as well.
- Fixed a race that could cause an immediate auto-resume of a suspended tool call to fail on some storage backends. Resume now succeeds reliably whether the underlying storage is fast or slow. ([#17015](https://github.com/mastra-ai/mastra/pull/17015))
Fixes [#16158](https://github.com/mastra-ai/mastra/issues/16158).
- Fixed a race in Harness `sendMessage` where a phantom `agent_end: 'complete'` event could fire before any chunks arrived. Subscribers, such as apps running on Cloudflare Workers or Durable Objects, will no longer miss text deltas, messages, or tool events when an agent run completes. ([#17024](https://github.com/mastra-ai/mastra/pull/17024))
The cause was `AgentThreadStreamRuntime.subscribeToThread`'s `activeRunId()` returning `null` during the gap between `sendSignal` reserving a runId and `registerRun` populating the stream record, which made `waitForCurrentThreadStreamIdle()` exit immediately and `sendMessage` emit a synthetic `agent_end`. The subscriber now treats both a reserved-but-not-yet-registered local run and an active remote run as live, matching `sendSignal`'s own behavior.
- Suppressed noisy gateway fetch errors when models.dev is unreachable. The registry no longer retries or logs errors on network failure since all model data is already bundled at publish time. ([#16984](https://github.com/mastra-ai/mastra/pull/16984))
### [@mastra/acp@0.2.0](https://github.com/mastra-ai/mastra/blob/@mastra/acp@0.2.0/packages/acp/CHANGELOG.md)
#### Minor Changes
- Added programmatic model selection for ACP agents using the `model` option. ([#17010](https://github.com/mastra-ai/mastra/pull/17010))
You can now set the model directly when creating `AcpAgent` or `createACPTool`, instead of relying on environment variables.
```ts
const codeAgent = new AcpAgent({
id: 'code-agent',
description: 'ACP-compatible coding agent',
command: 'claude',
args: ['--acp'],
model: 'claude-sonnet-4-20250514',
});
```
Discover available models with `getAvailableModels()` and change the model at runtime with `setModel()`. Invalid model IDs throw a descriptive error listing valid options.
#### Patch Changes
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
### [@mastra/client-js@1.21.0](https://github.com/mastra-ai/mastra/blob/@mastra/client-js@1.21.0/client-sdks/client-js/CHANGELOG.md)
#### Minor Changes
- Client-side tool tracing is now built in. When server-side observability is configured, the SDK automatically measures execution duration and ships it back to the server. To add child spans and structured logs from inside your tool's `execute(input, context)` function, use the `observe` helper on the execution context: ([#16425](https://github.com/mastra-ai/mastra/pull/16425))
```ts
execute: async ({ userId }, { observe }) => {
observe.log('info', 'fetching user', { userId });
return observe.span('fetch user', () => fetch(`/api/users/${userId}`));
};
```
The `createTool()` helper now calls `execute(input, context)` so client tools receive the same execution context shape as core tools.
#### Patch Changes
- Fixed client-side tools getting stuck in `input-available` state in React's `useChat` messages. After a client tool finished executing, the React UI never observed a terminal `tool-result` (or `tool-error`) chunk for it, so the matching `dynamic-tool` part stayed at `state: 'input-available'` indefinitely. The client now emits a synthetic Mastra-shaped terminal chunk into the streamed response right after the client tool resolves or rejects, so the React reducer correctly flips the part to `output-available` (or `output-error`) and renders the tool result. ([#16916](https://github.com/mastra-ai/mastra/pull/16916))
Also fixed the client stream parser so final `tool-call` chunks are not treated as partial streaming tool calls while preparing client-tool continuation messages.
- Port the `yj/magnificent-marquess` frontend stack onto `rain-purpose`. ([#17105](https://github.com/mastra-ai/mastra/pull/17105))
- `@mastra/client-js`: new `ToolProvider` resource and a `getModelPolicy` accessor on the root client. Route types regenerated for the new endpoints.
- `@internal/playground`: Agent Builder routes (agents, skills, infrastructure, favorite, library) wired into the router, `RoutePermissionGuard` and `RoleImpersonationProvider` applied to the app shell, new login layout, role-impersonation banner, `useRestoreFocus` hook, `StudioIndexRedirect` home, and supporting tweaks across agents, browser view, LLM, and CMS surfaces.
Existing client-tools-on-signals work and the unrouted Agent Builder view/edit pages are preserved.
- Made optional memory response fields optional in server schemas and generated client types. ([#17070](https://github.com/mastra-ai/mastra/pull/17070))
- Add `StoredSkill.favorite()` and `StoredSkill.unfavorite()` methods, mirroring the existing `StoredAgent` favorite API. Both are idempotent and call `PUT`/`DELETE /api/stored/skills/:id/favorite`. ([#17101](https://github.com/mastra-ai/mastra/pull/17101))
- Agent Builder action routes (`/agent-builder/*`) are now registered automatically through the standard server route pipeline. Any adapter built on `@mastra/server` (Hono, Express, Fastify, Koa, etc.) serves the 15 `/agent-builder/*` endpoints without consumers wiring them manually. ([#17085](https://github.com/mastra-ai/mastra/pull/17085))
**Example**
```ts
import { MastraClient } from '@mastra/client-js';
const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
// `/agent-builder/*` routes are now reachable out-of-the-box
const actions = await client.getAgentBuilderActions();
const action = client.getAgentBuilderAction('generate-agent');
const { runId } = await action.createRun();
const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId);
```
**Why**
Previously, `AGENT_BUILDER_ROUTES` was a type-only entry in the route registry to keep `@mastra/agent-builder` out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of `@mastra/agent-builder` is preserved — handlers still resolve the workflow module on first request via dynamic `import()`, so Cloudflare bundles are unaffected.
**New EE permissions**
The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes:
- `agent-builder:read`
- `agent-builder:write`
- `agent-builder:execute`
Two legacy stream routes (`STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`, `OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`) are now registered through the standard pipeline as well.
- Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. ([#17045](https://github.com/mastra-ai/mastra/pull/17045))
Enable automatic reconnection with `subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true })`.
- Fixed `clientTools` being silently dropped — and never executed — on thread-backed chats. When a chat had a `threadId`, the React `useChat` hook routed messages through the new agent signals path but did not pass the `clientTools` map into the signal startup flow, so client-side tools were unavailable when the model requested them. ([#16540](https://github.com/mastra-ai/mastra/pull/16540))
The signals path now carries `clientTools` and other per-send stream options on `sendSignal`. When the subscribed stream finishes with `tool-calls`, the client executes matching local tools with observability support, emits tool result chunks, and posts a continuation with the assistant tool-call messages plus tool-result messages so the run resumes on the same thread with the same per-send options.
### [@mastra/convex@1.2.0](https://github.com/mastra-ai/mastra/blob/@mastra/convex@1.2.0/stores/convex/CHANGELOG.md)
#### Minor Changes
- Convex can now persist channel installations and provider configuration. ([#16718](https://github.com/mastra-ai/mastra/pull/16718))
```ts
import { ConvexStore } from '@mastra/convex';
const storage = new ConvexStore({
id: 'app-storage',
deploymentUrl: process.env.CONVEX_URL!,
adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
});
const channels = await storage.getStore('channels');
await channels?.saveInstallation({
id: 'slack-agent-1',
platform: 'slack',
agentId: 'agent-1',
status: 'active',
webhookId: 'webhook-1',
data: { teamId: 'T123', botUserId: 'U123' },
createdAt: new Date(),
updatedAt: new Date(),
});
```
- Workflow schedules can now be stored in Convex. ([#16710](https://github.com/mastra-ai/mastra/pull/16710))
```ts
import { ConvexStore } from '@mastra/convex';
const storage = new ConvexStore({
id: 'app-storage',
deploymentUrl: process.env.CONVEX_URL!,
adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
});
const schedules = await storage.getStore('schedules');
await schedules?.createSchedule({
id: 'daily-summary',
target: { type: 'workflow', workflowId: 'summary-workflow' },
cron: '0 9 * * *',
status: 'active',
nextFireAt: Date.now(),
createdAt: Date.now(),
updatedAt: Date.now(),
});
```
#### Patch Changes
- Improved Convex background task reliability with safer lifecycle updates, faster filtering, and smoother upgrades from older storage rows. ([#16724](https://github.com/mastra-ai/mastra/pull/16724))
### [@mastra/deployer@1.37.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer@1.37.0/packages/deployer/CHANGELOG.md)
#### Patch Changes
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
### [@mastra/editor@0.10.0](https://github.com/mastra-ai/mastra/blob/@mastra/editor@0.10.0/packages/editor/CHANGELOG.md)
#### Minor Changes
- Ship `EditorAgentBuilder` and Agent Builder runtime through the `@mastra/editor/ee` subpath. ([#16948](https://github.com/mastra-ai/mastra/pull/16948))
- Adds `EditorAgentBuilder` class and supporting types under `@mastra/editor/ee` (dormant unless `MastraEditorConfig.builder` is configured).
- Wires builder resolution on `MastraEditor`: `hasEnabledBuilderConfig()`, `resolveBuilder()`, `ensureBuilderWorkspaces()`, and `reconcileBuilderWorkspaces()`.
- Adds builder defaults plumbing in the agent namespace (`applyBuilderDefaults`, `BUILDER_BASELINE_DEFAULTS` enabling `observationalMemory: true` by default for Builder-created agents).
- Adds a defense-in-depth license guard inside `MastraEditor.resolveBuilder()` that mirrors the server-startup check in `MastraServer.validateAgentBuilderLicense()`. Dev environments bypass via `isEEEnabled()`; production without a valid `MASTRA_EE_LICENSE` throws `[mastra/auth-ee] Agent Builder is configured but no valid EE license was found.`
- Bumps the `@mastra/core` peer dependency to `>=1.34.0-0 <2.0.0-0` to cover the `@mastra/core/agent-builder/ee` and `@mastra/core/auth/ee` subpaths consumed by the builder runtime.
Opt-in usage:
```ts
import { Mastra } from '@mastra/core';
import { MastraEditor } from '@mastra/editor';
const editor = new MastraEditor({
builder: {
enabled: true,
configuration: {
agent: {
models: { default: { provider: 'openai', modelId: 'gpt-4o-mini' } },
},
},
},
});
new Mastra({ storage, editor });
// Later, on demand:
const builder = await editor.resolveBuilder();
// `builder` is undefined when the builder is not configured/enabled.
// In production it requires a valid MASTRA_EE_LICENSE; dev environments bypass.
```
This is plumbing — no UI consumer ships in this release.
#### Patch Changes
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
### [@mastra/evals@1.2.3](https://github.com/mastra-ai/mastra/blob/@mastra/evals@1.2.3/packages/evals/CHANGELOG.md)
#### Patch Changes
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
### [@mastra/express@1.3.24](https://github.com/mastra-ai/mastra/blob/@mastra/express@1.3.24/server-adapters/express/CHANGELOG.md)
#### Patch Changes
- Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. ([#16335](https://github.com/mastra-ai/mastra/pull/16335))
```ts
registerApiRoute('/stream', {
method: 'GET',
handler: async c => {
const stream = await agent.stream(prompt, {
abortSignal: c.req.raw.signal,
});
return stream.toTextStreamResponse();
},
});
```
- Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. ([#17045](https://github.com/mastra-ai/mastra/pull/17045))
Enable automatic reconnection with `subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true })`.
### [@mastra/fastembed@1.1.1](https://github.com/mastra-ai/mastra/blob/@mastra/fastembed@1.1.1/packages/fastembed/CHANGELOG.md)
#### Patch Changes
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
### [@mastra/fastify@1.3.24](https://github.com/mastra-ai/mastra/blob/@mastra/fastify@1.3.24/server-adapters/fastify/CHANGELOG.md)
#### Patch Changes
- Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. ([#16335](https://github.com/mastra-ai/mastra/pull/16335))
```ts
registerApiRoute('/stream', {
method: 'GET',
handler: async c => {
const stream = await agent.stream(prompt, {
abortSignal: c.req.raw.signal,
});
return stream.toTextStreamResponse();
},
});
```
### [@mastra/files-sdk@0.2.0](https://github.com/mastra-ai/mastra/blob/@mastra/files-sdk@0.2.0/workspaces/files-sdk/CHANGELOG.md)
#### Minor Changes
- Added @mastra/files-sdk workspace filesystem provider — a unified storage adapter backed by [FilesSDK](https://files-sdk.dev). Supports any FilesSDK adapter (S3, R2, GCS, Azure Blob, Vercel Blob, local filesystem, and more) through a single `FilesSDKFilesystem` class that implements the `WorkspaceFilesystem` interface. ([#17027](https://github.com/mastra-ai/mastra/pull/17027))
**Usage**
```ts
import { Files } from 'files-sdk';
import { s3 } from 'files-sdk/s3';
import { FilesSDKFilesystem } from '@mastra/files-sdk';
const files = new Files({ adapter: s3({ bucket: 'my-bucket', region: 'us-east-1' }) });
const filesystem = new FilesSDKFilesystem({ files });
```
Swap adapters without changing code — just replace `s3()` with `r2()`, `gcs()`, `azure()`, `fs()`, etc.
#### Patch Changes
### [@mastra/hono@1.4.19](https://github.com/mastra-ai/mastra/blob/@mastra/hono@1.4.19/server-adapters/hono/CHANGELOG.md)
#### Patch Changes
- Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. ([#16335](https://github.com/mastra-ai/mastra/pull/16335))
```ts
registerApiRoute('/stream', {
method: 'GET',
handler: async c => {
const stream = await agent.stream(prompt, {
abortSignal: c.req.raw.signal,
});
return stream.toTextStreamResponse();
},
});
```
- Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. ([#17045](https://github.com/mastra-ai/mastra/pull/17045))
Enable automatic reconnection with `subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true })`.
- Fixed SSE streams so browser clients can connect to long-lived subscriptions before the first stream event is available. ([#16540](https://github.com/mastra-ai/mastra/pull/16540))
### [@mastra/koa@1.5.7](https://github.com/mastra-ai/mastra/blob/@mastra/koa@1.5.7/server-adapters/koa/CHANGELOG.md)
#### Patch Changes
- Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. ([#16335](https://github.com/mastra-ai/mastra/pull/16335))
```ts
registerApiRoute('/stream', {
method: 'GET',
handler: async c => {
const stream = await agent.stream(prompt, {
abortSignal: c.req.raw.signal,
});
return stream.toTextStreamResponse();
},
});
```
- Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. ([#17045](https://github.com/mastra-ai/mastra/pull/17045))
Enable automatic reconnection with `subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true })`.
### [@mastra/mcp@1.8.1](https://github.com/mastra-ai/mastra/blob/@mastra/mcp@1.8.1/packages/mcp/CHANGELOG.md)
#### Patch Changes
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
### [@mastra/memory@1.20.0](https://github.com/mastra-ai/mastra/blob/@mastra/memory@1.20.0/packages/memory/CHANGELOG.md)
#### Minor Changes
- Add per-provider capability files and `auto` mode for `observeAttachments` ([#16922](https://github.com/mastra-ai/mastra/pull/16922))
- Generate per-provider capability files (e.g. `capabilities/openai.json`) alongside the model router registry, sourced from models.dev API
- Export `modelSupportsAttachments(modelRouterId)` from `@mastra/core/llm` to check whether a model supports image/file attachments
- Extend `observeAttachments` config to accept `'auto'` in addition to `boolean | string[]`
- When set to `'auto'`, the observer resolves the model (including function-based models) and checks the capability registry before deciding to forward or drop attachment parts
#### Patch Changes
- Fixed observational memory so in-progress messages are not removed during observation cleanup. ([#16913](https://github.com/mastra-ai/mastra/pull/16913))
- Removed zod as a required peer dependency. Internal schemas now use plain JSON Schema objects instead of zod runtime. ([#16726](https://github.com/mastra-ai/mastra/pull/16726))
- Fixed processor workflow steps so `sendSignal` is available when processors inject Agent signals, and updated Observational Memory temporal gap markers to use Agent signals. ([#16927](https://github.com/mastra-ai/mastra/pull/16927))
### [@mastra/observability@1.14.0](https://github.com/mastra-ai/mastra/blob/@mastra/observability@1.14.0/observability/mastra/CHANGELOG.md)
#### Minor Changes
- Support ingesting client-side tool telemetry. Spans, logs, and duration metrics captured by the client SDK during tool execution are forwarded through the observability bus to your existing exporters. Client tool durations are reported via the existing `mastra_tool_duration_ms` metric with a `toolType: 'client'` label to distinguish them from server-side tool durations. ([#16425](https://github.com/mastra-ai/mastra/pull/16425))
#### Patch Changes
- Paused observability uploads after invalid credentials so exporters stop repeatedly sending unauthorized requests. ([#16743](https://github.com/mastra-ai/mastra/pull/16743))
### [@mastra/playground-ui@30.0.0](https://github.com/mastra-ai/mastra/blob/@mastra/playground-ui@30.0.0/packages/playground-ui/CHANGELOG.md)
#### Minor Changes
- Added a Drawer component — a panel that slides in from any edge of the screen with swipe-to-dismiss gestures. ([#16958](https://github.com/mastra-ai/mastra/pull/16958))
The Drawer can be anchored to any of the four screen edges and supports snap points, nested stacking, controlled state, non-modal mode, swipe-to-open areas, and detached triggers.
```tsx
import {
Drawer,
DrawerTrigger,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerDescription,
DrawerFooter,
DrawerClose,
Button,
} from '@mastra/playground-ui';
<Drawer side="right">
<DrawerTrigger asChild>
<Button>Open</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Library</DrawerTitle>
<DrawerDescription>A panel that slides in from the right edge.</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose asChild>
<Button variant="outline">Close</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>;
```
- Added a reusable `HoverCard` component (`HoverCard`, `HoverCardTrigger`, `HoverCardContent`) built on Base UI. You can now use these exported components to add hover card interactions anywhere in your UI. ([#16919](https://github.com/mastra-ai/mastra/pull/16919))
```tsx
import { HoverCard, HoverCardTrigger, HoverCardContent } from '@mastra/playground-ui';
<HoverCard>
<HoverCardTrigger>Weather Agent</HoverCardTrigger>
<HoverCardContent>Answers questions about current conditions and forecasts.</HoverCardContent>
</HoverCard>;
```
#### Patch Changes
- Migrated the Switch component to Base UI for smoother animations and consistent behavior. No API changes — `checked`, `defaultChecked`, `onCheckedChange`, and `disabled` work exactly as before. ([#16891](https://github.com/mastra-ai/mastra/pull/16891))
- Improved the Select component by migrating it to Base UI for more reliable positioning and accessibility. The public API (`Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, `SelectValue`, `SelectGroup`) is unchanged, so no consumer updates are needed. ([#16918](https://github.com/mastra-ai/mastra/pull/16918))
- Added `DataList.RowStatic`, a non-interactive row primitive. It renders a row that looks identical to other list rows but does not respond to clicks and shows no hover/focus state — use it alongside `DataList.RowButton` / `DataList.RowLink` when only some rows are clickable (e.g. error or placeholder entries in an otherwise navigable list). ([#16970](https://github.com/mastra-ai/mastra/pull/16970))
```tsx
{
rows.map(row =>
row.href ? (
<DataList.RowLink key={row.id} to={row.href} LinkComponent={Link}>
{row.cells}
</DataList.RowLink>
) : (
<DataList.RowStatic key={row.id}>{row.cells}</DataList.RowStatic>
),
);
}
```
- Added `DataList` primitives and props for building selection-aware, condensed list rows that match the Traces/Logs visual style. ([#16820](https://github.com/mastra-ai/mastra/pull/16820))
**New cells** on `DataList`:
- `IdCell` — compact mono cell that truncates long IDs to 8 chars.
- `MonoCell` — compact mono + truncate text cell (for input previews, JSON summaries, etc.).
- `DateCell` — compact date cell rendering "Today" or "MMM dd".
- `TimeCell` — compact mono time cell rendering `HH:mm:ss.SSS` with the millisecond portion tinted.
- `SelectCell` — labelled checkbox cell with a shift-key range-select handler.
- `TopSelectCell` — header version with `indeterminate` support for "select all".
- `TopCells` — non-interactive header sibling of `RowButton`, for hosting top cells beside a leading select cell.
**New props** on `DataList.RowButton` and `DataList.RowLink`:
- `flushLeft` — drops the default left margin when wrapped beside a leading cell.
- `colStart` — places the row starting at a column line (e.g. `colStart={2}` to leave column 1 for a leading cell).
- `featured` — applies the highlighted background to mark the active row.
**New props** on existing wrappers:
- `as` on `DataList.Cell` and `DataList.TopCell` — render the cell as any HTML element (e.g. `<label>` so the whole cell is clickable).
- `hasLeadingCell` on `DataList.Top` — drops default gap and left padding so a leading cell sits flush, mirroring how `Row` + `RowButton` compose.
**Example** — selection row with a checkbox in column 1 and an interactive button spanning the rest:
```tsx
<DataList.Row>
<DataList.SelectCell checked={isSelected} onToggle={shiftKey => toggle(id, shiftKey)} />
<DataList.RowButton flushLeft colStart={2} featured={isFeatured} onClick={onRowClick}>
<DataList.IdCell id={item.id} />
<DataList.MonoCell>{item.input}</DataList.MonoCell>
</DataList.RowButton>
</DataList.Row>
```
Internally the Traces and Logs list views now use the shared primitives — no behavior change for those consumers.
- Added support for trailing cells in `DataList` rows. `DataList.RowButton` and `DataList.RowLink` now accept `colEnd` and `flushRight` (mirrors of the existing `colStart`/`flushLeft`), so a row can sit beside a non-interactive trailing cell (e.g. an actions column) and stay aligned with the header. Rows wrapped in `DataList.Row` now render a full-width separator that extends through the leading and trailing cells. `DataList.MonoCell` also gained an optional `height` prop so non-compact lists can use it without forcing compact padding. ([#16888](https://github.com/mastra-ai/mastra/pull/16888))
**Usage**
```tsx
<DataList.Row>
<DataList.RowButton flushLeft flushRight colEnd={-2} onClick={onClick}>
{/* main row content */}
</DataList.RowButton>
<DataList.Cell>
{/* trailing actions, e.g. icon buttons */}
</DataList.Cell>
</DataList.Row>
<DataList.MonoCell height="default">long mono text…</DataList.MonoCell>
```
- Migrated the Label component off Radix UI. It now renders a native `<label>` element with the same props and styling — `htmlFor`, `className`, and children behave exactly as before. ([#16892](https://github.com/mastra-ai/mastra/pull/16892))
- Fixed Studio Settings page (and other default-height `PageLayout` pages) clipping their content with no scrollbar on viewports shorter than the form. Users on short laptop screens (under ~991px tall) could not reach the Save button under the Mastra Connection headers form, making it impossible to apply changes. Default-height `PageLayout` pages now grow with their content and scroll through the studio chrome wrapper; `height="full"` pages (Logs, Traces, Metrics, etc.) are unchanged. ([#16999](https://github.com/mastra-ai/mastra/pull/16999))
- Restyled scrollbars across the studio UI to match the design system — thin, themed thumb on a transparent track — replacing the default OS scrollbars that clashed with dark and light surfaces. ([#16918](https://github.com/mastra-ai/mastra/pull/16918))
- Exported ContextMenu from the package entry so it can be imported alongside other Base UI primitives. ([#17062](https://github.com/mastra-ai/mastra/pull/17062))
- Design-system additions to support theming: ([#17059](https://github.com/mastra-ai/mastra/pull/17059))
- `Avatar` now accepts optional `color` and `textColor` props for per-instance tinting, and falls back to the initial when the image fails to load.
- `Searchbar` accepts an optional `className` to let consumers tune layout without forking.
- `TabList` accepts a `style` prop and the active-tab indicator now reads from the `--tab-indicator-color` CSS variable, letting parents theme the indicator (e.g. per-agent accent color).
- `stringToColor` now accepts any `number` for the `lightness` argument and defaults to `90` instead of `75` for a lighter fallback chip.
- Global `body` rule enables `font-smoothing` / `-webkit-font-smoothing` for crisper UI text.
- Removed `EntryList` and its sub-components (`EntryList.Header`, `EntryList.Entries`, `EntryList.Entry`, `EntryList.EntryText`, `EntryList.Pagination`, `EntryList.NoMatch`, `EntryListSkeleton`, etc.) from the public API. All in-repo list views have migrated to `DataList`, which is the recommended replacement. ([#16910](https://github.com/mastra-ai/mastra/pull/16910))
**Migration:**
```tsx
// Before
import { EntryList, EntryListSkeleton } from '@mastra/playground-ui';
<EntryList>
<EntryList.Trim>
<EntryList.Header columns={columns} />
<EntryList.Entries>
{items.map(item => (
<EntryList.Entry key={item.id} columns={columns} entry={item} onClick={…}>
{columns.map(col => <EntryList.EntryText key={col.name}>{item[col.name]}</EntryList.EntryText>)}
</EntryList.Entry>
))}
</EntryList.Entries>
</EntryList.Trim>
<EntryList.Pagination …/>
</EntryList>
// After
import { DataList } from '@mastra/playground-ui';
<DataList columns={gridColumns}>
<DataList.Top>
{columns.map(col => <DataList.TopCell key={col.name}>{col.label}</DataList.TopCell>)}
</DataList.Top>
{items.map(item => (
<DataList.RowButton key={item.id} onClick={…}>
{columns.map(col => <DataList.Cell key={col.name}>{item[col.name]}</DataList.Cell>)}
</DataList.RowButton>
))}
<DataList.Pagination …/>
</DataList>
```
- Improved the Checkbox component by migrating it to Base UI. The public API is unchanged — `checked` (including the `'indeterminate'` value), `defaultChecked`, `onCheckedChange`, and `disabled` all behave as before. ([#16905](https://github.com/mastra-ai/mastra/pull/16905))
- Fixed MetricsDataTable sticky header and column backgrounds to use surface3 token, matching DashboardCard surface ([#16828](https://github.com/mastra-ai/mastra/pull/16828))
- Improved the RadioGroup component by migrating it to Base UI. The public API (`RadioGroup`, `RadioGroupItem`, `value`, `onValueChange`, `disabled`) is unchanged. Also fixes the radio indicator sizing/centering — the control now stays square and the inner dot is properly centered. ([#16904](https://github.com/mastra-ai/mastra/pull/16904))
### [@mastra/posthog@1.0.26](https://github.com/mastra-ai/mastra/blob/@mastra/posthog@1.0.26/observability/posthog/CHANGELOG.md)
#### Patch Changes
- Fixed negative PostHog cost calculations when cache tokens are present. ([#16876](https://github.com/mastra-ai/mastra/pull/16876))
### [@mastra/react@0.4.1](https://github.com/mastra-ai/mastra/blob/@mastra/react@0.4.1/client-sdks/react/CHANGELOG.md)
#### Patch Changes
- Added support for reasoning-start and reasoning-end stream chunks so reasoning blocks are opened and closed with provider metadata preserved. ([#17061](https://github.com/mastra-ai/mastra/pull/17061))
- Fixed `clientTools` being silently dropped — and never executed — on thread-backed chats. When a chat had a `threadId`, the React `useChat` hook routed messages through the new agent signals path but did not pass the `clientTools` map into the signal startup flow, so client-side tools were unavailable when the model requested them. ([#16540](https://github.com/mastra-ai/mastra/pull/16540))
The signals path now carries `clientTools` and other per-send stream options on `sendSignal`. When the subscribed stream finishes with `tool-calls`, the client executes matching local tools with observability support, emits tool result chunks, and posts a continuation with the assistant tool-call messages plus tool-result messages so the run resumes on the same thread with the same per-send options.
### [@mastra/sentry@1.1.0](https://github.com/mastra-ai/mastra/blob/@mastra/sentry@1.1.0/observability/sentry/CHANGELOG.md)
#### Minor Changes
- Added the `gen_ai.conversation.id` span attribute to the Sentry exporter, sourced from `metadata.threadId`. Spans from the same chat thread now group together in Sentry's Conversations view (part of AI Agent Monitoring). ([#16925](https://github.com/mastra-ai/mastra/pull/16925))
```typescript
const agent = mastra.getAgent('chat');
// Pass threadId as before — the Sentry exporter now emits it as gen_ai.conversation.id
await agent.generate('Hello', {
memory: { thread: 'thread-123', resource: 'user-1' },
});
```
#### Patch Changes
### [@mastra/server@1.37.0](https://github.com/mastra-ai/mastra/blob/@mastra/server@1.37.0/packages/server/CHANGELOG.md)
#### Minor Changes
- Connecting or disconnecting an agent through a channel (e.g. Slack) now requires the same write permission as editing the underlying stored agent. The check runs on `POST /channels/:platform/connect` and `POST /channels/:platform/:agentId/disconnect` whenever the target agent has a record in the stored-agents store. Callers without write access receive a `404 Not found`, matching the behavior of the stored-agent edit routes. Agents defined in code (no stored-agents record) are unaffected and continue to honor only the route's existing auth requirement. ([#16949](https://github.com/mastra-ai/mastra/pull/16949))
The caller must either own the stored agent, have admin bypass, or hold `agents:edit` (or a scoped `agents:edit:<agentId>`).
```http
POST /channels/slack/connect
Authorization: Bearer <token-with-agents:edit>
Content-Type: application/json
{ "agentId": "support-bot" }
```
```http
POST /channels/slack/support-bot/disconnect
Authorization: Bearer <token-with-agents:edit>
```
`@mastra/core` is bumped as a patch to ship the regenerated permission definitions that back this check.
- Gate stored-workspace handlers by author. Previously any authenticated caller within a tenant could list, read, update, or delete another user's workspace. ([#16974](https://github.com/mastra-ai/mastra/pull/16974))
**Behavior changes**
- `POST /stored/workspaces` — server stamps `authorId` from the authenticated caller; any body-provided `authorId` is ignored.
- `GET /stored/workspaces/:id`, `PATCH /stored/workspaces/:id`, `DELETE /stored/workspaces/:id` — return `404 Not found` unless the caller is the owner, an admin (`*`), or holds `stored-workspaces:<action>[:<id>]`.
- `GET /stored/workspaces` — filters to the caller's own rows plus legacy unowned records; admins still see every row.
- Legacy workspaces created before this change (no `authorId`) remain accessible to any authenticated caller for backwards compatibility.
**Example**
```ts
// Client POST body — authorId is ignored if sent
await fetch('/stored/workspaces', {
method: 'POST',
body: JSON.stringify({ name: 'My workspace', authorId: 'someone-else' }),
});
// Stored row — authorId is stamped from the authenticated caller
// {
// id: 'my-workspace',
// name: 'My workspace',
// authorId: 'user_abc123', // from requestContext, NOT from body
// ...
// }
```
**Migration**
- Existing rows with `authorId === null/undefined` remain readable/writable by any authenticated caller — no action required for backwards compatibility.
- To lock down legacy rows, backfill `authorId` directly in the `workspaces` table with the original creator's id.
- For service accounts or tooling that need cross-user access, grant `stored-workspaces:*` (or per-id `stored-workspaces:<action>:<id>`) instead of relying on the legacy unowned bypass.
- Admins (callers with `*`) continue to see and mutate every row regardless of `authorId`.
The `@mastra/core` patch regenerates `permissions.generated.ts` to include the `auth` and `infrastructure` resources that already had routes on `main`.
- Agent Builder action routes (`/agent-builder/*`) are now registered automatically through the standard server route pipeline. Any adapter built on `@mastra/server` (Hono, Express, Fastify, Koa, etc.) serves the 15 `/agent-builder/*` endpoints without consumers wiring them manually. ([#17085](https://github.com/mastra-ai/mastra/pull/17085))
**Example**
```ts
import { MastraClient } from '@mastra/client-js';
const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
// `/agent-builder/*` routes are now reachable out-of-the-box
const actions = await client.getAgentBuilderActions();
const action = client.getAgentBuilderAction('generate-agent');
const { runId } = await action.createRun();
const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId);
```
**Why**
Previously, `AGENT_BUILDER_ROUTES` was a type-only entry in the route registry to keep `@mastra/agent-builder` out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of `@mastra/agent-builder` is preserved — handlers still resolve the workflow module on first request via dynamic `import()`, so Cloudflare bundles are unaffected.
**New EE permissions**
The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes:
- `agent-builder:read`
- `agent-builder:write`
- `agent-builder:execute`
Two legacy stream routes (`STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`, `OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`) are now registered through the standard pipeline as well.
#### Patch Changes
- Developers can now cancel long-running custom API route work when clients disconnect. Node-based adapters pass abort signals into custom route handlers, clean up response streams correctly, and still surface upstream response body errors. ([#16335](https://github.com/mastra-ai/mastra/pull/16335))
```ts
registerApiRoute('/stream', {
method: 'GET',
handler: async c => {
const stream = await agent.stream(prompt, {
abortSignal: c.req.raw.signal,
});
return stream.toTextStreamResponse();
},
});
```
- Made optional memory response fields optional in server schemas and generated client types. ([#17070](https://github.com/mastra-ai/mastra/pull/17070))
- Improved agent thread subscription resilience by keeping server streams active during idle periods and allowing the JavaScript client to reconnect when subscription streams close or resubscribe requests fail. ([#17045](https://github.com/mastra-ai/mastra/pull/17045))
Enable automatic reconnection with `subscription.processDataStream({ onChunk: chunk => console.log(chunk), reconnect: true })`.
### [@mastra/slack@1.3.0](https://github.com/mastra-ai/mastra/blob/@mastra/slack@1.3.0/channels/slack/CHANGELOG.md)
#### Minor Changes
- Improved Slack channel UX: ([#16937](https://github.com/mastra-ai/mastra/pull/16937))
- **Flat adapter config** — `SlackProvider` now accepts per-adapter options (`formatError`, `streaming`, `typingStatus`, `toolDisplay`) directly at the top level instead of nesting under `adapterConfig`. The `adapterConfig` field still works as a deprecated fallback.
- **Breaking change** — the `cards: boolean` and `formatToolCall` fields have been removed from `SlackProviderConfig`/`SlackAdapterChannelConfig`. Migrate `cards: false` → `toolDisplay: 'text'`, and `formatToolCall: (info) => msg` → `toolDisplay: (event) => event.kind === 'result' ? { kind: 'post', message: msg } : undefined`.
- **Opinionated defaults** — `SlackProvider` now defaults `streaming: true` and `toolDisplay: 'grouped'` since the grouped "Thinking Steps" widget renders well in Slack's AI Assistant UI. When `streaming: false` is explicitly set, `toolDisplay` falls back to `'cards'` since `'grouped'` requires streaming. Override either per-config to restore other modes.
- **AI Assistant manifest** — `assistant:write` is now part of `DEFAULT_BOT_SCOPES` and the generated manifest declares the matching `assistant_view` feature, so newly generated app manifests support the AI Assistant surface and thread context in DMs.
- **Slack DM tool-approval routing** — clicks on tool-approval cards in Slack DMs now resume the correct Mastra thread.
```ts
import { SlackProvider } from '@mastra/slack';
const slack = new SlackProvider({
// Top-level options (preferred):
streaming: true,
toolDisplay: 'grouped', // or 'cards' | 'text' | 'timeline' | 'hidden' | ToolDisplayFn
});
```
#### Patch Changes
### [@mastra/spanner@1.0.0](https://github.com/mastra-ai/mastra/blob/@mastra/spanner@1.0.0/stores/spanner/CHANGELOG.md)
#### Major Changes
- Added a Google Cloud Spanner storage adapter (`@mastra/spanner`) targeting the GoogleSQL dialect. The adapter implements the `memory`, `workflows`, `scores`, `backgroundTasks`, `agents`, `mcpClients`, `mcpServers`, `skills`, `blobs`, `promptBlocks`, `scorerDefinitions`, `schedules`, and `observability` storage domains and works with both managed Cloud Spanner instances and the local Spanner emulator. The `schedules` domain plugs into Mastra's built-in `WorkflowScheduler` for cron-driven workflow triggers, and the `observability` domain persists AI tracing spans in `mastra_ai_spans` to power the Studio traces UI (JSON containment filters compile to per-key `JSON_VALUE` equality and `EXISTS` over `JSON_QUERY_ARRAY` since Spanner has no `@>` operator). ([#15955](https://github.com/mastra-ai/mastra/pull/15955))
```typescript
import { SpannerStore } from '@mastra/spanner';
const storage = new SpannerStore({
id: 'spanner-storage',
projectId: process.env.SPANNER_PROJECT_ID!,
instanceId: process.env.SPANNER_INSTANCE_ID!,
databaseId: process.env.SPANNER_DATABASE_ID!,
});
```
#### Patch Changes
### [@mastra/voice-murf@0.12.1](https://github.com/mastra-ai/mastra/blob/@mastra/voice-murf@0.12.1/voice/murf/CHANGELOG.md)
#### Patch Changes
- Replaced ky HTTP client with native fetch and built-in retry logic, removing the external dependency ([#16960](https://github.com/mastra-ai/mastra/pull/16960))
### Other updated packages
The following packages were updated with dependency changes only:
- [@mastra/agent-builder@1.0.38](https://github.com/mastra-ai/mastra/blob/@mastra/agent-builder@1.0.38/packages/agent-builder/CHANGELOG.md)
- [@mastra/arize@1.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/arize@1.1.2/observability/arize/CHANGELOG.md)
- [@mastra/arthur@0.2.12](https://github.com/mastra-ai/mastra/blob/@mastra/arthur@0.2.12/observability/arthur/CHANGELOG.md)
- [@mastra/braintrust@1.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/braintrust@1.1.2/observability/braintrust/CHANGELOG.md)
- [@mastra/datadog@1.2.2](https://github.com/mastra-ai/mastra/blob/@mastra/datadog@1.2.2/observability/datadog/CHANGELOG.md)
- [@mastra/deployer-cloud@1.37.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-cloud@1.37.0/deployers/cloud/CHANGELOG.md)
- [@mastra/deployer-cloudflare@1.1.38](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-cloudflare@1.1.38/deployers/cloudflare/CHANGELOG.md)
- [@mastra/deployer-netlify@1.1.14](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-netlify@1.1.14/deployers/netlify/CHANGELOG.md)
- [@mastra/deployer-vercel@1.1.32](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-vercel@1.1.32/deployers/vercel/CHANGELOG.md)
- [@mastra/laminar@1.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/laminar@1.1.2/observability/laminar/CHANGELOG.md)
- [@mastra/langfuse@1.3.2](https://github.com/mastra-ai/mastra/blob/@mastra/langfuse@1.3.2/observability/langfuse/CHANGELOG.md)
- [@mastra/langsmith@1.2.2](https://github.com/mastra-ai/mastra/blob/@mastra/langsmith@1.2.2/observability/langsmith/CHANGELOG.md)
- [@mastra/longmemeval@1.0.43](https://github.com/mastra-ai/mastra/blob/@mastra/longmemeval@1.0.43/explorations/longmemeval/CHANGELOG.md)
- [@mastra/mcp-docs-server@1.1.40](https://github.com/mastra-ai/mastra/blob/@mastra/mcp-docs-server@1.1.40/packages/mcp-docs-server/CHANGELOG.md)
- [@mastra/nestjs@0.1.8](https://github.com/mastra-ai/mastra/blob/@mastra/nestjs@0.1.8/server-adapters/nestjs/CHANGELOG.md)
- [@mastra/opencode@0.0.40](https://github.com/mastra-ai/mastra/blob/@mastra/opencode@0.0.40/integrations/opencode/CHANGELOG.md)
- [@mastra/otel-bridge@1.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/otel-bridge@1.1.2/observability/otel-bridge/CHANGELOG.md)
- [@mastra/otel-exporter@1.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/otel-exporter@1.1.2/observability/otel-exporter/CHANGELOG.md)
- [@mastra/temporal@0.1.7](https://github.com/mastra-ai/mastra/blob/@mastra/temporal@0.1.7/workflows/temporal/CHANGELOG.md)