@sentry/junior 0.103.0 → 0.104.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 (77) hide show
  1. package/dist/{agent-hooks-NHIXBJW3.js → agent-hooks-EOFSY2PD.js} +3 -3
  2. package/dist/api/conversations/archive.d.ts +7 -0
  3. package/dist/api/conversations/detail.d.ts +7 -0
  4. package/dist/api/conversations/list.d.ts +109 -0
  5. package/dist/api/conversations/routes.d.ts +3 -0
  6. package/dist/api/conversations/schema.d.ts +72 -69
  7. package/dist/api/conversations/stats.d.ts +7 -0
  8. package/dist/api/conversations/subagent.d.ts +7 -0
  9. package/dist/api/http.d.ts +5 -0
  10. package/dist/api/locations/detail.d.ts +10 -2
  11. package/dist/api/locations/list.d.ts +7 -0
  12. package/dist/api/locations/routes.d.ts +3 -0
  13. package/dist/api/locations/schema.d.ts +4 -3
  14. package/dist/api/people/list.d.ts +7 -0
  15. package/dist/api/people/profile.d.ts +7 -0
  16. package/dist/api/people/routes.d.ts +3 -0
  17. package/dist/api/people/schema.d.ts +35 -33
  18. package/dist/api/people/shared.d.ts +3 -3
  19. package/dist/api/route.d.ts +7 -0
  20. package/dist/api/schema.js +11 -17
  21. package/dist/api.js +2184 -88
  22. package/dist/app.js +107 -101
  23. package/dist/chat/conversations/history.d.ts +6 -6
  24. package/dist/chat/conversations/store.d.ts +1 -0
  25. package/dist/chat/pi/messages.d.ts +2 -2
  26. package/dist/chat/plugins/agent-hooks.d.ts +2 -2
  27. package/dist/chat/resource-events/store.d.ts +2 -2
  28. package/dist/chat/slack/tools/conversation-search.d.ts +2 -2
  29. package/dist/chat/state/session-log.d.ts +2 -2
  30. package/dist/chat/task-execution/store.d.ts +2 -2
  31. package/dist/chat/tools/search-tools.d.ts +2 -2
  32. package/dist/chunk-AIRE7Q2W.js +362 -0
  33. package/dist/{chunk-HOBDQQJY.js → chunk-BCULHGZK.js} +23 -3
  34. package/dist/{chunk-DEQZ23O3.js → chunk-O7ILGCMJ.js} +77 -0
  35. package/dist/{chunk-C4BT2P2K.js → chunk-QDKRE3LS.js} +339 -8
  36. package/dist/{chunk-GY5ZRYPH.js → chunk-QVTVCJR4.js} +5 -5
  37. package/dist/{chunk-JXQWOCBE.js → chunk-RMZ65YAA.js} +2 -2
  38. package/dist/{chunk-E2VS47AZ.js → chunk-RYDF4GOX.js} +2 -2
  39. package/dist/{chunk-OE453VRG.js → chunk-TMWUGGSA.js} +26 -28
  40. package/dist/{chunk-JPXLAJUU.js → chunk-VDHMFCDY.js} +9 -10
  41. package/dist/cli/chat.js +10 -11
  42. package/dist/cli/plugins.js +4 -4
  43. package/dist/cli/upgrade.js +3 -4
  44. package/dist/db/schema/conversations.d.ts +17 -0
  45. package/dist/db/schema.d.ts +17 -0
  46. package/dist/{db-I343BSOM.js → db-PDMC67T3.js} +1 -1
  47. package/dist/{legacy-import-M7VBOQTK.js → legacy-import-G333RHJZ.js} +2 -2
  48. package/dist/reporting-schema.d.ts +4 -4
  49. package/dist/{runner-IEE5G4NV.js → runner-6A7MQRUE.js} +7 -8
  50. package/migrations/0004_useful_magus.sql +1 -0
  51. package/migrations/meta/0004_snapshot.json +1041 -0
  52. package/migrations/meta/_journal.json +7 -0
  53. package/package.json +6 -6
  54. package/dist/api/conversations/list.query.d.ts +0 -103
  55. package/dist/chunk-2RNOB32R.js +0 -17
  56. package/dist/chunk-6FPTHPXF.js +0 -77
  57. package/dist/chunk-6LBNXC4F.js +0 -66
  58. package/dist/chunk-7LLRS2OK.js +0 -215
  59. package/dist/chunk-B43K4C5K.js +0 -102
  60. package/dist/chunk-BQCPAIAB.js +0 -319
  61. package/dist/chunk-FW7DVHTL.js +0 -673
  62. package/dist/chunk-GCGRQJR5.js +0 -84
  63. package/dist/chunk-HRDWLE7Q.js +0 -273
  64. package/dist/chunk-LVUKF5CE.js +0 -63
  65. package/dist/chunk-QNVHH5PU.js +0 -20
  66. package/dist/chunk-QOZOJNSE.js +0 -96
  67. package/dist/chunk-VKFOVMZL.js +0 -331
  68. package/dist/detail-ENVV4HIY.js +0 -68
  69. package/dist/detail-UIOKVIET.js +0 -29
  70. package/dist/list-WR5U6JVM.js +0 -30
  71. package/dist/list-XZ4HKQ4R.js +0 -121
  72. package/dist/list-ZOUSMSEH.js +0 -30
  73. package/dist/plugin-conversations-DTUP4RH6.js +0 -37
  74. package/dist/profile-UO2N3XXG.js +0 -212
  75. package/dist/reporting/plugin-conversations.d.ts +0 -3
  76. package/dist/stats-NZIET4LT.js +0 -174
  77. package/dist/subagent-DY6XKTJ5.js +0 -49
package/dist/api.js CHANGED
@@ -1,29 +1,2200 @@
1
1
  import {
2
+ activityDays,
3
+ actorDirectoryReportSchema,
4
+ actorProfileReportSchema,
5
+ conversationDetailReportSchema,
2
6
  conversationFeedQuerySchema,
7
+ conversationFeedSchema,
3
8
  conversationParamsSchema,
9
+ conversationStatsReportSchema,
10
+ conversationSubagentTranscriptReportSchema,
11
+ locationDetailReportSchema,
12
+ locationDirectoryReportSchema,
4
13
  locationParamsSchema,
5
14
  personParamsSchema,
6
15
  subagentParamsSchema
7
- } from "./chunk-2RNOB32R.js";
8
- import "./chunk-GCGRQJR5.js";
9
- import "./chunk-6LBNXC4F.js";
10
- import "./chunk-7LLRS2OK.js";
16
+ } from "./chunk-AIRE7Q2W.js";
11
17
  import {
12
- readHealthReport
13
- } from "./chunk-QNVHH5PU.js";
18
+ buildSentryConversationUrl,
19
+ buildSentryTraceUrl,
20
+ formatSlackConversationRedactedLabel,
21
+ readHealthReport,
22
+ resolveSlackConversationContextFromThreadId
23
+ } from "./chunk-O7ILGCMJ.js";
14
24
  import {
15
25
  pluginOperationalReportFeedSchema,
16
26
  pluginReportsSchema,
17
27
  runtimeInfoReportSchema,
18
28
  skillReportsSchema
19
29
  } from "./chunk-CEA3A3UA.js";
30
+ import {
31
+ listBoundedAgentTurnSessionSummariesForConversation,
32
+ loadProjection,
33
+ projectSteps
34
+ } from "./chunk-QDKRE3LS.js";
35
+ import "./chunk-RYDF4GOX.js";
20
36
  import "./chunk-PDO5BLNM.js";
21
- import "./chunk-FXFQNHRU.js";
37
+ import "./chunk-CCTCIISK.js";
38
+ import {
39
+ getAgentStepStore,
40
+ getConversationMessageStore,
41
+ getDb,
42
+ juniorConversations,
43
+ juniorIdentities,
44
+ juniorUsers
45
+ } from "./chunk-BCULHGZK.js";
46
+ import "./chunk-4ZNGQH7C.js";
47
+ import {
48
+ stripRuntimeTurnContext,
49
+ unescapeXml,
50
+ unwrapCurrentInstruction
51
+ } from "./chunk-M3UAVLEN.js";
52
+ import {
53
+ juniorDestinations
54
+ } from "./chunk-FXFQNHRU.js";
55
+ import {
56
+ canExposeConversationPayload,
57
+ parseSlackThreadId,
58
+ resolveConversationPrivacy
59
+ } from "./chunk-RT5BUHR5.js";
60
+ import "./chunk-B7PUBQQM.js";
61
+ import {
62
+ logException
63
+ } from "./chunk-AEWZ7F65.js";
64
+ import {
65
+ isRecord
66
+ } from "./chunk-OB42YVAE.js";
67
+ import "./chunk-6GWA276C.js";
68
+ import "./chunk-7FBGKXPE.js";
22
69
  import "./chunk-MLKGABMK.js";
23
70
 
24
71
  // src/api.ts
72
+ import { Hono as Hono4 } from "hono";
73
+
74
+ // src/api/conversations/routes.ts
25
75
  import { Hono } from "hono";
76
+
77
+ // src/api/conversations/archive.ts
78
+ import { and, eq, isNotNull } from "drizzle-orm";
79
+ import { HTTPException as HTTPException2 } from "hono/http-exception";
80
+ import { z } from "zod";
81
+
82
+ // src/api/http.ts
26
83
  import { HTTPException } from "hono/http-exception";
84
+ function parseParams(schema, params) {
85
+ const result = schema.safeParse(params);
86
+ if (result.success) return result.data;
87
+ throw new HTTPException(400, {
88
+ cause: result.error,
89
+ message: "Invalid route parameters."
90
+ });
91
+ }
92
+ function parseQuery(schema, query) {
93
+ const result = schema.safeParse(query);
94
+ if (result.success) return result.data;
95
+ throw new HTTPException(400, {
96
+ cause: result.error,
97
+ message: "Invalid query parameters."
98
+ });
99
+ }
100
+
101
+ // src/api/conversations/archive.ts
102
+ var archiveBodySchema = z.object({ archived: z.boolean(), lastSeenAt: z.string().datetime() }).strict();
103
+ async function archiveIfUnchanged(args) {
104
+ const db = getDb();
105
+ const rows = await db.update(juniorConversations).set({ archivedAt: args.archived ? /* @__PURE__ */ new Date() : null }).where(
106
+ and(
107
+ eq(juniorConversations.conversationId, args.conversationId),
108
+ args.archived ? eq(juniorConversations.lastActivityAt, new Date(args.lastSeenAt)) : isNotNull(juniorConversations.archivedAt)
109
+ )
110
+ ).returning({ conversationId: juniorConversations.conversationId });
111
+ if (rows.length > 0) return "updated";
112
+ const [existing] = await db.select({
113
+ archivedAt: juniorConversations.archivedAt,
114
+ conversationId: juniorConversations.conversationId
115
+ }).from(juniorConversations).where(eq(juniorConversations.conversationId, args.conversationId)).limit(1);
116
+ if (!existing) return "not_found";
117
+ if (!args.archived && existing.archivedAt === null) return "updated";
118
+ return "conflict";
119
+ }
120
+ var archive_default = {
121
+ method: "patch",
122
+ path: "/:conversationId/archive",
123
+ handler: async (c) => {
124
+ const { conversationId } = parseParams(
125
+ conversationParamsSchema,
126
+ c.req.param()
127
+ );
128
+ let input;
129
+ try {
130
+ input = await c.req.json();
131
+ } catch (error) {
132
+ throw new HTTPException2(400, {
133
+ cause: error,
134
+ message: "Invalid request body."
135
+ });
136
+ }
137
+ const parsed = archiveBodySchema.safeParse(input);
138
+ if (!parsed.success) {
139
+ throw new HTTPException2(400, {
140
+ cause: parsed.error,
141
+ message: "Invalid request body."
142
+ });
143
+ }
144
+ const body = parsed.data;
145
+ const result = await archiveIfUnchanged({ ...body, conversationId });
146
+ if (result === "not_found") {
147
+ return Response.json(
148
+ { error: "Conversation not found." },
149
+ { status: 404 }
150
+ );
151
+ }
152
+ if (result === "conflict") {
153
+ return Response.json(
154
+ { error: "Conversation received new activity." },
155
+ { status: 409 }
156
+ );
157
+ }
158
+ return Response.json({ archived: body.archived });
159
+ }
160
+ };
161
+
162
+ // src/api/conversations/detail-projection.ts
163
+ import { isDeepStrictEqual } from "util";
164
+
165
+ // src/api/conversations/transcript.ts
166
+ var SAFE_METADATA_KEY_LIMIT = 20;
167
+ var LEGACY_ADVISOR_TASK_OPEN = "<advisor-task>\n";
168
+ var LEGACY_ADVISOR_TASK_CLOSE = "\n</advisor-task>";
169
+ var LEGACY_EXECUTOR_CONTEXT_OPEN = "<executor-context>\n";
170
+ var LEGACY_EXECUTOR_CONTEXT_CLOSE = "\n</executor-context>";
171
+ function unwrapLegacyAdvisorTask(text) {
172
+ if (!text.startsWith(LEGACY_ADVISOR_TASK_OPEN) || !text.endsWith(LEGACY_EXECUTOR_CONTEXT_CLOSE)) {
173
+ return void 0;
174
+ }
175
+ const taskEnd = text.indexOf(
176
+ LEGACY_ADVISOR_TASK_CLOSE,
177
+ LEGACY_ADVISOR_TASK_OPEN.length
178
+ );
179
+ if (taskEnd < 0) return void 0;
180
+ const contextStart = taskEnd + LEGACY_ADVISOR_TASK_CLOSE.length + 2;
181
+ if (!text.startsWith(LEGACY_EXECUTOR_CONTEXT_OPEN, contextStart)) {
182
+ return void 0;
183
+ }
184
+ const task = text.slice(LEGACY_ADVISOR_TASK_OPEN.length, taskEnd);
185
+ const context = text.slice(
186
+ contextStart + LEGACY_EXECUTOR_CONTEXT_OPEN.length,
187
+ -LEGACY_EXECUTOR_CONTEXT_CLOSE.length
188
+ );
189
+ return `${unescapeXml(task)}
190
+
191
+ Executor context:
192
+ ${unescapeXml(context)}`;
193
+ }
194
+ function textPart(text) {
195
+ return { type: "text", text };
196
+ }
197
+ function recordField(value, names) {
198
+ for (const name of names) {
199
+ if (value[name] !== void 0) {
200
+ return value[name];
201
+ }
202
+ }
203
+ return void 0;
204
+ }
205
+ function thinkingOutput(part) {
206
+ const output = recordField(part, ["thinking", "text", "content", "output"]);
207
+ return isRecord(output) ? recordField(output, ["thinking", "text", "content", "output"]) : output;
208
+ }
209
+ function isDisplayableTranscriptPart(part) {
210
+ if (part.type !== "thinking") return true;
211
+ return typeof part.output === "string" && part.output.trim().length > 0;
212
+ }
213
+ function normalizeTranscriptPart(part, options = {}) {
214
+ const displayText = (text) => {
215
+ if (options.unwrapCurrentTask) {
216
+ const instruction = unwrapCurrentInstruction(text);
217
+ if (instruction !== void 0) return instruction;
218
+ }
219
+ if (options.unwrapLegacyAdvisorTask) {
220
+ return unwrapLegacyAdvisorTask(text) ?? text;
221
+ }
222
+ return text;
223
+ };
224
+ if (typeof part === "string") {
225
+ return textPart(displayText(part));
226
+ }
227
+ if (!isRecord(part)) {
228
+ return { type: "unknown", output: part };
229
+ }
230
+ const rawType = typeof part.type === "string" ? part.type : "unknown";
231
+ if (rawType === "text") {
232
+ const text = recordField(part, ["text", "content"]);
233
+ return textPart(
234
+ typeof text === "string" ? displayText(text) : JSON.stringify(text) ?? ""
235
+ );
236
+ }
237
+ if (rawType === "toolCall") {
238
+ return {
239
+ type: "tool_call",
240
+ ...typeof part.id === "string" ? { id: part.id } : {},
241
+ ...typeof part.name === "string" ? { name: part.name } : {},
242
+ input: recordField(part, ["arguments", "input", "args"])
243
+ };
244
+ }
245
+ if (rawType === "toolResult") {
246
+ return {
247
+ type: "tool_result",
248
+ ...typeof part.id === "string" ? { id: part.id } : {},
249
+ ...typeof part.name === "string" ? { name: part.name } : {},
250
+ output: recordField(part, ["result", "output", "content"])
251
+ };
252
+ }
253
+ if (rawType === "thinking") {
254
+ return {
255
+ type: "thinking",
256
+ output: thinkingOutput(part)
257
+ };
258
+ }
259
+ return {
260
+ type: "unknown",
261
+ ...rawType !== "unknown" ? { sourceType: rawType } : {},
262
+ output: part
263
+ };
264
+ }
265
+ function normalizeToolResultMessage(record) {
266
+ const content = record.content;
267
+ let output = content;
268
+ if (Array.isArray(content) && content.length === 1 && isRecord(content[0])) {
269
+ const extracted = recordField(content[0], [
270
+ "text",
271
+ "content",
272
+ "output",
273
+ "result"
274
+ ]);
275
+ output = extracted !== void 0 ? extracted : content;
276
+ }
277
+ return {
278
+ type: "tool_result",
279
+ ...typeof record.toolCallId === "string" ? { id: record.toolCallId } : {},
280
+ ...typeof record.name === "string" ? { name: record.name } : typeof record.toolName === "string" ? { name: record.toolName } : {},
281
+ output
282
+ };
283
+ }
284
+ function normalizeTranscriptMessage(message) {
285
+ return normalizeMessage(message);
286
+ }
287
+ function normalizeSubagentTranscriptMessage(message, subagentKind) {
288
+ return normalizeMessage(message, {
289
+ unwrapLegacyAdvisorTask: subagentKind === "advisor"
290
+ });
291
+ }
292
+ function normalizeMessage(message, options = {}) {
293
+ const record = message;
294
+ const content = record.content;
295
+ const role = transcriptRole(record.role);
296
+ return {
297
+ role,
298
+ ...role === "assistant" && (record.stopReason === "error" || record.stopReason === "aborted") ? { outcome: record.stopReason } : {},
299
+ ...typeof record.timestamp === "number" ? { timestamp: record.timestamp } : {},
300
+ parts: role === "toolResult" ? [normalizeToolResultMessage(record)] : Array.isArray(content) ? content.map(
301
+ (part) => normalizeTranscriptPart(part, {
302
+ unwrapCurrentTask: role === "user",
303
+ unwrapLegacyAdvisorTask: options.unwrapLegacyAdvisorTask && role === "user"
304
+ })
305
+ ).filter(isDisplayableTranscriptPart) : [
306
+ normalizeTranscriptPart(content, {
307
+ unwrapCurrentTask: role === "user",
308
+ unwrapLegacyAdvisorTask: options.unwrapLegacyAdvisorTask && role === "user"
309
+ })
310
+ ]
311
+ };
312
+ }
313
+ function transcriptRole(role) {
314
+ return role === "assistant" || role === "system" || role === "tool" || role === "toolResult" || role === "user" ? role : "unknown";
315
+ }
316
+ function serializedChars(value) {
317
+ if (typeof value === "string") return value.length;
318
+ return JSON.stringify(value)?.length ?? 0;
319
+ }
320
+ function serializedBytes(value) {
321
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
322
+ return new TextEncoder().encode(serialized ?? "").byteLength;
323
+ }
324
+ function payloadType(value) {
325
+ return Array.isArray(value) ? "array" : typeof value;
326
+ }
327
+ function payloadKeys(value) {
328
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
329
+ return void 0;
330
+ }
331
+ const keys = Object.keys(value).slice(
332
+ 0,
333
+ SAFE_METADATA_KEY_LIMIT
334
+ );
335
+ return keys.length > 0 ? keys : void 0;
336
+ }
337
+ function redactedPayloadFields(prefix, value) {
338
+ const keys = payloadKeys(value);
339
+ return {
340
+ [`${prefix}Type`]: payloadType(value),
341
+ [`${prefix}SizeBytes`]: serializedBytes(value),
342
+ [`${prefix}SizeChars`]: serializedChars(value),
343
+ ...keys ? { [`${prefix}Keys`]: keys } : {}
344
+ };
345
+ }
346
+ function redactTranscriptPart(part) {
347
+ if (part.type === "text") {
348
+ return {
349
+ type: "text",
350
+ redacted: true,
351
+ bytes: serializedBytes(part.text ?? ""),
352
+ chars: serializedChars(part.text ?? "")
353
+ };
354
+ }
355
+ if (part.type === "thinking") {
356
+ return {
357
+ type: "thinking",
358
+ redacted: true,
359
+ ...redactedPayloadFields("output", part.output)
360
+ };
361
+ }
362
+ if (part.type === "tool_call") {
363
+ return {
364
+ type: "tool_call",
365
+ redacted: true,
366
+ ...part.id ? { id: part.id } : {},
367
+ ...part.name ? { name: part.name } : {},
368
+ ...redactedPayloadFields("input", part.input)
369
+ };
370
+ }
371
+ if (part.type === "tool_result") {
372
+ return {
373
+ type: "tool_result",
374
+ redacted: true,
375
+ ...part.id ? { id: part.id } : {},
376
+ ...part.name ? { name: part.name } : {},
377
+ ...redactedPayloadFields("output", part.output)
378
+ };
379
+ }
380
+ return {
381
+ type: "unknown",
382
+ redacted: true,
383
+ ...part.sourceType ? { sourceType: part.sourceType } : {},
384
+ ...redactedPayloadFields("output", part.output ?? part.input ?? part.text)
385
+ };
386
+ }
387
+ function redactTranscriptMessage(message) {
388
+ return {
389
+ role: message.role,
390
+ ...message.outcome ? { outcome: message.outcome } : {},
391
+ ...typeof message.timestamp === "number" ? { timestamp: message.timestamp } : {},
392
+ parts: message.parts.map(redactTranscriptPart)
393
+ };
394
+ }
395
+ function isConversationMessageRole(role) {
396
+ return role === "user" || role === "assistant";
397
+ }
398
+ function hasTextPart(message) {
399
+ return message.parts.some((part) => {
400
+ if (part.type !== "text") return false;
401
+ if (part.redacted) return true;
402
+ return typeof part.text === "string" && part.text.trim().length > 0;
403
+ });
404
+ }
405
+ function isConversationMessage(message) {
406
+ if (!isConversationMessageRole(message.role)) return false;
407
+ if (message.role === "assistant") return hasTextPart(message);
408
+ return message.parts.length > 0;
409
+ }
410
+ function countConversationMessages(transcript) {
411
+ return transcript.filter(isConversationMessage).length;
412
+ }
413
+ function traceIdFromTranscript(transcript) {
414
+ for (const message of transcript) {
415
+ for (const part of message.parts) {
416
+ const text = part.text ?? (typeof part.output === "string" ? part.output : typeof part.input === "string" ? part.input : void 0);
417
+ const match = text?.match(
418
+ /\btrace[_-]?id["']?\s*[:=]\s*["']?([a-f0-9]{16,32})\b/i
419
+ );
420
+ if (match?.[1]) {
421
+ return match[1];
422
+ }
423
+ }
424
+ }
425
+ return void 0;
426
+ }
427
+ function subagentTranscriptReport(activity, options = {}) {
428
+ return {
429
+ type: "subagent",
430
+ ...options.subagentConversationId ? { subagentConversationId: options.subagentConversationId } : {},
431
+ createdAt: activity.createdAt,
432
+ id: activity.id,
433
+ ...activity.modelId ? { modelId: activity.modelId } : {},
434
+ status: activity.status,
435
+ ...options.subagentSentryConversationUrl ? { subagentSentryConversationUrl: options.subagentSentryConversationUrl } : {},
436
+ subagentKind: activity.subagentKind,
437
+ transcript: options.transcript ?? [],
438
+ transcriptAvailable: Boolean(options.transcript?.length),
439
+ ...activity.endedAt ? { endedAt: activity.endedAt } : {},
440
+ ...activity.outcome ? { outcome: activity.outcome } : {},
441
+ ...activity.parentToolCallId ? { parentToolCallId: activity.parentToolCallId } : {},
442
+ ...activity.reasoningLevel ? { reasoningLevel: activity.reasoningLevel } : {},
443
+ ...options.transcriptMessageCount !== void 0 ? { transcriptMessageCount: options.transcriptMessageCount } : {},
444
+ ...options.transcriptRedacted ? { transcriptRedacted: options.transcriptRedacted } : {},
445
+ ...options.transcriptRedactionReason ? { transcriptRedactionReason: options.transcriptRedactionReason } : {},
446
+ ...options.transcriptExpired ? { transcriptExpired: options.transcriptExpired } : {},
447
+ ...options.transcriptExpiredAt ? { transcriptExpiredAt: options.transcriptExpiredAt } : {},
448
+ ...options.unavailableReason ? { unavailableReason: options.unavailableReason } : {}
449
+ };
450
+ }
451
+
452
+ // src/api/conversations/activity.ts
453
+ function toolResultStatuses(messages) {
454
+ const statuses = /* @__PURE__ */ new Map();
455
+ for (const message of messages) {
456
+ const record = message;
457
+ if (record.role !== "toolResult" || typeof record.toolCallId !== "string") {
458
+ continue;
459
+ }
460
+ statuses.set(record.toolCallId, record.isError ? "error" : "completed");
461
+ }
462
+ return statuses;
463
+ }
464
+ function activityPayloadFields(args, canExposePayload) {
465
+ if (args === void 0) {
466
+ return {};
467
+ }
468
+ return canExposePayload ? { args } : { redacted: true, ...redactedPayloadFields("input", args) };
469
+ }
470
+ function buildConversationActivityFromSteps(args) {
471
+ const toolStatuses = toolResultStatuses(args.messages);
472
+ const subagentEnds = /* @__PURE__ */ new Map();
473
+ const subagentsByToolCallId = /* @__PURE__ */ new Map();
474
+ const orphanSubagents = [];
475
+ for (const step of args.steps) {
476
+ if (step.entry.type === "subagent_ended") {
477
+ subagentEnds.set(
478
+ step.entry.subagentInvocationId,
479
+ step
480
+ );
481
+ }
482
+ }
483
+ for (const step of args.steps) {
484
+ if (step.entry.type !== "subagent_started") {
485
+ continue;
486
+ }
487
+ const start = step;
488
+ const parentStatus = start.entry.parentToolCallId ? toolStatuses.get(start.entry.parentToolCallId) : void 0;
489
+ const activity = subagentActivityFromSteps(
490
+ start,
491
+ subagentEnds.get(start.entry.subagentInvocationId),
492
+ { canExposeTranscript: args.canExposePayload, parentStatus }
493
+ );
494
+ if (start.entry.parentToolCallId) {
495
+ subagentsByToolCallId.set(start.entry.parentToolCallId, [
496
+ ...subagentsByToolCallId.get(start.entry.parentToolCallId) ?? [],
497
+ activity
498
+ ]);
499
+ continue;
500
+ }
501
+ orphanSubagents.push(activity);
502
+ }
503
+ const rows = [];
504
+ for (const step of args.steps) {
505
+ if (step.entry.type !== "tool_execution_started") {
506
+ continue;
507
+ }
508
+ rows.push({
509
+ type: "tool_execution",
510
+ id: step.entry.toolCallId,
511
+ toolCallId: step.entry.toolCallId,
512
+ toolName: step.entry.toolName,
513
+ createdAt: new Date(step.createdAtMs).toISOString(),
514
+ status: toolStatuses.get(step.entry.toolCallId) ?? "running",
515
+ subagents: subagentsByToolCallId.get(step.entry.toolCallId) ?? [],
516
+ ...activityPayloadFields(step.entry.args, args.canExposePayload)
517
+ });
518
+ }
519
+ return [...rows, ...orphanSubagents].sort(
520
+ (left, right) => Date.parse(left.createdAt) - Date.parse(right.createdAt) || left.id.localeCompare(right.id)
521
+ );
522
+ }
523
+ function subagentActivityFromSteps(start, end, options = {}) {
524
+ return {
525
+ type: "subagent",
526
+ id: start.entry.subagentInvocationId,
527
+ subagentKind: start.entry.subagentKind,
528
+ ...start.entry.modelId ? { modelId: start.entry.modelId } : {},
529
+ ...start.entry.parentToolCallId ? { parentToolCallId: start.entry.parentToolCallId } : {},
530
+ ...start.entry.reasoningLevel ? { reasoningLevel: start.entry.reasoningLevel } : {},
531
+ createdAt: new Date(start.createdAtMs).toISOString(),
532
+ ...end ? {
533
+ endedAt: new Date(end.createdAtMs).toISOString(),
534
+ outcome: end.entry.outcome,
535
+ status: end.entry.outcome,
536
+ // Every subagent is a child conversation whose transcript loads on
537
+ // demand; expose the affordance only when the parent is public.
538
+ ...options.canExposeTranscript ? { transcriptAvailable: true } : {}
539
+ } : { status: options.parentStatus ?? "running" }
540
+ };
541
+ }
542
+
543
+ // src/api/conversations/shared.ts
544
+ function slackStatsLocationLabel(input) {
545
+ const channelId = input.channel;
546
+ if (!channelId) return void 0;
547
+ if (input.channelNameRedacted && input.channelName) {
548
+ return input.channelName;
549
+ }
550
+ const name = input.channelName?.replace(/^#/, "");
551
+ if (channelId.startsWith("D")) return "Direct Message";
552
+ if (channelId.startsWith("C")) return name ? `#${name}` : "Public Channel";
553
+ if (channelId.startsWith("G")) {
554
+ if (name?.startsWith("mpdm-")) return "Group DM";
555
+ return "Private Channel";
556
+ }
557
+ return name || channelId;
558
+ }
559
+ function surfaceFallbackLabel(surface) {
560
+ if (surface === "scheduler") return "Scheduler";
561
+ if (surface === "api") return "API";
562
+ if (surface === "internal") return "Internal";
563
+ return "Conversation";
564
+ }
565
+
566
+ // src/api/conversations/projection.ts
567
+ var PRIVATE_CONVERSATION_LABEL = "Private Conversation";
568
+ function privateConversationLabel(slackConversation) {
569
+ if (!slackConversation) {
570
+ return PRIVATE_CONVERSATION_LABEL;
571
+ }
572
+ return slackConversation.visibility === "private" ? formatSlackConversationRedactedLabel(slackConversation) ?? PRIVATE_CONVERSATION_LABEL : PRIVATE_CONVERSATION_LABEL;
573
+ }
574
+ function surfaceFromConversationId(conversationId) {
575
+ if (parseSlackThreadId(conversationId)) return "slack";
576
+ if (conversationId.startsWith("scheduler:")) return "scheduler";
577
+ if (conversationId.startsWith("api:")) return "api";
578
+ return "internal";
579
+ }
580
+ function surfaceFromSource(source, conversationId) {
581
+ if (source === "slack" || source === "api" || source === "scheduler") {
582
+ return source;
583
+ }
584
+ return surfaceFromConversationId(conversationId);
585
+ }
586
+ function actorIdentityReport(actor) {
587
+ if (!actor) return void 0;
588
+ const identity = {
589
+ ...actor.email !== void 0 ? { email: actor.email } : {},
590
+ ...actor.fullName !== void 0 ? { fullName: actor.fullName } : {},
591
+ ...actor.slackUserId !== void 0 ? { slackUserId: actor.slackUserId } : {},
592
+ ...actor.slackUserName !== void 0 ? { slackUserName: actor.slackUserName } : {}
593
+ };
594
+ return Object.keys(identity).length > 0 ? identity : void 0;
595
+ }
596
+ function statusFromConversation(conversation) {
597
+ if (conversation.execution.status === "idle") {
598
+ return "completed";
599
+ }
600
+ if (conversation.execution.status === "failed") {
601
+ return "failed";
602
+ }
603
+ return "active";
604
+ }
605
+ function titleFromConversation(args) {
606
+ const slackThread = parseSlackThreadId(args.conversation.conversationId);
607
+ const effectiveChannelName = args.conversation.channelName;
608
+ const slackConversation = resolveSlackConversationContextFromThreadId({
609
+ threadId: args.conversation.conversationId,
610
+ channelName: effectiveChannelName
611
+ });
612
+ const privateLabel = resolveConversationPrivacy({
613
+ conversationId: args.conversation.conversationId,
614
+ visibility: args.conversation.visibility
615
+ }) !== "public" ? privateConversationLabel(slackConversation) : void 0;
616
+ return privateLabel ?? args.conversation.title ?? slackStatsLocationLabel({
617
+ channel: slackThread?.channelId,
618
+ channelName: effectiveChannelName
619
+ }) ?? surfaceFallbackLabel(args.surface);
620
+ }
621
+ function channelNameFromConversation(conversation) {
622
+ const effectiveChannelName = conversation.channelName;
623
+ const slackThread = parseSlackThreadId(conversation.conversationId);
624
+ if (!effectiveChannelName && !slackThread) {
625
+ return void 0;
626
+ }
627
+ const slackConversation = resolveSlackConversationContextFromThreadId({
628
+ threadId: conversation.conversationId,
629
+ channelName: effectiveChannelName
630
+ });
631
+ if (resolveConversationPrivacy({
632
+ conversationId: conversation.conversationId,
633
+ visibility: conversation.visibility
634
+ }) !== "public") {
635
+ return privateConversationLabel(slackConversation);
636
+ }
637
+ return effectiveChannelName;
638
+ }
639
+ function channelNameRedactedFromConversation(conversation) {
640
+ const effectiveChannelName = conversation.channelName;
641
+ const slackThread = parseSlackThreadId(conversation.conversationId);
642
+ if (!effectiveChannelName && !slackThread) {
643
+ return false;
644
+ }
645
+ return resolveConversationPrivacy({
646
+ conversationId: conversation.conversationId,
647
+ visibility: conversation.visibility
648
+ }) !== "public";
649
+ }
650
+ function conversationSummaryFromStoredConversation(args) {
651
+ const { conversation, durationMs, usage } = args;
652
+ const surface = surfaceFromSource(
653
+ conversation.source,
654
+ conversation.conversationId
655
+ );
656
+ const actorIdentity = actorIdentityReport(conversation.actor);
657
+ const slackThread = parseSlackThreadId(conversation.conversationId);
658
+ const channelName = channelNameFromConversation(conversation);
659
+ const channelNameRedacted = channelNameRedactedFromConversation(conversation);
660
+ return {
661
+ conversationId: conversation.conversationId,
662
+ cumulativeDurationMs: durationMs,
663
+ displayTitle: titleFromConversation({ conversation, surface }),
664
+ lastProgressAt: new Date(
665
+ conversation.execution.updatedAtMs ?? conversation.updatedAtMs
666
+ ).toISOString(),
667
+ lastSeenAt: new Date(conversation.lastActivityAtMs).toISOString(),
668
+ startedAt: new Date(conversation.createdAtMs).toISOString(),
669
+ status: statusFromConversation(conversation),
670
+ surface,
671
+ ...usage ? { cumulativeUsage: usage } : {},
672
+ ...actorIdentity ? { actorIdentity } : {},
673
+ ...conversation.archivedAtMs ? { archivedAt: new Date(conversation.archivedAtMs).toISOString() } : {},
674
+ ...slackThread ? { channel: slackThread.channelId } : {},
675
+ ...channelName ? { channelName } : {},
676
+ ...channelNameRedacted ? { channelNameRedacted: true } : {},
677
+ ...args.locationId && !channelNameRedacted ? { locationId: args.locationId } : {}
678
+ };
679
+ }
680
+
681
+ // src/api/conversations/detail-projection.ts
682
+ var COMPACTION_SUMMARY_PREFIXES = [
683
+ "Context compaction summary for future Junior turns:",
684
+ "Context handoff summary for future Junior turns:"
685
+ ];
686
+ var MODEL_HANDOFF_SUMMARY_PREFIX = "Model handoff checkpoint. Continue the outstanding request now using this summary as the complete prior context:";
687
+ function messageText(message) {
688
+ return normalizeTranscriptMessage(message).parts.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n").trim();
689
+ }
690
+ function summaryAfterPrefix(message, prefixes) {
691
+ const text = messageText(message);
692
+ const prefix = prefixes.find((candidate) => text.startsWith(candidate));
693
+ if (!prefix) return void 0;
694
+ return text.slice(prefix.length).trim();
695
+ }
696
+ function summaryIndex(messages, provenance, prefixes) {
697
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
698
+ if (provenance[index]?.authority === "context" && summaryAfterPrefix(messages[index], prefixes) !== void 0) {
699
+ return index;
700
+ }
701
+ }
702
+ return -1;
703
+ }
704
+ function matchingPrefix(left, right) {
705
+ const limit = Math.min(left.length, right.length);
706
+ for (let index = 0; index < limit; index += 1) {
707
+ if (!isDeepStrictEqual(left[index], right[index])) return index;
708
+ }
709
+ return limit;
710
+ }
711
+ function historyContent(args) {
712
+ const contextEvents = [];
713
+ const messages = [];
714
+ const epochs = /* @__PURE__ */ new Map();
715
+ for (const step of args.steps) {
716
+ const epoch = epochs.get(step.contextEpoch);
717
+ if (epoch) epoch.push(step);
718
+ else epochs.set(step.contextEpoch, [step]);
719
+ }
720
+ let previousModelId;
721
+ let previousProjection = [];
722
+ for (const steps of epochs.values()) {
723
+ const marker = steps.find(
724
+ (step) => step.entry.type === "context_epoch_started"
725
+ );
726
+ const projection = projectSteps(steps);
727
+ const projected = [];
728
+ const projectedProvenance = [];
729
+ projection.messages.forEach((message, index) => {
730
+ for (const retained of stripRuntimeTurnContext([message])) {
731
+ projected.push(retained);
732
+ projectedProvenance.push(projection.provenance[index]);
733
+ }
734
+ });
735
+ const replacementSummaryIndex = marker?.entry.reason === "compaction" ? summaryIndex(
736
+ projected,
737
+ projectedProvenance,
738
+ COMPACTION_SUMMARY_PREFIXES
739
+ ) : marker?.entry.reason === "handoff" ? summaryIndex(projected, projectedProvenance, [
740
+ MODEL_HANDOFF_SUMMARY_PREFIX
741
+ ]) : -1;
742
+ const summary = marker?.entry.reason === "compaction" && replacementSummaryIndex >= 0 ? summaryAfterPrefix(
743
+ projected[replacementSummaryIndex],
744
+ COMPACTION_SUMMARY_PREFIXES
745
+ ) : void 0;
746
+ const handoffMessage = marker?.entry.reason === "handoff" && replacementSummaryIndex >= 0 ? messageText(projected[replacementSummaryIndex]) || void 0 : void 0;
747
+ if (marker?.entry.reason === "compaction") {
748
+ contextEvents.push({
749
+ type: "context_compacted",
750
+ createdAt: new Date(marker.createdAtMs).toISOString(),
751
+ ...marker.entry.modelId ? { modelId: marker.entry.modelId } : {},
752
+ ...args.canExposePayload && summary ? { summary } : {},
753
+ transcriptIndex: messages.length
754
+ });
755
+ } else if (marker?.entry.reason === "handoff") {
756
+ contextEvents.push({
757
+ type: "model_handoff",
758
+ createdAt: new Date(marker.createdAtMs).toISOString(),
759
+ ...previousModelId ? { fromModelId: previousModelId } : {},
760
+ toModelId: marker.entry.modelId,
761
+ ...args.canExposePayload && handoffMessage ? { message: handoffMessage } : {},
762
+ transcriptIndex: messages.length
763
+ });
764
+ }
765
+ if (marker?.entry.reason === "rollback") {
766
+ messages.push(
767
+ ...projected.slice(matchingPrefix(previousProjection, projected))
768
+ );
769
+ } else {
770
+ const copiedMessageIndexes = /* @__PURE__ */ new Set();
771
+ projected.forEach((message, index) => {
772
+ if (index === replacementSummaryIndex) return;
773
+ let copiedCompactionMessage = false;
774
+ if (marker?.entry.reason === "compaction" && replacementSummaryIndex >= 0 && index < replacementSummaryIndex) {
775
+ const copiedIndex = messages.findIndex(
776
+ (candidate, candidateIndex) => !copiedMessageIndexes.has(candidateIndex) && isDeepStrictEqual(candidate, message)
777
+ );
778
+ copiedCompactionMessage = copiedIndex >= 0;
779
+ if (copiedCompactionMessage) copiedMessageIndexes.add(copiedIndex);
780
+ }
781
+ if (!copiedCompactionMessage) messages.push(message);
782
+ });
783
+ }
784
+ previousModelId = marker?.entry.modelId ?? previousModelId;
785
+ previousProjection = projected;
786
+ }
787
+ return { contextEvents, messages };
788
+ }
789
+ async function conversationContent(args) {
790
+ const steps = await args.stepStore.loadHistory(args.conversationId);
791
+ const history = historyContent({
792
+ canExposePayload: args.canExposePayload,
793
+ steps
794
+ });
795
+ const messages = history.messages;
796
+ const transcript = messages.length > 0 ? messages.map((message) => normalizeTranscriptMessage(message)) : (await args.messageStore.list(args.conversationId)).map(
797
+ visibleMessageTranscript
798
+ );
799
+ return {
800
+ activity: buildConversationActivityFromSteps({
801
+ canExposePayload: args.canExposePayload,
802
+ steps,
803
+ messages
804
+ }),
805
+ contextEvents: history.contextEvents,
806
+ transcript
807
+ };
808
+ }
809
+ function visibleMessageTranscript(message) {
810
+ return {
811
+ role: message.role,
812
+ timestamp: message.createdAtMs,
813
+ parts: [{ type: "text", text: message.text }]
814
+ };
815
+ }
816
+ async function buildConversationDetail(args) {
817
+ const { conversation } = args;
818
+ const conversationId = conversation.conversationId;
819
+ const nowMs = Date.now();
820
+ const stepStore = getAgentStepStore();
821
+ const messageStore = getConversationMessageStore();
822
+ const transcriptPurgedAtMs = conversation.transcriptPurgedAtMs;
823
+ const transcriptExpiredAt = transcriptPurgedAtMs !== void 0 ? new Date(transcriptPurgedAtMs).toISOString() : void 0;
824
+ const canExposeSqlContent = canExposeConversationPayload({
825
+ conversationId,
826
+ visibility: conversation.visibility
827
+ });
828
+ const currentContent = transcriptPurgedAtMs === void 0 ? await conversationContent({
829
+ conversationId,
830
+ messageStore,
831
+ stepStore,
832
+ canExposePayload: canExposeSqlContent
833
+ }) : { activity: [], contextEvents: [], transcript: [] };
834
+ const currentTranscript = currentContent.transcript;
835
+ const traceId = canExposeSqlContent ? traceIdFromTranscript(currentTranscript) : void 0;
836
+ const sentryTraceUrl = traceId ? buildSentryTraceUrl(traceId) : void 0;
837
+ const sentryConversationUrl = buildSentryConversationUrl(conversationId);
838
+ return {
839
+ ...conversationSummaryFromStoredConversation({
840
+ conversation,
841
+ durationMs: args.durationMs,
842
+ ...args.locationId ? { locationId: args.locationId } : {},
843
+ usage: args.usage
844
+ }),
845
+ ...traceId ? { traceId } : {},
846
+ ...sentryTraceUrl ? { sentryTraceUrl } : {},
847
+ activity: currentContent.activity,
848
+ contextEvents: currentContent.contextEvents,
849
+ transcriptAvailable: transcriptExpiredAt === void 0 && canExposeSqlContent && currentTranscript.length > 0,
850
+ ...currentTranscript.length > 0 ? {
851
+ transcriptMessageCount: countConversationMessages(currentTranscript)
852
+ } : {},
853
+ ...!canExposeSqlContent && transcriptExpiredAt === void 0 ? {
854
+ transcriptMetadata: currentTranscript.map(redactTranscriptMessage),
855
+ transcriptRedacted: true,
856
+ transcriptRedactionReason: "non_public_conversation"
857
+ } : {},
858
+ ...transcriptExpiredAt !== void 0 ? {
859
+ transcriptExpired: true,
860
+ transcriptExpiredAt,
861
+ transcriptMetadata: []
862
+ } : {},
863
+ transcript: transcriptExpiredAt === void 0 && canExposeSqlContent ? currentTranscript : [],
864
+ generatedAt: new Date(nowMs).toISOString(),
865
+ ...sentryConversationUrl ? { sentryConversationUrl } : {}
866
+ };
867
+ }
868
+ async function buildConversationSubagent(conversation, subagentId) {
869
+ const conversationId = conversation.conversationId;
870
+ const stepStore = getAgentStepStore();
871
+ const parentSteps = await stepStore.loadHistory(conversationId);
872
+ if (conversation?.transcriptPurgedAtMs !== void 0) {
873
+ return {
874
+ type: "subagent",
875
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
876
+ id: subagentId,
877
+ status: "completed",
878
+ subagentKind: "unknown",
879
+ transcript: [],
880
+ transcriptAvailable: false,
881
+ transcriptExpired: true,
882
+ transcriptExpiredAt: new Date(
883
+ conversation.transcriptPurgedAtMs
884
+ ).toISOString()
885
+ };
886
+ }
887
+ const start = parentSteps.find(
888
+ (step) => step.entry.type === "subagent_started" && step.entry.subagentInvocationId === subagentId
889
+ );
890
+ if (!start) {
891
+ return {
892
+ type: "subagent",
893
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
894
+ id: subagentId,
895
+ status: "error",
896
+ subagentKind: "unknown",
897
+ transcript: [],
898
+ transcriptAvailable: false,
899
+ unavailableReason: "not_found"
900
+ };
901
+ }
902
+ const end = parentSteps.find(
903
+ (step) => step.entry.type === "subagent_ended" && step.entry.subagentInvocationId === subagentId
904
+ );
905
+ const childConversationId = start.entry.childConversationId;
906
+ const activity = subagentActivityFromSteps(start, end);
907
+ const subagentSentryConversationUrl = buildSentryConversationUrl(childConversationId);
908
+ const conversationFields = {
909
+ subagentConversationId: childConversationId,
910
+ ...subagentSentryConversationUrl ? { subagentSentryConversationUrl } : {}
911
+ };
912
+ const canExposeTranscript = canExposeConversationPayload({
913
+ conversationId,
914
+ visibility: conversation?.visibility
915
+ });
916
+ if (!canExposeTranscript) {
917
+ return subagentTranscriptReport(activity, {
918
+ ...conversationFields,
919
+ transcriptRedacted: true,
920
+ transcriptRedactionReason: "non_public_conversation"
921
+ });
922
+ }
923
+ const childMessages = await loadProjection({
924
+ conversationId: childConversationId
925
+ });
926
+ if (childMessages.length === 0) {
927
+ return subagentTranscriptReport(activity, {
928
+ ...conversationFields,
929
+ unavailableReason: "missing_transcript_ref"
930
+ });
931
+ }
932
+ const transcript = childMessages.map(
933
+ (message) => normalizeSubagentTranscriptMessage(message, activity.subagentKind)
934
+ );
935
+ return subagentTranscriptReport(activity, {
936
+ ...conversationFields,
937
+ transcript,
938
+ transcriptMessageCount: countConversationMessages(transcript)
939
+ });
940
+ }
941
+
942
+ // src/api/conversations/list.ts
943
+ import { and as and2, asc, desc, eq as eq2, isNull } from "drizzle-orm";
944
+ var CONVERSATION_FEED_LIMIT = 50;
945
+ async function conversationRows(db, limit, actorEmail) {
946
+ return db.select({
947
+ conversation: juniorConversations,
948
+ destinationId: juniorDestinations.id,
949
+ destinationVisibility: juniorDestinations.visibility,
950
+ identityDisplayName: juniorIdentities.displayName,
951
+ identityEmail: juniorIdentities.email,
952
+ identityHandle: juniorIdentities.handle,
953
+ identityProvider: juniorIdentities.provider,
954
+ identitySubjectId: juniorIdentities.providerSubjectId,
955
+ identityTenantId: juniorIdentities.providerTenantId
956
+ }).from(juniorConversations).leftJoin(
957
+ juniorDestinations,
958
+ eq2(juniorDestinations.id, juniorConversations.destinationId)
959
+ ).leftJoin(
960
+ juniorIdentities,
961
+ eq2(juniorIdentities.id, juniorConversations.actorIdentityId)
962
+ ).where(
963
+ and2(
964
+ isNull(juniorConversations.parentConversationId),
965
+ isNull(juniorConversations.archivedAt),
966
+ actorEmail ? and2(
967
+ eq2(juniorIdentities.emailNormalized, actorEmail),
968
+ eq2(juniorIdentities.emailVerified, true)
969
+ ) : void 0
970
+ )
971
+ ).orderBy(
972
+ desc(juniorConversations.lastActivityAt),
973
+ asc(juniorConversations.conversationId)
974
+ ).limit(limit);
975
+ }
976
+ function conversationFromRow(row) {
977
+ const value = row.conversation;
978
+ const actor = row.identityProvider === "slack" ? {
979
+ platform: "slack",
980
+ ...row.identityEmail ? { email: row.identityEmail } : {},
981
+ ...row.identityDisplayName ? { fullName: row.identityDisplayName } : {},
982
+ ...row.identitySubjectId ? { slackUserId: row.identitySubjectId } : {},
983
+ ...row.identityHandle ? { slackUserName: row.identityHandle } : {},
984
+ ...row.identityTenantId ? { teamId: row.identityTenantId } : {}
985
+ } : void 0;
986
+ return {
987
+ schemaVersion: 1,
988
+ conversationId: value.conversationId,
989
+ createdAtMs: value.createdAt.getTime(),
990
+ lastActivityAtMs: value.lastActivityAt.getTime(),
991
+ updatedAtMs: value.updatedAt.getTime(),
992
+ execution: {
993
+ status: value.executionStatus,
994
+ ...value.runId ? { runId: value.runId } : {},
995
+ ...value.executionUpdatedAt ? { updatedAtMs: value.executionUpdatedAt.getTime() } : {}
996
+ },
997
+ ...actor ? { actor } : {},
998
+ ...value.archivedAt ? { archivedAtMs: value.archivedAt.getTime() } : {},
999
+ ...value.channelName ? { channelName: value.channelName } : {},
1000
+ ...value.source ? { source: value.source } : {},
1001
+ ...value.title ? { title: value.title } : {},
1002
+ ...value.transcriptPurgedAt ? { transcriptPurgedAtMs: value.transcriptPurgedAt.getTime() } : {},
1003
+ ...row.destinationVisibility ? {
1004
+ visibility: row.destinationVisibility === "public" ? "public" : "private"
1005
+ } : {}
1006
+ };
1007
+ }
1008
+ async function readConversationRecordFromSql(conversationId) {
1009
+ const db = getDb();
1010
+ const rows = await db.select({
1011
+ conversation: juniorConversations,
1012
+ destinationId: juniorDestinations.id,
1013
+ destinationVisibility: juniorDestinations.visibility,
1014
+ identityDisplayName: juniorIdentities.displayName,
1015
+ identityEmail: juniorIdentities.email,
1016
+ identityHandle: juniorIdentities.handle,
1017
+ identityProvider: juniorIdentities.provider,
1018
+ identitySubjectId: juniorIdentities.providerSubjectId,
1019
+ identityTenantId: juniorIdentities.providerTenantId
1020
+ }).from(juniorConversations).leftJoin(
1021
+ juniorDestinations,
1022
+ eq2(juniorDestinations.id, juniorConversations.destinationId)
1023
+ ).leftJoin(
1024
+ juniorIdentities,
1025
+ eq2(juniorIdentities.id, juniorConversations.actorIdentityId)
1026
+ ).where(eq2(juniorConversations.conversationId, conversationId)).limit(1);
1027
+ const row = rows[0];
1028
+ return row ? {
1029
+ conversation: conversationFromRow(row),
1030
+ durationMs: row.conversation.durationMs,
1031
+ ...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
1032
+ usage: row.conversation.usage
1033
+ } : void 0;
1034
+ }
1035
+ async function readConversationFeedFromSql(options = {}) {
1036
+ const nowMs = Date.now();
1037
+ const rows = await conversationRows(
1038
+ getDb(),
1039
+ options.limit ?? CONVERSATION_FEED_LIMIT,
1040
+ options.actorEmail
1041
+ );
1042
+ return {
1043
+ conversations: rows.map(
1044
+ (row) => conversationSummaryFromStoredConversation({
1045
+ conversation: conversationFromRow(row),
1046
+ durationMs: row.conversation.durationMs,
1047
+ ...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
1048
+ usage: row.conversation.usage ?? void 0
1049
+ })
1050
+ ),
1051
+ generatedAt: new Date(nowMs).toISOString(),
1052
+ source: "conversation_index"
1053
+ };
1054
+ }
1055
+ async function readConversationFeed(options = {}) {
1056
+ return conversationFeedSchema.parse(
1057
+ await readConversationFeedFromSql({ actorEmail: options.actorEmail })
1058
+ );
1059
+ }
1060
+ var list_default = {
1061
+ method: "get",
1062
+ path: "/",
1063
+ handler: async (c) => {
1064
+ const { actorEmail } = parseQuery(
1065
+ conversationFeedQuerySchema,
1066
+ c.req.query()
1067
+ );
1068
+ return Response.json(await readConversationFeed({ actorEmail }));
1069
+ }
1070
+ };
1071
+
1072
+ // src/api/conversations/detail.query.ts
1073
+ async function readLatestRun(conversationId) {
1074
+ try {
1075
+ return (await listBoundedAgentTurnSessionSummariesForConversation(conversationId)).find((summary) => summary.modelId || summary.reasoningLevel);
1076
+ } catch (error) {
1077
+ logException(error, "conversation_execution_settings_read_failed", {
1078
+ conversationId
1079
+ });
1080
+ return void 0;
1081
+ }
1082
+ }
1083
+ async function readConversationDetailFromSql(conversationId) {
1084
+ const record = await readConversationRecordFromSql(conversationId);
1085
+ if (!record) return void 0;
1086
+ const [report, latestRun] = await Promise.all([
1087
+ buildConversationDetail({
1088
+ ...record,
1089
+ usage: record.usage ?? void 0
1090
+ }),
1091
+ readLatestRun(conversationId)
1092
+ ]);
1093
+ return {
1094
+ ...report,
1095
+ ...latestRun?.modelId ? { modelId: latestRun.modelId } : {},
1096
+ ...latestRun?.reasoningLevel ? { reasoningLevel: latestRun.reasoningLevel } : {}
1097
+ };
1098
+ }
1099
+
1100
+ // src/api/conversations/detail.ts
1101
+ async function readConversationDetail(conversationId) {
1102
+ const report = await readConversationDetailFromSql(conversationId);
1103
+ return report ? conversationDetailReportSchema.parse(report) : void 0;
1104
+ }
1105
+ var detail_default = {
1106
+ method: "get",
1107
+ path: "/:conversationId",
1108
+ handler: async (c) => {
1109
+ const { conversationId } = parseParams(
1110
+ conversationParamsSchema,
1111
+ c.req.param()
1112
+ );
1113
+ const report = await readConversationDetail(conversationId);
1114
+ return report ? Response.json(report) : Response.json({ error: "Conversation not found." }, { status: 404 });
1115
+ }
1116
+ };
1117
+
1118
+ // src/api/conversations/stats.query.ts
1119
+ import { and as and3, eq as eq3, gte, isNull as isNull2, lte } from "drizzle-orm";
1120
+
1121
+ // src/api/conversations/aggregate.ts
1122
+ import { sql } from "drizzle-orm";
1123
+ function tokenValue() {
1124
+ return sql`
1125
+ CASE
1126
+ WHEN ${juniorConversations.usage}->>'totalTokens' IS NOT NULL
1127
+ THEN (${juniorConversations.usage}->>'totalTokens')::double precision
1128
+ WHEN COALESCE(
1129
+ ${juniorConversations.usage}->>'inputTokens',
1130
+ ${juniorConversations.usage}->>'outputTokens',
1131
+ ${juniorConversations.usage}->>'cachedInputTokens',
1132
+ ${juniorConversations.usage}->>'cacheCreationTokens'
1133
+ ) IS NOT NULL
1134
+ THEN COALESCE((${juniorConversations.usage}->>'inputTokens')::double precision, 0)
1135
+ + COALESCE((${juniorConversations.usage}->>'outputTokens')::double precision, 0)
1136
+ + COALESCE((${juniorConversations.usage}->>'cachedInputTokens')::double precision, 0)
1137
+ + COALESCE((${juniorConversations.usage}->>'cacheCreationTokens')::double precision, 0)
1138
+ ELSE NULL
1139
+ END
1140
+ `;
1141
+ }
1142
+ function costValue() {
1143
+ return sql`
1144
+ CASE
1145
+ WHEN ${juniorConversations.usage}->'cost'->>'total' IS NOT NULL
1146
+ THEN (${juniorConversations.usage}->'cost'->>'total')::double precision
1147
+ WHEN COALESCE(
1148
+ ${juniorConversations.usage}->'cost'->>'input',
1149
+ ${juniorConversations.usage}->'cost'->>'output',
1150
+ ${juniorConversations.usage}->'cost'->>'cacheRead',
1151
+ ${juniorConversations.usage}->'cost'->>'cacheWrite'
1152
+ ) IS NOT NULL
1153
+ THEN COALESCE((${juniorConversations.usage}->'cost'->>'input')::double precision, 0)
1154
+ + COALESCE((${juniorConversations.usage}->'cost'->>'output')::double precision, 0)
1155
+ + COALESCE((${juniorConversations.usage}->'cost'->>'cacheRead')::double precision, 0)
1156
+ + COALESCE((${juniorConversations.usage}->'cost'->>'cacheWrite')::double precision, 0)
1157
+ ELSE NULL
1158
+ END
1159
+ `;
1160
+ }
1161
+ function conversationAggregateColumns() {
1162
+ return {
1163
+ active: sql`COUNT(*) FILTER (
1164
+ WHERE ${juniorConversations.executionStatus} NOT IN ('idle', 'failed')
1165
+ )::integer`,
1166
+ conversations: sql`COUNT(*)::integer`,
1167
+ costUsd: sql`SUM(${costValue()})::double precision`,
1168
+ durationMs: sql`COALESCE(SUM(${juniorConversations.durationMs}), 0)::double precision`,
1169
+ failed: sql`COUNT(*) FILTER (
1170
+ WHERE ${juniorConversations.executionStatus} = 'failed'
1171
+ )::integer`,
1172
+ tokens: sql`SUM(${tokenValue()})::double precision`
1173
+ };
1174
+ }
1175
+ function conversationRangeColumns() {
1176
+ return {
1177
+ firstSeenAt: sql`MIN(${juniorConversations.createdAt})`.mapWith(
1178
+ juniorConversations.createdAt
1179
+ ),
1180
+ lastSeenAt: sql`MAX(${juniorConversations.lastActivityAt})`.mapWith(
1181
+ juniorConversations.lastActivityAt
1182
+ )
1183
+ };
1184
+ }
1185
+ function conversationActiveDaysColumn() {
1186
+ return sql`COUNT(DISTINCT (${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC')::date)::integer`;
1187
+ }
1188
+
1189
+ // src/api/conversations/stats.query.ts
1190
+ var WINDOW_MS = 90 * 24 * 60 * 60 * 1e3;
1191
+ function emptyStatsItem(label) {
1192
+ return {
1193
+ active: 0,
1194
+ conversations: 0,
1195
+ durationMs: 0,
1196
+ failed: 0,
1197
+ label
1198
+ };
1199
+ }
1200
+ function addUsd(current, next) {
1201
+ return Math.round(((current ?? 0) + next) * 1e12) / 1e12;
1202
+ }
1203
+ function actorLabel(row) {
1204
+ return row.userEmail?.trim() || row.identityEmail?.trim() || row.userDisplayName?.trim() || row.identityDisplayName?.trim() || row.identityHandle?.trim() || row.identitySubjectId?.trim() || "Unknown";
1205
+ }
1206
+ function surfaceLabel(source) {
1207
+ if (source === "scheduler") return "Scheduler";
1208
+ if (source === "api") return "API";
1209
+ if (source === "internal" || source === "local") return "Internal";
1210
+ return "Conversation";
1211
+ }
1212
+ function locationLabel(row) {
1213
+ if (row.destinationProvider !== "slack") {
1214
+ return surfaceLabel(row.source);
1215
+ }
1216
+ if (row.destinationKind === "dm") {
1217
+ return "Direct Message";
1218
+ }
1219
+ if (row.destinationVisibility !== "public") {
1220
+ return "Private Conversation";
1221
+ }
1222
+ const name = (row.channelName ?? row.destinationDisplayName)?.trim().replace(/^#/, "");
1223
+ return name ? `#${name}` : "Public Channel";
1224
+ }
1225
+ function addAggregate(map, label, row) {
1226
+ const item = map.get(label) ?? emptyStatsItem(label);
1227
+ item.active += row.active;
1228
+ item.conversations += row.conversations;
1229
+ item.durationMs += row.durationMs;
1230
+ item.failed += row.failed;
1231
+ if (row.tokens !== null) {
1232
+ item.tokens = (item.tokens ?? 0) + row.tokens;
1233
+ }
1234
+ if (row.costUsd !== null) {
1235
+ item.costUsd = addUsd(item.costUsd, row.costUsd);
1236
+ }
1237
+ map.set(label, item);
1238
+ }
1239
+ function statsItems(map) {
1240
+ return [...map.values()].sort(
1241
+ (left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
1242
+ );
1243
+ }
1244
+ function statsWhere(start, end) {
1245
+ return and3(
1246
+ isNull2(juniorConversations.parentConversationId),
1247
+ gte(juniorConversations.lastActivityAt, start),
1248
+ lte(juniorConversations.lastActivityAt, end)
1249
+ );
1250
+ }
1251
+ async function aggregateStats(db, start, end) {
1252
+ const where = statsWhere(start, end);
1253
+ const [totalsRows, actorRows, locationRows] = await Promise.all([
1254
+ db.select(conversationAggregateColumns()).from(juniorConversations).where(where),
1255
+ db.select({
1256
+ identityDisplayName: juniorIdentities.displayName,
1257
+ identityEmail: juniorIdentities.emailNormalized,
1258
+ identityHandle: juniorIdentities.handle,
1259
+ identitySubjectId: juniorIdentities.providerSubjectId,
1260
+ userDisplayName: juniorUsers.displayName,
1261
+ userEmail: juniorUsers.primaryEmailNormalized,
1262
+ ...conversationAggregateColumns()
1263
+ }).from(juniorConversations).leftJoin(
1264
+ juniorIdentities,
1265
+ eq3(juniorIdentities.id, juniorConversations.actorIdentityId)
1266
+ ).leftJoin(juniorUsers, eq3(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
1267
+ juniorIdentities.displayName,
1268
+ juniorIdentities.emailNormalized,
1269
+ juniorIdentities.handle,
1270
+ juniorIdentities.providerSubjectId,
1271
+ juniorUsers.displayName,
1272
+ juniorUsers.primaryEmailNormalized
1273
+ ),
1274
+ db.select({
1275
+ channelName: juniorConversations.channelName,
1276
+ destinationDisplayName: juniorDestinations.displayName,
1277
+ destinationKind: juniorDestinations.kind,
1278
+ destinationProvider: juniorDestinations.provider,
1279
+ destinationVisibility: juniorDestinations.visibility,
1280
+ source: juniorConversations.source,
1281
+ ...conversationAggregateColumns()
1282
+ }).from(juniorConversations).leftJoin(
1283
+ juniorDestinations,
1284
+ eq3(juniorDestinations.id, juniorConversations.destinationId)
1285
+ ).where(where).groupBy(
1286
+ juniorConversations.channelName,
1287
+ juniorConversations.source,
1288
+ juniorDestinations.displayName,
1289
+ juniorDestinations.kind,
1290
+ juniorDestinations.provider,
1291
+ juniorDestinations.visibility
1292
+ )
1293
+ ]);
1294
+ return { actorRows, locationRows, totals: totalsRows[0] };
1295
+ }
1296
+ async function readConversationStatsFromSql() {
1297
+ const nowMs = Date.now();
1298
+ const windowStartMs = nowMs - WINDOW_MS;
1299
+ const { actorRows, locationRows, totals } = await aggregateStats(
1300
+ getDb(),
1301
+ new Date(windowStartMs),
1302
+ new Date(nowMs)
1303
+ );
1304
+ const actors = /* @__PURE__ */ new Map();
1305
+ const locations = /* @__PURE__ */ new Map();
1306
+ for (const row of actorRows) {
1307
+ addAggregate(actors, actorLabel(row), row);
1308
+ }
1309
+ for (const row of locationRows) {
1310
+ addAggregate(locations, locationLabel(row), row);
1311
+ }
1312
+ return {
1313
+ active: totals?.active ?? 0,
1314
+ conversations: totals?.conversations ?? 0,
1315
+ durationMs: totals?.durationMs ?? 0,
1316
+ failed: totals?.failed ?? 0,
1317
+ generatedAt: new Date(nowMs).toISOString(),
1318
+ locations: statsItems(locations),
1319
+ actors: statsItems(actors),
1320
+ source: "conversation_index",
1321
+ ...totals?.costUsd !== null && totals?.costUsd !== void 0 ? { costUsd: addUsd(void 0, totals.costUsd) } : {},
1322
+ ...totals?.tokens !== null && totals?.tokens !== void 0 ? { tokens: totals.tokens } : {},
1323
+ windowEnd: new Date(nowMs).toISOString(),
1324
+ windowStart: new Date(windowStartMs).toISOString()
1325
+ };
1326
+ }
1327
+
1328
+ // src/api/conversations/stats.ts
1329
+ async function readConversationStats() {
1330
+ return conversationStatsReportSchema.parse(
1331
+ await readConversationStatsFromSql()
1332
+ );
1333
+ }
1334
+ var stats_default = {
1335
+ method: "get",
1336
+ path: "/stats",
1337
+ handler: async () => Response.json(await readConversationStats())
1338
+ };
1339
+
1340
+ // src/api/conversations/subagent.ts
1341
+ async function readConversationSubagent(conversationId, subagentId) {
1342
+ const record = await readConversationRecordFromSql(conversationId);
1343
+ if (!record) {
1344
+ return conversationSubagentTranscriptReportSchema.parse({
1345
+ type: "subagent",
1346
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
1347
+ id: subagentId,
1348
+ status: "error",
1349
+ subagentKind: "unknown",
1350
+ transcript: [],
1351
+ transcriptAvailable: false,
1352
+ unavailableReason: "not_found"
1353
+ });
1354
+ }
1355
+ return conversationSubagentTranscriptReportSchema.parse(
1356
+ await buildConversationSubagent(record.conversation, subagentId)
1357
+ );
1358
+ }
1359
+ var subagent_default = {
1360
+ method: "get",
1361
+ path: "/:conversationId/subagents/:subagentId",
1362
+ handler: async (c) => {
1363
+ const { conversationId, subagentId } = parseParams(
1364
+ subagentParamsSchema,
1365
+ c.req.param()
1366
+ );
1367
+ const report = await readConversationSubagent(conversationId, subagentId);
1368
+ return report.unavailableReason === "not_found" ? Response.json(report, { status: 404 }) : Response.json(report);
1369
+ }
1370
+ };
1371
+
1372
+ // src/api/conversations/routes.ts
1373
+ var routes = [
1374
+ list_default,
1375
+ stats_default,
1376
+ archive_default,
1377
+ detail_default,
1378
+ subagent_default
1379
+ ];
1380
+ function createConversationRoutes() {
1381
+ const app = new Hono();
1382
+ for (const route of routes) app.on(route.method, route.path, route.handler);
1383
+ return app;
1384
+ }
1385
+
1386
+ // src/api/locations/routes.ts
1387
+ import { Hono as Hono2 } from "hono";
1388
+
1389
+ // src/api/locations/query.ts
1390
+ import { and as and4, asc as asc2, desc as desc2, eq as eq4, gte as gte2, isNull as isNull3, sql as sql2 } from "drizzle-orm";
1391
+
1392
+ // src/api/conversations/reporting.ts
1393
+ var PRIVATE_CONVERSATION_LABEL2 = "Private Conversation";
1394
+ function channelFromConversationId(conversationId) {
1395
+ const [provider, channel] = conversationId.split(":");
1396
+ return provider === "slack" && channel ? channel : void 0;
1397
+ }
1398
+ function surfaceFromRow(row) {
1399
+ if (row.source === "api" || row.source === "scheduler" || row.source === "slack") {
1400
+ return row.source;
1401
+ }
1402
+ if (row.conversationId.startsWith("slack:")) return "slack";
1403
+ if (row.conversationId.startsWith("scheduler:")) return "scheduler";
1404
+ if (row.conversationId.startsWith("api:")) return "api";
1405
+ return "internal";
1406
+ }
1407
+ function statusFromRow(row) {
1408
+ if (row.executionStatus === "failed") return "failed";
1409
+ if (row.executionStatus === "idle") return "completed";
1410
+ return "active";
1411
+ }
1412
+ function surfaceLabel2(surface) {
1413
+ if (surface === "scheduler") return "Scheduler";
1414
+ if (surface === "api") return "API";
1415
+ if (surface === "internal") return "Internal";
1416
+ return "Conversation";
1417
+ }
1418
+ function slackLocationLabel(args) {
1419
+ const channelId = args.channel;
1420
+ if (!channelId) return void 0;
1421
+ if (args.channelNameRedacted && args.channelName) return args.channelName;
1422
+ const name = args.channelName?.replace(/^#/, "");
1423
+ if (channelId.startsWith("D")) return "Direct Message";
1424
+ if (channelId.startsWith("C")) {
1425
+ return name ? `#${name}` : "Public Channel";
1426
+ }
1427
+ if (channelId.startsWith("G")) {
1428
+ if (name?.startsWith("mpdm-")) return "Group DM";
1429
+ return "Private Channel";
1430
+ }
1431
+ return name || channelId;
1432
+ }
1433
+ function channelNameFromRow(row) {
1434
+ if (row.destinationVisibility !== "public") {
1435
+ return PRIVATE_CONVERSATION_LABEL2;
1436
+ }
1437
+ return row.channelName ?? void 0;
1438
+ }
1439
+ function titleFromRow(row, surface) {
1440
+ if (row.destinationVisibility !== "public") {
1441
+ return PRIVATE_CONVERSATION_LABEL2;
1442
+ }
1443
+ const channel = channelFromConversationId(row.conversationId);
1444
+ return row.title ?? slackLocationLabel({
1445
+ channel,
1446
+ channelName: row.channelName ?? void 0
1447
+ }) ?? surfaceLabel2(surface);
1448
+ }
1449
+ function actorFromRow(row) {
1450
+ const actor = {
1451
+ ...row.email ? { email: row.email } : {},
1452
+ ...row.fullName ? { fullName: row.fullName } : {},
1453
+ ...row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
1454
+ ...row.handle ? { slackUserName: row.handle } : {}
1455
+ };
1456
+ return Object.keys(actor).length ? actor : void 0;
1457
+ }
1458
+ function summaryFromRow(row) {
1459
+ const surface = surfaceFromRow(row);
1460
+ const channel = channelFromConversationId(row.conversationId);
1461
+ const channelName = channelNameFromRow(row);
1462
+ const channelNameRedacted = row.destinationVisibility !== "public";
1463
+ const actorIdentity = actorFromRow(row);
1464
+ return {
1465
+ conversationId: row.conversationId,
1466
+ cumulativeDurationMs: row.durationMs,
1467
+ displayTitle: titleFromRow(row, surface),
1468
+ lastProgressAt: new Date(
1469
+ row.executionUpdatedAt ?? row.updatedAt
1470
+ ).toISOString(),
1471
+ lastSeenAt: row.lastActivityAt.toISOString(),
1472
+ startedAt: row.createdAt.toISOString(),
1473
+ status: statusFromRow(row),
1474
+ surface,
1475
+ ...actorIdentity ? { actorIdentity } : {},
1476
+ ...channel ? { channel } : {},
1477
+ ...channelName ? { channelName } : {},
1478
+ ...channelNameRedacted ? { channelNameRedacted: true } : {},
1479
+ ...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {}
1480
+ };
1481
+ }
1482
+
1483
+ // src/api/locations/query.ts
1484
+ var RECENT_LIMIT = 25;
1485
+ var ACTIVITY_DAYS = 90;
1486
+ function emptyMetrics() {
1487
+ return {
1488
+ active: 0,
1489
+ conversations: 0,
1490
+ durationMs: 0,
1491
+ failed: 0
1492
+ };
1493
+ }
1494
+ function addMetrics(target, row) {
1495
+ target.active += row.active;
1496
+ target.conversations += row.conversations;
1497
+ target.durationMs += row.durationMs;
1498
+ target.failed += row.failed;
1499
+ if (row.tokens !== null) {
1500
+ target.tokens = (target.tokens ?? 0) + row.tokens;
1501
+ }
1502
+ if (row.costUsd !== null) {
1503
+ target.costUsd = Math.round(((target.costUsd ?? 0) + row.costUsd) * 1e12) / 1e12;
1504
+ }
1505
+ }
1506
+ function publicLabel(input) {
1507
+ const name = input.displayName?.trim().replace(/^#/, "");
1508
+ return name ? `#${name}` : `Public channel ${input.providerDestinationId}`;
1509
+ }
1510
+ function actorLabel2(actor) {
1511
+ return actor.email?.trim() || actor.fullName?.trim() || actor.slackUserName?.trim() || actor.slackUserId?.trim() || "Unknown";
1512
+ }
1513
+ function emptyActor(actor) {
1514
+ return {
1515
+ ...emptyMetrics(),
1516
+ actor,
1517
+ label: actorLabel2(actor)
1518
+ };
1519
+ }
1520
+ function topLevelWhere() {
1521
+ return isNull3(juniorConversations.parentConversationId);
1522
+ }
1523
+ function publicLocationWhere(destinationId) {
1524
+ return and4(
1525
+ topLevelWhere(),
1526
+ eq4(juniorDestinations.id, destinationId),
1527
+ eq4(juniorDestinations.visibility, "public")
1528
+ );
1529
+ }
1530
+ function locationColumns() {
1531
+ return {
1532
+ destinationDisplayName: juniorDestinations.displayName,
1533
+ destinationId: juniorDestinations.id,
1534
+ destinationKind: juniorDestinations.kind,
1535
+ destinationProvider: juniorDestinations.provider,
1536
+ destinationProviderId: juniorDestinations.providerDestinationId,
1537
+ destinationVisibility: juniorDestinations.visibility
1538
+ };
1539
+ }
1540
+ function locationGroupBy() {
1541
+ return [
1542
+ juniorDestinations.displayName,
1543
+ juniorDestinations.id,
1544
+ juniorDestinations.kind,
1545
+ juniorDestinations.provider,
1546
+ juniorDestinations.providerDestinationId,
1547
+ juniorDestinations.visibility
1548
+ ];
1549
+ }
1550
+ function locationFromAggregate(row) {
1551
+ if (row.destinationVisibility !== "public" || !row.destinationId || !row.destinationProvider || !row.destinationProviderId || !row.destinationKind) {
1552
+ return void 0;
1553
+ }
1554
+ const location = {
1555
+ ...emptyMetrics(),
1556
+ firstSeenAt: row.firstSeenAt.toISOString(),
1557
+ id: row.destinationId,
1558
+ kind: row.destinationKind,
1559
+ label: publicLabel({
1560
+ displayName: row.destinationDisplayName,
1561
+ providerDestinationId: row.destinationProviderId
1562
+ }),
1563
+ lastSeenAt: row.lastSeenAt.toISOString(),
1564
+ provider: row.destinationProvider,
1565
+ providerDestinationId: row.destinationProviderId,
1566
+ visibility: "public"
1567
+ };
1568
+ addMetrics(location, row);
1569
+ return location;
1570
+ }
1571
+ async function directoryRows(db) {
1572
+ return db.select({
1573
+ ...locationColumns(),
1574
+ ...conversationAggregateColumns(),
1575
+ ...conversationRangeColumns()
1576
+ }).from(juniorConversations).leftJoin(
1577
+ juniorDestinations,
1578
+ eq4(juniorDestinations.id, juniorConversations.destinationId)
1579
+ ).where(topLevelWhere()).groupBy(...locationGroupBy());
1580
+ }
1581
+ async function directoryActivityRows(db, start) {
1582
+ const date = sql2`TO_CHAR(
1583
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
1584
+ 'YYYY-MM-DD'
1585
+ )`;
1586
+ return db.select({
1587
+ conversations: sql2`COUNT(*)::integer`,
1588
+ date,
1589
+ visibility: juniorDestinations.visibility
1590
+ }).from(juniorConversations).leftJoin(
1591
+ juniorDestinations,
1592
+ eq4(juniorDestinations.id, juniorConversations.destinationId)
1593
+ ).where(and4(topLevelWhere(), gte2(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
1594
+ }
1595
+ function directoryActivityDays(rows, nowMs) {
1596
+ const days = /* @__PURE__ */ new Map();
1597
+ for (const row of rows) {
1598
+ const day = days.get(row.date) ?? {
1599
+ date: row.date,
1600
+ privateConversations: 0,
1601
+ publicConversations: 0
1602
+ };
1603
+ if (row.visibility === "public") {
1604
+ day.publicConversations += row.conversations;
1605
+ } else {
1606
+ day.privateConversations += row.conversations;
1607
+ }
1608
+ days.set(row.date, day);
1609
+ }
1610
+ const end = new Date(nowMs);
1611
+ end.setUTCHours(0, 0, 0, 0);
1612
+ const start = new Date(end);
1613
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
1614
+ const activity = [];
1615
+ for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
1616
+ const date = cursor.toISOString().slice(0, 10);
1617
+ activity.push(
1618
+ days.get(date) ?? {
1619
+ date,
1620
+ privateConversations: 0,
1621
+ publicConversations: 0
1622
+ }
1623
+ );
1624
+ }
1625
+ return activity;
1626
+ }
1627
+ async function readLocationDirectoryFromSql() {
1628
+ const nowMs = Date.now();
1629
+ const end = new Date(nowMs);
1630
+ end.setUTCHours(0, 0, 0, 0);
1631
+ const start = new Date(end);
1632
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
1633
+ const [rows, activityRows] = await Promise.all([
1634
+ directoryRows(getDb()),
1635
+ directoryActivityRows(getDb(), start)
1636
+ ]);
1637
+ const locations = [];
1638
+ const privateActivity = {
1639
+ ...emptyMetrics(),
1640
+ label: "Private activity"
1641
+ };
1642
+ for (const row of rows) {
1643
+ const location = locationFromAggregate(row);
1644
+ if (location) locations.push(location);
1645
+ else addMetrics(privateActivity, row);
1646
+ }
1647
+ return {
1648
+ activityDays: directoryActivityDays(activityRows, nowMs),
1649
+ generatedAt: new Date(nowMs).toISOString(),
1650
+ locations: locations.sort(
1651
+ (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.label.localeCompare(right.label)
1652
+ ),
1653
+ privateActivity,
1654
+ source: "conversation_index",
1655
+ windowEnd: end.toISOString(),
1656
+ windowStart: start.toISOString()
1657
+ };
1658
+ }
1659
+ async function recentLocationRows(db, locationId) {
1660
+ return db.select({
1661
+ channelName: juniorConversations.channelName,
1662
+ conversationId: juniorConversations.conversationId,
1663
+ createdAt: juniorConversations.createdAt,
1664
+ destinationId: juniorDestinations.id,
1665
+ destinationVisibility: juniorDestinations.visibility,
1666
+ durationMs: juniorConversations.durationMs,
1667
+ email: sql2`COALESCE(
1668
+ ${juniorUsers.primaryEmailNormalized},
1669
+ ${juniorIdentities.email}
1670
+ )`,
1671
+ executionStatus: juniorConversations.executionStatus,
1672
+ executionUpdatedAt: juniorConversations.executionUpdatedAt,
1673
+ fullName: juniorUsers.displayName,
1674
+ handle: juniorIdentities.handle,
1675
+ lastActivityAt: juniorConversations.lastActivityAt,
1676
+ providerSubjectId: sql2`CASE
1677
+ WHEN ${juniorIdentities.provider} = 'slack'
1678
+ THEN ${juniorIdentities.providerSubjectId}
1679
+ ELSE NULL
1680
+ END`,
1681
+ source: juniorConversations.source,
1682
+ title: juniorConversations.title,
1683
+ updatedAt: juniorConversations.updatedAt,
1684
+ usage: juniorConversations.usage
1685
+ }).from(juniorConversations).innerJoin(
1686
+ juniorDestinations,
1687
+ eq4(juniorDestinations.id, juniorConversations.destinationId)
1688
+ ).leftJoin(
1689
+ juniorIdentities,
1690
+ eq4(juniorIdentities.id, juniorConversations.actorIdentityId)
1691
+ ).leftJoin(juniorUsers, eq4(juniorUsers.id, juniorIdentities.userId)).where(
1692
+ and4(
1693
+ publicLocationWhere(locationId),
1694
+ isNull3(juniorConversations.archivedAt)
1695
+ )
1696
+ ).orderBy(
1697
+ desc2(juniorConversations.lastActivityAt),
1698
+ asc2(juniorConversations.conversationId)
1699
+ ).limit(RECENT_LIMIT);
1700
+ }
1701
+ async function readLocationDetailFromSql(locationId) {
1702
+ const nowMs = Date.now();
1703
+ const end = new Date(nowMs);
1704
+ end.setUTCHours(0, 0, 0, 0);
1705
+ const start = new Date(end);
1706
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
1707
+ const where = publicLocationWhere(locationId);
1708
+ const activityDate = sql2`TO_CHAR(
1709
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
1710
+ 'YYYY-MM-DD'
1711
+ )`;
1712
+ const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
1713
+ getDb().select({
1714
+ ...locationColumns(),
1715
+ ...conversationAggregateColumns(),
1716
+ ...conversationRangeColumns()
1717
+ }).from(juniorConversations).innerJoin(
1718
+ juniorDestinations,
1719
+ eq4(juniorDestinations.id, juniorConversations.destinationId)
1720
+ ).where(where).groupBy(...locationGroupBy()),
1721
+ getDb().select({ date: activityDate, ...conversationAggregateColumns() }).from(juniorConversations).innerJoin(
1722
+ juniorDestinations,
1723
+ eq4(juniorDestinations.id, juniorConversations.destinationId)
1724
+ ).where(and4(where, gte2(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
1725
+ getDb().select({
1726
+ actorIdentityId: juniorConversations.actorIdentityId,
1727
+ email: juniorUsers.primaryEmailNormalized,
1728
+ fullName: juniorUsers.displayName,
1729
+ handle: juniorIdentities.handle,
1730
+ identityEmail: juniorIdentities.email,
1731
+ identityProvider: juniorIdentities.provider,
1732
+ providerSubjectId: juniorIdentities.providerSubjectId,
1733
+ ...conversationAggregateColumns()
1734
+ }).from(juniorConversations).innerJoin(
1735
+ juniorDestinations,
1736
+ eq4(juniorDestinations.id, juniorConversations.destinationId)
1737
+ ).leftJoin(
1738
+ juniorIdentities,
1739
+ eq4(juniorIdentities.id, juniorConversations.actorIdentityId)
1740
+ ).leftJoin(juniorUsers, eq4(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
1741
+ juniorConversations.actorIdentityId,
1742
+ juniorUsers.primaryEmailNormalized,
1743
+ juniorUsers.displayName,
1744
+ juniorIdentities.handle,
1745
+ juniorIdentities.email,
1746
+ juniorIdentities.provider,
1747
+ juniorIdentities.providerSubjectId
1748
+ ),
1749
+ recentLocationRows(getDb(), locationId)
1750
+ ]);
1751
+ const locationRow = locationRows[0];
1752
+ const location = locationRow ? locationFromAggregate(locationRow) : void 0;
1753
+ if (!location) return void 0;
1754
+ const days = /* @__PURE__ */ new Map();
1755
+ for (const row of dayRows) {
1756
+ days.set(row.date, {
1757
+ active: row.active,
1758
+ conversations: row.conversations,
1759
+ date: row.date,
1760
+ durationMs: row.durationMs,
1761
+ failed: row.failed,
1762
+ ...row.tokens !== null ? { tokens: row.tokens } : {}
1763
+ });
1764
+ }
1765
+ const actors = [];
1766
+ for (const row of actorRows) {
1767
+ if (!row.actorIdentityId) continue;
1768
+ const actor = {
1769
+ ...row.email ?? row.identityEmail ? { email: row.email ?? row.identityEmail ?? void 0 } : {},
1770
+ ...row.fullName ? { fullName: row.fullName } : {},
1771
+ ...row.identityProvider === "slack" && row.providerSubjectId ? { slackUserId: row.providerSubjectId } : {},
1772
+ ...row.handle ? { slackUserName: row.handle } : {}
1773
+ };
1774
+ if (!actor.email && !actor.slackUserId) continue;
1775
+ const item = emptyActor(actor);
1776
+ addMetrics(item, row);
1777
+ actors.push(item);
1778
+ }
1779
+ const activity = activityDays(days, nowMs, ACTIVITY_DAYS);
1780
+ return {
1781
+ ...location,
1782
+ activityDays: activity,
1783
+ actors: actors.sort(
1784
+ (left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
1785
+ ),
1786
+ generatedAt: new Date(nowMs).toISOString(),
1787
+ recentConversations: recentRows.map(summaryFromRow),
1788
+ source: "conversation_index",
1789
+ windowEnd: end.toISOString(),
1790
+ windowStart: activity[0] ? `${activity[0].date}T00:00:00.000Z` : end.toISOString()
1791
+ };
1792
+ }
1793
+
1794
+ // src/api/locations/detail.ts
1795
+ async function readLocationDetail(locationId) {
1796
+ const report = await readLocationDetailFromSql(locationId);
1797
+ return report ? locationDetailReportSchema.parse(report) : void 0;
1798
+ }
1799
+ var detail_default2 = {
1800
+ method: "get",
1801
+ path: "/:locationId",
1802
+ handler: async (c) => {
1803
+ const { locationId } = parseParams(locationParamsSchema, c.req.param());
1804
+ const report = await readLocationDetail(locationId);
1805
+ return report ? Response.json(report) : Response.json({ error: "Location not found." }, { status: 404 });
1806
+ }
1807
+ };
1808
+
1809
+ // src/api/locations/list.ts
1810
+ async function readLocationDirectory() {
1811
+ return locationDirectoryReportSchema.parse(
1812
+ await readLocationDirectoryFromSql()
1813
+ );
1814
+ }
1815
+ var list_default2 = {
1816
+ method: "get",
1817
+ path: "/",
1818
+ handler: async () => Response.json(await readLocationDirectory())
1819
+ };
1820
+
1821
+ // src/api/locations/routes.ts
1822
+ var routes2 = [list_default2, detail_default2];
1823
+ function createLocationRoutes() {
1824
+ const app = new Hono2();
1825
+ for (const route of routes2) app.on(route.method, route.path, route.handler);
1826
+ return app;
1827
+ }
1828
+
1829
+ // src/api/people/routes.ts
1830
+ import { Hono as Hono3 } from "hono";
1831
+
1832
+ // src/api/people/list.query.ts
1833
+ import { and as and6, eq as eq6, gte as gte3, sql as sql4 } from "drizzle-orm";
1834
+
1835
+ // src/api/people/shared.ts
1836
+ import { and as and5, asc as asc3, desc as desc3, eq as eq5, isNull as isNull4, sql as sql3 } from "drizzle-orm";
1837
+ var RECENT_LIMIT2 = 25;
1838
+ var ACTIVITY_DAYS2 = 365;
1839
+ function normalizeEmail(email) {
1840
+ const normalized = email?.trim().toLowerCase();
1841
+ return normalized || void 0;
1842
+ }
1843
+ function emptyTotals() {
1844
+ return {
1845
+ active: 0,
1846
+ activeDays: 0,
1847
+ conversations: 0,
1848
+ durationMs: 0,
1849
+ failed: 0
1850
+ };
1851
+ }
1852
+ function emptyActivityDay(date) {
1853
+ return {
1854
+ active: 0,
1855
+ conversations: 0,
1856
+ date,
1857
+ durationMs: 0,
1858
+ failed: 0
1859
+ };
1860
+ }
1861
+ function activityDays2(days, nowMs) {
1862
+ const items = [];
1863
+ const end = new Date(nowMs);
1864
+ end.setUTCHours(0, 0, 0, 0);
1865
+ const start = new Date(end);
1866
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
1867
+ for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
1868
+ const date = cursor.toISOString().slice(0, 10);
1869
+ items.push(days.get(date) ?? emptyActivityDay(date));
1870
+ }
1871
+ return items;
1872
+ }
1873
+ function statsItems2(map) {
1874
+ return [...map.values()].sort(
1875
+ (left, right) => right.conversations - left.conversations || right.durationMs - left.durationMs || left.label.localeCompare(right.label)
1876
+ );
1877
+ }
1878
+ function verifiedActorWhere(email) {
1879
+ const normalizedEmail = normalizeEmail(email);
1880
+ return and5(
1881
+ eq5(juniorIdentities.provider, "slack"),
1882
+ eq5(juniorIdentities.emailVerified, true),
1883
+ sql3`${juniorUsers.primaryEmailNormalized} IS NOT NULL`,
1884
+ normalizedEmail ? eq5(juniorUsers.primaryEmailNormalized, normalizedEmail) : void 0
1885
+ );
1886
+ }
1887
+ async function recentActorRows(email) {
1888
+ return getDb().select({
1889
+ channelName: juniorConversations.channelName,
1890
+ conversationId: juniorConversations.conversationId,
1891
+ createdAt: juniorConversations.createdAt,
1892
+ destinationId: juniorDestinations.id,
1893
+ destinationVisibility: juniorDestinations.visibility,
1894
+ durationMs: juniorConversations.durationMs,
1895
+ email: juniorUsers.primaryEmailNormalized,
1896
+ executionStatus: juniorConversations.executionStatus,
1897
+ executionUpdatedAt: juniorConversations.executionUpdatedAt,
1898
+ fullName: juniorUsers.displayName,
1899
+ handle: juniorIdentities.handle,
1900
+ lastActivityAt: juniorConversations.lastActivityAt,
1901
+ providerSubjectId: juniorIdentities.providerSubjectId,
1902
+ source: juniorConversations.source,
1903
+ title: juniorConversations.title,
1904
+ updatedAt: juniorConversations.updatedAt,
1905
+ usage: juniorConversations.usage
1906
+ }).from(juniorConversations).innerJoin(
1907
+ juniorIdentities,
1908
+ eq5(juniorIdentities.id, juniorConversations.actorIdentityId)
1909
+ ).innerJoin(juniorUsers, eq5(juniorUsers.id, juniorIdentities.userId)).leftJoin(
1910
+ juniorDestinations,
1911
+ eq5(juniorDestinations.id, juniorConversations.destinationId)
1912
+ ).where(
1913
+ and5(verifiedActorWhere(email), isNull4(juniorConversations.archivedAt))
1914
+ ).orderBy(
1915
+ desc3(juniorConversations.lastActivityAt),
1916
+ asc3(juniorConversations.conversationId)
1917
+ ).limit(RECENT_LIMIT2);
1918
+ }
1919
+
1920
+ // src/api/people/list.query.ts
1921
+ var DIRECTORY_ACTIVITY_DAYS = 90;
1922
+ function activityWindow(nowMs) {
1923
+ const end = new Date(nowMs);
1924
+ end.setUTCHours(0, 0, 0, 0);
1925
+ const start = new Date(end);
1926
+ start.setUTCDate(start.getUTCDate() - (DIRECTORY_ACTIVITY_DAYS - 1));
1927
+ return { end, start };
1928
+ }
1929
+ function directoryActivityDays2(rows, nowMs) {
1930
+ const days = new Map(rows.map((row) => [row.date, row]));
1931
+ const { end, start } = activityWindow(nowMs);
1932
+ const items = [];
1933
+ for (const cursor = new Date(start); cursor.getTime() <= end.getTime(); cursor.setUTCDate(cursor.getUTCDate() + 1)) {
1934
+ const date = cursor.toISOString().slice(0, 10);
1935
+ items.push(days.get(date) ?? { activePeople: 0, conversations: 0, date });
1936
+ }
1937
+ return items;
1938
+ }
1939
+ async function readPeopleListFromSql() {
1940
+ const nowMs = Date.now();
1941
+ const { end, start } = activityWindow(nowMs);
1942
+ const activityDate = sql4`TO_CHAR(
1943
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
1944
+ 'YYYY-MM-DD'
1945
+ )`;
1946
+ const [rows, activityRows] = await Promise.all([
1947
+ getDb().select({
1948
+ email: juniorUsers.primaryEmailNormalized,
1949
+ fullName: juniorUsers.displayName,
1950
+ slackUserId: sql4`MAX(${juniorIdentities.providerSubjectId})`,
1951
+ slackUserName: sql4`MAX(${juniorIdentities.handle})`,
1952
+ activeDays: conversationActiveDaysColumn(),
1953
+ ...conversationAggregateColumns(),
1954
+ ...conversationRangeColumns()
1955
+ }).from(juniorConversations).innerJoin(
1956
+ juniorIdentities,
1957
+ eq6(juniorIdentities.id, juniorConversations.actorIdentityId)
1958
+ ).innerJoin(juniorUsers, eq6(juniorUsers.id, juniorIdentities.userId)).where(verifiedActorWhere()).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
1959
+ getDb().select({
1960
+ activePeople: sql4`COUNT(DISTINCT ${juniorUsers.id})::int`,
1961
+ conversations: sql4`COUNT(*)::int`,
1962
+ date: activityDate
1963
+ }).from(juniorConversations).innerJoin(
1964
+ juniorIdentities,
1965
+ eq6(juniorIdentities.id, juniorConversations.actorIdentityId)
1966
+ ).innerJoin(juniorUsers, eq6(juniorUsers.id, juniorIdentities.userId)).where(
1967
+ and6(
1968
+ verifiedActorWhere(),
1969
+ gte3(juniorConversations.lastActivityAt, start)
1970
+ )
1971
+ ).groupBy(activityDate)
1972
+ ]);
1973
+ const people = rows.map((row) => {
1974
+ const actor = {
1975
+ email: row.email,
1976
+ ...row.fullName ? { fullName: row.fullName } : {},
1977
+ ...row.slackUserId ? { slackUserId: row.slackUserId } : {},
1978
+ ...row.slackUserName ? { slackUserName: row.slackUserName } : {}
1979
+ };
1980
+ return {
1981
+ active: row.active,
1982
+ activeDays: row.activeDays,
1983
+ conversations: row.conversations,
1984
+ durationMs: row.durationMs,
1985
+ failed: row.failed,
1986
+ firstSeenAt: row.firstSeenAt.toISOString(),
1987
+ lastSeenAt: row.lastSeenAt.toISOString(),
1988
+ actor,
1989
+ ...row.tokens !== null ? { tokens: row.tokens } : {}
1990
+ };
1991
+ });
1992
+ return {
1993
+ activityDays: directoryActivityDays2(activityRows, nowMs),
1994
+ generatedAt: new Date(nowMs).toISOString(),
1995
+ people: people.sort(
1996
+ (left, right) => Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt) || right.conversations - left.conversations || left.actor.email.localeCompare(right.actor.email)
1997
+ ),
1998
+ source: "conversation_index",
1999
+ windowEnd: end.toISOString(),
2000
+ windowStart: start.toISOString()
2001
+ };
2002
+ }
2003
+
2004
+ // src/api/people/list.ts
2005
+ async function readPeopleList() {
2006
+ return actorDirectoryReportSchema.parse(await readPeopleListFromSql());
2007
+ }
2008
+ var list_default3 = {
2009
+ method: "get",
2010
+ path: "/",
2011
+ handler: async () => Response.json(await readPeopleList())
2012
+ };
2013
+
2014
+ // src/api/people/profile.query.ts
2015
+ import { and as and7, eq as eq7, gte as gte4, sql as sql5 } from "drizzle-orm";
2016
+ function emptyProfile(email, nowMs) {
2017
+ const end = new Date(nowMs);
2018
+ end.setUTCHours(0, 0, 0, 0);
2019
+ const start = new Date(end);
2020
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
2021
+ return {
2022
+ activityDays: activityDays2(/* @__PURE__ */ new Map(), nowMs),
2023
+ generatedAt: new Date(nowMs).toISOString(),
2024
+ locations: [],
2025
+ recentConversations: [],
2026
+ actor: { email },
2027
+ source: "conversation_index",
2028
+ surfaces: [],
2029
+ totals: emptyTotals(),
2030
+ windowEnd: end.toISOString(),
2031
+ windowStart: start.toISOString()
2032
+ };
2033
+ }
2034
+ function addAggregate2(map, label, row) {
2035
+ const item = map.get(label) ?? {
2036
+ active: 0,
2037
+ conversations: 0,
2038
+ durationMs: 0,
2039
+ failed: 0,
2040
+ label
2041
+ };
2042
+ item.active += row.active;
2043
+ item.conversations += row.conversations;
2044
+ item.durationMs += row.durationMs;
2045
+ item.failed += row.failed;
2046
+ if (row.tokens !== null) item.tokens = (item.tokens ?? 0) + row.tokens;
2047
+ map.set(label, item);
2048
+ }
2049
+ function surfaceExpression() {
2050
+ return sql5`CASE
2051
+ WHEN ${juniorConversations.source} IN ('api', 'scheduler', 'slack')
2052
+ THEN ${juniorConversations.source}
2053
+ WHEN ${juniorConversations.conversationId} LIKE 'slack:%' THEN 'slack'
2054
+ WHEN ${juniorConversations.conversationId} LIKE 'scheduler:%' THEN 'scheduler'
2055
+ WHEN ${juniorConversations.conversationId} LIKE 'api:%' THEN 'api'
2056
+ ELSE 'internal'
2057
+ END`;
2058
+ }
2059
+ function locationLabel2(row) {
2060
+ if (row.surface !== "slack")
2061
+ return surfaceLabel2(row.surface);
2062
+ if (row.destinationVisibility !== "public") return "Private Conversation";
2063
+ return slackLocationLabel({
2064
+ channel: row.channel || void 0,
2065
+ channelName: row.channelName ?? void 0
2066
+ }) ?? "Conversation";
2067
+ }
2068
+ async function readPeopleProfileFromSql(email) {
2069
+ const nowMs = Date.now();
2070
+ const normalizedEmail = normalizeEmail(email);
2071
+ if (!normalizedEmail) return emptyProfile("", nowMs);
2072
+ const end = new Date(nowMs);
2073
+ end.setUTCHours(0, 0, 0, 0);
2074
+ const start = new Date(end);
2075
+ start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
2076
+ const where = verifiedActorWhere(normalizedEmail);
2077
+ const surface = surfaceExpression();
2078
+ const activityDate = sql5`TO_CHAR(
2079
+ ${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
2080
+ 'YYYY-MM-DD'
2081
+ )`;
2082
+ const channel = sql5`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
2083
+ const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
2084
+ getDb().select({
2085
+ email: juniorUsers.primaryEmailNormalized,
2086
+ fullName: juniorUsers.displayName,
2087
+ slackUserId: sql5`MAX(${juniorIdentities.providerSubjectId})`,
2088
+ slackUserName: sql5`MAX(${juniorIdentities.handle})`,
2089
+ activeDays: conversationActiveDaysColumn(),
2090
+ ...conversationAggregateColumns()
2091
+ }).from(juniorConversations).innerJoin(
2092
+ juniorIdentities,
2093
+ eq7(juniorIdentities.id, juniorConversations.actorIdentityId)
2094
+ ).innerJoin(juniorUsers, eq7(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
2095
+ getDb().select({
2096
+ date: activityDate,
2097
+ ...conversationAggregateColumns()
2098
+ }).from(juniorConversations).innerJoin(
2099
+ juniorIdentities,
2100
+ eq7(juniorIdentities.id, juniorConversations.actorIdentityId)
2101
+ ).innerJoin(juniorUsers, eq7(juniorUsers.id, juniorIdentities.userId)).where(and7(where, gte4(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
2102
+ getDb().select({
2103
+ channel,
2104
+ channelName: juniorConversations.channelName,
2105
+ destinationVisibility: juniorDestinations.visibility,
2106
+ surface,
2107
+ ...conversationAggregateColumns()
2108
+ }).from(juniorConversations).innerJoin(
2109
+ juniorIdentities,
2110
+ eq7(juniorIdentities.id, juniorConversations.actorIdentityId)
2111
+ ).innerJoin(juniorUsers, eq7(juniorUsers.id, juniorIdentities.userId)).leftJoin(
2112
+ juniorDestinations,
2113
+ eq7(juniorDestinations.id, juniorConversations.destinationId)
2114
+ ).where(where).groupBy(
2115
+ channel,
2116
+ juniorConversations.channelName,
2117
+ juniorDestinations.visibility,
2118
+ surface
2119
+ ),
2120
+ getDb().select({ surface, ...conversationAggregateColumns() }).from(juniorConversations).innerJoin(
2121
+ juniorIdentities,
2122
+ eq7(juniorIdentities.id, juniorConversations.actorIdentityId)
2123
+ ).innerJoin(juniorUsers, eq7(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(surface),
2124
+ recentActorRows(normalizedEmail)
2125
+ ]);
2126
+ const totalsRow = totalsRows[0];
2127
+ if (!totalsRow) return emptyProfile(normalizedEmail, nowMs);
2128
+ const actor = {
2129
+ email: totalsRow.email,
2130
+ ...totalsRow.fullName ? { fullName: totalsRow.fullName } : {},
2131
+ ...totalsRow.slackUserId ? { slackUserId: totalsRow.slackUserId } : {},
2132
+ ...totalsRow.slackUserName ? { slackUserName: totalsRow.slackUserName } : {}
2133
+ };
2134
+ const days = /* @__PURE__ */ new Map();
2135
+ for (const row of dayRows) {
2136
+ days.set(row.date, {
2137
+ active: row.active,
2138
+ conversations: row.conversations,
2139
+ date: row.date,
2140
+ durationMs: row.durationMs,
2141
+ failed: row.failed,
2142
+ ...row.tokens !== null ? { tokens: row.tokens } : {}
2143
+ });
2144
+ }
2145
+ const locations = /* @__PURE__ */ new Map();
2146
+ for (const row of locationRows) {
2147
+ addAggregate2(locations, locationLabel2(row), row);
2148
+ }
2149
+ const surfaces = /* @__PURE__ */ new Map();
2150
+ for (const row of surfaceRows) {
2151
+ addAggregate2(
2152
+ surfaces,
2153
+ surfaceLabel2(row.surface),
2154
+ row
2155
+ );
2156
+ }
2157
+ return {
2158
+ activityDays: activityDays2(days, nowMs),
2159
+ generatedAt: new Date(nowMs).toISOString(),
2160
+ locations: statsItems2(locations),
2161
+ recentConversations: recentRows.map(summaryFromRow),
2162
+ actor,
2163
+ source: "conversation_index",
2164
+ surfaces: statsItems2(surfaces),
2165
+ totals: {
2166
+ active: totalsRow.active,
2167
+ activeDays: totalsRow.activeDays,
2168
+ conversations: totalsRow.conversations,
2169
+ durationMs: totalsRow.durationMs,
2170
+ failed: totalsRow.failed,
2171
+ ...totalsRow.tokens !== null ? { tokens: totalsRow.tokens } : {}
2172
+ },
2173
+ windowEnd: end.toISOString(),
2174
+ windowStart: start.toISOString()
2175
+ };
2176
+ }
2177
+
2178
+ // src/api/people/profile.ts
2179
+ async function readPeopleProfile(email) {
2180
+ return actorProfileReportSchema.parse(await readPeopleProfileFromSql(email));
2181
+ }
2182
+ var profile_default = {
2183
+ method: "get",
2184
+ path: "/:email",
2185
+ handler: async (c) => {
2186
+ const { email } = parseParams(personParamsSchema, c.req.param());
2187
+ return Response.json(await readPeopleProfile(email));
2188
+ }
2189
+ };
2190
+
2191
+ // src/api/people/routes.ts
2192
+ var routes3 = [list_default3, profile_default];
2193
+ function createPeopleRoutes() {
2194
+ const app = new Hono3();
2195
+ for (const route of routes3) app.on(route.method, route.path, route.handler);
2196
+ return app;
2197
+ }
27
2198
 
28
2199
  // src/reporting.ts
29
2200
  import { readFileSync } from "fs";
@@ -72,44 +2243,18 @@ async function readRuntimeInfoReport() {
72
2243
  });
73
2244
  }
74
2245
  async function readPluginOperationalReportFeed() {
75
- const listRecent = async (listOptions) => {
76
- const { listRecentConversationSummaries } = await import("./plugin-conversations-DTUP4RH6.js");
77
- return listRecentConversationSummaries(listOptions?.limit);
78
- };
79
2246
  const nowMs = Date.now();
80
- const { getPluginOperationalReports } = await import("./agent-hooks-NHIXBJW3.js");
2247
+ const { getPluginOperationalReports } = await import("./agent-hooks-EOFSY2PD.js");
81
2248
  return pluginOperationalReportFeedSchema.parse({
82
2249
  source: "plugins",
83
2250
  generatedAt: new Date(nowMs).toISOString(),
84
- reports: await getPluginOperationalReports(nowMs, {
85
- listRecent
86
- })
2251
+ reports: await getPluginOperationalReports(nowMs)
87
2252
  });
88
2253
  }
89
2254
 
90
2255
  // src/api.ts
91
- function parseParams(schema, params) {
92
- const result = schema.safeParse(params);
93
- if (result.success) {
94
- return result.data;
95
- }
96
- throw new HTTPException(400, {
97
- cause: result.error,
98
- message: "Invalid route parameters."
99
- });
100
- }
101
- function parseQuery(schema, query) {
102
- const result = schema.safeParse(query);
103
- if (result.success) {
104
- return result.data;
105
- }
106
- throw new HTTPException(400, {
107
- cause: result.error,
108
- message: "Invalid query parameters."
109
- });
110
- }
111
2256
  function createJuniorApi() {
112
- const app = new Hono();
2257
+ const app = new Hono4();
113
2258
  app.get("/api/health", async () => {
114
2259
  return Response.json(await readHealthReport());
115
2260
  });
@@ -125,58 +2270,9 @@ function createJuniorApi() {
125
2270
  app.get("/api/plugin-reports", async () => {
126
2271
  return Response.json(await readPluginOperationalReportFeed());
127
2272
  });
128
- app.get("/api/conversations", async (c) => {
129
- const { readConversationFeed } = await import("./list-ZOUSMSEH.js");
130
- const { actorEmail } = parseQuery(
131
- conversationFeedQuerySchema,
132
- c.req.query()
133
- );
134
- return Response.json(await readConversationFeed({ actorEmail }));
135
- });
136
- app.get("/api/conversations/stats", async () => {
137
- const { readConversationStats } = await import("./stats-NZIET4LT.js");
138
- return Response.json(await readConversationStats());
139
- });
140
- app.get("/api/conversations/:conversationId", async (c) => {
141
- const { readConversationDetail } = await import("./detail-ENVV4HIY.js");
142
- const { conversationId } = parseParams(
143
- conversationParamsSchema,
144
- c.req.param()
145
- );
146
- const report = await readConversationDetail(conversationId);
147
- return report ? Response.json(report) : Response.json({ error: "Conversation not found." }, { status: 404 });
148
- });
149
- app.get(
150
- "/api/conversations/:conversationId/subagents/:subagentId",
151
- async (c) => {
152
- const { readConversationSubagent } = await import("./subagent-DY6XKTJ5.js");
153
- const { conversationId, subagentId } = parseParams(
154
- subagentParamsSchema,
155
- c.req.param()
156
- );
157
- const report = await readConversationSubagent(conversationId, subagentId);
158
- return report.unavailableReason === "not_found" ? Response.json(report, { status: 404 }) : Response.json(report);
159
- }
160
- );
161
- app.get("/api/people", async () => {
162
- const { readPeopleList } = await import("./list-XZ4HKQ4R.js");
163
- return Response.json(await readPeopleList());
164
- });
165
- app.get("/api/people/:email", async (c) => {
166
- const { email } = parseParams(personParamsSchema, c.req.param());
167
- const { readPeopleProfile } = await import("./profile-UO2N3XXG.js");
168
- return Response.json(await readPeopleProfile(email));
169
- });
170
- app.get("/api/locations", async () => {
171
- const { readLocationDirectory } = await import("./list-WR5U6JVM.js");
172
- return Response.json(await readLocationDirectory());
173
- });
174
- app.get("/api/locations/:locationId", async (c) => {
175
- const { locationId } = parseParams(locationParamsSchema, c.req.param());
176
- const { readLocationDetail } = await import("./detail-UIOKVIET.js");
177
- const report = await readLocationDetail(locationId);
178
- return report ? Response.json(report) : Response.json({ error: "Location not found." }, { status: 404 });
179
- });
2273
+ app.route("/api/conversations", createConversationRoutes());
2274
+ app.route("/api/people", createPeopleRoutes());
2275
+ app.route("/api/locations", createLocationRoutes());
180
2276
  return app;
181
2277
  }
182
2278
  export {