@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.
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  PluginHookDeniedError,
3
3
  createPluginHookRunner,
4
+ getPluginDashboardRoutes,
4
5
  getPluginOperationalReports,
5
6
  getPluginRoutes,
6
7
  getPluginSlackConversationLink,
@@ -10,9 +11,9 @@ import {
10
11
  getPlugins,
11
12
  setPlugins,
12
13
  validatePlugins
13
- } from "./chunk-WBSGTHNO.js";
14
+ } from "./chunk-SSWBYEFH.js";
14
15
  import "./chunk-RARSKPVT.js";
15
- import "./chunk-NYKJ3KON.js";
16
+ import "./chunk-237T7XAN.js";
16
17
  import "./chunk-G3E7SCME.js";
17
18
  import "./chunk-LXTPBU4K.js";
18
19
  import "./chunk-Q6XFTRV5.js";
@@ -25,6 +26,7 @@ import "./chunk-MLKGABMK.js";
25
26
  export {
26
27
  PluginHookDeniedError,
27
28
  createPluginHookRunner,
29
+ getPluginDashboardRoutes,
28
30
  getPluginOperationalReports,
29
31
  getPluginRoutes,
30
32
  getPluginSlackConversationLink,
@@ -1,13 +1,13 @@
1
1
  export { createApp } from "./app";
2
- export type { JuniorAppOptions } from "./app";
2
+ export type { JuniorAppOptions, JuniorDashboardOptions } from "./app";
3
3
  export { initSentry } from "./instrumentation";
4
4
  export { juniorNitro } from "./nitro";
5
- export type { JuniorNitroOptions } from "./nitro";
5
+ export type { JuniorNitroDashboardOptions, JuniorNitroOptions } from "./nitro";
6
6
  export { defineJuniorPlugins } from "./plugins";
7
7
  export type { JuniorPluginInput, JuniorPluginSet, JuniorPluginSetOptions, } from "./plugins";
8
8
  export type { PluginRunContext, PluginRunTranscriptEntry, PluginTaskContext, PluginTaskDefinition, PluginTasks, } from "@sentry/junior-plugin-api";
9
9
  export { pluginRunContextSchema, pluginRunTranscriptEntrySchema, } from "@sentry/junior-plugin-api";
10
10
  export { createJuniorReporting } from "./reporting";
11
- export type { PluginConversationStatus, PluginConversations, PluginConversationSummary, ConversationFeed, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationStatsItem, ConversationStatsReport, ConversationSummaryReport, ConversationSurface, ConversationUsage, HealthReport, JuniorReporting, PluginOperationalReport, PluginOperationalReportFeed, PluginPackageContentItemReport, PluginPackageContentReport, PluginReport, RequesterIdentity, RuntimeInfoReport, SkillReport, TranscriptMessage, TranscriptPart, TranscriptPartType, TranscriptRole, } from "./reporting";
11
+ export type { PluginConversationStatus, PluginConversations, PluginConversationSummary, ConversationActivityReport, ConversationActivityStatus, ConversationFeed, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSubagentActivityReport, ConversationStatsItem, ConversationStatsReport, ConversationSummaryReport, ConversationSurface, ConversationToolActivityReport, ConversationUsage, HealthReport, JuniorReporting, PluginOperationalReport, PluginOperationalReportFeed, PluginPackageContentItemReport, PluginPackageContentReport, PluginReport, RequesterIdentity, RuntimeInfoReport, SkillReport, TranscriptMessage, TranscriptPart, TranscriptPartType, TranscriptRole, } from "./reporting";
12
12
  export { juniorVercelConfig } from "./vercel";
13
13
  export type { JuniorVercelConfigOptions } from "./vercel";
package/dist/app.d.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { Hono } from "hono";
2
+ import type { JuniorReporting } from "./reporting";
2
3
  import { type JuniorPluginSet } from "./plugins";
3
4
  import { type VercelConversationWorkCallbackOptions } from "@/chat/task-execution/vercel-callback";
4
5
  import type { WaitUntilFn } from "@/handlers/types";
5
6
  export { defineJuniorPlugins } from "./plugins";
6
7
  export type { JuniorPluginInput, JuniorPluginSet, JuniorPluginSetOptions, } from "./plugins";
7
8
  export interface JuniorAppOptions {
9
+ /** Authenticated dashboard mounted by core when configured. */
10
+ dashboard?: JuniorDashboardOptions;
8
11
  /** Slack-specific overrides applied after env parsing. */
9
12
  slack?: {
10
13
  /** Slack emoji shown while Junior is processing. Defaults to `eyes`. */
@@ -29,5 +32,29 @@ export interface JuniorAppOptions {
29
32
  };
30
33
  waitUntil?: WaitUntilFn;
31
34
  }
35
+ export interface JuniorDashboardOptions {
36
+ /** Browser auth route prefix used by Better Auth. */
37
+ authPath?: string;
38
+ /** Require a dashboard browser session before serving dashboard pages and APIs. */
39
+ authRequired?: boolean;
40
+ /** Exact Google account emails allowed to open the dashboard. */
41
+ allowedEmails?: string[];
42
+ /** Google Workspace domains allowed to open the dashboard. */
43
+ allowedGoogleDomains?: string[];
44
+ /** Browser route prefix for the dashboard shell. */
45
+ basePath?: string;
46
+ /** Public deployment origin used for auth callbacks and external links. */
47
+ baseURL?: string;
48
+ /** Disable dashboard route mounting while preserving serializable config shape. */
49
+ disabled?: boolean;
50
+ /** Overlay dashboard visual-QA fixture conversations onto real reporting data. */
51
+ mockConversations?: boolean;
52
+ /** Reporting implementation used by dashboard APIs. Defaults to core reporting. */
53
+ reporting?: JuniorReporting;
54
+ /** Browser session lifetime in seconds. */
55
+ sessionMaxAgeSeconds?: number;
56
+ /** Additional trusted origins accepted by Better Auth. */
57
+ trustedOrigins?: string[];
58
+ }
32
59
  /** Create a Hono app with all Junior routes. */
33
60
  export declare function createApp(options?: JuniorAppOptions): Promise<Hono>;
package/dist/app.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  updateConversationStats,
72
72
  uploadFilesToThread,
73
73
  upsertConversationMessage
74
- } from "./chunk-W36B5PT4.js";
74
+ } from "./chunk-NNM7YQLL.js";
75
75
  import {
76
76
  CONVERSATION_WORK_CHECK_IN_INTERVAL_MS,
77
77
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
@@ -90,7 +90,7 @@ import {
90
90
  requestConversationContinuation,
91
91
  requestConversationWork,
92
92
  startConversationWork
93
- } from "./chunk-KPL4WJWA.js";
93
+ } from "./chunk-LUNMJQ7D.js";
94
94
  import {
95
95
  JUNIOR_THREAD_STATE_TTL_MS,
96
96
  coerceThreadConversationState
@@ -129,7 +129,7 @@ import {
129
129
  recordAuthorizationCompleted,
130
130
  splitSlackReplyText,
131
131
  truncateStatusText
132
- } from "./chunk-TO3UAY2M.js";
132
+ } from "./chunk-R5T7QY3P.js";
133
133
  import {
134
134
  validatePluginEgressCredentialHooks,
135
135
  validatePluginRegistrations
@@ -142,13 +142,14 @@ import {
142
142
  } from "./chunk-SG5WAA7H.js";
143
143
  import {
144
144
  bindSlackDirectCredentialSubject,
145
+ getPluginDashboardRoutes,
145
146
  getPluginRoutes,
146
147
  getPluginSlackConversationLink,
147
148
  getPlugins,
148
149
  setPlugins,
149
150
  validatePlugins,
150
151
  verifySlackDirectCredentialSubject
151
- } from "./chunk-WBSGTHNO.js";
152
+ } from "./chunk-SSWBYEFH.js";
152
153
  import {
153
154
  createPluginLogger,
154
155
  createPluginState
@@ -156,7 +157,7 @@ import {
156
157
  import {
157
158
  getConversationStore,
158
159
  getDb
159
- } from "./chunk-NYKJ3KON.js";
160
+ } from "./chunk-237T7XAN.js";
160
161
  import "./chunk-G3E7SCME.js";
161
162
  import {
162
163
  acquireActiveLock,
@@ -247,8 +248,60 @@ import {
247
248
  import "./chunk-MLKGABMK.js";
248
249
 
249
250
  // src/app.ts
251
+ import { createRequire } from "module";
252
+ import { pathToFileURL } from "url";
250
253
  import { Hono } from "hono";
251
254
 
255
+ // src/chat/slack/dashboard-link.ts
256
+ var dashboardConversationLinkOptions;
257
+ function withHttps(host) {
258
+ return /^https?:\/\//.test(host) ? host : `https://${host}`;
259
+ }
260
+ function stripTrailingSlashes(value) {
261
+ let end = value.length;
262
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
263
+ end -= 1;
264
+ }
265
+ return end === value.length ? value : value.slice(0, end);
266
+ }
267
+ function normalizeDashboardPath(path2, fallback) {
268
+ const value = path2?.trim() || fallback;
269
+ const withSlash = value.startsWith("/") ? value : `/${value}`;
270
+ return stripTrailingSlashes(withSlash);
271
+ }
272
+ function resolveDashboardBaseURL(config) {
273
+ const explicit = config.baseURL ?? process.env.BETTER_AUTH_URL ?? process.env.JUNIOR_BASE_URL;
274
+ if (explicit?.trim()) {
275
+ return stripTrailingSlashes(withHttps(explicit.trim()));
276
+ }
277
+ const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
278
+ if (vercelProd) {
279
+ return stripTrailingSlashes(withHttps(vercelProd));
280
+ }
281
+ const vercelUrl = process.env.VERCEL_URL?.trim();
282
+ if (vercelUrl) {
283
+ return stripTrailingSlashes(withHttps(vercelUrl));
284
+ }
285
+ return "http://localhost:3000";
286
+ }
287
+ function setDashboardConversationLinkOptions(options) {
288
+ const previous = dashboardConversationLinkOptions;
289
+ dashboardConversationLinkOptions = options?.disabled ? void 0 : options;
290
+ return previous;
291
+ }
292
+ function getDashboardConversationLink(conversationId) {
293
+ if (!dashboardConversationLinkOptions) {
294
+ return void 0;
295
+ }
296
+ const baseURL = resolveDashboardBaseURL(dashboardConversationLinkOptions);
297
+ const basePath = normalizeDashboardPath(
298
+ dashboardConversationLinkOptions.basePath,
299
+ "/"
300
+ );
301
+ const path2 = basePath === "/" ? `/conversations/${encodeURIComponent(conversationId)}` : `${basePath}/conversations/${encodeURIComponent(conversationId)}`;
302
+ return `${baseURL}${path2}`;
303
+ }
304
+
252
305
  // src/chat/slack/reply.ts
253
306
  import { Buffer as Buffer2 } from "buffer";
254
307
 
@@ -267,7 +320,7 @@ function buildSlackReplyFooter(args) {
267
320
  label: "ID",
268
321
  value: conversationId
269
322
  };
270
- const conversationUrl = getPluginSlackConversationLink(conversationId)?.url ?? buildSentryConversationUrl(conversationId);
323
+ const conversationUrl = getPluginSlackConversationLink(conversationId)?.url ?? getDashboardConversationLink(conversationId) ?? buildSentryConversationUrl(conversationId);
271
324
  if (conversationUrl) {
272
325
  idItem.url = conversationUrl;
273
326
  }
@@ -11158,6 +11211,7 @@ function createProductionConversationWorkOptions(options) {
11158
11211
  }
11159
11212
 
11160
11213
  // src/app.ts
11214
+ var DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
11161
11215
  async function defaultWaitUntil() {
11162
11216
  try {
11163
11217
  const { waitUntil } = await import("@vercel/functions");
@@ -11176,6 +11230,8 @@ async function resolveVirtualConfig() {
11176
11230
  try {
11177
11231
  const mod = await import("#junior/config");
11178
11232
  return {
11233
+ createDashboardApp: mod.createDashboardApp,
11234
+ dashboard: mod.dashboard,
11179
11235
  pluginSet: mod.pluginSet,
11180
11236
  plugins: mod.plugins,
11181
11237
  pluginRuntimeRegistrations: mod.pluginRuntimeRegistrations ?? []
@@ -11238,7 +11294,161 @@ function validateBuildIncludesPluginRuntimeRegistrations(runtimeRegistrations, v
11238
11294
  `createApp() is missing plugin registration(s) with runtime code bundled by juniorNitro(): ${missing.join(", ")}. Pass a runtime-safe plugin module to juniorNitro({ plugins: "./plugins" }) or pass the same defineJuniorPlugins(...) set to createApp({ plugins }).`
11239
11295
  );
11240
11296
  }
11241
- function mountPluginRoutes(app, routes) {
11297
+ async function createDashboardRouteRegistrations(args) {
11298
+ if (!args.dashboard || args.dashboard.disabled) {
11299
+ return [];
11300
+ }
11301
+ const createDashboardApp = args.createDashboardApp ?? await loadDashboardAppFactory();
11302
+ return dashboardRouteRegistrations({
11303
+ dashboard: args.dashboard,
11304
+ createDashboardApp,
11305
+ pluginRoutes: args.pluginRoutes
11306
+ });
11307
+ }
11308
+ async function loadDashboardAppFactory() {
11309
+ try {
11310
+ const appRequire = createRequire(`${process.cwd()}/package.json`);
11311
+ const mod = await import(pathToFileURL(appRequire.resolve(DASHBOARD_PACKAGE_NAME)).href);
11312
+ return dashboardAppFactoryFromModule(mod);
11313
+ } catch (error) {
11314
+ if (isMissingDashboardPackage(error)) {
11315
+ throw new Error(
11316
+ 'createApp({ dashboard }) requires installing "@sentry/junior-dashboard"',
11317
+ { cause: error }
11318
+ );
11319
+ }
11320
+ throw error;
11321
+ }
11322
+ }
11323
+ function dashboardAppFactoryFromModule(mod) {
11324
+ if (!mod || typeof mod !== "object" || typeof mod.createDashboardApp !== "function") {
11325
+ throw new Error(
11326
+ '@sentry/junior-dashboard must export a "createDashboardApp" function'
11327
+ );
11328
+ }
11329
+ return mod.createDashboardApp;
11330
+ }
11331
+ function isMissingDashboardPackage(error) {
11332
+ if (!(error instanceof Error)) {
11333
+ return false;
11334
+ }
11335
+ const code = error.code;
11336
+ return (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") && error.message.includes("@sentry/junior-dashboard");
11337
+ }
11338
+ function stripTrailingSlashes2(value) {
11339
+ let end = value.length;
11340
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
11341
+ end -= 1;
11342
+ }
11343
+ return end === value.length ? value : value.slice(0, end);
11344
+ }
11345
+ function normalizeDashboardPath2(path2, fallback) {
11346
+ const value = path2?.trim() || fallback;
11347
+ const withSlash = value.startsWith("/") ? value : `/${value}`;
11348
+ return stripTrailingSlashes2(withSlash);
11349
+ }
11350
+ function dashboardHostRoutePaths(dashboard) {
11351
+ const basePath = normalizeDashboardPath2(dashboard.basePath, "/");
11352
+ const authPath = normalizeDashboardPath2(dashboard.authPath, "/api/auth");
11353
+ const pagePaths = basePath === "/" ? [
11354
+ "/",
11355
+ "/conversations",
11356
+ "/conversations/*",
11357
+ "/plugins",
11358
+ "/plugins/*",
11359
+ "/sessions",
11360
+ "/sessions/*"
11361
+ ] : [basePath, `${basePath}/*`];
11362
+ return [
11363
+ ...pagePaths,
11364
+ "/favicon.ico",
11365
+ "/api/dashboard",
11366
+ "/api/dashboard/*",
11367
+ authPath,
11368
+ `${authPath}/*`
11369
+ ];
11370
+ }
11371
+ function routePrefixCoversPath(routePrefix, path2) {
11372
+ return routePrefix === "/" || path2 === routePrefix || path2.startsWith(`${routePrefix}/`);
11373
+ }
11374
+ function routeSegments(path2) {
11375
+ return normalizeDashboardPath2(path2, "/").split("/").filter(Boolean);
11376
+ }
11377
+ function routeSegmentMatches(pattern, value) {
11378
+ return pattern === value || pattern === "*" || pattern.startsWith(":");
11379
+ }
11380
+ function routePatternMatchesConcretePath(pattern, concretePath) {
11381
+ const patternSegments = routeSegments(pattern);
11382
+ const pathSegments = routeSegments(concretePath);
11383
+ for (let index = 0; index < patternSegments.length; index += 1) {
11384
+ const segment = patternSegments[index];
11385
+ if (segment === "**" || segment === "*") {
11386
+ return true;
11387
+ }
11388
+ const value = pathSegments[index];
11389
+ if (!value || !routeSegmentMatches(segment, value)) {
11390
+ return false;
11391
+ }
11392
+ }
11393
+ return patternSegments.length === pathSegments.length;
11394
+ }
11395
+ function routePatternExamples(routePath) {
11396
+ const normalized = normalizeDashboardPath2(routePath, "/");
11397
+ if (!normalized.endsWith("/*") && !normalized.endsWith("/**")) {
11398
+ return [normalized];
11399
+ }
11400
+ const prefix = normalizeDashboardPath2(
11401
+ normalized.endsWith("/*") ? normalized.slice(0, -2) : normalized.slice(0, -3),
11402
+ "/"
11403
+ );
11404
+ return [
11405
+ prefix,
11406
+ prefix === "/" ? "/__dashboard__" : `${prefix}/__dashboard__`
11407
+ ];
11408
+ }
11409
+ function routePatternOverlaps(ownedPath, routePath) {
11410
+ if (ownedPath.endsWith("/*") && routePrefixCoversPath(ownedPath.slice(0, -2), routePath)) {
11411
+ return true;
11412
+ }
11413
+ return routePatternExamples(ownedPath).some(
11414
+ (example) => routePatternMatchesConcretePath(routePath, example)
11415
+ );
11416
+ }
11417
+ function dashboardOwnedRoutePath(routePath, dashboard) {
11418
+ return dashboardHostRoutePaths(dashboard).some(
11419
+ (path2) => routePatternOverlaps(path2, routePath)
11420
+ );
11421
+ }
11422
+ function dashboardRouteRegistrations(args) {
11423
+ let app;
11424
+ const fetch2 = (request) => {
11425
+ app ??= args.createDashboardApp({
11426
+ ...args.dashboard,
11427
+ pluginRoutes: args.pluginRoutes
11428
+ });
11429
+ if (!app || typeof app.fetch !== "function") {
11430
+ throw new Error("createDashboardApp() must return an app with fetch()");
11431
+ }
11432
+ return app.fetch(request);
11433
+ };
11434
+ return dashboardHostRoutePaths(args.dashboard).map((path2) => ({
11435
+ handler: fetch2,
11436
+ path: path2
11437
+ }));
11438
+ }
11439
+ function validateDashboardRouteOwnership(args) {
11440
+ if (!args.dashboard || args.dashboard.disabled) {
11441
+ return;
11442
+ }
11443
+ for (const route of args.routes) {
11444
+ if (dashboardOwnedRoutePath(route.path, args.dashboard)) {
11445
+ throw new Error(
11446
+ `Plugin "${route.pluginName}" route "${route.path}" conflicts with core dashboard routes`
11447
+ );
11448
+ }
11449
+ }
11450
+ }
11451
+ function mountRoutes(app, routes) {
11242
11452
  for (const route of routes) {
11243
11453
  const handler = (c) => route.handler(c.req.raw);
11244
11454
  const methods = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
@@ -11254,6 +11464,7 @@ function mountPluginRoutes(app, routes) {
11254
11464
  }
11255
11465
  async function createApp(options) {
11256
11466
  const virtualConfig = await resolveVirtualConfig();
11467
+ const dashboard = options?.dashboard ?? virtualConfig?.dashboard;
11257
11468
  const configuredPlugins = options?.plugins ?? virtualConfig?.pluginSet;
11258
11469
  const plugins = pluginRuntimeRegistrationsFromPluginSet(configuredPlugins);
11259
11470
  const pluginConfig = configuredPlugins ? pluginCatalogConfigFromPluginSet(configuredPlugins) : virtualConfig?.plugins ?? pluginCatalogConfigFromEnv();
@@ -11268,7 +11479,9 @@ async function createApp(options) {
11268
11479
  const previousPlugins = setPlugins(plugins);
11269
11480
  const previousConfigDefaults = getConfigDefaults();
11270
11481
  const previousSlackReactionConfig = getSlackReactionConfig();
11482
+ const previousDashboardLinkOptions = setDashboardConversationLinkOptions(dashboard);
11271
11483
  let pluginRoutes = [];
11484
+ let pluginDashboardRoutes = [];
11272
11485
  let sandboxEgressTracePropagationDomains = [];
11273
11486
  try {
11274
11487
  sandboxEgressTracePropagationDomains = normalizeSandboxEgressTracePropagationDomains(
@@ -11286,11 +11499,16 @@ async function createApp(options) {
11286
11499
  );
11287
11500
  }
11288
11501
  pluginRoutes = getPluginRoutes();
11502
+ validateDashboardRouteOwnership({ dashboard, routes: pluginRoutes });
11503
+ if (dashboard && !dashboard.disabled) {
11504
+ pluginDashboardRoutes = getPluginDashboardRoutes();
11505
+ }
11289
11506
  } catch (error) {
11290
11507
  setPluginCatalogConfig(previousPluginCatalogConfig);
11291
11508
  setPlugins(previousPlugins);
11292
11509
  setConfigDefaults(previousConfigDefaults);
11293
11510
  setSlackReactionConfig(previousSlackReactionConfig);
11511
+ setDashboardConversationLinkOptions(previousDashboardLinkOptions);
11294
11512
  throw error;
11295
11513
  }
11296
11514
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
@@ -11318,7 +11536,15 @@ async function createApp(options) {
11318
11536
  next
11319
11537
  );
11320
11538
  });
11321
- mountPluginRoutes(app, pluginRoutes);
11539
+ mountRoutes(app, pluginRoutes);
11540
+ mountRoutes(
11541
+ app,
11542
+ await createDashboardRouteRegistrations({
11543
+ dashboard,
11544
+ createDashboardApp: virtualConfig?.createDashboardApp,
11545
+ pluginRoutes: pluginDashboardRoutes
11546
+ })
11547
+ );
11322
11548
  app.get("/", () => GET());
11323
11549
  app.get("/health", () => GET());
11324
11550
  app.get("/api/oauth/callback/mcp/:provider", (c) => {
@@ -1,4 +1,6 @@
1
1
  /** Copy app and declared plugin package content into the server output. */
2
2
  export declare function copyAppAndPluginContent(cwd: string, serverRoot: string, packageNames?: unknown): void;
3
+ /** Copy dashboard browser assets when core dashboard routes are enabled. */
4
+ export declare function copyDashboardAssets(cwd: string, serverRoot: string): void;
3
5
  /** Copy extra file patterns into server output for files the bundler cannot trace. */
4
6
  export declare function copyIncludedFiles(cwd: string, serverRoot: string, patterns?: unknown): void;
@@ -1,5 +1,6 @@
1
1
  import type { Nitro } from "nitro/types";
2
2
  import type { PluginCatalogConfig } from "@/chat/plugins/types";
3
+ import type { JuniorDashboardOptions } from "@/app";
3
4
  import { type JuniorPluginSet } from "@/plugins";
4
5
  export interface RuntimePluginModule {
5
6
  exportName: string;
@@ -7,6 +8,7 @@ export interface RuntimePluginModule {
7
8
  }
8
9
  /** Render the virtual config module consumed by createApp(). */
9
10
  export declare function renderVirtualConfig(options: {
11
+ dashboard?: Omit<JuniorDashboardOptions, "reporting">;
10
12
  plugins?: PluginCatalogConfig;
11
13
  pluginModule?: RuntimePluginModule;
12
14
  pluginRuntimeRegistrations?: string[];
@@ -17,4 +19,5 @@ export declare function injectVirtualConfig(nitro: Nitro, options?: {
17
19
  pluginModule?: RuntimePluginModule;
18
20
  plugins?: PluginCatalogConfig;
19
21
  pluginRuntimeRegistrations?: string[];
22
+ dashboard?: Omit<JuniorDashboardOptions, "reporting">;
20
23
  }): void;
@@ -1,7 +1,7 @@
1
1
  import type { Destination } from "@sentry/junior-plugin-api";
2
2
  import type { StoredSlackRequester } from "@/chat/requester";
3
3
  export type ConversationSource = "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
4
- export type ConversationStatus = "awaiting_resume" | "idle" | "pending" | "running";
4
+ export type ConversationStatus = "awaiting_resume" | "failed" | "idle" | "pending" | "running";
5
5
  export interface ConversationExecution {
6
6
  lastCheckpointAtMs?: number;
7
7
  lastEnqueuedAtMs?: number;
@@ -1,4 +1,4 @@
1
- import type { PluginConversations, PluginRoute, PluginOperationalReport, SlackConversationLink, PluginRegistration } from "@sentry/junior-plugin-api";
1
+ import type { PluginConversations, PluginRoute, PluginOperationalReport, PluginRouteApp, SlackConversationLink, PluginRegistration } from "@sentry/junior-plugin-api";
2
2
  import type { PluginPromptContributionContext } from "@/chat/plugins/prompt";
3
3
  import type { ToolDefinition } from "@/chat/tools/definition";
4
4
  import type { ToolRuntimeContext } from "@/chat/tools/types";
@@ -19,6 +19,10 @@ export interface ToolHookResult {
19
19
  export interface PluginRouteRegistration extends PluginRoute {
20
20
  pluginName: string;
21
21
  }
22
+ export interface PluginDashboardRouteRegistration {
23
+ app: PluginRouteApp;
24
+ pluginName: string;
25
+ }
22
26
  export interface PluginHookRunner {
23
27
  beforeToolExecute(input: ToolHookInput): Promise<ToolHookResult>;
24
28
  prepareSandbox(sandbox: SandboxInstance): Promise<void>;
@@ -39,6 +43,8 @@ export declare function getPluginUserPromptContributions(args: {
39
43
  export declare function getPluginTools(context: ToolRuntimeContext): Record<string, ToolDefinition<any>>;
40
44
  /** Collect route handlers exposed by plugins for app-level mounting. */
41
45
  export declare function getPluginRoutes(): PluginRouteRegistration[];
46
+ /** Collect dashboard-scoped route apps exposed by plugins. */
47
+ export declare function getPluginDashboardRoutes(): PluginDashboardRouteRegistration[];
42
48
  /** Resolve the first plugin conversation URL for finalized Slack footers. */
43
49
  export declare function getPluginSlackConversationLink(conversationId: string): SlackConversationLink | undefined;
44
50
  /** Collect read-only operational summaries exposed by plugins. */
@@ -0,0 +1,9 @@
1
+ export interface DashboardConversationLinkOptions {
2
+ basePath?: string;
3
+ baseURL?: string;
4
+ disabled?: boolean;
5
+ }
6
+ /** Configure core dashboard links used in Slack footers. */
7
+ export declare function setDashboardConversationLinkOptions(options: DashboardConversationLinkOptions | undefined): DashboardConversationLinkOptions | undefined;
8
+ /** Build the dashboard conversation URL when the core dashboard is enabled. */
9
+ export declare function getDashboardConversationLink(conversationId: string): string | undefined;
@@ -2,6 +2,11 @@ import { z } from "zod";
2
2
  import type { PiMessage } from "@/chat/pi/messages";
3
3
  import { type StoredSlackRequester } from "@/chat/requester";
4
4
  declare const authorizationKindSchema: z.ZodUnion<readonly [z.ZodLiteral<"plugin">, z.ZodLiteral<"mcp">]>;
5
+ declare const transcriptRefSchema: z.ZodObject<{
6
+ type: z.ZodLiteral<"advisor_session">;
7
+ parentConversationId: z.ZodString;
8
+ key: z.ZodString;
9
+ }, z.core.$strip>;
5
10
  declare const sessionLogEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
6
11
  schemaVersion: z.ZodLiteral<1>;
7
12
  type: z.ZodLiteral<"pi_message">;
@@ -74,10 +79,50 @@ declare const sessionLogEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
74
79
  provider: z.ZodString;
75
80
  requesterId: z.ZodString;
76
81
  authorizationId: z.ZodString;
82
+ }, z.core.$strip>, z.ZodObject<{
83
+ schemaVersion: z.ZodLiteral<1>;
84
+ type: z.ZodLiteral<"tool_execution_started">;
85
+ sessionId: z.ZodDefault<z.ZodString>;
86
+ createdAtMs: z.ZodNumber;
87
+ toolCallId: z.ZodString;
88
+ toolName: z.ZodString;
89
+ args: z.ZodOptional<z.ZodUnknown>;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ schemaVersion: z.ZodLiteral<1>;
92
+ type: z.ZodLiteral<"subagent_started">;
93
+ sessionId: z.ZodDefault<z.ZodString>;
94
+ subagentInvocationId: z.ZodString;
95
+ subagentKind: z.ZodString;
96
+ parentToolCallId: z.ZodOptional<z.ZodString>;
97
+ parentConversationId: z.ZodString;
98
+ parentSessionId: z.ZodOptional<z.ZodString>;
99
+ transcriptRef: z.ZodObject<{
100
+ type: z.ZodLiteral<"advisor_session">;
101
+ parentConversationId: z.ZodString;
102
+ key: z.ZodString;
103
+ }, z.core.$strip>;
104
+ historyMode: z.ZodLiteral<"shared">;
105
+ createdAtMs: z.ZodNumber;
106
+ }, z.core.$strip>, z.ZodObject<{
107
+ schemaVersion: z.ZodLiteral<1>;
108
+ type: z.ZodLiteral<"subagent_ended">;
109
+ sessionId: z.ZodDefault<z.ZodString>;
110
+ subagentInvocationId: z.ZodString;
111
+ outcome: z.ZodUnion<readonly [z.ZodLiteral<"success">, z.ZodLiteral<"error">, z.ZodLiteral<"aborted">]>;
112
+ errorCode: z.ZodOptional<z.ZodString>;
113
+ createdAtMs: z.ZodNumber;
77
114
  }, z.core.$strip>], "type">;
78
115
  /** Requester identity stored with turn-start messages for durable continuation. */
79
116
  export type SessionLogEntry = z.infer<typeof sessionLogEntrySchema>;
80
117
  export type AuthorizationKind = z.infer<typeof authorizationKindSchema>;
118
+ export type TranscriptRef = z.infer<typeof transcriptRefSchema>;
119
+ export type SessionActivityEntry = Extract<SessionLogEntry, {
120
+ type: "tool_execution_started";
121
+ }> | Extract<SessionLogEntry, {
122
+ type: "subagent_started";
123
+ }> | Extract<SessionLogEntry, {
124
+ type: "subagent_ended";
125
+ }>;
81
126
  interface Scope {
82
127
  conversationId: string;
83
128
  }
@@ -94,6 +139,11 @@ export interface SessionProjection {
94
139
  messages: PiMessage[];
95
140
  requester?: StoredSlackRequester;
96
141
  }
142
+ /** Load chronological host-only runtime activity entries for reporting. */
143
+ export declare function loadActivityEntries(args: Scope & {
144
+ store?: SessionLogStore;
145
+ sessionId?: string;
146
+ }): Promise<SessionActivityEntry[]>;
97
147
  /** Load the committed Pi-message projection for a conversation. */
98
148
  export declare function loadMessages(args: Scope & {
99
149
  store?: SessionLogStore;
@@ -142,6 +192,40 @@ export declare function recordAuthorizationCompleted(args: Scope & {
142
192
  authorizationId: string;
143
193
  ttlMs: number;
144
194
  }): Promise<void>;
195
+ /** Record a host-observed parent tool start without adding it to Pi replay. */
196
+ export declare function recordToolExecutionStarted(args: Scope & {
197
+ args?: unknown;
198
+ createdAtMs?: number;
199
+ sessionId?: string;
200
+ store?: SessionLogStore;
201
+ toolCallId: string;
202
+ toolName: string;
203
+ ttlMs: number;
204
+ }): Promise<void>;
205
+ /** Record that a child agent execution became visible from this parent run. */
206
+ export declare function recordSubagentStarted(args: Scope & {
207
+ createdAtMs?: number;
208
+ historyMode: "shared";
209
+ parentConversationId: string;
210
+ parentSessionId?: string;
211
+ parentToolCallId?: string;
212
+ sessionId?: string;
213
+ store?: SessionLogStore;
214
+ subagentInvocationId: string;
215
+ subagentKind: string;
216
+ transcriptRef: TranscriptRef;
217
+ ttlMs: number;
218
+ }): Promise<void>;
219
+ /** Record the terminal state for a previously-started child agent execution. */
220
+ export declare function recordSubagentEnded(args: Scope & {
221
+ createdAtMs?: number;
222
+ errorCode?: string;
223
+ outcome: "success" | "error" | "aborted";
224
+ sessionId?: string;
225
+ store?: SessionLogStore;
226
+ subagentInvocationId: string;
227
+ ttlMs: number;
228
+ }): Promise<void>;
145
229
  /**
146
230
  * Append conversation-log entries and advance the current Pi projection.
147
231
  *
@@ -7,7 +7,7 @@ export declare const CONVERSATION_WORK_LEASE_TTL_MS = 90000;
7
7
  export declare const CONVERSATION_WORK_CHECK_IN_INTERVAL_MS = 15000;
8
8
  export declare const CONVERSATION_WORK_STALE_ENQUEUE_MS = 60000;
9
9
  export type Source = "api" | "internal" | "local" | "plugin" | "scheduler" | "slack";
10
- export type ExecutionStatus = "awaiting_resume" | "idle" | "pending" | "running";
10
+ export type ExecutionStatus = "awaiting_resume" | "failed" | "idle" | "pending" | "running";
11
11
  export interface AgentInput {
12
12
  attachments?: unknown[];
13
13
  authorId?: string;
@@ -127,6 +127,26 @@ export declare function recordConversationActivity(args: {
127
127
  state?: StateAdapter;
128
128
  title?: string;
129
129
  }): Promise<void>;
130
+ /** Store task-execution metadata for local/no-SQL reporting. */
131
+ export declare function recordConversationExecution(args: {
132
+ channelName?: string;
133
+ conversationId: string;
134
+ createdAtMs: number;
135
+ destination?: Destination;
136
+ execution: {
137
+ lastCheckpointAtMs?: number;
138
+ lastEnqueuedAtMs?: number;
139
+ runId?: string;
140
+ status: ExecutionStatus;
141
+ updatedAtMs?: number;
142
+ };
143
+ lastActivityAtMs: number;
144
+ requester?: StoredSlackRequester;
145
+ source?: Source;
146
+ state?: StateAdapter;
147
+ title?: string;
148
+ updatedAtMs: number;
149
+ }): Promise<void>;
130
150
  /** Record that a wake-up nudge was accepted for the conversation. */
131
151
  export declare function markConversationWorkEnqueued(args: {
132
152
  conversationId: string;
@@ -23,6 +23,7 @@ export interface AdvisorToolRuntimeContext {
23
23
  conversationPrivacy?: ConversationPrivacy;
24
24
  getTools: () => AgentTool[];
25
25
  logContext?: LogContext;
26
+ parentSessionId?: string;
26
27
  store?: AdvisorSessionStore;
27
28
  streamFn?: StreamFn;
28
29
  }