@sentry/junior 0.102.0 → 0.102.2

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.
@@ -1,3 +1,3 @@
1
1
  import type { ConversationStatsReport } from "./schema";
2
- /** Build complete seven-day dashboard stats from normalized durable SQL records. */
2
+ /** Build complete 90-day dashboard stats from normalized durable SQL records. */
3
3
  export declare function readConversationStatsFromSql(): Promise<ConversationStatsReport>;
@@ -1,5 +1,10 @@
1
1
  /** Expose public conversation destinations as the dashboard's location index. */
2
2
  export declare function readLocationDirectory(): Promise<{
3
+ activityDays: {
4
+ date: string;
5
+ privateConversations: number;
6
+ publicConversations: number;
7
+ }[];
3
8
  generatedAt: string;
4
9
  locations: {
5
10
  active: number;
@@ -27,4 +32,6 @@ export declare function readLocationDirectory(): Promise<{
27
32
  tokens?: number | undefined;
28
33
  };
29
34
  source: "conversation_index";
35
+ windowEnd: string;
36
+ windowStart: string;
30
37
  }>;
@@ -37,7 +37,17 @@ export declare const locationActorSummaryReportSchema: z.ZodObject<{
37
37
  slackUserName: z.ZodOptional<z.ZodString>;
38
38
  }, z.core.$strict>;
39
39
  }, z.core.$strict>;
40
+ export declare const locationActivityDayReportSchema: z.ZodObject<{
41
+ date: z.ZodString;
42
+ privateConversations: z.ZodNumber;
43
+ publicConversations: z.ZodNumber;
44
+ }, z.core.$strict>;
40
45
  export declare const locationDirectoryReportSchema: z.ZodObject<{
46
+ activityDays: z.ZodArray<z.ZodObject<{
47
+ date: z.ZodString;
48
+ privateConversations: z.ZodNumber;
49
+ publicConversations: z.ZodNumber;
50
+ }, z.core.$strict>>;
41
51
  generatedAt: z.ZodString;
42
52
  locations: z.ZodArray<z.ZodObject<{
43
53
  active: z.ZodNumber;
@@ -72,6 +82,8 @@ export declare const locationDirectoryReportSchema: z.ZodObject<{
72
82
  tokens: z.ZodOptional<z.ZodNumber>;
73
83
  }, z.core.$strict>;
74
84
  source: z.ZodLiteral<"conversation_index">;
85
+ windowEnd: z.ZodString;
86
+ windowStart: z.ZodString;
75
87
  }, z.core.$strict>;
76
88
  export declare const locationDetailReportSchema: z.ZodObject<{
77
89
  active: z.ZodNumber;
@@ -171,5 +183,6 @@ export declare const locationDetailReportSchema: z.ZodObject<{
171
183
  }, z.core.$strict>;
172
184
  export type LocationSummaryReport = z.infer<typeof locationSummaryReportSchema>;
173
185
  export type LocationActorSummaryReport = z.infer<typeof locationActorSummaryReportSchema>;
186
+ export type LocationActivityDayReport = z.infer<typeof locationActivityDayReportSchema>;
174
187
  export type LocationDirectoryReport = z.infer<typeof locationDirectoryReportSchema>;
175
188
  export type LocationDetailReport = z.infer<typeof locationDetailReportSchema>;
@@ -1,6 +1,6 @@
1
1
  import type { ActorActivityDayReport, ConversationStatsItem, ActorTotalsReport } from "./schema";
2
2
  export declare const RECENT_LIMIT = 25;
3
- export declare const ACTIVITY_DAYS = 366;
3
+ export declare const ACTIVITY_DAYS = 90;
4
4
  /** Normalize emails before matching people API rows. */
5
5
  export declare function normalizeEmail(email: string | undefined): string | undefined;
6
6
  /** Build a zeroed totals object for people API aggregations. */
@@ -3,8 +3,8 @@ export type { DailyConversationActivity } from "./activity";
3
3
  export { conversationDetailReportSchema, conversationFeedSchema, conversationStatsReportSchema, conversationSubagentTranscriptReportSchema, } from "./conversations/schema";
4
4
  export type { ActorIdentity, ConversationActivityReport, ConversationActivityStatus, ConversationContextEvent, ConversationCost, ConversationDetailReport, ConversationFeed, ConversationReportStatus, ConversationStatsItem, ConversationStatsReport, ConversationSubagentActivityReport, ConversationSubagentTranscriptReport, ConversationSummaryReport, ConversationSurface, ConversationToolActivityReport, ConversationUsage, TranscriptMessage, TranscriptPart, TranscriptPartType, TranscriptRole, } from "./conversations/schema";
5
5
  export { actorDirectoryReportSchema, actorProfileReportSchema, } from "./people/schema";
6
- export { locationDetailReportSchema, locationDirectoryReportSchema, } from "./locations/schema";
7
- export type { LocationActorSummaryReport, LocationDetailReport, LocationDirectoryReport, LocationSummaryReport, } from "./locations/schema";
6
+ export { locationActivityDayReportSchema, locationDetailReportSchema, locationDirectoryReportSchema, } from "./locations/schema";
7
+ export type { LocationActorSummaryReport, LocationActivityDayReport, LocationDetailReport, LocationDirectoryReport, LocationSummaryReport, } from "./locations/schema";
8
8
  export type { ActorActivityDayReport, ActorDirectoryReport, ActorProfileReport, ActorSummaryReport, ActorTotalsReport, PeopleActivityDayReport, } from "./people/schema";
9
9
  export { healthReportSchema, pluginOperationalReportFeedSchema, pluginOperationalReportSchema, pluginPackageContentItemReportSchema, pluginPackageContentReportSchema, pluginReportSchema, pluginReportsSchema, runtimeInfoReportSchema, skillReportSchema, skillReportsSchema, } from "../reporting-schema";
10
10
  export type { HealthReport, PluginOperationalReport, PluginOperationalReportFeed, PluginPackageContentItemReport, PluginPackageContentReport, PluginReport, PluginReports, RuntimeInfoReport, SkillReport, SkillReports, } from "../reporting-schema";
@@ -7,9 +7,10 @@ import {
7
7
  } from "../chunk-2RNOB32R.js";
8
8
  import {
9
9
  dailyConversationActivitySchema,
10
+ locationActivityDayReportSchema,
10
11
  locationDetailReportSchema,
11
12
  locationDirectoryReportSchema
12
- } from "../chunk-HNIT4HXU.js";
13
+ } from "../chunk-WPHCUGN7.js";
13
14
  import {
14
15
  actorDirectoryReportSchema,
15
16
  actorProfileReportSchema
@@ -46,6 +47,7 @@ export {
46
47
  conversationSubagentTranscriptReportSchema,
47
48
  dailyConversationActivitySchema,
48
49
  healthReportSchema,
50
+ locationActivityDayReportSchema,
49
51
  locationDetailReportSchema,
50
52
  locationDirectoryReportSchema,
51
53
  locationParamsSchema,
package/dist/api.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  personParamsSchema,
6
6
  subagentParamsSchema
7
7
  } from "./chunk-2RNOB32R.js";
8
- import "./chunk-HNIT4HXU.js";
8
+ import "./chunk-WPHCUGN7.js";
9
9
  import "./chunk-4YN2ZXKB.js";
10
10
  import "./chunk-HYDQULR2.js";
11
11
  import {
@@ -134,7 +134,7 @@ function createJuniorApi() {
134
134
  return Response.json(await readConversationFeed({ actorEmail }));
135
135
  });
136
136
  app.get("/api/conversations/stats", async () => {
137
- const { readConversationStats } = await import("./stats-ZYFQFCTL.js");
137
+ const { readConversationStats } = await import("./stats-7UFY6FPE.js");
138
138
  return Response.json(await readConversationStats());
139
139
  });
140
140
  app.get("/api/conversations/:conversationId", async (c) => {
@@ -159,21 +159,21 @@ function createJuniorApi() {
159
159
  }
160
160
  );
161
161
  app.get("/api/people", async () => {
162
- const { readPeopleList } = await import("./list-UMZKA5AI.js");
162
+ const { readPeopleList } = await import("./list-NTUT6A5M.js");
163
163
  return Response.json(await readPeopleList());
164
164
  });
165
165
  app.get("/api/people/:email", async (c) => {
166
166
  const { email } = parseParams(personParamsSchema, c.req.param());
167
- const { readPeopleProfile } = await import("./profile-CQMPFKED.js");
167
+ const { readPeopleProfile } = await import("./profile-3FRQP4HP.js");
168
168
  return Response.json(await readPeopleProfile(email));
169
169
  });
170
170
  app.get("/api/locations", async () => {
171
- const { readLocationDirectory } = await import("./list-Z3CC52CZ.js");
171
+ const { readLocationDirectory } = await import("./list-FWLIZHSW.js");
172
172
  return Response.json(await readLocationDirectory());
173
173
  });
174
174
  app.get("/api/locations/:locationId", async (c) => {
175
175
  const { locationId } = parseParams(locationParamsSchema, c.req.param());
176
- const { readLocationDetail } = await import("./detail-U6IP2Z4N.js");
176
+ const { readLocationDetail } = await import("./detail-V37NTVBN.js");
177
177
  const report = await readLocationDetail(locationId);
178
178
  return report ? Response.json(report) : Response.json({ error: "Location not found." }, { status: 404 });
179
179
  });
package/dist/app.js CHANGED
@@ -11747,6 +11747,7 @@ function dashboardHostRoutePaths(dashboard) {
11747
11747
  return [
11748
11748
  ...pagePaths,
11749
11749
  "/favicon.ico",
11750
+ "/_junior/dashboard/avatar.png",
11750
11751
  "/_junior/dashboard/client.js",
11751
11752
  loginPath,
11752
11753
  "/api/health",
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-F4CQEUTP.js";
8
8
  import {
9
9
  activityDays
10
- } from "./chunk-HNIT4HXU.js";
10
+ } from "./chunk-WPHCUGN7.js";
11
11
  import {
12
12
  getDb,
13
13
  juniorConversations,
@@ -21,7 +21,7 @@ import {
21
21
  // src/api/locations/query.ts
22
22
  import { and, asc, desc, eq, gte, isNull, sql } from "drizzle-orm";
23
23
  var RECENT_LIMIT = 25;
24
- var ACTIVITY_DAYS = 30;
24
+ var ACTIVITY_DAYS = 90;
25
25
  function emptyMetrics() {
26
26
  return {
27
27
  active: 0,
@@ -117,9 +117,62 @@ async function directoryRows(db) {
117
117
  eq(juniorDestinations.id, juniorConversations.destinationId)
118
118
  ).where(topLevelWhere()).groupBy(...locationGroupBy());
119
119
  }
120
+ async function directoryActivityRows(db, start) {
121
+ const date = sql`TO_CHAR(
122
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
123
+ 'YYYY-MM-DD'
124
+ )`;
125
+ return db.select({
126
+ conversations: sql`COUNT(*)::integer`,
127
+ date,
128
+ visibility: juniorDestinations.visibility
129
+ }).from(juniorConversations).leftJoin(
130
+ juniorDestinations,
131
+ eq(juniorDestinations.id, juniorConversations.destinationId)
132
+ ).where(and(topLevelWhere(), gte(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
133
+ }
134
+ function directoryActivityDays(rows, nowMs) {
135
+ const days = /* @__PURE__ */ new Map();
136
+ for (const row of rows) {
137
+ const day = days.get(row.date) ?? {
138
+ date: row.date,
139
+ privateConversations: 0,
140
+ publicConversations: 0
141
+ };
142
+ if (row.visibility === "public") {
143
+ day.publicConversations += row.conversations;
144
+ } else {
145
+ day.privateConversations += row.conversations;
146
+ }
147
+ days.set(row.date, day);
148
+ }
149
+ const end = new Date(nowMs);
150
+ end.setUTCHours(0, 0, 0, 0);
151
+ const start = new Date(end);
152
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
153
+ const activity = [];
154
+ for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
155
+ const date = cursor.toISOString().slice(0, 10);
156
+ activity.push(
157
+ days.get(date) ?? {
158
+ date,
159
+ privateConversations: 0,
160
+ publicConversations: 0
161
+ }
162
+ );
163
+ }
164
+ return activity;
165
+ }
120
166
  async function readLocationDirectoryFromSql() {
121
167
  const nowMs = Date.now();
122
- const rows = await directoryRows(getDb());
168
+ const end = new Date(nowMs);
169
+ end.setUTCHours(0, 0, 0, 0);
170
+ const start = new Date(end);
171
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
172
+ const [rows, activityRows] = await Promise.all([
173
+ directoryRows(getDb()),
174
+ directoryActivityRows(getDb(), start)
175
+ ]);
123
176
  const locations = [];
124
177
  const privateActivity = {
125
178
  ...emptyMetrics(),
@@ -131,12 +184,15 @@ async function readLocationDirectoryFromSql() {
131
184
  else addMetrics(privateActivity, row);
132
185
  }
133
186
  return {
187
+ activityDays: directoryActivityDays(activityRows, nowMs),
134
188
  generatedAt: new Date(nowMs).toISOString(),
135
189
  locations: locations.sort(
136
190
  (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.label.localeCompare(right.label)
137
191
  ),
138
192
  privateActivity,
139
- source: "conversation_index"
193
+ source: "conversation_index",
194
+ windowEnd: end.toISOString(),
195
+ windowStart: start.toISOString()
140
196
  };
141
197
  }
142
198
  async function recentLocationRows(db, locationId) {
@@ -11,7 +11,7 @@ import {
11
11
  // src/api/people/shared.ts
12
12
  import { and, asc, desc, eq, sql } from "drizzle-orm";
13
13
  var RECENT_LIMIT = 25;
14
- var ACTIVITY_DAYS = 366;
14
+ var ACTIVITY_DAYS = 90;
15
15
  function normalizeEmail(email) {
16
16
  const normalized = email?.trim().toLowerCase();
17
17
  return normalized || void 0;
@@ -51,11 +51,19 @@ var locationSummaryReportSchema = conversationStatsItemSchema.extend({
51
51
  visibility: z2.literal("public")
52
52
  }).strict();
53
53
  var locationActorSummaryReportSchema = conversationStatsItemSchema.extend({ actor: actorIdentitySchema }).strict();
54
+ var locationActivityDayReportSchema = z2.object({
55
+ date: z2.string(),
56
+ privateConversations: z2.number(),
57
+ publicConversations: z2.number()
58
+ }).strict();
54
59
  var locationDirectoryReportSchema = z2.object({
60
+ activityDays: z2.array(locationActivityDayReportSchema),
55
61
  generatedAt: z2.string(),
56
62
  locations: z2.array(locationSummaryReportSchema),
57
63
  privateActivity: conversationStatsItemSchema,
58
- source: z2.literal("conversation_index")
64
+ source: z2.literal("conversation_index"),
65
+ windowEnd: z2.string(),
66
+ windowStart: z2.string()
59
67
  }).strict();
60
68
  var locationDetailReportSchema = locationSummaryReportSchema.extend({
61
69
  activityDays: z2.array(dailyConversationActivitySchema),
@@ -70,6 +78,7 @@ var locationDetailReportSchema = locationSummaryReportSchema.extend({
70
78
  export {
71
79
  dailyConversationActivitySchema,
72
80
  activityDays,
81
+ locationActivityDayReportSchema,
73
82
  locationDirectoryReportSchema,
74
83
  locationDetailReportSchema
75
84
  };
@@ -1310,6 +1310,291 @@ var agentTurnSessionActorMigration = {
1310
1310
  run: migrateAgentTurnSessionActor
1311
1311
  };
1312
1312
 
1313
+ // src/cli/upgrade/migrations/conversation-usage.ts
1314
+ var CONVERSATION_USAGE_REPAIR_BATCH_SIZE = 500;
1315
+ var MAX_SAFE_INTEGER = 9007199254740991;
1316
+ var MAX_FINITE_DOUBLE = "1.7976931348623157e308";
1317
+ var REPAIR_LOCK = "junior:upgrade:repair-conversation-usage";
1318
+ var USAGE_REPAIR_BATCH_SQL = `
1319
+ WITH candidates AS MATERIALIZED (
1320
+ SELECT
1321
+ conversation_id,
1322
+ updated_at,
1323
+ execution_updated_at
1324
+ FROM junior_conversations
1325
+ WHERE parent_conversation_id IS NULL
1326
+ AND execution_status = 'idle'
1327
+ AND conversation_id > $1
1328
+ ORDER BY conversation_id
1329
+ LIMIT $2
1330
+ ),
1331
+ -- Occurrence numbers preserve intentional duplicates within one epoch while
1332
+ -- collapsing context copies of the same message across rebuilt epochs.
1333
+ message_occurrences AS (
1334
+ SELECT
1335
+ step.conversation_id,
1336
+ step.payload -> 'message' AS message,
1337
+ row_number() OVER (
1338
+ PARTITION BY
1339
+ step.conversation_id,
1340
+ step.context_epoch,
1341
+ step.payload -> 'message'
1342
+ ORDER BY step.seq
1343
+ ) AS occurrence
1344
+ FROM junior_agent_steps AS step
1345
+ INNER JOIN candidates AS candidate
1346
+ ON candidate.conversation_id = step.conversation_id
1347
+ WHERE step.type = 'pi_message'
1348
+ AND step.role = 'assistant'
1349
+ AND jsonb_typeof(step.payload -> 'message' -> 'usage') = 'object'
1350
+ ),
1351
+ canonical_messages AS (
1352
+ SELECT DISTINCT conversation_id, message, occurrence
1353
+ FROM message_occurrences
1354
+ ),
1355
+ usage_values AS (
1356
+ SELECT
1357
+ conversation_id,
1358
+ CASE
1359
+ WHEN jsonb_typeof(message -> 'usage' -> 'input') = 'number'
1360
+ THEN greatest(0, floor((message -> 'usage' ->> 'input')::numeric))
1361
+ WHEN jsonb_typeof(message -> 'usage' -> 'inputTokens') = 'number'
1362
+ THEN greatest(0, floor((message -> 'usage' ->> 'inputTokens')::numeric))
1363
+ END AS input_tokens,
1364
+ CASE
1365
+ WHEN jsonb_typeof(message -> 'usage' -> 'output') = 'number'
1366
+ THEN greatest(0, floor((message -> 'usage' ->> 'output')::numeric))
1367
+ WHEN jsonb_typeof(message -> 'usage' -> 'outputTokens') = 'number'
1368
+ THEN greatest(0, floor((message -> 'usage' ->> 'outputTokens')::numeric))
1369
+ END AS output_tokens,
1370
+ CASE
1371
+ WHEN jsonb_typeof(message -> 'usage' -> 'cacheRead') = 'number'
1372
+ THEN greatest(0, floor((message -> 'usage' ->> 'cacheRead')::numeric))
1373
+ WHEN jsonb_typeof(message -> 'usage' -> 'cachedInputTokens') = 'number'
1374
+ THEN greatest(0, floor((message -> 'usage' ->> 'cachedInputTokens')::numeric))
1375
+ END AS cached_input_tokens,
1376
+ CASE
1377
+ WHEN jsonb_typeof(message -> 'usage' -> 'cacheWrite') = 'number'
1378
+ THEN greatest(0, floor((message -> 'usage' ->> 'cacheWrite')::numeric))
1379
+ WHEN jsonb_typeof(message -> 'usage' -> 'cacheCreationTokens') = 'number'
1380
+ THEN greatest(0, floor((message -> 'usage' ->> 'cacheCreationTokens')::numeric))
1381
+ END AS cache_creation_tokens,
1382
+ CASE
1383
+ WHEN jsonb_typeof(message -> 'usage' -> 'reasoning') = 'number'
1384
+ THEN greatest(0, floor((message -> 'usage' ->> 'reasoning')::numeric))
1385
+ WHEN jsonb_typeof(message -> 'usage' -> 'reasoningTokens') = 'number'
1386
+ THEN greatest(0, floor((message -> 'usage' ->> 'reasoningTokens')::numeric))
1387
+ END AS reasoning_tokens,
1388
+ CASE
1389
+ WHEN jsonb_typeof(message -> 'usage' -> 'totalTokens') = 'number'
1390
+ THEN greatest(0, floor((message -> 'usage' ->> 'totalTokens')::numeric))
1391
+ END AS total_tokens,
1392
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'input') = 'number'
1393
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'input')::numeric)
1394
+ END AS cost_input,
1395
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'output') = 'number'
1396
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'output')::numeric)
1397
+ END AS cost_output,
1398
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'cacheRead') = 'number'
1399
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'cacheRead')::numeric)
1400
+ END AS cost_cache_read,
1401
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'cacheWrite') = 'number'
1402
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'cacheWrite')::numeric)
1403
+ END AS cost_cache_write,
1404
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'total') = 'number'
1405
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'total')::numeric)
1406
+ END AS cost_total
1407
+ FROM canonical_messages
1408
+ ),
1409
+ rollups AS (
1410
+ SELECT
1411
+ conversation_id,
1412
+ sum(input_tokens) AS input_tokens,
1413
+ sum(output_tokens) AS output_tokens,
1414
+ sum(cached_input_tokens) AS cached_input_tokens,
1415
+ sum(cache_creation_tokens) AS cache_creation_tokens,
1416
+ sum(reasoning_tokens) AS reasoning_tokens,
1417
+ sum(
1418
+ CASE
1419
+ WHEN input_tokens IS NULL
1420
+ AND output_tokens IS NULL
1421
+ AND cached_input_tokens IS NULL
1422
+ AND cache_creation_tokens IS NULL
1423
+ THEN total_tokens
1424
+ END
1425
+ ) AS total_only_tokens,
1426
+ round(sum(cost_input), 12) AS cost_input,
1427
+ round(sum(cost_output), 12) AS cost_output,
1428
+ round(sum(cost_cache_read), 12) AS cost_cache_read,
1429
+ round(sum(cost_cache_write), 12) AS cost_cache_write,
1430
+ round(sum(cost_total), 12) AS cost_total
1431
+ FROM usage_values
1432
+ GROUP BY conversation_id
1433
+ HAVING count(input_tokens)
1434
+ + count(output_tokens)
1435
+ + count(cached_input_tokens)
1436
+ + count(cache_creation_tokens)
1437
+ + count(reasoning_tokens)
1438
+ + count(total_tokens)
1439
+ + count(cost_input)
1440
+ + count(cost_output)
1441
+ + count(cost_cache_read)
1442
+ + count(cost_cache_write)
1443
+ + count(cost_total) > 0
1444
+ ),
1445
+ valid_rollups AS (
1446
+ SELECT *
1447
+ FROM rollups
1448
+ WHERE (input_tokens IS NULL OR input_tokens <= ${MAX_SAFE_INTEGER})
1449
+ AND (output_tokens IS NULL OR output_tokens <= ${MAX_SAFE_INTEGER})
1450
+ AND (cached_input_tokens IS NULL OR cached_input_tokens <= ${MAX_SAFE_INTEGER})
1451
+ AND (cache_creation_tokens IS NULL OR cache_creation_tokens <= ${MAX_SAFE_INTEGER})
1452
+ AND (reasoning_tokens IS NULL OR reasoning_tokens <= ${MAX_SAFE_INTEGER})
1453
+ AND (total_only_tokens IS NULL OR total_only_tokens <= ${MAX_SAFE_INTEGER})
1454
+ AND (
1455
+ total_only_tokens IS NULL
1456
+ OR total_only_tokens
1457
+ + coalesce(input_tokens, 0)
1458
+ + coalesce(output_tokens, 0)
1459
+ + coalesce(cached_input_tokens, 0)
1460
+ + coalesce(cache_creation_tokens, 0) <= ${MAX_SAFE_INTEGER}
1461
+ )
1462
+ AND (cost_input IS NULL OR cost_input <= ${MAX_FINITE_DOUBLE}::numeric)
1463
+ AND (cost_output IS NULL OR cost_output <= ${MAX_FINITE_DOUBLE}::numeric)
1464
+ AND (cost_cache_read IS NULL OR cost_cache_read <= ${MAX_FINITE_DOUBLE}::numeric)
1465
+ AND (cost_cache_write IS NULL OR cost_cache_write <= ${MAX_FINITE_DOUBLE}::numeric)
1466
+ AND (cost_total IS NULL OR cost_total <= ${MAX_FINITE_DOUBLE}::numeric)
1467
+ ),
1468
+ normalized AS (
1469
+ SELECT
1470
+ conversation_id,
1471
+ jsonb_strip_nulls(jsonb_build_object(
1472
+ 'inputTokens', CASE WHEN total_only_tokens IS NULL THEN input_tokens END,
1473
+ 'outputTokens', CASE WHEN total_only_tokens IS NULL THEN output_tokens END,
1474
+ 'cachedInputTokens', CASE
1475
+ WHEN total_only_tokens IS NULL THEN cached_input_tokens
1476
+ END,
1477
+ 'cacheCreationTokens', CASE
1478
+ WHEN total_only_tokens IS NULL THEN cache_creation_tokens
1479
+ END,
1480
+ 'reasoningTokens', reasoning_tokens,
1481
+ 'totalTokens', CASE
1482
+ WHEN total_only_tokens IS NOT NULL
1483
+ THEN total_only_tokens
1484
+ + coalesce(input_tokens, 0)
1485
+ + coalesce(output_tokens, 0)
1486
+ + coalesce(cached_input_tokens, 0)
1487
+ + coalesce(cache_creation_tokens, 0)
1488
+ END,
1489
+ 'cost', CASE
1490
+ WHEN cost_input IS NULL
1491
+ AND cost_output IS NULL
1492
+ AND cost_cache_read IS NULL
1493
+ AND cost_cache_write IS NULL
1494
+ AND cost_total IS NULL
1495
+ THEN NULL
1496
+ ELSE jsonb_strip_nulls(jsonb_build_object(
1497
+ 'input', cost_input,
1498
+ 'output', cost_output,
1499
+ 'cacheRead', cost_cache_read,
1500
+ 'cacheWrite', cost_cache_write,
1501
+ 'total', cost_total
1502
+ ))
1503
+ END
1504
+ )) AS usage
1505
+ FROM valid_rollups
1506
+ ),
1507
+ matched AS MATERIALIZED (
1508
+ SELECT conversation.conversation_id
1509
+ FROM junior_conversations AS conversation
1510
+ INNER JOIN candidates AS candidate USING (conversation_id)
1511
+ INNER JOIN normalized USING (conversation_id)
1512
+ WHERE conversation.execution_status = 'idle'
1513
+ AND conversation.updated_at = candidate.updated_at
1514
+ AND conversation.execution_updated_at IS NOT DISTINCT FROM candidate.execution_updated_at
1515
+ FOR UPDATE OF conversation
1516
+ ),
1517
+ updated AS (
1518
+ UPDATE junior_conversations AS conversation
1519
+ SET usage_json = normalized.usage
1520
+ FROM normalized
1521
+ INNER JOIN matched USING (conversation_id)
1522
+ WHERE conversation.conversation_id = normalized.conversation_id
1523
+ AND conversation.usage_json IS DISTINCT FROM normalized.usage
1524
+ RETURNING conversation.conversation_id
1525
+ )
1526
+ SELECT
1527
+ candidate.conversation_id AS "conversationId",
1528
+ updated.conversation_id IS NOT NULL AS changed,
1529
+ matched.conversation_id IS NOT NULL AS matched,
1530
+ normalized.conversation_id IS NOT NULL AS repairable
1531
+ FROM candidates AS candidate
1532
+ LEFT JOIN normalized USING (conversation_id)
1533
+ LEFT JOIN matched USING (conversation_id)
1534
+ LEFT JOIN updated USING (conversation_id)
1535
+ ORDER BY candidate.conversation_id
1536
+ `;
1537
+ async function repairConversationUsage(_context, options = {}) {
1538
+ let executor = options.executor;
1539
+ let closeExecutor;
1540
+ if (!executor) {
1541
+ const { sql } = getChatConfig();
1542
+ executor = createJuniorSqlExecutor({
1543
+ connectionString: sql.databaseUrl,
1544
+ driver: sql.driver
1545
+ });
1546
+ closeExecutor = () => executor.close();
1547
+ }
1548
+ const batchSize = Math.max(
1549
+ 1,
1550
+ Math.floor(options.batchSize ?? CONVERSATION_USAGE_REPAIR_BATCH_SIZE)
1551
+ );
1552
+ try {
1553
+ let cursor = "";
1554
+ let existing = 0;
1555
+ let migrated = 0;
1556
+ let missing = 0;
1557
+ let scanned = 0;
1558
+ let skipped = 0;
1559
+ while (true) {
1560
+ const sources = await executor.withLock(
1561
+ REPAIR_LOCK,
1562
+ () => executor.query(USAGE_REPAIR_BATCH_SQL, [
1563
+ cursor,
1564
+ batchSize
1565
+ ])
1566
+ );
1567
+ if (sources.length === 0) break;
1568
+ scanned += sources.length;
1569
+ for (const source of sources) {
1570
+ if (!source.repairable) {
1571
+ missing += 1;
1572
+ } else if (source.changed) {
1573
+ migrated += 1;
1574
+ } else if (source.matched) {
1575
+ existing += 1;
1576
+ } else {
1577
+ skipped += 1;
1578
+ }
1579
+ }
1580
+ cursor = sources.at(-1).conversationId;
1581
+ }
1582
+ return {
1583
+ existing,
1584
+ migrated,
1585
+ missing,
1586
+ scanned,
1587
+ ...skipped > 0 ? { skipped } : {}
1588
+ };
1589
+ } finally {
1590
+ await closeExecutor?.();
1591
+ }
1592
+ }
1593
+ var conversationUsageRepairMigration = {
1594
+ name: "repair-conversation-usage",
1595
+ run: repairConversationUsage
1596
+ };
1597
+
1313
1598
  // src/cli/upgrade.ts
1314
1599
  var DEFAULT_IO = {
1315
1600
  info: console.log
@@ -1321,6 +1606,7 @@ var MIGRATIONS = [
1321
1606
  coreSqlSchemaMigration,
1322
1607
  sqlConversationMigration,
1323
1608
  sqlConversationHistoryMigration,
1609
+ conversationUsageRepairMigration,
1324
1610
  sqlPluginMigration,
1325
1611
  pluginStorageMigration
1326
1612
  ];
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  readLocationDetailFromSql
3
- } from "./chunk-MR6H67P2.js";
3
+ } from "./chunk-4XNQSJAX.js";
4
4
  import "./chunk-QOZOJNSE.js";
5
5
  import "./chunk-F4CQEUTP.js";
6
6
  import {
7
7
  locationDetailReportSchema
8
- } from "./chunk-HNIT4HXU.js";
8
+ } from "./chunk-WPHCUGN7.js";
9
9
  import "./chunk-HYDQULR2.js";
10
10
  import "./chunk-PDO5BLNM.js";
11
11
  import "./chunk-QZGVPPIJ.js";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  readLocationDirectoryFromSql
3
- } from "./chunk-MR6H67P2.js";
3
+ } from "./chunk-4XNQSJAX.js";
4
4
  import "./chunk-QOZOJNSE.js";
5
5
  import "./chunk-F4CQEUTP.js";
6
6
  import {
7
7
  locationDirectoryReportSchema
8
- } from "./chunk-HNIT4HXU.js";
8
+ } from "./chunk-WPHCUGN7.js";
9
9
  import "./chunk-HYDQULR2.js";
10
10
  import "./chunk-PDO5BLNM.js";
11
11
  import "./chunk-QZGVPPIJ.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  verifiedActorWhere
3
- } from "./chunk-E4EMFKGB.js";
3
+ } from "./chunk-KFP3TZOS.js";
4
4
  import {
5
5
  conversationActiveDaysColumn,
6
6
  conversationAggregateColumns,
@@ -11,7 +11,7 @@ import {
11
11
  recentActorRows,
12
12
  statsItems,
13
13
  verifiedActorWhere
14
- } from "./chunk-E4EMFKGB.js";
14
+ } from "./chunk-KFP3TZOS.js";
15
15
  import {
16
16
  conversationActiveDaysColumn,
17
17
  conversationAggregateColumns
@@ -25,7 +25,7 @@ import "./chunk-MLKGABMK.js";
25
25
 
26
26
  // src/api/conversations/stats.query.ts
27
27
  import { and, eq, gte, isNull, lte } from "drizzle-orm";
28
- var WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
28
+ var WINDOW_MS = 90 * 24 * 60 * 60 * 1e3;
29
29
  function emptyStatsItem(label) {
30
30
  return {
31
31
  active: 0,
@@ -31,4 +31,4 @@
31
31
  "breakpoints": true
32
32
  }
33
33
  ]
34
- }
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.102.0",
3
+ "version": "0.102.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -75,7 +75,7 @@
75
75
  "pg": "^8.16.3",
76
76
  "yaml": "^2.9.0",
77
77
  "zod": "^4.4.3",
78
- "@sentry/junior-plugin-api": "0.102.0"
78
+ "@sentry/junior-plugin-api": "0.102.2"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@emnapi/core": "^1.10.0",
@@ -92,9 +92,9 @@
92
92
  "typescript": "^6.0.3",
93
93
  "vercel": "^54.4.0",
94
94
  "vitest": "^4.1.7",
95
- "@sentry/junior-github": "0.102.0",
96
- "@sentry/junior-scheduler": "0.102.0",
97
- "@sentry/junior-memory": "0.102.0",
95
+ "@sentry/junior-memory": "0.102.2",
96
+ "@sentry/junior-github": "0.102.2",
97
+ "@sentry/junior-scheduler": "0.102.2",
98
98
  "@sentry/junior-testing": "0.0.0"
99
99
  },
100
100
  "scripts": {