@sentry/junior 0.93.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.
- package/dist/{agent-hooks-ZG7IDOIN.js → agent-hooks-FTQ5EGPO.js} +9 -10
- package/dist/api/people/list.js +7 -7
- package/dist/api/people/profile.js +7 -7
- package/dist/api/people/shared.d.ts +2 -2
- package/dist/api-reference.d.ts +1 -1
- package/dist/app.js +474 -231
- package/dist/{catalog-runtime-NZHMSMYC.js → catalog-runtime-2D7IVKUT.js} +3 -3
- package/dist/chat/advisor-request.d.ts +4 -0
- package/dist/chat/agent/request.d.ts +2 -0
- package/dist/chat/agent/resume.d.ts +2 -0
- package/dist/chat/conversations/history.d.ts +128 -0
- package/dist/chat/conversations/legacy-import.d.ts +38 -0
- package/dist/chat/conversations/messages.d.ts +39 -0
- package/dist/chat/conversations/projection.d.ts +94 -0
- package/dist/chat/conversations/retention.d.ts +36 -0
- package/dist/chat/conversations/sql/conversation-row.d.ts +14 -0
- package/dist/chat/conversations/sql/history.d.ts +4 -0
- package/dist/chat/conversations/sql/legacy-history-import.d.ts +55 -0
- package/dist/chat/conversations/sql/messages.d.ts +4 -0
- package/dist/chat/conversations/sql/migrations.d.ts +14 -3
- package/dist/chat/conversations/sql/purge.d.ts +53 -0
- package/dist/chat/conversations/sql/store.d.ts +7 -2
- package/dist/chat/conversations/store.d.ts +19 -0
- package/dist/chat/conversations/visible-compactions.d.ts +11 -0
- package/dist/chat/conversations/visible-messages.d.ts +47 -0
- package/dist/chat/db.d.ts +12 -1
- package/dist/chat/egress/credentialed.d.ts +1 -1
- package/dist/chat/identities/sql.d.ts +1 -1
- package/dist/chat/logging.d.ts +2 -1
- package/dist/chat/resource-events/store.d.ts +2 -1
- package/dist/chat/runtime/reply-executor.d.ts +8 -0
- package/dist/chat/runtime/thread-state.d.ts +2 -0
- package/dist/chat/sandbox/egress/schemas.d.ts +4 -0
- package/dist/chat/sandbox/egress/session.d.ts +2 -2
- package/dist/chat/sandbox/resources.d.ts +4 -0
- package/dist/chat/services/mcp-auth-orchestration.d.ts +8 -0
- package/dist/chat/services/plugin-auth-orchestration.d.ts +12 -0
- package/dist/chat/services/turn-session-record.d.ts +14 -2
- package/dist/chat/sleep.d.ts +2 -0
- package/dist/chat/state/conversation.d.ts +7 -4
- package/dist/chat/state/session-log.d.ts +23 -1
- package/dist/chat/state/turn-session.d.ts +6 -0
- package/dist/chat/tool-support/private-trace-result.d.ts +4 -0
- package/dist/chat/tool-support/zod-tool.d.ts +1 -0
- package/dist/chat/tools/advisor/tool.d.ts +9 -4
- package/dist/chat/tools/definition.d.ts +2 -0
- package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
- package/dist/chat/tools/system-time.d.ts +8 -0
- package/dist/chat/xml.d.ts +2 -0
- package/dist/{chunk-NYWPISSO.js → chunk-2XXEKR44.js} +1 -1
- package/dist/{chunk-QGEWC27H.js → chunk-5XMNOD3S.js} +5 -7
- package/dist/{chunk-NFXSLTYG.js → chunk-6ONME5IG.js} +864 -62
- package/dist/{chunk-LFLTJQVR.js → chunk-7FBGKXPE.js} +4 -0
- package/dist/{chunk-MC4ZZE4Q.js → chunk-AYP4ERVZ.js} +1 -1
- package/dist/{chunk-GHGPTPBL.js → chunk-B7PUBQQM.js} +1 -1
- package/dist/chunk-CM5EZD5F.js +17 -0
- package/dist/chunk-CRRBJ3JP.js +1037 -0
- package/dist/{chunk-G3E7SCME.js → chunk-CZEX6KIQ.js} +23 -0
- package/dist/{chunk-TNUUATT7.js → chunk-E3E2XNCN.js} +2 -2
- package/dist/{chunk-LNCREA5Z.js → chunk-EKNJM5IG.js} +3 -127
- package/dist/{chunk-NAPITCZ6.js → chunk-GUE7HTNR.js} +251 -6
- package/dist/{chunk-BTH37NCU.js → chunk-GVOUCI2O.js} +1 -1
- package/dist/{chunk-YYBX2BD5.js → chunk-GY34VGP5.js} +1 -1
- package/dist/{chunk-JJUJ4CO2.js → chunk-HCXXB35T.js} +1489 -360
- package/dist/{chunk-YTNPU7I2.js → chunk-HDTO3A74.js} +2 -2
- package/dist/{chunk-YJ4OPVNA.js → chunk-HZNJLKKI.js} +1 -1
- package/dist/{chunk-HDNWZMUN.js → chunk-KDPCGVIT.js} +116 -51
- package/dist/chunk-N4RNJ32C.js +48 -0
- package/dist/{chunk-UUB3D2XI.js → chunk-NSCHU54X.js} +8 -10
- package/dist/{chunk-PFXC67GJ.js → chunk-RAIB6WSG.js} +6 -80
- package/dist/chunk-WAGQ377B.js +724 -0
- package/dist/{chunk-6H4PP63X.js → chunk-WCMQJE4F.js} +24 -14
- package/dist/{chunk-S6QKIGRM.js → chunk-WPOJ443W.js} +15 -25
- package/dist/{chunk-MAVOJ54A.js → chunk-XX4N4E6C.js} +25 -388
- package/dist/cli/chat.js +24 -23
- package/dist/cli/check.js +4 -4
- package/dist/cli/plugins.js +11 -12
- package/dist/cli/snapshot-warmup.js +7 -8
- package/dist/cli/upgrade.js +78 -17
- package/dist/db/schema/agent-steps.d.ts +134 -0
- package/dist/db/schema/conversation-messages.d.ts +153 -0
- package/dist/{chat/conversations/sql → db}/schema/conversations.d.ts +34 -0
- package/dist/{chat/conversations/sql → db}/schema.d.ts +319 -8
- package/dist/db-W55B74OP.js +24 -0
- package/dist/deployment.d.ts +2 -0
- package/dist/handlers/retention.d.ts +6 -0
- package/dist/instrumentation.js +16 -3
- package/dist/legacy-import-ZPLGLAFD.js +17 -0
- package/dist/nitro.js +14 -4
- package/dist/reporting/conversations/activity.d.ts +40 -0
- package/dist/reporting/conversations/context.d.ts +8 -0
- package/dist/reporting/conversations/details.d.ts +6 -0
- package/dist/reporting/conversations/index.d.ts +4 -0
- package/dist/reporting/conversations/shared.d.ts +9 -0
- package/dist/reporting/conversations/statistics.d.ts +4 -0
- package/dist/reporting/conversations/summaries.d.ts +17 -0
- package/dist/reporting/conversations/transcript.d.ts +28 -0
- package/dist/reporting/conversations/types.d.ts +178 -0
- package/dist/reporting/conversations.d.ts +1 -178
- package/dist/reporting.d.ts +4 -3
- package/dist/reporting.js +603 -827
- package/dist/{runner-XAZPJNAQ.js → runner-OOSBDYRK.js} +35 -41
- package/dist/{validation-KYAWETI4.js → validation-QCGRFRFZ.js} +4 -4
- package/migrations/README.md +32 -0
- package/migrations/meta/0000_snapshot.json +978 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +9 -6
- package/dist/chat/sql/schema.d.ts +0 -1049
- package/dist/chat/state/conversation-details.d.ts +0 -47
- package/dist/chunk-7STN2ZSK.js +0 -250
- package/dist/chunk-LE5ANBUZ.js +0 -1040
- package/dist/chunk-QL66APYJ.js +0 -1377
- package/dist/db-YDN6AFJH.js +0 -18
- /package/dist/{chat/sql → db}/db.d.ts +0 -0
- /package/dist/{chat/sql → db}/executor.d.ts +0 -0
- /package/dist/{chat/sql → db}/neon.d.ts +0 -0
- /package/dist/{chat/sql → db}/postgres.d.ts +0 -0
- /package/dist/{chat/conversations/sql → db}/schema/destinations.d.ts +0 -0
- /package/dist/{chat/conversations/sql → db}/schema/identities.d.ts +0 -0
- /package/dist/{chat/conversations/sql → db}/schema/migrations.d.ts +0 -0
- /package/dist/{chat/conversations/sql → db}/schema/timestamps.d.ts +0 -0
- /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-
|
|
7
|
+
} from "./chunk-EKNJM5IG.js";
|
|
10
8
|
import {
|
|
11
|
-
|
|
12
|
-
} from "./chunk-
|
|
9
|
+
unwrapAdvisorRequest
|
|
10
|
+
} from "./chunk-N4RNJ32C.js";
|
|
13
11
|
import {
|
|
14
|
-
getAgentTurnSessionRecord,
|
|
15
12
|
listAgentTurnSessionSummariesForConversation,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./chunk-
|
|
13
|
+
loadProjection,
|
|
14
|
+
projectSteps
|
|
15
|
+
} from "./chunk-WAGQ377B.js";
|
|
16
|
+
import "./chunk-CRRBJ3JP.js";
|
|
19
17
|
import {
|
|
20
18
|
getPluginOperationalReports
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
23
|
-
import "./chunk-
|
|
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-
|
|
26
|
+
} from "./chunk-6ONME5IG.js";
|
|
30
27
|
import {
|
|
31
28
|
unwrapCurrentInstruction
|
|
32
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-GUE7HTNR.js";
|
|
33
30
|
import {
|
|
34
31
|
canExposeConversationPayload,
|
|
35
32
|
parseSlackThreadId,
|
|
36
33
|
resolveConversationPrivacy
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-
|
|
34
|
+
} from "./chunk-GY34VGP5.js";
|
|
35
|
+
import "./chunk-B7PUBQQM.js";
|
|
39
36
|
import {
|
|
40
37
|
discoverSkills
|
|
41
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-HDTO3A74.js";
|
|
42
39
|
import {
|
|
43
40
|
pluginCatalogRuntime
|
|
44
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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) =>
|
|
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
|
|
729
|
-
const end = options.end;
|
|
238
|
+
function redactTranscriptMessage(message) {
|
|
730
239
|
return {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
878
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
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
|
-
|
|
887
|
-
|
|
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
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
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
|
-
|
|
906
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
|
|
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
|
-
|
|
925
|
-
conversation.
|
|
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
|
|
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
|
|
596
|
+
sessionReportFromConversation(conversation, nowMs)
|
|
1025
597
|
]
|
|
1026
598
|
);
|
|
1027
599
|
return {
|
|
1028
600
|
conversationId: conversation.conversationId,
|
|
1029
|
-
displayTitle: titleFromConversation({ conversation,
|
|
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
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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 ??
|
|
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,
|
|
700
|
+
async function readConversationSubagentTranscriptReport(conversationId, _runId, subagentId, options = {}) {
|
|
1139
701
|
const store = conversationStore(options);
|
|
1140
|
-
const
|
|
1141
|
-
|
|
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
|
-
|
|
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: "
|
|
712
|
+
status: "completed",
|
|
1151
713
|
subagentKind: "unknown",
|
|
1152
714
|
transcript: [],
|
|
1153
715
|
transcriptAvailable: false,
|
|
1154
|
-
|
|
716
|
+
transcriptExpired: true,
|
|
717
|
+
transcriptExpiredAt: new Date(
|
|
718
|
+
conversation.transcriptPurgedAtMs
|
|
719
|
+
).toISOString()
|
|
1155
720
|
};
|
|
1156
721
|
}
|
|
1157
|
-
const
|
|
1158
|
-
|
|
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
|
|
1180
|
-
|
|
1181
|
-
conversation?.visibility
|
|
737
|
+
const end = parentSteps.find(
|
|
738
|
+
(step) => step.entry.type === "subagent_ended" && step.entry.subagentInvocationId === subagentId
|
|
1182
739
|
);
|
|
1183
|
-
const
|
|
1184
|
-
const
|
|
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
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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 =
|
|
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 {
|