@sentry/junior-dashboard 0.91.0 → 0.92.1

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.
@@ -1,4 +1,4 @@
1
- import type { ConversationActivityReport, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSurface, ConversationUsage, RequesterIdentity, TranscriptMessage } from "@sentry/junior/reporting";
1
+ import type { ConversationActivityReport, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSurface, ConversationUsage, ActorIdentity, TranscriptMessage } from "@sentry/junior/reporting";
2
2
  export type MockRunOptions = {
3
3
  activity?: ConversationActivityReport[];
4
4
  channel?: string;
@@ -11,7 +11,7 @@ export type MockRunOptions = {
11
11
  id?: string;
12
12
  lastProgressAt?: string;
13
13
  lastSeenAt?: string;
14
- requesterIdentity?: RequesterIdentity;
14
+ actorIdentity?: ActorIdentity;
15
15
  sentryTraceUrl?: string;
16
16
  startedAt?: string;
17
17
  status?: ConversationReportStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-dashboard",
3
- "version": "0.91.0",
3
+ "version": "0.92.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -33,8 +33,8 @@
33
33
  "react-router": "^7.16.0",
34
34
  "recharts": "^3.8.1",
35
35
  "shiki": "4.1.0",
36
- "@sentry/junior": "0.91.0",
37
- "@sentry/junior-plugin-api": "0.91.0"
36
+ "@sentry/junior": "0.92.1",
37
+ "@sentry/junior-plugin-api": "0.92.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@tailwindcss/cli": "^4.3.0",
package/src/app.ts CHANGED
@@ -224,7 +224,7 @@ function emptyConversationStatsReport(): ConversationStatsReport {
224
224
  generatedAt: new Date(nowMs).toISOString(),
225
225
  hung: 0,
226
226
  locations: [],
227
- requesters: [],
227
+ actors: [],
228
228
  sampleLimit: 0,
229
229
  sampleSize: 0,
230
230
  source: "conversation_index",
package/src/client/api.ts CHANGED
@@ -11,8 +11,8 @@ import type {
11
11
  Identity,
12
12
  Plugin,
13
13
  PluginReportFeed,
14
- RequesterDirectory,
15
- RequesterProfile,
14
+ ActorDirectory,
15
+ ActorProfile,
16
16
  Runtime,
17
17
  Skill,
18
18
  } from "./types";
@@ -89,7 +89,7 @@ function emptyConversationStatsReport(): ConversationStatsReport {
89
89
  generatedAt: new Date(nowMs).toISOString(),
90
90
  hung: 0,
91
91
  locations: [],
92
- requesters: [],
92
+ actors: [],
93
93
  sampleLimit: 0,
94
94
  sampleSize: 0,
95
95
  source: "conversation_index",
@@ -120,8 +120,8 @@ async function readPluginReports(): Promise<PluginReportFeed> {
120
120
  return await read<PluginReportFeed>("/api/plugin-reports");
121
121
  }
122
122
 
123
- async function readRequesterDirectory(): Promise<RequesterDirectory> {
124
- return await read<RequesterDirectory>("/api/people");
123
+ async function readActorDirectory(): Promise<ActorDirectory> {
124
+ return await read<ActorDirectory>("/api/people");
125
125
  }
126
126
 
127
127
  /** Fetch dashboard shell data shared across browser routes. */
@@ -159,22 +159,22 @@ export function useConversationsData() {
159
159
  });
160
160
  }
161
161
 
162
- /** Fetch the requester directory used by the People dashboard route. */
163
- export function useRequesterDirectoryData() {
162
+ /** Fetch the actor directory used by the People dashboard route. */
163
+ export function useActorDirectoryData() {
164
164
  return useQuery({
165
165
  queryKey: ["dashboard", "people"],
166
- queryFn: readRequesterDirectory,
166
+ queryFn: readActorDirectory,
167
167
  retry: false,
168
168
  });
169
169
  }
170
170
 
171
- /** Fetch one requester profile for the People detail dashboard route. */
172
- export function useRequesterProfileData(email: string | undefined) {
171
+ /** Fetch one actor profile for the People detail dashboard route. */
172
+ export function useActorProfileData(email: string | undefined) {
173
173
  return useQuery({
174
174
  enabled: Boolean(email),
175
175
  queryKey: ["dashboard", "people", email],
176
- queryFn: async (): Promise<RequesterProfile> =>
177
- read<RequesterProfile>(`/api/people/${encodeURIComponent(email!)}`),
176
+ queryFn: async (): Promise<ActorProfile> =>
177
+ read<ActorProfile>(`/api/people/${encodeURIComponent(email!)}`),
178
178
  retry: false,
179
179
  });
180
180
  }
@@ -16,7 +16,7 @@ import {
16
16
  buildConversations,
17
17
  conversationRuntimeMs,
18
18
  conversationDisplayTitle,
19
- conversationRequesterLabel,
19
+ conversationActorLabel,
20
20
  conversationPath,
21
21
  filterRecentConversations,
22
22
  formatDurationTick,
@@ -358,7 +358,7 @@ function chartTooltipRows(
358
358
  detail: ConversationDetailFeed | undefined,
359
359
  ): Array<[string, ReactNode]> {
360
360
  const session = point.conversation;
361
- const requester = conversationRequesterLabel(point.conversation);
361
+ const actor = conversationActorLabel(point.conversation);
362
362
  const location = session
363
363
  ? slackLocationLabel(session, { includeId: false })
364
364
  : undefined;
@@ -396,7 +396,7 @@ function chartTooltipRows(
396
396
  ),
397
397
  ]
398
398
  : null,
399
- requester ? ["requester", requester] : null,
399
+ actor ? ["actor", actor] : null,
400
400
  location ? ["surface", location] : null,
401
401
  ];
402
402
  return rows.filter((row): row is [string, ReactNode] => row !== null);
@@ -65,18 +65,16 @@ export function ConversationFilterSelect(props: {
65
65
  /** Render the conversation list search and facet controls as one compact toolbar. */
66
66
  export function ConversationListToolbar(props: {
67
67
  query: string;
68
- requester: string;
69
- requesterOptions: ConversationListFilterOption[];
68
+ actor: string;
69
+ actorOptions: ConversationListFilterOption[];
70
70
  source: string;
71
71
  sourceOptions: ConversationListFilterOption[];
72
72
  onQueryChange(value: string): void;
73
- onRequesterChange(value: string): void;
73
+ onActorChange(value: string): void;
74
74
  onSourceChange(value: string): void;
75
75
  onClear?(): void;
76
76
  }) {
77
- const filtered = Boolean(
78
- props.query.trim() || props.requester || props.source,
79
- );
77
+ const filtered = Boolean(props.query.trim() || props.actor || props.source);
80
78
  return (
81
79
  <div className="border-b border-white/10 bg-[#050505] px-3 py-3">
82
80
  <div className="grid min-w-0 gap-2 md:grid-cols-[minmax(14rem,1fr)_minmax(9rem,13rem)_minmax(11rem,16rem)_auto]">
@@ -94,11 +92,11 @@ export function ConversationListToolbar(props: {
94
92
  onChange={props.onSourceChange}
95
93
  />
96
94
  <ConversationFilterSelect
97
- allLabel="All requesters"
98
- label="Requester"
99
- options={props.requesterOptions}
100
- value={props.requester}
101
- onChange={props.onRequesterChange}
95
+ allLabel="All actors"
96
+ label="Actor"
97
+ options={props.actorOptions}
98
+ value={props.actor}
99
+ onChange={props.onActorChange}
102
100
  />
103
101
  {filtered && props.onClear ? (
104
102
  <Button
@@ -2,7 +2,7 @@ import { Link } from "react-router";
2
2
 
3
3
  import {
4
4
  conversationDisplayTitle,
5
- conversationRequesterLabel,
5
+ conversationActorLabel,
6
6
  peoplePath,
7
7
  visualStatusForConversation,
8
8
  } from "../format";
@@ -29,8 +29,8 @@ export function ConversationSummary(props: { conversation: Conversation }) {
29
29
  }
30
30
 
31
31
  function ConversationIdentity(props: { conversation: Conversation }) {
32
- const email = props.conversation.requesterIdentity?.email?.trim();
33
- const owner = conversationRequesterLabel(props.conversation);
32
+ const email = props.conversation.actorIdentity?.email?.trim();
33
+ const owner = conversationActorLabel(props.conversation);
34
34
  const id = props.conversation.id;
35
35
 
36
36
  if (!owner) return id;
@@ -13,7 +13,7 @@ import {
13
13
  formatMessageTimestamp,
14
14
  formatMs,
15
15
  formatTurnDuration,
16
- requesterLabel,
16
+ actorLabel,
17
17
  summarizeMessages,
18
18
  summarizeToolCalls,
19
19
  summarizeUsage,
@@ -566,7 +566,7 @@ function redactedMessageSize(part: TranscriptViewPart): string | undefined {
566
566
  }
567
567
 
568
568
  function turnActorLabel(turn: ConversationTurn): string {
569
- return requesterLabel(turn.requesterIdentity) ?? "User";
569
+ return actorLabel(turn.actorIdentity) ?? "User";
570
570
  }
571
571
 
572
572
  function turnMessageSummary(turn: ConversationTurn) {
@@ -8,7 +8,7 @@ import type {
8
8
  ConversationFilter,
9
9
  ConversationSummary,
10
10
  MarkupNode,
11
- RequesterIdentity,
11
+ ActorIdentity,
12
12
  TranscriptViewMessage,
13
13
  TranscriptViewPart,
14
14
  TurnUsage,
@@ -390,7 +390,7 @@ function transcriptMessageAuthor(
390
390
  const kind = transcriptRoleKind(message.role);
391
391
  if (kind === "assistant") return "Junior";
392
392
  if (kind === "user") {
393
- return requesterLabel(turn.requesterIdentity) ?? "User";
393
+ return actorLabel(turn.actorIdentity) ?? "User";
394
394
  }
395
395
  if (kind === "system") return "System";
396
396
  if (kind === "tool") return "Tool";
@@ -572,30 +572,30 @@ export function conversationDisplayTitle(
572
572
  return conversation?.displayTitle ?? "Conversation";
573
573
  }
574
574
 
575
- /** Prefer stable requester identifiers while keeping Slack ids as a last resort. */
576
- export function requesterLabel(
577
- requester: RequesterIdentity | undefined,
575
+ /** Prefer stable actor identifiers while keeping Slack ids as a last resort. */
576
+ export function actorLabel(
577
+ actor: ActorIdentity | undefined,
578
578
  ): string | undefined {
579
- const email = requester?.email?.trim() || undefined;
580
- const fullName = requester?.fullName?.trim() || undefined;
581
- const slackUserName = requester?.slackUserName?.trim() || undefined;
582
- return email ?? fullName ?? slackUserName ?? requester?.slackUserId;
579
+ const email = actor?.email?.trim() || undefined;
580
+ const fullName = actor?.fullName?.trim() || undefined;
581
+ const slackUserName = actor?.slackUserName?.trim() || undefined;
582
+ return email ?? fullName ?? slackUserName ?? actor?.slackUserId;
583
583
  }
584
584
 
585
- /** Derive the conversation owner label from structured requester identity. */
586
- export function conversationRequesterLabel(
585
+ /** Derive the conversation owner label from structured actor identity. */
586
+ export function conversationActorLabel(
587
587
  conversation: Conversation | undefined,
588
588
  ): string | undefined {
589
- return requesterLabel(conversation?.requesterIdentity);
589
+ return actorLabel(conversation?.actorIdentity);
590
590
  }
591
591
 
592
- /** Return the stable requester key used by dashboard list filters. */
593
- export function conversationRequesterKey(
592
+ /** Return the stable actor key used by dashboard list filters. */
593
+ export function conversationActorKey(
594
594
  conversation: Conversation | undefined,
595
595
  ): string | undefined {
596
596
  return (
597
- conversation?.requesterIdentity?.email?.trim() ||
598
- conversationRequesterLabel(conversation)
597
+ conversation?.actorIdentity?.email?.trim() ||
598
+ conversationActorLabel(conversation)
599
599
  );
600
600
  }
601
601
 
@@ -605,7 +605,7 @@ export function conversationIdentityMeta(
605
605
  conversationId: string | undefined,
606
606
  ): string {
607
607
  const id = conversationId ?? conversation?.id;
608
- const owner = conversationRequesterLabel(conversation);
608
+ const owner = conversationActorLabel(conversation);
609
609
  if (!id) return owner ?? "";
610
610
  return owner ? `${owner} · ${id}` : id;
611
611
  }
@@ -683,7 +683,7 @@ export function conversationPath(conversationId: string): string {
683
683
  return `/conversations/${encodeURIComponent(conversationId)}`;
684
684
  }
685
685
 
686
- /** Build the canonical requester profile route for a trusted email address. */
686
+ /** Build the canonical actor profile route for a trusted email address. */
687
687
  export function peoplePath(email: string): string {
688
688
  return `/people/${encodeURIComponent(email)}`;
689
689
  }
@@ -939,7 +939,7 @@ export function buildConversations(
939
939
  : sortedTurns.some(isFailedConversationSummary)
940
940
  ? "failed"
941
941
  : newest.status;
942
- const requesterTurn = sortedTurns.find((turn) => turn.requesterIdentity);
942
+ const actorTurn = sortedTurns.find((turn) => turn.actorIdentity);
943
943
  return {
944
944
  channel: newest.channel,
945
945
  channelName: recentTurns.find((turn) => turn.channelName)?.channelName,
@@ -947,7 +947,7 @@ export function buildConversations(
947
947
  id,
948
948
  lastProgressAt: newest.lastProgressAt,
949
949
  lastSeenAt: newest.lastSeenAt,
950
- requesterIdentity: requesterTurn?.requesterIdentity,
950
+ actorIdentity: actorTurn?.actorIdentity,
951
951
  sentryTraceUrl: newest.sentryTraceUrl,
952
952
  startedAt: oldest.startedAt,
953
953
  status,
@@ -1019,7 +1019,7 @@ export function filterConversations(
1019
1019
 
1020
1020
  export type ConversationListFilters = {
1021
1021
  query?: string;
1022
- requester?: string;
1022
+ actor?: string;
1023
1023
  source?: string;
1024
1024
  };
1025
1025
 
@@ -1045,7 +1045,7 @@ function uniqueConversationOptions(
1045
1045
  }
1046
1046
 
1047
1047
  function conversationSearchHaystack(conversation: Conversation): string {
1048
- const requester = conversation.requesterIdentity;
1048
+ const actor = conversation.actorIdentity;
1049
1049
  return [
1050
1050
  conversation.displayTitle,
1051
1051
  conversation.id,
@@ -1053,10 +1053,10 @@ function conversationSearchHaystack(conversation: Conversation): string {
1053
1053
  conversation.channelName,
1054
1054
  conversation.status,
1055
1055
  conversation.surface,
1056
- requester?.email,
1057
- requester?.fullName,
1058
- requester?.slackUserId,
1059
- requester?.slackUserName,
1056
+ actor?.email,
1057
+ actor?.fullName,
1058
+ actor?.slackUserId,
1059
+ actor?.slackUserName,
1060
1060
  ]
1061
1061
  .filter(Boolean)
1062
1062
  .join(" ")
@@ -1074,17 +1074,17 @@ export function conversationSourceOptions(
1074
1074
  );
1075
1075
  }
1076
1076
 
1077
- /** Return requester filter options present in the loaded conversation rows. */
1078
- export function conversationRequesterOptions(
1077
+ /** Return actor filter options present in the loaded conversation rows. */
1078
+ export function conversationActorOptions(
1079
1079
  conversations: Conversation[],
1080
1080
  ): ConversationListFilterOption[] {
1081
1081
  return uniqueConversationOptions(
1082
1082
  conversations,
1083
- conversationRequesterKey,
1083
+ conversationActorKey,
1084
1084
  (conversation, value) =>
1085
- conversation.requesterIdentity?.fullName?.trim() ||
1086
- conversation.requesterIdentity?.email?.trim() ||
1087
- conversation.requesterIdentity?.slackUserName?.trim() ||
1085
+ conversation.actorIdentity?.fullName?.trim() ||
1086
+ conversation.actorIdentity?.email?.trim() ||
1087
+ conversation.actorIdentity?.slackUserName?.trim() ||
1088
1088
  value,
1089
1089
  );
1090
1090
  }
@@ -1096,11 +1096,11 @@ export function filterConversationList(
1096
1096
  ): Conversation[] {
1097
1097
  const query = filters.query?.trim().toLowerCase();
1098
1098
  const source = filters.source?.trim();
1099
- const requester = filters.requester?.trim();
1099
+ const actor = filters.actor?.trim();
1100
1100
 
1101
1101
  return conversations.filter((conversation) => {
1102
1102
  if (source && conversation.surface !== source) return false;
1103
- if (requester && conversationRequesterKey(conversation) !== requester) {
1103
+ if (actor && conversationActorKey(conversation) !== actor) {
1104
1104
  return false;
1105
1105
  }
1106
1106
  if (query && !conversationSearchHaystack(conversation).includes(query)) {
@@ -2,7 +2,7 @@ import {
2
2
  conversationDisplayTitle,
3
3
  formatMs,
4
4
  formatUsageTotal,
5
- requesterLabel,
5
+ actorLabel,
6
6
  slackLocationLabel,
7
7
  stringifyPartValue,
8
8
  transcriptRoleKind,
@@ -33,11 +33,7 @@ export function buildConversationMarkdown(
33
33
  lines.push(`# ${headingText(conversationTitle(detail, conversation))}`, "");
34
34
  addMetaLine(lines, "Conversation ID", inlineCode(detail.conversationId));
35
35
  addMetaLine(lines, "Generated", detail.generatedAt);
36
- addMetaLine(
37
- lines,
38
- "Requester",
39
- conversationRequester(conversation, firstTurn),
40
- );
36
+ addMetaLine(lines, "Actor", conversationActor(conversation, firstTurn));
41
37
  addMetaLine(lines, "Location", conversationLocation(conversation, firstTurn));
42
38
  addMetaLine(
43
39
  lines,
@@ -249,15 +245,11 @@ function conversationTitle(
249
245
  return conversation ? conversationDisplayTitle(conversation) : "Conversation";
250
246
  }
251
247
 
252
- function conversationRequester(
248
+ function conversationActor(
253
249
  conversation: Conversation | undefined,
254
250
  turn: ConversationTurn | undefined,
255
251
  ): string {
256
- return (
257
- requesterLabel(
258
- conversation?.requesterIdentity ?? turn?.requesterIdentity,
259
- ) ?? ""
260
- );
252
+ return actorLabel(conversation?.actorIdentity ?? turn?.actorIdentity) ?? "";
261
253
  }
262
254
 
263
255
  function conversationLocation(
@@ -274,7 +266,7 @@ function messageRoleLabel(
274
266
  ): string {
275
267
  const kind = transcriptRoleKind(message.role);
276
268
  if (kind === "assistant") return "Junior";
277
- if (kind === "user") return requesterLabel(turn.requesterIdentity) ?? "User";
269
+ if (kind === "user") return actorLabel(turn.actorIdentity) ?? "User";
278
270
  if (kind === "system") return "System";
279
271
  if (kind === "tool") return "Tool";
280
272
  return headingText(message.role || "Unknown");
@@ -10,7 +10,7 @@ import {
10
10
  buildConversations,
11
11
  conversationDisplayTitle,
12
12
  conversationFromDetail,
13
- conversationRequesterLabel,
13
+ conversationActorLabel,
14
14
  formatConversationDuration,
15
15
  formatRelativeTime,
16
16
  formatTime,
@@ -177,8 +177,8 @@ function ConversationIdentity(props: {
177
177
  conversationId: string | undefined;
178
178
  detail: ConversationDetailFeed | undefined;
179
179
  }) {
180
- const email = props.conversation?.requesterIdentity?.email?.trim();
181
- const owner = conversationRequesterLabel(props.conversation);
180
+ const email = props.conversation?.actorIdentity?.email?.trim();
181
+ const owner = conversationActorLabel(props.conversation);
182
182
  const id = props.conversationId ?? props.conversation?.id;
183
183
 
184
184
  return (
@@ -8,7 +8,7 @@ import { SectionHeader } from "../components/SectionHeader";
8
8
  import { SectionTitle } from "../components/SectionTitle";
9
9
  import {
10
10
  buildConversations,
11
- conversationRequesterOptions,
11
+ conversationActorOptions,
12
12
  conversationSourceOptions,
13
13
  filterConversationList,
14
14
  filterConversations,
@@ -22,16 +22,16 @@ export function ConversationsPage(props: { data?: DashboardData }) {
22
22
  const [params, setParams] = useSearchParams();
23
23
  const filter = getFilter(params.get("filter"));
24
24
  const query = params.get("q") ?? "";
25
- const requester = params.get("requester") ?? "";
25
+ const actor = params.get("actor") ?? "";
26
26
  const source = params.get("source") ?? "";
27
27
  const summaries = props.data?.conversations.conversations ?? [];
28
28
  const conversations = buildConversations(summaries);
29
29
  const sourceOptions = conversationSourceOptions(conversations);
30
- const requesterOptions = conversationRequesterOptions(conversations);
30
+ const actorOptions = conversationActorOptions(conversations);
31
31
  const statusConversations = filterConversations(conversations, filter);
32
32
  const visibleConversations = filterConversationList(statusConversations, {
33
33
  query,
34
- requester,
34
+ actor,
35
35
  source,
36
36
  });
37
37
  const search = params.toString();
@@ -45,7 +45,7 @@ export function ConversationsPage(props: { data?: DashboardData }) {
45
45
  setParams(next);
46
46
  }
47
47
 
48
- function updateParam(name: "q" | "requester" | "source", value: string) {
48
+ function updateParam(name: "q" | "actor" | "source", value: string) {
49
49
  const next = new URLSearchParams(params);
50
50
  const nextValue = name === "q" ? value : value.trim();
51
51
  if (nextValue.trim()) {
@@ -59,7 +59,7 @@ export function ConversationsPage(props: { data?: DashboardData }) {
59
59
  function clearListFilters() {
60
60
  const next = new URLSearchParams(params);
61
61
  next.delete("q");
62
- next.delete("requester");
62
+ next.delete("actor");
63
63
  next.delete("source");
64
64
  setParams(next, { replace: true });
65
65
  }
@@ -80,12 +80,12 @@ export function ConversationsPage(props: { data?: DashboardData }) {
80
80
  </SectionHeader>
81
81
  <ConversationListToolbar
82
82
  query={query}
83
- requester={requester}
84
- requesterOptions={requesterOptions}
83
+ actor={actor}
84
+ actorOptions={actorOptions}
85
85
  source={source}
86
86
  sourceOptions={sourceOptions}
87
87
  onQueryChange={(value) => updateParam("q", value)}
88
- onRequesterChange={(value) => updateParam("requester", value)}
88
+ onActorChange={(value) => updateParam("actor", value)}
89
89
  onSourceChange={(value) => updateParam("source", value)}
90
90
  onClear={clearListFilters}
91
91
  />