MastraCode web chat: render via standard Mastra message parts#18620
MastraCode web chat: render via standard Mastra message parts#18620mfrachet wants to merge 8 commits into
Conversation
Route the web transcript through @mastra/react MessageFactory using a controller-message accumulator that converts AgentControllerMessage payloads into MastraDBMessage parts. This replaces the bespoke segment/tool render path so message rendering uses the shared Mastra parts model. Visible chat behavior (role labels, bubbles, markdown, thinking, streaming cursor, inline tool cards, expand/collapse) is preserved, and harness/status metadata renders as status cards. Updates the colocated accumulator, transcript reducer, and MSW/e2e coverage to the new representation. Co-Authored-By: Mastra Code (anthropic/claude-opus-4-8) <noreply@mastra.ai>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
WalkthroughReplaces the transcript segments model with message entries backed by ChangesUnified message transcript model
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
🦋 Changeset detectedLatest commit: 1dd9c56 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Dependency limit exceeded — report not shown. This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report. Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard. Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account. |
…ccumulator-mastra-db
PR triageLinked issue check skipped for core contributor @mfrachet. PR complexity score
Applied label: Changed test gateChanged tests failed against the base branch as expected. Label: |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
mastracode/src/web/ui/transcript.ts (1)
521-530: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
hydrate()still drops the live non-message state.Lines 529-530 rebuild the reducer from
initialTranscript, so any existing notices, prompts, subagents, tasks, goal state, and OM state vanish wheneverhydrateruns on a populated session. That means reconnect hydration cannot preserve the non-message session state this reducer is supposed to carry forward.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/src/web/ui/transcript.ts` around lines 521 - 530, The hydrate() helper is resetting the transcript to initialTranscript, which drops existing non-message state during reconnect. Update hydrate() in transcript.ts to preserve the current TranscriptState fields for notices, prompts, subagents, tasks, goal, and OM-related state instead of rebuilding from initialTranscript, while still replacing entries and the supplied modeId/modelId/threadId/omProgress/usage values. Use the hydrate() function and TranscriptState shape to locate the state merge logic.
🧹 Nitpick comments (2)
mastracode/src/web/ui/transcript.test.ts (1)
87-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtend this case to the re-emitted
message_update.The test stops right before the transition that regressed here: once a real assistant
message_updatearrives aftertool_start/tool_end, the reducer should reuse the tool-only placeholder instead of leaving a second assistant entry behind. A follow-up assertion here would lock that behavior down.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/src/web/ui/transcript.test.ts` around lines 87 - 122, Extend the transcriptReducer test to cover the real assistant message_update after tool_start and tool_end: assert that the reducer reuses the existing tool-only placeholder and does not leave a second assistant entry. Use the existing messageParts helper and the tool lifecycle setup in transcript.test.ts so the new assertion targets the same transcriptReducer behavior and the re-emitted message_update path.mastracode/src/web/ui/agent-controller-message-accumulator.test.ts (1)
47-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a regression that
tool_resultdoes not populateharnessContent.The new metadata test only exercises status messages, so it would not catch assistant tool outputs being copied into
content.metadata.harnessContentand doubling persisted payload size. A small assistant-message case here would pin that contract down.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@mastracode/src/web/ui/agent-controller-message-accumulator.test.ts` around lines 47 - 64, The new status-message test in toMastraDBMessage only covers harness metadata for structured status content, so add a regression case in agent-controller-message-accumulator.test.ts for an assistant message containing tool_result content and verify that harnessContent is not set in content.metadata while the rendered parts still serialize normally. Use the existing toMastraDBMessage helper and AgentControllerMessage shape to locate the relevant conversion path and pin down that tool outputs are not copied into persisted harness metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/thirty-candies-invite.md:
- Line 5: The changeset entry is written as an internal implementation note
instead of a user-facing outcome; rewrite the summary in the changeset markdown
to describe the visible effect of the MastraCode web chat rendering update.
Focus on what users will notice in transcript/message display rather than
mentioning standard Mastra message parts or the refactor details.
In `@mastracode/e2e/web/streaming-text.scenario.test.ts`:
- Around line 22-23: The assertion in streaming-text.scenario.test.ts only fails
when last.streaming is truthy, so it can miss cases where message_end leaves the
flag undefined. Update the check around last.kind and last.streaming to
explicitly assert that the assistant entry from the message_end flow has
streaming === false, using the existing last variable in the test.
In `@mastracode/src/web/ui/__tests__/message-rendering.msw.test.tsx`:
- Around line 53-65: The SSE test helper in delayedSse currently leaves emit as
a no-op until ReadableStream.start runs, so the test can fire too early and miss
the update. Update the message-rendering MSW tests to wait for the stream to be
ready by using a real readiness signal from delayedSse before calling emit, and
apply the same fix anywhere the helper is reused in the message-rendering test
file so the SSE subscription is established before emitting events.
In `@mastracode/src/web/ui/agent-controller-message-accumulator.ts`:
- Around line 88-90: The isHarnessMetadataContent predicate in
agent-controller-message-accumulator.ts is incorrectly treating tool_result as
harness metadata, causing tool output to be duplicated in harnessContent. Update
that helper to explicitly exclude tool_result so only true metadata parts are
captured, while keeping text, thinking, and tool_call excluded as before. Verify
the AgentControllerMessageAccumulator path still feeds statusMetadata() the
notification/OM/error parts it expects from content.metadata.harnessContent.
In `@mastracode/src/web/ui/components.tsx`:
- Around line 294-310: The suspension payload parser in suspensionPayloadShape
is dropping the options field, so AskUserCard never receives prompt choices from
prompt.suspendPayload. Update this parser to read and վերադարձ the options
property alongside question, requestedPath, reason, title, and plan, using the
same safe property-access pattern already used in components.tsx so the payload
shape stays intact.
- Around line 627-637: Update toolFromInvocationPart so persisted failed tool
states are not treated as running: when toolInvocation.state is output-error or
output-denied, set ToolCall.status to error instead of running. Also carry any
available errorText from the ToolInvocationPart into the returned ToolCall so
failed tools render with their failure details, while preserving the current
runtime override behavior in toolFromInvocationPart.
In `@mastracode/src/web/ui/transcript.ts`:
- Around line 547-558: The assistant transcript logic is creating a separate
bubble for the real assistant message instead of merging it with the earlier
tool-only placeholder. Update upsertAssistant in transcript.ts to detect and
reuse the synthesized assistant-tools placeholder entry (not just entries with
the same message.id), so the later message_update replaces that entry and
preserves its runtimeTools. Use the existing toMessageEntry,
preserveRuntimeToolParts, and assistant-tools entry shape to reconcile the
placeholder into the first real assistant message.
---
Outside diff comments:
In `@mastracode/src/web/ui/transcript.ts`:
- Around line 521-530: The hydrate() helper is resetting the transcript to
initialTranscript, which drops existing non-message state during reconnect.
Update hydrate() in transcript.ts to preserve the current TranscriptState fields
for notices, prompts, subagents, tasks, goal, and OM-related state instead of
rebuilding from initialTranscript, while still replacing entries and the
supplied modeId/modelId/threadId/omProgress/usage values. Use the hydrate()
function and TranscriptState shape to locate the state merge logic.
---
Nitpick comments:
In `@mastracode/src/web/ui/agent-controller-message-accumulator.test.ts`:
- Around line 47-64: The new status-message test in toMastraDBMessage only
covers harness metadata for structured status content, so add a regression case
in agent-controller-message-accumulator.test.ts for an assistant message
containing tool_result content and verify that harnessContent is not set in
content.metadata while the rendered parts still serialize normally. Use the
existing toMastraDBMessage helper and AgentControllerMessage shape to locate the
relevant conversion path and pin down that tool outputs are not copied into
persisted harness metadata.
In `@mastracode/src/web/ui/transcript.test.ts`:
- Around line 87-122: Extend the transcriptReducer test to cover the real
assistant message_update after tool_start and tool_end: assert that the reducer
reuses the existing tool-only placeholder and does not leave a second assistant
entry. Use the existing messageParts helper and the tool lifecycle setup in
transcript.test.ts so the new assertion targets the same transcriptReducer
behavior and the re-emitted message_update path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 847ed42a-e6ab-41b4-a40e-97ae6c415477
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (16)
.changeset/thirty-candies-invite.mdmastracode/e2e/web/driver.tsmastracode/e2e/web/fixtures/plan-approval.jsonmastracode/e2e/web/notification.scenario.test.tsmastracode/e2e/web/sse-reconnect.scenario.test.tsmastracode/e2e/web/streaming-text.scenario.test.tsmastracode/e2e/web/transcript-hydrate.scenario.test.tsmastracode/package.jsonmastracode/src/web/ui/App.tsxmastracode/src/web/ui/SettingsPanel.tsxmastracode/src/web/ui/__tests__/message-rendering.msw.test.tsxmastracode/src/web/ui/agent-controller-message-accumulator.test.tsmastracode/src/web/ui/agent-controller-message-accumulator.tsmastracode/src/web/ui/components.tsxmastracode/src/web/ui/transcript.test.tsmastracode/src/web/ui/transcript.ts
…-ai/mastra into mastra-code-accumulator-mastra-db
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@mastracode/src/web/ui/components.tsx`:
- Line 317: The suspension payload mapping in the component that builds the
controller payload should preserve the target path instead of only reading
requestedPath. Update the payload extraction logic in the relevant
dispatcher/card builder so it also reads payload.path as a fallback (alongside
requestedPath) when constructing the object used by the access/plan prompt flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 89f3da0d-c65f-488a-8996-f929fe5535ee
📒 Files selected for processing (7)
.changeset/thirty-candies-invite.mdmastracode/e2e/web/streaming-text.scenario.test.tsmastracode/e2e/web/transcript-hydrate.scenario.test.tsmastracode/src/web/ui/__tests__/message-rendering.msw.test.tsxmastracode/src/web/ui/agent-controller-message-accumulator.tsmastracode/src/web/ui/components.tsxmastracode/src/web/ui/transcript.ts
✅ Files skipped from review due to trivial changes (1)
- .changeset/thirty-candies-invite.md
🚧 Files skipped from review as they are similar to previous changes (5)
- mastracode/e2e/web/streaming-text.scenario.test.ts
- mastracode/src/web/ui/agent-controller-message-accumulator.ts
- mastracode/src/web/ui/tests/message-rendering.msw.test.tsx
- mastracode/e2e/web/transcript-hydrate.scenario.test.ts
- mastracode/src/web/ui/transcript.ts
| return { | ||
| question: stringProperty(payload, 'question'), | ||
| options, | ||
| requestedPath: stringProperty(payload, 'requestedPath'), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Preserve path from suspension payloads.
Line 317 only reads requestedPath, but the suspension payload flow passes the controller payload through unchanged and the existing dispatcher contract uses payload.path for access/plan prompts. Add path as a fallback so those cards don’t lose the target path.
Proposed fix
- requestedPath: stringProperty(payload, 'requestedPath'),
+ requestedPath: stringProperty(payload, 'requestedPath') ?? stringProperty(payload, 'path'),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| requestedPath: stringProperty(payload, 'requestedPath'), | |
| requestedPath: stringProperty(payload, 'requestedPath') ?? stringProperty(payload, 'path'), |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@mastracode/src/web/ui/components.tsx` at line 317, The suspension payload
mapping in the component that builds the controller payload should preserve the
target path instead of only reading requestedPath. Update the payload extraction
logic in the relevant dispatcher/card builder so it also reads payload.path as a
fallback (alongside requestedPath) when constructing the object used by the
access/plan prompt flow.
Summary
MastraCode's web chat previously rendered messages through a bespoke segment/tool model (
AssistantSegment,ToolCall) that duplicated message-part rendering logic. This PR migrates the web transcript to render through@mastra/react'sMessageFactoryusing the shared Mastra message-parts model.A new controller-message accumulator converts
AgentControllerMessagepayloads from@mastra/client-jsinto standardMastraDBMessageobjects (content.format: 2,content.parts, andcontent.metadata.harnessContent). The transcript reducer upserts these messages on stream events and on hydration, while keeping all non-message session state (approvals, suspensions, notifications, tasks, goals, subagents, usage, OM phase, local notices) unchanged.Visible chat behavior is preserved: role labels (
You/Steer/Agent/System), bubble styles, assistant markdown, thinking blocks, the streaming cursor, inline tool cards with expand/collapse, and the "Expand all (n)" toggle all behave as before. Harness/status metadata renders as compact status cards rather than raw content.What changed
agent-controller-message-accumulator.ts(+ unit tests) converting controller messages/events toMastraDBMessageparts.transcript.ts) hydrates and upserts messages via the accumulator, overlaying runtime tool state for instant tool-card visibility.components.tsxrenders message parts viaMessageFactorywith role wrappers and per-part renderers (text, reasoning, tool-invocation, file) plus a status-metadata card path.mastracodepatch — "Improved MastraCode web chat message rendering to use standard Mastra message parts."Test plan
pnpm --filter ./mastracode check:ui(web UI typecheck) — passingpnpm --filter ./mastracode test— all unit/MSW suites passing (incl. accumulator, transcript reducer, message-rendering MSW)ELI5
This update makes MastraCode web chat understand and display its conversation history in a single, standard message format. That way, streaming updates, tool cards, and status info stay consistent whether the chat is just starting, hydrating from saved state, or reconnecting.
Summary
@mastra/reactMessageFactory, using the shared Mastra message-parts model (MastraDBMessagewithcontent.format: 2+content.parts) instead of bespoke “segments/tool” rendering.toMastraDBMessage) to convertAgentControllerMessagepayloads into standardMastraDBMessageobjects, including orderedtool-invocationparts and harness metadata viacontent.metadata.harnessContent.kind: "message"entries (MessageEntry) and upsert them during streaming/hydration, while preserving non-message session state (approvals, suspensions, notifications, tasks/goals/subagents, usage, OM phase, local notices).runtimeToolsand inserting/updating correspondingtool-invocationparts inline inside the assistant/user message, including placeholder/tool-only assistant behavior when tool events arrive first.MessageBubbleviaMessageFactory) to keep existing visible behavior the same (role labels/bubbles, markdown, thinking blocks, streaming cursor, expand/collapse tool cards, “Expand all (n)” toggle) with harness/status metadata rendered as compact status cards.