@sentry/junior 0.77.0 → 0.79.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.
@@ -2,8 +2,9 @@ import {
2
2
  getConversation,
3
3
  listConversationsByActivity,
4
4
  recordConversationActivity,
5
+ recordConversationExecution,
5
6
  requestConversationWork
6
- } from "../chunk-KPL4WJWA.js";
7
+ } from "../chunk-LUNMJQ7D.js";
7
8
  import {
8
9
  JUNIOR_THREAD_STATE_TTL_MS,
9
10
  coerceThreadConversationState
@@ -22,7 +23,7 @@ import {
22
23
  createJuniorSqlExecutor,
23
24
  createSqlStore,
24
25
  getDb
25
- } from "../chunk-NYKJ3KON.js";
26
+ } from "../chunk-237T7XAN.js";
26
27
  import {
27
28
  disconnectStateAdapter,
28
29
  getConnectedStateContext
@@ -75,8 +76,7 @@ function createStateConversationStore(state) {
75
76
  return {
76
77
  get: (args) => getConversation({ ...args, state }),
77
78
  recordActivity: (args) => recordConversationActivity({ ...args, state }),
78
- recordExecution: async () => {
79
- },
79
+ recordExecution: (args) => recordConversationExecution({ ...args, state }),
80
80
  listByActivity: (args) => listConversationsByActivity({ ...args, state })
81
81
  };
82
82
  }
@@ -2,7 +2,7 @@ import {
2
2
  closeDb,
3
3
  getConversationStore,
4
4
  getDb
5
- } from "./chunk-NYKJ3KON.js";
5
+ } from "./chunk-237T7XAN.js";
6
6
  import "./chunk-Q6XFTRV5.js";
7
7
  import "./chunk-T77LUIX3.js";
8
8
  import "./chunk-VALUBQ7R.js";
package/dist/nitro.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { type JuniorPluginSet } from "./plugins";
2
+ import type { JuniorDashboardOptions } from "./app";
3
+ export type JuniorNitroDashboardOptions = Omit<JuniorDashboardOptions, "reporting">;
2
4
  export interface JuniorPluginModuleReference {
3
5
  /** Runtime-safe module that exports a `defineJuniorPlugins(...)` set. */
4
6
  module: string;
@@ -8,6 +10,8 @@ export interface JuniorPluginModuleReference {
8
10
  export type JuniorNitroPluginSource = JuniorPluginModuleReference | JuniorPluginSet | string;
9
11
  export interface JuniorNitroOptions {
10
12
  cwd?: string;
13
+ /** Authenticated dashboard configuration injected for createApp(). */
14
+ dashboard?: JuniorNitroDashboardOptions;
11
15
  maxDuration?: number;
12
16
  /** Vercel Queue topic for durable conversation work. Must match the runtime queue producer topic. */
13
17
  conversationWorkQueueTopic?: string;
package/dist/nitro.js CHANGED
@@ -54,6 +54,8 @@ function globToRegex(pattern) {
54
54
  }
55
55
 
56
56
  // src/build/copy-build-content.ts
57
+ var DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
58
+ var DASHBOARD_ASSET_NAMES = ["client.js", "tailwind.css"];
57
59
  function copyAppAndPluginContent(cwd, serverRoot, packageNames) {
58
60
  copyIfExists(path.join(cwd, "app"), path.join(serverRoot, "app"));
59
61
  const packagedContent = discoverInstalledPluginPackageContent(cwd, {
@@ -83,6 +85,27 @@ function copyAppAndPluginContent(cwd, serverRoot, packageNames) {
83
85
  }
84
86
  }
85
87
  }
88
+ function copyDashboardAssets(cwd, serverRoot) {
89
+ const packageDir = resolvePackageDir(cwd, DASHBOARD_PACKAGE_NAME);
90
+ if (!packageDir) {
91
+ throw new Error(
92
+ `createApp({ dashboard }) requires installing "${DASHBOARD_PACKAGE_NAME}"`
93
+ );
94
+ }
95
+ for (const fileName of DASHBOARD_ASSET_NAMES) {
96
+ const source = dashboardAssetPath(packageDir, fileName);
97
+ copyIfExists(
98
+ source,
99
+ path.join(
100
+ serverRoot,
101
+ "node_modules",
102
+ DASHBOARD_PACKAGE_NAME,
103
+ "dist",
104
+ fileName
105
+ )
106
+ );
107
+ }
108
+ }
86
109
  function copyIncludedFiles(cwd, serverRoot, patterns) {
87
110
  if (patterns === void 0) return;
88
111
  if (!Array.isArray(patterns)) {
@@ -170,6 +193,20 @@ function copyIfExists(source, target) {
170
193
  cpSync(source, target, { recursive: true });
171
194
  return true;
172
195
  }
196
+ function dashboardAssetPath(packageDir, fileName) {
197
+ const candidates = [
198
+ path.join(packageDir, fileName),
199
+ path.join(packageDir, "dist", fileName)
200
+ ];
201
+ for (const candidate of candidates) {
202
+ if (existsSync(candidate)) {
203
+ return candidate;
204
+ }
205
+ }
206
+ throw new Error(
207
+ `Junior dashboard asset ${fileName} was not built; run pnpm --filter @sentry/junior-dashboard build before building Nitro`
208
+ );
209
+ }
173
210
  function copyRootIntoServerOutput(cwd, serverRoot, root) {
174
211
  copyIfExists(root, resolveServerOutputPath(cwd, serverRoot, root));
175
212
  }
@@ -205,14 +242,25 @@ function renderRuntimePluginImport(module) {
205
242
  }
206
243
  return `import { ${module.exportName} as juniorRuntimePluginSet } from ${JSON.stringify(module.specifier)};`;
207
244
  }
245
+ function renderDashboardImport(enabled) {
246
+ return enabled ? [
247
+ 'import { createDashboardApp as juniorCreateDashboardApp } from "@sentry/junior-dashboard";',
248
+ "export const createDashboardApp = juniorCreateDashboardApp;"
249
+ ] : ["export const createDashboardApp = undefined;"];
250
+ }
251
+ function dashboardEnabled(dashboard) {
252
+ return Boolean(dashboard && !dashboard.disabled);
253
+ }
208
254
  function renderVirtualConfig(options) {
209
255
  const lines = [
256
+ ...renderDashboardImport(dashboardEnabled(options.dashboard)),
210
257
  ...options.pluginModule ? [
211
258
  renderRuntimePluginImport(options.pluginModule),
212
259
  "export const pluginSet = juniorRuntimePluginSet;"
213
260
  ] : ["export const pluginSet = undefined;"],
214
261
  `export const plugins = ${JSON.stringify(options.plugins ?? { packages: [] })};`,
215
- `export const pluginRuntimeRegistrations = ${JSON.stringify(options.pluginRuntimeRegistrations ?? [])};`
262
+ `export const pluginRuntimeRegistrations = ${JSON.stringify(options.pluginRuntimeRegistrations ?? [])};`,
263
+ `export const dashboard = ${JSON.stringify(options.dashboard)};`
216
264
  ];
217
265
  return lines.join("\n");
218
266
  }
@@ -227,7 +275,8 @@ function injectVirtualConfig(nitro, options = {}) {
227
275
  plugins: pluginCatalogConfigFromPluginSet(pluginSet),
228
276
  pluginRuntimeRegistrations: pluginRuntimeRegistrationsFromPluginSet(
229
277
  pluginSet
230
- ).map((plugin) => plugin.manifest.name)
278
+ ).map((plugin) => plugin.manifest.name),
279
+ dashboard: options.dashboard
231
280
  });
232
281
  };
233
282
  }
@@ -255,6 +304,9 @@ function assertSerializableDirectPluginSet(pluginSet) {
255
304
  `juniorNitro({ plugins }) cannot receive a direct defineJuniorPlugins(...) set with runtime plugin registration(s): ${pluginRuntimeNames.join(", ")}. Export the set from a runtime-safe plugin module and pass juniorNitro({ plugins: "./plugins" }) so createApp() can import the same registrations at runtime.`
256
305
  );
257
306
  }
307
+ function dashboardEnabled2(dashboard) {
308
+ return Boolean(dashboard && !dashboard.disabled);
309
+ }
258
310
  function runtimeModuleForResolvedPluginModule(moduleRef) {
259
311
  return {
260
312
  exportName: moduleRef.exportName,
@@ -356,7 +408,8 @@ function juniorNitro(options = {}) {
356
408
  pluginModule: runtimeModuleForResolvedPluginModule(pluginModule)
357
409
  } : {},
358
410
  plugins: pluginCatalogConfig,
359
- pluginRuntimeRegistrations
411
+ pluginRuntimeRegistrations,
412
+ dashboard: options.dashboard
360
413
  });
361
414
  const copyBuildContent = async () => {
362
415
  const pluginSet = await loadConfiguredPluginSet();
@@ -366,6 +419,9 @@ function juniorNitro(options = {}) {
366
419
  nitro.options.output.serverDir,
367
420
  compiledPluginCatalogConfig?.packages
368
421
  );
422
+ if (dashboardEnabled2(options.dashboard)) {
423
+ copyDashboardAssets(cwd, nitro.options.output.serverDir);
424
+ }
369
425
  copyIncludedFiles(
370
426
  cwd,
371
427
  nitro.options.output.serverDir,
@@ -67,6 +67,7 @@ export interface TranscriptMessage {
67
67
  timestamp?: number;
68
68
  }
69
69
  export interface ConversationRunReport extends ConversationSummaryReport {
70
+ activity?: ConversationActivityReport[];
70
71
  transcriptAvailable: boolean;
71
72
  transcriptMetadata?: TranscriptMessage[];
72
73
  transcriptMessageCount?: number;
@@ -74,6 +75,35 @@ export interface ConversationRunReport extends ConversationSummaryReport {
74
75
  transcriptRedactionReason?: "non_public_conversation";
75
76
  transcript: TranscriptMessage[];
76
77
  }
78
+ export type ConversationActivityStatus = "aborted" | "completed" | "error" | "running" | "success";
79
+ interface ActivityPayloadMetadata {
80
+ inputKeys?: string[];
81
+ inputSizeBytes?: number;
82
+ inputSizeChars?: number;
83
+ inputType?: string;
84
+ }
85
+ export interface ConversationSubagentActivityReport {
86
+ type: "subagent";
87
+ createdAt: string;
88
+ endedAt?: string;
89
+ id: string;
90
+ outcome?: "success" | "error" | "aborted";
91
+ parentToolCallId?: string;
92
+ status: ConversationActivityStatus;
93
+ subagentKind: string;
94
+ }
95
+ export interface ConversationToolActivityReport extends ActivityPayloadMetadata {
96
+ type: "tool_execution";
97
+ args?: unknown;
98
+ createdAt: string;
99
+ id: string;
100
+ redacted?: boolean;
101
+ status: ConversationActivityStatus;
102
+ subagents: ConversationSubagentActivityReport[];
103
+ toolCallId: string;
104
+ toolName: string;
105
+ }
106
+ export type ConversationActivityReport = ConversationToolActivityReport | ConversationSubagentActivityReport;
77
107
  export interface ConversationReport {
78
108
  conversationId: string;
79
109
  /** Always-populated display title, computed the same way as per-run reports. */
@@ -1,6 +1,6 @@
1
1
  import type { PluginOperationalReport } from "@sentry/junior-plugin-api";
2
2
  import { type ConversationFeed, type PluginConversationSummary, type ConversationReport, type ConversationStatsReport } from "./reporting/conversations";
3
- export type { PluginConversationStatus, PluginConversations, PluginConversationSummary, ConversationFeed, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationStatsItem, ConversationStatsReport, ConversationSummaryReport, ConversationSurface, ConversationUsage, RequesterIdentity, TranscriptMessage, TranscriptPart, TranscriptPartType, TranscriptRole, } from "./reporting/conversations";
3
+ export type { PluginConversationStatus, PluginConversations, PluginConversationSummary, ConversationActivityReport, ConversationActivityStatus, ConversationFeed, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSubagentActivityReport, ConversationStatsItem, ConversationStatsReport, ConversationSummaryReport, ConversationSurface, ConversationToolActivityReport, ConversationUsage, RequesterIdentity, TranscriptMessage, TranscriptPart, TranscriptPartType, TranscriptRole, } from "./reporting/conversations";
4
4
  export interface HealthReport {
5
5
  status: "ok";
6
6
  service: string;
package/dist/reporting.js CHANGED
@@ -10,15 +10,16 @@ import {
10
10
  import {
11
11
  buildSystemPrompt,
12
12
  getAgentTurnSessionRecord,
13
- listAgentTurnSessionSummariesForConversation
14
- } from "./chunk-TO3UAY2M.js";
13
+ listAgentTurnSessionSummariesForConversation,
14
+ loadActivityEntries
15
+ } from "./chunk-R5T7QY3P.js";
15
16
  import {
16
17
  getPluginOperationalReports
17
- } from "./chunk-WBSGTHNO.js";
18
+ } from "./chunk-SSWBYEFH.js";
18
19
  import "./chunk-RARSKPVT.js";
19
20
  import {
20
21
  getConversationStore
21
- } from "./chunk-NYKJ3KON.js";
22
+ } from "./chunk-237T7XAN.js";
22
23
  import "./chunk-G3E7SCME.js";
23
24
  import "./chunk-LXTPBU4K.js";
24
25
  import "./chunk-Q6XFTRV5.js";
@@ -163,6 +164,9 @@ function statusFromConversation(conversation, fallback, nowMs) {
163
164
  if (conversation.execution.status === "idle") {
164
165
  return "completed";
165
166
  }
167
+ if (conversation.execution.status === "failed") {
168
+ return "failed";
169
+ }
166
170
  const updatedAtMs = conversation.execution.updatedAtMs ?? conversation.updatedAtMs;
167
171
  if (conversation.execution.status === "running" && nowMs - updatedAtMs > HUNG_TURN_PROGRESS_MS) {
168
172
  return "hung";
@@ -662,6 +666,86 @@ function redactTranscriptMessage(message) {
662
666
  parts: message.parts.map(redactTranscriptPart)
663
667
  };
664
668
  }
669
+ function toolResultStatuses(messages) {
670
+ const statuses = /* @__PURE__ */ new Map();
671
+ for (const message of messages) {
672
+ const record = message;
673
+ if (record.role !== "toolResult" || typeof record.toolCallId !== "string") {
674
+ continue;
675
+ }
676
+ statuses.set(record.toolCallId, record.isError ? "error" : "completed");
677
+ }
678
+ return statuses;
679
+ }
680
+ function activityPayloadFields(args, canExposePayload) {
681
+ if (args === void 0) {
682
+ return {};
683
+ }
684
+ return canExposePayload ? { args } : { redacted: true, ...redactedPayloadFields("input", args) };
685
+ }
686
+ function subagentActivity(entry, options) {
687
+ const end = options.end;
688
+ return {
689
+ type: "subagent",
690
+ id: entry.subagentInvocationId,
691
+ subagentKind: entry.subagentKind,
692
+ ...entry.parentToolCallId ? { parentToolCallId: entry.parentToolCallId } : {},
693
+ createdAt: new Date(entry.createdAtMs).toISOString(),
694
+ ...end ? {
695
+ endedAt: new Date(end.createdAtMs).toISOString(),
696
+ outcome: end.outcome,
697
+ status: end.outcome
698
+ } : { status: options.parentStatus ?? "running" }
699
+ };
700
+ }
701
+ function buildConversationActivity(args) {
702
+ const toolStatuses = toolResultStatuses(args.messages);
703
+ const subagentEnds = /* @__PURE__ */ new Map();
704
+ const subagentsByToolCallId = /* @__PURE__ */ new Map();
705
+ const orphanSubagents = [];
706
+ for (const entry of args.entries) {
707
+ if (entry.type === "subagent_ended") {
708
+ subagentEnds.set(entry.subagentInvocationId, entry);
709
+ }
710
+ }
711
+ for (const entry of args.entries) {
712
+ if (entry.type !== "subagent_started") {
713
+ continue;
714
+ }
715
+ const parentStatus = entry.parentToolCallId ? toolStatuses.get(entry.parentToolCallId) : void 0;
716
+ const activity = subagentActivity(entry, {
717
+ end: subagentEnds.get(entry.subagentInvocationId),
718
+ parentStatus
719
+ });
720
+ if (entry.parentToolCallId) {
721
+ subagentsByToolCallId.set(entry.parentToolCallId, [
722
+ ...subagentsByToolCallId.get(entry.parentToolCallId) ?? [],
723
+ activity
724
+ ]);
725
+ continue;
726
+ }
727
+ orphanSubagents.push(activity);
728
+ }
729
+ const rows = [];
730
+ for (const entry of args.entries) {
731
+ if (entry.type !== "tool_execution_started") {
732
+ continue;
733
+ }
734
+ rows.push({
735
+ type: "tool_execution",
736
+ id: entry.toolCallId,
737
+ toolCallId: entry.toolCallId,
738
+ toolName: entry.toolName,
739
+ createdAt: new Date(entry.createdAtMs).toISOString(),
740
+ status: toolStatuses.get(entry.toolCallId) ?? "running",
741
+ subagents: subagentsByToolCallId.get(entry.toolCallId) ?? [],
742
+ ...activityPayloadFields(entry.args, args.canExposePayload)
743
+ });
744
+ }
745
+ return [...rows, ...orphanSubagents].sort(
746
+ (left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt) || left.id.localeCompare(right.id)
747
+ );
748
+ }
665
749
  function isConversationMessageRole(role) {
666
750
  return role === "user" || role === "assistant";
667
751
  }
@@ -796,22 +880,8 @@ async function readConversationStatsReport(options = {}) {
796
880
  });
797
881
  const truncated = conversations.length > CONVERSATION_STATS_LIMIT;
798
882
  const sampledConversations = conversations.slice(0, CONVERSATION_STATS_LIMIT);
799
- const detailsByConversationId = await getConversationDetailsForIds(
800
- sampledConversations.map((conversation) => conversation.conversationId)
801
- );
802
- const reportsByConversation = await reportsFromConversations({
803
- conversations: sampledConversations,
804
- detailsByConversationId,
805
- nowMs
806
- });
807
- const sessions = sampledConversations.flatMap(
808
- (conversation) => reportsByConversation.get(conversation.conversationId) ?? [
809
- sessionReportFromConversation(
810
- conversation,
811
- nowMs,
812
- detailsByConversationId.get(conversation.conversationId)
813
- )
814
- ]
883
+ const sessions = sampledConversations.map(
884
+ (conversation) => sessionReportFromConversation(conversation, nowMs)
815
885
  );
816
886
  return buildConversationStatsReport({
817
887
  generatedAt,
@@ -875,10 +945,13 @@ async function readConversationReport(conversationId, options = {}) {
875
945
  );
876
946
  const runs = await Promise.all(
877
947
  summaries.map(async (summary) => {
878
- const sessionRecord = await getAgentTurnSessionRecord(
879
- summary.conversationId,
880
- summary.sessionId
881
- );
948
+ const [sessionRecord, activityEntries] = await Promise.all([
949
+ getAgentTurnSessionRecord(summary.conversationId, summary.sessionId),
950
+ loadActivityEntries({
951
+ conversationId: summary.conversationId,
952
+ sessionId: summary.sessionId
953
+ })
954
+ ]);
882
955
  const scopedMessages = sessionRecord?.piMessages ? turnScopedMessages(
883
956
  sessionRecord.piMessages,
884
957
  sessionRecord.turnStartMessageIndex
@@ -887,6 +960,11 @@ async function readConversationReport(conversationId, options = {}) {
887
960
  const normalizedTranscript = scopedMessages.messages.map(
888
961
  normalizeTranscriptMessage
889
962
  );
963
+ const activity = buildConversationActivity({
964
+ canExposePayload: canExposeTranscript,
965
+ entries: activityEntries,
966
+ messages: scopedMessages.messages
967
+ });
890
968
  const transcriptMessageCount = countConversationMessages(normalizedTranscript);
891
969
  const transcript = canExposeTranscript ? [
892
970
  ...scopedMessages.startsAtRunBoundary && normalizedTranscript.length > 0 && sessionRecord?.source ? [systemPromptMessage(sessionRecord.source)] : [],
@@ -899,6 +977,7 @@ async function readConversationReport(conversationId, options = {}) {
899
977
  ...sessionReportFromSummary(summary, nowMs, details),
900
978
  ...traceId ? { traceId } : {},
901
979
  ...sentryTraceUrl ? { sentryTraceUrl } : {},
980
+ activity,
902
981
  transcriptAvailable: Boolean(sessionRecord) && canExposeTranscript,
903
982
  ...sessionRecord && transcriptMessageCount > 0 ? { transcriptMessageCount } : {},
904
983
  ...!canExposeTranscript ? {
@@ -922,6 +1001,7 @@ async function readConversationReport(conversationId, options = {}) {
922
1001
  const effectiveRuns = runs.length > 0 || !conversation ? runs : [
923
1002
  {
924
1003
  ...sessionReportFromConversation(conversation, nowMs, details),
1004
+ activity: [],
925
1005
  transcriptAvailable: false,
926
1006
  transcript: []
927
1007
  }
@@ -14,7 +14,7 @@ import {
14
14
  startActiveTurn,
15
15
  updateConversationStats,
16
16
  upsertConversationMessage
17
- } from "./chunk-W36B5PT4.js";
17
+ } from "./chunk-NNM7YQLL.js";
18
18
  import {
19
19
  coerceThreadConversationState
20
20
  } from "./chunk-Z4CIQ3EB.js";
@@ -23,10 +23,10 @@ import "./chunk-KNFROR7R.js";
23
23
  import {
24
24
  commitMessages,
25
25
  loadProjection
26
- } from "./chunk-TO3UAY2M.js";
27
- import "./chunk-WBSGTHNO.js";
26
+ } from "./chunk-R5T7QY3P.js";
27
+ import "./chunk-SSWBYEFH.js";
28
28
  import "./chunk-RARSKPVT.js";
29
- import "./chunk-NYKJ3KON.js";
29
+ import "./chunk-237T7XAN.js";
30
30
  import "./chunk-G3E7SCME.js";
31
31
  import "./chunk-LXTPBU4K.js";
32
32
  import "./chunk-Q6XFTRV5.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.77.0",
3
+ "version": "0.79.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -70,7 +70,7 @@
70
70
  "pg": "^8.16.3",
71
71
  "yaml": "^2.9.0",
72
72
  "zod": "^4.4.3",
73
- "@sentry/junior-plugin-api": "0.77.0"
73
+ "@sentry/junior-plugin-api": "0.79.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@emnapi/core": "^1.10.0",
@@ -86,9 +86,9 @@
86
86
  "typescript": "^6.0.3",
87
87
  "vercel": "^54.4.0",
88
88
  "vitest": "^4.1.7",
89
- "@sentry/junior-memory": "0.77.0",
90
- "@sentry/junior-testing": "0.0.0",
91
- "@sentry/junior-scheduler": "0.77.0"
89
+ "@sentry/junior-scheduler": "0.79.0",
90
+ "@sentry/junior-memory": "0.79.0",
91
+ "@sentry/junior-testing": "0.0.0"
92
92
  },
93
93
  "scripts": {
94
94
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",