@sentry/junior 0.66.2 → 0.67.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/app.js +97 -630
- package/dist/chat/config.d.ts +1 -0
- package/dist/chat/ingress/slash-command.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +3 -1
- package/dist/chat/respond.d.ts +2 -0
- package/dist/chat/services/turn-session-record.d.ts +6 -1
- package/dist/chat/state/turn-session.d.ts +4 -0
- package/dist/{chunk-DG2I6GXC.js → chunk-HFMZE67J.js} +1801 -1014
- package/dist/{chunk-JA5QR3N4.js → chunk-KWEE2436.js} +1 -1
- package/dist/{chunk-SAYCFF7O.js → chunk-NWU2Z6SM.js} +12 -1
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +2 -2
- package/dist/reporting.d.ts +45 -2
- package/dist/reporting.js +301 -11
- package/package.json +3 -3
|
@@ -500,6 +500,7 @@ async function completeObject(params) {
|
|
|
500
500
|
var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
|
|
501
501
|
var DEFAULT_AGENT_TURN_TIMEOUT_MS = 12 * 60 * 1e3;
|
|
502
502
|
var DEFAULT_FUNCTION_MAX_DURATION_SECONDS = 300;
|
|
503
|
+
var DEFAULT_SLACK_SLASH_COMMAND = "/jr";
|
|
503
504
|
var ADVISOR_THINKING_LEVELS = [
|
|
504
505
|
"minimal",
|
|
505
506
|
"low",
|
|
@@ -589,6 +590,15 @@ function parseOptionalPositiveInteger(envName, rawValue) {
|
|
|
589
590
|
}
|
|
590
591
|
return value;
|
|
591
592
|
}
|
|
593
|
+
function parseSlashCommand(rawValue) {
|
|
594
|
+
const command = toOptionalTrimmed(rawValue) ?? DEFAULT_SLACK_SLASH_COMMAND;
|
|
595
|
+
if (!command.startsWith("/") || /\s/.test(command)) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
"JUNIOR_SLASH_COMMAND must start with / and contain no whitespace"
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
return command;
|
|
601
|
+
}
|
|
592
602
|
var DEFAULT_MODEL_ID = getModel("vercel-ai-gateway", "openai/gpt-5.4").id;
|
|
593
603
|
var DEFAULT_FAST_MODEL_ID = getModel(
|
|
594
604
|
"vercel-ai-gateway",
|
|
@@ -638,7 +648,8 @@ function readChatConfig(env = process.env) {
|
|
|
638
648
|
botToken: toOptionalTrimmed(env.SLACK_BOT_TOKEN) ?? toOptionalTrimmed(env.SLACK_BOT_USER_TOKEN),
|
|
639
649
|
signingSecret: toOptionalTrimmed(env.SLACK_SIGNING_SECRET),
|
|
640
650
|
clientId: toOptionalTrimmed(env.SLACK_CLIENT_ID),
|
|
641
|
-
clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET)
|
|
651
|
+
clientSecret: toOptionalTrimmed(env.SLACK_CLIENT_SECRET),
|
|
652
|
+
slashCommand: parseSlashCommand(env.JUNIOR_SLASH_COMMAND)
|
|
642
653
|
},
|
|
643
654
|
state: {
|
|
644
655
|
adapter: env.JUNIOR_STATE_ADAPTER?.trim().toLowerCase() === "memory" ? "memory" : "redis",
|
package/dist/cli/init.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveRuntimeDependencySnapshot
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-KWEE2436.js";
|
|
4
4
|
import {
|
|
5
5
|
disconnectStateAdapter
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-NWU2Z6SM.js";
|
|
7
7
|
import {
|
|
8
8
|
getPluginProviders,
|
|
9
9
|
getPluginRuntimeDependencies,
|
package/dist/reporting.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getPluginPackageContent } from "@/chat/plugins/registry";
|
|
2
|
+
import { type AgentTurnSurface } from "@/chat/state/turn-session";
|
|
3
|
+
import type { PluginOperationalReport } from "@sentry/junior-plugin-api";
|
|
2
4
|
export interface HealthReport {
|
|
3
5
|
status: "ok";
|
|
4
6
|
service: string;
|
|
@@ -20,7 +22,7 @@ export interface RuntimeInfoReport {
|
|
|
20
22
|
packagedContent: ReturnType<typeof getPluginPackageContent>;
|
|
21
23
|
}
|
|
22
24
|
export type DashboardSessionStatus = "active" | "completed" | "failed" | "hung" | "superseded";
|
|
23
|
-
export type DashboardSurface =
|
|
25
|
+
export type DashboardSurface = AgentTurnSurface;
|
|
24
26
|
export interface DashboardTurnUsage {
|
|
25
27
|
inputTokens?: number;
|
|
26
28
|
outputTokens?: number;
|
|
@@ -99,6 +101,40 @@ export interface DashboardSessionFeed {
|
|
|
99
101
|
source: "turn_session_records";
|
|
100
102
|
generatedAt: string;
|
|
101
103
|
}
|
|
104
|
+
export interface DashboardConversationStatsItem {
|
|
105
|
+
active: number;
|
|
106
|
+
conversations: number;
|
|
107
|
+
durationMs: number;
|
|
108
|
+
failed: number;
|
|
109
|
+
hung: number;
|
|
110
|
+
label: string;
|
|
111
|
+
tokens?: number;
|
|
112
|
+
turns: number;
|
|
113
|
+
}
|
|
114
|
+
export interface DashboardConversationStatsReport {
|
|
115
|
+
active: number;
|
|
116
|
+
conversations: number;
|
|
117
|
+
durationMs: number;
|
|
118
|
+
failed: number;
|
|
119
|
+
generatedAt: string;
|
|
120
|
+
hung: number;
|
|
121
|
+
locations: DashboardConversationStatsItem[];
|
|
122
|
+
requesters: DashboardConversationStatsItem[];
|
|
123
|
+
sampleLimit: number;
|
|
124
|
+
sampleSize: number;
|
|
125
|
+
source: "turn_session_records";
|
|
126
|
+
tokens?: number;
|
|
127
|
+
truncated: boolean;
|
|
128
|
+
turns: number;
|
|
129
|
+
windowEnd: string;
|
|
130
|
+
windowStart: string;
|
|
131
|
+
}
|
|
132
|
+
export type { PluginOperationalReport } from "@sentry/junior-plugin-api";
|
|
133
|
+
export interface PluginOperationalReportFeed {
|
|
134
|
+
generatedAt: string;
|
|
135
|
+
reports: PluginOperationalReport[];
|
|
136
|
+
source: "trusted_plugins";
|
|
137
|
+
}
|
|
102
138
|
export interface JuniorReporting {
|
|
103
139
|
/** Read the public runtime health snapshot without exposing discovery data. */
|
|
104
140
|
getHealth(): Promise<HealthReport>;
|
|
@@ -115,6 +151,10 @@ export interface JuniorReporting {
|
|
|
115
151
|
* actor, route, usage, and links that can later be reconstructed from spans.
|
|
116
152
|
*/
|
|
117
153
|
getSessions(): Promise<DashboardSessionFeed>;
|
|
154
|
+
/** Read aggregate conversation stats for authenticated dashboard views. */
|
|
155
|
+
getConversationStats?(): Promise<DashboardConversationStatsReport>;
|
|
156
|
+
/** Read sanitized operational summaries contributed by trusted plugins. */
|
|
157
|
+
getPluginOperationalReports?(): Promise<PluginOperationalReportFeed>;
|
|
118
158
|
/**
|
|
119
159
|
* Read one conversation transcript for the dashboard.
|
|
120
160
|
*
|
|
@@ -125,4 +165,7 @@ export interface JuniorReporting {
|
|
|
125
165
|
getConversation(conversationId: string): Promise<DashboardConversationReport>;
|
|
126
166
|
}
|
|
127
167
|
/** Create the read-only reporting boundary used by authenticated dashboard routes. */
|
|
128
|
-
export declare function createJuniorReporting(): JuniorReporting
|
|
168
|
+
export declare function createJuniorReporting(): JuniorReporting & {
|
|
169
|
+
getConversationStats(): Promise<DashboardConversationStatsReport>;
|
|
170
|
+
getPluginOperationalReports(): Promise<PluginOperationalReportFeed>;
|
|
171
|
+
};
|
package/dist/reporting.js
CHANGED
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
buildSentryTraceUrl,
|
|
5
5
|
buildSystemPrompt,
|
|
6
6
|
formatSlackConversationRedactedLabel,
|
|
7
|
+
getAgentPluginOperationalReports,
|
|
7
8
|
getAgentTurnSessionRecord,
|
|
8
9
|
listAgentTurnSessionSummaries,
|
|
9
10
|
listAgentTurnSessionSummariesForConversation,
|
|
10
11
|
resolveSlackConversationContextFromThreadId
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HFMZE67J.js";
|
|
12
13
|
import {
|
|
13
14
|
discoverSkills
|
|
14
15
|
} from "./chunk-YL5G5YC4.js";
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
canExposeConversationPayload,
|
|
17
18
|
parseSlackThreadId,
|
|
18
19
|
resolveConversationPrivacy
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-NWU2Z6SM.js";
|
|
20
21
|
import {
|
|
21
22
|
getPluginPackageContent,
|
|
22
23
|
getPluginProviders,
|
|
@@ -34,6 +35,9 @@ import path from "path";
|
|
|
34
35
|
var HUNG_TURN_PROGRESS_MS = 5 * 60 * 1e3;
|
|
35
36
|
var SAFE_METADATA_KEY_LIMIT = 20;
|
|
36
37
|
var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
|
|
38
|
+
var DASHBOARD_SESSION_FEED_LIMIT = 50;
|
|
39
|
+
var DASHBOARD_CONVERSATION_STATS_LIMIT = 5e3;
|
|
40
|
+
var RECENT_CONVERSATION_STATS_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
37
41
|
function readDescriptionText() {
|
|
38
42
|
try {
|
|
39
43
|
const raw = readFileSync(
|
|
@@ -61,9 +65,9 @@ async function readPlugins() {
|
|
|
61
65
|
name: plugin.manifest.name
|
|
62
66
|
}));
|
|
63
67
|
}
|
|
64
|
-
function statusFromCheckpoint(summary) {
|
|
68
|
+
function statusFromCheckpoint(summary, nowMs = Date.now()) {
|
|
65
69
|
const state = summary.state;
|
|
66
|
-
if (state === "running" &&
|
|
70
|
+
if (state === "running" && nowMs - summary.lastProgressAtMs > HUNG_TURN_PROGRESS_MS) {
|
|
67
71
|
return "hung";
|
|
68
72
|
}
|
|
69
73
|
if (state === "running" || state === "awaiting_resume") {
|
|
@@ -75,7 +79,13 @@ function statusFromCheckpoint(summary) {
|
|
|
75
79
|
return state;
|
|
76
80
|
}
|
|
77
81
|
function surfaceFromConversationId(conversationId) {
|
|
78
|
-
|
|
82
|
+
if (parseSlackThreadId(conversationId)) return "slack";
|
|
83
|
+
if (conversationId.startsWith("scheduler:")) return "scheduler";
|
|
84
|
+
if (conversationId.startsWith("api:")) return "api";
|
|
85
|
+
return "internal";
|
|
86
|
+
}
|
|
87
|
+
function surfaceFromSummary(summary) {
|
|
88
|
+
return summary.surface ?? surfaceFromConversationId(summary.conversationId);
|
|
79
89
|
}
|
|
80
90
|
function titleFromSummary(summary) {
|
|
81
91
|
if (summary.state === "awaiting_resume" && summary.resumeReason) {
|
|
@@ -104,7 +114,7 @@ function turnUsageReport(usage) {
|
|
|
104
114
|
};
|
|
105
115
|
return Object.keys(report).length > 0 ? report : void 0;
|
|
106
116
|
}
|
|
107
|
-
function sessionReportFromSummary(summary) {
|
|
117
|
+
function sessionReportFromSummary(summary, nowMs = Date.now()) {
|
|
108
118
|
const slackThread = parseSlackThreadId(summary.conversationId);
|
|
109
119
|
const privacy = resolveConversationPrivacy({
|
|
110
120
|
conversationId: summary.conversationId
|
|
@@ -126,14 +136,14 @@ function sessionReportFromSummary(summary) {
|
|
|
126
136
|
conversationId: summary.conversationId,
|
|
127
137
|
...conversationTitle ? { conversationTitle } : {},
|
|
128
138
|
id: summary.sessionId,
|
|
129
|
-
status: statusFromCheckpoint(summary),
|
|
139
|
+
status: statusFromCheckpoint(summary, nowMs),
|
|
130
140
|
startedAt: new Date(summary.startedAtMs).toISOString(),
|
|
131
141
|
lastProgressAt: new Date(summary.lastProgressAtMs).toISOString(),
|
|
132
142
|
lastSeenAt: new Date(summary.updatedAtMs).toISOString(),
|
|
133
143
|
...summary.state === "completed" ? { completedAt: new Date(summary.updatedAtMs).toISOString() } : {},
|
|
134
144
|
cumulativeDurationMs: summary.cumulativeDurationMs,
|
|
135
145
|
...cumulativeUsage ? { cumulativeUsage } : {},
|
|
136
|
-
surface:
|
|
146
|
+
surface: surfaceFromSummary(summary),
|
|
137
147
|
title: titleFromSummary(summary),
|
|
138
148
|
...requesterIdentity ? { requesterIdentity } : {},
|
|
139
149
|
...slackThread ? { channel: slackThread.channelId } : {},
|
|
@@ -143,6 +153,245 @@ function sessionReportFromSummary(summary) {
|
|
|
143
153
|
...sentryTraceUrl ? { sentryTraceUrl } : {}
|
|
144
154
|
};
|
|
145
155
|
}
|
|
156
|
+
function reportTime(value) {
|
|
157
|
+
const time = Date.parse(value);
|
|
158
|
+
return Number.isFinite(time) ? time : void 0;
|
|
159
|
+
}
|
|
160
|
+
function usageTokenTotal(usage) {
|
|
161
|
+
if (!usage) return void 0;
|
|
162
|
+
const components = [
|
|
163
|
+
usage.inputTokens,
|
|
164
|
+
usage.outputTokens,
|
|
165
|
+
usage.cachedInputTokens,
|
|
166
|
+
usage.cacheCreationTokens
|
|
167
|
+
].reduce((sum, value) => {
|
|
168
|
+
const count = typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
169
|
+
return count === void 0 ? sum : (sum ?? 0) + count;
|
|
170
|
+
}, void 0);
|
|
171
|
+
if (components !== void 0) {
|
|
172
|
+
return components;
|
|
173
|
+
}
|
|
174
|
+
return typeof usage.totalTokens === "number" && Number.isFinite(usage.totalTokens) ? Math.max(0, Math.floor(usage.totalTokens)) : void 0;
|
|
175
|
+
}
|
|
176
|
+
function turnDurationSnapshot(turn) {
|
|
177
|
+
return typeof turn.cumulativeDurationMs === "number" && Number.isFinite(turn.cumulativeDurationMs) ? Math.max(0, Math.floor(turn.cumulativeDurationMs)) : void 0;
|
|
178
|
+
}
|
|
179
|
+
function turnContributions(turns) {
|
|
180
|
+
let previousDuration = 0;
|
|
181
|
+
let previousTokens = 0;
|
|
182
|
+
return turns.map((turn) => {
|
|
183
|
+
const duration = turnDurationSnapshot(turn);
|
|
184
|
+
const tokens = usageTokenTotal(turn.cumulativeUsage);
|
|
185
|
+
const contribution = {
|
|
186
|
+
durationMs: duration === void 0 ? 0 : Math.max(0, duration - previousDuration),
|
|
187
|
+
turn
|
|
188
|
+
};
|
|
189
|
+
if (tokens !== void 0) {
|
|
190
|
+
contribution.tokens = Math.max(0, tokens - previousTokens);
|
|
191
|
+
}
|
|
192
|
+
if (duration !== void 0) {
|
|
193
|
+
previousDuration = Math.max(previousDuration, duration);
|
|
194
|
+
}
|
|
195
|
+
if (tokens !== void 0) {
|
|
196
|
+
previousTokens = Math.max(previousTokens, tokens);
|
|
197
|
+
}
|
|
198
|
+
return contribution;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function contributionDurationTotal(contributions) {
|
|
202
|
+
return contributions.reduce(
|
|
203
|
+
(sum, contribution) => sum + contribution.durationMs,
|
|
204
|
+
0
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
function addTokenTotal(total, tokens) {
|
|
208
|
+
return tokens === void 0 ? total : (total ?? 0) + tokens;
|
|
209
|
+
}
|
|
210
|
+
function contributionTokenTotal(contributions) {
|
|
211
|
+
return contributions.reduce(
|
|
212
|
+
(sum, contribution) => addTokenTotal(sum, contribution.tokens),
|
|
213
|
+
void 0
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
function requesterLabel(requester) {
|
|
217
|
+
const email = requester?.email?.trim() || void 0;
|
|
218
|
+
const fullName = requester?.fullName?.trim() || void 0;
|
|
219
|
+
const slackUserName = requester?.slackUserName?.trim() || void 0;
|
|
220
|
+
return email ?? fullName ?? slackUserName ?? requester?.slackUserId;
|
|
221
|
+
}
|
|
222
|
+
function slackStatsLocationLabel(input) {
|
|
223
|
+
const channelId = input.channel;
|
|
224
|
+
if (!channelId) return void 0;
|
|
225
|
+
const name = input.channelName?.replace(/^#/, "");
|
|
226
|
+
if (channelId.startsWith("D")) {
|
|
227
|
+
return "Direct Message";
|
|
228
|
+
}
|
|
229
|
+
if (channelId.startsWith("C")) {
|
|
230
|
+
return name ? `#${name}` : "Public Channel";
|
|
231
|
+
}
|
|
232
|
+
if (channelId.startsWith("G")) {
|
|
233
|
+
if (name?.startsWith("mpdm-")) return "Group DM";
|
|
234
|
+
return "Private Channel";
|
|
235
|
+
}
|
|
236
|
+
return name || channelId;
|
|
237
|
+
}
|
|
238
|
+
function locationLabel(turn) {
|
|
239
|
+
return slackStatsLocationLabel(turn) ?? (turn.surface === "scheduler" ? "Scheduler" : turn.surface === "api" ? "API" : turn.surface === "internal" ? "Internal" : "Unknown");
|
|
240
|
+
}
|
|
241
|
+
function emptyStatsItem(label) {
|
|
242
|
+
return {
|
|
243
|
+
active: 0,
|
|
244
|
+
conversations: 0,
|
|
245
|
+
durationMs: 0,
|
|
246
|
+
failed: 0,
|
|
247
|
+
hung: 0,
|
|
248
|
+
label,
|
|
249
|
+
turns: 0
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function addItemTokens(item, tokens) {
|
|
253
|
+
if (tokens !== void 0) {
|
|
254
|
+
item.tokens = (item.tokens ?? 0) + tokens;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function statusSignals(turns) {
|
|
258
|
+
return {
|
|
259
|
+
active: turns.some((turn) => turn.status === "active"),
|
|
260
|
+
failed: turns.some((turn) => turn.status === "failed"),
|
|
261
|
+
hung: turns.some((turn) => turn.status === "hung")
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function statsItems(map) {
|
|
265
|
+
return [...map.values()].sort(
|
|
266
|
+
(left, right) => right.conversations - left.conversations || right.turns - left.turns || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
function newestTurn(turns) {
|
|
270
|
+
return [...turns].sort(
|
|
271
|
+
(left, right) => (reportTime(right.lastSeenAt) ?? 0) - (reportTime(left.lastSeenAt) ?? 0) || right.id.localeCompare(left.id)
|
|
272
|
+
)[0];
|
|
273
|
+
}
|
|
274
|
+
function recentConversationGroups(args) {
|
|
275
|
+
const startMs = args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS;
|
|
276
|
+
const groups = /* @__PURE__ */ new Map();
|
|
277
|
+
for (const session of args.sessions) {
|
|
278
|
+
groups.set(session.conversationId, [
|
|
279
|
+
...groups.get(session.conversationId) ?? [],
|
|
280
|
+
session
|
|
281
|
+
]);
|
|
282
|
+
}
|
|
283
|
+
return [...groups.values()].map(
|
|
284
|
+
(turns) => [...turns].sort(
|
|
285
|
+
(left, right) => (reportTime(left.startedAt) ?? 0) - (reportTime(right.startedAt) ?? 0) || left.id.localeCompare(right.id)
|
|
286
|
+
)
|
|
287
|
+
).filter((turns) => {
|
|
288
|
+
const activityAt = reportTime(newestTurn(turns).lastSeenAt);
|
|
289
|
+
return activityAt !== void 0 && activityAt >= startMs && activityAt <= args.nowMs;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function conversationDurationMs(turns) {
|
|
293
|
+
if (!turns.some((turn) => turnDurationSnapshot(turn) !== void 0)) {
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
return contributionDurationTotal(turnContributions(turns));
|
|
297
|
+
}
|
|
298
|
+
function buildConversationStatsReport(args) {
|
|
299
|
+
const conversations = recentConversationGroups(args);
|
|
300
|
+
const requesters = /* @__PURE__ */ new Map();
|
|
301
|
+
const locations = /* @__PURE__ */ new Map();
|
|
302
|
+
let durationMs = 0;
|
|
303
|
+
let tokens;
|
|
304
|
+
let active = 0;
|
|
305
|
+
let failed = 0;
|
|
306
|
+
let hung = 0;
|
|
307
|
+
for (const turns of conversations) {
|
|
308
|
+
const contributions = turnContributions(turns);
|
|
309
|
+
const conversationSignals = statusSignals(turns);
|
|
310
|
+
const conversationTokens = contributionTokenTotal(contributions);
|
|
311
|
+
durationMs += contributionDurationTotal(contributions);
|
|
312
|
+
tokens = addTokenTotal(tokens, conversationTokens);
|
|
313
|
+
active += conversationSignals.active ? 1 : 0;
|
|
314
|
+
failed += conversationSignals.failed ? 1 : 0;
|
|
315
|
+
hung += conversationSignals.hung ? 1 : 0;
|
|
316
|
+
const requesterTurns = /* @__PURE__ */ new Map();
|
|
317
|
+
for (const contribution of contributions) {
|
|
318
|
+
const requester = requesterLabel(contribution.turn.requesterIdentity) ?? "Unknown";
|
|
319
|
+
requesterTurns.set(requester, [
|
|
320
|
+
...requesterTurns.get(requester) ?? [],
|
|
321
|
+
contribution
|
|
322
|
+
]);
|
|
323
|
+
}
|
|
324
|
+
for (const [requester, requesterContributions] of requesterTurns) {
|
|
325
|
+
const item = requesters.get(requester) ?? emptyStatsItem(requester);
|
|
326
|
+
const signals = statusSignals(
|
|
327
|
+
requesterContributions.map((contribution) => contribution.turn)
|
|
328
|
+
);
|
|
329
|
+
item.conversations += 1;
|
|
330
|
+
item.turns += requesterContributions.length;
|
|
331
|
+
item.durationMs += contributionDurationTotal(requesterContributions);
|
|
332
|
+
item.active += signals.active ? 1 : 0;
|
|
333
|
+
item.failed += signals.failed ? 1 : 0;
|
|
334
|
+
item.hung += signals.hung ? 1 : 0;
|
|
335
|
+
addItemTokens(item, contributionTokenTotal(requesterContributions));
|
|
336
|
+
requesters.set(requester, item);
|
|
337
|
+
}
|
|
338
|
+
const location = locationLabel(newestTurn(turns));
|
|
339
|
+
const locationItem = locations.get(location) ?? emptyStatsItem(location);
|
|
340
|
+
locationItem.conversations += 1;
|
|
341
|
+
locationItem.turns += turns.length;
|
|
342
|
+
locationItem.durationMs += conversationDurationMs(turns);
|
|
343
|
+
locationItem.active += conversationSignals.active ? 1 : 0;
|
|
344
|
+
locationItem.failed += conversationSignals.failed ? 1 : 0;
|
|
345
|
+
locationItem.hung += conversationSignals.hung ? 1 : 0;
|
|
346
|
+
addItemTokens(locationItem, conversationTokens);
|
|
347
|
+
locations.set(location, locationItem);
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
active,
|
|
351
|
+
conversations: conversations.length,
|
|
352
|
+
durationMs,
|
|
353
|
+
failed,
|
|
354
|
+
generatedAt: args.generatedAt,
|
|
355
|
+
hung,
|
|
356
|
+
locations: statsItems(locations),
|
|
357
|
+
requesters: statsItems(requesters),
|
|
358
|
+
sampleLimit: args.sampleLimit,
|
|
359
|
+
sampleSize: args.sampleSize,
|
|
360
|
+
source: "turn_session_records",
|
|
361
|
+
...tokens !== void 0 ? { tokens } : {},
|
|
362
|
+
truncated: args.truncated,
|
|
363
|
+
turns: conversations.reduce((sum, turns) => sum + turns.length, 0),
|
|
364
|
+
windowEnd: new Date(args.nowMs).toISOString(),
|
|
365
|
+
windowStart: new Date(
|
|
366
|
+
args.nowMs - RECENT_CONVERSATION_STATS_WINDOW_MS
|
|
367
|
+
).toISOString()
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
async function completeSampledConversationSummaries(args) {
|
|
371
|
+
if (!args.truncated) {
|
|
372
|
+
return args.summaries;
|
|
373
|
+
}
|
|
374
|
+
const conversationIds = [
|
|
375
|
+
...new Set(args.summaries.map((summary) => summary.conversationId))
|
|
376
|
+
];
|
|
377
|
+
const groups = await Promise.all(
|
|
378
|
+
conversationIds.map(
|
|
379
|
+
(conversationId) => listAgentTurnSessionSummariesForConversation(conversationId)
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
const summariesByTurn = /* @__PURE__ */ new Map();
|
|
383
|
+
for (const group of groups) {
|
|
384
|
+
for (const summary of group) {
|
|
385
|
+
summariesByTurn.set(
|
|
386
|
+
`${summary.conversationId}:${summary.sessionId}`,
|
|
387
|
+
summary
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return [...summariesByTurn.values()].sort(
|
|
392
|
+
(left, right) => right.updatedAtMs - left.updatedAtMs
|
|
393
|
+
);
|
|
394
|
+
}
|
|
146
395
|
function canExposeConversationTranscript(summary) {
|
|
147
396
|
return canExposeConversationPayload({
|
|
148
397
|
conversationId: summary.conversationId
|
|
@@ -365,11 +614,50 @@ function traceIdFromTranscript(transcript) {
|
|
|
365
614
|
return void 0;
|
|
366
615
|
}
|
|
367
616
|
async function readSessions() {
|
|
368
|
-
const
|
|
617
|
+
const nowMs = Date.now();
|
|
618
|
+
const summaries = await listAgentTurnSessionSummaries(
|
|
619
|
+
DASHBOARD_SESSION_FEED_LIMIT
|
|
620
|
+
);
|
|
369
621
|
return {
|
|
370
622
|
source: "turn_session_records",
|
|
371
|
-
generatedAt:
|
|
372
|
-
sessions: summaries.map(
|
|
623
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
624
|
+
sessions: summaries.map(
|
|
625
|
+
(summary) => sessionReportFromSummary(summary, nowMs)
|
|
626
|
+
)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
async function readConversationStats() {
|
|
630
|
+
const nowMs = Date.now();
|
|
631
|
+
const generatedAt = new Date(nowMs).toISOString();
|
|
632
|
+
const summaries = await listAgentTurnSessionSummaries(
|
|
633
|
+
DASHBOARD_CONVERSATION_STATS_LIMIT + 1
|
|
634
|
+
);
|
|
635
|
+
const truncated = summaries.length >= DASHBOARD_CONVERSATION_STATS_LIMIT;
|
|
636
|
+
const sampledSummaries = summaries.slice(
|
|
637
|
+
0,
|
|
638
|
+
DASHBOARD_CONVERSATION_STATS_LIMIT
|
|
639
|
+
);
|
|
640
|
+
const reportSummaries = await completeSampledConversationSummaries({
|
|
641
|
+
summaries: sampledSummaries,
|
|
642
|
+
truncated
|
|
643
|
+
});
|
|
644
|
+
return buildConversationStatsReport({
|
|
645
|
+
generatedAt,
|
|
646
|
+
nowMs,
|
|
647
|
+
sampleLimit: DASHBOARD_CONVERSATION_STATS_LIMIT,
|
|
648
|
+
sampleSize: sampledSummaries.length,
|
|
649
|
+
sessions: reportSummaries.map(
|
|
650
|
+
(summary) => sessionReportFromSummary(summary, nowMs)
|
|
651
|
+
),
|
|
652
|
+
truncated
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
async function readPluginOperationalReports() {
|
|
656
|
+
const nowMs = Date.now();
|
|
657
|
+
return {
|
|
658
|
+
source: "trusted_plugins",
|
|
659
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
660
|
+
reports: await getAgentPluginOperationalReports(nowMs)
|
|
373
661
|
};
|
|
374
662
|
}
|
|
375
663
|
async function readConversation(conversationId) {
|
|
@@ -436,6 +724,8 @@ function createJuniorReporting() {
|
|
|
436
724
|
getPlugins: readPlugins,
|
|
437
725
|
getSkills: readSkills,
|
|
438
726
|
getSessions: readSessions,
|
|
727
|
+
getConversationStats: readConversationStats,
|
|
728
|
+
getPluginOperationalReports: readPluginOperationalReports,
|
|
439
729
|
getConversation: readConversation
|
|
440
730
|
};
|
|
441
731
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"node-html-markdown": "^2.0.0",
|
|
66
66
|
"yaml": "^2.9.0",
|
|
67
67
|
"zod": "^4.4.3",
|
|
68
|
-
"@sentry/junior-plugin-api": "0.
|
|
68
|
+
"@sentry/junior-plugin-api": "0.67.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^25.9.1",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"typescript": "^6.0.3",
|
|
78
78
|
"vercel": "^54.4.0",
|
|
79
79
|
"vitest": "^4.1.7",
|
|
80
|
-
"@sentry/junior-scheduler": "0.
|
|
80
|
+
"@sentry/junior-scheduler": "0.67.0"
|
|
81
81
|
},
|
|
82
82
|
"scripts": {
|
|
83
83
|
"build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",
|