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