@sentry/junior 0.191.0 → 0.193.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/{agent-hooks-HIWWBBKO.js → agent-hooks-DWY3MZZ7.js} +11 -11
  2. package/dist/api/activity.d.ts +3 -1
  3. package/dist/api/code/overview.d.ts +12 -0
  4. package/dist/api/locations/detail.d.ts +8 -0
  5. package/dist/api/locations/list.d.ts +5 -0
  6. package/dist/api/people/code.d.ts +6 -0
  7. package/dist/api/people/shared.d.ts +2 -0
  8. package/dist/api/reporting-window.d.ts +40 -0
  9. package/dist/api/schema/code.d.ts +14 -0
  10. package/dist/api/schema/conversation.d.ts +27 -0
  11. package/dist/api/schema/location.d.ts +15 -0
  12. package/dist/api/schema/person.d.ts +104 -0
  13. package/dist/api/schema/task.d.ts +15 -2
  14. package/dist/api/schema.d.ts +1 -1
  15. package/dist/api/schema.js +2 -2
  16. package/dist/api.js +536 -141
  17. package/dist/app.js +50 -83
  18. package/dist/{catalog-runtime-4OKH4265.js → catalog-runtime-NAD44W5K.js} +4 -4
  19. package/dist/chat/agent/prompt.d.ts +1 -1
  20. package/dist/chat/agent-dispatch/types.d.ts +2 -1
  21. package/dist/chat/agent-dispatch/validation.d.ts +1 -1
  22. package/dist/chat/agent-invocations/types.d.ts +24 -28
  23. package/dist/chat/event-tasks/tool-support.d.ts +10 -1
  24. package/dist/chat/plugins/agent-hooks.d.ts +2 -2
  25. package/dist/chat/prompt.d.ts +4 -5
  26. package/dist/chat/services/turn-session-routing.d.ts +4 -3
  27. package/dist/chat/slack/tool-support/context.d.ts +3 -6
  28. package/dist/chat/source.d.ts +4 -6
  29. package/dist/chat/tasks/execution-stats.d.ts +12 -0
  30. package/dist/chat/tools/delete-event-task.d.ts +1 -1
  31. package/dist/chat/tools/types.d.ts +10 -9
  32. package/dist/chat/tools/update-event-task.d.ts +1 -1
  33. package/dist/{chunk-S7NSFCCW.js → chunk-2CYWEVOP.js} +4 -4
  34. package/dist/{chunk-6KISW5UY.js → chunk-3YZ2Q6AW.js} +2 -2
  35. package/dist/{chunk-X6PQKQTY.js → chunk-42N6CDWF.js} +4 -4
  36. package/dist/{chunk-Q32BSFN4.js → chunk-4UA5C7AM.js} +32 -25
  37. package/dist/{chunk-VGYZ3YZM.js → chunk-5E5SLAVD.js} +21 -21
  38. package/dist/{chunk-3PDR63SX.js → chunk-5LEVZYXF.js} +4 -4
  39. package/dist/{chunk-SEIZ2XLD.js → chunk-5S6AQFNG.js} +1 -1
  40. package/dist/{chunk-AWFSSFAX.js → chunk-AZVDODSP.js} +11 -11
  41. package/dist/{chunk-LOYTPSAK.js → chunk-BFP3ZKG6.js} +12 -8
  42. package/dist/{chunk-GQN4REF5.js → chunk-COCVPDWX.js} +11 -7
  43. package/dist/{chunk-CECRMPXJ.js → chunk-D4XQZOHB.js} +8 -8
  44. package/dist/{chunk-WNIVCR74.js → chunk-D5YQPCEG.js} +2 -2
  45. package/dist/{chunk-CCPOZMS4.js → chunk-DVU734PI.js} +3 -3
  46. package/dist/{chunk-EQZUBYRF.js → chunk-DXFUYLHX.js} +1 -1
  47. package/dist/{chunk-PFAZSRGR.js → chunk-EYIMZY5O.js} +114 -19
  48. package/dist/{chunk-NK3UEKAW.js → chunk-FQ5HFSFR.js} +81 -33
  49. package/dist/{chunk-K2NKG42A.js → chunk-GTXQHZ2O.js} +5 -5
  50. package/dist/{chunk-BVWC5DBM.js → chunk-GUKGU6R7.js} +1 -1
  51. package/dist/{chunk-ZJRGWZZ2.js → chunk-GUMBIDDL.js} +74 -4
  52. package/dist/{chunk-DP6H3U2X.js → chunk-HMBTX5RK.js} +1 -1
  53. package/dist/{chunk-CUUE5MGF.js → chunk-IBXXTO3J.js} +3 -3
  54. package/dist/{chunk-YNRLCUCB.js → chunk-IOUUS6H4.js} +1 -1
  55. package/dist/{chunk-MFZOIKET.js → chunk-IQCL5JGL.js} +1 -1
  56. package/dist/{chunk-WCSXTDOL.js → chunk-K47OVEO4.js} +1 -1
  57. package/dist/{chunk-7PYCRKQL.js → chunk-KWXZMHJS.js} +3 -3
  58. package/dist/{chunk-5IMKUVMS.js → chunk-LG7Q4OW4.js} +9 -9
  59. package/dist/{chunk-HNMRX2EN.js → chunk-LXRTHE43.js} +6 -7
  60. package/dist/{chunk-RZS4F5K4.js → chunk-N6Q2PNSI.js} +3 -3
  61. package/dist/{chunk-BVYUIVXE.js → chunk-OICYQPKP.js} +1 -1
  62. package/dist/{chunk-NY5SJGJZ.js → chunk-PYBFVK6K.js} +73 -64
  63. package/dist/{chunk-ZNFNGDT7.js → chunk-QDU5MW2R.js} +1 -1
  64. package/dist/{chunk-QRNJO7DZ.js → chunk-QWY4KI2V.js} +1 -1
  65. package/dist/{chunk-L4JLLS6C.js → chunk-QY2IU47O.js} +1 -1
  66. package/dist/{chunk-UTL3MHPC.js → chunk-RC745GGZ.js} +8 -1
  67. package/dist/{chunk-SGRL7NV6.js → chunk-S5HS6WRH.js} +5 -5
  68. package/dist/{chunk-WBNQYVCM.js → chunk-THFNL3TL.js} +3 -4
  69. package/dist/{chunk-ZRN3OS7M.js → chunk-THYNA2QN.js} +1 -1
  70. package/dist/{chunk-TH6LMEOW.js → chunk-WH5VV2RW.js} +1 -1
  71. package/dist/{chunk-2JAWOTUT.js → chunk-WJJDQLVL.js} +2 -2
  72. package/dist/{chunk-KTW62MG5.js → chunk-ZFPEPWOO.js} +6 -6
  73. package/dist/cli/chat.js +33 -33
  74. package/dist/cli/check.js +5 -5
  75. package/dist/cli/plugins.js +12 -12
  76. package/dist/cli/snapshot-create.js +9 -9
  77. package/dist/cli/upgrade.js +6 -6
  78. package/dist/db/schema/agent-invocations.d.ts +16 -0
  79. package/dist/db/schema/stats.d.ts +5 -1
  80. package/dist/db/schema.d.ts +16 -0
  81. package/dist/{db-LG4ZEWA3.js → db-H5MDRQEY.js} +6 -6
  82. package/dist/instrumentation.js +3 -3
  83. package/dist/nitro.js +6 -6
  84. package/dist/{oauth-callback-server-KI7ZRTOG.js → oauth-callback-server-IOU3NUHT.js} +29 -29
  85. package/dist/reporting-schema.d.ts +2 -2
  86. package/dist/{runner-K2EOSAZQ.js → runner-FZ5ZHLFG.js} +21 -21
  87. package/dist/{skills-JTRDFKMA.js → skills-AR3WD6BX.js} +5 -5
  88. package/dist/spawn-ZU6JABR7.js +42 -0
  89. package/dist/task-runner-EEGGG2PM.js +36 -0
  90. package/dist/{validation-NBWURHAP.js → validation-VYE7TQY7.js} +5 -5
  91. package/dist/version.js +1 -1
  92. package/dist/{work-35TBEAUI.js → work-CTQFWYKE.js} +21 -21
  93. package/dist/{worker-F762M6XM.js → worker-6WQU3IT5.js} +10 -10
  94. package/package.json +5 -5
  95. package/dist/spawn-SRBETYCF.js +0 -42
  96. package/dist/task-runner-INQIQ4VF.js +0 -36
package/dist/api.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import {
2
+ DAY_MS,
3
+ HOUR_MS,
2
4
  acceptedConversationMessageSchema,
3
5
  activityDays,
6
+ activityHours,
4
7
  actorDirectoryReportSchema,
5
8
  actorProfileReportSchema,
6
9
  apiErrorSchema,
@@ -26,6 +29,8 @@ import {
26
29
  createPersonalTokenBodySchema,
27
30
  createdPersonalTokenSchema,
28
31
  deleteWorkspaceResponseSchema,
32
+ fillUtcDays,
33
+ fillUtcHours,
29
34
  locationDetailReportSchema,
30
35
  locationDirectoryReportSchema,
31
36
  locationParamsSchema,
@@ -39,11 +44,15 @@ import {
39
44
  taskListSchema,
40
45
  taskParamsSchema,
41
46
  taskRunListSchema,
47
+ trailingUtcDayWindow,
48
+ trailingUtcHourWindow,
49
+ utcDayKey,
50
+ utcHourKey,
42
51
  workspaceBodySchema,
43
52
  workspaceListSchema,
44
53
  workspaceParamsSchema,
45
54
  workspaceSchema
46
- } from "./chunk-PFAZSRGR.js";
55
+ } from "./chunk-EYIMZY5O.js";
47
56
  import {
48
57
  appendAndEnqueueWebMessage,
49
58
  conversationArchiveTimes,
@@ -63,27 +72,29 @@ import {
63
72
  resolveSlackConversationContextFromThreadId,
64
73
  viewerConversationMembership,
65
74
  webActorFromEmail
66
- } from "./chunk-5IMKUVMS.js";
75
+ } from "./chunk-LG7Q4OW4.js";
67
76
  import {
68
77
  buildSentryConversationUrl,
69
78
  buildSentryEventUrl,
70
79
  getWebAuthorization
71
- } from "./chunk-ZRN3OS7M.js";
80
+ } from "./chunk-THYNA2QN.js";
72
81
  import {
73
82
  readTaskExecutionByConversationId,
74
83
  readTaskExecutionDays,
84
+ readTaskExecutionHours,
75
85
  readTaskExecutionStatusDays,
86
+ readTaskExecutionStatusHours,
76
87
  readTaskExecutionSummaries,
77
88
  readTaskExecutions,
78
89
  readTaskRuns
79
- } from "./chunk-ZJRGWZZ2.js";
90
+ } from "./chunk-GUMBIDDL.js";
80
91
  import {
81
92
  healthReportSchema,
82
93
  pluginOperationalReportFeedSchema,
83
94
  pluginsSchema,
84
95
  runtimeInfoReportSchema,
85
96
  skillReportsSchema
86
- } from "./chunk-UTL3MHPC.js";
97
+ } from "./chunk-RC745GGZ.js";
87
98
  import {
88
99
  deleteEventTask,
89
100
  eventTaskBelongsToUser,
@@ -99,24 +110,24 @@ import {
99
110
  readScheduledTask,
100
111
  readStats,
101
112
  saveScheduledTask
102
- } from "./chunk-LOYTPSAK.js";
113
+ } from "./chunk-BFP3ZKG6.js";
103
114
  import {
104
115
  parseSlackThreadId
105
- } from "./chunk-DP6H3U2X.js";
116
+ } from "./chunk-HMBTX5RK.js";
106
117
  import {
107
118
  cancelHumanFacingPendingMessages,
108
119
  getConversation
109
- } from "./chunk-3PDR63SX.js";
120
+ } from "./chunk-5LEVZYXF.js";
110
121
  import "./chunk-DA2KGZZI.js";
111
122
  import {
112
123
  agentTurnUsageSchema,
113
124
  fallbackShortTitle,
114
125
  hasAgentTurnUsage,
115
126
  renderJuniorNativeConversationEvent
116
- } from "./chunk-WNIVCR74.js";
127
+ } from "./chunk-D5YQPCEG.js";
117
128
  import {
118
129
  getCachedSnapshot
119
- } from "./chunk-CUUE5MGF.js";
130
+ } from "./chunk-IBXXTO3J.js";
120
131
  import "./chunk-PDO5BLNM.js";
121
132
  import {
122
133
  getVercelConversationWorkQueue
@@ -142,7 +153,7 @@ import {
142
153
  updateWorkspace,
143
154
  withSlackRetries,
144
155
  workspaceRepoCheckoutPath
145
- } from "./chunk-NK3UEKAW.js";
156
+ } from "./chunk-FQ5HFSFR.js";
146
157
  import "./chunk-3Y47VMHV.js";
147
158
  import {
148
159
  getConversationStore,
@@ -151,7 +162,7 @@ import {
151
162
  locationFromRow,
152
163
  parseSessionSource,
153
164
  resolveRootConversationId
154
- } from "./chunk-S7NSFCCW.js";
165
+ } from "./chunk-2CYWEVOP.js";
155
166
  import {
156
167
  conversationReadColumns,
157
168
  juniorApiTokens,
@@ -163,23 +174,23 @@ import {
163
174
  juniorIdentities,
164
175
  juniorSchedulerTasks,
165
176
  juniorUsers
166
- } from "./chunk-SEIZ2XLD.js";
177
+ } from "./chunk-5S6AQFNG.js";
167
178
  import {
168
179
  create,
169
180
  hash
170
- } from "./chunk-7PYCRKQL.js";
171
- import "./chunk-YNRLCUCB.js";
181
+ } from "./chunk-KWXZMHJS.js";
182
+ import "./chunk-IOUUS6H4.js";
172
183
  import {
173
184
  decodeStoredConversationEvent
174
185
  } from "./chunk-GFBR4S3Q.js";
175
186
  import {
176
187
  juniorDestinations
177
188
  } from "./chunk-VFUK3X5B.js";
178
- import "./chunk-2JAWOTUT.js";
189
+ import "./chunk-WJJDQLVL.js";
179
190
  import "./chunk-D4YV5TDV.js";
180
- import "./chunk-BVYUIVXE.js";
181
- import "./chunk-EQZUBYRF.js";
182
- import "./chunk-QRNJO7DZ.js";
191
+ import "./chunk-OICYQPKP.js";
192
+ import "./chunk-DXFUYLHX.js";
193
+ import "./chunk-QWY4KI2V.js";
183
194
  import "./chunk-CEMMLV5L.js";
184
195
  import {
185
196
  parseSlackChannelId,
@@ -191,9 +202,9 @@ import "./chunk-TXKUKMVO.js";
191
202
  import {
192
203
  logException,
193
204
  logWarn
194
- } from "./chunk-L4JLLS6C.js";
205
+ } from "./chunk-QY2IU47O.js";
195
206
  import "./chunk-PEQXYSSE.js";
196
- import "./chunk-ZNFNGDT7.js";
207
+ import "./chunk-QDU5MW2R.js";
197
208
 
198
209
  // src/api.ts
199
210
  import { Hono as Hono9 } from "hono";
@@ -2448,8 +2459,9 @@ async function readViewerTasks(user) {
2448
2459
  const visible = selected.filter(
2449
2460
  (candidate) => candidate.ownedByViewer || destinations.get(destinationKey(candidate.task.destination))?.visibility === "public"
2450
2461
  );
2451
- const [executionDays, scheduledStats, eventStats] = await Promise.all([
2462
+ const [executionDays, executionHours, scheduledStats, eventStats] = await Promise.all([
2452
2463
  readTaskExecutionDays(),
2464
+ readTaskExecutionHours(),
2453
2465
  readTaskExecutionSummaries("scheduled", "junior"),
2454
2466
  readTaskExecutionSummaries("event", "junior")
2455
2467
  ]);
@@ -2485,6 +2497,7 @@ async function readViewerTasks(user) {
2485
2497
  });
2486
2498
  return {
2487
2499
  executionDays,
2500
+ executionHours,
2488
2501
  tasks,
2489
2502
  truncated: ownedCandidates.length > TASK_LIST_LIMIT || publicCandidates.length > TASK_LIST_LIMIT
2490
2503
  };
@@ -2518,7 +2531,7 @@ var ViewerTaskNotFoundError = class extends Error {
2518
2531
  async function readViewerTaskExecutions(user, kind, id) {
2519
2532
  const candidate = await resolveViewerTaskCandidate(user, kind, id);
2520
2533
  if (!candidate) throw new ViewerTaskNotFoundError();
2521
- const [task, executions, executionDays] = await Promise.all([
2534
+ const [task, executions, executionDays, executionHours] = await Promise.all([
2522
2535
  taskSummaryForCandidate(candidate),
2523
2536
  readTaskExecutions({
2524
2537
  kind,
@@ -2528,10 +2541,15 @@ async function readViewerTaskExecutions(user, kind, id) {
2528
2541
  readTaskExecutionStatusDays({
2529
2542
  kind,
2530
2543
  taskId: id
2544
+ }),
2545
+ readTaskExecutionStatusHours({
2546
+ kind,
2547
+ taskId: id
2531
2548
  })
2532
2549
  ]);
2533
2550
  return {
2534
2551
  executionDays,
2552
+ executionHours,
2535
2553
  executions: executions.slice(0, TASK_EXECUTION_LIST_LIMIT),
2536
2554
  task,
2537
2555
  truncated: executions.length > TASK_EXECUTION_LIST_LIMIT
@@ -3073,6 +3091,27 @@ function statsWindow(nowMs) {
3073
3091
  start.setUTCDate(start.getUTCDate() - (WINDOW_DAYS - 1));
3074
3092
  return { end, start };
3075
3093
  }
3094
+ var HOUR_MS2 = 60 * 60 * 1e3;
3095
+ var WINDOW_HOURS = 24;
3096
+ function startOfUtcHour(valueMs) {
3097
+ const date = new Date(valueMs);
3098
+ date.setUTCMinutes(0, 0, 0);
3099
+ return date;
3100
+ }
3101
+ function utcHourKey2(value) {
3102
+ return value.toISOString().slice(0, 13);
3103
+ }
3104
+ function metricPoint(date, row) {
3105
+ return {
3106
+ conversations: row?.conversations ?? 0,
3107
+ date,
3108
+ durationMs: row?.durationMs ?? 0,
3109
+ ...row?.cachedInputTokens !== null && row?.cachedInputTokens !== void 0 ? { cachedInputTokens: row.cachedInputTokens } : void 0,
3110
+ ...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: addUsd(void 0, row.costUsd) } : void 0,
3111
+ ...row?.inputTokens !== null && row?.inputTokens !== void 0 ? { inputTokens: row.inputTokens } : void 0,
3112
+ ...row?.tokens !== null && row?.tokens !== void 0 ? { tokens: row.tokens } : void 0
3113
+ };
3114
+ }
3076
3115
  function metricDays(rows, endMs) {
3077
3116
  const byDate = new Map(rows.map((row) => [row.date, row]));
3078
3117
  const end = new Date(endMs);
@@ -3082,21 +3121,33 @@ function metricDays(rows, endMs) {
3082
3121
  const days = [];
3083
3122
  for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
3084
3123
  const date = cursor.toISOString().slice(0, 10);
3085
- const row = byDate.get(date);
3086
- days.push({
3087
- conversations: row?.conversations ?? 0,
3088
- date,
3089
- durationMs: row?.durationMs ?? 0,
3090
- ...row?.cachedInputTokens !== null && row?.cachedInputTokens !== void 0 ? { cachedInputTokens: row.cachedInputTokens } : void 0,
3091
- ...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: addUsd(void 0, row.costUsd) } : void 0,
3092
- ...row?.inputTokens !== null && row?.inputTokens !== void 0 ? { inputTokens: row.inputTokens } : void 0,
3093
- ...row?.tokens !== null && row?.tokens !== void 0 ? { tokens: row.tokens } : void 0
3094
- });
3124
+ days.push(metricPoint(date, byDate.get(date)));
3095
3125
  }
3096
3126
  return days;
3097
3127
  }
3098
- function guardianStats(rows, endMs) {
3099
- const byDate = new Map(rows.map((row) => [row.date, row]));
3128
+ function metricHours(rows, endMs) {
3129
+ const byHour = new Map(rows.map((row) => [row.date, row]));
3130
+ const end = startOfUtcHour(endMs);
3131
+ const start = new Date(end.getTime() - (WINDOW_HOURS - 1) * HOUR_MS2);
3132
+ const hours = [];
3133
+ for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setTime(cursor.getTime() + HOUR_MS2)) {
3134
+ const date = utcHourKey2(cursor);
3135
+ hours.push(metricPoint(date, byHour.get(date)));
3136
+ }
3137
+ return hours;
3138
+ }
3139
+ function guardianPoint(date, row) {
3140
+ return {
3141
+ allow: row?.allow ?? 0,
3142
+ ask: row?.ask ?? 0,
3143
+ date,
3144
+ deny: row?.deny ?? 0,
3145
+ requests: row?.requests ?? 0,
3146
+ ...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: row.costUsd } : void 0
3147
+ };
3148
+ }
3149
+ function guardianStats(dayRows, hourRows, endMs) {
3150
+ const byDate = new Map(dayRows.map((row) => [row.date, row]));
3100
3151
  const end = new Date(endMs);
3101
3152
  end.setUTCHours(0, 0, 0, 0);
3102
3153
  const start = new Date(end);
@@ -3117,20 +3168,22 @@ function guardianStats(rows, endMs) {
3117
3168
  if (row?.costUsd !== null && row?.costUsd !== void 0) {
3118
3169
  costUsd = addUsd(costUsd, row.costUsd);
3119
3170
  }
3120
- metricDays2.push({
3121
- allow: row?.allow ?? 0,
3122
- ask: row?.ask ?? 0,
3123
- date,
3124
- deny: row?.deny ?? 0,
3125
- requests: row?.requests ?? 0,
3126
- ...row?.costUsd !== null && row?.costUsd !== void 0 ? { costUsd: row.costUsd } : void 0
3127
- });
3171
+ metricDays2.push(guardianPoint(date, row));
3172
+ }
3173
+ const byHour = new Map(hourRows.map((row) => [row.date, row]));
3174
+ const hourEnd = startOfUtcHour(endMs);
3175
+ const hourStart = new Date(hourEnd.getTime() - (WINDOW_HOURS - 1) * HOUR_MS2);
3176
+ const metricHours2 = [];
3177
+ for (const cursor = new Date(hourStart); cursor.getTime() <= hourEnd.getTime(); cursor.setTime(cursor.getTime() + HOUR_MS2)) {
3178
+ const date = utcHourKey2(cursor);
3179
+ metricHours2.push(guardianPoint(date, byHour.get(date)));
3128
3180
  }
3129
3181
  return {
3130
3182
  allow,
3131
3183
  ask,
3132
3184
  deny,
3133
3185
  metricDays: metricDays2,
3186
+ metricHours: metricHours2,
3134
3187
  requests,
3135
3188
  ...costUsd !== void 0 ? { costUsd } : void 0
3136
3189
  };
@@ -3141,7 +3194,17 @@ async function aggregateStats(db, start, end) {
3141
3194
  ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
3142
3195
  'YYYY-MM-DD'
3143
3196
  )`;
3144
- const [totalsRows, actorRows, locationRows, metricRows] = await Promise.all([
3197
+ const activityHour = sql10`TO_CHAR(
3198
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
3199
+ 'YYYY-MM-DD"T"HH24'
3200
+ )`;
3201
+ const [
3202
+ totalsRows,
3203
+ actorRows,
3204
+ locationRows,
3205
+ metricRows,
3206
+ metricHourRows
3207
+ ] = await Promise.all([
3145
3208
  db.select(treeAggregateColumns).from(juniorConversations).innerJoin(
3146
3209
  treeConversation,
3147
3210
  eq10(
@@ -3213,41 +3276,72 @@ async function aggregateStats(db, start, end) {
3213
3276
  treeConversation.rootConversationId,
3214
3277
  juniorConversations.conversationId
3215
3278
  )
3216
- ).where(where).groupBy(activityDate)
3279
+ ).where(where).groupBy(activityDate),
3280
+ db.select({
3281
+ cachedInputTokens: treeAggregateColumns.cachedInputTokens,
3282
+ conversations: treeAggregateColumns.conversations,
3283
+ costUsd: treeAggregateColumns.costUsd,
3284
+ date: activityHour,
3285
+ durationMs: treeAggregateColumns.durationMs,
3286
+ inputTokens: treeAggregateColumns.inputTokens,
3287
+ tokens: treeAggregateColumns.tokens
3288
+ }).from(juniorConversations).innerJoin(
3289
+ treeConversation,
3290
+ eq10(
3291
+ treeConversation.rootConversationId,
3292
+ juniorConversations.conversationId
3293
+ )
3294
+ ).where(where).groupBy(activityHour)
3217
3295
  ]);
3218
- return { actorRows, locationRows, metricRows, totals: totalsRows[0] };
3296
+ return {
3297
+ actorRows,
3298
+ locationRows,
3299
+ metricHourRows,
3300
+ metricRows,
3301
+ totals: totalsRows[0]
3302
+ };
3219
3303
  }
3220
3304
  async function aggregateGuardianStats(db, start, end) {
3221
- const date = sql10`TO_CHAR(
3305
+ const day = sql10`TO_CHAR(
3222
3306
  ${juniorConversationEvents.createdAt} AT TIME ZONE 'UTC',
3223
3307
  'YYYY-MM-DD'
3224
3308
  )`;
3309
+ const hour = sql10`TO_CHAR(
3310
+ ${juniorConversationEvents.createdAt} AT TIME ZONE 'UTC',
3311
+ 'YYYY-MM-DD"T"HH24'
3312
+ )`;
3225
3313
  const decision = sql10`${juniorConversationEvents.payload}->>'decision'`;
3226
3314
  const cost2 = sql10`CASE
3227
3315
  WHEN jsonb_typeof(${juniorConversationEvents.payload}->'costUsd') = 'number'
3228
3316
  THEN (${juniorConversationEvents.payload}->>'costUsd')::double precision
3229
3317
  ELSE NULL
3230
3318
  END`;
3231
- return await db.select({
3319
+ const selectColumns = {
3232
3320
  allow: sql10`COUNT(*) FILTER (WHERE ${decision} = 'allow')::integer`,
3233
3321
  ask: sql10`COUNT(*) FILTER (WHERE ${decision} = 'ask')::integer`,
3234
3322
  costUsd: sql10`SUM(${cost2})::double precision`,
3235
- date,
3236
3323
  deny: sql10`COUNT(*) FILTER (WHERE ${decision} = 'deny')::integer`,
3237
3324
  requests: sql10`COUNT(*)::integer`
3238
- }).from(juniorConversationEvents).where(
3239
- and10(
3240
- eq10(juniorConversationEvents.type, "guardian_action_reviewed"),
3241
- gte(juniorConversationEvents.createdAt, start),
3242
- lte(juniorConversationEvents.createdAt, end)
3243
- )
3244
- ).groupBy(date);
3325
+ };
3326
+ const where = and10(
3327
+ eq10(juniorConversationEvents.type, "guardian_action_reviewed"),
3328
+ gte(juniorConversationEvents.createdAt, start),
3329
+ lte(juniorConversationEvents.createdAt, end)
3330
+ );
3331
+ const [dayRows, hourRows] = await Promise.all([
3332
+ db.select({ ...selectColumns, date: day }).from(juniorConversationEvents).where(where).groupBy(day),
3333
+ db.select({ ...selectColumns, date: hour }).from(juniorConversationEvents).where(where).groupBy(hour)
3334
+ ]);
3335
+ return { dayRows, hourRows };
3245
3336
  }
3246
3337
  async function readConversationStatsFromSql() {
3247
3338
  const nowMs = Date.now();
3248
3339
  const { end, start } = statsWindow(nowMs);
3249
3340
  const db = getDb();
3250
- const [{ actorRows, locationRows, metricRows, totals }, guardianRows] = await Promise.all([
3341
+ const [
3342
+ { actorRows, locationRows, metricHourRows, metricRows, totals },
3343
+ guardian
3344
+ ] = await Promise.all([
3251
3345
  aggregateStats(db, start, end),
3252
3346
  aggregateGuardianStats(db, start, end)
3253
3347
  ]);
@@ -3266,8 +3360,9 @@ async function readConversationStatsFromSql() {
3266
3360
  durationMs: totals?.durationMs ?? 0,
3267
3361
  failed: totals?.failed ?? 0,
3268
3362
  generatedAt: new Date(nowMs).toISOString(),
3269
- guardian: guardianStats(guardianRows, nowMs),
3363
+ guardian: guardianStats(guardian.dayRows, guardian.hourRows, nowMs),
3270
3364
  metricDays: metricDays(metricRows, nowMs),
3365
+ metricHours: metricHours(metricHourRows, nowMs),
3271
3366
  locations: statsItems(locations),
3272
3367
  actors: statsItems(actors),
3273
3368
  source: "conversation_index",
@@ -3503,7 +3598,7 @@ import { Hono as Hono2 } from "hono";
3503
3598
  // src/api/code/overview.ts
3504
3599
  import { desc as desc4, eq as eq11, gte as gte2, or as or5, sql as sql11 } from "drizzle-orm";
3505
3600
  import { z as z5 } from "zod";
3506
- var DAY_MS = 24 * 60 * 60 * 1e3;
3601
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
3507
3602
  var WINDOW_DAYS2 = 30;
3508
3603
  var ACTIVITY_DAYS = 90;
3509
3604
  var summaryRowSchema = z5.object({
@@ -3537,7 +3632,7 @@ var repositoryRowSchema = z5.object({
3537
3632
  var activityDaySchema = z5.object({
3538
3633
  closed: z5.number().int().nonnegative(),
3539
3634
  created: z5.number().int().nonnegative(),
3540
- date: z5.string().date(),
3635
+ date: z5.string().regex(/^\d{4}-\d{2}-\d{2}(T\d{2})?$/),
3541
3636
  merged: z5.number().int().nonnegative()
3542
3637
  }).strict();
3543
3638
  function queryRows(result) {
@@ -3602,12 +3697,17 @@ function ownershipFilter(userId) {
3602
3697
  async function readCodeWindows(args) {
3603
3698
  const db = getDb();
3604
3699
  const windowEnd = new Date(args.nowMs);
3605
- const windowStart = new Date(args.nowMs - WINDOW_DAYS2 * DAY_MS);
3606
- const activityStart = startOfUtcDay(args.nowMs - (ACTIVITY_DAYS - 1) * DAY_MS);
3700
+ const windowStart = new Date(args.nowMs - WINDOW_DAYS2 * DAY_MS2);
3701
+ const activityStart = startOfUtcDay(args.nowMs - (ACTIVITY_DAYS - 1) * DAY_MS2);
3607
3702
  const changes = juniorCodeChanges;
3608
3703
  const ownership = ownershipFilter(args.userId);
3609
3704
  const conversationTreeCost = conversationTreeCostExpr();
3610
- const [summaryResult, activityResult] = await Promise.all([
3705
+ const activityHourEnd = new Date(args.nowMs);
3706
+ activityHourEnd.setUTCMinutes(0, 0, 0);
3707
+ const activityHourStart = new Date(
3708
+ activityHourEnd.getTime() - 23 * (DAY_MS2 / 24)
3709
+ );
3710
+ const [summaryResult, activityResult, activityHourResult] = await Promise.all([
3611
3711
  db.execute(sql11`
3612
3712
  WITH recent_changes AS (
3613
3713
  SELECT
@@ -3707,6 +3807,47 @@ async function readCodeWindows(args) {
3707
3807
  FROM days
3708
3808
  LEFT JOIN daily ON daily.day = days.day
3709
3809
  ORDER BY days.day
3810
+ `),
3811
+ db.execute(sql11`
3812
+ WITH hours AS (
3813
+ SELECT generate_series(
3814
+ date_trunc('hour', ${activityHourStart}::timestamptz AT TIME ZONE 'UTC'),
3815
+ date_trunc('hour', ${activityHourEnd}::timestamptz AT TIME ZONE 'UTC'),
3816
+ interval '1 hour'
3817
+ ) AS hour
3818
+ ), hourly AS (
3819
+ SELECT
3820
+ date_trunc('hour', event_at AT TIME ZONE 'UTC') AS hour,
3821
+ count(*) FILTER (WHERE kind = 'created')::integer AS created,
3822
+ count(*) FILTER (WHERE kind = 'merged')::integer AS merged,
3823
+ count(*) FILTER (WHERE kind = 'closed')::integer AS closed
3824
+ FROM (
3825
+ SELECT ${changes.openedAt} AS event_at, 'created'::text AS kind
3826
+ FROM ${changes}
3827
+ WHERE ${changes.openedAt} >= ${activityHourStart}
3828
+ ${ownership ? sql11`AND ${ownership}` : sql11``}
3829
+ UNION ALL
3830
+ SELECT ${changes.mergedAt} AS event_at, 'merged'::text AS kind
3831
+ FROM ${changes}
3832
+ WHERE ${changes.mergedAt} >= ${activityHourStart}
3833
+ ${ownership ? sql11`AND ${ownership}` : sql11``}
3834
+ UNION ALL
3835
+ SELECT ${changes.closedAt} AS event_at, 'closed'::text AS kind
3836
+ FROM ${changes}
3837
+ WHERE ${changes.state} = 'closed'
3838
+ AND ${changes.closedAt} >= ${activityHourStart}
3839
+ ${ownership ? sql11`AND ${ownership}` : sql11``}
3840
+ ) AS events
3841
+ GROUP BY date_trunc('hour', event_at AT TIME ZONE 'UTC')
3842
+ )
3843
+ SELECT
3844
+ to_char(hours.hour, 'YYYY-MM-DD"T"HH24') AS "date",
3845
+ coalesce(hourly.created, 0)::integer AS "created",
3846
+ coalesce(hourly.merged, 0)::integer AS "merged",
3847
+ coalesce(hourly.closed, 0)::integer AS "closed"
3848
+ FROM hours
3849
+ LEFT JOIN hourly ON hourly.hour = hours.hour
3850
+ ORDER BY hours.hour
3710
3851
  `)
3711
3852
  ]);
3712
3853
  const summary = summaryRowSchema.parse(
@@ -3722,6 +3863,7 @@ async function readCodeWindows(args) {
3722
3863
  );
3723
3864
  return {
3724
3865
  activityDays: z5.array(activityDaySchema).parse(queryRows(activityResult)),
3866
+ activityHours: z5.array(activityDaySchema).parse(queryRows(activityHourResult)),
3725
3867
  summary: {
3726
3868
  ...summary,
3727
3869
  mergeRate: mergeRate(summary.merged, summary.closed)
@@ -3818,6 +3960,7 @@ async function readCodeOverview(nowMs = Date.now()) {
3818
3960
  ]);
3819
3961
  return codeOverviewReportSchema.parse({
3820
3962
  activityDays: windows.activityDays,
3963
+ activityHours: windows.activityHours,
3821
3964
  changes: recentChanges.map((change) => ({
3822
3965
  ...change,
3823
3966
  closedAt: change.closedAt?.toISOString(),
@@ -3845,6 +3988,7 @@ async function readPersonCodeOverview(args) {
3845
3988
  });
3846
3989
  return codePersonReportSchema.parse({
3847
3990
  activityDays: windows.activityDays,
3991
+ activityHours: windows.activityHours,
3848
3992
  generatedAt: windows.windowEnd.toISOString(),
3849
3993
  summary: windows.summary,
3850
3994
  windowEnd: windows.windowEnd.toISOString(),
@@ -4037,28 +4181,49 @@ async function directoryRows(db) {
4037
4181
  eq12(juniorDestinations.id, juniorConversations.destinationId)
4038
4182
  ).where(topLevelWhere()).groupBy(...locationGroupBy());
4039
4183
  }
4040
- async function directoryActivityRows(db, start) {
4041
- const date = sql12`TO_CHAR(
4184
+ async function directoryActivityRows(db, start, hourStart) {
4185
+ const day = sql12`TO_CHAR(
4042
4186
  ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4043
4187
  'YYYY-MM-DD'
4044
4188
  )`;
4045
- return db.select({
4189
+ const hour = sql12`TO_CHAR(
4190
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4191
+ 'YYYY-MM-DD"T"HH24'
4192
+ )`;
4193
+ const select = {
4046
4194
  conversations: sql12`COUNT(*)::integer`,
4047
- date,
4048
4195
  visibility: juniorDestinations.visibility
4049
- }).from(juniorConversations).leftJoin(
4050
- juniorDestinations,
4051
- eq12(juniorDestinations.id, juniorConversations.destinationId)
4052
- ).where(and11(topLevelWhere(), gte3(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
4196
+ };
4197
+ const [dayRows, hourRows] = await Promise.all([
4198
+ db.select({ ...select, date: day }).from(juniorConversations).leftJoin(
4199
+ juniorDestinations,
4200
+ eq12(juniorDestinations.id, juniorConversations.destinationId)
4201
+ ).where(
4202
+ and11(topLevelWhere(), gte3(juniorConversations.lastActivityAt, start))
4203
+ ).groupBy(day, juniorDestinations.visibility),
4204
+ db.select({ ...select, date: hour }).from(juniorConversations).leftJoin(
4205
+ juniorDestinations,
4206
+ eq12(juniorDestinations.id, juniorConversations.destinationId)
4207
+ ).where(
4208
+ and11(
4209
+ topLevelWhere(),
4210
+ gte3(juniorConversations.lastActivityAt, hourStart)
4211
+ )
4212
+ ).groupBy(hour, juniorDestinations.visibility)
4213
+ ]);
4214
+ return { dayRows, hourRows };
4053
4215
  }
4054
- function directoryActivityDays(rows, nowMs) {
4216
+ function emptyLocationActivityDay(date) {
4217
+ return {
4218
+ date,
4219
+ privateConversations: 0,
4220
+ publicConversations: 0
4221
+ };
4222
+ }
4223
+ function accumulateLocationActivity(rows) {
4055
4224
  const days = /* @__PURE__ */ new Map();
4056
4225
  for (const row of rows) {
4057
- const day = days.get(row.date) ?? {
4058
- date: row.date,
4059
- privateConversations: 0,
4060
- publicConversations: 0
4061
- };
4226
+ const day = days.get(row.date) ?? emptyLocationActivityDay(row.date);
4062
4227
  if (row.visibility === "public") {
4063
4228
  day.publicConversations += row.conversations;
4064
4229
  } else {
@@ -4066,32 +4231,30 @@ function directoryActivityDays(rows, nowMs) {
4066
4231
  }
4067
4232
  days.set(row.date, day);
4068
4233
  }
4069
- const end = new Date(nowMs);
4070
- end.setUTCHours(0, 0, 0, 0);
4071
- const start = new Date(end);
4072
- start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
4073
- const activity = [];
4074
- for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
4075
- const date = cursor.toISOString().slice(0, 10);
4076
- activity.push(
4077
- days.get(date) ?? {
4078
- date,
4079
- privateConversations: 0,
4080
- publicConversations: 0
4081
- }
4082
- );
4083
- }
4084
- return activity;
4234
+ return days;
4235
+ }
4236
+ function directoryActivityDays(rows, nowMs) {
4237
+ return fillUtcDays({
4238
+ count: ACTIVITY_DAYS2,
4239
+ empty: emptyLocationActivityDay,
4240
+ nowMs,
4241
+ rows: accumulateLocationActivity(rows)
4242
+ });
4243
+ }
4244
+ function directoryActivityHours(rows, nowMs) {
4245
+ return fillUtcHours({
4246
+ empty: emptyLocationActivityDay,
4247
+ nowMs,
4248
+ rows: accumulateLocationActivity(rows)
4249
+ });
4085
4250
  }
4086
4251
  async function readLocationDirectoryFromSql() {
4087
4252
  const nowMs = Date.now();
4088
- const end = new Date(nowMs);
4089
- end.setUTCHours(0, 0, 0, 0);
4090
- const start = new Date(end);
4091
- start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
4092
- const [rows, activityRows] = await Promise.all([
4253
+ const { end, start } = trailingUtcDayWindow(nowMs, ACTIVITY_DAYS2);
4254
+ const hourWindow = trailingUtcHourWindow(nowMs);
4255
+ const [rows, activity] = await Promise.all([
4093
4256
  directoryRows(getDb()),
4094
- directoryActivityRows(getDb(), start)
4257
+ directoryActivityRows(getDb(), start, hourWindow.start)
4095
4258
  ]);
4096
4259
  const locations = [];
4097
4260
  const privateActivity = {
@@ -4104,7 +4267,8 @@ async function readLocationDirectoryFromSql() {
4104
4267
  else addMetrics(privateActivity, row);
4105
4268
  }
4106
4269
  return {
4107
- activityDays: directoryActivityDays(activityRows, nowMs),
4270
+ activityDays: directoryActivityDays(activity.dayRows, nowMs),
4271
+ activityHours: directoryActivityHours(activity.hourRows, nowMs),
4108
4272
  generatedAt: new Date(nowMs).toISOString(),
4109
4273
  locations: locations.sort(
4110
4274
  (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.label.localeCompare(right.label)
@@ -4153,16 +4317,18 @@ async function recentLocationRows(db, locationId) {
4153
4317
  }
4154
4318
  async function readLocationDetailFromSql(locationId, options = {}) {
4155
4319
  const nowMs = Date.now();
4156
- const end = new Date(nowMs);
4157
- end.setUTCHours(0, 0, 0, 0);
4158
- const start = new Date(end);
4159
- start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
4320
+ const { end, start } = trailingUtcDayWindow(nowMs, ACTIVITY_DAYS2);
4321
+ const hourWindow = trailingUtcHourWindow(nowMs);
4160
4322
  const where = publicLocationWhere(locationId);
4161
4323
  const activityDate = sql12`TO_CHAR(
4162
4324
  ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4163
4325
  'YYYY-MM-DD'
4164
4326
  )`;
4165
- const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
4327
+ const activityHour = sql12`TO_CHAR(
4328
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4329
+ 'YYYY-MM-DD"T"HH24'
4330
+ )`;
4331
+ const [locationRows, dayRows, hourRows, actorRows, recentRows] = await Promise.all([
4166
4332
  getDb().select({
4167
4333
  ...locationColumns(),
4168
4334
  ...treeAggregateColumns2,
@@ -4190,6 +4356,21 @@ async function readLocationDetailFromSql(locationId, options = {}) {
4190
4356
  juniorDestinations,
4191
4357
  eq12(juniorDestinations.id, juniorConversations.destinationId)
4192
4358
  ).where(and11(where, gte3(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
4359
+ getDb().select({
4360
+ date: activityHour,
4361
+ ...treeAggregateColumns2
4362
+ }).from(juniorConversations).innerJoin(
4363
+ treeConversation2,
4364
+ eq12(
4365
+ treeConversation2.rootConversationId,
4366
+ juniorConversations.conversationId
4367
+ )
4368
+ ).innerJoin(
4369
+ juniorDestinations,
4370
+ eq12(juniorDestinations.id, juniorConversations.destinationId)
4371
+ ).where(
4372
+ and11(where, gte3(juniorConversations.lastActivityAt, hourWindow.start))
4373
+ ).groupBy(activityHour),
4193
4374
  getDb().select({
4194
4375
  actorIdentityId: juniorConversations.actorIdentityId,
4195
4376
  email: juniorUsers.primaryEmailNormalized,
@@ -4236,6 +4417,17 @@ async function readLocationDetailFromSql(locationId, options = {}) {
4236
4417
  ...row.tokens !== null ? { tokens: row.tokens } : void 0
4237
4418
  });
4238
4419
  }
4420
+ const hours = /* @__PURE__ */ new Map();
4421
+ for (const row of hourRows) {
4422
+ hours.set(row.date, {
4423
+ active: row.active,
4424
+ conversations: row.conversations,
4425
+ date: row.date,
4426
+ durationMs: row.durationMs,
4427
+ failed: row.failed,
4428
+ ...row.tokens !== null ? { tokens: row.tokens } : void 0
4429
+ });
4430
+ }
4239
4431
  const actors = [];
4240
4432
  for (const row of actorRows) {
4241
4433
  if (!row.actorIdentityId) continue;
@@ -4251,6 +4443,7 @@ async function readLocationDetailFromSql(locationId, options = {}) {
4251
4443
  actors.push(item);
4252
4444
  }
4253
4445
  const activity = activityDays(days, nowMs, ACTIVITY_DAYS2);
4446
+ const hourlyActivity = activityHours(hours, nowMs);
4254
4447
  const recentConversationIds = recentRows.map((row) => row.conversationId);
4255
4448
  const [accessByConversation, metricsByRoot] = await Promise.all([
4256
4449
  readConversationAccessFromSql(
@@ -4263,6 +4456,7 @@ async function readLocationDetailFromSql(locationId, options = {}) {
4263
4456
  return {
4264
4457
  ...location,
4265
4458
  activityDays: activity,
4459
+ activityHours: hourlyActivity,
4266
4460
  actors: actors.sort(
4267
4461
  (left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
4268
4462
  ),
@@ -4430,16 +4624,19 @@ function emptyActivityDay(date) {
4430
4624
  };
4431
4625
  }
4432
4626
  function activityDays2(days, nowMs) {
4433
- const items = [];
4434
- const end = new Date(nowMs);
4435
- end.setUTCHours(0, 0, 0, 0);
4436
- const start = new Date(end);
4437
- start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS3 - 1));
4438
- for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
4439
- const date = cursor.toISOString().slice(0, 10);
4440
- items.push(days.get(date) ?? emptyActivityDay(date));
4441
- }
4442
- return items;
4627
+ return fillUtcDays({
4628
+ count: ACTIVITY_DAYS3,
4629
+ empty: emptyActivityDay,
4630
+ nowMs,
4631
+ rows: days
4632
+ });
4633
+ }
4634
+ function activityHours2(hours, nowMs) {
4635
+ return fillUtcHours({
4636
+ empty: emptyActivityDay,
4637
+ nowMs,
4638
+ rows: hours
4639
+ });
4443
4640
  }
4444
4641
  function statsItems2(map) {
4445
4642
  return [...map.values()].sort(
@@ -4487,31 +4684,126 @@ async function recentActorRows(email) {
4487
4684
 
4488
4685
  // src/api/people/list.query.ts
4489
4686
  var DIRECTORY_ACTIVITY_DAYS = 90;
4490
- function activityWindow(nowMs) {
4491
- const end = new Date(nowMs);
4492
- end.setUTCHours(0, 0, 0, 0);
4493
- const start = new Date(end);
4494
- start.setUTCDate(start.getUTCDate() - (DIRECTORY_ACTIVITY_DAYS - 1));
4495
- return { end, start };
4687
+ var DAY_METRIC_LOOKBACK = 180;
4688
+ var HOUR_METRIC_LOOKBACK = 48;
4689
+ function emptyDirectoryDay(date) {
4690
+ return { activePeople: 0, conversations: 0, date };
4691
+ }
4692
+ function emptyBucket() {
4693
+ return { conversations: 0, costUsd: 0, durationMs: 0 };
4694
+ }
4695
+ function addUsd2(current, next) {
4696
+ return Math.round((current + next) * 1e12) / 1e12;
4496
4697
  }
4497
4698
  function directoryActivityDays2(rows, nowMs) {
4498
- const days = new Map(rows.map((row) => [row.date, row]));
4499
- const { end, start } = activityWindow(nowMs);
4500
- const items = [];
4501
- for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
4502
- const date = cursor.toISOString().slice(0, 10);
4503
- items.push(days.get(date) ?? { activePeople: 0, conversations: 0, date });
4699
+ return fillUtcDays({
4700
+ count: DIRECTORY_ACTIVITY_DAYS,
4701
+ empty: emptyDirectoryDay,
4702
+ nowMs,
4703
+ rows: new Map(rows.map((row) => [row.date, row]))
4704
+ });
4705
+ }
4706
+ function directoryActivityHours2(rows, nowMs) {
4707
+ return fillUtcHours({
4708
+ empty: emptyDirectoryDay,
4709
+ nowMs,
4710
+ rows: new Map(rows.map((row) => [row.date, row]))
4711
+ });
4712
+ }
4713
+ function sumBuckets(buckets, startMs, endMs, keyFor, stepMs) {
4714
+ const total = emptyBucket();
4715
+ for (let cursor = startMs; cursor <= endMs; cursor += stepMs) {
4716
+ const bucket = buckets.get(keyFor(cursor));
4717
+ if (!bucket) continue;
4718
+ total.conversations += bucket.conversations;
4719
+ total.costUsd = addUsd2(total.costUsd, bucket.costUsd);
4720
+ total.durationMs += bucket.durationMs;
4721
+ }
4722
+ return total;
4723
+ }
4724
+ function rangeWindow(buckets, nowMs, range) {
4725
+ if (range === 1) {
4726
+ const current2 = trailingUtcHourWindow(nowMs, 24);
4727
+ const priorEndMs2 = current2.start.getTime() - HOUR_MS;
4728
+ const priorStartMs2 = priorEndMs2 - 23 * HOUR_MS;
4729
+ const currentTotals2 = sumBuckets(
4730
+ buckets,
4731
+ current2.start.getTime(),
4732
+ current2.end.getTime(),
4733
+ utcHourKey,
4734
+ HOUR_MS
4735
+ );
4736
+ const priorTotals2 = sumBuckets(
4737
+ buckets,
4738
+ priorStartMs2,
4739
+ priorEndMs2,
4740
+ utcHourKey,
4741
+ HOUR_MS
4742
+ );
4743
+ return {
4744
+ conversations: currentTotals2.conversations,
4745
+ costUsd: currentTotals2.costUsd,
4746
+ durationMs: currentTotals2.durationMs,
4747
+ priorCostUsd: priorTotals2.costUsd
4748
+ };
4504
4749
  }
4505
- return items;
4750
+ const current = trailingUtcDayWindow(nowMs, range);
4751
+ const priorEndMs = current.start.getTime() - DAY_MS;
4752
+ const priorStartMs = priorEndMs - (range - 1) * DAY_MS;
4753
+ const currentTotals = sumBuckets(
4754
+ buckets,
4755
+ current.start.getTime(),
4756
+ current.end.getTime(),
4757
+ utcDayKey,
4758
+ DAY_MS
4759
+ );
4760
+ const priorTotals = sumBuckets(
4761
+ buckets,
4762
+ priorStartMs,
4763
+ priorEndMs,
4764
+ utcDayKey,
4765
+ DAY_MS
4766
+ );
4767
+ return {
4768
+ conversations: currentTotals.conversations,
4769
+ costUsd: currentTotals.costUsd,
4770
+ durationMs: currentTotals.durationMs,
4771
+ priorCostUsd: priorTotals.costUsd
4772
+ };
4773
+ }
4774
+ function buildActorWindows(args) {
4775
+ return {
4776
+ 1: rangeWindow(args.hourBuckets, args.nowMs, 1),
4777
+ 7: rangeWindow(args.dayBuckets, args.nowMs, 7),
4778
+ 30: rangeWindow(args.dayBuckets, args.nowMs, 30),
4779
+ 90: rangeWindow(args.dayBuckets, args.nowMs, 90)
4780
+ };
4506
4781
  }
4507
4782
  async function readPeopleListFromSql() {
4508
4783
  const nowMs = Date.now();
4509
- const { end, start } = activityWindow(nowMs);
4784
+ const { end, start } = trailingUtcDayWindow(nowMs, DIRECTORY_ACTIVITY_DAYS);
4510
4785
  const activityDate = sql14`TO_CHAR(
4511
4786
  ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4512
4787
  'YYYY-MM-DD'
4513
4788
  )`;
4514
- const [rows, activityRows] = await Promise.all([
4789
+ const activityHour = sql14`TO_CHAR(
4790
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4791
+ 'YYYY-MM-DD"T"HH24'
4792
+ )`;
4793
+ const hourWindow = trailingUtcHourWindow(nowMs);
4794
+ const dayMetricStart = trailingUtcDayWindow(nowMs, DAY_METRIC_LOOKBACK).start;
4795
+ const hourMetricStart = trailingUtcHourWindow(
4796
+ nowMs,
4797
+ HOUR_METRIC_LOOKBACK
4798
+ ).start;
4799
+ const metricsJoin = peopleTreeMetricsJoin();
4800
+ const [
4801
+ rows,
4802
+ activityRows,
4803
+ activityHourRows,
4804
+ dayMetricRows,
4805
+ hourMetricRows
4806
+ ] = await Promise.all([
4515
4807
  getDb().select({
4516
4808
  email: juniorUsers.primaryEmailNormalized,
4517
4809
  fullName: juniorUsers.displayName,
@@ -4523,7 +4815,7 @@ async function readPeopleListFromSql() {
4523
4815
  }).from(juniorConversations).innerJoin(
4524
4816
  juniorIdentities,
4525
4817
  eq14(juniorIdentities.id, juniorConversations.actorIdentityId)
4526
- ).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, peopleTreeMetricsJoin()).where(verifiedActorWhere()).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
4818
+ ).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(verifiedActorWhere()).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
4527
4819
  getDb().select({
4528
4820
  activePeople: sql14`COUNT(DISTINCT ${juniorUsers.id})::int`,
4529
4821
  conversations: sql14`COUNT(*)::int`,
@@ -4536,8 +4828,77 @@ async function readPeopleListFromSql() {
4536
4828
  verifiedActorWhere(),
4537
4829
  gte4(juniorConversations.lastActivityAt, start)
4538
4830
  )
4539
- ).groupBy(activityDate)
4831
+ ).groupBy(activityDate),
4832
+ getDb().select({
4833
+ activePeople: sql14`COUNT(DISTINCT ${juniorUsers.id})::int`,
4834
+ conversations: sql14`COUNT(*)::int`,
4835
+ date: activityHour
4836
+ }).from(juniorConversations).innerJoin(
4837
+ juniorIdentities,
4838
+ eq14(juniorIdentities.id, juniorConversations.actorIdentityId)
4839
+ ).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).where(
4840
+ and13(
4841
+ verifiedActorWhere(),
4842
+ gte4(juniorConversations.lastActivityAt, hourWindow.start)
4843
+ )
4844
+ ).groupBy(activityHour),
4845
+ getDb().select({
4846
+ email: juniorUsers.primaryEmailNormalized,
4847
+ date: activityDate,
4848
+ conversations: peopleTreeAggregateColumns.conversations,
4849
+ costUsd: peopleTreeAggregateColumns.costUsd,
4850
+ durationMs: peopleTreeAggregateColumns.durationMs
4851
+ }).from(juniorConversations).innerJoin(
4852
+ juniorIdentities,
4853
+ eq14(juniorIdentities.id, juniorConversations.actorIdentityId)
4854
+ ).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(
4855
+ and13(
4856
+ verifiedActorWhere(),
4857
+ gte4(juniorConversations.lastActivityAt, dayMetricStart)
4858
+ )
4859
+ ).groupBy(
4860
+ juniorUsers.primaryEmailNormalized,
4861
+ activityDate
4862
+ ),
4863
+ getDb().select({
4864
+ email: juniorUsers.primaryEmailNormalized,
4865
+ date: activityHour,
4866
+ conversations: peopleTreeAggregateColumns.conversations,
4867
+ costUsd: peopleTreeAggregateColumns.costUsd,
4868
+ durationMs: peopleTreeAggregateColumns.durationMs
4869
+ }).from(juniorConversations).innerJoin(
4870
+ juniorIdentities,
4871
+ eq14(juniorIdentities.id, juniorConversations.actorIdentityId)
4872
+ ).innerJoin(juniorUsers, eq14(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(
4873
+ and13(
4874
+ verifiedActorWhere(),
4875
+ gte4(juniorConversations.lastActivityAt, hourMetricStart)
4876
+ )
4877
+ ).groupBy(
4878
+ juniorUsers.primaryEmailNormalized,
4879
+ activityHour
4880
+ )
4540
4881
  ]);
4882
+ const dayBucketsByEmail = /* @__PURE__ */ new Map();
4883
+ for (const row of dayMetricRows) {
4884
+ const buckets = dayBucketsByEmail.get(row.email) ?? /* @__PURE__ */ new Map();
4885
+ buckets.set(row.date, {
4886
+ conversations: row.conversations,
4887
+ costUsd: row.costUsd ?? 0,
4888
+ durationMs: row.durationMs
4889
+ });
4890
+ dayBucketsByEmail.set(row.email, buckets);
4891
+ }
4892
+ const hourBucketsByEmail = /* @__PURE__ */ new Map();
4893
+ for (const row of hourMetricRows) {
4894
+ const buckets = hourBucketsByEmail.get(row.email) ?? /* @__PURE__ */ new Map();
4895
+ buckets.set(row.date, {
4896
+ conversations: row.conversations,
4897
+ costUsd: row.costUsd ?? 0,
4898
+ durationMs: row.durationMs
4899
+ });
4900
+ hourBucketsByEmail.set(row.email, buckets);
4901
+ }
4541
4902
  const people = rows.map((row) => {
4542
4903
  const actor = {
4543
4904
  email: row.email,
@@ -4554,11 +4915,17 @@ async function readPeopleListFromSql() {
4554
4915
  firstSeenAt: row.firstSeenAt.toISOString(),
4555
4916
  lastSeenAt: row.lastSeenAt.toISOString(),
4556
4917
  actor,
4918
+ windows: buildActorWindows({
4919
+ dayBuckets: dayBucketsByEmail.get(row.email) ?? /* @__PURE__ */ new Map(),
4920
+ hourBuckets: hourBucketsByEmail.get(row.email) ?? /* @__PURE__ */ new Map(),
4921
+ nowMs
4922
+ }),
4557
4923
  ...row.tokens !== null ? { tokens: row.tokens } : void 0
4558
4924
  };
4559
4925
  });
4560
4926
  return {
4561
4927
  activityDays: directoryActivityDays2(activityRows, nowMs),
4928
+ activityHours: directoryActivityHours2(activityHourRows, nowMs),
4562
4929
  generatedAt: new Date(nowMs).toISOString(),
4563
4930
  people: people.sort(
4564
4931
  (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.actor.email.localeCompare(right.actor.email)
@@ -4585,7 +4952,7 @@ async function readPeoplePluginReports(args) {
4585
4952
  source: "plugins"
4586
4953
  });
4587
4954
  }
4588
- const { getPluginProfileReports } = await import("./agent-hooks-HIWWBBKO.js");
4955
+ const { getPluginProfileReports } = await import("./agent-hooks-DWY3MZZ7.js");
4589
4956
  return pluginOperationalReportFeedSchema.parse({
4590
4957
  generatedAt: new Date(nowMs).toISOString(),
4591
4958
  reports: await getPluginProfileReports({
@@ -4606,6 +4973,7 @@ function emptyProfile(email, nowMs) {
4606
4973
  start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS3 - 1));
4607
4974
  return {
4608
4975
  activityDays: activityDays2(/* @__PURE__ */ new Map(), nowMs),
4976
+ activityHours: activityHours2(/* @__PURE__ */ new Map(), nowMs),
4609
4977
  generatedAt: new Date(nowMs).toISOString(),
4610
4978
  locations: [],
4611
4979
  recentConversations: [],
@@ -4666,8 +5034,13 @@ async function readPeopleProfileFromSql(email, options = {}) {
4666
5034
  ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
4667
5035
  'YYYY-MM-DD'
4668
5036
  )`;
5037
+ const activityHour = sql15`TO_CHAR(
5038
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
5039
+ 'YYYY-MM-DD"T"HH24'
5040
+ )`;
4669
5041
  const channel = sql15`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
4670
- const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
5042
+ const hourWindow = trailingUtcHourWindow(nowMs);
5043
+ const [totalsRows, dayRows, hourRows, locationRows, surfaceRows, recentRows] = await Promise.all([
4671
5044
  getDb().select({
4672
5045
  email: juniorUsers.primaryEmailNormalized,
4673
5046
  fullName: juniorUsers.displayName,
@@ -4686,6 +5059,15 @@ async function readPeopleProfileFromSql(email, options = {}) {
4686
5059
  juniorIdentities,
4687
5060
  eq15(juniorIdentities.id, juniorConversations.actorIdentityId)
4688
5061
  ).innerJoin(juniorUsers, eq15(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(and14(where, gte5(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
5062
+ getDb().select({
5063
+ date: activityHour,
5064
+ ...peopleTreeAggregateColumns
5065
+ }).from(juniorConversations).innerJoin(
5066
+ juniorIdentities,
5067
+ eq15(juniorIdentities.id, juniorConversations.actorIdentityId)
5068
+ ).innerJoin(juniorUsers, eq15(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(
5069
+ and14(where, gte5(juniorConversations.lastActivityAt, hourWindow.start))
5070
+ ).groupBy(activityHour),
4689
5071
  getDb().select({
4690
5072
  channel,
4691
5073
  channelName: juniorConversations.channelName,
@@ -4730,6 +5112,18 @@ async function readPeopleProfileFromSql(email, options = {}) {
4730
5112
  ...row.tokens !== null ? { tokens: row.tokens } : void 0
4731
5113
  });
4732
5114
  }
5115
+ const hours = /* @__PURE__ */ new Map();
5116
+ for (const row of hourRows) {
5117
+ hours.set(row.date, {
5118
+ active: row.active,
5119
+ conversations: row.conversations,
5120
+ ...row.costUsd !== null ? { costUsd: row.costUsd } : void 0,
5121
+ date: row.date,
5122
+ durationMs: row.durationMs,
5123
+ failed: row.failed,
5124
+ ...row.tokens !== null ? { tokens: row.tokens } : void 0
5125
+ });
5126
+ }
4733
5127
  const locations = /* @__PURE__ */ new Map();
4734
5128
  for (const row of locationRows) {
4735
5129
  addAggregate2(locations, locationLabel2(row), row);
@@ -4753,6 +5147,7 @@ async function readPeopleProfileFromSql(email, options = {}) {
4753
5147
  ]);
4754
5148
  return {
4755
5149
  activityDays: activityDays2(days, nowMs),
5150
+ activityHours: activityHours2(hours, nowMs),
4756
5151
  generatedAt: new Date(nowMs).toISOString(),
4757
5152
  locations: statsItems2(locations),
4758
5153
  recentConversations: recentRows.map(
@@ -4788,7 +5183,7 @@ async function readPeopleProfile(email, options = {}) {
4788
5183
  import { and as and15, eq as eq16, gte as gte6, lte as lte2, sql as sql16 } from "drizzle-orm";
4789
5184
  var SEVEN_DAY_OFFSET = 6;
4790
5185
  var THIRTY_DAY_OFFSET = 29;
4791
- function addUsd2(current, next) {
5186
+ function addUsd3(current, next) {
4792
5187
  return Math.round((current + next) * 1e12) / 1e12;
4793
5188
  }
4794
5189
  function spendWindow(nowMs) {
@@ -4827,9 +5222,9 @@ async function readPersonalSpendFromSql(email, nowMs = Date.now()) {
4827
5222
  let thirtyDaysUsd = 0;
4828
5223
  for (const row of rows) {
4829
5224
  if (row.costUsd === null) continue;
4830
- thirtyDaysUsd = addUsd2(thirtyDaysUsd, row.costUsd);
5225
+ thirtyDaysUsd = addUsd3(thirtyDaysUsd, row.costUsd);
4831
5226
  if (row.date >= sevenDayStartDate) {
4832
- sevenDaysUsd = addUsd2(sevenDaysUsd, row.costUsd);
5227
+ sevenDaysUsd = addUsd3(sevenDaysUsd, row.costUsd);
4833
5228
  }
4834
5229
  }
4835
5230
  return {
@@ -5389,7 +5784,7 @@ function readDescriptionText(home) {
5389
5784
  }
5390
5785
  }
5391
5786
  async function readSkillReports() {
5392
- const { discoverSkills } = await import("./skills-JTRDFKMA.js");
5787
+ const { discoverSkills } = await import("./skills-AR3WD6BX.js");
5393
5788
  const skills = await discoverSkills();
5394
5789
  return skillReportsSchema.parse(
5395
5790
  skills.map((skill) => ({
@@ -5399,7 +5794,7 @@ async function readSkillReports() {
5399
5794
  );
5400
5795
  }
5401
5796
  async function readPlugins() {
5402
- const { pluginCatalogRuntime } = await import("./catalog-runtime-4OKH4265.js");
5797
+ const { pluginCatalogRuntime } = await import("./catalog-runtime-NAD44W5K.js");
5403
5798
  return pluginsSchema.parse(
5404
5799
  pluginCatalogRuntime.getProviders().map((plugin) => ({
5405
5800
  configKeys: plugin.manifest.configKeys ?? [],
@@ -5412,7 +5807,7 @@ async function readPlugins() {
5412
5807
  async function readRuntimeInfoReport() {
5413
5808
  const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
5414
5809
  import("./discovery-DWVDM7UK.js"),
5415
- import("./catalog-runtime-4OKH4265.js"),
5810
+ import("./catalog-runtime-NAD44W5K.js"),
5416
5811
  readPlugins(),
5417
5812
  readSkillReports()
5418
5813
  ]);
@@ -5428,7 +5823,7 @@ async function readRuntimeInfoReport() {
5428
5823
  }
5429
5824
  async function readPluginOperationalReportFeed() {
5430
5825
  const nowMs = Date.now();
5431
- const { getPluginOperationalReports } = await import("./agent-hooks-HIWWBBKO.js");
5826
+ const { getPluginOperationalReports } = await import("./agent-hooks-DWY3MZZ7.js");
5432
5827
  return pluginOperationalReportFeedSchema.parse({
5433
5828
  source: "plugins",
5434
5829
  generatedAt: new Date(nowMs).toISOString(),