@sentry/junior-dashboard 0.243.0 → 0.245.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -94,7 +94,7 @@ export function eventLogSummary(data: ConversationReportEventData): string {
94
94
  case "handoff":
95
95
  return "Handoff · New configuration applied";
96
96
  case "subagent":
97
- return `${data.subagentKind} · ${data.status} · ${data.childConversationId}`;
97
+ return `${data.agentName ?? data.subagentKind} · ${data.status} · ${data.childConversationId}`;
98
98
  }
99
99
  }
100
100
 
@@ -362,6 +362,9 @@ export function transcriptMessagesFromEvents(
362
362
  childConversationId: data.childConversationId,
363
363
  subagentKind: data.subagentKind,
364
364
  status: data.status,
365
+ ...(data.agentName ? { agentName: data.agentName } : undefined),
366
+ ...(data.task ? { task: data.task } : undefined),
367
+ ...(data.title ? { title: data.title } : undefined),
365
368
  };
366
369
  subagents.set(subagentId, part);
367
370
  messages.push({
@@ -194,7 +194,12 @@ export function messageRawText(message: TranscriptViewMessage): string {
194
194
  if (part.type === "reasoning") return part.text ?? "reasoning redacted";
195
195
  if (part.type === "tool_call") return `tool_call ${part.name}`;
196
196
  if (part.type === "subagent") {
197
- return `subagent ${part.subagentKind}\nstatus ${part.status}`;
197
+ return [
198
+ `subagent ${part.agentName ?? part.subagentKind}`,
199
+ ...(part.title ? [`title ${part.title}`] : []),
200
+ ...(part.task ? [`task ${part.task}`] : []),
201
+ `status ${part.status}`,
202
+ ].join("\n");
198
203
  }
199
204
  if (part.type === "structured_event") {
200
205
  return [
@@ -160,7 +160,12 @@ export function entryMatchesSearch(
160
160
 
161
161
  if (entry.kind === "subagent") {
162
162
  return (
163
- textContains(entry.part.subagentKind, normalizedQuery) ||
163
+ textContains(
164
+ entry.part.agentName ?? entry.part.subagentKind,
165
+ normalizedQuery,
166
+ ) ||
167
+ textContains(entry.part.title, normalizedQuery) ||
168
+ textContains(entry.part.task, normalizedQuery) ||
164
169
  textContains(entry.part.status, normalizedQuery)
165
170
  );
166
171
  }
@@ -660,6 +660,21 @@ function compareTimeDesc(a: string | undefined, b: string | undefined): number {
660
660
  return (parseTime(b) ?? 0) - (parseTime(a) ?? 0);
661
661
  }
662
662
 
663
+ /**
664
+ * Return the title of a loaded Conversation. A child agent Conversation uses
665
+ * its agent label from the agent tree, because it has no title of its own.
666
+ */
667
+ export function conversationDetailTitle(
668
+ detail:
669
+ | Pick<ConversationTranscript, "agentTree" | "displayTitle">
670
+ | undefined,
671
+ ): string | undefined {
672
+ return (
673
+ detail?.agentTree?.at(-1)?.label ??
674
+ (detail?.displayTitle?.trim() || undefined)
675
+ );
676
+ }
677
+
663
678
  /** Return the display title prepared by the reporting API. */
664
679
  export function conversationDisplayTitle(
665
680
  conversation: Conversation | undefined,
@@ -396,9 +396,14 @@ function appendSubagent(
396
396
  part: TranscriptViewSubagentPart,
397
397
  timestamp: number | undefined,
398
398
  ): void {
399
- lines.push("", `### Subagent: ${headingText(part.subagentKind)}`);
399
+ lines.push(
400
+ "",
401
+ `### Subagent: ${headingText(part.agentName ?? part.subagentKind)}`,
402
+ );
400
403
  addEventMeta(lines, conversationTranscript, timestamp);
401
404
  addMetaLine(lines, "Status", part.status);
405
+ addMetaLine(lines, "Title", part.title);
406
+ addMetaLine(lines, "Task", part.task);
402
407
  }
403
408
 
404
409
  function appendTool(
@@ -11,7 +11,7 @@ export function SegmentedTabsFixture() {
11
11
  <SegmentedTabs
12
12
  items={[
13
13
  { label: "Details", value: "details" },
14
- { label: "Memories", value: "memories" },
14
+ { count: 3, label: "Memories", value: "memories" },
15
15
  { label: "Usage", value: "usage" },
16
16
  ]}
17
17
  label="Conversation panels"
@@ -38,10 +38,13 @@ export type TranscriptViewToolCallPart = {
38
38
  };
39
39
 
40
40
  export type TranscriptViewSubagentPart = {
41
+ agentName?: string;
41
42
  childConversationId: string;
42
43
  id: string;
43
44
  status: "aborted" | "blocked" | "completed" | "error" | "running";
44
45
  subagentKind: string;
46
+ task?: string;
47
+ title?: string;
45
48
  type: "subagent";
46
49
  };
47
50
 
@@ -719,6 +719,9 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
719
719
  startedAt: iso(Date.parse(startedAt), 4_000),
720
720
  childConversationId: DASHBOARD_QA_PLAN_ID,
721
721
  subagentKind: "agent",
722
+ agentName: "planner",
723
+ task: "Review the dashboard plan before editing.",
724
+ title: "Review the dashboard plan",
722
725
  parentToolCallId: "qa-advisor",
723
726
  status: "running",
724
727
  }),
@@ -728,6 +731,9 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
728
731
  startedAt: iso(Date.parse(startedAt), 4_000),
729
732
  childConversationId: DASHBOARD_QA_PLAN_ID,
730
733
  subagentKind: "agent",
734
+ agentName: "planner",
735
+ task: "Review the dashboard plan before editing.",
736
+ title: "Review the dashboard plan",
731
737
  parentToolCallId: "qa-advisor",
732
738
  status: "completed",
733
739
  }),
@@ -750,6 +756,7 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
750
756
  startedAt: iso(Date.parse(startedAt), 25_000),
751
757
  childConversationId: DASHBOARD_QA_REVIEW_ID,
752
758
  subagentKind: "agent",
759
+ agentName: "reviewer",
753
760
  status: "running",
754
761
  }),
755
762
  reportEvent(7, iso(Date.parse(startedAt), 44_000), {
@@ -758,6 +765,7 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
758
765
  startedAt: iso(Date.parse(startedAt), 25_000),
759
766
  childConversationId: DASHBOARD_QA_REVIEW_ID,
760
767
  subagentKind: "agent",
768
+ agentName: "reviewer",
761
769
  status: "blocked",
762
770
  }),
763
771
  reportEvent(8, iso(Date.parse(startedAt), 50_000), {
@@ -963,6 +971,10 @@ function advisorConversation(
963
971
  nowMs: number,
964
972
  conversationId: string,
965
973
  text: string,
974
+ metrics: Pick<
975
+ ConversationDetailReport,
976
+ "cumulativeDurationMs" | "cumulativeUsage"
977
+ >,
966
978
  ): MockConversation {
967
979
  const startedAt = iso(nowMs, -10 * 60_000);
968
980
  const conversation = detail(nowMs, {
@@ -971,7 +983,7 @@ function advisorConversation(
971
983
  startedAt,
972
984
  lastSeenAt: iso(Date.parse(startedAt), 18_000),
973
985
  lastProgressAt: iso(Date.parse(startedAt), 18_000),
974
- cumulativeDurationMs: 18_000,
986
+ ...metrics,
975
987
  events: [
976
988
  reportEvent(0, startedAt, {
977
989
  type: "message",
@@ -1391,11 +1403,28 @@ function mockConversations(nowMs: number): MockConversation[] {
1391
1403
  nowMs,
1392
1404
  DASHBOARD_QA_PLAN_ID,
1393
1405
  "Review the dashboard plan before editing.",
1406
+ {
1407
+ cumulativeDurationMs: 72_400,
1408
+ cumulativeUsage: {
1409
+ inputTokens: 38_400,
1410
+ outputTokens: 2_150,
1411
+ cachedInputTokens: 21_600,
1412
+ cost: { input: 0.094, output: 0.048, total: 0.142 },
1413
+ },
1414
+ },
1394
1415
  ),
1395
1416
  advisorConversation(
1396
1417
  nowMs,
1397
1418
  DASHBOARD_QA_REVIEW_ID,
1398
1419
  "Review the implementation after the first advisor pass.",
1420
+ {
1421
+ cumulativeDurationMs: 9_800,
1422
+ cumulativeUsage: {
1423
+ inputTokens: 6_200,
1424
+ outputTokens: 310,
1425
+ cost: { input: 0.015, output: 0.004, total: 0.019 },
1426
+ },
1427
+ },
1399
1428
  ),
1400
1429
  longConversation(nowMs),
1401
1430
  incidentConversation(nowMs),
@@ -1821,7 +1850,8 @@ export function readMockConversationPendingMessages(
1821
1850
  export function readMockConversationChildAgents(
1822
1851
  conversationId: string,
1823
1852
  ): ConversationChildAgentsReport | undefined {
1824
- const conversation = mockConversations(NOW_MS).find(
1853
+ const conversations = mockConversations(NOW_MS);
1854
+ const conversation = conversations.find(
1825
1855
  (candidate) => candidate.conversationId === conversationId,
1826
1856
  );
1827
1857
  if (!conversation) return undefined;
@@ -1829,9 +1859,20 @@ export function readMockConversationChildAgents(
1829
1859
  return { agents: [], conversationId };
1830
1860
  }
1831
1861
  const startedAtMs = NOW_MS - 5 * 60 * 60_000;
1862
+ // Match the API: each row has the usage of its own child Conversation.
1863
+ const childMetrics = (childConversationId: string) => {
1864
+ const child = conversations.find(
1865
+ (candidate) => candidate.conversationId === childConversationId,
1866
+ )!;
1867
+ return {
1868
+ cumulativeDurationMs: child.cumulativeDurationMs,
1869
+ cumulativeUsage: child.cumulativeUsage,
1870
+ };
1871
+ };
1832
1872
  return {
1833
1873
  agents: [
1834
1874
  {
1875
+ ...childMetrics(DASHBOARD_QA_REVIEW_ID),
1835
1876
  conversationId: DASHBOARD_QA_REVIEW_ID,
1836
1877
  error: "GitHub access is needed to read the pull request.",
1837
1878
  name: "reviewer",
@@ -1840,9 +1881,11 @@ export function readMockConversationChildAgents(
1840
1881
  updatedAt: iso(startedAtMs, 44_000),
1841
1882
  },
1842
1883
  {
1884
+ ...childMetrics(DASHBOARD_QA_PLAN_ID),
1843
1885
  conversationId: DASHBOARD_QA_PLAN_ID,
1844
1886
  status: "completed",
1845
1887
  task: "Review the dashboard plan before editing.",
1888
+ title: "Review the dashboard plan",
1846
1889
  updatedAt: iso(startedAtMs, 20_000),
1847
1890
  },
1848
1891
  ],
@@ -1850,6 +1893,36 @@ export function readMockConversationChildAgents(
1850
1893
  };
1851
1894
  }
1852
1895
 
1896
+ /** Build the agent tree of a mock child Conversation, like the API. */
1897
+ function mockAgentTree(
1898
+ conversation: MockConversation,
1899
+ ): ConversationDetailReport["agentTree"] {
1900
+ if (!conversation.parentConversationId) return undefined;
1901
+ const conversations = mockConversations(NOW_MS);
1902
+ const chain = [conversation];
1903
+ let current = conversation;
1904
+ while (current.parentConversationId) {
1905
+ const parent = conversations.find(
1906
+ (candidate) => candidate.conversationId === current.parentConversationId,
1907
+ );
1908
+ if (!parent) break;
1909
+ chain.unshift(parent);
1910
+ current = parent;
1911
+ }
1912
+ return chain.map((item) => {
1913
+ if (!item.parentConversationId) {
1914
+ return { conversationId: item.conversationId, label: item.displayTitle };
1915
+ }
1916
+ const agent = readMockConversationChildAgents(
1917
+ item.parentConversationId,
1918
+ )?.agents.find((child) => child.conversationId === item.conversationId);
1919
+ return {
1920
+ conversationId: item.conversationId,
1921
+ label: agent?.title ?? agent?.name ?? "Subagent",
1922
+ };
1923
+ });
1924
+ }
1925
+
1853
1926
  /** Return one canonical-event visual-QA conversation detail fixture. */
1854
1927
  export function readMockConversationDetail(
1855
1928
  conversationId: string,
@@ -1860,9 +1933,11 @@ export function readMockConversationDetail(
1860
1933
  );
1861
1934
  if (!conversation) return undefined;
1862
1935
  const { parentConversationId, ...conversationDetail } = conversation;
1936
+ const agentTree = mockAgentTree(conversation);
1863
1937
  // Match the API: only readable root Conversations can be forked.
1864
1938
  const detail = {
1865
1939
  ...conversationDetail,
1940
+ ...(agentTree ? { agentTree } : undefined),
1866
1941
  canFork:
1867
1942
  !parentConversationId &&
1868
1943
  conversationDetail.eventHistory.status !== "redacted",