@sentry/junior-dashboard 0.257.0 → 0.258.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.
- package/README.md +3 -3
- package/dist/app.js +12 -40
- package/dist/client.js +34 -34
- package/dist/index.d.ts +36 -41
- package/dist/index.js +12 -40
- package/package.json +6 -5
- package/src/api/schema.ts +1 -2
- package/src/app.ts +10 -56
- package/src/auth.ts +8 -5
- package/src/authenticated-routes.ts +1 -2
- package/src/client/conversations/TranscriptActivityGroup.tsx +3 -2
- package/src/client/conversations/transcriptFailure.ts +0 -4
- package/src/client/conversations/transcriptMarkdownLinks.ts +3 -0
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +4 -4
- package/src/client/pages/automations/automationDraft.ts +1 -1
- package/src/client/pages/system/PluginBarChart.tsx +8 -8
- package/src/client.tsx +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.258.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"react-router": "^7.18.2",
|
|
34
34
|
"shiki": "4.1.0",
|
|
35
35
|
"zod": "^4.5.4",
|
|
36
|
-
"@sentry/junior": "0.
|
|
37
|
-
"@sentry/junior
|
|
36
|
+
"@sentry/junior-plugin-api": "0.258.0",
|
|
37
|
+
"@sentry/junior": "0.258.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@oxlint/plugins": "1.79.0",
|
|
@@ -47,15 +47,16 @@
|
|
|
47
47
|
"eslint": "^10.8.0",
|
|
48
48
|
"oxlint": "^1.79.0",
|
|
49
49
|
"tailwindcss": "^4.3.0",
|
|
50
|
+
"tsdown": "0.23.1",
|
|
50
51
|
"tsup": "^8.5.1",
|
|
51
|
-
"typescript": "^
|
|
52
|
+
"typescript": "^7.0.2",
|
|
52
53
|
"vitest": "^4.1.7"
|
|
53
54
|
},
|
|
54
55
|
"scripts": {
|
|
55
56
|
"build": "pnpm --filter @sentry/junior-plugin-api build && pnpm --filter @sentry/junior build && pnpm run build:client && pnpm run build:css && pnpm run build:server",
|
|
56
57
|
"build:client": "tsup --config tsup.client.config.ts",
|
|
57
58
|
"build:css": "tailwindcss -i src/tailwind.css -o dist/tailwind.css --minify",
|
|
58
|
-
"build:server": "tsup --config tsup.config.ts",
|
|
59
|
+
"build:server": "tsup --config tsup.config.ts && tsdown --config tsdown.dts.config.ts",
|
|
59
60
|
"lint": "oxlint --config .oxlintrc.json --deny-warnings src",
|
|
60
61
|
"test": "vitest run -c vitest.config.ts",
|
|
61
62
|
"typecheck": "tsc --noEmit",
|
package/src/api/schema.ts
CHANGED
|
@@ -33,8 +33,7 @@ export const dashboardProfileUpdateSchema = z
|
|
|
33
33
|
|
|
34
34
|
export const dashboardConfigSchema = z
|
|
35
35
|
.object({
|
|
36
|
-
|
|
37
|
-
allowedGoogleDomainCount: z.number(),
|
|
36
|
+
allowedDomainCount: z.number(),
|
|
38
37
|
authRequired: z.boolean(),
|
|
39
38
|
authPath: z.string(),
|
|
40
39
|
basePath: z.string(),
|
package/src/app.ts
CHANGED
|
@@ -78,14 +78,14 @@ export interface JuniorDashboardOptions {
|
|
|
78
78
|
baseURL?: string;
|
|
79
79
|
authPath?: string;
|
|
80
80
|
authRequired?: boolean;
|
|
81
|
-
allowedGoogleDomains?: string[];
|
|
82
|
-
allowedEmails?: string[];
|
|
83
81
|
auth?: DashboardAuth;
|
|
84
82
|
componentGallery?: boolean;
|
|
85
83
|
mockConversations?: boolean;
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
interface DashboardRuntimeOptions extends JuniorDashboardOptions {
|
|
87
|
+
/** The app's `allowedDomains`, from core. Auth requires at least one. */
|
|
88
|
+
allowedDomains?: string[];
|
|
89
89
|
authenticatedRoutes?: readonly AuthenticatedRoute[];
|
|
90
90
|
/** The queue `createApp()` uses, so web input reaches the same worker. */
|
|
91
91
|
conversationWorkQueue?: ConversationWorkQueue;
|
|
@@ -113,37 +113,6 @@ function normalizeValues(values: string[] | undefined): string[] {
|
|
|
113
113
|
];
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/** Read dashboard list env vars as comma-separated strings or JSON arrays. */
|
|
117
|
-
function readEnvList(name: string): string[] | undefined {
|
|
118
|
-
const value = process.env[name];
|
|
119
|
-
if (!value?.trim()) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (value.trim().startsWith("[")) {
|
|
124
|
-
let parsed: unknown;
|
|
125
|
-
try {
|
|
126
|
-
parsed = JSON.parse(value);
|
|
127
|
-
} catch (error) {
|
|
128
|
-
throw new Error(`${name} must be a JSON string array`, {
|
|
129
|
-
cause: error,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
if (
|
|
133
|
-
!Array.isArray(parsed) ||
|
|
134
|
-
parsed.some((item) => typeof item !== "string")
|
|
135
|
-
) {
|
|
136
|
-
throw new Error(`${name} must be a JSON string array`);
|
|
137
|
-
}
|
|
138
|
-
return parsed;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return value
|
|
142
|
-
.split(",")
|
|
143
|
-
.map((item) => item.trim())
|
|
144
|
-
.filter(Boolean);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
116
|
/** Read dashboard boolean env vars; only explicit true/false values apply. */
|
|
148
117
|
function readEnvFlag(name: string): boolean | undefined {
|
|
149
118
|
const value = process.env[name]?.trim();
|
|
@@ -160,11 +129,6 @@ function resolveDashboardOptions(
|
|
|
160
129
|
...options,
|
|
161
130
|
authRequired:
|
|
162
131
|
options.authRequired ?? readEnvFlag("JUNIOR_DASHBOARD_AUTH_REQUIRED"),
|
|
163
|
-
allowedGoogleDomains:
|
|
164
|
-
options.allowedGoogleDomains ??
|
|
165
|
-
readEnvList("JUNIOR_DASHBOARD_GOOGLE_DOMAINS"),
|
|
166
|
-
allowedEmails:
|
|
167
|
-
options.allowedEmails ?? readEnvList("JUNIOR_DASHBOARD_ALLOWED_EMAILS"),
|
|
168
132
|
componentGallery:
|
|
169
133
|
options.componentGallery ??
|
|
170
134
|
readEnvFlag("JUNIOR_DASHBOARD_COMPONENT_GALLERY"),
|
|
@@ -404,8 +368,7 @@ export function createDashboardApp(
|
|
|
404
368
|
options.authPath ?? DEFAULT_AUTH_PATH,
|
|
405
369
|
DEFAULT_AUTH_PATH,
|
|
406
370
|
);
|
|
407
|
-
const allowedDomains = normalizeValues(options.
|
|
408
|
-
const allowedEmails = normalizeValues(options.allowedEmails);
|
|
371
|
+
const allowedDomains = normalizeValues(options.allowedDomains);
|
|
409
372
|
const agentName = options.agentName?.trim() || "Junior";
|
|
410
373
|
|
|
411
374
|
const authRequired = options.authRequired !== false;
|
|
@@ -415,17 +378,11 @@ export function createDashboardApp(
|
|
|
415
378
|
canonicalBaseURL = resolveDashboardBaseURL({ baseURL: configuredBaseURL });
|
|
416
379
|
}
|
|
417
380
|
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
allowedDomains.length === 0 &&
|
|
421
|
-
allowedEmails.length === 0
|
|
422
|
-
) {
|
|
423
|
-
throw new Error(
|
|
424
|
-
"Junior dashboard auth requires allowedGoogleDomains or allowedEmails",
|
|
425
|
-
);
|
|
381
|
+
if (authRequired && allowedDomains.length === 0) {
|
|
382
|
+
throw new Error("Junior dashboard auth requires allowedDomains");
|
|
426
383
|
}
|
|
427
384
|
|
|
428
|
-
// Core owns the Google sign-in session
|
|
385
|
+
// Core owns the Google sign-in session.
|
|
429
386
|
const auth = options.auth;
|
|
430
387
|
if (authRequired && !auth) {
|
|
431
388
|
throw new Error("Junior dashboard auth requires the core sign-in session");
|
|
@@ -448,10 +405,7 @@ export function createDashboardApp(
|
|
|
448
405
|
return Response.redirect(returnUrl, 302);
|
|
449
406
|
}
|
|
450
407
|
const session = await auth.getSession(c.req.raw);
|
|
451
|
-
if (
|
|
452
|
-
session &&
|
|
453
|
-
dashboardSessionIsAuthorized(session, allowedDomains, allowedEmails)
|
|
454
|
-
) {
|
|
408
|
+
if (session && dashboardSessionIsAuthorized(session, allowedDomains)) {
|
|
455
409
|
return Response.redirect(returnUrl, 302);
|
|
456
410
|
}
|
|
457
411
|
return auth.signInWithGoogle(c.req.raw, returnUrl);
|
|
@@ -540,7 +494,7 @@ export function createDashboardApp(
|
|
|
540
494
|
componentGallery: options.componentGallery,
|
|
541
495
|
});
|
|
542
496
|
}
|
|
543
|
-
if (!dashboardSessionIsAuthorized(session, allowedDomains
|
|
497
|
+
if (!dashboardSessionIsAuthorized(session, allowedDomains)) {
|
|
544
498
|
return forbidden(c.req.raw, agentName);
|
|
545
499
|
}
|
|
546
500
|
const sanitizedSession = sanitizeDashboardSession(session);
|
|
@@ -563,6 +517,7 @@ export function createDashboardApp(
|
|
|
563
517
|
c.set("viewer", viewer);
|
|
564
518
|
}
|
|
565
519
|
await next();
|
|
520
|
+
return undefined;
|
|
566
521
|
};
|
|
567
522
|
|
|
568
523
|
app.use("*", requireAuth);
|
|
@@ -621,8 +576,7 @@ export function createDashboardApp(
|
|
|
621
576
|
);
|
|
622
577
|
app.get("/api/config", () => {
|
|
623
578
|
return jsonResponse(dashboardConfigSchema, {
|
|
624
|
-
|
|
625
|
-
allowedGoogleDomainCount: allowedDomains.length,
|
|
579
|
+
allowedDomainCount: allowedDomains.length,
|
|
626
580
|
authRequired,
|
|
627
581
|
authPath,
|
|
628
582
|
basePath,
|
package/src/auth.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { DashboardSession } from "./api/schema";
|
|
|
4
4
|
export type DashboardUser = DashboardSession["user"];
|
|
5
5
|
export type { DashboardSession };
|
|
6
6
|
|
|
7
|
-
/** Google sign-in session that core
|
|
7
|
+
/** Google sign-in session that core owns. */
|
|
8
8
|
export interface DashboardAuth {
|
|
9
9
|
handler(request: Request): Promise<Response>;
|
|
10
10
|
getSession(request: Request): Promise<DashboardSession | null>;
|
|
@@ -25,19 +25,22 @@ export function dashboardBearerSession(email: string): DashboardSession {
|
|
|
25
25
|
return { user: { email, emailVerified: true } };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Check the allowed domains against one verified session. Core accepts a
|
|
30
|
+
* Google sign-in only for a Workspace account on an allowed domain. This
|
|
31
|
+
* check also applies a changed list to existing sessions and personal tokens.
|
|
32
|
+
*/
|
|
29
33
|
export function dashboardSessionIsAuthorized(
|
|
30
34
|
session: DashboardSession,
|
|
31
35
|
allowedDomains: string[],
|
|
32
|
-
allowedEmails: string[],
|
|
33
36
|
): boolean {
|
|
34
37
|
const email = session.user.email.toLowerCase();
|
|
35
38
|
const separator = email.lastIndexOf("@");
|
|
36
39
|
const domain = separator > 0 ? email.slice(separator + 1) : undefined;
|
|
37
40
|
return Boolean(
|
|
38
41
|
session.user.emailVerified &&
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
domain !== undefined &&
|
|
43
|
+
allowedDomains.includes(domain),
|
|
41
44
|
);
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -19,8 +19,7 @@ export function isAuthPath(
|
|
|
19
19
|
const requestSegments = segments(pathname);
|
|
20
20
|
return routes.some((route) => {
|
|
21
21
|
const routeSegments = segments(route.path);
|
|
22
|
-
for (
|
|
23
|
-
const pattern = routeSegments[index];
|
|
22
|
+
for (const [index, pattern] of routeSegments.entries()) {
|
|
24
23
|
if (pattern === "*") return true;
|
|
25
24
|
const value = requestSegments[index];
|
|
26
25
|
if (!value || (pattern !== value && !pattern.startsWith(":"))) {
|
|
@@ -101,9 +101,10 @@ export function activityGroupSummary(
|
|
|
101
101
|
(entry) => entry.kind === "attachments_delivered",
|
|
102
102
|
).length;
|
|
103
103
|
const eventCount = entries.filter((entry) => entry.kind === "message").length;
|
|
104
|
+
const [onlyHandoff] = handoffs;
|
|
104
105
|
const handoffSummary =
|
|
105
|
-
handoffs.length === 1
|
|
106
|
-
? `model handoff to ${
|
|
106
|
+
handoffs.length === 1 && onlyHandoff
|
|
107
|
+
? `model handoff to ${onlyHandoff.modelId} (${onlyHandoff.modelProfile})`
|
|
107
108
|
: handoffs.length > 1
|
|
108
109
|
? countLabel(handoffs.length, "model handoff", "model handoffs")
|
|
109
110
|
: undefined;
|
|
@@ -55,8 +55,6 @@ export function transcriptFailureTitle(
|
|
|
55
55
|
case "agent_run_failed":
|
|
56
56
|
return "Internal error";
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
return "Internal error";
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
/** Short description for one failed turn in the transcript. */
|
|
@@ -111,8 +109,6 @@ export function transcriptFailureDescription(
|
|
|
111
109
|
case "agent_run_failed":
|
|
112
110
|
return `${agentName} hit an internal error during this turn.`;
|
|
113
111
|
}
|
|
114
|
-
|
|
115
|
-
return `${agentName} hit an internal error during this turn.`;
|
|
116
112
|
}
|
|
117
113
|
|
|
118
114
|
/** Search text for one failed turn in the transcript. */
|
|
@@ -88,6 +88,7 @@ function findNextMarkdownLink(
|
|
|
88
88
|
start: linkStart,
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
return undefined;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
function findMarkdownLinkSyntaxRanges(
|
|
@@ -148,6 +149,7 @@ function readMarkdownDestination(
|
|
|
148
149
|
const href = text.slice(start, index);
|
|
149
150
|
return href ? { end: index + 1, href } : undefined;
|
|
150
151
|
}
|
|
152
|
+
return undefined;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
function findCommonMarkAutolinks(
|
|
@@ -199,6 +201,7 @@ function findNextBareLink(
|
|
|
199
201
|
start: match.index,
|
|
200
202
|
};
|
|
201
203
|
}
|
|
204
|
+
return undefined;
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
function findBareLinks(
|
|
@@ -141,10 +141,10 @@ export function AutomationOutcomeFields(props: {
|
|
|
141
141
|
disabled={index === 0}
|
|
142
142
|
onClick={() => {
|
|
143
143
|
const next = [...props.value];
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
];
|
|
144
|
+
const previous = next[index - 1];
|
|
145
|
+
if (!previous) return;
|
|
146
|
+
next[index - 1] = outcome;
|
|
147
|
+
next[index] = previous;
|
|
148
148
|
props.onChange(next);
|
|
149
149
|
}}
|
|
150
150
|
>
|
|
@@ -53,7 +53,7 @@ export function scheduleDraft(
|
|
|
53
53
|
...fields,
|
|
54
54
|
timezone: value.schedule.timezone,
|
|
55
55
|
time: `${String(time.hour).padStart(2, "0")}:${String(time.minute).padStart(2, "0")}`,
|
|
56
|
-
weekdays: days?.
|
|
56
|
+
weekdays: days?.flatMap((day) => weekdays[day] ?? []),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
if (value.nextRunAtMs) {
|
|
@@ -23,6 +23,12 @@ const toneColors = {
|
|
|
23
23
|
warning: "#fbbf24",
|
|
24
24
|
} as const;
|
|
25
25
|
|
|
26
|
+
function seriesColor(series: Widget["series"][number], index: number): string {
|
|
27
|
+
return series.tone
|
|
28
|
+
? toneColors[series.tone]
|
|
29
|
+
: (colors[index % colors.length] ?? colors[0]);
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
/** Render a validated plugin-owned categorical bar chart. */
|
|
27
33
|
export function PluginBarChart(props: {
|
|
28
34
|
range?: TimeRangeDays;
|
|
@@ -69,9 +75,7 @@ export function PluginBarChart(props: {
|
|
|
69
75
|
<ChartLegend
|
|
70
76
|
ariaLabel="Chart legend"
|
|
71
77
|
items={widget.series.map((series, index) => ({
|
|
72
|
-
color: series
|
|
73
|
-
? toneColors[series.tone]
|
|
74
|
-
: colors[index % colors.length],
|
|
78
|
+
color: seriesColor(series, index),
|
|
75
79
|
key: series.key,
|
|
76
80
|
label: series.label,
|
|
77
81
|
}))}
|
|
@@ -122,11 +126,7 @@ export function PluginBarChart(props: {
|
|
|
122
126
|
>
|
|
123
127
|
<rect
|
|
124
128
|
aria-label={`${formatActivityCategoryTooltipLabel(category.label)}, ${series.label}: ${formatted}`}
|
|
125
|
-
fill={
|
|
126
|
-
series.tone
|
|
127
|
-
? toneColors[series.tone]
|
|
128
|
-
: colors[seriesIndex % colors.length]
|
|
129
|
-
}
|
|
129
|
+
fill={seriesColor(series, seriesIndex)}
|
|
130
130
|
height={renderedHeight}
|
|
131
131
|
opacity={value ? 0.82 : 0.1}
|
|
132
132
|
rx="1.5"
|
package/src/client.tsx
CHANGED
|
@@ -22,18 +22,18 @@ class DashboardErrorBoundary extends Component<
|
|
|
22
22
|
{ children: ReactNode },
|
|
23
23
|
ErrorBoundaryState
|
|
24
24
|
> {
|
|
25
|
-
state: ErrorBoundaryState = { error: null };
|
|
25
|
+
override state: ErrorBoundaryState = { error: null };
|
|
26
26
|
|
|
27
27
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
28
28
|
return { error };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
31
|
+
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
32
32
|
const stack = error.stack ?? errorInfo.componentStack;
|
|
33
33
|
window.__JUNIOR_DASHBOARD_SHOW_ERROR__?.(stack ? new Error(stack) : error);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
render() {
|
|
36
|
+
override render() {
|
|
37
37
|
if (this.state.error) {
|
|
38
38
|
return <DashboardErrorPanel error={this.state.error} />;
|
|
39
39
|
}
|