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