feat(dashboard): surface system prompt in turn transcript#469
Conversation
Prepend a synthetic system-role message to each turn's transcript at the reporting layer so the dashboard can show the exact prompt the model received. The message is assembled from buildSystemPrompt() at read time — no storage changes needed — and is intentionally skipped from transcriptMessageCount. In the UI, system messages render collapsed by default (same UX pattern as thinking blocks) with a char count in the summary, so the transcript stays scannable without the full prompt taking over the view. This is a forward-compatible placeholder: when the transcript source moves to traces the system message will arrive as a span attribute instead of a synthetic injection. Co-Authored-By: claude-opus-4-5 <noreply@anthropic.com> Co-authored-by: David Cramer <david@sentry.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-Authored-By: claude-opus-4-5 <noreply@anthropic.com> Co-authored-by: David Cramer <david@sentry.io>
…ntent Replace the plain HighlightedCode fallback with the full groupTranscriptParts → TranscriptPartView pipeline so system message content goes through TranscriptText → parseMarkdownBlocks → XML detection → StructuredMarkup. This gives the system prompt the same collapsible XML tag renderer used for user/tool messages that contain runtime context blocks. Also remove the redundant TranscriptMessageShell wrapper: transcriptMessageClass is now applied directly to the <details> element so the amber styling and the disclosure are a single DOM node instead of two nested containers. Co-Authored-By: claude-opus-4-5 <noreply@anthropic.com> Co-authored-by: U039RR91S <david@sentry.io>
- Use StructuredMarkup directly with language:"xml" so the system prompt renders through the collapsible XML tree renderer instead of HighlightedCode. detectLanguage returns "markdown" for the system prompt (starts with plain text, not "<"), so bypassing it is the right call here. - Replace rawText.length / "chars" with TextEncoder byte count + formatBytes, consistent with how the rest of the dashboard shows message sizes. Co-Authored-By: claude-opus-4-5 <noreply@anthropic.com> Co-authored-by: David Cramer <david@sentry.io>
detectLanguage was returning "markdown" for the system prompt because the markdown heuristic (## headings, - bullets) fired before the XML check, and the XML check required text to start with "<". The system prompt starts with plain text but contains block-level XML elements. Fix: add a block-level XML check before the markdown heuristic that requires a matched open+close tag pair on their own lines. This makes the system prompt naturally detected as "xml" without special-casing in the component layer. With the detection fixed, SystemMessageView can use the same groupTranscriptParts → TranscriptPartView → TranscriptText pipeline as every other message kind, removing the direct StructuredMarkup bypass. The StructuredMarkup import is also removed from TranscriptTurn.tsx. Tests added for the new detection behaviour: - mixed prose + block XML detected as xml (system prompt shape) - unclosed block tag not detected as xml - normal markdown without XML stays markdown Co-Authored-By: claude-opus-4-5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c05050a. Configure here.
| const transcript = canExposeTranscript ? normalizedTranscript : []; | ||
| const transcript = canExposeTranscript | ||
| ? [systemPromptMessage(), ...normalizedTranscript] | ||
| : []; |
There was a problem hiding this comment.
System prompt skews trace fallback
Medium Severity
When summary.traceId and sessionRecord.traceId are missing, traceIdFromTranscript runs on the exposed transcript that now starts with the synthetic system message. That helper returns the first trace_id-like hex match in message text, so prompt content from buildSystemPrompt() is scanned before user or assistant messages and can supply a false ID or hide the real one in the turn log.
Reviewed by Cursor Bugbot for commit c05050a. Configure here.


Prepend the system prompt as a synthetic
system-role message to each turn's transcript in the dashboard. The message is assembled frombuildSystemPrompt()at read time — no Redis storage changes, no piMessages bloat.Reporting layer injection
readConversationnow prepends asystemPromptMessage()to each exposed turn transcript. It is excluded fromtranscriptMessageCount(which already only countsuser/assistantroles), so all existing stats stay correct.UI: collapsed by default
New
SystemMessageViewcomponent renders system messages behind a<details>disclosure, closed by default. The summary shows the role label + char count so the transcript stays scannable. This mirrors the existingThinkingPartViewpattern.Forward-compatible placeholder
When the transcript source moves to traces the system message will arrive as a span attribute instead of a synthetic injection — this change adds nothing that would conflict with that migration.
View Session in Sentry