@sentry/junior 0.68.0 → 0.70.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.
Files changed (67) hide show
  1. package/dist/app.js +1779 -746
  2. package/dist/build/virtual-config.d.ts +2 -2
  3. package/dist/chat/agent-dispatch/heartbeat.d.ts +2 -2
  4. package/dist/chat/agent-dispatch/store.d.ts +4 -1
  5. package/dist/chat/agent-dispatch/types.d.ts +2 -4
  6. package/dist/chat/agent-dispatch/validation.d.ts +3 -2
  7. package/dist/chat/credentials/context.d.ts +49 -24
  8. package/dist/chat/credentials/user-token-store.d.ts +6 -0
  9. package/dist/chat/destination.d.ts +12 -0
  10. package/dist/chat/ingress/message-router.d.ts +1 -1
  11. package/dist/chat/mcp/auth-store.d.ts +2 -0
  12. package/dist/chat/mcp/oauth.d.ts +2 -0
  13. package/dist/chat/oauth-flow.d.ts +7 -0
  14. package/dist/chat/plugins/agent-hooks.d.ts +9 -9
  15. package/dist/chat/plugins/auth/auth-token-placeholder.d.ts +2 -2
  16. package/dist/chat/plugins/auth/oauth-request.d.ts +3 -1
  17. package/dist/chat/plugins/credential-hooks.d.ts +53 -0
  18. package/dist/chat/plugins/logging.d.ts +1 -1
  19. package/dist/chat/plugins/state.d.ts +1 -1
  20. package/dist/chat/plugins/types.d.ts +19 -23
  21. package/dist/chat/respond.d.ts +2 -0
  22. package/dist/chat/runtime/reply-executor.d.ts +3 -1
  23. package/dist/chat/runtime/slack-runtime.d.ts +8 -3
  24. package/dist/chat/sandbox/egress-credentials.d.ts +34 -0
  25. package/dist/chat/sandbox/egress-schemas.d.ts +105 -0
  26. package/dist/chat/sandbox/egress-session.d.ts +17 -17
  27. package/dist/chat/sandbox/sandbox.d.ts +3 -0
  28. package/dist/chat/sandbox/session.d.ts +1 -0
  29. package/dist/chat/services/mcp-auth-orchestration.d.ts +2 -0
  30. package/dist/chat/services/pending-auth.d.ts +2 -0
  31. package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -0
  32. package/dist/chat/services/provider-retry.d.ts +13 -4
  33. package/dist/chat/services/timeout-resume.d.ts +2 -0
  34. package/dist/chat/services/turn-session-record.d.ts +6 -0
  35. package/dist/chat/slack/attachment-fetchers.d.ts +11 -0
  36. package/dist/chat/state/conversation-details.d.ts +46 -0
  37. package/dist/chat/state/conversation.d.ts +1 -0
  38. package/dist/chat/state/turn-session.d.ts +4 -3
  39. package/dist/chat/task-execution/queue.d.ts +2 -0
  40. package/dist/chat/task-execution/store.d.ts +5 -0
  41. package/dist/chat/task-execution/vercel-callback.d.ts +4 -0
  42. package/dist/chat/task-execution/vercel-queue.d.ts +2 -0
  43. package/dist/chat/task-execution/worker.d.ts +4 -2
  44. package/dist/chat/tools/slack/context.d.ts +3 -0
  45. package/dist/chat/tools/types.d.ts +21 -2
  46. package/dist/chunk-76YMBKW7.js +326 -0
  47. package/dist/{chunk-PIVOJIUD.js → chunk-B5HKWWQB.js} +9 -5
  48. package/dist/chunk-BBXYXOJW.js +1858 -0
  49. package/dist/{chunk-V47RLIO2.js → chunk-GT67ZWZQ.js} +4 -4
  50. package/dist/{chunk-UQQSW7QB.js → chunk-HOGQL2H6.js} +197 -343
  51. package/dist/{chunk-75UZ4JLC.js → chunk-IGLNC5H6.js} +21 -9
  52. package/dist/{chunk-EBVQXCD2.js → chunk-JS4HURDT.js} +362 -280
  53. package/dist/chunk-R62YWUNO.js +264 -0
  54. package/dist/{chunk-OIIXZOOC.js → chunk-UXG6TU2U.js} +311 -2015
  55. package/dist/cli/check.js +4 -4
  56. package/dist/cli/init.js +18 -1
  57. package/dist/cli/snapshot-warmup.js +5 -4
  58. package/dist/nitro.d.ts +1 -1
  59. package/dist/nitro.js +21 -19
  60. package/dist/plugins.d.ts +2 -2
  61. package/dist/reporting.d.ts +8 -4
  62. package/dist/reporting.js +72 -29
  63. package/package.json +6 -4
  64. package/dist/chat/plugins/auth/github-app-broker.d.ts +0 -4
  65. package/dist/chat/plugins/github-permissions.d.ts +0 -11
  66. package/dist/chat/queue/thread-message-dispatcher.d.ts +0 -33
  67. package/dist/chunk-KVZL5NZS.js +0 -519
package/dist/cli/check.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  parseSkillFile
3
- } from "../chunk-V47RLIO2.js";
3
+ } from "../chunk-GT67ZWZQ.js";
4
4
  import {
5
5
  parseInlinePluginManifest,
6
6
  parsePluginManifest
7
- } from "../chunk-OIIXZOOC.js";
8
- import "../chunk-Z3YD6NHK.js";
7
+ } from "../chunk-UXG6TU2U.js";
9
8
  import {
10
9
  JUNIOR_CONVERSATION_WORK_CALLBACK_ROUTE,
11
10
  JUNIOR_HEARTBEAT_ROUTE,
12
11
  LEGACY_JUNIOR_CONVERSATION_WORK_FUNCTION
13
12
  } from "../chunk-6YY4Q3D4.js";
14
- import "../chunk-KVZL5NZS.js";
13
+ import "../chunk-BBXYXOJW.js";
14
+ import "../chunk-Z3YD6NHK.js";
15
15
  import "../chunk-2KG3PWR4.js";
16
16
 
17
17
  // src/cli/check.ts
package/dist/cli/init.js CHANGED
@@ -31,6 +31,17 @@ export const POST = (request: Request) => app.fetch(request);
31
31
  `
32
32
  );
33
33
  }
34
+ function writePluginsFile(targetDir) {
35
+ fs.writeFileSync(
36
+ path.join(targetDir, "plugins.ts"),
37
+ `import { defineJuniorPlugins } from "@sentry/junior";
38
+
39
+ export const plugins = defineJuniorPlugins([
40
+ "@sentry/junior-maintenance",
41
+ ]);
42
+ `
43
+ );
44
+ }
34
45
  function writeNitroConfig(targetDir) {
35
46
  fs.writeFileSync(
36
47
  path.join(targetDir, "nitro.config.ts"),
@@ -39,7 +50,11 @@ import { juniorNitro } from "@sentry/junior/nitro";
39
50
 
40
51
  export default defineConfig({
41
52
  preset: "vercel",
42
- modules: [juniorNitro()],
53
+ modules: [
54
+ juniorNitro({
55
+ plugins: "./plugins",
56
+ }),
57
+ ],
43
58
  routes: {
44
59
  "/**": { handler: "./server.ts" },
45
60
  },
@@ -129,6 +144,7 @@ async function runInit(dir, log = console.log) {
129
144
  },
130
145
  dependencies: {
131
146
  "@sentry/junior": "latest",
147
+ "@sentry/junior-maintenance": "latest",
132
148
  hono: "^4.12.0"
133
149
  },
134
150
  devDependencies: {
@@ -199,6 +215,7 @@ SENTRY_ORG_SLUG=
199
215
  );
200
216
  writeServerEntry(target);
201
217
  writeQueueConsumerEntry(target);
218
+ writePluginsFile(target);
202
219
  writeNitroConfig(target);
203
220
  writeViteConfig(target);
204
221
  writeVercelJson(target);
@@ -1,16 +1,17 @@
1
1
  import {
2
2
  resolveRuntimeDependencySnapshot
3
- } from "../chunk-PIVOJIUD.js";
3
+ } from "../chunk-B5HKWWQB.js";
4
4
  import {
5
5
  disconnectStateAdapter
6
- } from "../chunk-EBVQXCD2.js";
6
+ } from "../chunk-R62YWUNO.js";
7
7
  import {
8
8
  getPluginProviders,
9
9
  getPluginRuntimeDependencies,
10
10
  getPluginRuntimePostinstall
11
- } from "../chunk-OIIXZOOC.js";
11
+ } from "../chunk-UXG6TU2U.js";
12
+ import "../chunk-JS4HURDT.js";
13
+ import "../chunk-BBXYXOJW.js";
12
14
  import "../chunk-Z3YD6NHK.js";
13
- import "../chunk-KVZL5NZS.js";
14
15
  import "../chunk-2KG3PWR4.js";
15
16
 
16
17
  // src/cli/snapshot-warmup.ts
package/dist/nitro.d.ts CHANGED
@@ -11,7 +11,7 @@ export interface JuniorNitroOptions {
11
11
  maxDuration?: number;
12
12
  /** Vercel Queue topic for durable conversation work. Must match the runtime queue producer topic. */
13
13
  conversationWorkQueueTopic?: string;
14
- /** Plugin catalog set or runtime-safe plugin module. Direct sets must not include trusted hooks. */
14
+ /** Plugin catalog set or runtime-safe plugin module. Direct sets must not include runtime hooks. */
15
15
  plugins?: JuniorNitroPluginSource;
16
16
  /**
17
17
  * Extra file patterns to copy into the server output for files that the
package/dist/nitro.js CHANGED
@@ -1,18 +1,21 @@
1
1
  import {
2
- DEFAULT_CONVERSATION_WORK_QUEUE_TOPIC,
3
2
  pluginCatalogConfigFromPluginSet,
4
- trustedPluginRegistrationsFromPluginSet
5
- } from "./chunk-75UZ4JLC.js";
3
+ pluginHookRegistrationsFromPluginSet,
4
+ resolveConversationWorkQueueTopic
5
+ } from "./chunk-IGLNC5H6.js";
6
+ import "./chunk-76YMBKW7.js";
6
7
  import {
7
8
  JUNIOR_CONVERSATION_WORK_CALLBACK_ROUTE,
8
9
  JUNIOR_HEARTBEAT_CRON_SCHEDULE,
9
10
  JUNIOR_HEARTBEAT_ROUTE
10
11
  } from "./chunk-6YY4Q3D4.js";
12
+ import "./chunk-JS4HURDT.js";
11
13
  import {
12
14
  discoverInstalledPluginPackageContent,
13
15
  isValidPackageName,
14
16
  resolvePackageDir
15
- } from "./chunk-KVZL5NZS.js";
17
+ } from "./chunk-BBXYXOJW.js";
18
+ import "./chunk-Z3YD6NHK.js";
16
19
  import "./chunk-2KG3PWR4.js";
17
20
 
18
21
  // src/nitro.ts
@@ -192,7 +195,7 @@ function renderVirtualConfig(options) {
192
195
  "export const pluginSet = juniorRuntimePluginSet;"
193
196
  ] : ["export const pluginSet = undefined;"],
194
197
  `export const plugins = ${JSON.stringify(options.plugins ?? { packages: [] })};`,
195
- `export const trustedPluginRegistrations = ${JSON.stringify(options.trustedPluginRegistrations ?? [])};`
198
+ `export const pluginHookRegistrations = ${JSON.stringify(options.pluginHookRegistrations ?? [])};`
196
199
  ];
197
200
  return lines.join("\n");
198
201
  }
@@ -205,7 +208,7 @@ function injectVirtualConfig(nitro, options = {}) {
205
208
  return renderVirtualConfig({
206
209
  pluginModule: options.pluginModule,
207
210
  plugins: pluginCatalogConfigFromPluginSet(pluginSet),
208
- trustedPluginRegistrations: trustedPluginRegistrationsFromPluginSet(
211
+ pluginHookRegistrations: pluginHookRegistrationsFromPluginSet(
209
212
  pluginSet
210
213
  ).map((plugin) => plugin.name)
211
214
  });
@@ -300,19 +303,16 @@ async function loadPluginSetFromModule(moduleRef) {
300
303
  );
301
304
  }
302
305
  function assertSerializableDirectPluginSet(pluginSet) {
303
- const trustedPluginNames = trustedPluginRegistrationsFromPluginSet(
304
- pluginSet
305
- ).map((plugin) => plugin.name);
306
- if (trustedPluginNames.length === 0) {
306
+ const pluginHookNames = pluginHookRegistrationsFromPluginSet(pluginSet).map(
307
+ (plugin) => plugin.name
308
+ );
309
+ if (pluginHookNames.length === 0) {
307
310
  return;
308
311
  }
309
312
  throw new Error(
310
- `juniorNitro({ plugins }) cannot receive a direct defineJuniorPlugins(...) set with trusted plugin registration(s): ${trustedPluginNames.join(", ")}. Export the set from a runtime-safe plugin module and pass juniorNitro({ plugins: "./plugins" }) so createApp() can import the same hooks at runtime.`
313
+ `juniorNitro({ plugins }) cannot receive a direct defineJuniorPlugins(...) set with runtime hook registration(s): ${pluginHookNames.join(", ")}. Export the set from a runtime-safe plugin module and pass juniorNitro({ plugins: "./plugins" }) so createApp() can import the same hooks at runtime.`
311
314
  );
312
315
  }
313
- function resolveConversationWorkQueueTopic(options) {
314
- return options.conversationWorkQueueTopic?.trim() || process.env.JUNIOR_CONVERSATION_WORK_QUEUE_TOPIC?.trim() || DEFAULT_CONVERSATION_WORK_QUEUE_TOPIC;
315
- }
316
316
  function bundleOpenTelemetryLoaderHooks(nitro) {
317
317
  const existing = Array.isArray(nitro.options.noExternals) ? nitro.options.noExternals : [];
318
318
  const additions = ["import-in-the-middle", "require-in-the-middle"].filter(
@@ -324,7 +324,9 @@ function bundleOpenTelemetryLoaderHooks(nitro) {
324
324
  }
325
325
  function configureVercelDeployment(nitro, options) {
326
326
  const defaultMaxDuration = options.maxDuration ?? DEFAULT_FUNCTION_MAX_DURATION_SECONDS;
327
- const queueTopic = resolveConversationWorkQueueTopic(options);
327
+ const queueTopic = resolveConversationWorkQueueTopic({
328
+ topic: options.conversationWorkQueueTopic
329
+ });
328
330
  nitro.options.vercel ??= {};
329
331
  nitro.options.vercel.config ??= { version: 3 };
330
332
  nitro.options.vercel.config.crons ??= [];
@@ -379,16 +381,16 @@ function juniorNitro(options = {}) {
379
381
  return pluginSetPromise;
380
382
  };
381
383
  const pluginCatalogConfig = pluginCatalogConfigFromPluginSet(directPluginSet);
382
- const trustedPluginRegistrations = trustedPluginRegistrationsFromPluginSet(directPluginSet).map(
383
- (plugin) => plugin.name
384
- );
384
+ const pluginHookRegistrations = pluginHookRegistrationsFromPluginSet(
385
+ directPluginSet
386
+ ).map((plugin) => plugin.name);
385
387
  injectVirtualConfig(nitro, {
386
388
  ...pluginModule ? {
387
389
  loadPluginSet: loadConfiguredPluginSet,
388
390
  pluginModule: pluginModule.runtimeModule
389
391
  } : {},
390
392
  plugins: pluginCatalogConfig,
391
- trustedPluginRegistrations
393
+ pluginHookRegistrations
392
394
  });
393
395
  const copyBuildContent = async () => {
394
396
  const pluginSet = await loadConfiguredPluginSet();
package/dist/plugins.d.ts CHANGED
@@ -18,5 +18,5 @@ export interface JuniorPluginSet {
18
18
  export declare function defineJuniorPlugins(inputs: JuniorPluginInput[], options?: JuniorPluginSetOptions): JuniorPluginSet;
19
19
  /** Build the manifest catalog config implied by one plugin set. */
20
20
  export declare function pluginCatalogConfigFromPluginSet(pluginSet: JuniorPluginSet | undefined): PluginCatalogConfig | undefined;
21
- /** Return registrations that expose trusted in-process runtime behavior. */
22
- export declare function trustedPluginRegistrationsFromPluginSet(pluginSet: JuniorPluginSet | undefined): JuniorPluginRegistration[];
21
+ /** Return registrations that expose in-process runtime hooks. */
22
+ export declare function pluginHookRegistrationsFromPluginSet(pluginSet: JuniorPluginSet | undefined): JuniorPluginRegistration[];
@@ -37,7 +37,10 @@ export interface DashboardRequesterIdentity {
37
37
  slackUserName?: string;
38
38
  }
39
39
  export interface DashboardSessionReport {
40
- conversationTitle?: string;
40
+ /** Always-populated display title. LLM-generated title when available, otherwise the
41
+ * Slack channel/conversation location label or a generic fallback. Privacy redaction
42
+ * wins over everything else for non-public conversations. */
43
+ displayTitle: string;
41
44
  cumulativeDurationMs: number;
42
45
  cumulativeUsage?: DashboardTurnUsage;
43
46
  conversationId: string;
@@ -48,7 +51,6 @@ export interface DashboardSessionReport {
48
51
  lastProgressAt: string;
49
52
  completedAt?: string;
50
53
  surface: DashboardSurface;
51
- title: string;
52
54
  requesterIdentity?: DashboardRequesterIdentity;
53
55
  channel?: string;
54
56
  channelName?: string;
@@ -93,6 +95,8 @@ export interface DashboardTurnReport extends DashboardSessionReport {
93
95
  }
94
96
  export interface DashboardConversationReport {
95
97
  conversationId: string;
98
+ /** Always-populated display title, computed the same way as DashboardSessionReport.displayTitle. */
99
+ displayTitle: string;
96
100
  generatedAt: string;
97
101
  turns: DashboardTurnReport[];
98
102
  }
@@ -133,7 +137,7 @@ export type { PluginOperationalReport } from "@sentry/junior-plugin-api";
133
137
  export interface PluginOperationalReportFeed {
134
138
  generatedAt: string;
135
139
  reports: PluginOperationalReport[];
136
- source: "trusted_plugins";
140
+ source: "plugins";
137
141
  }
138
142
  export interface JuniorReporting {
139
143
  /** Read the public runtime health snapshot without exposing discovery data. */
@@ -153,7 +157,7 @@ export interface JuniorReporting {
153
157
  getSessions(): Promise<DashboardSessionFeed>;
154
158
  /** Read aggregate conversation stats for authenticated dashboard views. */
155
159
  getConversationStats?(): Promise<DashboardConversationStatsReport>;
156
- /** Read sanitized operational summaries contributed by trusted plugins. */
160
+ /** Read sanitized operational summaries contributed by plugins. */
157
161
  getPluginOperationalReports?(): Promise<PluginOperationalReportFeed>;
158
162
  /**
159
163
  * Read one conversation transcript for the dashboard.
package/dist/reporting.js CHANGED
@@ -6,27 +6,31 @@ import {
6
6
  formatSlackConversationRedactedLabel,
7
7
  getAgentPluginOperationalReports,
8
8
  getAgentTurnSessionRecord,
9
+ getConversationDetails,
10
+ getConversationDetailsForIds,
9
11
  listAgentTurnSessionSummaries,
10
12
  listAgentTurnSessionSummariesForConversation,
11
13
  resolveSlackConversationContextFromThreadId
12
- } from "./chunk-UQQSW7QB.js";
14
+ } from "./chunk-HOGQL2H6.js";
13
15
  import {
14
16
  discoverSkills
15
- } from "./chunk-V47RLIO2.js";
17
+ } from "./chunk-GT67ZWZQ.js";
18
+ import "./chunk-R62YWUNO.js";
19
+ import {
20
+ getPluginPackageContent,
21
+ getPluginProviders
22
+ } from "./chunk-UXG6TU2U.js";
23
+ import "./chunk-76YMBKW7.js";
16
24
  import {
17
25
  canExposeConversationPayload,
18
26
  parseSlackThreadId,
19
27
  resolveConversationPrivacy
20
- } from "./chunk-EBVQXCD2.js";
28
+ } from "./chunk-JS4HURDT.js";
21
29
  import {
22
- getPluginPackageContent,
23
- getPluginProviders,
30
+ homeDir,
24
31
  isRecord
25
- } from "./chunk-OIIXZOOC.js";
32
+ } from "./chunk-BBXYXOJW.js";
26
33
  import "./chunk-Z3YD6NHK.js";
27
- import {
28
- homeDir
29
- } from "./chunk-KVZL5NZS.js";
30
34
  import "./chunk-2KG3PWR4.js";
31
35
 
32
36
  // src/reporting.ts
@@ -87,12 +91,6 @@ function surfaceFromConversationId(conversationId) {
87
91
  function surfaceFromSummary(summary) {
88
92
  return summary.surface ?? surfaceFromConversationId(summary.conversationId);
89
93
  }
90
- function titleFromSummary(summary) {
91
- if (summary.state === "awaiting_resume" && summary.resumeReason) {
92
- return `Awaiting ${summary.resumeReason} resume`;
93
- }
94
- return `Turn ${summary.sessionId}`;
95
- }
96
94
  function requesterIdentityReport(requester) {
97
95
  if (!requester) return void 0;
98
96
  const identity = {
@@ -114,27 +112,33 @@ function turnUsageReport(usage) {
114
112
  };
115
113
  return Object.keys(report).length > 0 ? report : void 0;
116
114
  }
117
- function sessionReportFromSummary(summary, nowMs = Date.now()) {
115
+ function sessionReportFromSummary(summary, nowMs = Date.now(), details) {
118
116
  const slackThread = parseSlackThreadId(summary.conversationId);
119
117
  const privacy = resolveConversationPrivacy({
120
118
  conversationId: summary.conversationId
121
119
  });
120
+ const effectiveChannelName = details?.channelName ?? summary.channelName;
122
121
  const slackConversation = resolveSlackConversationContextFromThreadId({
123
122
  threadId: summary.conversationId,
124
- channelName: summary.channelName
123
+ channelName: effectiveChannelName
125
124
  });
126
125
  const privateLabel = privacy !== "public" ? slackConversation ? formatSlackConversationRedactedLabel(slackConversation) : PRIVATE_CONVERSATION_LABEL : void 0;
127
- const conversationTitle = privateLabel ?? summary.conversationTitle;
128
- const channelName = privateLabel ?? summary.channelName;
126
+ const channelName = privateLabel ?? effectiveChannelName;
127
+ const effectiveSurface = details?.originSurface ?? surfaceFromSummary(summary);
128
+ const displayTitle = privateLabel ?? details?.displayTitle ?? slackStatsLocationLabel({
129
+ channel: slackThread?.channelId,
130
+ channelName: effectiveChannelName
131
+ }) ?? surfaceFallbackLabel(effectiveSurface);
132
+ const effectiveRequester = details?.originRequester ?? summary.requester;
129
133
  const sentryConversationUrl = buildSentryConversationUrl(
130
134
  summary.conversationId
131
135
  );
132
136
  const sentryTraceUrl = summary.traceId ? buildSentryTraceUrl(summary.traceId) : void 0;
133
- const requesterIdentity = requesterIdentityReport(summary.requester);
137
+ const requesterIdentity = requesterIdentityReport(effectiveRequester);
134
138
  const cumulativeUsage = turnUsageReport(summary.cumulativeUsage);
135
139
  return {
136
140
  conversationId: summary.conversationId,
137
- ...conversationTitle ? { conversationTitle } : {},
141
+ displayTitle,
138
142
  id: summary.sessionId,
139
143
  status: statusFromCheckpoint(summary, nowMs),
140
144
  startedAt: new Date(summary.startedAtMs).toISOString(),
@@ -143,8 +147,7 @@ function sessionReportFromSummary(summary, nowMs = Date.now()) {
143
147
  ...summary.state === "completed" ? { completedAt: new Date(summary.updatedAtMs).toISOString() } : {},
144
148
  cumulativeDurationMs: summary.cumulativeDurationMs,
145
149
  ...cumulativeUsage ? { cumulativeUsage } : {},
146
- surface: surfaceFromSummary(summary),
147
- title: titleFromSummary(summary),
150
+ surface: effectiveSurface,
148
151
  ...requesterIdentity ? { requesterIdentity } : {},
149
152
  ...slackThread ? { channel: slackThread.channelId } : {},
150
153
  ...channelName ? { channelName } : {},
@@ -235,8 +238,27 @@ function slackStatsLocationLabel(input) {
235
238
  }
236
239
  return name || channelId;
237
240
  }
241
+ function surfaceFallbackLabel(surface) {
242
+ if (surface === "scheduler") return "Scheduler";
243
+ if (surface === "api") return "API";
244
+ if (surface === "internal") return "Internal";
245
+ return "Conversation";
246
+ }
247
+ function displayTitleFromDetails(conversationId, details) {
248
+ if (!details) return void 0;
249
+ const slackThread = parseSlackThreadId(conversationId);
250
+ const slackConversation = resolveSlackConversationContextFromThreadId({
251
+ threadId: conversationId,
252
+ channelName: details.channelName
253
+ });
254
+ const privateLabel = resolveConversationPrivacy({ conversationId }) !== "public" ? formatSlackConversationRedactedLabel(slackConversation) ?? PRIVATE_CONVERSATION_LABEL : void 0;
255
+ return privateLabel ?? details.displayTitle ?? slackStatsLocationLabel({
256
+ channel: slackThread?.channelId,
257
+ channelName: details.channelName
258
+ }) ?? (details.originSurface ? surfaceFallbackLabel(details.originSurface) : void 0);
259
+ }
238
260
  function locationLabel(turn) {
239
- return slackStatsLocationLabel(turn) ?? (turn.surface === "scheduler" ? "Scheduler" : turn.surface === "api" ? "API" : turn.surface === "internal" ? "Internal" : "Unknown");
261
+ return slackStatsLocationLabel(turn) ?? surfaceFallbackLabel(turn.surface);
240
262
  }
241
263
  function emptyStatsItem(label) {
242
264
  return {
@@ -618,11 +640,18 @@ async function readSessions() {
618
640
  const summaries = await listAgentTurnSessionSummaries(
619
641
  DASHBOARD_SESSION_FEED_LIMIT
620
642
  );
643
+ const detailsByConversationId = await getConversationDetailsForIds(
644
+ summaries.map((s) => s.conversationId)
645
+ );
621
646
  return {
622
647
  source: "turn_session_records",
623
648
  generatedAt: new Date(nowMs).toISOString(),
624
649
  sessions: summaries.map(
625
- (summary) => sessionReportFromSummary(summary, nowMs)
650
+ (summary) => sessionReportFromSummary(
651
+ summary,
652
+ nowMs,
653
+ detailsByConversationId.get(summary.conversationId)
654
+ )
626
655
  )
627
656
  };
628
657
  }
@@ -641,13 +670,20 @@ async function readConversationStats() {
641
670
  summaries: sampledSummaries,
642
671
  truncated
643
672
  });
673
+ const detailsByConversationId = await getConversationDetailsForIds(
674
+ reportSummaries.map((summary) => summary.conversationId)
675
+ );
644
676
  return buildConversationStatsReport({
645
677
  generatedAt,
646
678
  nowMs,
647
679
  sampleLimit: DASHBOARD_CONVERSATION_STATS_LIMIT,
648
680
  sampleSize: sampledSummaries.length,
649
681
  sessions: reportSummaries.map(
650
- (summary) => sessionReportFromSummary(summary, nowMs)
682
+ (summary) => sessionReportFromSummary(
683
+ summary,
684
+ nowMs,
685
+ detailsByConversationId.get(summary.conversationId)
686
+ )
651
687
  ),
652
688
  truncated
653
689
  });
@@ -655,13 +691,17 @@ async function readConversationStats() {
655
691
  async function readPluginOperationalReports() {
656
692
  const nowMs = Date.now();
657
693
  return {
658
- source: "trusted_plugins",
694
+ source: "plugins",
659
695
  generatedAt: new Date(nowMs).toISOString(),
660
696
  reports: await getAgentPluginOperationalReports(nowMs)
661
697
  };
662
698
  }
663
699
  async function readConversation(conversationId) {
664
- const summaries = (await listAgentTurnSessionSummariesForConversation(conversationId)).sort(
700
+ const [rawSummaries, details] = await Promise.all([
701
+ listAgentTurnSessionSummariesForConversation(conversationId),
702
+ getConversationDetails(conversationId)
703
+ ]);
704
+ const summaries = rawSummaries.sort(
665
705
  (left, right) => left.startedAtMs - right.startedAtMs || left.updatedAtMs - right.updatedAtMs || left.sessionId.localeCompare(right.sessionId)
666
706
  );
667
707
  const turns = await Promise.all(
@@ -684,7 +724,7 @@ async function readConversation(conversationId) {
684
724
  const traceId = summary.traceId ?? sessionRecord?.traceId ?? (canExposeTranscript ? traceIdFromTranscript(transcript) : void 0);
685
725
  const sentryTraceUrl = traceId ? buildSentryTraceUrl(traceId) : void 0;
686
726
  return {
687
- ...sessionReportFromSummary(summary),
727
+ ...sessionReportFromSummary(summary, Date.now(), details),
688
728
  ...traceId ? { traceId } : {},
689
729
  ...sentryTraceUrl ? { sentryTraceUrl } : {},
690
730
  transcriptAvailable: Boolean(sessionRecord) && canExposeTranscript,
@@ -698,8 +738,11 @@ async function readConversation(conversationId) {
698
738
  };
699
739
  })
700
740
  );
741
+ const firstTurn = turns[0];
742
+ const displayTitle = firstTurn?.displayTitle ?? displayTitleFromDetails(conversationId, details) ?? surfaceFallbackLabel(firstTurn?.surface ?? "slack");
701
743
  return {
702
744
  conversationId,
745
+ displayTitle,
703
746
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
704
747
  turns
705
748
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.68.0",
3
+ "version": "0.70.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -65,10 +65,11 @@
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.68.0"
68
+ "@sentry/junior-plugin-api": "0.70.0"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.9.1",
72
+ "@vitest/coverage-v8": "^4.1.7",
72
73
  "dependency-cruiser": "^17.4.0",
73
74
  "msw": "^2.14.6",
74
75
  "nitro": "3.0.260522-beta",
@@ -77,7 +78,7 @@
77
78
  "typescript": "^6.0.3",
78
79
  "vercel": "^54.4.0",
79
80
  "vitest": "^4.1.7",
80
- "@sentry/junior-scheduler": "0.68.0"
81
+ "@sentry/junior-scheduler": "0.70.0"
81
82
  },
82
83
  "scripts": {
83
84
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",
@@ -88,6 +89,7 @@
88
89
  "test:slack-boundary": "node scripts/check-slack-test-boundary.mjs",
89
90
  "test:arch-boundary": "depcruise --config .dependency-cruiser.mjs src/chat",
90
91
  "typecheck": "tsc --noEmit",
91
- "skills:check": "node scripts/check-skills.mjs"
92
+ "skills:check": "node scripts/check-skills.mjs",
93
+ "test:coverage": "pnpm run test:slack-boundary && pnpm run test:arch-boundary && vitest run --maxWorkers=4 --coverage --reporter=default --reporter=junit --outputFile.junit=coverage/results.junit.xml"
92
94
  }
93
95
  }
@@ -1,4 +0,0 @@
1
- import type { CredentialBroker } from "@/chat/credentials/broker";
2
- import type { GitHubAppCredentials, PluginManifest } from "../types";
3
- /** Create a broker that keeps GitHub App tokens on the host while authorizing provider traffic. */
4
- export declare function createGitHubAppBroker(manifest: PluginManifest, credentials: GitHubAppCredentials): CredentialBroker;
@@ -1,11 +0,0 @@
1
- type GitHubPermissionRequest = Record<string, "read" | "write">;
2
- export declare const DEFAULT_GITHUB_SYSTEM_READ_SCOPES: Set<string>;
3
- /** Validate and normalize GitHub App read scopes from plugin configuration. */
4
- export declare function normalizeGitHubSystemReadPermissionScopes(scopes: string[], context: string): string[];
5
- /** Convert plugin capabilities into the GitHub App installation permission body. */
6
- export declare function githubCapabilitiesToPermissions(capabilities: string[], pluginName: string): GitHubPermissionRequest;
7
- /** Convert configured system scopes into read-only GitHub App permissions. */
8
- export declare function githubSystemReadPermissionsFromScopes(scopes: string[]): GitHubPermissionRequest;
9
- /** Intersect installation permissions with the allowed system read scope set. */
10
- export declare function githubInstallationReadPermissions(permissions: Record<string, string> | undefined, allowedScopes: Set<string>): GitHubPermissionRequest;
11
- export {};
@@ -1,33 +0,0 @@
1
- import type { Message, Thread } from "chat";
2
- import type { SlackTurnRuntime } from "@/chat/runtime/slack-runtime";
3
- import { downloadPrivateSlackFile as downloadPrivateSlackFileImpl } from "@/chat/slack/client";
4
- export type ThreadMessageKind = "new_mention" | "subscribed_message";
5
- export interface ThreadMessageDispatchArgs {
6
- beforeFirstResponsePost?: () => Promise<void>;
7
- kind: ThreadMessageKind;
8
- message: Message;
9
- thread: Thread;
10
- }
11
- export type ThreadMessageRuntime = Pick<SlackTurnRuntime<unknown>, "handleNewMention" | "handleSubscribedMessage">;
12
- export type ThreadMessageDispatcher = (args: ThreadMessageDispatchArgs) => Promise<void>;
13
- export interface CreateThreadMessageDispatcherOptions {
14
- downloadPrivateSlackFile?: typeof downloadPrivateSlackFileImpl;
15
- runtime: ThreadMessageRuntime;
16
- }
17
- /**
18
- * Attach Slack private-file download functions to deserialized attachments.
19
- *
20
- * The Chat SDK's `concurrency: "queue"` strategy serializes queued messages
21
- * via `Message.toJSON()`, which strips `fetchData` (a function) and `data`
22
- * (a Buffer). When dequeued, attachments have a `url` but no fetcher.
23
- * This re-attaches a bot-token-auth'd download callback.
24
- *
25
- * No-ops when `fetchData` is already present, so safe to call unconditionally.
26
- */
27
- export declare function rehydrateAttachmentFetchers(message: {
28
- attachments: Array<{
29
- fetchData?: unknown;
30
- url?: string;
31
- }>;
32
- }, downloadPrivateSlackFile?: typeof downloadPrivateSlackFileImpl): void;
33
- export declare function createThreadMessageDispatcher(options: CreateThreadMessageDispatcherOptions): ThreadMessageDispatcher;