@sentry/junior 0.82.0 → 0.84.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 (45) hide show
  1. package/dist/{agent-hooks-5ZRILRC3.js → agent-hooks-XDS5DIVS.js} +8 -8
  2. package/dist/api-reference.d.ts +1 -1
  3. package/dist/app.d.ts +1 -1
  4. package/dist/app.js +3833 -3263
  5. package/dist/chat/config.d.ts +1 -1
  6. package/dist/chat/conversations/store.d.ts +1 -1
  7. package/dist/chat/plugins/agent-hooks.d.ts +3 -3
  8. package/dist/chat/resource-events/ingest.d.ts +23 -0
  9. package/dist/chat/resource-events/notification.d.ts +20 -0
  10. package/dist/chat/resource-events/store.d.ts +79 -0
  11. package/dist/chat/services/pending-auth.d.ts +5 -0
  12. package/dist/chat/slack/client.d.ts +3 -1
  13. package/dist/chat/state/session-log.d.ts +4 -0
  14. package/dist/chat/task-execution/slack-work.d.ts +22 -0
  15. package/dist/chat/task-execution/state.d.ts +8 -2
  16. package/dist/chat/task-execution/store.d.ts +30 -8
  17. package/dist/chat/tools/resource-events.d.ts +19 -0
  18. package/dist/{chunk-PQ2U2AO3.js → chunk-AAD2D7K3.js} +11 -11
  19. package/dist/{chunk-ABRNFE3N.js → chunk-ALL7GG6U.js} +7 -6
  20. package/dist/{chunk-4XHCVBXH.js → chunk-BAPRSWNW.js} +81 -13
  21. package/dist/{chunk-NC6LR6U4.js → chunk-BFPOSIYV.js} +725 -169
  22. package/dist/{chunk-GUO4EE7L.js → chunk-CWMMGUWM.js} +1 -1
  23. package/dist/{chunk-YA2JCC7G.js → chunk-JUI6XIHU.js} +13 -5
  24. package/dist/{chunk-TR2G37II.js → chunk-KNVN4HUT.js} +8 -8
  25. package/dist/{chunk-X2FL5ZH5.js → chunk-MJ4E2PK7.js} +1 -1
  26. package/dist/{chunk-3JKTVW4R.js → chunk-MZ4DUVFD.js} +2 -2
  27. package/dist/{chunk-2AJ4TEKE.js → chunk-S7SC5IUJ.js} +26 -4
  28. package/dist/{chunk-IOBSRZK5.js → chunk-SBYMRDH7.js} +1 -1
  29. package/dist/{chunk-LX5GBMEP.js → chunk-ZPD5YQFZ.js} +2 -2
  30. package/dist/cli/chat.js +4 -4
  31. package/dist/cli/plugins.js +6 -6
  32. package/dist/cli/snapshot-warmup.js +3 -3
  33. package/dist/cli/upgrade.js +16 -32
  34. package/dist/{db-GQJKBX5W.js → db-7RRWCYF3.js} +3 -3
  35. package/dist/handlers/github-webhook/check-suite.d.ts +3 -0
  36. package/dist/handlers/github-webhook/pull-request-review.d.ts +3 -0
  37. package/dist/handlers/github-webhook/pull-request.d.ts +3 -0
  38. package/dist/handlers/github-webhook/resource.d.ts +10 -0
  39. package/dist/handlers/github-webhook.d.ts +15 -0
  40. package/dist/nitro.js +3 -3
  41. package/dist/reporting/conversations.d.ts +79 -2
  42. package/dist/reporting.d.ts +12 -4
  43. package/dist/reporting.js +431 -27
  44. package/dist/{runner-VRFJLG2M.js → runner-CJL5IKFO.js} +9 -9
  45. package/package.json +6 -6
@@ -1,6 +1,6 @@
1
1
  import type { PluginOperationalReport } from "@sentry/junior-plugin-api";
2
- import { type ConversationFeed, type PluginConversationSummary, type ConversationReport, type ConversationStatsReport } 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";
2
+ import { type ConversationFeed, type PluginConversationSummary, type ConversationReport, type ConversationSubagentTranscriptReport, type ConversationStatsReport, type RequesterDirectoryReport, type RequesterProfileReport } from "./reporting/conversations";
3
+ export type { PluginConversationStatus, PluginConversations, PluginConversationSummary, ConversationActivityReport, ConversationActivityStatus, ConversationFeed, ConversationReport, ConversationReportStatus, ConversationRunReport, ConversationSubagentActivityReport, ConversationSubagentTranscriptReport, ConversationStatsItem, ConversationStatsReport, ConversationSummaryReport, ConversationSurface, ConversationToolActivityReport, ConversationUsage, RequesterActivityDayReport, RequesterDirectoryReport, RequesterIdentity, RequesterProfileReport, RequesterSummaryReport, RequesterTotalsReport, TranscriptMessage, TranscriptPart, TranscriptPartType, TranscriptRole, } from "./reporting/conversations";
4
4
  export interface HealthReport {
5
5
  status: "ok";
6
6
  service: string;
@@ -49,10 +49,14 @@ export interface JuniorReporting {
49
49
  getPlugins(): Promise<PluginReport[]>;
50
50
  /** Read discovered skill names for reporting consumers. */
51
51
  getSkills(): Promise<SkillReport[]>;
52
- /** Read recent conversation summaries for reporting consumers. */
53
- getSessions(): Promise<ConversationFeed>;
52
+ /** List recent conversation summaries for reporting consumers. */
53
+ listConversations(): Promise<ConversationFeed>;
54
54
  /** Read aggregate conversation stats for reporting consumers. */
55
55
  getConversationStats?(): Promise<ConversationStatsReport>;
56
+ /** List requester profiles derived from trusted conversation requester emails. */
57
+ listRequesters?(): Promise<RequesterDirectoryReport>;
58
+ /** Read one requester profile derived from trusted conversation requester emails. */
59
+ getRequesterProfile?(email: string): Promise<RequesterProfileReport>;
56
60
  /** Read recent conversation summaries without transcript payloads. */
57
61
  listRecentConversations?(options?: {
58
62
  limit?: number;
@@ -67,10 +71,14 @@ export interface JuniorReporting {
67
71
  * source. Avoid adding fields that require Redis-only transcript internals.
68
72
  */
69
73
  getConversation(conversationId: string): Promise<ConversationReport>;
74
+ /** Load a child-agent transcript only when an operator opens that subagent. */
75
+ getConversationSubagentTranscript(conversationId: string, runId: string, subagentId: string): Promise<ConversationSubagentTranscriptReport>;
70
76
  }
71
77
  /** Create the read-only reporting boundary used by plugins and other consumers. */
72
78
  export declare function createJuniorReporting(): JuniorReporting & {
73
79
  getConversationStats(): Promise<ConversationStatsReport>;
80
+ listRequesters(): Promise<RequesterDirectoryReport>;
81
+ getRequesterProfile(email: string): Promise<RequesterProfileReport>;
74
82
  listRecentConversations(options?: {
75
83
  limit?: number;
76
84
  }): Promise<PluginConversationSummary[]>;
package/dist/reporting.js CHANGED
@@ -6,24 +6,26 @@ import {
6
6
  getConversationDetails,
7
7
  getConversationDetailsForIds,
8
8
  resolveSlackConversationContextFromThreadId
9
- } from "./chunk-LX5GBMEP.js";
9
+ } from "./chunk-ZPD5YQFZ.js";
10
10
  import {
11
11
  buildSystemPrompt,
12
12
  getAgentTurnSessionRecord,
13
13
  listAgentTurnSessionSummariesForConversation,
14
14
  loadActivityEntries
15
- } from "./chunk-YA2JCC7G.js";
15
+ } from "./chunk-JUI6XIHU.js";
16
16
  import {
17
17
  getPluginOperationalReports
18
- } from "./chunk-TR2G37II.js";
19
- import "./chunk-GUO4EE7L.js";
18
+ } from "./chunk-KNVN4HUT.js";
19
+ import "./chunk-CWMMGUWM.js";
20
20
  import {
21
21
  getConversationStore
22
- } from "./chunk-PQ2U2AO3.js";
22
+ } from "./chunk-AAD2D7K3.js";
23
23
  import "./chunk-G3E7SCME.js";
24
- import "./chunk-X2FL5ZH5.js";
25
- import "./chunk-4XHCVBXH.js";
26
- import "./chunk-ABRNFE3N.js";
24
+ import {
25
+ getStateAdapter
26
+ } from "./chunk-MJ4E2PK7.js";
27
+ import "./chunk-BAPRSWNW.js";
28
+ import "./chunk-ALL7GG6U.js";
27
29
  import {
28
30
  canExposeConversationPayload,
29
31
  parseSlackThreadId,
@@ -53,11 +55,15 @@ import { readFileSync } from "fs";
53
55
  import path from "path";
54
56
 
55
57
  // src/reporting/conversations.ts
58
+ import { z } from "zod";
56
59
  var HUNG_TURN_PROGRESS_MS = 5 * 60 * 1e3;
57
60
  var SAFE_METADATA_KEY_LIMIT = 20;
58
61
  var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
59
62
  var CONVERSATION_FEED_LIMIT = 50;
60
63
  var CONVERSATION_STATS_LIMIT = 5e3;
64
+ var REQUESTER_PROFILE_SAMPLE_LIMIT = 5e3;
65
+ var REQUESTER_PROFILE_RECENT_LIMIT = 25;
66
+ var REQUESTER_PROFILE_ACTIVITY_DAYS = 366;
61
67
  var RECENT_CONVERSATION_STATS_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
62
68
  function conversationStore(options = {}) {
63
69
  return options.conversationStore ?? getConversationStore();
@@ -132,9 +138,6 @@ function sessionReportFromSummary(summary, nowMs = Date.now(), details) {
132
138
  channelName: effectiveChannelName
133
139
  }) ?? surfaceFallbackLabel(effectiveSurface);
134
140
  const requesterIdentity = requesterIdentityReport(details?.originRequester) ?? sessionRequesterIdentityReport(summary.requester);
135
- const sentryConversationUrl = buildSentryConversationUrl(
136
- summary.conversationId
137
- );
138
141
  const sentryTraceUrl = summary.traceId ? buildSentryTraceUrl(summary.traceId) : void 0;
139
142
  const cumulativeUsage = usageReport(summary.cumulativeUsage);
140
143
  return {
@@ -152,7 +155,6 @@ function sessionReportFromSummary(summary, nowMs = Date.now(), details) {
152
155
  ...requesterIdentity ? { requesterIdentity } : {},
153
156
  ...slackThread ? { channel: slackThread.channelId } : {},
154
157
  ...channelName ? { channelName } : {},
155
- ...sentryConversationUrl ? { sentryConversationUrl } : {},
156
158
  ...summary.traceId ? { traceId: summary.traceId } : {},
157
159
  ...sentryTraceUrl ? { sentryTraceUrl } : {}
158
160
  };
@@ -239,9 +241,6 @@ function applyConversationIndexMetadata(args) {
239
241
  }
240
242
  function sessionReportFromConversation(conversation, nowMs, details) {
241
243
  const surface = details?.originSurface ?? surfaceFromSource(conversation.source, conversation.conversationId);
242
- const sentryConversationUrl = buildSentryConversationUrl(
243
- conversation.conversationId
244
- );
245
244
  const requesterIdentity = requesterIdentityReport(
246
245
  details?.originRequester ?? conversation.requester
247
246
  );
@@ -261,8 +260,7 @@ function sessionReportFromConversation(conversation, nowMs, details) {
261
260
  surface,
262
261
  ...requesterIdentity ? { requesterIdentity } : {},
263
262
  ...slackThread ? { channel: slackThread.channelId } : {},
264
- ...channelName ? { channelName } : {},
265
- ...sentryConversationUrl ? { sentryConversationUrl } : {}
263
+ ...channelName ? { channelName } : {}
266
264
  };
267
265
  }
268
266
  function reportTime(value) {
@@ -405,10 +403,10 @@ function newestRun(runs) {
405
403
  function recentConversationGroups(args) {
406
404
  const startMs = args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS;
407
405
  const groups = /* @__PURE__ */ new Map();
408
- for (const session of args.sessions) {
409
- groups.set(session.conversationId, [
410
- ...groups.get(session.conversationId) ?? [],
411
- session
406
+ for (const summary of args.summaries) {
407
+ groups.set(summary.conversationId, [
408
+ ...groups.get(summary.conversationId) ?? [],
409
+ summary
412
410
  ]);
413
411
  }
414
412
  return [...groups.values()].map(
@@ -498,6 +496,277 @@ function buildConversationStatsReport(args) {
498
496
  ).toISOString()
499
497
  };
500
498
  }
499
+ function normalizeRequesterEmail(email) {
500
+ const normalized = email?.trim().toLowerCase();
501
+ return normalized || void 0;
502
+ }
503
+ function requesterIdentityWithEmail(requester) {
504
+ const email = normalizeRequesterEmail(requester?.email);
505
+ if (!email) return void 0;
506
+ return {
507
+ email,
508
+ ...requester?.fullName ? { fullName: requester.fullName } : {},
509
+ ...requester?.slackUserId ? { slackUserId: requester.slackUserId } : {},
510
+ ...requester?.slackUserName ? { slackUserName: requester.slackUserName } : {}
511
+ };
512
+ }
513
+ function mergeRequesterIdentity(current, next) {
514
+ return {
515
+ email: current.email,
516
+ ...current.fullName ?? next.fullName ? { fullName: current.fullName ?? next.fullName } : {},
517
+ ...current.slackUserId ?? next.slackUserId ? { slackUserId: current.slackUserId ?? next.slackUserId } : {},
518
+ ...current.slackUserName ?? next.slackUserName ? { slackUserName: current.slackUserName ?? next.slackUserName } : {}
519
+ };
520
+ }
521
+ function reportDate(value) {
522
+ const time = reportTime(value);
523
+ if (time === void 0) return void 0;
524
+ return new Date(time).toISOString().slice(0, 10);
525
+ }
526
+ function emptyRequesterTotals() {
527
+ return {
528
+ active: 0,
529
+ activeDays: 0,
530
+ conversations: 0,
531
+ durationMs: 0,
532
+ failed: 0,
533
+ hung: 0,
534
+ runs: 0
535
+ };
536
+ }
537
+ function addRequesterTokens(target, tokens) {
538
+ if (tokens !== void 0) {
539
+ target.tokens = (target.tokens ?? 0) + tokens;
540
+ }
541
+ }
542
+ function addStatsSignals(item, signals) {
543
+ item.active += signals.active ? 1 : 0;
544
+ item.failed += signals.failed ? 1 : 0;
545
+ item.hung += signals.hung ? 1 : 0;
546
+ }
547
+ function addConversationSignals(target, signals) {
548
+ target.active += signals.active ? 1 : 0;
549
+ target.failed += signals.failed ? 1 : 0;
550
+ target.hung += signals.hung ? 1 : 0;
551
+ }
552
+ function directoryItem(accumulator) {
553
+ return {
554
+ active: accumulator.active,
555
+ activeDays: accumulator.activeDates.size,
556
+ conversations: accumulator.conversations,
557
+ durationMs: accumulator.durationMs,
558
+ failed: accumulator.failed,
559
+ firstSeenAt: new Date(accumulator.firstSeenMs).toISOString(),
560
+ hung: accumulator.hung,
561
+ lastSeenAt: new Date(accumulator.lastSeenMs).toISOString(),
562
+ requester: accumulator.requester,
563
+ runs: accumulator.runs,
564
+ ...accumulator.tokens !== void 0 ? { tokens: accumulator.tokens } : {}
565
+ };
566
+ }
567
+ function emptyRequesterActivityDay(date) {
568
+ return {
569
+ active: 0,
570
+ conversations: 0,
571
+ date,
572
+ durationMs: 0,
573
+ failed: 0,
574
+ hung: 0,
575
+ runs: 0
576
+ };
577
+ }
578
+ function requesterActivityDays(args) {
579
+ const items = [];
580
+ const end = new Date(args.nowMs);
581
+ end.setUTCHours(0, 0, 0, 0);
582
+ const start = new Date(end);
583
+ start.setUTCDate(start.getUTCDate() - (REQUESTER_PROFILE_ACTIVITY_DAYS - 1));
584
+ for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
585
+ const date = cursor.toISOString().slice(0, 10);
586
+ items.push(args.days.get(date) ?? emptyRequesterActivityDay(date));
587
+ }
588
+ return items;
589
+ }
590
+ async function requesterSample(options = {}) {
591
+ const conversations = await conversationStore(options).listByActivity({
592
+ limit: REQUESTER_PROFILE_SAMPLE_LIMIT + 1
593
+ });
594
+ return {
595
+ conversations: conversations.slice(0, REQUESTER_PROFILE_SAMPLE_LIMIT),
596
+ truncated: conversations.length > REQUESTER_PROFILE_SAMPLE_LIMIT
597
+ };
598
+ }
599
+ async function readRequesterDirectoryReport(options = {}) {
600
+ const nowMs = Date.now();
601
+ const { conversations, truncated } = await requesterSample(options);
602
+ const detailsByConversationId = await getConversationDetailsForIds(
603
+ conversations.map((conversation) => conversation.conversationId)
604
+ );
605
+ const reportsByConversation = await reportsFromConversations({
606
+ conversations,
607
+ detailsByConversationId,
608
+ nowMs
609
+ });
610
+ const people = /* @__PURE__ */ new Map();
611
+ for (const conversation of conversations) {
612
+ const reports = [
613
+ ...reportsByConversation.get(conversation.conversationId) ?? [
614
+ sessionReportFromConversation(
615
+ conversation,
616
+ nowMs,
617
+ detailsByConversationId.get(conversation.conversationId)
618
+ )
619
+ ]
620
+ ].sort(
621
+ (left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
622
+ );
623
+ const newest = newestRun(reports);
624
+ const requester = requesterIdentityWithEmail(newest.requesterIdentity);
625
+ if (!requester) continue;
626
+ const lastSeenMs = reportTime(newest.lastSeenAt) ?? conversation.lastActivityAtMs;
627
+ const firstSeenMs = reportTime(reports[0]?.startedAt ?? newest.startedAt) ?? conversation.createdAtMs;
628
+ const contributions = runContributions(reports);
629
+ const signals = statusSignals(reports);
630
+ const date = reportDate(newest.lastSeenAt);
631
+ const email = requester.email;
632
+ const accumulator = people.get(email) ?? {
633
+ ...emptyRequesterTotals(),
634
+ activeDates: /* @__PURE__ */ new Set(),
635
+ firstSeenMs,
636
+ lastSeenMs,
637
+ requester
638
+ };
639
+ accumulator.requester = mergeRequesterIdentity(
640
+ accumulator.requester,
641
+ requester
642
+ );
643
+ accumulator.conversations += 1;
644
+ accumulator.runs += reports.length;
645
+ accumulator.durationMs += contributionDurationTotal(contributions);
646
+ addRequesterTokens(accumulator, contributionTokenTotal(contributions));
647
+ addConversationSignals(accumulator, signals);
648
+ accumulator.firstSeenMs = Math.min(accumulator.firstSeenMs, firstSeenMs);
649
+ accumulator.lastSeenMs = Math.max(accumulator.lastSeenMs, lastSeenMs);
650
+ if (date) accumulator.activeDates.add(date);
651
+ people.set(email, accumulator);
652
+ }
653
+ return {
654
+ generatedAt: new Date(nowMs).toISOString(),
655
+ people: [...people.values()].map(directoryItem).sort(
656
+ (left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.conversations - left.conversations || left.requester.email.localeCompare(right.requester.email)
657
+ ),
658
+ sampleLimit: REQUESTER_PROFILE_SAMPLE_LIMIT,
659
+ sampleSize: conversations.length,
660
+ source: "conversation_index",
661
+ truncated
662
+ };
663
+ }
664
+ async function readRequesterProfileReport(email, options = {}) {
665
+ const nowMs = Date.now();
666
+ const normalizedEmail = normalizeRequesterEmail(email) ?? email;
667
+ const { conversations, truncated } = await requesterSample(options);
668
+ const detailsByConversationId = await getConversationDetailsForIds(
669
+ conversations.map((conversation) => conversation.conversationId)
670
+ );
671
+ const reportsByConversation = await reportsFromConversations({
672
+ conversations,
673
+ detailsByConversationId,
674
+ nowMs
675
+ });
676
+ const matchingConversations = conversations.filter((conversation) => {
677
+ const reports = reportsByConversation.get(conversation.conversationId) ?? [];
678
+ const report = reports.length > 0 ? newestRun(reports) : sessionReportFromConversation(
679
+ conversation,
680
+ nowMs,
681
+ detailsByConversationId.get(conversation.conversationId)
682
+ );
683
+ return requesterIdentityWithEmail(report.requesterIdentity)?.email === normalizedEmail;
684
+ });
685
+ let requester;
686
+ const totals = emptyRequesterTotals();
687
+ const activeDates = /* @__PURE__ */ new Set();
688
+ const activityDays = /* @__PURE__ */ new Map();
689
+ const locations = /* @__PURE__ */ new Map();
690
+ const surfaces = /* @__PURE__ */ new Map();
691
+ const recentConversations = [];
692
+ for (const conversation of matchingConversations) {
693
+ const reports = [
694
+ ...reportsByConversation.get(conversation.conversationId) ?? [
695
+ sessionReportFromConversation(
696
+ conversation,
697
+ nowMs,
698
+ detailsByConversationId.get(conversation.conversationId)
699
+ )
700
+ ]
701
+ ].sort(
702
+ (left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
703
+ );
704
+ const newest = newestRun(reports);
705
+ const identity = requesterIdentityWithEmail(newest.requesterIdentity);
706
+ if (identity) {
707
+ requester = requester ? mergeRequesterIdentity(requester, identity) : identity;
708
+ }
709
+ recentConversations.push(newest);
710
+ const contributions = runContributions(reports);
711
+ const signals = statusSignals(reports);
712
+ const durationMs = contributionDurationTotal(contributions);
713
+ const tokens = contributionTokenTotal(contributions);
714
+ const date = reportDate(newest.lastSeenAt);
715
+ totals.conversations += 1;
716
+ totals.runs += reports.length;
717
+ totals.durationMs += durationMs;
718
+ addRequesterTokens(totals, tokens);
719
+ addConversationSignals(totals, signals);
720
+ if (date) {
721
+ activeDates.add(date);
722
+ const day = activityDays.get(date) ?? emptyRequesterActivityDay(date);
723
+ day.conversations += 1;
724
+ day.runs += reports.length;
725
+ day.durationMs += durationMs;
726
+ addRequesterTokens(day, tokens);
727
+ addConversationSignals(day, signals);
728
+ activityDays.set(date, day);
729
+ }
730
+ const location = locationLabel(newest);
731
+ const locationItem = locations.get(location) ?? emptyStatsItem(location);
732
+ locationItem.conversations += 1;
733
+ locationItem.runs += reports.length;
734
+ locationItem.durationMs += durationMs;
735
+ addItemTokens(locationItem, tokens);
736
+ addStatsSignals(locationItem, signals);
737
+ locations.set(location, locationItem);
738
+ const surface = surfaceFallbackLabel(newest.surface);
739
+ const surfaceItem = surfaces.get(surface) ?? emptyStatsItem(surface);
740
+ surfaceItem.conversations += 1;
741
+ surfaceItem.runs += reports.length;
742
+ surfaceItem.durationMs += durationMs;
743
+ addItemTokens(surfaceItem, tokens);
744
+ addStatsSignals(surfaceItem, signals);
745
+ surfaces.set(surface, surfaceItem);
746
+ }
747
+ totals.activeDays = activeDates.size;
748
+ const end = new Date(nowMs);
749
+ end.setUTCHours(0, 0, 0, 0);
750
+ const start = new Date(end);
751
+ start.setUTCDate(start.getUTCDate() - (REQUESTER_PROFILE_ACTIVITY_DAYS - 1));
752
+ return {
753
+ activityDays: requesterActivityDays({ days: activityDays, nowMs }),
754
+ generatedAt: new Date(nowMs).toISOString(),
755
+ locations: statsItems(locations),
756
+ recentConversations: recentConversations.sort(
757
+ (left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.conversationId.localeCompare(left.conversationId)
758
+ ).slice(0, REQUESTER_PROFILE_RECENT_LIMIT),
759
+ requester: requester ?? { email: normalizedEmail },
760
+ sampleLimit: REQUESTER_PROFILE_SAMPLE_LIMIT,
761
+ sampleSize: conversations.length,
762
+ source: "conversation_index",
763
+ surfaces: statsItems(surfaces),
764
+ totals,
765
+ truncated,
766
+ windowEnd: end.toISOString(),
767
+ windowStart: start.toISOString()
768
+ };
769
+ }
501
770
  function canExposeConversationTranscript(summary) {
502
771
  return canExposeConversationPayload({
503
772
  conversationId: summary.conversationId
@@ -694,7 +963,8 @@ function subagentActivity(entry, options) {
694
963
  ...end ? {
695
964
  endedAt: new Date(end.createdAtMs).toISOString(),
696
965
  outcome: end.outcome,
697
- status: end.outcome
966
+ status: end.outcome,
967
+ ...options.canExposeTranscript && end.transcriptStartMessageIndex !== void 0 && end.transcriptEndMessageIndex !== void 0 ? { transcriptAvailable: true } : {}
698
968
  } : { status: options.parentStatus ?? "running" }
699
969
  };
700
970
  }
@@ -714,6 +984,7 @@ function buildConversationActivity(args) {
714
984
  }
715
985
  const parentStatus = entry.parentToolCallId ? toolStatuses.get(entry.parentToolCallId) : void 0;
716
986
  const activity = subagentActivity(entry, {
987
+ canExposeTranscript: args.canExposePayload,
717
988
  end: subagentEnds.get(entry.subagentInvocationId),
718
989
  parentStatus
719
990
  });
@@ -810,6 +1081,62 @@ function traceIdFromTranscript(transcript) {
810
1081
  }
811
1082
  return void 0;
812
1083
  }
1084
+ function subagentTranscriptReport(activity, options = {}) {
1085
+ return {
1086
+ type: "subagent",
1087
+ ...options.subagentConversationId ? { subagentConversationId: options.subagentConversationId } : {},
1088
+ createdAt: activity.createdAt,
1089
+ id: activity.id,
1090
+ status: activity.status,
1091
+ ...options.subagentSentryConversationUrl ? { subagentSentryConversationUrl: options.subagentSentryConversationUrl } : {},
1092
+ subagentKind: activity.subagentKind,
1093
+ transcript: options.transcript ?? [],
1094
+ transcriptAvailable: Boolean(options.transcript?.length),
1095
+ ...activity.endedAt ? { endedAt: activity.endedAt } : {},
1096
+ ...activity.outcome ? { outcome: activity.outcome } : {},
1097
+ ...activity.parentToolCallId ? { parentToolCallId: activity.parentToolCallId } : {},
1098
+ ...options.transcriptMessageCount !== void 0 ? { transcriptMessageCount: options.transcriptMessageCount } : {},
1099
+ ...options.transcriptRedacted ? { transcriptRedacted: options.transcriptRedacted } : {},
1100
+ ...options.transcriptRedactionReason ? { transcriptRedactionReason: options.transcriptRedactionReason } : {},
1101
+ ...options.unavailableReason ? { unavailableReason: options.unavailableReason } : {}
1102
+ };
1103
+ }
1104
+ function subagentConversationFields(ref) {
1105
+ if (ref.type !== "advisor_session") {
1106
+ return {};
1107
+ }
1108
+ const subagentConversationId = ref.key;
1109
+ const subagentSentryConversationUrl = buildSentryConversationUrl(
1110
+ subagentConversationId
1111
+ );
1112
+ return {
1113
+ subagentConversationId,
1114
+ ...subagentSentryConversationUrl ? { subagentSentryConversationUrl } : {}
1115
+ };
1116
+ }
1117
+ var piMessageSchema = z.object({
1118
+ content: z.array(z.unknown()),
1119
+ role: z.string().min(1)
1120
+ }).passthrough().transform((value) => value);
1121
+ async function readTranscriptRefMessages(ref) {
1122
+ if (ref.type !== "advisor_session") {
1123
+ return [];
1124
+ }
1125
+ const stateAdapter = getStateAdapter();
1126
+ await stateAdapter.connect();
1127
+ const value = await stateAdapter.get(ref.key);
1128
+ const parsed = z.array(piMessageSchema).safeParse(value);
1129
+ return parsed.success ? parsed.data : [];
1130
+ }
1131
+ function transcriptSliceBounds(end) {
1132
+ if (end?.transcriptStartMessageIndex === void 0 || end.transcriptEndMessageIndex === void 0 || end.transcriptEndMessageIndex < end.transcriptStartMessageIndex) {
1133
+ return void 0;
1134
+ }
1135
+ return {
1136
+ end: end.transcriptEndMessageIndex,
1137
+ start: end.transcriptStartMessageIndex
1138
+ };
1139
+ }
813
1140
  async function summariesByConversation(conversations) {
814
1141
  const entries = await Promise.all(
815
1142
  conversations.map(async (conversation) => {
@@ -858,7 +1185,7 @@ async function readConversationFeed(options = {}) {
858
1185
  return {
859
1186
  source: "conversation_index",
860
1187
  generatedAt: new Date(nowMs).toISOString(),
861
- sessions: conversations.map(
1188
+ conversations: conversations.map(
862
1189
  (conversation) => newestRun(
863
1190
  reportsByConversation.get(conversation.conversationId) ?? [
864
1191
  sessionReportFromConversation(
@@ -880,7 +1207,7 @@ async function readConversationStatsReport(options = {}) {
880
1207
  });
881
1208
  const truncated = conversations.length > CONVERSATION_STATS_LIMIT;
882
1209
  const sampledConversations = conversations.slice(0, CONVERSATION_STATS_LIMIT);
883
- const sessions = sampledConversations.map(
1210
+ const summaries = sampledConversations.map(
884
1211
  (conversation) => sessionReportFromConversation(conversation, nowMs)
885
1212
  );
886
1213
  return buildConversationStatsReport({
@@ -888,7 +1215,7 @@ async function readConversationStatsReport(options = {}) {
888
1215
  nowMs,
889
1216
  sampleLimit: CONVERSATION_STATS_LIMIT,
890
1217
  sampleSize: sampledConversations.length,
891
- sessions,
1218
+ summaries,
892
1219
  truncated
893
1220
  });
894
1221
  }
@@ -1008,13 +1335,83 @@ async function readConversationReport(conversationId, options = {}) {
1008
1335
  ];
1009
1336
  const firstRun = effectiveRuns[0];
1010
1337
  const displayTitle = firstRun?.displayTitle ?? displayTitleFromDetails(conversationId, details) ?? surfaceFallbackLabel(firstRun?.surface ?? "slack");
1338
+ const sentryConversationUrl = buildSentryConversationUrl(conversationId);
1011
1339
  return {
1012
1340
  conversationId,
1013
1341
  displayTitle,
1014
1342
  generatedAt: new Date(nowMs).toISOString(),
1343
+ ...sentryConversationUrl ? { sentryConversationUrl } : {},
1015
1344
  runs: effectiveRuns
1016
1345
  };
1017
1346
  }
1347
+ async function readConversationSubagentTranscriptReport(conversationId, runId, subagentId) {
1348
+ const summaries = await listAgentTurnSessionSummariesForConversation(conversationId);
1349
+ const summary = summaries.find((candidate) => candidate.sessionId === runId);
1350
+ if (!summary) {
1351
+ return {
1352
+ type: "subagent",
1353
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1354
+ id: subagentId,
1355
+ status: "error",
1356
+ subagentKind: "unknown",
1357
+ transcript: [],
1358
+ transcriptAvailable: false,
1359
+ unavailableReason: "not_found"
1360
+ };
1361
+ }
1362
+ const entries = await loadActivityEntries({
1363
+ conversationId,
1364
+ sessionId: runId
1365
+ });
1366
+ const start = entries.find(
1367
+ (entry) => entry.type === "subagent_started" && entry.subagentInvocationId === subagentId
1368
+ );
1369
+ const end = entries.find(
1370
+ (entry) => entry.type === "subagent_ended" && entry.subagentInvocationId === subagentId
1371
+ );
1372
+ if (!start) {
1373
+ return {
1374
+ type: "subagent",
1375
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1376
+ id: subagentId,
1377
+ status: "error",
1378
+ subagentKind: "unknown",
1379
+ transcript: [],
1380
+ transcriptAvailable: false,
1381
+ unavailableReason: "not_found"
1382
+ };
1383
+ }
1384
+ const canExposeTranscript = canExposeConversationTranscript(summary);
1385
+ const activity = subagentActivity(start, { canExposeTranscript, end });
1386
+ const conversationFields = subagentConversationFields(start.transcriptRef);
1387
+ if (!canExposeTranscript) {
1388
+ return subagentTranscriptReport(activity, {
1389
+ ...conversationFields,
1390
+ transcriptRedacted: true,
1391
+ transcriptRedactionReason: "non_public_conversation"
1392
+ });
1393
+ }
1394
+ const bounds = transcriptSliceBounds(end);
1395
+ if (!bounds) {
1396
+ return subagentTranscriptReport(activity, {
1397
+ ...conversationFields,
1398
+ unavailableReason: "missing_transcript_range"
1399
+ });
1400
+ }
1401
+ const messages = await readTranscriptRefMessages(start.transcriptRef);
1402
+ if (messages.length === 0) {
1403
+ return subagentTranscriptReport(activity, {
1404
+ ...conversationFields,
1405
+ unavailableReason: "missing_transcript_ref"
1406
+ });
1407
+ }
1408
+ const transcript = messages.slice(0, bounds.end).map(normalizeTranscriptMessage);
1409
+ return subagentTranscriptReport(activity, {
1410
+ ...conversationFields,
1411
+ transcript,
1412
+ transcriptMessageCount: countConversationMessages(transcript)
1413
+ });
1414
+ }
1018
1415
 
1019
1416
  // src/reporting.ts
1020
1417
  function readDescriptionText() {
@@ -1068,8 +1465,10 @@ function createJuniorReporting() {
1068
1465
  },
1069
1466
  getPlugins: readPlugins,
1070
1467
  getSkills: readSkills,
1071
- getSessions: () => readConversationFeed({ conversationStore: conversationStore2 }),
1468
+ listConversations: () => readConversationFeed({ conversationStore: conversationStore2 }),
1072
1469
  getConversationStats: () => readConversationStatsReport({ conversationStore: conversationStore2 }),
1470
+ listRequesters: () => readRequesterDirectoryReport({ conversationStore: conversationStore2 }),
1471
+ getRequesterProfile: (email) => readRequesterProfileReport(email, { conversationStore: conversationStore2 }),
1073
1472
  listRecentConversations: listRecent,
1074
1473
  getPluginOperationalReports: async () => {
1075
1474
  const nowMs = Date.now();
@@ -1081,7 +1480,12 @@ function createJuniorReporting() {
1081
1480
  })
1082
1481
  };
1083
1482
  },
1084
- getConversation: (conversationId) => readConversationReport(conversationId, { conversationStore: conversationStore2 })
1483
+ getConversation: (conversationId) => readConversationReport(conversationId, { conversationStore: conversationStore2 }),
1484
+ getConversationSubagentTranscript: (conversationId, runId, subagentId) => readConversationSubagentTranscriptReport(
1485
+ conversationId,
1486
+ runId,
1487
+ subagentId
1488
+ )
1085
1489
  };
1086
1490
  }
1087
1491
  export {
@@ -14,26 +14,26 @@ import {
14
14
  startActiveTurn,
15
15
  updateConversationStats,
16
16
  upsertConversationMessage
17
- } from "./chunk-NC6LR6U4.js";
17
+ } from "./chunk-BFPOSIYV.js";
18
18
  import {
19
19
  coerceThreadConversationState
20
20
  } from "./chunk-66NX7MNW.js";
21
- import "./chunk-3JKTVW4R.js";
21
+ import "./chunk-MZ4DUVFD.js";
22
22
  import "./chunk-KNFROR7R.js";
23
23
  import {
24
24
  commitMessages,
25
25
  loadProjection
26
- } from "./chunk-YA2JCC7G.js";
27
- import "./chunk-TR2G37II.js";
28
- import "./chunk-GUO4EE7L.js";
29
- import "./chunk-PQ2U2AO3.js";
26
+ } from "./chunk-JUI6XIHU.js";
27
+ import "./chunk-KNVN4HUT.js";
28
+ import "./chunk-CWMMGUWM.js";
29
+ import "./chunk-AAD2D7K3.js";
30
30
  import "./chunk-G3E7SCME.js";
31
- import "./chunk-X2FL5ZH5.js";
32
- import "./chunk-4XHCVBXH.js";
31
+ import "./chunk-MJ4E2PK7.js";
32
+ import "./chunk-BAPRSWNW.js";
33
33
  import {
34
34
  stripRuntimeTurnContext,
35
35
  trimTrailingAssistantMessages
36
- } from "./chunk-ABRNFE3N.js";
36
+ } from "./chunk-ALL7GG6U.js";
37
37
  import "./chunk-DR75T7J3.js";
38
38
  import "./chunk-WCXVEQPI.js";
39
39
  import "./chunk-TW23AT22.js";