Highlights
Classifiers as a First-Class Primitive (Core + Workflows)
Mastra now supports registering classifiers on new Mastra({ classifiers }), with a full management API (getClassifier*, listClassifiers, addClassifier, removeClassifier) and automatic root tracing for classifier evaluations. Those configured classifiers can also be used as typed workflow steps (including fluent + dynamic graphs) to power typed branching/conditional control flow with token usage.
Policy Enforcement with ClassifierProcessor (Input/Output/Streaming)
New ClassifierProcessor lets you apply typed classifier policies to agent input, output, and streaming content, enabling safety/routing gates with explicit abort behavior. It now fails closed by default (abort on classifier failure), with an opt-in errorStrategy: 'warn' to preserve prior fail-open behavior.
Native Background Tool Execution with context.background.adopt()
Tools can now acknowledge immediately while handing a long-running native background operation to Mastra via context.background.adopt({ completion, cancel }), so execution can track completion/cancellation without keeping execute() pending. This enables cleaner async/background tool patterns (with the caveat that adopted handles are in-memory and don’t resume after restart).
Connected SaaS Tools at Scale via @mastra/connect (New Generated Providers)
@mastra/connect@0.3.0 adds ten generated tool providers (Slack, GitHub, Google Mail/Calendar, Fireflies, PostHog, Stripe, Discord, Twitter/X, HubSpot) built from Nango templates, including new agent-focused actions. With Mastra Platform connections, tools: connect() resolves the right tools per request with no extra configuration.
More Reliable Durable Runs on Inngest (Configurable Retries + Better Resume)
Inngest workflows and durable agents now accept a retries option, allowing runs to survive process restarts/redeploys by retrying the function and continuing past completed steps. Durable agent resume methods (e.g., resumeStream(), tool approval routes) now properly resume suspended Inngest runs instead of failing with missing snapshots in key scenarios (like editor overrides).
Breaking Changes
@mastra/playground-ui: removed@mastra/playground-ui/lib/springs;FluidHoverHighlightAPI changed to accept onlyhoverandclassName.@mastra/playground-ui:PageLayout/shell APIs reworked (removedPageLayoutRoot,MainContentLayout,MainContentContent, and multipleAppShellheader-related props/contexts); migrate to the newPageLayoutwithbreadcrumbs,headerActions, andactionRow.- Trace grouping deprecations (Core/Server/Client): grouped results in
queryTraces()/ trace-querygroupare deprecated; migrate to thread query (queryTraceThreads()/queryThreadscontract).
Changelog
@mastra/core@1.69.0
Minor Changes
-
Added classifier registration on
Mastra. (#24738)Classifiers can now be passed to
new Mastra({ classifiers })and accessed withgetClassifier,getClassifierById,listClassifiers,addClassifier, andremoveClassifier. Registered classifiers evaluated without an active trace start a rootCLASSIFIER_EVALUATIONspan through theMastrainstance's configured observability provider.const mastra = new Mastra({ classifiers: { router } }); const classifier = mastra.getClassifier('router');
-
Added configured classifiers as typed workflow steps with fluent and dynamic graph support. Classifier steps expose complete typed answers and token usage for existing branch and conditional control flow. (#24747)
workflow .map({ message: { initData: true, path: 'message' } }) .classifier(router) .branch([ [async ({ inputData }) => inputData.answers.route.choice === 'billing', billingStep], [async ({ inputData }) => inputData.answers.route.choice === 'support', supportStep], ]);
-
Deprecated the
groupoption on the trace-query request contract. Grouping remains functional until the next major release; use thequeryThreadsthread-query contract for new code (exposed asqueryTraceThreads()in@mastra/client-js). (#23790)Before:
import type { TraceQueryRequest } from '@mastra/core/storage'; const request: TraceQueryRequest = { timeRange, group: { by: ['threadId'] } };
After:
import type { QueryThreadsInput } from '@mastra/core/storage'; const input: QueryThreadsInput = { traces: { timeRange } };
-
Added
context.background.adopt()so tools can return an acknowledgement while native background tasks track their existing operation through completion and cancellation. (#24418)Previously, background tools had to keep
execute()pending:execute: async (input, context) => { const operation = startResearch(input, context.abortSignal); return await operation.finished; };
Tools can now adopt their operation during native background execution:
execute: async (input, context) => { const operation = startResearch(input, context.abortSignal); if (context.background) { context.background.adopt({ completion: operation.finished, cancel: reason => operation.cancel(reason), }); return { answer: 'Research started' }; } return await operation.finished; };
startResearchrepresents the tool's operation API. Itsfinishedpromise must resolve with the final tool result after cleanup, or reject on failure. Adopt once, beforeexecute()returns. The handle stays in memory and cannot resume after a process restart. -
Added
rootSpanNametotracingOptionsso each agent or workflow run can set its own root span name. Runs of the same workflow no longer all show up asworkflow run: 'my-workflow'in trace lists. (#24564)await run.start({ inputData: { skillId: 'typescript' }, tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, });
The name applies to the root span only. Child spans keep their default names, and entity filters still match on the workflow or agent id. Related: #24518
Also in: @mastra/observability@1.18.0, @mastra/server@1.69.0
-
Added
ClassifierProcessorfor applying typed classifier policies to agent input, output, and streaming content. (#24768)import { Agent } from '@mastra/core/agent'; import { Classifier } from '@mastra/core/classifier'; import { ClassifierProcessor } from '@mastra/core/processors'; const safety = new Classifier({ id: 'safety', model, questions: { unsafe: { type: 'boolean', criteria: { true: 'Unsafe', false: 'Safe' } }, }, }); const agent = new Agent({ id: 'support-agent', name: 'Support agent', instructions: 'Answer support questions.', model: 'openai/gpt-5-mini', inputProcessors: [ new ClassifierProcessor({ classifier: safety, onResult: (answers, { abort }) => { if (answers.unsafe.probability > 0.8) abort('Rejected by safety policy'); }, }), ], });
-
Added a typed Classifier primitive for fixed-option evaluation with AI SDK evaluation models. (#24458)
import { Classifier } from '@mastra/core/classifier'; const classifier = new Classifier({ id: 'router', model }); const result = await classifier.evaluate({ state: request, questions: { route: { type: 'choice', criteria: { support: 'Support request', sales: 'Sales request' }, }, }, });
-
Added
validateSkillContent()and exportedvalidateSkillMetadata()from@mastra/core/skills, so apps can validate aSKILL.mdbefore saving it using the same rules applied at load time. (#24766)import { validateSkillContent } from '@mastra/core/skills'; const result = validateSkillContent({ content: skillMarkdown, directoryName: 'my-skill' }); if (!result.valid) console.error(result.errors);
Patch Changes
-
Update provider registry and model documentation with latest models and providers (
7fefefd) -
Fixed deferred aborts for paused tool calls and tool approvals. They no longer stop a new run started after switching threads or running
/new. (#24743) -
ClassifierProcessornow fails closed by default. When the classifier call fails, the request is aborted instead of letting unchecked content through. PasserrorStrategy: 'warn'to keep the previous fail-open behavior: (#24793)new ClassifierProcessor({ classifier, onResult, errorStrategy: 'warn' });
-
Honor
tracingOptions.rootSpanNamewhen creating a root span. The caller-supplied name replaces the defaultagent run: '<id>'orworkflow run: '<id>'name on the root span only. Works for the default and Inngest workflow engines because the name is applied when the span starts, before any durable snapshot is taken. (#24564)const span = observability.startSpan({ type: SpanType.WORKFLOW_RUN, name: "workflow run: 'skill-analyze'", tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); span.name; // "skill-analyze: typescript"
Related: #24518
Also in: @mastra/observability@1.18.0, @mastra/server@1.69.0
-
Tool calls whose approval policy is a function now run in parallel when that policy returns
falsefor the actual call. This applies to the defaulttoolCallConcurrencystrategy and tostrategy: 'called'. Previously, any function policy forced sequential execution. This included the policy thatMCPClientattaches whenrequireToolApprovalis a function. Each call's policy is evaluated once, and that verdict is reused when the tool runs. (#24763) -
Fixed a crash where a background process started with the
execute_commandworkspace tool could terminate the host process. If the exit callback threw or the process could not be observed after the PID was returned, the failure escaped as an unhandled promise rejection. These failures are now caught and logged instead. (#24638) -
Fixed internal spans being exported after they are rebuilt with
rebuildSpan()whenincludeInternalSpansis disabled. Spans created withtracingPolicy.internalnow keep their internal status throughexportSpan()andrebuildSpan(), so they stay out of exporters by default and remain included whenincludeInternalSpansis enabled. (#24742)Also in: @mastra/observability@1.18.0
-
Fixed
JSON.stringify(requestContext)throwingTypeError: Do not know how to serialize a BigIntwhen a BigInt was stored directly inRequestContext. Such values are now left out oftoJSON()output, matching how nested BigInts were already handled. BigInts are still included whenBigInt.prototype.toJSONreturns a JSON-serializable value, andrequestContext.get()still returns the original value. (#24751) -
Fixed
runEvalssavingmastra__authTokenand values that cannot be stored in score rows. Saved scores keep string, number and boolean request context values, and nested object values use dotted keys. (#24753) -
Added
rootSpanNameto the generatedtracingOptionsrequest types so per-run root span names can be sent from the client. (#24564)const run = await client.getWorkflow('skillAnalyze').createRun(); await run.startAsync({ inputData: { skillId: 'typescript' }, tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, });
Related: #24518
Also in: @mastra/client-js@1.48.0, @mastra/observability@1.18.0, @mastra/server@1.69.0
-
Fixed chat channel messages (Slack, Discord, etc.) silently disappearing when the agent failed during setup. If workspace, instructions, tools, or model resolution throws before the run starts, the error is now posted back to the chat thread instead of being dropped with no reply. (#24300)
-
Fix an event-loop hang when a run starts immediately after a persisted idle signal. The synthetic run that rebroadcasts the signal resolved its completion promise before its deferred cleanup ran, so a same-thread run waiting on it re-awaited an already-resolved promise in an unbounded microtask loop — pinning a CPU core and stopping timers and HTTP process-wide. The waiter now yields to the timer queue when the same run is still active after its completion promise settles. (#24613)
-
Accept
rootSpanNameintracingOptionson agent and workflow HTTP routes so clients can set a per-run root span name. (#24564)POST /api/workflows/skillAnalyze/start-async { "inputData": { "skillId": "typescript" }, "tracingOptions": { "rootSpanName": "skill-analyze: typescript" } }
Related: #24518
Also in: @mastra/observability@1.18.0, @mastra/server@1.69.0
@mastra/auth-cloud@1.2.6
Patch Changes
- Improved Cloud authentication portability by generating PKCE verifier and state values with Web Crypto while preserving synchronous login APIs. (#24583)
@mastra/auth-google@0.1.4
Patch Changes
- Improved Google authentication portability by signing service account tokens with Web Crypto. (#24583)
@mastra/auth-studio@1.3.7
Patch Changes
- Improved Studio authentication portability by hashing credential cache keys with Web Crypto. (#24583)
@mastra/clickhouse@1.20.1
Patch Changes
- Fixed ClickHouse delta polling for scores returning the same score again after a retried or duplicate insert. Delta reads now return each score once, in the first poll after it was written, and always with its latest values. (#24607)
@mastra/client-js@1.48.0
Minor Changes
-
Added
transportsto MCP server info responses so consumers can tell which endpoints a server serves. MCP v2 servers report['streamable-http']only; MCP 1.x servers also report'sse'. Servers that predate the field omit it, so treat absence as 1.x. (#24388)import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); const { servers } = await client.getMcpServers(); for (const server of servers) { const hasSse = server.transports?.includes('sse') ?? true; const path = hasSse ? 'sse' : 'mcp'; console.log(`${server.name}: http://localhost:4111/api/mcp/${server.id}/${path}`); }
-
Deprecated grouped results from
queryTraces(). Existing grouped queries continue to work until the next major release; usequeryTraceThreads()for new code. (#23790)Before:
await mastraClient.queryTraces({ timeRange, group: { by: ['threadId'] } });
After:
await mastraClient.queryTraceThreads({ traces: { timeRange } });
Patch Changes
-
Added client support for classifier workflow steps and their rendered workflow graph entries. (#24747)
Also in: @mastra/playground-ui@57.0.0, @mastra/react@1.6.1
@mastra/code-sdk@1.8.1
Patch Changes
-
Added configured classifier discovery and typed routing paths to workflow authoring tools. (#24747)
-
Missing provider credentials for signed-in Factory accounts now throw
ProviderAuthRequiredErrorinstead of a plainError, so hosts can classify the failure as an authentication error without matching on the message text. (#24737) -
Fixed hooks running twice for sessions started in your home directory. There, the project hooks file (
~/.mastracode/hooks.json) is the same file as the global one, and it was loaded as both. It is now loaded once, including when the project directory is a symlink to your home directory. (#24744) -
Fixed plugin tools so background execution is enabled only when the tool declares support;
mastra_expertis no longer opted in by name. Removed global plugin-call serialization, allowing independently declared tools to run concurrently. (#24418)
@mastra/codemod@1.1.4
Patch Changes
-
Fixed the agent property codemod to migrate tools access to
listTools(). (#24785) -
Fixed codemod runs reporting success when a transform crashed. Every jscodeshift transformation error (not just syntax errors) is now reported against the correct file, and both individual codemod runs and
v1exit with a non-zero code when any file fails to transform. (#24783)
@mastra/connect@0.3.0
Minor Changes
-
Added ten generated tool providers to @mastra/connect: Slack, GitHub, Google Mail, Google Calendar, Fireflies, PostHog, Stripe, Discord, Twitter/X, and HubSpot. Each ships checked-in tools generated from Nango integration templates, including new agent-focused actions (PostHog HogQL queries, Stripe balance/dispute/coupon/account reads, GitHub tags and trees, Slack Connect shared-channel invites, Twitter search and following lookups, HubSpot form submission). Attach a provider connection in Mastra Platform and the tools resolve through connect() with no extra configuration: (#24606)
import { Agent } from '@mastra/core/agent'; import { connect } from '@mastra/connect'; const agent = new Agent({ id: 'ops-agent', model: 'anthropic/claude-sonnet-4-6', tools: connect(), });
Patch Changes
-
Generated
@mastra/connectproviders now cover more of the upstream template catalog. Actions that authenticate with the raw connection credential — token-introspection endpoints, for example — are generated instead of skipped, and actions that validate their input with the template validation helper are generated too. The credential is fetched from the platform only for the specific actions that read it. Under the hood this extends the platform proxy runtime and the provider generator; agents consume the resulting tools through the normal provider workflow with no API changes: (#24605)import { Agent } from '@mastra/core/agent'; import { connect } from '@mastra/connect'; const assistant = new Agent({ id: 'assistant', name: 'Assistant', instructions: 'Help with connected services.', model: 'anthropic/claude-sonnet-4-6', tools: connect(), // tools for every connected provider, resolved per request });
@mastra/deployer@1.69.0
Minor Changes
-
Extended pnpm patch preservation to yarn (Berry) and bun, so
mastra buildkeeps patches applied in the bundled output regardless of package manager. (#21716)Yarn needs no configuration:
.yarn/patches/is copied into the output and Yarn applies the patches its lockfile already references.Bun declares patches in
package.json, andmastra buildrewrites them to output-relative paths in the bundledpackage.json:{ "patchedDependencies": { "lodash@4.17.21": "patches/lodash.patch" } }
Patch Changes
-
Fixed
mastra builddiscarding pnpm patches. Patched dependencies declared in your workspace are now carried into the built output, so deployed apps run the same patched code as your source project. (#21716) -
Fixed compatibility by requiring @mastra/core 1.58.0 or newer. These packages all build on @mastra/server, which needs core 1.58.0, but they still advertised support for core versions as old as 1.50.0. Installing one of those older pairings produced a broken setup instead of a clear version conflict. (#24715)
If your package manager reports a peer conflict after this release, upgrade @mastra/core to 1.58.0 or newer.
Also in: @mastra/deployer-cloud@1.69.0, @mastra/deployer-cloudflare@1.2.28, @mastra/deployer-netlify@1.2.28, @mastra/deployer-sandbox@0.3.13, @mastra/deployer-vercel@1.2.28, @mastra/elysia@0.1.9, @mastra/express@1.5.13, @mastra/fastify@1.5.13, @mastra/hono@1.7.11, @mastra/koa@1.7.13, @mastra/nestjs@0.2.28, @mastra/next@0.2.27, @mastra/tanstack-start@0.2.27, @mastra/temporal@0.4.7
@mastra/factory@0.17.0
Minor Changes
-
Added
skipRulestoupsertLinkedWorkItemdecisions so a rule can file a card directly on a stage it names, with none of the board's phase rules run for it. (#24649)External records that arrive already past a board's first step no longer have to pass through it. Set
skipRules: trueon the decision to place the card onstageas its first entry: no arrival rule, no destination-entry rule, no transition row, and nothing started for the card.issueOpened: context => { const decision = defaultGithubRules.issueOpened(context); if (!decision) return decision; // Triage already happened upstream: file it on Planning and leave it there. return { ...decision, stage: 'planning', skipRules: true }; };
The same decision on an existing card relocates it — used by the GitHub issue reconciliation sweep, which now replays an open issue through the rules ingress when its labels changed, so label-derived placement is re-applied even when the
labeledwebhook never arrived.moveCardToBoardnow takes an optionaltargetStage, so a placement can name a phase on the card's own board (Work › Intake to Work › Planning), not just a board's landing phase. Relocation guards still apply: terminal cards, cards with a session on their current phase's role, and cards that changed under the dispatcher stay put.A pull request opening is now evaluated once per card it concerns, like a merge: its own Review card is filed by the arrival (the evaluation flagged
pullRequestIntake) and the Work item that authored the pull request is answered in a second evaluation, so a rule can place the item that is now out for review. The built-inpullRequestOpenedfiles the card only on the arrival. -
Added a
platform.githubconfig key toMastraFactoryfor overriding the GitHub integration the factory installs itself. (#24665)When Platform credentials are present,
MastraFactoryinstalls aPlatformGithubIntegrationautomatically. You can now change its event handlers and app slug from the factory config, instead of constructing the integration and re-declaring the auto-install guard yourself:// Before import { PlatformGithubIntegration } from '@mastra/factory/integrations/platform/github/integration'; new MastraFactory({ storage, integrations: [new PlatformGithubIntegration({ rules: { issueOpened }, slug: 'factory-app' })], }); // After new MastraFactory({ storage, platform: { github: { rules: { issueOpened }, slug: 'factory-app' } }, });
For example, replacing
issueOpenedlets a newly created GitHub-issue work item land on the board its existing labels select instead of defaulting to Work.slugfalls back toplatform.githubAppSlugwhen omitted.An explicit integration with id
githubinintegrationsstill takes precedence, which makesplatform.githuba no-op; the factory logs a warning rather than ignoring it silently, and warns the same way when no Platform credentials and no explicit GitHub integration are present.
Patch Changes
-
Fixed Slack typing statuses to avoid repeated status updates during streamed responses. (#24711)
-
Fixed automated skill invocations failing when consecutive agent runs end before accepting queued work. (#24750)
-
Fixed GitHub sessions not being subscribed to pull requests they opened through the shared
source_control_create_change_requesttool. Comments and closes on those pull requests now reach the session, and the transcript shows the pull request link. (#24571) -
Fixed the transcript of a GitHub user session never showing its pull request link. Subscriptions a user session created are now found when the session asks for them by its own id. (#24572)
-
Platform GitLab discovery now considers only connections on the Platform's
gitlab(OAuth) integration. Thegitlab-group,gitlab-group-tokenandgitlab-patintegration ids are no longer queried or accepted, so connections created through those flows are not discovered. (#24686) -
Fixed review sessions that resumed after a Factory restart reading repository instruction files from the untrusted pull request checkout before their security state was restored. (#24576)
-
Fixed the session workspace Changes panel and file diffs going empty once a build session committed its work. Changes are now compared against the branch the session started from, so committed work stays visible. (#24574)
-
Fixed diff-comment tool calls failing on OpenAI models. Creating or replying to a line-anchored review comment with
source_control_create_diff_commentno longer produces an invalid function schema that the model API rejects; both modes are validated from one object input. (#24661)
@mastra/inngest@1.9.1
Patch Changes
-
Fixed InngestAgent losing durable execution in two cases (#24736). (#24758)
- Editor overrides:
__fork()now returns an Inngest-backed agent, so agents with published editor overrides keep running on Inngest instead of silently running in-process. - Resuming suspended runs:
resumeStream(),approveToolCall(),declineToolCall(),approveToolCallGenerate()anddeclineToolCallGenerate()now resume the suspended Inngest run. Previously they threwAGENT_RESUME_NO_SNAPSHOT_FOUND, which brokechatRoutetool approval.
- Editor overrides:
-
Inngest workflows and durable agents now accept a
retriesoption, so a run can survive a process restart or redeploy. Before this change, every Inngest function was created withretries: 0and there was no way to change it. If a call to the application failed (for example, the process restarted mid-run), the whole run failed straight away. (#24741)retriesis passed to Inngest as its function-level retry count. When set, Inngest calls the function again after a failed request, skips the steps that already finished, and continues the run. Errors thrown by your own step code are still retried per step throughretryConfigorstep.retries, and are never retried again at the function level. The default is still0.const workflow = createWorkflow({ id: 'my-workflow', inputSchema, outputSchema, retries: 3, }); const durableAgent = createInngestAgent({ agent, inngest, retries: 3 });
@mastra/koa@1.7.13
Patch Changes
- Decode route parameters once before passing them to handlers. Percent-encoded skill names and reference paths now resolve consistently with other server adapters. (#24637)
@mastra/playground-ui@57.0.0
Minor Changes
-
Aligned the Ask User card with the other AI surfaces in the chat stream. It now sits on a raised card with the same radius and a labelled header, uses design-system typography, and — most visibly — renders option pickers with the design-system
CheckboxandRadioGroupinstead of native browser controls, so selecting an option animates and matches every other control in Studio. (#24677)Fixed along the way: option descriptions used the 10px
metarole (reserved for badges and micro labels) and now usecaption; the card was hand-built from the frame fill plus a manual border instead of the card primitive, so it read as a recessed panel rather than a card.Removed exports
AskUserOptionControlandAskUserOptionDescriptionare gone — they wrapped the native<input>that no longer exists. Compose a row withAskUserOptionRowinstead, passing the control you want:// Before <AskUserOptionControl type="radio" label="Staging" description="Validate first." /> // After <AskUserOptionRow label="Staging" description="Validate first." control={<RadioGroupItem value="Staging" />} />
AskUserQuestionis now typed againstTxtrather than<legend>, so it takesvariant,tone, andas. -
Added a Linear-style Advanced filter to
FilterBar, so filters can be combined withand/orinstead of only being ANDed together. (#24654)Pass a
FilterBarExpressionasvalueto opt in. Top-level chips stay flat and implicitly ANDed; each top-level group renders as a single Advanced filter · N chip that opens a popover with a recursive rule builder: one editable chip per condition laid out aswhere/and/orrows, nested groups as cards with their ownand|orswitch, and+ Condition/+ Group/Clear allfooters. From the bar input, pick Advanced filter… at the end of the field list to create a group and start adding conditions into it. Empty groups are pruned when the popover closes. Nesting depth is bounded by the newmaxDepthprop (default3).A flat
FilterBarItem[]value keeps working unchanged and never shows the option.import { FilterBar, type FilterBarExpression } from '@mastra/playground-ui'; const [value, setValue] = useState<FilterBarExpression>({ logic: 'and', nodes: [ { id: '1', fieldId: 'status', operatorId: 'is', value: 'error' }, { id: 'g1', kind: 'group', logic: 'or', nodes: [ { id: '2', fieldId: 'env', operatorId: 'is', value: 'prod' }, { id: '3', fieldId: 'env', operatorId: 'is', value: 'staging' }, ], }, ], }); <FilterBar fields={fields} operators={operators} value={value} onValueChange={setValue}> <FilterBar.Chips /> <FilterBar.Input /> </FilterBar>;
-
Redesigned metrics KPI cards: the value now sits next to a colored change badge ("+15.3% vs prior period"), and cost changes treat a decrease as good. Added
MetricsCardGroup, a frame that lays out KPI or chart cards in rows that flex and wrap to fill the width, with aninsetvariant that sets the cards into a thick raised border likeDataList. (#24716) -
Fixed menus painting two hover backgrounds at once. DropdownMenu, ContextMenu, Select and Combobox rows no longer paint their own hover background under the moving highlight, destructive items tint that highlight instead of stacking a second one, and an open submenu keeps its parent row lit on the same surface. Moving the pointer or clicking inside a submenu no longer moves the parent menu's highlight or activates the parent row under it. PropertyFilter lists now use the same moving highlight, which also follows keyboard focus, and virtualized DataList rows no longer make the highlight blink while scrolling. (#24712)
The moving highlight now runs on CSS transitions instead of
framer-motion, which is no longer a dependency of@mastra/playground-ui. It fades in and travels as before, and appears instantly without the fade-out when the pointer leaves.Breaking: the
@mastra/playground-ui/lib/springsentry point is removed, andFluidHoverHighlightnow takes onlyhoverandclassName. Pass theuseFluidHoverreturn value ashover:const hover = useFluidHover(containerRef); <FluidHoverHighlight hover={hover} className="rounded-lg" />;
-
Added a
sizeprop toButtonsGroup, and the group now owns the control rung of every segment it holds. (#24696)Why
A
ButtonsGroupimposed no height of its own. Each segment brought its own off the control ladder (sm28px,md30px,lg32px), so two segments on different rungs rendered a step in the joined pill — and nothing stopped that from happening. It was easy to hit by accident, becauseCopyButtondefaults tosmwhileButton,Input,SelectTrigger,ComboboxandInputGroupall default tomd.What changed
The rung is declared once, on the group, and a child's own
sizecan no longer lift a segment off it. Height, the width of an icon-mode circle, and the glyph size all follow the group.// before — the rung repeated on every segment, and nothing checked they agreed <ButtonsGroup> <CopyButton content={value} size="sm" /> <Button size="sm" aria-label="Expand"> <ExpandIcon /> </Button> </ButtonsGroup> // after — one declaration, and a step in the pill is no longer expressible <ButtonsGroup size="sm"> <CopyButton content={value} /> <Button aria-label="Expand"> <ExpandIcon /> </Button> </ButtonsGroup>
sizedefaults tomd. A group whose segments were allsmneedssize="sm"on the group — without it those segments now render atmd.size="icon-sm" | "icon-md" | "icon-lg"stays on aButton: onButtontheicon-*sizes also select the square shape, and only their rung is overridden.InputGroupinside a groupA field keys its type scale off its own
data-size(text-caption/text-body-sm/text-body), which the group's stylesheet cannot reach — forcing only the box left asmgroup reading atmd.ButtonsGroupnow publishes its rung on the newControlSizeContext(exported fromds/primitives/control-size) andInputGrouptakes it over its ownsize, the same rule as every other segment: inside a group, an explicitsizeon the field is inert. Outside one,sizebehaves exactly as before.A field's addon glyph is still a flat
size-4at every rung. That isInputGroup's own behaviour, in or out of a group, and changing it moves every field in the app — left as a follow-up.Removed
ButtonsGroupTextno longer takes asizeprop. A text segment only exists inside a group, and the group sets its height.ButtonsGroupSeparatoris gone. A group joins its segments with a seam — one shared border, halved between neighbours — so an extra rule between them drew a second line on top of that seam. It had no call site outside its own story. A group that genuinely needs to separate two clusters should render its own divider, or be two groups.One material for a neutral filled control
A group put the mismatch in plain sight: with every segment finally the same height, four of them still had four different fills. A field is the raised card material (
bg-card+shadow-raised, white in light), aButtondefaultwas the translucent 6%--fillrung with a 1px border, andButtonsGroupTextwas--surface-panel, the opaque twin of that rung.Button'sdefaultvariant now wears the same raised material as a field, and so does the text segment. In light a default button is white on the off-white canvas, like the input and the select trigger beside it; in dark the two were already within a few levels of each other, so little moves. The material has no border of its own — its edge is the rimshadow-raiseddraws, and focus repaints that rim.States follow the material rather than the fill: hover and press wash through
--surface-tint(fill-subtle, thenfill) instead of swapping the background, because a pinned card fill cannot be swapped without going translucent.Removed with it:
fieldTriggerSurfaceStyle, which existed only to undo the Button's fill on a Select/Combobox trigger, and thefieldkey ofcontrolTriggerOpenState—defaultnow is the field's open state.raisedControlSurfaceStyle(exported fromds/primitives/form-element) is the one definition.Hover and focus inside a group
A segment's leading edge belongs to its neighbour, so hovering an
outlinesegment lit only three of its sides, and keyboard focus showed no edge at all — the group's seam colour overrode the segment's focus border. The focused segment now takes the focus colour on every side, and the neighbour that owns the shared seam takes the hover or focus colour with it. -
Pages now own their header.
PageLayoutacceptsbreadcrumbsandactionsprops and renders the header row (breadcrumbs left, actions right) above a plain scrollable<main>. (#24643)Breaking:
PageLayoutno longer takeswidth,heightorheading; applymax-w-*/ grid classes viaclassNameinstead.PageLayoutRootis gone — importPageLayoutdirectly.MainContentLayoutandMainContentContentare removed; usePageLayout.AppShelldropsrouteHeader,renderFrame,mainLabelandAppShellFrameProps, along withPageHeadingContext/usePageHeading.AppShellonly lays outsidebar,mobileHeaderandchildren; the framed card styling moved to the consumer.
-
FilterBar: a field that takes plain typed text (#24659)
A field marked
searchstays pinned at the top of the field list whatever is typed, so text that names no field still commits as a filter instead of forcing a field pick first. Options also take astartnode rendered before their label, for an avatar or an icon.<FilterBar fields={[ { id: 'text', label: 'Text', search: true, operators: ['contains'] }, { id: 'teammate', label: 'Teammate', operators: ['is'], suggestions: [{ value: 'github:alice', label: 'Alice', start: <Avatar name="Alice" /> }], }, ]} operators={DEFAULT_FILTER_OPERATORS} value={items} onValueChange={setItems} > <FilterBar.Chips /> <FilterBar.Input /> </FilterBar>
Typing
flaky loginand pressing Enter now commitsText contains flaky login;Teammateis one arrow below.Fixed: the option list opens on its first row again after a field is picked or a chip is committed with the keyboard. It used to keep the highlight index of the list it replaced, so a field reached with ArrowDown opened the value list on its second option.
Patch Changes
-
Add
ActionRow(ActionRow.Start,ActionRow.End) — a toolbar line that pushes a start group and an end group apart and wraps on narrow viewports.PageLayout'sactionRowslot now stacks multiple rows with a consistent gap. (#24643) -
AppShellnow insets its body (p-1.5 lg:p-2, dropping the left inset atlgwhen asidebaris passed) so the frame rendered inside it no longer needs its own margins. Consumers that putm-*classes on their own frame should remove them.PageShellbody padding is nowp-4(wasp-4 px-6) to match the rest of the page layouts. (#24643) -
AppShellaccepts asidebarslot and owns the sidebar/content grid, so consumers no longer wrap the sidebar and shell in a hand-rolled grid. (#24643) -
Add
DisabledFeatureButton, an icon-only disabled control with a keyboard-reachable tooltip and docs link for features that are not yet available. The focusable trigger carries the accessible name and disabled state. (#24777) -
EmptyStatenow renders a defaultCircleSlashIconwheniconSlotis omitted;iconSlotis optional (passnullfor no icon). All playground callsites drop their expliciticonSlot. (#24643) -
Add a
variant="fill"option toEmptyStatethat centers the block in the full height of its parent, replacing theflex h-full items-center justify-centerwrapper every empty-state call site used to hand-roll. (#24643) -
Increased SidebarNew section header spacing to make navigation sections easier to distinguish. Hide the navigation scrollbar when idle and show it on hover, focus, or scrolling. (#24713)
-
PageLayoutis now a minimal shell:breadcrumbs,headerActions(renamed fromactions), a newactionRowslot pinned above the scrolling body, andchildren. The<main>body carriesp-4by default and no longer acceptsclassName. ThePageLayout.TopArea/MainArea/Row/Columnslots,NoDataPageLayout, andPageShellare removed — pass toolbars viaactionRowand compose the body with plain elements. (#24643) -
Improved DataList status examples and removed the standalone Badge indicator story. (#24705)
-
Page status states now own their centering.
SessionExpired,PermissionDeniedandErrorStateacceptvariant="fill", andSpinneracceptsfillplus a newsize="lg", so call sites no longer hand-roll aflex h-full items-center justify-centerwrapper around them.ErrorStateis rebuilt onEmptyStateand drops its fixedh-[30vh]height, which used to push the block above center inside a full-height parent. (#24643) -
Fixed the
SideDialogcode section header, where the copy button and the multiline toggle rendered at two different heights (28px next to 30px) inside their joinedButtonsGroup, so one segment poked out of the pill.CopyButtondefaults tosmwhileButtondefaults tomd, and this header passed neither — both are nowsm, matching the same header inDataDetailsPanelandDataCodeSection. The multiline toggle is icon-only and now carries an accessible name. (#24691)A
ButtonsGroupimposes no height of its own: every segment must sit on the same rung of the control size ladder (sm28px,md30px,lg32px), or it will poke out. -
Traces filter bar (on
/tracesand agent traces) now supports advanced AND/OR filter groups. Groups are persisted in the URL asfilterGroupparams, restored from saved filters, and sent toqueryTracesas nestedor/andpredicates.FilterBar'screateItemIdnow only applies to root-level items so a group can hold several conditions on the same field. (#24675)
@mastra/quickjs@0.1.2
Patch Changes
- Fix the CommonJS build of
@mastra/quickjsso programs run. Previously everyrun()failed with(0, ts_blank_space.default) is not a functionbecause the CJS bundle externalised the ESM-onlyts-blank-space. The build now splits per format: ESM keepsts-blank-spaceandtypescriptexternal, while CJS bundles them in. (#24722)
@mastra/s3vectors@1.1.3
Patch Changes
-
Fixed
deleteVectors()throwing "not yet implemented" when called withids. You can now bulk delete vectors by id; large lists are split into batches automatically. Deleting byfilteris still not supported and throws a clear error suggesting to delete byidsinstead. (#24752)await vectorStore.deleteVectors({ indexName: 'docs', ids: ['doc-1', 'doc-2'] });
@mastra/server@1.69.0
Minor Changes
-
Deprecated thread grouping on the advanced trace-query endpoint. Grouped requests remain supported until the next major release; use the thread-query endpoint for new integrations. (#23790)
Before:
await mastraClient.queryTraces({ timeRange, group: { by: ['threadId'] } });
After:
await mastraClient.queryTraceThreads({ traces: { timeRange } });
Patch Changes
-
Added server schema support for serialized classifier workflow steps. (#24747)
-
Preserve literal percent-encoded sequences in workspace filesystem paths. Read, write, create, and delete operations now target the exact requested file instead of decoding query and body values a second time. Closes #24620. (#24637)
-
MCP server listings and details now report which transports a server offers (
streamable-http, plusssefor MCP 1.x servers), so clients can tell MCP v2 servers apart without probing routes. Thetransportsfield is optional on the response types so clients keep working against older servers that do not send it. The MCP tool info response type also declares the optionalidthat MCP v2 servers include. (#24388)curl http://localhost:4111/api/mcp/v0/servers # { "servers": [{ "id": "notes", "name": "notes", "version_detail": { ... }, "transports": ["streamable-http"] }], ... } -
Fixed @mastra/server compatibility by requiring @mastra/core 1.58.0 or newer. Older versions of @mastra/core are missing functionality that @mastra/server depends on, so installing them together resulted in a broken setup rather than a clear version conflict. (#24692)
@mastra/weaviate@0.1.1
Patch Changes
-
Raise the
@mastra/corepeer dependency floor to1.68.0. Every published version of this store has shipped alongside core 1.68, but the previous>=1.0.0-0range advertised compatibility with 68 earlier core releases that were never tested, so a package manager installed those pairings without a peer warning. (#24676)If your package manager reports a
@mastra/weaviatepeer conflict, upgrade@mastra/coreto1.68.0or newer.
Other updated packages
The following packages were updated with dependency changes only:
- @mastra/arize@1.3.18
- @mastra/arthur@0.4.18
- @mastra/braintrust@1.3.15
- @mastra/datadog@1.4.10
- @mastra/deepeval@0.1.12
- @mastra/laminar@1.3.20
- @mastra/langfuse@1.5.9
- @mastra/langsmith@1.3.20
- @mastra/longmemeval@1.1.28
- @mastra/mcp-docs-server@1.2.28
- @mastra/opencode@0.1.28
- @mastra/otel-bridge@1.5.10
- @mastra/otel-exporter@1.4.1
- @mastra/posthog@1.3.12
- @mastra/sentry@1.2.20