@sentry/junior-dashboard 0.62.0 → 0.63.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.
@@ -0,0 +1,16 @@
1
+ type ToolInvocationRef = {
2
+ id?: string;
3
+ name?: string;
4
+ };
5
+
6
+ /** Match tool call/result refs without inferring relationships from missing metadata. */
7
+ export function sameToolInvocation(
8
+ left: ToolInvocationRef,
9
+ right: ToolInvocationRef,
10
+ ): boolean {
11
+ if (left.id || right.id) {
12
+ return Boolean(left.id && right.id && left.id === right.id);
13
+ }
14
+ if (left.name && right.name) return left.name === right.name;
15
+ return false;
16
+ }
@@ -1,119 +1,59 @@
1
1
  import type { BundledLanguage } from "shiki/bundle/web";
2
+ import type {
3
+ DashboardConversationReport,
4
+ DashboardRequesterIdentity,
5
+ DashboardSessionFeed,
6
+ DashboardSessionReport,
7
+ DashboardTurnReport,
8
+ DashboardTurnUsage,
9
+ HealthReport,
10
+ PluginReport,
11
+ RuntimeInfoReport,
12
+ SkillReport,
13
+ } from "@sentry/junior/reporting";
2
14
 
3
- export type Health = { service: string; status: string; timestamp: string };
15
+ export type Health = HealthReport;
4
16
 
5
- export type Runtime = {
6
- cwd: string;
7
- descriptionText?: string;
8
- homeDir: string;
9
- packagedContent: { packageNames: string[] };
10
- };
17
+ export type Runtime = RuntimeInfoReport;
11
18
 
12
- export type Plugin = { name: string };
19
+ export type Plugin = PluginReport;
13
20
 
14
- export type Skill = { name: string; pluginProvider?: string };
21
+ export type Skill = SkillReport;
15
22
 
16
- export type RequesterIdentity = {
17
- email?: string;
18
- fullName?: string;
19
- slackUserId?: string;
20
- slackUserName?: string;
21
- };
23
+ export type RequesterIdentity = DashboardRequesterIdentity;
22
24
 
23
- export type TurnUsage = {
24
- cachedInputTokens?: number;
25
- cacheCreationTokens?: number;
26
- inputTokens?: number;
27
- outputTokens?: number;
28
- totalTokens?: number;
29
- };
25
+ export type TurnUsage = DashboardTurnUsage;
30
26
 
31
- export type Session = {
32
- channel?: string;
33
- channelName?: string;
34
- conversationId?: string;
35
- conversationTitle?: string;
36
- cumulativeDurationMs?: number;
37
- cumulativeUsage?: TurnUsage;
38
- id: string;
39
- lastProgressAt?: string;
40
- lastSeenAt?: string;
41
- requester?: string;
42
- requesterIdentity?: RequesterIdentity;
43
- sentryConversationUrl?: string;
44
- sentryTraceUrl?: string;
45
- startedAt?: string;
46
- status: string;
47
- surface?: string;
48
- title?: string;
49
- traceId?: string;
50
- };
27
+ export type Session = DashboardSessionReport;
51
28
 
52
- export type TranscriptPart = {
53
- bytes?: number;
54
- chars?: number;
55
- id?: string;
56
- input?: unknown;
57
- inputKeys?: string[];
58
- inputSizeBytes?: number;
59
- inputSizeChars?: number;
60
- inputType?: string;
61
- name?: string;
62
- output?: unknown;
63
- outputKeys?: string[];
64
- outputSizeBytes?: number;
65
- outputSizeChars?: number;
66
- outputType?: string;
67
- redacted?: boolean;
68
- text?: string;
69
- type: string;
70
- };
29
+ export type TranscriptPart =
30
+ DashboardTurnReport["transcript"][number]["parts"][number];
71
31
 
72
- export type TranscriptMessage = {
73
- parts: TranscriptPart[];
74
- role: string;
75
- timestamp?: number;
76
- };
32
+ export type TranscriptMessage = DashboardTurnReport["transcript"][number];
77
33
 
78
- export type ConversationTurn = Session & {
79
- transcript: TranscriptMessage[];
80
- transcriptAvailable: boolean;
81
- transcriptMetadata?: TranscriptMessage[];
82
- transcriptMessageCount?: number;
83
- transcriptRedacted?: boolean;
84
- transcriptRedactionReason?: "non_public_conversation";
85
- };
34
+ export type ConversationTurn = DashboardTurnReport;
86
35
 
87
- export type ConversationDetailFeed = {
88
- conversationId: string;
89
- generatedAt: string;
90
- turns: ConversationTurn[];
91
- };
36
+ export type ConversationDetailFeed = DashboardConversationReport;
92
37
 
93
38
  export type Conversation = {
94
39
  channel?: string;
95
40
  channelName?: string;
96
41
  conversationTitle?: string;
97
42
  id: string;
98
- lastProgressAt?: string;
99
- lastSeenAt?: string;
100
- requester?: string;
43
+ lastProgressAt: string;
44
+ lastSeenAt: string;
101
45
  requesterIdentity?: RequesterIdentity;
102
46
  sentryConversationUrl?: string;
103
47
  sentryTraceUrl?: string;
104
- startedAt?: string;
48
+ startedAt: string;
105
49
  status: Session["status"];
106
- surface?: string;
50
+ surface: Session["surface"];
107
51
  title: string;
108
52
  traceId?: string;
109
53
  turns: Session[];
110
54
  };
111
55
 
112
- export type SessionFeed = {
113
- generatedAt?: string;
114
- sessions: Session[];
115
- source: string;
116
- };
56
+ export type SessionFeed = DashboardSessionFeed;
117
57
 
118
58
  export type Identity = { user: { email?: string; hostedDomain?: string } };
119
59
 
@@ -141,7 +81,11 @@ export type SessionFilter = "active" | "recent" | "hung" | "failed" | "all";
141
81
 
142
82
  export type VisualStatus = "active" | "failed" | "hung" | "idle";
143
83
 
144
- export type CodeBlock = { code: string; fenced?: boolean; language: BundledLanguage };
84
+ export type CodeBlock = {
85
+ code: string;
86
+ fenced?: boolean;
87
+ language: BundledLanguage;
88
+ };
145
89
 
146
90
  export type MarkupNode =
147
91
  | {
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ import {
2
+ type AgentPluginRoute,
3
+ defineJuniorPlugin,
4
+ type JuniorPlugin,
5
+ } from "@sentry/junior-plugin-api";
6
+ import { buildDashboardConversationURL, normalizeDashboardPath } from "./url";
7
+ import { createDashboardApp, type JuniorDashboardOptions } from "./app";
8
+
9
+ export { createDashboardApp, type JuniorDashboardOptions } from "./app";
10
+
11
+ export interface JuniorDashboardPluginOptions extends JuniorDashboardOptions {
12
+ disabled?: boolean;
13
+ }
14
+
15
+ function dashboardRoutePaths(options: JuniorDashboardPluginOptions): string[] {
16
+ const basePath = normalizeDashboardPath(options.basePath, "/");
17
+ const authPath = normalizeDashboardPath(options.authPath, "/api/auth");
18
+ const pagePaths =
19
+ basePath === "/"
20
+ ? ["/", "/conversations", "/conversations/*", "/sessions", "/sessions/*"]
21
+ : [basePath, `${basePath}/*`];
22
+
23
+ return [
24
+ ...pagePaths,
25
+ "/favicon.ico",
26
+ "/api/dashboard/*",
27
+ authPath,
28
+ `${authPath}/*`,
29
+ ];
30
+ }
31
+
32
+ function dashboardRoutes(
33
+ options: JuniorDashboardPluginOptions,
34
+ ): AgentPluginRoute[] {
35
+ let app: ReturnType<typeof createDashboardApp> | undefined;
36
+ const fetch = (request: Request) => {
37
+ app ??= createDashboardApp(options);
38
+ return app.fetch(request);
39
+ };
40
+
41
+ return dashboardRoutePaths(options).map((path) => ({
42
+ handler: fetch,
43
+ path,
44
+ }));
45
+ }
46
+
47
+ /** Register dashboard routes and Slack footer links through trusted plugin hooks. */
48
+ export function juniorDashboardPlugin(
49
+ options: JuniorDashboardPluginOptions = {},
50
+ ): JuniorPlugin {
51
+ return defineJuniorPlugin({
52
+ name: "dashboard",
53
+ hooks: {
54
+ routes() {
55
+ if (options.disabled) {
56
+ return [];
57
+ }
58
+ return dashboardRoutes(options);
59
+ },
60
+ slackConversationLink(ctx) {
61
+ if (options.disabled) {
62
+ return undefined;
63
+ }
64
+ return {
65
+ url: buildDashboardConversationURL({
66
+ basePath: options.basePath,
67
+ baseURL: options.baseURL,
68
+ conversationId: ctx.conversationId,
69
+ }),
70
+ };
71
+ },
72
+ },
73
+ });
74
+ }
package/src/nitro.ts CHANGED
@@ -104,7 +104,12 @@ function dashboardPageRoutes(
104
104
  };
105
105
  }
106
106
 
107
- /** Mount the authenticated Junior dashboard into a Nitro deployment. */
107
+ /**
108
+ * Mount the authenticated Junior dashboard into a Nitro deployment.
109
+ *
110
+ * @deprecated Register `juniorDashboardPlugin()` in `createApp({ plugins })`;
111
+ * this helper remains for existing Nitro apps.
112
+ */
108
113
  export function juniorDashboardNitro(options: JuniorDashboardNitroOptions): {
109
114
  nitro: { setup(nitro: unknown): void };
110
115
  } {
package/src/url.ts ADDED
@@ -0,0 +1,68 @@
1
+ export interface DashboardBaseURLConfig {
2
+ baseURL?: string;
3
+ }
4
+
5
+ export interface DashboardConversationURLConfig extends DashboardBaseURLConfig {
6
+ basePath?: string;
7
+ conversationId: string;
8
+ }
9
+
10
+ function withHttps(host: string): string {
11
+ return /^https?:\/\//.test(host) ? host : `https://${host}`;
12
+ }
13
+
14
+ function stripTrailingSlashes(value: string): string {
15
+ let end = value.length;
16
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
17
+ end -= 1;
18
+ }
19
+ return end === value.length ? value : value.slice(0, end);
20
+ }
21
+
22
+ /** Normalize dashboard route prefixes so plugin and Nitro paths agree. */
23
+ export function normalizeDashboardPath(
24
+ path: string | undefined,
25
+ fallback: string,
26
+ ): string {
27
+ const value = path?.trim() || fallback;
28
+ const withSlash = value.startsWith("/") ? value : `/${value}`;
29
+ return stripTrailingSlashes(withSlash);
30
+ }
31
+
32
+ /** Resolve the dashboard origin used for browser auth and external links. */
33
+ export function resolveDashboardBaseURL(
34
+ config: DashboardBaseURLConfig = {},
35
+ ): string {
36
+ const explicit =
37
+ config.baseURL ??
38
+ process.env.BETTER_AUTH_URL ??
39
+ process.env.JUNIOR_BASE_URL;
40
+ if (explicit?.trim()) {
41
+ return stripTrailingSlashes(withHttps(explicit.trim()));
42
+ }
43
+
44
+ const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
45
+ if (vercelProd) {
46
+ return stripTrailingSlashes(withHttps(vercelProd));
47
+ }
48
+
49
+ const vercelUrl = process.env.VERCEL_URL?.trim();
50
+ if (vercelUrl) {
51
+ return stripTrailingSlashes(withHttps(vercelUrl));
52
+ }
53
+
54
+ return "http://localhost:3000";
55
+ }
56
+
57
+ /** Build the dashboard conversation URL shown from outside the browser app. */
58
+ export function buildDashboardConversationURL(
59
+ config: DashboardConversationURLConfig,
60
+ ): string {
61
+ const baseURL = resolveDashboardBaseURL({ baseURL: config.baseURL });
62
+ const basePath = normalizeDashboardPath(config.basePath, "/");
63
+ const path =
64
+ basePath === "/"
65
+ ? `/conversations/${encodeURIComponent(config.conversationId)}`
66
+ : `${basePath}/conversations/${encodeURIComponent(config.conversationId)}`;
67
+ return `${baseURL}${path}`;
68
+ }