@sentry/junior 0.66.2 → 0.67.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,7 +2,7 @@ import {
2
2
  SANDBOX_WORKSPACE_ROOT,
3
3
  getStateAdapter,
4
4
  toOptionalTrimmed
5
- } from "./chunk-SAYCFF7O.js";
5
+ } from "./chunk-NWU2Z6SM.js";
6
6
  import {
7
7
  getPluginRuntimeDependencies,
8
8
  getPluginRuntimePostinstall,
@@ -500,6 +500,7 @@ async function completeObject(params) {
500
500
  var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
501
501
  var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
502
502
  var DEFAULT_FUNCTION_MAX_DURATION_SECONDS = 300;
503
+ var DEFAULT_SLACK_SLASH_COMMAND = "/jr";
503
504
  var ADVISOR_THINKING_LEVELS = [
504
505
  "minimal",
505
506
  "low",
@@ -589,6 +590,15 @@ function parseOptionalPositiveInteger(envName, rawValue) {
589
590
  }
590
591
  return value;
591
592
  }
593
+ function parseSlashCommand(rawValue) {
594
+ const command = toOptionalTrimmed(rawValue) ?? DEFAULT_SLACK_SLASH_COMMAND;
595
+ if (!command.startsWith("/") || /\s/.test(command)) {
596
+ throw new Error(
597
+ "JUNIOR_SLASH_COMMAND must start with / and contain no whitespace"
598
+ );
599
+ }
600
+ return command;
601
+ }
592
602
  var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
593
603
  var DEFAULT_FAST_MODEL_ID = getModel(
594
604
  "vercel-ai-gateway",
@@ -638,7 +648,8 @@ function readChatConfig(env = process.env) {
638
648
  botToken: toOptionalTrimmed(env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(env.SLACK_BOT_USER_TOKEN),
639
649
  signingSecret: toOptionalTrimmed(env.SLACK_SIGNING_SECRET),
640
650
  clientId: toOptionalTrimmed(env.SLACK_CLIENT_ID),
641
- clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET)
651
+ clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET),
652
+ slashCommand: parseSlashCommand(env.JUNIOR_SLASH_COMMAND)
642
653
  },
643
654
  state: {
644
655
  adapter: env.JUNIOR_STATE_ADAPTER?.trim().toLowerCase() === "memory" ? "memory" : "redis",
package/dist/cli/init.js CHANGED
@@ -186,6 +186,7 @@ Operational context and domain knowledge for ${name}.
186
186
  SLACK_SIGNING_SECRET=
187
187
  JUNIOR_SECRET=
188
188
  JUNIOR_BOT_NAME=
189
+ JUNIOR_SLASH_COMMAND=
189
190
  AI_MODEL=
190
191
  AI_FAST_MODEL=
191
192
  AI_VISION_MODEL=
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  resolveRuntimeDependencySnapshot
3
- } from "../chunk-JA5QR3N4.js";
3
+ } from "../chunk-KWEE2436.js";
4
4
  import {
5
5
  disconnectStateAdapter
6
- } from "../chunk-SAYCFF7O.js";
6
+ } from "../chunk-NWU2Z6SM.js";
7
7
  import {
8
8
  getPluginProviders,
9
9
  getPluginRuntimeDependencies,
@@ -1,4 +1,6 @@
1
1
  import { getPluginPackageContent } from "@/chat/plugins/registry";
2
+ import { type AgentTurnSurface } from "@/chat/state/turn-session";
3
+ import type { PluginOperationalReport } from "@sentry/junior-plugin-api";
2
4
  export interface HealthReport {
3
5
  status: "ok";
4
6
  service: string;
@@ -20,7 +22,7 @@ export interface RuntimeInfoReport {
20
22
  packagedContent: ReturnType<typeof getPluginPackageContent>;
21
23
  }
22
24
  export type DashboardSessionStatus = "active" | "completed" | "failed" | "hung" | "superseded";
23
- export type DashboardSurface = "slack" | "api" | "scheduler" | "internal";
25
+ export type DashboardSurface = AgentTurnSurface;
24
26
  export interface DashboardTurnUsage {
25
27
  inputTokens?: number;
26
28
  outputTokens?: number;
@@ -99,6 +101,40 @@ export interface DashboardSessionFeed {
99
101
  source: "turn_session_records";
100
102
  generatedAt: string;
101
103
  }
104
+ export interface DashboardConversationStatsItem {
105
+ active: number;
106
+ conversations: number;
107
+ durationMs: number;
108
+ failed: number;
109
+ hung: number;
110
+ label: string;
111
+ tokens?: number;
112
+ turns: number;
113
+ }
114
+ export interface DashboardConversationStatsReport {
115
+ active: number;
116
+ conversations: number;
117
+ durationMs: number;
118
+ failed: number;
119
+ generatedAt: string;
120
+ hung: number;
121
+ locations: DashboardConversationStatsItem[];
122
+ requesters: DashboardConversationStatsItem[];
123
+ sampleLimit: number;
124
+ sampleSize: number;
125
+ source: "turn_session_records";
126
+ tokens?: number;
127
+ truncated: boolean;
128
+ turns: number;
129
+ windowEnd: string;
130
+ windowStart: string;
131
+ }
132
+ export type { PluginOperationalReport } from "@sentry/junior-plugin-api";
133
+ export interface PluginOperationalReportFeed {
134
+ generatedAt: string;
135
+ reports: PluginOperationalReport[];
136
+ source: "trusted_plugins";
137
+ }
102
138
  export interface JuniorReporting {
103
139
  /** Read the public runtime health snapshot without exposing discovery data. */
104
140
  getHealth(): Promise<HealthReport>;
@@ -115,6 +151,10 @@ export interface JuniorReporting {
115
151
  * actor, route, usage, and links that can later be reconstructed from spans.
116
152
  */
117
153
  getSessions(): Promise<DashboardSessionFeed>;
154
+ /** Read aggregate conversation stats for authenticated dashboard views. */
155
+ getConversationStats?(): Promise<DashboardConversationStatsReport>;
156
+ /** Read sanitized operational summaries contributed by trusted plugins. */
157
+ getPluginOperationalReports?(): Promise<PluginOperationalReportFeed>;
118
158
  /**
119
159
  * Read one conversation transcript for the dashboard.
120
160
  *
@@ -125,4 +165,7 @@ export interface JuniorReporting {
125
165
  getConversation(conversationId: string): Promise<DashboardConversationReport>;
126
166
  }
127
167
  /** Create the read-only reporting boundary used by authenticated dashboard routes. */
128
- export declare function createJuniorReporting(): JuniorReporting;
168
+ export declare function createJuniorReporting(): JuniorReporting & {
169
+ getConversationStats(): Promise<DashboardConversationStatsReport>;
170
+ getPluginOperationalReports(): Promise<PluginOperationalReportFeed>;
171
+ };
package/dist/reporting.js CHANGED
@@ -4,11 +4,12 @@ import {
4
4
  buildSentryTraceUrl,
5
5
  buildSystemPrompt,
6
6
  formatSlackConversationRedactedLabel,
7
+ getAgentPluginOperationalReports,
7
8
  getAgentTurnSessionRecord,
8
9
  listAgentTurnSessionSummaries,
9
10
  listAgentTurnSessionSummariesForConversation,
10
11
  resolveSlackConversationContextFromThreadId
11
- } from "./chunk-DG2I6GXC.js";
12
+ } from "./chunk-HFMZE67J.js";
12
13
  import {
13
14
  discoverSkills
14
15
  } from "./chunk-YL5G5YC4.js";
@@ -16,7 +17,7 @@ import {
16
17
  canExposeConversationPayload,
17
18
  parseSlackThreadId,
18
19
  resolveConversationPrivacy
19
- } from "./chunk-SAYCFF7O.js";
20
+ } from "./chunk-NWU2Z6SM.js";
20
21
  import {
21
22
  getPluginPackageContent,
22
23
  getPluginProviders,
@@ -34,6 +35,9 @@ import path from "path";
34
35
  var HUNG_TURN_PROGRESS_MS = 5 * 60 * 1e3;
35
36
  var SAFE_METADATA_KEY_LIMIT = 20;
36
37
  var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
38
+ var DASHBOARD_SESSION_FEED_LIMIT = 50;
39
+ var DASHBOARD_CONVERSATION_STATS_LIMIT = 5e3;
40
+ var RECENT_CONVERSATION_STATS_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
37
41
  function readDescriptionText() {
38
42
  try {
39
43
  const raw = readFileSync(
@@ -61,9 +65,9 @@ async function readPlugins() {
61
65
  name: plugin.manifest.name
62
66
  }));
63
67
  }
64
- function statusFromCheckpoint(summary) {
68
+ function statusFromCheckpoint(summary, nowMs = Date.now()) {
65
69
  const state = summary.state;
66
- if (state === "running" && Date.now() - summary.lastProgressAtMs > HUNG_TURN_PROGRESS_MS) {
70
+ if (state === "running" && nowMs - summary.lastProgressAtMs > HUNG_TURN_PROGRESS_MS) {
67
71
  return "hung";
68
72
  }
69
73
  if (state === "running" || state === "awaiting_resume") {
@@ -75,7 +79,13 @@ function statusFromCheckpoint(summary) {
75
79
  return state;
76
80
  }
77
81
  function surfaceFromConversationId(conversationId) {
78
- return parseSlackThreadId(conversationId) ? "slack" : "internal";
82
+ if (parseSlackThreadId(conversationId)) return "slack";
83
+ if (conversationId.startsWith("scheduler:")) return "scheduler";
84
+ if (conversationId.startsWith("api:")) return "api";
85
+ return "internal";
86
+ }
87
+ function surfaceFromSummary(summary) {
88
+ return summary.surface ?? surfaceFromConversationId(summary.conversationId);
79
89
  }
80
90
  function titleFromSummary(summary) {
81
91
  if (summary.state === "awaiting_resume" && summary.resumeReason) {
@@ -104,7 +114,7 @@ function turnUsageReport(usage) {
104
114
  };
105
115
  return Object.keys(report).length > 0 ? report : void 0;
106
116
  }
107
- function sessionReportFromSummary(summary) {
117
+ function sessionReportFromSummary(summary, nowMs = Date.now()) {
108
118
  const slackThread = parseSlackThreadId(summary.conversationId);
109
119
  const privacy = resolveConversationPrivacy({
110
120
  conversationId: summary.conversationId
@@ -126,14 +136,14 @@ function sessionReportFromSummary(summary) {
126
136
  conversationId: summary.conversationId,
127
137
  ...conversationTitle ? { conversationTitle } : {},
128
138
  id: summary.sessionId,
129
- status: statusFromCheckpoint(summary),
139
+ status: statusFromCheckpoint(summary, nowMs),
130
140
  startedAt: new Date(summary.startedAtMs).toISOString(),
131
141
  lastProgressAt: new Date(summary.lastProgressAtMs).toISOString(),
132
142
  lastSeenAt: new Date(summary.updatedAtMs).toISOString(),
133
143
  ...summary.state === "completed" ? { completedAt: new Date(summary.updatedAtMs).toISOString() } : {},
134
144
  cumulativeDurationMs: summary.cumulativeDurationMs,
135
145
  ...cumulativeUsage ? { cumulativeUsage } : {},
136
- surface: surfaceFromConversationId(summary.conversationId),
146
+ surface: surfaceFromSummary(summary),
137
147
  title: titleFromSummary(summary),
138
148
  ...requesterIdentity ? { requesterIdentity } : {},
139
149
  ...slackThread ? { channel: slackThread.channelId } : {},
@@ -143,6 +153,245 @@ function sessionReportFromSummary(summary) {
143
153
  ...sentryTraceUrl ? { sentryTraceUrl } : {}
144
154
  };
145
155
  }
156
+ function reportTime(value) {
157
+ const time = Date.parse(value);
158
+ return Number.isFinite(time) ? time : void 0;
159
+ }
160
+ function usageTokenTotal(usage) {
161
+ if (!usage) return void 0;
162
+ const components = [
163
+ usage.inputTokens,
164
+ usage.outputTokens,
165
+ usage.cachedInputTokens,
166
+ usage.cacheCreationTokens
167
+ ].reduce((sum, value) => {
168
+ const count = typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
169
+ return count === void 0 ? sum : (sum ?? 0) + count;
170
+ }, void 0);
171
+ if (components !== void 0) {
172
+ return components;
173
+ }
174
+ return typeof usage.totalTokens === "number" && Number.isFinite(usage.totalTokens) ? Math.max(0, Math.floor(usage.totalTokens)) : void 0;
175
+ }
176
+ function turnDurationSnapshot(turn) {
177
+ return typeof turn.cumulativeDurationMs === "number" && Number.isFinite(turn.cumulativeDurationMs) ? Math.max(0, Math.floor(turn.cumulativeDurationMs)) : void 0;
178
+ }
179
+ function turnContributions(turns) {
180
+ let previousDuration = 0;
181
+ let previousTokens = 0;
182
+ return turns.map((turn) => {
183
+ const duration = turnDurationSnapshot(turn);
184
+ const tokens = usageTokenTotal(turn.cumulativeUsage);
185
+ const contribution = {
186
+ durationMs: duration === void 0 ? 0 : Math.max(0, duration - previousDuration),
187
+ turn
188
+ };
189
+ if (tokens !== void 0) {
190
+ contribution.tokens = Math.max(0, tokens - previousTokens);
191
+ }
192
+ if (duration !== void 0) {
193
+ previousDuration = Math.max(previousDuration, duration);
194
+ }
195
+ if (tokens !== void 0) {
196
+ previousTokens = Math.max(previousTokens, tokens);
197
+ }
198
+ return contribution;
199
+ });
200
+ }
201
+ function contributionDurationTotal(contributions) {
202
+ return contributions.reduce(
203
+ (sum, contribution) => sum + contribution.durationMs,
204
+ 0
205
+ );
206
+ }
207
+ function addTokenTotal(total, tokens) {
208
+ return tokens === void 0 ? total : (total ?? 0) + tokens;
209
+ }
210
+ function contributionTokenTotal(contributions) {
211
+ return contributions.reduce(
212
+ (sum, contribution) => addTokenTotal(sum, contribution.tokens),
213
+ void 0
214
+ );
215
+ }
216
+ function requesterLabel(requester) {
217
+ const email = requester?.email?.trim() || void 0;
218
+ const fullName = requester?.fullName?.trim() || void 0;
219
+ const slackUserName = requester?.slackUserName?.trim() || void 0;
220
+ return email ?? fullName ?? slackUserName ?? requester?.slackUserId;
221
+ }
222
+ function slackStatsLocationLabel(input) {
223
+ const channelId = input.channel;
224
+ if (!channelId) return void 0;
225
+ const name = input.channelName?.replace(/^#/, "");
226
+ if (channelId.startsWith("D")) {
227
+ return "Direct Message";
228
+ }
229
+ if (channelId.startsWith("C")) {
230
+ return name ? `#${name}` : "Public Channel";
231
+ }
232
+ if (channelId.startsWith("G")) {
233
+ if (name?.startsWith("mpdm-")) return "Group DM";
234
+ return "Private Channel";
235
+ }
236
+ return name || channelId;
237
+ }
238
+ function locationLabel(turn) {
239
+ return slackStatsLocationLabel(turn) ?? (turn.surface === "scheduler" ? "Scheduler" : turn.surface === "api" ? "API" : turn.surface === "internal" ? "Internal" : "Unknown");
240
+ }
241
+ function emptyStatsItem(label) {
242
+ return {
243
+ active: 0,
244
+ conversations: 0,
245
+ durationMs: 0,
246
+ failed: 0,
247
+ hung: 0,
248
+ label,
249
+ turns: 0
250
+ };
251
+ }
252
+ function addItemTokens(item, tokens) {
253
+ if (tokens !== void 0) {
254
+ item.tokens = (item.tokens ?? 0) + tokens;
255
+ }
256
+ }
257
+ function statusSignals(turns) {
258
+ return {
259
+ active: turns.some((turn) => turn.status === "active"),
260
+ failed: turns.some((turn) => turn.status === "failed"),
261
+ hung: turns.some((turn) => turn.status === "hung")
262
+ };
263
+ }
264
+ function statsItems(map) {
265
+ return [...map.values()].sort(
266
+ (left, right) => right.conversations - left.conversations || right.turns - left.turns || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
267
+ );
268
+ }
269
+ function newestTurn(turns) {
270
+ return [...turns].sort(
271
+ (left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.id.localeCompare(left.id)
272
+ )[0];
273
+ }
274
+ function recentConversationGroups(args) {
275
+ const startMs = args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS;
276
+ const groups = /* @__PURE__ */ new Map();
277
+ for (const session of args.sessions) {
278
+ groups.set(session.conversationId, [
279
+ ...groups.get(session.conversationId) ?? [],
280
+ session
281
+ ]);
282
+ }
283
+ return [...groups.values()].map(
284
+ (turns) => [...turns].sort(
285
+ (left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
286
+ )
287
+ ).filter((turns) => {
288
+ const activityAt = reportTime(newestTurn(turns).lastSeenAt);
289
+ return activityAt !== void 0 && activityAt >= startMs && activityAt <= args.nowMs;
290
+ });
291
+ }
292
+ function conversationDurationMs(turns) {
293
+ if (!turns.some((turn) => turnDurationSnapshot(turn) !== void 0)) {
294
+ return 0;
295
+ }
296
+ return contributionDurationTotal(turnContributions(turns));
297
+ }
298
+ function buildConversationStatsReport(args) {
299
+ const conversations = recentConversationGroups(args);
300
+ const requesters = /* @__PURE__ */ new Map();
301
+ const locations = /* @__PURE__ */ new Map();
302
+ let durationMs = 0;
303
+ let tokens;
304
+ let active = 0;
305
+ let failed = 0;
306
+ let hung = 0;
307
+ for (const turns of conversations) {
308
+ const contributions = turnContributions(turns);
309
+ const conversationSignals = statusSignals(turns);
310
+ const conversationTokens = contributionTokenTotal(contributions);
311
+ durationMs += contributionDurationTotal(contributions);
312
+ tokens = addTokenTotal(tokens, conversationTokens);
313
+ active += conversationSignals.active ? 1 : 0;
314
+ failed += conversationSignals.failed ? 1 : 0;
315
+ hung += conversationSignals.hung ? 1 : 0;
316
+ const requesterTurns = /* @__PURE__ */ new Map();
317
+ for (const contribution of contributions) {
318
+ const requester = requesterLabel(contribution.turn.requesterIdentity) ?? "Unknown";
319
+ requesterTurns.set(requester, [
320
+ ...requesterTurns.get(requester) ?? [],
321
+ contribution
322
+ ]);
323
+ }
324
+ for (const [requester, requesterContributions] of requesterTurns) {
325
+ const item = requesters.get(requester) ?? emptyStatsItem(requester);
326
+ const signals = statusSignals(
327
+ requesterContributions.map((contribution) => contribution.turn)
328
+ );
329
+ item.conversations += 1;
330
+ item.turns += requesterContributions.length;
331
+ item.durationMs += contributionDurationTotal(requesterContributions);
332
+ item.active += signals.active ? 1 : 0;
333
+ item.failed += signals.failed ? 1 : 0;
334
+ item.hung += signals.hung ? 1 : 0;
335
+ addItemTokens(item, contributionTokenTotal(requesterContributions));
336
+ requesters.set(requester, item);
337
+ }
338
+ const location = locationLabel(newestTurn(turns));
339
+ const locationItem = locations.get(location) ?? emptyStatsItem(location);
340
+ locationItem.conversations += 1;
341
+ locationItem.turns += turns.length;
342
+ locationItem.durationMs += conversationDurationMs(turns);
343
+ locationItem.active += conversationSignals.active ? 1 : 0;
344
+ locationItem.failed += conversationSignals.failed ? 1 : 0;
345
+ locationItem.hung += conversationSignals.hung ? 1 : 0;
346
+ addItemTokens(locationItem, conversationTokens);
347
+ locations.set(location, locationItem);
348
+ }
349
+ return {
350
+ active,
351
+ conversations: conversations.length,
352
+ durationMs,
353
+ failed,
354
+ generatedAt: args.generatedAt,
355
+ hung,
356
+ locations: statsItems(locations),
357
+ requesters: statsItems(requesters),
358
+ sampleLimit: args.sampleLimit,
359
+ sampleSize: args.sampleSize,
360
+ source: "turn_session_records",
361
+ ...tokens !== void 0 ? { tokens } : {},
362
+ truncated: args.truncated,
363
+ turns: conversations.reduce((sum, turns) => sum + turns.length, 0),
364
+ windowEnd: new Date(args.nowMs).toISOString(),
365
+ windowStart: new Date(
366
+ args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS
367
+ ).toISOString()
368
+ };
369
+ }
370
+ async function completeSampledConversationSummaries(args) {
371
+ if (!args.truncated) {
372
+ return args.summaries;
373
+ }
374
+ const conversationIds = [
375
+ ...new Set(args.summaries.map((summary) => summary.conversationId))
376
+ ];
377
+ const groups = await Promise.all(
378
+ conversationIds.map(
379
+ (conversationId) => listAgentTurnSessionSummariesForConversation(conversationId)
380
+ )
381
+ );
382
+ const summariesByTurn = /* @__PURE__ */ new Map();
383
+ for (const group of groups) {
384
+ for (const summary of group) {
385
+ summariesByTurn.set(
386
+ `${summary.conversationId}:${summary.sessionId}`,
387
+ summary
388
+ );
389
+ }
390
+ }
391
+ return [...summariesByTurn.values()].sort(
392
+ (left, right) => right.updatedAtMs - left.updatedAtMs
393
+ );
394
+ }
146
395
  function canExposeConversationTranscript(summary) {
147
396
  return canExposeConversationPayload({
148
397
  conversationId: summary.conversationId
@@ -365,11 +614,50 @@ function traceIdFromTranscript(transcript) {
365
614
  return void 0;
366
615
  }
367
616
  async function readSessions() {
368
- const summaries = await listAgentTurnSessionSummaries(50);
617
+ const nowMs = Date.now();
618
+ const summaries = await listAgentTurnSessionSummaries(
619
+ DASHBOARD_SESSION_FEED_LIMIT
620
+ );
369
621
  return {
370
622
  source: "turn_session_records",
371
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
372
- sessions: summaries.map(sessionReportFromSummary)
623
+ generatedAt: new Date(nowMs).toISOString(),
624
+ sessions: summaries.map(
625
+ (summary) => sessionReportFromSummary(summary, nowMs)
626
+ )
627
+ };
628
+ }
629
+ async function readConversationStats() {
630
+ const nowMs = Date.now();
631
+ const generatedAt = new Date(nowMs).toISOString();
632
+ const summaries = await listAgentTurnSessionSummaries(
633
+ DASHBOARD_CONVERSATION_STATS_LIMIT + 1
634
+ );
635
+ const truncated = summaries.length >= DASHBOARD_CONVERSATION_STATS_LIMIT;
636
+ const sampledSummaries = summaries.slice(
637
+ 0,
638
+ DASHBOARD_CONVERSATION_STATS_LIMIT
639
+ );
640
+ const reportSummaries = await completeSampledConversationSummaries({
641
+ summaries: sampledSummaries,
642
+ truncated
643
+ });
644
+ return buildConversationStatsReport({
645
+ generatedAt,
646
+ nowMs,
647
+ sampleLimit: DASHBOARD_CONVERSATION_STATS_LIMIT,
648
+ sampleSize: sampledSummaries.length,
649
+ sessions: reportSummaries.map(
650
+ (summary) => sessionReportFromSummary(summary, nowMs)
651
+ ),
652
+ truncated
653
+ });
654
+ }
655
+ async function readPluginOperationalReports() {
656
+ const nowMs = Date.now();
657
+ return {
658
+ source: "trusted_plugins",
659
+ generatedAt: new Date(nowMs).toISOString(),
660
+ reports: await getAgentPluginOperationalReports(nowMs)
373
661
  };
374
662
  }
375
663
  async function readConversation(conversationId) {
@@ -436,6 +724,8 @@ function createJuniorReporting() {
436
724
  getPlugins: readPlugins,
437
725
  getSkills: readSkills,
438
726
  getSessions: readSessions,
727
+ getConversationStats: readConversationStats,
728
+ getPluginOperationalReports: readPluginOperationalReports,
439
729
  getConversation: readConversation
440
730
  };
441
731
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.66.2",
3
+ "version": "0.67.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -65,7 +65,7 @@
65
65
  "node-html-markdown": "^2.0.0",
66
66
  "yaml": "^2.9.0",
67
67
  "zod": "^4.4.3",
68
- "@sentry/junior-plugin-api": "0.66.2"
68
+ "@sentry/junior-plugin-api": "0.67.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.9.1",
@@ -77,7 +77,7 @@
77
77
  "typescript": "^6.0.3",
78
78
  "vercel": "^54.4.0",
79
79
  "vitest": "^4.1.7",
80
- "@sentry/junior-scheduler": "0.66.2"
80
+ "@sentry/junior-scheduler": "0.67.0"
81
81
  },
82
82
  "scripts": {
83
83
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",