@sentry/junior 0.94.0 → 0.96.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 (127) hide show
  1. package/dist/{agent-hooks-ZG7IDOIN.js → agent-hooks-5URSMY3O.js} +10 -11
  2. package/dist/api/people/list.js +8 -8
  3. package/dist/api/people/profile.js +8 -8
  4. package/dist/api/people/shared.d.ts +2 -2
  5. package/dist/api-reference.d.ts +2 -2
  6. package/dist/app.js +434 -199
  7. package/dist/{catalog-runtime-NZHMSMYC.js → catalog-runtime-MSMAYYIK.js} +4 -4
  8. package/dist/chat/advisor-request.d.ts +4 -0
  9. package/dist/chat/agent/request.d.ts +2 -0
  10. package/dist/chat/agent/resume.d.ts +2 -0
  11. package/dist/chat/conversations/history.d.ts +128 -0
  12. package/dist/chat/conversations/legacy-import.d.ts +38 -0
  13. package/dist/chat/conversations/messages.d.ts +39 -0
  14. package/dist/chat/conversations/projection.d.ts +94 -0
  15. package/dist/chat/conversations/retention.d.ts +36 -0
  16. package/dist/chat/conversations/sql/conversation-row.d.ts +14 -0
  17. package/dist/chat/conversations/sql/history.d.ts +4 -0
  18. package/dist/chat/conversations/sql/legacy-history-import.d.ts +55 -0
  19. package/dist/chat/conversations/sql/messages.d.ts +4 -0
  20. package/dist/chat/conversations/sql/migrations.d.ts +14 -3
  21. package/dist/chat/conversations/sql/purge.d.ts +53 -0
  22. package/dist/chat/conversations/sql/store.d.ts +7 -2
  23. package/dist/chat/conversations/store.d.ts +19 -0
  24. package/dist/chat/conversations/visible-compactions.d.ts +11 -0
  25. package/dist/chat/conversations/visible-messages.d.ts +47 -0
  26. package/dist/chat/db.d.ts +12 -1
  27. package/dist/chat/identities/sql.d.ts +1 -1
  28. package/dist/chat/logging.d.ts +4 -3
  29. package/dist/chat/plugins/auth/oauth-request.d.ts +2 -0
  30. package/dist/chat/resource-events/notification.d.ts +2 -0
  31. package/dist/chat/resource-events/store.d.ts +2 -1
  32. package/dist/chat/runtime/agent-run-outcome.d.ts +3 -0
  33. package/dist/chat/runtime/reply-executor.d.ts +8 -0
  34. package/dist/chat/runtime/thread-state.d.ts +2 -0
  35. package/dist/chat/sandbox/resources.d.ts +4 -0
  36. package/dist/chat/sentry.d.ts +1 -1
  37. package/dist/chat/services/mcp-auth-orchestration.d.ts +8 -0
  38. package/dist/chat/services/plugin-auth-orchestration.d.ts +12 -0
  39. package/dist/chat/services/turn-session-record.d.ts +14 -2
  40. package/dist/chat/sleep.d.ts +2 -0
  41. package/dist/chat/state/conversation.d.ts +7 -4
  42. package/dist/chat/state/session-log.d.ts +23 -1
  43. package/dist/chat/state/turn-session.d.ts +9 -1
  44. package/dist/chat/tool-support/private-trace-result.d.ts +4 -0
  45. package/dist/chat/tool-support/zod-tool.d.ts +1 -0
  46. package/dist/chat/tools/advisor/tool.d.ts +9 -4
  47. package/dist/chat/tools/definition.d.ts +2 -0
  48. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
  49. package/dist/chat/tools/system-time.d.ts +8 -0
  50. package/dist/chat/usage.d.ts +27 -13
  51. package/dist/chat/xml.d.ts +2 -0
  52. package/dist/{chunk-TNUUATT7.js → chunk-2NAZZPPS.js} +2 -2
  53. package/dist/{chunk-NYWPISSO.js → chunk-2XXEKR44.js} +1 -1
  54. package/dist/chunk-5C6XTTFI.js +1037 -0
  55. package/dist/{chunk-3DA7X2U3.js → chunk-6GWA276C.js} +15 -0
  56. package/dist/{chunk-LFLTJQVR.js → chunk-7FBGKXPE.js} +4 -0
  57. package/dist/{chunk-LNCREA5Z.js → chunk-AAZXTVJT.js} +4 -128
  58. package/dist/{chunk-GHGPTPBL.js → chunk-B7PUBQQM.js} +1 -1
  59. package/dist/{chunk-6H4PP63X.js → chunk-BYZWPRJA.js} +24 -14
  60. package/dist/chunk-CM5EZD5F.js +17 -0
  61. package/dist/{chunk-G3E7SCME.js → chunk-CZEX6KIQ.js} +23 -0
  62. package/dist/{chunk-YJ4OPVNA.js → chunk-DZENG5ML.js} +1 -1
  63. package/dist/{chunk-QGEWC27H.js → chunk-GQ3GK22G.js} +5 -7
  64. package/dist/{chunk-YYBX2BD5.js → chunk-GY34VGP5.js} +1 -1
  65. package/dist/{chunk-S6QKIGRM.js → chunk-I35CTGPO.js} +71 -29
  66. package/dist/{chunk-KRMANDHI.js → chunk-IYM2SIVG.js} +1489 -355
  67. package/dist/{chunk-BTH37NCU.js → chunk-L7CDEIVJ.js} +12 -4
  68. package/dist/chunk-MNTINMWR.js +865 -0
  69. package/dist/{chunk-MAVOJ54A.js → chunk-P3YFQLEN.js} +25 -388
  70. package/dist/{chunk-YTNPU7I2.js → chunk-PMNN6ST4.js} +2 -2
  71. package/dist/{chunk-PFXC67GJ.js → chunk-RAIB6WSG.js} +6 -80
  72. package/dist/{chunk-NFXSLTYG.js → chunk-RITD7E5L.js} +864 -62
  73. package/dist/{chunk-UUB3D2XI.js → chunk-S5KZ5LPG.js} +8 -10
  74. package/dist/{chunk-HDNWZMUN.js → chunk-UKHVMPX5.js} +112 -103
  75. package/dist/chunk-WM6KHPBQ.js +48 -0
  76. package/dist/{chunk-MC4ZZE4Q.js → chunk-YDT7SNCN.js} +1 -1
  77. package/dist/{chunk-NAPITCZ6.js → chunk-YDVEH3SB.js} +251 -6
  78. package/dist/cli/chat.js +25 -24
  79. package/dist/cli/check.js +5 -5
  80. package/dist/cli/main.js +1 -1
  81. package/dist/cli/plugins.js +12 -13
  82. package/dist/cli/snapshot-warmup.js +8 -9
  83. package/dist/cli/upgrade.js +79 -18
  84. package/dist/db/schema/agent-steps.d.ts +134 -0
  85. package/dist/db/schema/conversation-messages.d.ts +153 -0
  86. package/dist/{chat/conversations/sql → db}/schema/conversations.d.ts +34 -0
  87. package/dist/{chat/conversations/sql → db}/schema.d.ts +319 -8
  88. package/dist/db-2D6RMG67.js +24 -0
  89. package/dist/deployment.d.ts +2 -0
  90. package/dist/handlers/retention.d.ts +6 -0
  91. package/dist/instrumentation.js +17 -4
  92. package/dist/legacy-import-VZ2MEGP7.js +17 -0
  93. package/dist/nitro.js +14 -4
  94. package/dist/reporting/conversations/activity.d.ts +40 -0
  95. package/dist/reporting/conversations/context.d.ts +8 -0
  96. package/dist/reporting/conversations/details.d.ts +6 -0
  97. package/dist/reporting/conversations/index.d.ts +4 -0
  98. package/dist/reporting/conversations/shared.d.ts +9 -0
  99. package/dist/reporting/conversations/statistics.d.ts +4 -0
  100. package/dist/reporting/conversations/summaries.d.ts +20 -0
  101. package/dist/reporting/conversations/transcript.d.ts +28 -0
  102. package/dist/reporting/conversations/types.d.ts +191 -0
  103. package/dist/reporting/conversations.d.ts +1 -178
  104. package/dist/reporting.d.ts +5 -4
  105. package/dist/reporting.js +703 -837
  106. package/dist/{runner-XAZPJNAQ.js → runner-JZBRQP4F.js} +36 -42
  107. package/dist/{sentry-EGFOV3DH.js → sentry-JMWTS32N.js} +11 -1
  108. package/dist/{validation-KYAWETI4.js → validation-6XOOQGME.js} +5 -5
  109. package/migrations/README.md +32 -0
  110. package/migrations/meta/0000_snapshot.json +978 -0
  111. package/migrations/meta/_journal.json +13 -0
  112. package/package.json +10 -7
  113. package/dist/chat/sql/schema.d.ts +0 -1049
  114. package/dist/chat/state/conversation-details.d.ts +0 -47
  115. package/dist/chunk-7STN2ZSK.js +0 -250
  116. package/dist/chunk-LE5ANBUZ.js +0 -1040
  117. package/dist/chunk-QL66APYJ.js +0 -1377
  118. package/dist/db-YDN6AFJH.js +0 -18
  119. /package/dist/{chat/sql → db}/db.d.ts +0 -0
  120. /package/dist/{chat/sql → db}/executor.d.ts +0 -0
  121. /package/dist/{chat/sql → db}/neon.d.ts +0 -0
  122. /package/dist/{chat/sql → db}/postgres.d.ts +0 -0
  123. /package/dist/{chat/conversations/sql → db}/schema/destinations.d.ts +0 -0
  124. /package/dist/{chat/conversations/sql → db}/schema/identities.d.ts +0 -0
  125. /package/dist/{chat/conversations/sql → db}/schema/migrations.d.ts +0 -0
  126. /package/dist/{chat/conversations/sql → db}/schema/timestamps.d.ts +0 -0
  127. /package/dist/{chat/conversations/sql → db}/schema/users.d.ts +0 -0
package/dist/reporting.js CHANGED
@@ -3,539 +3,65 @@ import {
3
3
  buildSentryConversationUrl,
4
4
  buildSentryTraceUrl,
5
5
  formatSlackConversationRedactedLabel,
6
- getConversationDetails,
7
- getConversationDetailsForIds,
8
6
  resolveSlackConversationContextFromThreadId
9
- } from "./chunk-LNCREA5Z.js";
7
+ } from "./chunk-AAZXTVJT.js";
10
8
  import {
11
- buildSystemPrompt
12
- } from "./chunk-LE5ANBUZ.js";
9
+ unwrapAdvisorRequest
10
+ } from "./chunk-WM6KHPBQ.js";
13
11
  import {
14
- getAgentTurnSessionRecord,
15
12
  listAgentTurnSessionSummariesForConversation,
16
- loadActivityEntries,
17
- piContentMessageSchema
18
- } from "./chunk-QL66APYJ.js";
13
+ listAgentTurnSessionSummariesForConversations,
14
+ loadProjection,
15
+ projectSteps
16
+ } from "./chunk-MNTINMWR.js";
17
+ import "./chunk-5C6XTTFI.js";
19
18
  import {
20
19
  getPluginOperationalReports
21
- } from "./chunk-UUB3D2XI.js";
22
- import "./chunk-TNUUATT7.js";
23
- import "./chunk-G3E7SCME.js";
24
- import {
25
- getStateAdapter
26
- } from "./chunk-7STN2ZSK.js";
20
+ } from "./chunk-S5KZ5LPG.js";
21
+ import "./chunk-2NAZZPPS.js";
22
+ import "./chunk-CZEX6KIQ.js";
27
23
  import {
24
+ getAgentStepStore,
25
+ getConversationMessageStore,
28
26
  getConversationStore
29
- } from "./chunk-NFXSLTYG.js";
27
+ } from "./chunk-RITD7E5L.js";
30
28
  import {
31
29
  unwrapCurrentInstruction
32
- } from "./chunk-NAPITCZ6.js";
30
+ } from "./chunk-YDVEH3SB.js";
33
31
  import {
34
32
  canExposeConversationPayload,
35
33
  parseSlackThreadId,
36
34
  resolveConversationPrivacy
37
- } from "./chunk-YYBX2BD5.js";
38
- import "./chunk-GHGPTPBL.js";
35
+ } from "./chunk-GY34VGP5.js";
36
+ import "./chunk-B7PUBQQM.js";
39
37
  import {
40
38
  discoverSkills
41
- } from "./chunk-YTNPU7I2.js";
39
+ } from "./chunk-PMNN6ST4.js";
42
40
  import {
43
41
  pluginCatalogRuntime
44
- } from "./chunk-BTH37NCU.js";
45
- import {
46
- toStoredSlackActor
47
- } from "./chunk-S6QKIGRM.js";
42
+ } from "./chunk-L7CDEIVJ.js";
43
+ import "./chunk-I35CTGPO.js";
48
44
  import {
49
45
  isRecord
50
46
  } from "./chunk-OB42YVAE.js";
51
- import "./chunk-3DA7X2U3.js";
47
+ import "./chunk-6GWA276C.js";
52
48
  import {
53
49
  homeDir
54
50
  } from "./chunk-6APU57E6.js";
55
- import "./chunk-LFLTJQVR.js";
51
+ import "./chunk-7FBGKXPE.js";
56
52
  import "./chunk-MLKGABMK.js";
57
53
 
58
54
  // src/reporting.ts
59
55
  import { readFileSync } from "fs";
60
56
  import path from "path";
61
57
 
62
- // src/reporting/conversations.ts
63
- import { z } from "zod";
64
- var HUNG_TURN_PROGRESS_MS = 5 * 60 * 1e3;
65
- var SAFE_METADATA_KEY_LIMIT = 20;
66
- var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
67
- var CONVERSATION_FEED_LIMIT = 50;
68
- var CONVERSATION_STATS_LIMIT = 5e3;
69
- var RECENT_CONVERSATION_STATS_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
70
- function privateConversationLabel(slackConversation) {
71
- if (!slackConversation) {
72
- return PRIVATE_CONVERSATION_LABEL;
73
- }
74
- return slackConversation.visibility === "private" ? formatSlackConversationRedactedLabel(slackConversation) ?? PRIVATE_CONVERSATION_LABEL : PRIVATE_CONVERSATION_LABEL;
75
- }
58
+ // src/reporting/conversations/context.ts
76
59
  function conversationStore(options = {}) {
77
60
  return options.conversationStore ?? getConversationStore();
78
61
  }
79
- function statusFromCheckpoint(summary, nowMs = Date.now()) {
80
- const state = summary.state;
81
- if (state === "running" && nowMs - summary.lastProgressAtMs > HUNG_TURN_PROGRESS_MS) {
82
- return "hung";
83
- }
84
- if (state === "running" || state === "awaiting_resume") {
85
- return "active";
86
- }
87
- if (state === "abandoned") {
88
- return "superseded";
89
- }
90
- return state;
91
- }
92
- function surfaceFromConversationId(conversationId) {
93
- if (parseSlackThreadId(conversationId)) return "slack";
94
- if (conversationId.startsWith("scheduler:")) return "scheduler";
95
- if (conversationId.startsWith("api:")) return "api";
96
- return "internal";
97
- }
98
- function surfaceFromSummary(summary) {
99
- return summary.surface ?? surfaceFromConversationId(summary.conversationId);
100
- }
101
- function surfaceFromSource(source, conversationId) {
102
- if (source === "slack" || source === "api" || source === "scheduler") {
103
- return source;
104
- }
105
- return surfaceFromConversationId(conversationId);
106
- }
107
- function actorIdentityReport(actor) {
108
- if (!actor) return void 0;
109
- const identity = {
110
- ...actor.email !== void 0 ? { email: actor.email } : {},
111
- ...actor.fullName !== void 0 ? { fullName: actor.fullName } : {},
112
- ...actor.slackUserId !== void 0 ? { slackUserId: actor.slackUserId } : {},
113
- ...actor.slackUserName !== void 0 ? { slackUserName: actor.slackUserName } : {}
114
- };
115
- return Object.keys(identity).length > 0 ? identity : void 0;
116
- }
117
- function sessionActorIdentityReport(actor) {
118
- return actor?.platform === "slack" ? actorIdentityReport(toStoredSlackActor(actor)) : void 0;
119
- }
120
- function usageReport(usage) {
121
- if (!usage) return void 0;
122
- const report = {
123
- ...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
124
- ...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
125
- ...usage.cachedInputTokens !== void 0 ? { cachedInputTokens: usage.cachedInputTokens } : {},
126
- ...usage.cacheCreationTokens !== void 0 ? { cacheCreationTokens: usage.cacheCreationTokens } : {},
127
- ...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {}
128
- };
129
- return Object.keys(report).length > 0 ? report : void 0;
130
- }
131
- function sessionReportFromSummary(summary, nowMs = Date.now(), details, visibility) {
132
- const slackThread = parseSlackThreadId(summary.conversationId);
133
- const privacy = resolveConversationPrivacy({
134
- conversationId: summary.conversationId,
135
- visibility
136
- });
137
- const effectiveChannelName = details?.channelName ?? summary.channelName;
138
- const slackConversation = resolveSlackConversationContextFromThreadId({
139
- threadId: summary.conversationId,
140
- channelName: effectiveChannelName
141
- });
142
- const privateLabel = privacy !== "public" ? privateConversationLabel(slackConversation) : void 0;
143
- const channelName = privateLabel ?? effectiveChannelName;
144
- const effectiveSurface = details?.originSurface ?? surfaceFromSummary(summary);
145
- const displayTitle = privateLabel ?? details?.displayTitle ?? slackStatsLocationLabel({
146
- channel: slackThread?.channelId,
147
- channelName: effectiveChannelName
148
- }) ?? surfaceFallbackLabel(effectiveSurface);
149
- const actorIdentity = actorIdentityReport(details?.originActor) ?? sessionActorIdentityReport(summary.actor);
150
- const sentryTraceUrl = summary.traceId ? buildSentryTraceUrl(summary.traceId) : void 0;
151
- const cumulativeUsage = usageReport(summary.cumulativeUsage);
152
- return {
153
- conversationId: summary.conversationId,
154
- displayTitle,
155
- id: summary.sessionId,
156
- status: statusFromCheckpoint(summary, nowMs),
157
- startedAt: new Date(summary.startedAtMs).toISOString(),
158
- lastProgressAt: new Date(summary.lastProgressAtMs).toISOString(),
159
- lastSeenAt: new Date(summary.updatedAtMs).toISOString(),
160
- ...summary.state === "completed" ? { completedAt: new Date(summary.updatedAtMs).toISOString() } : {},
161
- cumulativeDurationMs: summary.cumulativeDurationMs,
162
- ...cumulativeUsage ? { cumulativeUsage } : {},
163
- surface: effectiveSurface,
164
- ...actorIdentity ? { actorIdentity } : {},
165
- ...slackThread ? { channel: slackThread.channelId } : {},
166
- ...channelName ? { channelName } : {},
167
- ...privateLabel ? { channelNameRedacted: true } : {},
168
- ...summary.traceId ? { traceId: summary.traceId } : {},
169
- ...sentryTraceUrl ? { sentryTraceUrl } : {}
170
- };
171
- }
172
- function statusFromConversation(conversation, fallback, nowMs) {
173
- if (fallback) {
174
- return fallback;
175
- }
176
- if (conversation.execution.status === "idle") {
177
- return "completed";
178
- }
179
- if (conversation.execution.status === "failed") {
180
- return "failed";
181
- }
182
- const updatedAtMs = conversation.execution.updatedAtMs ?? conversation.updatedAtMs;
183
- if (conversation.execution.status === "running" && nowMs - updatedAtMs > HUNG_TURN_PROGRESS_MS) {
184
- return "hung";
185
- }
186
- return "active";
187
- }
188
- function titleFromConversation(args) {
189
- const slackThread = parseSlackThreadId(args.conversation.conversationId);
190
- const effectiveChannelName = args.details?.channelName ?? args.conversation.channelName;
191
- const slackConversation = resolveSlackConversationContextFromThreadId({
192
- threadId: args.conversation.conversationId,
193
- channelName: effectiveChannelName
194
- });
195
- const privateLabel = resolveConversationPrivacy({
196
- conversationId: args.conversation.conversationId,
197
- visibility: args.conversation.visibility
198
- }) !== "public" ? privateConversationLabel(slackConversation) : void 0;
199
- return privateLabel ?? args.details?.displayTitle ?? args.conversation.title ?? slackStatsLocationLabel({
200
- channel: slackThread?.channelId,
201
- channelName: effectiveChannelName
202
- }) ?? surfaceFallbackLabel(args.surface);
203
- }
204
- function channelNameFromConversation(conversation, details) {
205
- const effectiveChannelName = details?.channelName ?? conversation.channelName;
206
- const slackThread = parseSlackThreadId(conversation.conversationId);
207
- if (!effectiveChannelName && !slackThread) {
208
- return void 0;
209
- }
210
- const slackConversation = resolveSlackConversationContextFromThreadId({
211
- threadId: conversation.conversationId,
212
- channelName: effectiveChannelName
213
- });
214
- if (resolveConversationPrivacy({
215
- conversationId: conversation.conversationId,
216
- visibility: conversation.visibility
217
- }) !== "public") {
218
- return privateConversationLabel(slackConversation);
219
- }
220
- return effectiveChannelName;
221
- }
222
- function channelNameRedactedFromConversation(conversation, details) {
223
- const effectiveChannelName = details?.channelName ?? conversation.channelName;
224
- const slackThread = parseSlackThreadId(conversation.conversationId);
225
- if (!effectiveChannelName && !slackThread) {
226
- return false;
227
- }
228
- return resolveConversationPrivacy({
229
- conversationId: conversation.conversationId,
230
- visibility: conversation.visibility
231
- }) !== "public";
232
- }
233
- function applyConversationIndexMetadata(args) {
234
- const surface = args.details?.originSurface ?? (args.conversation.source ? surfaceFromSource(
235
- args.conversation.source,
236
- args.conversation.conversationId
237
- ) : args.report.surface);
238
- const slackThread = parseSlackThreadId(args.conversation.conversationId);
239
- const effectiveChannelName = channelNameFromConversation(args.conversation, args.details) ?? args.report.channelName;
240
- const channelNameRedacted = channelNameRedactedFromConversation(
241
- args.conversation,
242
- args.details
243
- );
244
- const actorIdentity = actorIdentityReport(args.details?.originActor) ?? args.report.actorIdentity ?? actorIdentityReport(args.conversation.actor);
245
- const status = statusFromConversation(
246
- args.conversation,
247
- args.report.status,
248
- args.nowMs
249
- );
250
- const lastSeenAtMs = Math.max(
251
- reportTime(args.report.lastSeenAt) ?? 0,
252
- args.conversation.lastActivityAtMs
253
- );
254
- const { channelNameRedacted: _oldChannelNameRedacted, ...report } = args.report;
255
- return {
256
- ...report,
257
- displayTitle: titleFromConversation({
258
- conversation: args.conversation,
259
- details: args.details,
260
- surface
261
- }),
262
- status,
263
- lastSeenAt: new Date(lastSeenAtMs).toISOString(),
264
- surface,
265
- ...actorIdentity ? { actorIdentity } : {},
266
- ...slackThread ? { channel: slackThread.channelId } : {},
267
- ...effectiveChannelName ? { channelName: effectiveChannelName } : {},
268
- ...channelNameRedacted ? { channelNameRedacted: true } : {}
269
- };
270
- }
271
- function sessionReportFromConversation(conversation, nowMs, details) {
272
- const surface = details?.originSurface ?? surfaceFromSource(conversation.source, conversation.conversationId);
273
- const actorIdentity = actorIdentityReport(
274
- details?.originActor ?? conversation.actor
275
- );
276
- const slackThread = parseSlackThreadId(conversation.conversationId);
277
- const channelName = channelNameFromConversation(conversation, details);
278
- const channelNameRedacted = channelNameRedactedFromConversation(
279
- conversation,
280
- details
281
- );
282
- return {
283
- conversationId: conversation.conversationId,
284
- cumulativeDurationMs: 0,
285
- displayTitle: titleFromConversation({ conversation, details, surface }),
286
- id: conversation.execution.runId ?? conversation.conversationId,
287
- lastProgressAt: new Date(
288
- conversation.execution.updatedAtMs ?? conversation.updatedAtMs
289
- ).toISOString(),
290
- lastSeenAt: new Date(conversation.lastActivityAtMs).toISOString(),
291
- startedAt: new Date(conversation.createdAtMs).toISOString(),
292
- status: statusFromConversation(conversation, void 0, nowMs),
293
- surface,
294
- ...actorIdentity ? { actorIdentity } : {},
295
- ...slackThread ? { channel: slackThread.channelId } : {},
296
- ...channelName ? { channelName } : {},
297
- ...channelNameRedacted ? { channelNameRedacted: true } : {}
298
- };
299
- }
300
- function reportTime(value) {
301
- const time = Date.parse(value);
302
- return Number.isFinite(time) ? time : void 0;
303
- }
304
- function usageTokenTotal(usage) {
305
- if (!usage) return void 0;
306
- const components = [
307
- usage.inputTokens,
308
- usage.outputTokens,
309
- usage.cachedInputTokens,
310
- usage.cacheCreationTokens
311
- ].reduce((sum, value) => {
312
- const count = typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
313
- return count === void 0 ? sum : (sum ?? 0) + count;
314
- }, void 0);
315
- if (components !== void 0) {
316
- return components;
317
- }
318
- return typeof usage.totalTokens === "number" && Number.isFinite(usage.totalTokens) ? Math.max(0, Math.floor(usage.totalTokens)) : void 0;
319
- }
320
- function runDurationSnapshot(run) {
321
- return typeof run.cumulativeDurationMs === "number" && Number.isFinite(run.cumulativeDurationMs) ? Math.max(0, Math.floor(run.cumulativeDurationMs)) : void 0;
322
- }
323
- function runContributions(runs) {
324
- let previousDuration = 0;
325
- let previousTokens = 0;
326
- return runs.map((run) => {
327
- const duration = runDurationSnapshot(run);
328
- const tokens = usageTokenTotal(run.cumulativeUsage);
329
- const contribution = {
330
- durationMs: duration === void 0 ? 0 : Math.max(0, duration - previousDuration),
331
- run
332
- };
333
- if (tokens !== void 0) {
334
- contribution.tokens = Math.max(0, tokens - previousTokens);
335
- }
336
- if (duration !== void 0) {
337
- previousDuration = Math.max(previousDuration, duration);
338
- }
339
- if (tokens !== void 0) {
340
- previousTokens = Math.max(previousTokens, tokens);
341
- }
342
- return contribution;
343
- });
344
- }
345
- function contributionDurationTotal(contributions) {
346
- return contributions.reduce(
347
- (sum, contribution) => sum + contribution.durationMs,
348
- 0
349
- );
350
- }
351
- function addTokenTotal(total, tokens) {
352
- return tokens === void 0 ? total : (total ?? 0) + tokens;
353
- }
354
- function contributionTokenTotal(contributions) {
355
- return contributions.reduce(
356
- (sum, contribution) => addTokenTotal(sum, contribution.tokens),
357
- void 0
358
- );
359
- }
360
- function actorLabel(actor) {
361
- const email = actor?.email?.trim() || void 0;
362
- const fullName = actor?.fullName?.trim() || void 0;
363
- const slackUserName = actor?.slackUserName?.trim() || void 0;
364
- return email ?? fullName ?? slackUserName ?? actor?.slackUserId;
365
- }
366
- function slackStatsLocationLabel(input) {
367
- const channelId = input.channel;
368
- if (!channelId) return void 0;
369
- if (input.channelNameRedacted && input.channelName) {
370
- return input.channelName;
371
- }
372
- const name = input.channelName?.replace(/^#/, "");
373
- if (channelId.startsWith("D")) {
374
- return "Direct Message";
375
- }
376
- if (channelId.startsWith("C")) {
377
- return name ? `#${name}` : "Public Channel";
378
- }
379
- if (channelId.startsWith("G")) {
380
- if (name?.startsWith("mpdm-")) return "Group DM";
381
- return "Private Channel";
382
- }
383
- return name || channelId;
384
- }
385
- function surfaceFallbackLabel(surface) {
386
- if (surface === "scheduler") return "Scheduler";
387
- if (surface === "api") return "API";
388
- if (surface === "internal") return "Internal";
389
- return "Conversation";
390
- }
391
- function displayTitleFromDetails(conversationId, details, visibility) {
392
- if (!details) return void 0;
393
- const slackThread = parseSlackThreadId(conversationId);
394
- const slackConversation = resolveSlackConversationContextFromThreadId({
395
- threadId: conversationId,
396
- channelName: details.channelName
397
- });
398
- const privateLabel = resolveConversationPrivacy({ conversationId, visibility }) !== "public" ? privateConversationLabel(slackConversation) : void 0;
399
- return privateLabel ?? details.displayTitle ?? slackStatsLocationLabel({
400
- channel: slackThread?.channelId,
401
- channelName: details.channelName
402
- }) ?? (details.originSurface ? surfaceFallbackLabel(details.originSurface) : void 0);
403
- }
404
- function locationLabel(run) {
405
- return slackStatsLocationLabel(run) ?? surfaceFallbackLabel(run.surface);
406
- }
407
- function emptyStatsItem(label) {
408
- return {
409
- active: 0,
410
- conversations: 0,
411
- durationMs: 0,
412
- failed: 0,
413
- hung: 0,
414
- label,
415
- runs: 0
416
- };
417
- }
418
- function addItemTokens(item, tokens) {
419
- if (tokens !== void 0) {
420
- item.tokens = (item.tokens ?? 0) + tokens;
421
- }
422
- }
423
- function statusSignals(runs) {
424
- return {
425
- active: runs.some((run) => run.status === "active"),
426
- failed: runs.some((run) => run.status === "failed"),
427
- hung: runs.some((run) => run.status === "hung")
428
- };
429
- }
430
- function statsItems(map) {
431
- return [...map.values()].sort(
432
- (left, right) => right.conversations - left.conversations || right.runs - left.runs || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
433
- );
434
- }
435
- function newestRun(runs) {
436
- return [...runs].sort(
437
- (left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.id.localeCompare(left.id)
438
- )[0];
439
- }
440
- function recentConversationGroups(args) {
441
- const startMs = args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS;
442
- const groups = /* @__PURE__ */ new Map();
443
- for (const summary of args.summaries) {
444
- groups.set(summary.conversationId, [
445
- ...groups.get(summary.conversationId) ?? [],
446
- summary
447
- ]);
448
- }
449
- return [...groups.values()].map(
450
- (runs) => [...runs].sort(
451
- (left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
452
- )
453
- ).filter((runs) => {
454
- const activityAt = reportTime(newestRun(runs).lastSeenAt);
455
- return activityAt !== void 0 && activityAt >= startMs && activityAt <= args.nowMs;
456
- });
457
- }
458
- function conversationDurationMs(runs) {
459
- if (!runs.some((run) => runDurationSnapshot(run) !== void 0)) {
460
- return 0;
461
- }
462
- return contributionDurationTotal(runContributions(runs));
463
- }
464
- function buildConversationStatsReport(args) {
465
- const conversations = recentConversationGroups(args);
466
- const actors = /* @__PURE__ */ new Map();
467
- const locations = /* @__PURE__ */ new Map();
468
- let durationMs = 0;
469
- let tokens;
470
- let active = 0;
471
- let failed = 0;
472
- let hung = 0;
473
- for (const runs of conversations) {
474
- const contributions = runContributions(runs);
475
- const conversationSignals = statusSignals(runs);
476
- const conversationTokens = contributionTokenTotal(contributions);
477
- durationMs += contributionDurationTotal(contributions);
478
- tokens = addTokenTotal(tokens, conversationTokens);
479
- active += conversationSignals.active ? 1 : 0;
480
- failed += conversationSignals.failed ? 1 : 0;
481
- hung += conversationSignals.hung ? 1 : 0;
482
- const actorRuns = /* @__PURE__ */ new Map();
483
- for (const contribution of contributions) {
484
- const actor = actorLabel(contribution.run.actorIdentity) ?? "Unknown";
485
- actorRuns.set(actor, [...actorRuns.get(actor) ?? [], contribution]);
486
- }
487
- for (const [actor, actorContributions] of actorRuns) {
488
- const item = actors.get(actor) ?? emptyStatsItem(actor);
489
- const signals = statusSignals(
490
- actorContributions.map((contribution) => contribution.run)
491
- );
492
- item.conversations += 1;
493
- item.runs += actorContributions.length;
494
- item.durationMs += contributionDurationTotal(actorContributions);
495
- item.active += signals.active ? 1 : 0;
496
- item.failed += signals.failed ? 1 : 0;
497
- item.hung += signals.hung ? 1 : 0;
498
- addItemTokens(item, contributionTokenTotal(actorContributions));
499
- actors.set(actor, item);
500
- }
501
- const location = locationLabel(newestRun(runs));
502
- const locationItem = locations.get(location) ?? emptyStatsItem(location);
503
- locationItem.conversations += 1;
504
- locationItem.runs += runs.length;
505
- locationItem.durationMs += conversationDurationMs(runs);
506
- locationItem.active += conversationSignals.active ? 1 : 0;
507
- locationItem.failed += conversationSignals.failed ? 1 : 0;
508
- locationItem.hung += conversationSignals.hung ? 1 : 0;
509
- addItemTokens(locationItem, conversationTokens);
510
- locations.set(location, locationItem);
511
- }
512
- return {
513
- active,
514
- conversations: conversations.length,
515
- durationMs,
516
- failed,
517
- generatedAt: args.generatedAt,
518
- hung,
519
- locations: statsItems(locations),
520
- actors: statsItems(actors),
521
- sampleLimit: args.sampleLimit,
522
- sampleSize: args.sampleSize,
523
- source: "conversation_index",
524
- ...tokens !== void 0 ? { tokens } : {},
525
- truncated: args.truncated,
526
- runs: conversations.reduce((sum, runs) => sum + runs.length, 0),
527
- windowEnd: new Date(args.nowMs).toISOString(),
528
- windowStart: new Date(
529
- args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS
530
- ).toISOString()
531
- };
532
- }
533
- function canExposeConversationTranscript(summary, visibility) {
534
- return canExposeConversationPayload({
535
- conversationId: summary.conversationId,
536
- visibility
537
- });
538
- }
62
+
63
+ // src/reporting/conversations/transcript.ts
64
+ var SAFE_METADATA_KEY_LIMIT = 20;
539
65
  function textPart(text) {
540
66
  return { type: "text", text };
541
67
  }
@@ -548,7 +74,14 @@ function recordField(value, names) {
548
74
  return void 0;
549
75
  }
550
76
  function normalizeTranscriptPart(part, options = {}) {
551
- const displayText = (text) => options.unwrapCurrentTask ? unwrapCurrentInstruction(text) ?? text : text;
77
+ const displayText = (text) => {
78
+ if (options.unwrapCurrentTask) {
79
+ const instruction = unwrapCurrentInstruction(text);
80
+ if (instruction !== void 0) return instruction;
81
+ }
82
+ if (options.unwrapAdvisorTask) return unwrapAdvisorRequest(text) ?? text;
83
+ return text;
84
+ };
552
85
  if (typeof part === "string") {
553
86
  return textPart(displayText(part));
554
87
  }
@@ -609,7 +142,7 @@ function normalizeToolResultMessage(record) {
609
142
  output
610
143
  };
611
144
  }
612
- function normalizeTranscriptMessage(message) {
145
+ function normalizeTranscriptMessage(message, options = {}) {
613
146
  const record = message;
614
147
  const content = record.content;
615
148
  const role = transcriptRole(record.role);
@@ -618,10 +151,12 @@ function normalizeTranscriptMessage(message) {
618
151
  ...typeof record.timestamp === "number" ? { timestamp: record.timestamp } : {},
619
152
  parts: role === "toolResult" ? [normalizeToolResultMessage(record)] : Array.isArray(content) ? content.map(
620
153
  (part) => normalizeTranscriptPart(part, {
154
+ unwrapAdvisorTask: options.unwrapAdvisorTask && role === "user",
621
155
  unwrapCurrentTask: role === "user"
622
156
  })
623
157
  ) : [
624
158
  normalizeTranscriptPart(content, {
159
+ unwrapAdvisorTask: options.unwrapAdvisorTask && role === "user",
625
160
  unwrapCurrentTask: role === "user"
626
161
  })
627
162
  ]
@@ -690,105 +225,23 @@ function redactTranscriptPart(part) {
690
225
  type: "tool_result",
691
226
  redacted: true,
692
227
  ...part.id ? { id: part.id } : {},
693
- ...part.name ? { name: part.name } : {},
694
- ...redactedPayloadFields("output", part.output)
695
- };
696
- }
697
- return {
698
- type: "unknown",
699
- redacted: true,
700
- ...part.sourceType ? { sourceType: part.sourceType } : {},
701
- ...redactedPayloadFields("output", part.output ?? part.input ?? part.text)
702
- };
703
- }
704
- function redactTranscriptMessage(message) {
705
- return {
706
- role: message.role,
707
- ...typeof message.timestamp === "number" ? { timestamp: message.timestamp } : {},
708
- parts: message.parts.map(redactTranscriptPart)
709
- };
710
- }
711
- function toolResultStatuses(messages) {
712
- const statuses = /* @__PURE__ */ new Map();
713
- for (const message of messages) {
714
- const record = message;
715
- if (record.role !== "toolResult" || typeof record.toolCallId !== "string") {
716
- continue;
717
- }
718
- statuses.set(record.toolCallId, record.isError ? "error" : "completed");
719
- }
720
- return statuses;
721
- }
722
- function activityPayloadFields(args, canExposePayload) {
723
- if (args === void 0) {
724
- return {};
725
- }
726
- return canExposePayload ? { args } : { redacted: true, ...redactedPayloadFields("input", args) };
727
- }
728
- function subagentActivity(entry, options) {
729
- const end = options.end;
730
- return {
731
- type: "subagent",
732
- id: entry.subagentInvocationId,
733
- subagentKind: entry.subagentKind,
734
- ...entry.parentToolCallId ? { parentToolCallId: entry.parentToolCallId } : {},
735
- createdAt: new Date(entry.createdAtMs).toISOString(),
736
- ...end ? {
737
- endedAt: new Date(end.createdAtMs).toISOString(),
738
- outcome: end.outcome,
739
- status: end.outcome,
740
- ...options.canExposeTranscript && end.transcriptStartMessageIndex !== void 0 && end.transcriptEndMessageIndex !== void 0 ? { transcriptAvailable: true } : {}
741
- } : { status: options.parentStatus ?? "running" }
742
- };
743
- }
744
- function buildConversationActivity(args) {
745
- const toolStatuses = toolResultStatuses(args.messages);
746
- const subagentEnds = /* @__PURE__ */ new Map();
747
- const subagentsByToolCallId = /* @__PURE__ */ new Map();
748
- const orphanSubagents = [];
749
- for (const entry of args.entries) {
750
- if (entry.type === "subagent_ended") {
751
- subagentEnds.set(entry.subagentInvocationId, entry);
752
- }
753
- }
754
- for (const entry of args.entries) {
755
- if (entry.type !== "subagent_started") {
756
- continue;
757
- }
758
- const parentStatus = entry.parentToolCallId ? toolStatuses.get(entry.parentToolCallId) : void 0;
759
- const activity = subagentActivity(entry, {
760
- canExposeTranscript: args.canExposePayload,
761
- end: subagentEnds.get(entry.subagentInvocationId),
762
- parentStatus
763
- });
764
- if (entry.parentToolCallId) {
765
- subagentsByToolCallId.set(entry.parentToolCallId, [
766
- ...subagentsByToolCallId.get(entry.parentToolCallId) ?? [],
767
- activity
768
- ]);
769
- continue;
770
- }
771
- orphanSubagents.push(activity);
772
- }
773
- const rows = [];
774
- for (const entry of args.entries) {
775
- if (entry.type !== "tool_execution_started") {
776
- continue;
777
- }
778
- rows.push({
779
- type: "tool_execution",
780
- id: entry.toolCallId,
781
- toolCallId: entry.toolCallId,
782
- toolName: entry.toolName,
783
- createdAt: new Date(entry.createdAtMs).toISOString(),
784
- status: toolStatuses.get(entry.toolCallId) ?? "running",
785
- subagents: subagentsByToolCallId.get(entry.toolCallId) ?? [],
786
- ...activityPayloadFields(entry.args, args.canExposePayload)
787
- });
228
+ ...part.name ? { name: part.name } : {},
229
+ ...redactedPayloadFields("output", part.output)
230
+ };
788
231
  }
789
- return [...rows, ...orphanSubagents].sort(
790
- (left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt) || left.id.localeCompare(right.id)
791
- );
232
+ return {
233
+ type: "unknown",
234
+ redacted: true,
235
+ ...part.sourceType ? { sourceType: part.sourceType } : {},
236
+ ...redactedPayloadFields("output", part.output ?? part.input ?? part.text)
237
+ };
238
+ }
239
+ function redactTranscriptMessage(message) {
240
+ return {
241
+ role: message.role,
242
+ ...typeof message.timestamp === "number" ? { timestamp: message.timestamp } : {},
243
+ parts: message.parts.map(redactTranscriptPart)
244
+ };
792
245
  }
793
246
  function isConversationMessageRole(role) {
794
247
  return role === "user" || role === "assistant";
@@ -808,38 +261,6 @@ function isConversationMessage(message) {
808
261
  function countConversationMessages(transcript) {
809
262
  return transcript.filter(isConversationMessage).length;
810
263
  }
811
- function systemPromptMessage(source) {
812
- return {
813
- role: "system",
814
- parts: [
815
- {
816
- type: "text",
817
- text: buildSystemPrompt({ source })
818
- }
819
- ]
820
- };
821
- }
822
- function turnScopedMessages(messages, turnStartMessageIndex) {
823
- if (turnStartMessageIndex !== void 0 && turnStartMessageIndex >= 0 && turnStartMessageIndex < messages.length) {
824
- return {
825
- messages: messages.slice(turnStartMessageIndex),
826
- startsAtRunBoundary: turnStartMessageIndex === 0
827
- };
828
- }
829
- for (let index = messages.length - 1; index >= 0; index -= 1) {
830
- const record = messages[index];
831
- if (record.role === "user") {
832
- return {
833
- messages: messages.slice(index),
834
- startsAtRunBoundary: index === 0
835
- };
836
- }
837
- }
838
- return {
839
- messages,
840
- startsAtRunBoundary: messages.length > 0
841
- };
842
- }
843
264
  function traceIdFromTranscript(transcript) {
844
265
  for (const message of transcript) {
845
266
  for (const part of message.parts) {
@@ -860,6 +281,7 @@ function subagentTranscriptReport(activity, options = {}) {
860
281
  ...options.subagentConversationId ? { subagentConversationId: options.subagentConversationId } : {},
861
282
  createdAt: activity.createdAt,
862
283
  id: activity.id,
284
+ ...activity.modelId ? { modelId: activity.modelId } : {},
863
285
  status: activity.status,
864
286
  ...options.subagentSentryConversationUrl ? { subagentSentryConversationUrl: options.subagentSentryConversationUrl } : {},
865
287
  subagentKind: activity.subagentKind,
@@ -868,77 +290,313 @@ function subagentTranscriptReport(activity, options = {}) {
868
290
  ...activity.endedAt ? { endedAt: activity.endedAt } : {},
869
291
  ...activity.outcome ? { outcome: activity.outcome } : {},
870
292
  ...activity.parentToolCallId ? { parentToolCallId: activity.parentToolCallId } : {},
293
+ ...activity.reasoningLevel ? { reasoningLevel: activity.reasoningLevel } : {},
871
294
  ...options.transcriptMessageCount !== void 0 ? { transcriptMessageCount: options.transcriptMessageCount } : {},
872
295
  ...options.transcriptRedacted ? { transcriptRedacted: options.transcriptRedacted } : {},
873
296
  ...options.transcriptRedactionReason ? { transcriptRedactionReason: options.transcriptRedactionReason } : {},
297
+ ...options.transcriptExpired ? { transcriptExpired: options.transcriptExpired } : {},
298
+ ...options.transcriptExpiredAt ? { transcriptExpiredAt: options.transcriptExpiredAt } : {},
874
299
  ...options.unavailableReason ? { unavailableReason: options.unavailableReason } : {}
875
300
  };
876
301
  }
877
- function subagentConversationFields(ref) {
878
- if (ref.type !== "advisor_session") {
302
+
303
+ // src/reporting/conversations/activity.ts
304
+ function toolResultStatuses(messages) {
305
+ const statuses = /* @__PURE__ */ new Map();
306
+ for (const message of messages) {
307
+ const record = message;
308
+ if (record.role !== "toolResult" || typeof record.toolCallId !== "string") {
309
+ continue;
310
+ }
311
+ statuses.set(record.toolCallId, record.isError ? "error" : "completed");
312
+ }
313
+ return statuses;
314
+ }
315
+ function activityPayloadFields(args, canExposePayload) {
316
+ if (args === void 0) {
879
317
  return {};
880
318
  }
881
- const subagentConversationId = ref.key;
882
- const subagentSentryConversationUrl = buildSentryConversationUrl(
883
- subagentConversationId
319
+ return canExposePayload ? { args } : { redacted: true, ...redactedPayloadFields("input", args) };
320
+ }
321
+ function buildConversationActivityFromSteps(args) {
322
+ const toolStatuses = toolResultStatuses(args.messages);
323
+ const subagentEnds = /* @__PURE__ */ new Map();
324
+ const subagentsByToolCallId = /* @__PURE__ */ new Map();
325
+ const orphanSubagents = [];
326
+ for (const step of args.steps) {
327
+ if (step.entry.type === "subagent_ended") {
328
+ subagentEnds.set(
329
+ step.entry.subagentInvocationId,
330
+ step
331
+ );
332
+ }
333
+ }
334
+ for (const step of args.steps) {
335
+ if (step.entry.type !== "subagent_started") {
336
+ continue;
337
+ }
338
+ const start = step;
339
+ const parentStatus = start.entry.parentToolCallId ? toolStatuses.get(start.entry.parentToolCallId) : void 0;
340
+ const activity = subagentActivityFromSteps(
341
+ start,
342
+ subagentEnds.get(start.entry.subagentInvocationId),
343
+ { canExposeTranscript: args.canExposePayload, parentStatus }
344
+ );
345
+ if (start.entry.parentToolCallId) {
346
+ subagentsByToolCallId.set(start.entry.parentToolCallId, [
347
+ ...subagentsByToolCallId.get(start.entry.parentToolCallId) ?? [],
348
+ activity
349
+ ]);
350
+ continue;
351
+ }
352
+ orphanSubagents.push(activity);
353
+ }
354
+ const rows = [];
355
+ for (const step of args.steps) {
356
+ if (step.entry.type !== "tool_execution_started") {
357
+ continue;
358
+ }
359
+ rows.push({
360
+ type: "tool_execution",
361
+ id: step.entry.toolCallId,
362
+ toolCallId: step.entry.toolCallId,
363
+ toolName: step.entry.toolName,
364
+ createdAt: new Date(step.createdAtMs).toISOString(),
365
+ status: toolStatuses.get(step.entry.toolCallId) ?? "running",
366
+ subagents: subagentsByToolCallId.get(step.entry.toolCallId) ?? [],
367
+ ...activityPayloadFields(step.entry.args, args.canExposePayload)
368
+ });
369
+ }
370
+ return [...rows, ...orphanSubagents].sort(
371
+ (left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt) || left.id.localeCompare(right.id)
884
372
  );
373
+ }
374
+ function subagentActivityFromSteps(start, end, options = {}) {
885
375
  return {
886
- subagentConversationId,
887
- ...subagentSentryConversationUrl ? { subagentSentryConversationUrl } : {}
376
+ type: "subagent",
377
+ id: start.entry.subagentInvocationId,
378
+ subagentKind: start.entry.subagentKind,
379
+ ...start.entry.modelId ? { modelId: start.entry.modelId } : {},
380
+ ...start.entry.parentToolCallId ? { parentToolCallId: start.entry.parentToolCallId } : {},
381
+ ...start.entry.reasoningLevel ? { reasoningLevel: start.entry.reasoningLevel } : {},
382
+ createdAt: new Date(start.createdAtMs).toISOString(),
383
+ ...end ? {
384
+ endedAt: new Date(end.createdAtMs).toISOString(),
385
+ outcome: end.entry.outcome,
386
+ status: end.entry.outcome,
387
+ // Every subagent is a child conversation whose transcript loads on
388
+ // demand; expose the affordance only when the parent is public.
389
+ ...options.canExposeTranscript ? { transcriptAvailable: true } : {}
390
+ } : { status: options.parentStatus ?? "running" }
391
+ };
392
+ }
393
+
394
+ // src/reporting/conversations/shared.ts
395
+ function reportTime(value) {
396
+ const time = Date.parse(value);
397
+ return Number.isFinite(time) ? time : void 0;
398
+ }
399
+ function slackStatsLocationLabel(input) {
400
+ const channelId = input.channel;
401
+ if (!channelId) return void 0;
402
+ if (input.channelNameRedacted && input.channelName) {
403
+ return input.channelName;
404
+ }
405
+ const name = input.channelName?.replace(/^#/, "");
406
+ if (channelId.startsWith("D")) return "Direct Message";
407
+ if (channelId.startsWith("C")) return name ? `#${name}` : "Public Channel";
408
+ if (channelId.startsWith("G")) {
409
+ if (name?.startsWith("mpdm-")) return "Group DM";
410
+ return "Private Channel";
411
+ }
412
+ return name || channelId;
413
+ }
414
+ function surfaceFallbackLabel(surface) {
415
+ if (surface === "scheduler") return "Scheduler";
416
+ if (surface === "api") return "API";
417
+ if (surface === "internal") return "Internal";
418
+ return "Conversation";
419
+ }
420
+ function newestRun(runs) {
421
+ return [...runs].sort(
422
+ (left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.id.localeCompare(left.id)
423
+ )[0];
424
+ }
425
+
426
+ // src/reporting/conversations/summaries.ts
427
+ var HUNG_TURN_PROGRESS_MS = 5 * 60 * 1e3;
428
+ var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
429
+ var CONVERSATION_FEED_LIMIT = 50;
430
+ function privateConversationLabel(slackConversation) {
431
+ if (!slackConversation) {
432
+ return PRIVATE_CONVERSATION_LABEL;
433
+ }
434
+ return slackConversation.visibility === "private" ? formatSlackConversationRedactedLabel(slackConversation) ?? PRIVATE_CONVERSATION_LABEL : PRIVATE_CONVERSATION_LABEL;
435
+ }
436
+ function surfaceFromConversationId(conversationId) {
437
+ if (parseSlackThreadId(conversationId)) return "slack";
438
+ if (conversationId.startsWith("scheduler:")) return "scheduler";
439
+ if (conversationId.startsWith("api:")) return "api";
440
+ return "internal";
441
+ }
442
+ function surfaceFromSource(source, conversationId) {
443
+ if (source === "slack" || source === "api" || source === "scheduler") {
444
+ return source;
445
+ }
446
+ return surfaceFromConversationId(conversationId);
447
+ }
448
+ function actorIdentityReport(actor) {
449
+ if (!actor) return void 0;
450
+ if ("name" in actor) {
451
+ return { fullName: actor.name };
452
+ }
453
+ if ("userId" in actor) {
454
+ const identity2 = {
455
+ ...actor.email !== void 0 ? { email: actor.email } : {},
456
+ ...actor.fullName !== void 0 ? { fullName: actor.fullName } : {},
457
+ ...actor.platform === "slack" ? { slackUserId: actor.userId } : {},
458
+ ...actor.platform === "slack" && actor.userName !== void 0 ? { slackUserName: actor.userName } : {}
459
+ };
460
+ return Object.keys(identity2).length > 0 ? identity2 : void 0;
461
+ }
462
+ const identity = {
463
+ ...actor.email !== void 0 ? { email: actor.email } : {},
464
+ ...actor.fullName !== void 0 ? { fullName: actor.fullName } : {},
465
+ ...actor.slackUserId !== void 0 ? { slackUserId: actor.slackUserId } : {},
466
+ ...actor.slackUserName !== void 0 ? { slackUserName: actor.slackUserName } : {}
888
467
  };
468
+ return Object.keys(identity).length > 0 ? identity : void 0;
889
469
  }
890
- async function readTranscriptRefMessages(ref) {
891
- if (ref.type !== "advisor_session") {
892
- return [];
470
+ function statusFromConversation(conversation, fallback, nowMs) {
471
+ if (fallback) {
472
+ return fallback;
473
+ }
474
+ if (conversation.execution.status === "idle") {
475
+ return "completed";
476
+ }
477
+ if (conversation.execution.status === "failed") {
478
+ return "failed";
479
+ }
480
+ const updatedAtMs = conversation.execution.updatedAtMs ?? conversation.updatedAtMs;
481
+ if (conversation.execution.status === "running" && nowMs - updatedAtMs > HUNG_TURN_PROGRESS_MS) {
482
+ return "hung";
893
483
  }
894
- const stateAdapter = getStateAdapter();
895
- await stateAdapter.connect();
896
- const value = await stateAdapter.get(ref.key);
897
- const parsed = z.array(piContentMessageSchema).safeParse(value);
898
- return parsed.success ? parsed.data : [];
899
- }
900
- function transcriptSliceBounds(end) {
901
- if (end?.transcriptStartMessageIndex === void 0 || end.transcriptEndMessageIndex === void 0 || end.transcriptEndMessageIndex < end.transcriptStartMessageIndex) {
484
+ return "active";
485
+ }
486
+ function titleFromConversation(args) {
487
+ const slackThread = parseSlackThreadId(args.conversation.conversationId);
488
+ const effectiveChannelName = args.conversation.channelName;
489
+ const slackConversation = resolveSlackConversationContextFromThreadId({
490
+ threadId: args.conversation.conversationId,
491
+ channelName: effectiveChannelName
492
+ });
493
+ const privateLabel = resolveConversationPrivacy({
494
+ conversationId: args.conversation.conversationId,
495
+ visibility: args.conversation.visibility
496
+ }) !== "public" ? privateConversationLabel(slackConversation) : void 0;
497
+ return privateLabel ?? args.conversation.title ?? slackStatsLocationLabel({
498
+ channel: slackThread?.channelId,
499
+ channelName: effectiveChannelName
500
+ }) ?? surfaceFallbackLabel(args.surface);
501
+ }
502
+ function channelNameFromConversation(conversation) {
503
+ const effectiveChannelName = conversation.channelName;
504
+ const slackThread = parseSlackThreadId(conversation.conversationId);
505
+ if (!effectiveChannelName && !slackThread) {
902
506
  return void 0;
903
507
  }
508
+ const slackConversation = resolveSlackConversationContextFromThreadId({
509
+ threadId: conversation.conversationId,
510
+ channelName: effectiveChannelName
511
+ });
512
+ if (resolveConversationPrivacy({
513
+ conversationId: conversation.conversationId,
514
+ visibility: conversation.visibility
515
+ }) !== "public") {
516
+ return privateConversationLabel(slackConversation);
517
+ }
518
+ return effectiveChannelName;
519
+ }
520
+ function channelNameRedactedFromConversation(conversation) {
521
+ const effectiveChannelName = conversation.channelName;
522
+ const slackThread = parseSlackThreadId(conversation.conversationId);
523
+ if (!effectiveChannelName && !slackThread) {
524
+ return false;
525
+ }
526
+ return resolveConversationPrivacy({
527
+ conversationId: conversation.conversationId,
528
+ visibility: conversation.visibility
529
+ }) !== "public";
530
+ }
531
+ function sessionReportFromConversation(conversation, nowMs) {
532
+ const surface = surfaceFromSource(
533
+ conversation.source,
534
+ conversation.conversationId
535
+ );
536
+ const actorIdentity = actorIdentityReport(conversation.actor);
537
+ const slackThread = parseSlackThreadId(conversation.conversationId);
538
+ const channelName = channelNameFromConversation(conversation);
539
+ const channelNameRedacted = channelNameRedactedFromConversation(conversation);
904
540
  return {
905
- end: end.transcriptEndMessageIndex,
906
- start: end.transcriptStartMessageIndex
541
+ conversationId: conversation.conversationId,
542
+ cumulativeDurationMs: 0,
543
+ displayTitle: titleFromConversation({ conversation, surface }),
544
+ id: conversation.conversationId,
545
+ lastProgressAt: new Date(
546
+ conversation.execution.updatedAtMs ?? conversation.updatedAtMs
547
+ ).toISOString(),
548
+ lastSeenAt: new Date(conversation.lastActivityAtMs).toISOString(),
549
+ startedAt: new Date(conversation.createdAtMs).toISOString(),
550
+ status: statusFromConversation(conversation, void 0, nowMs),
551
+ surface,
552
+ ...actorIdentity ? { actorIdentity } : {},
553
+ ...slackThread ? { channel: slackThread.channelId } : {},
554
+ ...channelName ? { channelName } : {},
555
+ ...channelNameRedacted ? { channelNameRedacted: true } : {}
907
556
  };
908
557
  }
909
- async function summariesByConversation(conversations) {
910
- const entries = await Promise.all(
911
- conversations.map(async (conversation) => {
912
- const summaries = await listAgentTurnSessionSummariesForConversation(
913
- conversation.conversationId
914
- );
915
- return [conversation.conversationId, summaries];
916
- })
917
- );
918
- return new Map(entries);
558
+ function statusFromTurnSummary(summary, nowMs) {
559
+ if (summary.state === "running" && nowMs - summary.lastProgressAtMs > HUNG_TURN_PROGRESS_MS) {
560
+ return "hung";
561
+ }
562
+ if (summary.state === "running" || summary.state === "awaiting_resume") {
563
+ return "active";
564
+ }
565
+ if (summary.state === "abandoned") {
566
+ return "superseded";
567
+ }
568
+ return summary.state;
569
+ }
570
+ function sessionReportFromTurnSummary(conversation, summary, nowMs) {
571
+ const base = sessionReportFromConversation(conversation, nowMs);
572
+ if (summary.actor !== void 0) {
573
+ const actorIdentity = actorIdentityReport(summary.actor);
574
+ if (actorIdentity) {
575
+ base.actorIdentity = actorIdentity;
576
+ } else {
577
+ delete base.actorIdentity;
578
+ }
579
+ }
580
+ return {
581
+ ...base,
582
+ cumulativeDurationMs: summary.cumulativeDurationMs,
583
+ ...summary.cumulativeUsage ? { cumulativeUsage: summary.cumulativeUsage } : {},
584
+ id: summary.sessionId,
585
+ lastProgressAt: new Date(summary.lastProgressAtMs).toISOString(),
586
+ lastSeenAt: new Date(summary.updatedAtMs).toISOString(),
587
+ startedAt: new Date(summary.startedAtMs).toISOString(),
588
+ status: statusFromTurnSummary(summary, nowMs),
589
+ surface: summary.surface ?? base.surface,
590
+ ...summary.state === "completed" ? { completedAt: new Date(summary.updatedAtMs).toISOString() } : {},
591
+ ...summary.traceId ? { traceId: summary.traceId } : {}
592
+ };
919
593
  }
920
594
  async function reportsFromConversations(args) {
921
- const summaries = await summariesByConversation(args.conversations);
922
595
  const reports = /* @__PURE__ */ new Map();
923
596
  for (const conversation of args.conversations) {
924
- const details = args.detailsByConversationId.get(
925
- conversation.conversationId
926
- );
927
- const conversationSummaries = summaries.get(conversation.conversationId) ?? [];
928
- const conversationReports = conversationSummaries.length > 0 ? conversationSummaries.map(
929
- (summary) => applyConversationIndexMetadata({
930
- conversation,
931
- details,
932
- nowMs: args.nowMs,
933
- report: sessionReportFromSummary(
934
- summary,
935
- args.nowMs,
936
- details,
937
- conversation.visibility
938
- )
939
- })
940
- ) : [sessionReportFromConversation(conversation, args.nowMs, details)];
941
- reports.set(conversation.conversationId, conversationReports);
597
+ reports.set(conversation.conversationId, [
598
+ sessionReportFromConversation(conversation, args.nowMs)
599
+ ]);
942
600
  }
943
601
  return reports;
944
602
  }
@@ -948,12 +606,8 @@ async function readConversationFeed(options = {}) {
948
606
  const conversations = await store.listByActivity({
949
607
  limit: CONVERSATION_FEED_LIMIT
950
608
  });
951
- const detailsByConversationId = await getConversationDetailsForIds(
952
- conversations.map((conversation) => conversation.conversationId)
953
- );
954
609
  const reportsByConversation = await reportsFromConversations({
955
610
  conversations,
956
- detailsByConversationId,
957
611
  nowMs
958
612
  });
959
613
  return {
@@ -962,37 +616,12 @@ async function readConversationFeed(options = {}) {
962
616
  conversations: conversations.map(
963
617
  (conversation) => newestRun(
964
618
  reportsByConversation.get(conversation.conversationId) ?? [
965
- sessionReportFromConversation(
966
- conversation,
967
- nowMs,
968
- detailsByConversationId.get(conversation.conversationId)
969
- )
619
+ sessionReportFromConversation(conversation, nowMs)
970
620
  ]
971
621
  )
972
622
  )
973
623
  };
974
624
  }
975
- async function readConversationStatsReport(options = {}) {
976
- const store = conversationStore(options);
977
- const nowMs = Date.now();
978
- const generatedAt = new Date(nowMs).toISOString();
979
- const conversations = await store.listByActivity({
980
- limit: CONVERSATION_STATS_LIMIT + 1
981
- });
982
- const truncated = conversations.length > CONVERSATION_STATS_LIMIT;
983
- const sampledConversations = conversations.slice(0, CONVERSATION_STATS_LIMIT);
984
- const summaries = sampledConversations.map(
985
- (conversation) => sessionReportFromConversation(conversation, nowMs)
986
- );
987
- return buildConversationStatsReport({
988
- generatedAt,
989
- nowMs,
990
- sampleLimit: CONVERSATION_STATS_LIMIT,
991
- sampleSize: sampledConversations.length,
992
- summaries,
993
- truncated
994
- });
995
- }
996
625
  async function listRecentConversationSummaries(options = {}) {
997
626
  const store = conversationStore(options);
998
627
  const nowMs = Date.now();
@@ -1000,33 +629,25 @@ async function listRecentConversationSummaries(options = {}) {
1000
629
  const conversations = await store.listByActivity({
1001
630
  limit
1002
631
  });
1003
- const detailsByConversationId = await getConversationDetailsForIds(
1004
- conversations.map((conversation) => conversation.conversationId)
1005
- );
1006
632
  const reportsByConversation = await reportsFromConversations({
1007
633
  conversations,
1008
- detailsByConversationId,
1009
634
  nowMs
1010
635
  });
1011
636
  return conversations.map((conversation) => {
1012
- const details = detailsByConversationId.get(conversation.conversationId);
1013
637
  const surface = surfaceFromSource(
1014
638
  conversation.source,
1015
639
  conversation.conversationId
1016
640
  );
1017
- const channelName = channelNameFromConversation(conversation, details);
1018
- const channelNameRedacted = channelNameRedactedFromConversation(
1019
- conversation,
1020
- details
1021
- );
641
+ const channelName = channelNameFromConversation(conversation);
642
+ const channelNameRedacted = channelNameRedactedFromConversation(conversation);
1022
643
  const report = newestRun(
1023
644
  reportsByConversation.get(conversation.conversationId) ?? [
1024
- sessionReportFromConversation(conversation, nowMs, details)
645
+ sessionReportFromConversation(conversation, nowMs)
1025
646
  ]
1026
647
  );
1027
648
  return {
1028
649
  conversationId: conversation.conversationId,
1029
- displayTitle: titleFromConversation({ conversation, details, surface }),
650
+ displayTitle: titleFromConversation({ conversation, surface }),
1030
651
  lastActivityAt: new Date(conversation.lastActivityAtMs).toISOString(),
1031
652
  lastUpdatedAt: new Date(
1032
653
  conversation.execution.updatedAtMs ?? conversation.updatedAtMs
@@ -1038,94 +659,88 @@ async function listRecentConversationSummaries(options = {}) {
1038
659
  };
1039
660
  });
1040
661
  }
662
+
663
+ // src/reporting/conversations/details.ts
664
+ async function currentRunContent(args) {
665
+ const steps = await args.stepStore.loadCurrentEpoch(args.conversationId);
666
+ const messages = projectSteps(steps).messages;
667
+ const transcript = messages.length > 0 ? messages.map((message) => normalizeTranscriptMessage(message)) : (await args.messageStore.list(args.conversationId)).map(
668
+ visibleMessageTranscript
669
+ );
670
+ return {
671
+ activity: buildConversationActivityFromSteps({
672
+ canExposePayload: args.canExposePayload,
673
+ steps,
674
+ messages
675
+ }),
676
+ transcript
677
+ };
678
+ }
679
+ function visibleMessageTranscript(message) {
680
+ return {
681
+ role: message.role,
682
+ timestamp: message.createdAtMs,
683
+ parts: [{ type: "text", text: message.text }]
684
+ };
685
+ }
1041
686
  async function readConversationReport(conversationId, options = {}) {
1042
687
  const store = conversationStore(options);
1043
688
  const nowMs = Date.now();
1044
- const [rawSummaries, details, conversation] = await Promise.all([
1045
- listAgentTurnSessionSummariesForConversation(conversationId),
1046
- getConversationDetails(conversationId),
1047
- store.get({ conversationId })
1048
- ]);
1049
- const summaries = rawSummaries.sort(
1050
- (left, right) => left.startedAtMs - right.startedAtMs || left.updatedAtMs - right.updatedAtMs || left.sessionId.localeCompare(right.sessionId)
1051
- );
1052
- const runs = await Promise.all(
1053
- summaries.map(async (summary) => {
1054
- const [sessionRecord, activityEntries] = await Promise.all([
1055
- getAgentTurnSessionRecord(summary.conversationId, summary.sessionId),
1056
- loadActivityEntries({
1057
- conversationId: summary.conversationId,
1058
- sessionId: summary.sessionId
1059
- })
1060
- ]);
1061
- const scopedMessages = sessionRecord?.piMessages ? turnScopedMessages(
1062
- sessionRecord.piMessages,
1063
- sessionRecord.turnStartMessageIndex
1064
- ) : { messages: [], startsAtRunBoundary: false };
1065
- const canExposeTranscript = canExposeConversationTranscript(
1066
- summary,
1067
- conversation?.visibility
1068
- );
1069
- const normalizedTranscript = scopedMessages.messages.map(
1070
- normalizeTranscriptMessage
1071
- );
1072
- const activity = buildConversationActivity({
1073
- canExposePayload: canExposeTranscript,
1074
- entries: activityEntries,
1075
- messages: scopedMessages.messages
1076
- });
1077
- const transcriptMessageCount = countConversationMessages(normalizedTranscript);
1078
- const transcript = canExposeTranscript ? [
1079
- ...scopedMessages.startsAtRunBoundary && normalizedTranscript.length > 0 && sessionRecord?.source ? [systemPromptMessage(sessionRecord.source)] : [],
1080
- ...normalizedTranscript
1081
- ] : [];
1082
- const transcriptMetadata = canExposeTranscript ? void 0 : normalizedTranscript.map(redactTranscriptMessage);
1083
- const traceId = summary.traceId ?? sessionRecord?.traceId ?? (canExposeTranscript ? traceIdFromTranscript(transcript) : void 0);
1084
- const sentryTraceUrl = traceId ? buildSentryTraceUrl(traceId) : void 0;
1085
- const report = {
1086
- ...sessionReportFromSummary(
1087
- summary,
1088
- nowMs,
1089
- details,
1090
- conversation?.visibility
1091
- ),
1092
- ...traceId ? { traceId } : {},
1093
- ...sentryTraceUrl ? { sentryTraceUrl } : {},
1094
- activity,
1095
- transcriptAvailable: Boolean(sessionRecord) && canExposeTranscript,
1096
- ...sessionRecord && transcriptMessageCount > 0 ? { transcriptMessageCount } : {},
1097
- ...!canExposeTranscript ? {
1098
- transcriptMetadata,
1099
- transcriptRedacted: true,
1100
- transcriptRedactionReason: "non_public_conversation"
1101
- } : {},
1102
- transcript
1103
- };
1104
- return conversation ? {
1105
- ...report,
1106
- ...applyConversationIndexMetadata({
1107
- conversation,
1108
- details,
1109
- nowMs,
1110
- report
1111
- })
1112
- } : report;
1113
- })
1114
- );
1115
- const effectiveRuns = runs.length > 0 || !conversation ? runs : [
689
+ const conversation = await store.get({ conversationId });
690
+ const turnSummaries = conversation ? await listAgentTurnSessionSummariesForConversation(conversationId) : [];
691
+ const currentTurnSummary = conversation ? turnSummaries.find(
692
+ (summary) => summary.sessionId === conversation.execution.runId
693
+ ) : void 0;
694
+ const stepStore = getAgentStepStore();
695
+ const messageStore = options.messageStore ?? getConversationMessageStore();
696
+ const transcriptPurgedAtMs = conversation?.transcriptPurgedAtMs;
697
+ const transcriptExpiredAt = transcriptPurgedAtMs !== void 0 ? new Date(transcriptPurgedAtMs).toISOString() : void 0;
698
+ const canExposeSqlContent = conversation !== void 0 && canExposeConversationPayload({
699
+ conversationId,
700
+ visibility: conversation.visibility
701
+ });
702
+ const currentContent = conversation && transcriptPurgedAtMs === void 0 ? await currentRunContent({
703
+ conversationId,
704
+ messageStore,
705
+ stepStore,
706
+ canExposePayload: canExposeSqlContent
707
+ }) : { activity: [], transcript: [] };
708
+ const currentTranscript = currentContent.transcript;
709
+ const traceId = canExposeSqlContent ? traceIdFromTranscript(currentTranscript) : void 0;
710
+ const sentryTraceUrl = traceId ? buildSentryTraceUrl(traceId) : void 0;
711
+ const effectiveRuns = conversation ? [
1116
712
  {
1117
- ...sessionReportFromConversation(conversation, nowMs, details),
1118
- activity: [],
1119
- transcriptAvailable: false,
1120
- transcript: []
713
+ ...sessionReportFromConversation(conversation, nowMs),
714
+ ...currentTurnSummary ? {
715
+ cumulativeDurationMs: currentTurnSummary.cumulativeDurationMs,
716
+ ...currentTurnSummary.cumulativeUsage ? { cumulativeUsage: currentTurnSummary.cumulativeUsage } : {}
717
+ } : {},
718
+ ...currentTurnSummary?.modelId ? { modelId: currentTurnSummary.modelId } : {},
719
+ ...currentTurnSummary?.reasoningLevel ? { reasoningLevel: currentTurnSummary.reasoningLevel } : {},
720
+ ...traceId ? { traceId } : {},
721
+ ...sentryTraceUrl ? { sentryTraceUrl } : {},
722
+ activity: currentContent.activity,
723
+ transcriptAvailable: transcriptExpiredAt === void 0 && canExposeSqlContent && currentTranscript.length > 0,
724
+ ...currentTranscript.length > 0 ? {
725
+ transcriptMessageCount: countConversationMessages(currentTranscript)
726
+ } : {},
727
+ ...!canExposeSqlContent && transcriptExpiredAt === void 0 ? {
728
+ transcriptMetadata: currentTranscript.map(
729
+ redactTranscriptMessage
730
+ ),
731
+ transcriptRedacted: true,
732
+ transcriptRedactionReason: "non_public_conversation"
733
+ } : {},
734
+ ...transcriptExpiredAt !== void 0 ? {
735
+ transcriptExpired: true,
736
+ transcriptExpiredAt,
737
+ transcriptMetadata: []
738
+ } : {},
739
+ transcript: transcriptExpiredAt === void 0 && canExposeSqlContent ? currentTranscript : []
1121
740
  }
1122
- ];
741
+ ] : [];
1123
742
  const firstRun = effectiveRuns[0];
1124
- const displayTitle = firstRun?.displayTitle ?? displayTitleFromDetails(
1125
- conversationId,
1126
- details,
1127
- conversation?.visibility
1128
- ) ?? surfaceFallbackLabel(firstRun?.surface ?? "slack");
743
+ const displayTitle = firstRun?.displayTitle ?? surfaceFallbackLabel(firstRun?.surface ?? "slack");
1129
744
  const sentryConversationUrl = buildSentryConversationUrl(conversationId);
1130
745
  return {
1131
746
  conversationId,
@@ -1135,34 +750,30 @@ async function readConversationReport(conversationId, options = {}) {
1135
750
  runs: effectiveRuns
1136
751
  };
1137
752
  }
1138
- async function readConversationSubagentTranscriptReport(conversationId, runId, subagentId, options = {}) {
753
+ async function readConversationSubagentTranscriptReport(conversationId, _runId, subagentId, options = {}) {
1139
754
  const store = conversationStore(options);
1140
- const [summaries, conversation] = await Promise.all([
1141
- listAgentTurnSessionSummariesForConversation(conversationId),
1142
- store.get({ conversationId })
755
+ const stepStore = getAgentStepStore();
756
+ const [conversation, parentSteps] = await Promise.all([
757
+ store.get({ conversationId }),
758
+ stepStore.loadHistory(conversationId)
1143
759
  ]);
1144
- const summary = summaries.find((candidate) => candidate.sessionId === runId);
1145
- if (!summary) {
760
+ if (conversation?.transcriptPurgedAtMs !== void 0) {
1146
761
  return {
1147
762
  type: "subagent",
1148
763
  createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1149
764
  id: subagentId,
1150
- status: "error",
765
+ status: "completed",
1151
766
  subagentKind: "unknown",
1152
767
  transcript: [],
1153
768
  transcriptAvailable: false,
1154
- unavailableReason: "not_found"
769
+ transcriptExpired: true,
770
+ transcriptExpiredAt: new Date(
771
+ conversation.transcriptPurgedAtMs
772
+ ).toISOString()
1155
773
  };
1156
774
  }
1157
- const entries = await loadActivityEntries({
1158
- conversationId,
1159
- sessionId: runId
1160
- });
1161
- const start = entries.find(
1162
- (entry) => entry.type === "subagent_started" && entry.subagentInvocationId === subagentId
1163
- );
1164
- const end = entries.find(
1165
- (entry) => entry.type === "subagent_ended" && entry.subagentInvocationId === subagentId
775
+ const start = parentSteps.find(
776
+ (step) => step.entry.type === "subagent_started" && step.entry.subagentInvocationId === subagentId
1166
777
  );
1167
778
  if (!start) {
1168
779
  return {
@@ -1176,12 +787,20 @@ async function readConversationSubagentTranscriptReport(conversationId, runId, s
1176
787
  unavailableReason: "not_found"
1177
788
  };
1178
789
  }
1179
- const canExposeTranscript = canExposeConversationTranscript(
1180
- summary,
1181
- conversation?.visibility
790
+ const end = parentSteps.find(
791
+ (step) => step.entry.type === "subagent_ended" && step.entry.subagentInvocationId === subagentId
1182
792
  );
1183
- const activity = subagentActivity(start, { canExposeTranscript, end });
1184
- const conversationFields = subagentConversationFields(start.transcriptRef);
793
+ const childConversationId = start.entry.childConversationId;
794
+ const activity = subagentActivityFromSteps(start, end);
795
+ const subagentSentryConversationUrl = buildSentryConversationUrl(childConversationId);
796
+ const conversationFields = {
797
+ subagentConversationId: childConversationId,
798
+ ...subagentSentryConversationUrl ? { subagentSentryConversationUrl } : {}
799
+ };
800
+ const canExposeTranscript = canExposeConversationPayload({
801
+ conversationId,
802
+ visibility: conversation?.visibility
803
+ });
1185
804
  if (!canExposeTranscript) {
1186
805
  return subagentTranscriptReport(activity, {
1187
806
  ...conversationFields,
@@ -1189,21 +808,20 @@ async function readConversationSubagentTranscriptReport(conversationId, runId, s
1189
808
  transcriptRedactionReason: "non_public_conversation"
1190
809
  });
1191
810
  }
1192
- const bounds = transcriptSliceBounds(end);
1193
- if (!bounds) {
1194
- return subagentTranscriptReport(activity, {
1195
- ...conversationFields,
1196
- unavailableReason: "missing_transcript_range"
1197
- });
1198
- }
1199
- const messages = await readTranscriptRefMessages(start.transcriptRef);
1200
- if (messages.length === 0) {
811
+ const childMessages = await loadProjection({
812
+ conversationId: childConversationId
813
+ });
814
+ if (childMessages.length === 0) {
1201
815
  return subagentTranscriptReport(activity, {
1202
816
  ...conversationFields,
1203
817
  unavailableReason: "missing_transcript_ref"
1204
818
  });
1205
819
  }
1206
- const transcript = messages.slice(0, bounds.end).map(normalizeTranscriptMessage);
820
+ const transcript = childMessages.map(
821
+ (message) => normalizeTranscriptMessage(message, {
822
+ unwrapAdvisorTask: activity.subagentKind === "advisor"
823
+ })
824
+ );
1207
825
  return subagentTranscriptReport(activity, {
1208
826
  ...conversationFields,
1209
827
  transcript,
@@ -1211,6 +829,254 @@ async function readConversationSubagentTranscriptReport(conversationId, runId, s
1211
829
  });
1212
830
  }
1213
831
 
832
+ // src/reporting/conversations/statistics.ts
833
+ var CONVERSATION_STATS_LIMIT = 5e3;
834
+ var RECENT_CONVERSATION_STATS_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
835
+ function usageTokenTotal(usage) {
836
+ if (!usage) return void 0;
837
+ const components = [
838
+ usage.inputTokens,
839
+ usage.outputTokens,
840
+ usage.cachedInputTokens,
841
+ usage.cacheCreationTokens
842
+ ].reduce((sum, value) => {
843
+ const count = typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
844
+ return count === void 0 ? sum : (sum ?? 0) + count;
845
+ }, void 0);
846
+ if (components !== void 0) {
847
+ return components;
848
+ }
849
+ return typeof usage.totalTokens === "number" && Number.isFinite(usage.totalTokens) ? Math.max(0, Math.floor(usage.totalTokens)) : void 0;
850
+ }
851
+ function usageCostTotal(usage) {
852
+ if (!usage?.cost) return void 0;
853
+ if (typeof usage.cost.total === "number" && Number.isFinite(usage.cost.total)) {
854
+ return Math.max(0, usage.cost.total);
855
+ }
856
+ return [
857
+ usage.cost.input,
858
+ usage.cost.output,
859
+ usage.cost.cacheRead,
860
+ usage.cost.cacheWrite
861
+ ].reduce((sum, value) => {
862
+ const amount = typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : void 0;
863
+ return amount === void 0 ? sum : (sum ?? 0) + amount;
864
+ }, void 0);
865
+ }
866
+ function addUsd(left, right) {
867
+ return Math.round(((left ?? 0) + right) * 1e12) / 1e12;
868
+ }
869
+ function runDurationSnapshot(run) {
870
+ return typeof run.cumulativeDurationMs === "number" && Number.isFinite(run.cumulativeDurationMs) ? Math.max(0, Math.floor(run.cumulativeDurationMs)) : void 0;
871
+ }
872
+ function runContributions(runs) {
873
+ return runs.map((run) => {
874
+ const duration = runDurationSnapshot(run);
875
+ const tokens = usageTokenTotal(run.cumulativeUsage);
876
+ const costUsd = usageCostTotal(run.cumulativeUsage);
877
+ const contribution = {
878
+ durationMs: duration ?? 0,
879
+ run
880
+ };
881
+ if (tokens !== void 0) {
882
+ contribution.tokens = tokens;
883
+ }
884
+ if (costUsd !== void 0) {
885
+ contribution.costUsd = costUsd;
886
+ }
887
+ return contribution;
888
+ });
889
+ }
890
+ function contributionDurationTotal(contributions) {
891
+ return contributions.reduce(
892
+ (sum, contribution) => sum + contribution.durationMs,
893
+ 0
894
+ );
895
+ }
896
+ function addTokenTotal(total, tokens) {
897
+ return tokens === void 0 ? total : (total ?? 0) + tokens;
898
+ }
899
+ function contributionTokenTotal(contributions) {
900
+ return contributions.reduce(
901
+ (sum, contribution) => addTokenTotal(sum, contribution.tokens),
902
+ void 0
903
+ );
904
+ }
905
+ function contributionCostTotal(contributions) {
906
+ return contributions.reduce(
907
+ (sum, contribution) => contribution.costUsd === void 0 ? sum : addUsd(sum, contribution.costUsd),
908
+ void 0
909
+ );
910
+ }
911
+ function actorLabel(actor) {
912
+ const email = actor?.email?.trim() || void 0;
913
+ const fullName = actor?.fullName?.trim() || void 0;
914
+ const slackUserName = actor?.slackUserName?.trim() || void 0;
915
+ return email ?? fullName ?? slackUserName ?? actor?.slackUserId;
916
+ }
917
+ function locationLabel(run) {
918
+ return slackStatsLocationLabel(run) ?? surfaceFallbackLabel(run.surface);
919
+ }
920
+ function emptyStatsItem(label) {
921
+ return {
922
+ active: 0,
923
+ conversations: 0,
924
+ durationMs: 0,
925
+ failed: 0,
926
+ hung: 0,
927
+ label,
928
+ runs: 0
929
+ };
930
+ }
931
+ function addItemTokens(item, tokens) {
932
+ if (tokens !== void 0) {
933
+ item.tokens = (item.tokens ?? 0) + tokens;
934
+ }
935
+ }
936
+ function addItemCost(item, costUsd) {
937
+ if (costUsd !== void 0) {
938
+ item.costUsd = addUsd(item.costUsd, costUsd);
939
+ }
940
+ }
941
+ function statusSignals(runs) {
942
+ return {
943
+ active: runs.some((run) => run.status === "active"),
944
+ failed: runs.some((run) => run.status === "failed"),
945
+ hung: runs.some((run) => run.status === "hung")
946
+ };
947
+ }
948
+ function statsItems(map) {
949
+ return [...map.values()].sort(
950
+ (left, right) => right.conversations - left.conversations || right.runs - left.runs || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
951
+ );
952
+ }
953
+ function recentConversationGroups(args) {
954
+ const startMs = args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS;
955
+ const groups = /* @__PURE__ */ new Map();
956
+ for (const summary of args.summaries) {
957
+ groups.set(summary.conversationId, [
958
+ ...groups.get(summary.conversationId) ?? [],
959
+ summary
960
+ ]);
961
+ }
962
+ return [...groups.values()].map(
963
+ (runs) => [...runs].sort(
964
+ (left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
965
+ )
966
+ ).filter((runs) => {
967
+ const activityAt = reportTime(newestRun(runs).lastSeenAt);
968
+ return activityAt !== void 0 && activityAt >= startMs && activityAt <= args.nowMs;
969
+ });
970
+ }
971
+ function conversationDurationMs(runs) {
972
+ if (!runs.some((run) => runDurationSnapshot(run) !== void 0)) {
973
+ return 0;
974
+ }
975
+ return contributionDurationTotal(runContributions(runs));
976
+ }
977
+ function buildConversationStatsReport(args) {
978
+ const conversations = recentConversationGroups(args);
979
+ const actors = /* @__PURE__ */ new Map();
980
+ const locations = /* @__PURE__ */ new Map();
981
+ let durationMs = 0;
982
+ let costUsd;
983
+ let tokens;
984
+ let active = 0;
985
+ let failed = 0;
986
+ let hung = 0;
987
+ for (const runs of conversations) {
988
+ const contributions = runContributions(runs);
989
+ const conversationSignals = statusSignals(runs);
990
+ const conversationCostUsd = contributionCostTotal(contributions);
991
+ const conversationTokens = contributionTokenTotal(contributions);
992
+ durationMs += contributionDurationTotal(contributions);
993
+ costUsd = conversationCostUsd === void 0 ? costUsd : addUsd(costUsd, conversationCostUsd);
994
+ tokens = addTokenTotal(tokens, conversationTokens);
995
+ active += conversationSignals.active ? 1 : 0;
996
+ failed += conversationSignals.failed ? 1 : 0;
997
+ hung += conversationSignals.hung ? 1 : 0;
998
+ const actorRuns = /* @__PURE__ */ new Map();
999
+ for (const contribution of contributions) {
1000
+ const actor = actorLabel(contribution.run.actorIdentity) ?? "Unknown";
1001
+ actorRuns.set(actor, [...actorRuns.get(actor) ?? [], contribution]);
1002
+ }
1003
+ for (const [actor, actorContributions] of actorRuns) {
1004
+ const item = actors.get(actor) ?? emptyStatsItem(actor);
1005
+ const signals = statusSignals(
1006
+ actorContributions.map((contribution) => contribution.run)
1007
+ );
1008
+ item.conversations += 1;
1009
+ item.runs += actorContributions.length;
1010
+ item.durationMs += contributionDurationTotal(actorContributions);
1011
+ item.active += signals.active ? 1 : 0;
1012
+ item.failed += signals.failed ? 1 : 0;
1013
+ item.hung += signals.hung ? 1 : 0;
1014
+ addItemTokens(item, contributionTokenTotal(actorContributions));
1015
+ addItemCost(item, contributionCostTotal(actorContributions));
1016
+ actors.set(actor, item);
1017
+ }
1018
+ const location = locationLabel(newestRun(runs));
1019
+ const locationItem = locations.get(location) ?? emptyStatsItem(location);
1020
+ locationItem.conversations += 1;
1021
+ locationItem.runs += runs.length;
1022
+ locationItem.durationMs += conversationDurationMs(runs);
1023
+ locationItem.active += conversationSignals.active ? 1 : 0;
1024
+ locationItem.failed += conversationSignals.failed ? 1 : 0;
1025
+ locationItem.hung += conversationSignals.hung ? 1 : 0;
1026
+ addItemTokens(locationItem, conversationTokens);
1027
+ addItemCost(locationItem, conversationCostUsd);
1028
+ locations.set(location, locationItem);
1029
+ }
1030
+ return {
1031
+ active,
1032
+ conversations: conversations.length,
1033
+ durationMs,
1034
+ failed,
1035
+ generatedAt: args.generatedAt,
1036
+ hung,
1037
+ locations: statsItems(locations),
1038
+ actors: statsItems(actors),
1039
+ sampleLimit: args.sampleLimit,
1040
+ sampleSize: args.sampleSize,
1041
+ source: "conversation_index",
1042
+ ...costUsd !== void 0 ? { costUsd } : {},
1043
+ ...tokens !== void 0 ? { tokens } : {},
1044
+ truncated: args.truncated,
1045
+ runs: conversations.reduce((sum, runs) => sum + runs.length, 0),
1046
+ windowEnd: new Date(args.nowMs).toISOString(),
1047
+ windowStart: new Date(
1048
+ args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS
1049
+ ).toISOString()
1050
+ };
1051
+ }
1052
+ async function readConversationStatsReport(options = {}) {
1053
+ const store = conversationStore(options);
1054
+ const nowMs = Date.now();
1055
+ const generatedAt = new Date(nowMs).toISOString();
1056
+ const conversations = await store.listByActivity({
1057
+ limit: CONVERSATION_STATS_LIMIT + 1
1058
+ });
1059
+ const truncated = conversations.length > CONVERSATION_STATS_LIMIT;
1060
+ const sampledConversations = conversations.slice(0, CONVERSATION_STATS_LIMIT);
1061
+ const summariesByConversation = await listAgentTurnSessionSummariesForConversations(
1062
+ sampledConversations.map((conversation) => conversation.conversationId)
1063
+ );
1064
+ const summaries = sampledConversations.flatMap((conversation) => {
1065
+ const turnSummaries = summariesByConversation.get(conversation.conversationId) ?? [];
1066
+ return turnSummaries.length > 0 ? turnSummaries.map(
1067
+ (summary) => sessionReportFromTurnSummary(conversation, summary, nowMs)
1068
+ ) : [sessionReportFromConversation(conversation, nowMs)];
1069
+ });
1070
+ return buildConversationStatsReport({
1071
+ generatedAt,
1072
+ nowMs,
1073
+ sampleLimit: CONVERSATION_STATS_LIMIT,
1074
+ sampleSize: sampledConversations.length,
1075
+ summaries,
1076
+ truncated
1077
+ });
1078
+ }
1079
+
1214
1080
  // src/reporting.ts
1215
1081
  function readDescriptionText() {
1216
1082
  try {