@sentry/junior 0.109.0 → 0.110.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-FHHVLNPA.js → agent-hooks-ELT4ODFF.js} +12 -13
- package/dist/api/conversations/access.d.ts +9 -0
- package/dist/api/conversations/aggregate.d.ts +12 -1
- package/dist/api/conversations/archive.d.ts +3 -5
- package/dist/api/conversations/detail.d.ts +156 -7
- package/dist/api/conversations/events.d.ts +1 -1
- package/dist/api/conversations/list.d.ts +57 -6
- package/dist/api/conversations/projection.d.ts +6 -7
- package/dist/api/conversations/reporting.d.ts +10 -4
- package/dist/api/conversations/routes.d.ts +2 -1
- package/dist/api/conversations/shared.d.ts +1 -1
- package/dist/api/conversations/stats.d.ts +37 -6
- package/dist/api/conversations/stats.query.d.ts +1 -1
- package/dist/api/conversations/usage.d.ts +9 -0
- package/dist/api/http.d.ts +7 -0
- package/dist/api/locations/detail.d.ts +102 -6
- package/dist/api/locations/list.d.ts +43 -5
- package/dist/api/locations/query.d.ts +4 -2
- package/dist/api/locations/routes.d.ts +2 -1
- package/dist/api/people/list.d.ts +28 -6
- package/dist/api/people/list.query.d.ts +1 -1
- package/dist/api/people/profile.d.ts +96 -7
- package/dist/api/people/profile.query.d.ts +4 -2
- package/dist/api/people/routes.d.ts +2 -1
- package/dist/api/people/shared.d.ts +1219 -2
- package/dist/api/route.d.ts +18 -4
- package/dist/api/schema/common.d.ts +6 -0
- package/dist/api/{conversations/schema.d.ts → schema/conversation.d.ts} +31 -3
- package/dist/api/{locations/schema.d.ts → schema/location.d.ts} +5 -0
- package/dist/api/{people/schema.d.ts → schema/person.d.ts} +37 -45
- package/dist/api/schema.d.ts +8 -22
- package/dist/api/schema.js +10 -2
- package/dist/api.d.ts +5 -1
- package/dist/api.js +767 -384
- package/dist/app.js +87 -70
- package/dist/catalog-runtime-4GI22PXK.js +16 -0
- package/dist/chat/agent/resume.d.ts +11 -12
- package/dist/chat/conversations/history.d.ts +3 -0
- package/dist/chat/conversations/sql/privacy.d.ts +3 -15
- package/dist/chat/pi/messages.d.ts +2 -2
- package/dist/chat/pi/sql-model-usage.d.ts +4 -1
- package/dist/chat/services/auth-pause.d.ts +4 -0
- package/dist/chat/services/context-compaction.d.ts +1 -0
- package/dist/chat/task-execution/queue-signing.d.ts +10 -8
- package/dist/chat/task-execution/queue.d.ts +9 -7
- package/dist/chat/task-execution/slack-work.d.ts +6 -6
- package/dist/{chunk-QY636HZU.js → chunk-2T7TIEXI.js} +90 -7
- package/dist/{chunk-CEA3A3UA.js → chunk-2ZTTXTSZ.js} +24 -1
- package/dist/{chunk-FWJRXN4F.js → chunk-3S7YYHBI.js} +3 -139
- package/dist/{chunk-3SXUV423.js → chunk-3TV7OEHL.js} +1 -1
- package/dist/{chunk-MXW4GL53.js → chunk-3UBQI73J.js} +7 -7
- package/dist/{chunk-IVVGFRQQ.js → chunk-42WRQLJ6.js} +10 -38
- package/dist/{chunk-FENLIFJD.js → chunk-4QU44P7Q.js} +69 -10
- package/dist/{chunk-O3JMSZLI.js → chunk-6Y5L4BOM.js} +3 -5
- package/dist/{chunk-PLB7MRCQ.js → chunk-C4MZ3GP5.js} +1 -1
- package/dist/{chunk-72OMGPDS.js → chunk-C5XAFTHY.js} +7 -9
- package/dist/{chunk-HHVY4SMP.js → chunk-FPK7XY3F.js} +98 -92
- package/dist/{chunk-OUHNWH46.js → chunk-GK47NBDE.js} +1 -1
- package/dist/{chunk-ISMWX44P.js → chunk-IYKPU7H6.js} +2 -2
- package/dist/{chunk-TMBRFULF.js → chunk-JQFJO5WH.js} +7 -1
- package/dist/{chunk-RGX4V75M.js → chunk-L5XQPJ7T.js} +3 -3
- package/dist/{chunk-7Q6WCISF.js → chunk-MIAKQNYX.js} +43 -31
- package/dist/{chunk-IXKSQGIO.js → chunk-RHHRKSPI.js} +178 -142
- package/dist/{chunk-WHMOBKT7.js → chunk-SBOBTS27.js} +2 -2
- package/dist/{chunk-HO5NT6OE.js → chunk-W6RJZG7F.js} +4 -4
- package/dist/chunk-W75ZPAGG.js +44 -0
- package/dist/{chunk-JNAVF45E.js → chunk-WNOVRQ67.js} +2 -2
- package/dist/{chunk-TT6GMXNA.js → chunk-Y3H2DN6F.js} +2 -2
- package/dist/{chunk-MGUMR35T.js → chunk-Y5FK3CNG.js} +1 -1
- package/dist/{chunk-OCD3OZRK.js → chunk-ZKIOWEFL.js} +1 -1
- package/dist/cli/chat.js +23 -24
- package/dist/cli/check.js +8 -8
- package/dist/cli/plugins.js +15 -16
- package/dist/cli/snapshot-warmup.js +11 -11
- package/dist/cli/upgrade.js +9 -9
- package/dist/db/schema/conversations.d.ts +17 -0
- package/dist/db/schema.d.ts +17 -0
- package/dist/{db-V3NU72O2.js → db-Y6W6CWWW.js} +8 -9
- package/dist/instrumentation.js +4 -4
- package/dist/nitro.js +3 -4
- package/dist/reporting-schema.d.ts +46 -0
- package/dist/{runner-JYBCQBLK.js → runner-BBFV5GLR.js} +14 -15
- package/dist/{skills-U6MDAST7.js → skills-AR4AF32L.js} +8 -8
- package/dist/{validation-72N3JVRU.js → validation-FBIEPTC2.js} +8 -8
- package/dist/version.js +1 -1
- package/migrations/0007_thick_sugar_man.sql +23 -0
- package/migrations/meta/0007_snapshot.json +1013 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +5 -5
- package/dist/catalog-runtime-DMGJSLV2.js +0 -16
- package/dist/chunk-OB42YVAE.js +0 -16
- package/dist/chunk-SYAQIR5Z.js +0 -53
package/dist/api.js
CHANGED
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
activityDays,
|
|
3
3
|
actorDirectoryReportSchema,
|
|
4
4
|
actorProfileReportSchema,
|
|
5
|
+
apiErrorSchema,
|
|
6
|
+
archiveConversationBodySchema,
|
|
7
|
+
archiveConversationResponseSchema,
|
|
5
8
|
conversationDetailReportSchema,
|
|
6
9
|
conversationFeedQuerySchema,
|
|
7
10
|
conversationFeedSchema,
|
|
@@ -12,53 +15,51 @@ import {
|
|
|
12
15
|
locationDirectoryReportSchema,
|
|
13
16
|
locationParamsSchema,
|
|
14
17
|
personParamsSchema
|
|
15
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-FPK7XY3F.js";
|
|
16
19
|
import {
|
|
17
20
|
buildSentryConversationUrl,
|
|
18
21
|
formatSlackConversationRedactedLabel,
|
|
19
|
-
readConversationEventPrivacySnapshot,
|
|
20
22
|
readHealthReport,
|
|
21
23
|
resolveSlackConversationContextFromThreadId
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-3S7YYHBI.js";
|
|
23
25
|
import {
|
|
26
|
+
healthReportSchema,
|
|
24
27
|
pluginOperationalReportFeedSchema,
|
|
25
28
|
pluginReportsSchema,
|
|
26
29
|
runtimeInfoReportSchema,
|
|
27
30
|
skillReportsSchema
|
|
28
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-2ZTTXTSZ.js";
|
|
29
32
|
import {
|
|
30
33
|
agentTurnUsageSchema,
|
|
31
34
|
hasAgentTurnUsage
|
|
32
35
|
} from "./chunk-MU6HHZEN.js";
|
|
33
36
|
import "./chunk-PDO5BLNM.js";
|
|
34
|
-
import "./chunk-
|
|
37
|
+
import "./chunk-L5XQPJ7T.js";
|
|
35
38
|
import {
|
|
36
39
|
decodeStoredConversationEvent,
|
|
37
40
|
getDb,
|
|
38
41
|
getSqlExecutor
|
|
39
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-4QU44P7Q.js";
|
|
40
43
|
import {
|
|
41
44
|
juniorConversationEvents,
|
|
42
45
|
juniorConversations,
|
|
43
46
|
juniorIdentities,
|
|
44
47
|
juniorUsers
|
|
45
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-JQFJO5WH.js";
|
|
46
49
|
import "./chunk-4ZNGQH7C.js";
|
|
47
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-WNOVRQ67.js";
|
|
48
51
|
import {
|
|
49
52
|
juniorDestinations
|
|
50
53
|
} from "./chunk-VFUK3X5B.js";
|
|
51
54
|
import {
|
|
52
55
|
canExposeConversationPayload,
|
|
53
|
-
parseSlackThreadId
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
import "./chunk-
|
|
57
|
-
import "./chunk-ISMWX44P.js";
|
|
58
|
-
import "./chunk-OB42YVAE.js";
|
|
56
|
+
parseSlackThreadId
|
|
57
|
+
} from "./chunk-6Y5L4BOM.js";
|
|
58
|
+
import "./chunk-IYKPU7H6.js";
|
|
59
|
+
import "./chunk-42WRQLJ6.js";
|
|
59
60
|
import "./chunk-6GWA276C.js";
|
|
60
|
-
import "./chunk-
|
|
61
|
-
import "./chunk-
|
|
61
|
+
import "./chunk-W75ZPAGG.js";
|
|
62
|
+
import "./chunk-ZKIOWEFL.js";
|
|
62
63
|
import "./chunk-MLKGABMK.js";
|
|
63
64
|
|
|
64
65
|
// src/api.ts
|
|
@@ -67,32 +68,50 @@ import { Hono as Hono4 } from "hono";
|
|
|
67
68
|
// src/api/conversations/routes.ts
|
|
68
69
|
import { Hono } from "hono";
|
|
69
70
|
|
|
70
|
-
// src/api/conversations/archive.ts
|
|
71
|
-
import { and, eq, isNotNull } from "drizzle-orm";
|
|
72
|
-
import { HTTPException as HTTPException2 } from "hono/http-exception";
|
|
73
|
-
import { z } from "zod";
|
|
74
|
-
|
|
75
71
|
// src/api/http.ts
|
|
76
72
|
import { HTTPException } from "hono/http-exception";
|
|
73
|
+
function jsonResponse(schema, value, init) {
|
|
74
|
+
return Response.json(schema.parse(value), init);
|
|
75
|
+
}
|
|
76
|
+
function throwApiError(status, message2, cause) {
|
|
77
|
+
throw new HTTPException(status, {
|
|
78
|
+
...cause === void 0 ? {} : { cause },
|
|
79
|
+
message: message2,
|
|
80
|
+
res: jsonResponse(apiErrorSchema, { error: message2 }, { status })
|
|
81
|
+
});
|
|
82
|
+
}
|
|
77
83
|
function parseParams(schema, params) {
|
|
78
84
|
const result = schema.safeParse(params);
|
|
79
85
|
if (result.success) return result.data;
|
|
80
|
-
|
|
81
|
-
cause: result.error,
|
|
82
|
-
message: "Invalid route parameters."
|
|
83
|
-
});
|
|
86
|
+
return throwApiError(400, "Invalid route parameters.", result.error);
|
|
84
87
|
}
|
|
85
88
|
function parseQuery(schema, query) {
|
|
86
89
|
const result = schema.safeParse(query);
|
|
87
90
|
if (result.success) return result.data;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
return throwApiError(400, "Invalid query parameters.", result.error);
|
|
92
|
+
}
|
|
93
|
+
function parseBody(schema, body) {
|
|
94
|
+
const result = schema.safeParse(body);
|
|
95
|
+
if (result.success) return result.data;
|
|
96
|
+
return throwApiError(400, "Invalid request body.", result.error);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/api/route.ts
|
|
100
|
+
function defineApiRoute(route) {
|
|
101
|
+
return route;
|
|
102
|
+
}
|
|
103
|
+
function registerApiRoutes(app, routes5) {
|
|
104
|
+
for (const route of routes5) {
|
|
105
|
+
app.on(
|
|
106
|
+
route.method,
|
|
107
|
+
route.path,
|
|
108
|
+
async (context) => jsonResponse(route.responseSchema, await route.handler(context))
|
|
109
|
+
);
|
|
110
|
+
}
|
|
92
111
|
}
|
|
93
112
|
|
|
94
113
|
// src/api/conversations/archive.ts
|
|
95
|
-
|
|
114
|
+
import { and, eq, isNotNull } from "drizzle-orm";
|
|
96
115
|
async function archiveIfUnchanged(args) {
|
|
97
116
|
const db = getDb();
|
|
98
117
|
const rows = await db.update(juniorConversations).set({ archivedAt: args.archived ? /* @__PURE__ */ new Date() : null }).where(
|
|
@@ -110,9 +129,10 @@ async function archiveIfUnchanged(args) {
|
|
|
110
129
|
if (!args.archived && existing.archivedAt === null) return "updated";
|
|
111
130
|
return "conflict";
|
|
112
131
|
}
|
|
113
|
-
var archive_default = {
|
|
132
|
+
var archive_default = defineApiRoute({
|
|
114
133
|
method: "patch",
|
|
115
134
|
path: "/:conversationId/archive",
|
|
135
|
+
responseSchema: archiveConversationResponseSchema,
|
|
116
136
|
handler: async (c) => {
|
|
117
137
|
const { conversationId } = parseParams(
|
|
118
138
|
conversationParamsSchema,
|
|
@@ -122,35 +142,22 @@ var archive_default = {
|
|
|
122
142
|
try {
|
|
123
143
|
input = await c.req.json();
|
|
124
144
|
} catch (error) {
|
|
125
|
-
|
|
126
|
-
cause: error,
|
|
127
|
-
message: "Invalid request body."
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
const parsed = archiveBodySchema.safeParse(input);
|
|
131
|
-
if (!parsed.success) {
|
|
132
|
-
throw new HTTPException2(400, {
|
|
133
|
-
cause: parsed.error,
|
|
134
|
-
message: "Invalid request body."
|
|
135
|
-
});
|
|
145
|
+
throwApiError(400, "Invalid request body.", error);
|
|
136
146
|
}
|
|
137
|
-
const body =
|
|
147
|
+
const body = parseBody(archiveConversationBodySchema, input);
|
|
138
148
|
const result = await archiveIfUnchanged({ ...body, conversationId });
|
|
139
149
|
if (result === "not_found") {
|
|
140
|
-
|
|
141
|
-
{ error: "Conversation not found." },
|
|
142
|
-
{ status: 404 }
|
|
143
|
-
);
|
|
150
|
+
throwApiError(404, "Conversation not found.");
|
|
144
151
|
}
|
|
145
152
|
if (result === "conflict") {
|
|
146
|
-
|
|
147
|
-
{ error: "Conversation received new activity." },
|
|
148
|
-
{ status: 409 }
|
|
149
|
-
);
|
|
153
|
+
throwApiError(409, "Conversation received new activity.");
|
|
150
154
|
}
|
|
151
|
-
return
|
|
155
|
+
return { archived: body.archived };
|
|
152
156
|
}
|
|
153
|
-
};
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// src/api/conversations/detail.ts
|
|
160
|
+
import { and as and5, asc as asc2, eq as eq6, getTableColumns, inArray as inArray3, sql as sql3 } from "drizzle-orm";
|
|
154
161
|
|
|
155
162
|
// src/chat/pi/sql-model-usage.ts
|
|
156
163
|
import { and as and2, eq as eq2, sql } from "drizzle-orm";
|
|
@@ -219,7 +226,7 @@ function usageFromRow(row) {
|
|
|
219
226
|
};
|
|
220
227
|
return hasAgentTurnUsage(result) ? agentTurnUsageSchema.parse(result) : void 0;
|
|
221
228
|
}
|
|
222
|
-
async function readConversationModelUsageFromSql(executor,
|
|
229
|
+
async function readConversationModelUsageFromSql(executor, options) {
|
|
223
230
|
const modelId = sql`concat(${message}->>'provider', '/', ${message}->>'model')`;
|
|
224
231
|
const inputTokens = token("input", "inputTokens");
|
|
225
232
|
const outputTokens = token("output", "outputTokens");
|
|
@@ -245,9 +252,15 @@ async function readConversationModelUsageFromSql(executor, conversationId) {
|
|
|
245
252
|
costCacheRead: summedCost(nonnegativeNumber(cost, "cacheRead")),
|
|
246
253
|
costCacheWrite: summedCost(nonnegativeNumber(cost, "cacheWrite")),
|
|
247
254
|
costTotal: summedCost(nonnegativeNumber(cost, "total"))
|
|
248
|
-
}).from(juniorConversationEvents).
|
|
255
|
+
}).from(juniorConversationEvents).innerJoin(
|
|
256
|
+
juniorConversations,
|
|
257
|
+
eq2(
|
|
258
|
+
juniorConversations.conversationId,
|
|
259
|
+
juniorConversationEvents.conversationId
|
|
260
|
+
)
|
|
261
|
+
).where(
|
|
249
262
|
and2(
|
|
250
|
-
eq2(juniorConversationEvents.conversationId, conversationId),
|
|
263
|
+
options.includeDescendants ? eq2(juniorConversations.rootConversationId, options.conversationId) : eq2(juniorConversationEvents.conversationId, options.conversationId),
|
|
251
264
|
eq2(juniorConversationEvents.type, "agent_step"),
|
|
252
265
|
sql`${message}->>'role' = 'assistant'`,
|
|
253
266
|
sql`jsonb_typeof(${message}->'provider') = 'string'`,
|
|
@@ -263,7 +276,7 @@ async function readConversationModelUsageFromSql(executor, conversationId) {
|
|
|
263
276
|
}
|
|
264
277
|
|
|
265
278
|
// src/api/conversations/events.ts
|
|
266
|
-
import { z
|
|
279
|
+
import { z } from "zod";
|
|
267
280
|
var conversationReportSourceEventTypes = [
|
|
268
281
|
"message",
|
|
269
282
|
"message_handled",
|
|
@@ -277,31 +290,31 @@ var conversationReportSourceEventTypes = [
|
|
|
277
290
|
"subagent_started",
|
|
278
291
|
"subagent_ended"
|
|
279
292
|
];
|
|
280
|
-
var reportingAssistantMessageSchema =
|
|
281
|
-
role:
|
|
282
|
-
content:
|
|
293
|
+
var reportingAssistantMessageSchema = z.object({
|
|
294
|
+
role: z.literal("assistant"),
|
|
295
|
+
content: z.array(z.unknown())
|
|
283
296
|
}).passthrough();
|
|
284
|
-
var reportingToolCallPartSchema =
|
|
285
|
-
type:
|
|
286
|
-
id:
|
|
287
|
-
name:
|
|
288
|
-
arguments:
|
|
297
|
+
var reportingToolCallPartSchema = z.object({
|
|
298
|
+
type: z.literal("toolCall"),
|
|
299
|
+
id: z.string().min(1),
|
|
300
|
+
name: z.string().min(1),
|
|
301
|
+
arguments: z.unknown()
|
|
289
302
|
}).passthrough();
|
|
290
|
-
var reportingToolResultMessageSchema =
|
|
291
|
-
role:
|
|
292
|
-
toolCallId:
|
|
293
|
-
content:
|
|
294
|
-
details:
|
|
295
|
-
isError:
|
|
303
|
+
var reportingToolResultMessageSchema = z.object({
|
|
304
|
+
role: z.literal("toolResult"),
|
|
305
|
+
toolCallId: z.string().min(1),
|
|
306
|
+
content: z.array(z.unknown()),
|
|
307
|
+
details: z.unknown().optional(),
|
|
308
|
+
isError: z.boolean().optional()
|
|
296
309
|
}).passthrough();
|
|
297
|
-
var reportingToolResultDetailsSchema =
|
|
298
|
-
ok:
|
|
299
|
-
status:
|
|
310
|
+
var reportingToolResultDetailsSchema = z.object({
|
|
311
|
+
ok: z.boolean().optional(),
|
|
312
|
+
status: z.string().optional()
|
|
300
313
|
}).passthrough();
|
|
301
|
-
var reportingTextPartSchema =
|
|
302
|
-
var reportingMediaPartSchema =
|
|
303
|
-
type:
|
|
304
|
-
mimeType:
|
|
314
|
+
var reportingTextPartSchema = z.object({ type: z.literal("text"), text: z.string() }).passthrough();
|
|
315
|
+
var reportingMediaPartSchema = z.object({
|
|
316
|
+
type: z.enum(["image", "audio"]),
|
|
317
|
+
mimeType: z.string().min(1).optional()
|
|
305
318
|
}).passthrough();
|
|
306
319
|
function sanitizeModelContentPart(value) {
|
|
307
320
|
const text = reportingTextPartSchema.safeParse(value);
|
|
@@ -429,6 +442,9 @@ function projectConversationReportEvents(args) {
|
|
|
429
442
|
} else if (event.data.type === "handoff") {
|
|
430
443
|
data = {
|
|
431
444
|
type: "handoff",
|
|
445
|
+
modelProfile: event.data.modelProfile,
|
|
446
|
+
modelId: event.data.modelId,
|
|
447
|
+
...event.data.reasoningLevel ? { reasoningLevel: event.data.reasoningLevel } : {},
|
|
432
448
|
...event.data.triggeringToolCallId ? { triggeringToolCallId: event.data.triggeringToolCallId } : {}
|
|
433
449
|
};
|
|
434
450
|
} else {
|
|
@@ -450,7 +466,7 @@ function projectConversationReportEvents(args) {
|
|
|
450
466
|
}
|
|
451
467
|
|
|
452
468
|
// src/api/conversations/list.ts
|
|
453
|
-
import { and as
|
|
469
|
+
import { and as and4, asc, desc, eq as eq5, isNull as isNull2 } from "drizzle-orm";
|
|
454
470
|
|
|
455
471
|
// src/api/conversations/shared.ts
|
|
456
472
|
function slackStatsLocationLabel(input) {
|
|
@@ -521,16 +537,13 @@ function titleFromConversation(args) {
|
|
|
521
537
|
threadId: args.conversation.conversationId,
|
|
522
538
|
channelName: effectiveChannelName
|
|
523
539
|
});
|
|
524
|
-
const privateLabel =
|
|
525
|
-
conversationId: args.conversation.conversationId,
|
|
526
|
-
visibility: args.conversation.visibility
|
|
527
|
-
}) !== "public" ? privateConversationLabel(slackConversation) : void 0;
|
|
540
|
+
const privateLabel = args.canViewPrivateContent ? void 0 : privateConversationLabel(slackConversation);
|
|
528
541
|
return privateLabel ?? args.conversation.title ?? slackStatsLocationLabel({
|
|
529
542
|
channel: slackThread?.channelId,
|
|
530
543
|
channelName: effectiveChannelName
|
|
531
544
|
}) ?? surfaceFallbackLabel(args.surface);
|
|
532
545
|
}
|
|
533
|
-
function channelNameFromConversation(conversation) {
|
|
546
|
+
function channelNameFromConversation(conversation, canViewPrivateContent) {
|
|
534
547
|
const effectiveChannelName = conversation.channelName;
|
|
535
548
|
const slackThread = parseSlackThreadId(conversation.conversationId);
|
|
536
549
|
if (!effectiveChannelName && !slackThread) {
|
|
@@ -540,39 +553,45 @@ function channelNameFromConversation(conversation) {
|
|
|
540
553
|
threadId: conversation.conversationId,
|
|
541
554
|
channelName: effectiveChannelName
|
|
542
555
|
});
|
|
543
|
-
if (
|
|
544
|
-
conversationId: conversation.conversationId,
|
|
545
|
-
visibility: conversation.visibility
|
|
546
|
-
}) !== "public") {
|
|
556
|
+
if (!canViewPrivateContent) {
|
|
547
557
|
return privateConversationLabel(slackConversation);
|
|
548
558
|
}
|
|
549
559
|
return effectiveChannelName;
|
|
550
560
|
}
|
|
551
|
-
function channelNameRedactedFromConversation(conversation) {
|
|
561
|
+
function channelNameRedactedFromConversation(conversation, canViewPrivateContent) {
|
|
552
562
|
const effectiveChannelName = conversation.channelName;
|
|
553
563
|
const slackThread = parseSlackThreadId(conversation.conversationId);
|
|
554
564
|
if (!effectiveChannelName && !slackThread) {
|
|
555
565
|
return false;
|
|
556
566
|
}
|
|
557
|
-
return
|
|
558
|
-
conversationId: conversation.conversationId,
|
|
559
|
-
visibility: conversation.visibility
|
|
560
|
-
}) !== "public";
|
|
567
|
+
return !canViewPrivateContent;
|
|
561
568
|
}
|
|
562
569
|
function conversationSummaryFromStoredConversation(args) {
|
|
563
|
-
const { conversation, durationMs, usage:
|
|
570
|
+
const { conversation, durationMs, usage: usage3 } = args;
|
|
571
|
+
const canViewPrivateContent = args.access?.canViewPrivateContent ?? false;
|
|
564
572
|
const surface = surfaceFromSource(
|
|
565
573
|
conversation.source,
|
|
566
574
|
conversation.conversationId
|
|
567
575
|
);
|
|
568
576
|
const actorIdentity = actorIdentityReport(conversation.actor);
|
|
569
577
|
const slackThread = parseSlackThreadId(conversation.conversationId);
|
|
570
|
-
const channelName = channelNameFromConversation(
|
|
571
|
-
|
|
578
|
+
const channelName = channelNameFromConversation(
|
|
579
|
+
conversation,
|
|
580
|
+
canViewPrivateContent
|
|
581
|
+
);
|
|
582
|
+
const channelNameRedacted = channelNameRedactedFromConversation(
|
|
583
|
+
conversation,
|
|
584
|
+
canViewPrivateContent
|
|
585
|
+
);
|
|
572
586
|
return {
|
|
573
587
|
conversationId: conversation.conversationId,
|
|
574
588
|
cumulativeDurationMs: durationMs,
|
|
575
|
-
displayTitle: titleFromConversation({
|
|
589
|
+
displayTitle: titleFromConversation({
|
|
590
|
+
canViewPrivateContent,
|
|
591
|
+
conversation,
|
|
592
|
+
surface
|
|
593
|
+
}),
|
|
594
|
+
isParticipant: args.access?.isParticipant ?? false,
|
|
576
595
|
lastProgressAt: new Date(
|
|
577
596
|
conversation.execution.updatedAtMs ?? conversation.updatedAtMs
|
|
578
597
|
).toISOString(),
|
|
@@ -580,7 +599,7 @@ function conversationSummaryFromStoredConversation(args) {
|
|
|
580
599
|
startedAt: new Date(conversation.createdAtMs).toISOString(),
|
|
581
600
|
status: statusFromConversation(conversation),
|
|
582
601
|
surface,
|
|
583
|
-
...
|
|
602
|
+
...usage3 ? { cumulativeUsage: usage3 } : {},
|
|
584
603
|
...actorIdentity ? { actorIdentity } : {},
|
|
585
604
|
...conversation.archivedAtMs ? { archivedAt: new Date(conversation.archivedAtMs).toISOString() } : {},
|
|
586
605
|
...slackThread ? { channel: slackThread.channelId } : {},
|
|
@@ -590,6 +609,163 @@ function conversationSummaryFromStoredConversation(args) {
|
|
|
590
609
|
};
|
|
591
610
|
}
|
|
592
611
|
|
|
612
|
+
// src/api/conversations/access.ts
|
|
613
|
+
import { eq as eq3, inArray } from "drizzle-orm";
|
|
614
|
+
import { alias } from "drizzle-orm/pg-core";
|
|
615
|
+
var rootConversation = alias(juniorConversations, "access_root_conversation");
|
|
616
|
+
var rootDestination = alias(juniorDestinations, "access_root_destination");
|
|
617
|
+
var rootIdentity = alias(juniorIdentities, "access_root_identity");
|
|
618
|
+
async function readConversationAccessFromSql(db, conversationIds, verifiedViewerEmail) {
|
|
619
|
+
if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
|
|
620
|
+
const normalizedViewerEmail = verifiedViewerEmail?.trim().toLowerCase() || void 0;
|
|
621
|
+
const rows = await db.select({
|
|
622
|
+
conversationId: juniorConversations.conversationId,
|
|
623
|
+
parentConversationId: juniorConversations.parentConversationId,
|
|
624
|
+
storedRootConversationId: juniorConversations.rootConversationId,
|
|
625
|
+
rootConversationId: rootConversation.conversationId,
|
|
626
|
+
rootParentConversationId: rootConversation.parentConversationId,
|
|
627
|
+
rootRootConversationId: rootConversation.rootConversationId,
|
|
628
|
+
visibility: rootDestination.visibility,
|
|
629
|
+
rootEmailNormalized: rootIdentity.emailNormalized,
|
|
630
|
+
rootEmailVerified: rootIdentity.emailVerified
|
|
631
|
+
}).from(juniorConversations).leftJoin(
|
|
632
|
+
rootConversation,
|
|
633
|
+
eq3(
|
|
634
|
+
rootConversation.conversationId,
|
|
635
|
+
juniorConversations.rootConversationId
|
|
636
|
+
)
|
|
637
|
+
).leftJoin(
|
|
638
|
+
rootDestination,
|
|
639
|
+
eq3(rootDestination.id, rootConversation.destinationId)
|
|
640
|
+
).leftJoin(
|
|
641
|
+
rootIdentity,
|
|
642
|
+
eq3(rootIdentity.id, rootConversation.actorIdentityId)
|
|
643
|
+
).where(inArray(juniorConversations.conversationId, [...conversationIds]));
|
|
644
|
+
return new Map(
|
|
645
|
+
rows.map((row) => {
|
|
646
|
+
const hasValidRoot = row.rootConversationId !== null && row.rootConversationId === row.storedRootConversationId && row.rootRootConversationId === row.rootConversationId && row.rootParentConversationId === null && (row.parentConversationId !== null || row.storedRootConversationId === row.conversationId);
|
|
647
|
+
const validRootConversationId = hasValidRoot ? row.rootConversationId ?? void 0 : void 0;
|
|
648
|
+
const visibility = validRootConversationId === void 0 ? null : row.visibility;
|
|
649
|
+
const isParticipant = validRootConversationId !== void 0 && normalizedViewerEmail !== void 0 && row.rootEmailVerified === true && row.rootEmailNormalized === normalizedViewerEmail;
|
|
650
|
+
const canViewPrivateContent = isParticipant || validRootConversationId !== void 0 && canExposeConversationPayload({
|
|
651
|
+
conversationId: validRootConversationId,
|
|
652
|
+
...visibility === "public" || visibility === "private" ? { visibility } : {}
|
|
653
|
+
});
|
|
654
|
+
return [
|
|
655
|
+
row.conversationId,
|
|
656
|
+
{
|
|
657
|
+
canViewPrivateContent,
|
|
658
|
+
isParticipant,
|
|
659
|
+
visibility
|
|
660
|
+
}
|
|
661
|
+
];
|
|
662
|
+
})
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/api/conversations/usage.ts
|
|
667
|
+
import { and as and3, eq as eq4, inArray as inArray2, isNull, sql as sql2 } from "drizzle-orm";
|
|
668
|
+
import { alias as alias2 } from "drizzle-orm/pg-core";
|
|
669
|
+
var usage2 = juniorConversations.usage;
|
|
670
|
+
var metricRootConversation = alias2(
|
|
671
|
+
juniorConversations,
|
|
672
|
+
"metric_root_conversation"
|
|
673
|
+
);
|
|
674
|
+
var componentFields = [
|
|
675
|
+
"inputTokens",
|
|
676
|
+
"outputTokens",
|
|
677
|
+
"cachedInputTokens",
|
|
678
|
+
"cacheCreationTokens"
|
|
679
|
+
];
|
|
680
|
+
function summedUsageNumber(field) {
|
|
681
|
+
return sql2`sum((${usage2}->>${field})::double precision)::double precision`;
|
|
682
|
+
}
|
|
683
|
+
function summedCostNumber(field) {
|
|
684
|
+
return sql2`round(
|
|
685
|
+
sum((${usage2}->'cost'->>${field})::numeric),
|
|
686
|
+
12
|
|
687
|
+
)::double precision`;
|
|
688
|
+
}
|
|
689
|
+
function usageFromAggregate(row) {
|
|
690
|
+
const components = {};
|
|
691
|
+
let componentTotal = 0;
|
|
692
|
+
for (const field of componentFields) {
|
|
693
|
+
const value = row[field];
|
|
694
|
+
if (value === null) continue;
|
|
695
|
+
components[field] = value;
|
|
696
|
+
componentTotal += value;
|
|
697
|
+
}
|
|
698
|
+
const cost2 = {
|
|
699
|
+
...row.costInput === null ? {} : { input: row.costInput },
|
|
700
|
+
...row.costOutput === null ? {} : { output: row.costOutput },
|
|
701
|
+
...row.costCacheRead === null ? {} : { cacheRead: row.costCacheRead },
|
|
702
|
+
...row.costCacheWrite === null ? {} : { cacheWrite: row.costCacheWrite },
|
|
703
|
+
...row.costTotal === null ? {} : { total: row.costTotal }
|
|
704
|
+
};
|
|
705
|
+
const result = {
|
|
706
|
+
...row.totalOnlyTokens === null ? components : { totalTokens: row.totalOnlyTokens + componentTotal },
|
|
707
|
+
...row.reasoningTokens === null ? {} : { reasoningTokens: row.reasoningTokens },
|
|
708
|
+
...Object.keys(cost2).length === 0 ? {} : { cost: cost2 }
|
|
709
|
+
};
|
|
710
|
+
return hasAgentTurnUsage(result) ? result : void 0;
|
|
711
|
+
}
|
|
712
|
+
async function readRootConversationMetricsFromSql(db, rootConversationIds) {
|
|
713
|
+
if (rootConversationIds.length === 0) return /* @__PURE__ */ new Map();
|
|
714
|
+
const hasComponents = sql2`coalesce(
|
|
715
|
+
${usage2}->>'inputTokens',
|
|
716
|
+
${usage2}->>'outputTokens',
|
|
717
|
+
${usage2}->>'cachedInputTokens',
|
|
718
|
+
${usage2}->>'cacheCreationTokens'
|
|
719
|
+
) is not null`;
|
|
720
|
+
const rows = await db.select({
|
|
721
|
+
rootConversationId: juniorConversations.rootConversationId,
|
|
722
|
+
durationMs: sql2`coalesce(sum(${juniorConversations.durationMs}), 0)::double precision`,
|
|
723
|
+
inputTokens: summedUsageNumber("inputTokens"),
|
|
724
|
+
outputTokens: summedUsageNumber("outputTokens"),
|
|
725
|
+
cachedInputTokens: summedUsageNumber("cachedInputTokens"),
|
|
726
|
+
cacheCreationTokens: summedUsageNumber("cacheCreationTokens"),
|
|
727
|
+
totalOnlyTokens: sql2`sum(
|
|
728
|
+
case
|
|
729
|
+
when not (${hasComponents})
|
|
730
|
+
then (${usage2}->>'totalTokens')::double precision
|
|
731
|
+
end
|
|
732
|
+
)::double precision`,
|
|
733
|
+
reasoningTokens: summedUsageNumber("reasoningTokens"),
|
|
734
|
+
costInput: summedCostNumber("input"),
|
|
735
|
+
costOutput: summedCostNumber("output"),
|
|
736
|
+
costCacheRead: summedCostNumber("cacheRead"),
|
|
737
|
+
costCacheWrite: summedCostNumber("cacheWrite"),
|
|
738
|
+
costTotal: summedCostNumber("total")
|
|
739
|
+
}).from(juniorConversations).innerJoin(
|
|
740
|
+
metricRootConversation,
|
|
741
|
+
and3(
|
|
742
|
+
eq4(
|
|
743
|
+
metricRootConversation.conversationId,
|
|
744
|
+
juniorConversations.rootConversationId
|
|
745
|
+
),
|
|
746
|
+
isNull(metricRootConversation.parentConversationId),
|
|
747
|
+
eq4(
|
|
748
|
+
metricRootConversation.rootConversationId,
|
|
749
|
+
metricRootConversation.conversationId
|
|
750
|
+
)
|
|
751
|
+
)
|
|
752
|
+
).where(
|
|
753
|
+
inArray2(juniorConversations.rootConversationId, [...rootConversationIds])
|
|
754
|
+
).groupBy(juniorConversations.rootConversationId);
|
|
755
|
+
return new Map(
|
|
756
|
+
rows.flatMap((row) => {
|
|
757
|
+
if (!row.rootConversationId) return [];
|
|
758
|
+
const usage3 = usageFromAggregate(row);
|
|
759
|
+
return [
|
|
760
|
+
[
|
|
761
|
+
row.rootConversationId,
|
|
762
|
+
{ durationMs: row.durationMs, ...usage3 ? { usage: usage3 } : {} }
|
|
763
|
+
]
|
|
764
|
+
];
|
|
765
|
+
})
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
593
769
|
// src/api/conversations/list.ts
|
|
594
770
|
var CONVERSATION_FEED_LIMIT = 50;
|
|
595
771
|
async function conversationRows(db, limit, actorEmail) {
|
|
@@ -606,17 +782,17 @@ async function conversationRows(db, limit, actorEmail) {
|
|
|
606
782
|
userDisplayName: juniorUsers.displayName
|
|
607
783
|
}).from(juniorConversations).leftJoin(
|
|
608
784
|
juniorDestinations,
|
|
609
|
-
|
|
785
|
+
eq5(juniorDestinations.id, juniorConversations.destinationId)
|
|
610
786
|
).leftJoin(
|
|
611
787
|
juniorIdentities,
|
|
612
|
-
|
|
613
|
-
).leftJoin(juniorUsers,
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
actorEmail ?
|
|
618
|
-
|
|
619
|
-
|
|
788
|
+
eq5(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
789
|
+
).leftJoin(juniorUsers, eq5(juniorUsers.id, juniorIdentities.userId)).where(
|
|
790
|
+
and4(
|
|
791
|
+
isNull2(juniorConversations.parentConversationId),
|
|
792
|
+
isNull2(juniorConversations.archivedAt),
|
|
793
|
+
actorEmail ? and4(
|
|
794
|
+
eq5(juniorIdentities.emailNormalized, actorEmail),
|
|
795
|
+
eq5(juniorIdentities.emailVerified, true)
|
|
620
796
|
) : void 0
|
|
621
797
|
)
|
|
622
798
|
).orderBy(
|
|
@@ -651,10 +827,7 @@ function conversationFromRow(row) {
|
|
|
651
827
|
...value.channelName ? { channelName: value.channelName } : {},
|
|
652
828
|
...value.source ? { source: value.source } : {},
|
|
653
829
|
...value.title ? { title: value.title } : {},
|
|
654
|
-
...value.transcriptPurgedAt ? { transcriptPurgedAtMs: value.transcriptPurgedAt.getTime() } : {}
|
|
655
|
-
...row.destinationVisibility ? {
|
|
656
|
-
visibility: row.destinationVisibility === "public" ? "public" : "private"
|
|
657
|
-
} : {}
|
|
830
|
+
...value.transcriptPurgedAt ? { transcriptPurgedAtMs: value.transcriptPurgedAt.getTime() } : {}
|
|
658
831
|
};
|
|
659
832
|
}
|
|
660
833
|
async function readConversationRecordFromSql(conversationId) {
|
|
@@ -672,76 +845,110 @@ async function readConversationRecordFromSql(conversationId) {
|
|
|
672
845
|
userDisplayName: juniorUsers.displayName
|
|
673
846
|
}).from(juniorConversations).leftJoin(
|
|
674
847
|
juniorDestinations,
|
|
675
|
-
|
|
848
|
+
eq5(juniorDestinations.id, juniorConversations.destinationId)
|
|
676
849
|
).leftJoin(
|
|
677
850
|
juniorIdentities,
|
|
678
|
-
|
|
679
|
-
).leftJoin(juniorUsers,
|
|
851
|
+
eq5(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
852
|
+
).leftJoin(juniorUsers, eq5(juniorUsers.id, juniorIdentities.userId)).where(eq5(juniorConversations.conversationId, conversationId)).limit(1);
|
|
680
853
|
const row = rows[0];
|
|
681
854
|
return row ? {
|
|
682
855
|
conversation: conversationFromRow(row),
|
|
683
856
|
durationMs: row.conversation.durationMs,
|
|
684
857
|
...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
685
|
-
usage: row.conversation.usage
|
|
858
|
+
usage: row.conversation.usage,
|
|
859
|
+
rootConversationId: row.conversation.rootConversationId
|
|
686
860
|
} : void 0;
|
|
687
861
|
}
|
|
688
862
|
async function readConversationFeedFromSql(options = {}) {
|
|
689
863
|
const nowMs = Date.now();
|
|
864
|
+
const db = getDb();
|
|
690
865
|
const rows = await conversationRows(
|
|
691
|
-
|
|
866
|
+
db,
|
|
692
867
|
options.limit ?? CONVERSATION_FEED_LIMIT,
|
|
693
868
|
options.actorEmail
|
|
694
869
|
);
|
|
870
|
+
const conversationIds = rows.map((row) => row.conversation.conversationId);
|
|
871
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
872
|
+
readConversationAccessFromSql(
|
|
873
|
+
db,
|
|
874
|
+
conversationIds,
|
|
875
|
+
options.verifiedViewerEmail
|
|
876
|
+
),
|
|
877
|
+
readRootConversationMetricsFromSql(db, conversationIds)
|
|
878
|
+
]);
|
|
695
879
|
return {
|
|
696
|
-
conversations: rows.map(
|
|
697
|
-
(row)
|
|
880
|
+
conversations: rows.map((row) => {
|
|
881
|
+
const metrics = metricsByRoot.get(row.conversation.conversationId);
|
|
882
|
+
return conversationSummaryFromStoredConversation({
|
|
698
883
|
conversation: conversationFromRow(row),
|
|
699
|
-
|
|
884
|
+
access: accessByConversation.get(row.conversation.conversationId),
|
|
885
|
+
durationMs: metrics?.durationMs ?? row.conversation.durationMs,
|
|
700
886
|
...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
701
|
-
usage: row.conversation.usage ?? void 0
|
|
702
|
-
})
|
|
703
|
-
),
|
|
887
|
+
usage: metrics?.usage ?? row.conversation.usage ?? void 0
|
|
888
|
+
});
|
|
889
|
+
}),
|
|
704
890
|
generatedAt: new Date(nowMs).toISOString(),
|
|
705
891
|
source: "conversation_index"
|
|
706
892
|
};
|
|
707
893
|
}
|
|
708
894
|
async function readConversationFeed(options = {}) {
|
|
709
895
|
return conversationFeedSchema.parse(
|
|
710
|
-
await readConversationFeedFromSql(
|
|
896
|
+
await readConversationFeedFromSql(options)
|
|
711
897
|
);
|
|
712
898
|
}
|
|
713
|
-
var list_default = {
|
|
899
|
+
var list_default = defineApiRoute({
|
|
714
900
|
method: "get",
|
|
715
901
|
path: "/",
|
|
902
|
+
responseSchema: conversationFeedSchema,
|
|
716
903
|
handler: async (c) => {
|
|
717
904
|
const { actorEmail } = parseQuery(
|
|
718
905
|
conversationFeedQuerySchema,
|
|
719
906
|
c.req.query()
|
|
720
907
|
);
|
|
721
|
-
|
|
908
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
909
|
+
return readConversationFeed({
|
|
910
|
+
...actorEmail ? { actorEmail } : {},
|
|
911
|
+
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
912
|
+
});
|
|
722
913
|
}
|
|
723
|
-
};
|
|
914
|
+
});
|
|
724
915
|
|
|
725
916
|
// src/api/conversations/detail.ts
|
|
917
|
+
var conversationEventColumns = getTableColumns(juniorConversationEvents);
|
|
918
|
+
async function readConversationReportEventRows(executor, conversationId) {
|
|
919
|
+
return executor.db().select({
|
|
920
|
+
...conversationEventColumns,
|
|
921
|
+
// Replacement history is model context, never dashboard report data.
|
|
922
|
+
payload: sql3`case
|
|
923
|
+
when ${juniorConversationEvents.type} in ('compaction', 'handoff')
|
|
924
|
+
then jsonb_set(
|
|
925
|
+
${juniorConversationEvents.payload},
|
|
926
|
+
'{replacementHistory}',
|
|
927
|
+
'[]'::jsonb
|
|
928
|
+
)
|
|
929
|
+
else ${juniorConversationEvents.payload}
|
|
930
|
+
end`
|
|
931
|
+
}).from(juniorConversationEvents).where(
|
|
932
|
+
and5(
|
|
933
|
+
eq6(juniorConversationEvents.conversationId, conversationId),
|
|
934
|
+
inArray3(juniorConversationEvents.type, [
|
|
935
|
+
...conversationReportSourceEventTypes
|
|
936
|
+
])
|
|
937
|
+
)
|
|
938
|
+
).orderBy(asc2(juniorConversationEvents.seq));
|
|
939
|
+
}
|
|
726
940
|
function projectConversationDetail(args) {
|
|
727
941
|
const { conversation } = args;
|
|
728
942
|
const conversationId = conversation.conversationId;
|
|
729
943
|
const transcriptPurgedAtMs = conversation.transcriptPurgedAtMs;
|
|
730
|
-
const
|
|
731
|
-
const authorizedConversation = {
|
|
732
|
-
...conversationWithoutVisibility,
|
|
733
|
-
...args.effectiveVisibility ? { visibility: args.effectiveVisibility } : {}
|
|
734
|
-
};
|
|
735
|
-
const canExposePayload = canExposeConversationPayload({
|
|
736
|
-
conversationId: args.privacyConversationId ?? conversationId,
|
|
737
|
-
visibility: args.effectiveVisibility
|
|
738
|
-
});
|
|
944
|
+
const canExposePayload = args.access?.canViewPrivateContent ?? false;
|
|
739
945
|
const events = transcriptPurgedAtMs === void 0 ? args.events : [];
|
|
740
946
|
const modelUsage = transcriptPurgedAtMs === void 0 ? args.modelUsage : [];
|
|
741
947
|
const sentryConversationUrl = buildSentryConversationUrl(conversationId);
|
|
742
948
|
return {
|
|
743
949
|
...conversationSummaryFromStoredConversation({
|
|
744
|
-
|
|
950
|
+
access: args.access,
|
|
951
|
+
conversation,
|
|
745
952
|
durationMs: args.durationMs,
|
|
746
953
|
...args.locationId ? { locationId: args.locationId } : {},
|
|
747
954
|
usage: args.usage
|
|
@@ -759,19 +966,28 @@ function projectConversationDetail(args) {
|
|
|
759
966
|
...sentryConversationUrl ? { sentryConversationUrl } : {}
|
|
760
967
|
};
|
|
761
968
|
}
|
|
762
|
-
async function readConversationDetailFromSql(conversationId) {
|
|
969
|
+
async function readConversationDetailFromSql(conversationId, options) {
|
|
763
970
|
const record = await readConversationRecordFromSql(conversationId);
|
|
764
971
|
if (!record) return void 0;
|
|
765
972
|
const executor = getSqlExecutor();
|
|
766
|
-
const
|
|
767
|
-
|
|
973
|
+
const includeDescendantMetrics = record.rootConversationId === conversationId;
|
|
974
|
+
const [accessByConversation, eventRows, modelUsage, metricsByRoot] = await Promise.all([
|
|
975
|
+
readConversationAccessFromSql(
|
|
976
|
+
getDb(),
|
|
977
|
+
[conversationId],
|
|
978
|
+
options.verifiedViewerEmail
|
|
979
|
+
),
|
|
980
|
+
readConversationReportEventRows(executor, conversationId),
|
|
981
|
+
record.conversation.transcriptPurgedAtMs === void 0 ? readConversationModelUsageFromSql(executor, {
|
|
768
982
|
conversationId,
|
|
769
|
-
|
|
770
|
-
}),
|
|
771
|
-
|
|
983
|
+
includeDescendants: includeDescendantMetrics
|
|
984
|
+
}) : Promise.resolve([]),
|
|
985
|
+
readRootConversationMetricsFromSql(
|
|
986
|
+
getDb(),
|
|
987
|
+
includeDescendantMetrics ? [conversationId] : []
|
|
988
|
+
)
|
|
772
989
|
]);
|
|
773
|
-
|
|
774
|
-
const events = snapshot.events.map(
|
|
990
|
+
const events = eventRows.map(
|
|
775
991
|
(row) => decodeStoredConversationEvent({
|
|
776
992
|
schemaVersion: row.schemaVersion,
|
|
777
993
|
seq: row.seq,
|
|
@@ -782,106 +998,121 @@ async function readConversationDetailFromSql(conversationId) {
|
|
|
782
998
|
payload: row.payload
|
|
783
999
|
})
|
|
784
1000
|
);
|
|
785
|
-
const
|
|
1001
|
+
const metrics = metricsByRoot.get(conversationId);
|
|
786
1002
|
return projectConversationDetail({
|
|
787
1003
|
...record,
|
|
788
|
-
|
|
1004
|
+
access: accessByConversation.get(conversationId),
|
|
1005
|
+
durationMs: metrics?.durationMs ?? record.durationMs,
|
|
789
1006
|
events,
|
|
790
1007
|
modelUsage,
|
|
791
|
-
|
|
792
|
-
usage: record.usage ?? void 0
|
|
1008
|
+
usage: metrics?.usage ?? record.usage ?? void 0
|
|
793
1009
|
});
|
|
794
1010
|
}
|
|
795
|
-
async function readConversationDetail(conversationId) {
|
|
796
|
-
const report = await readConversationDetailFromSql(conversationId);
|
|
1011
|
+
async function readConversationDetail(conversationId, options = {}) {
|
|
1012
|
+
const report = await readConversationDetailFromSql(conversationId, options);
|
|
797
1013
|
return report ? conversationDetailReportSchema.parse(report) : void 0;
|
|
798
1014
|
}
|
|
799
|
-
var detail_default = {
|
|
1015
|
+
var detail_default = defineApiRoute({
|
|
800
1016
|
method: "get",
|
|
801
1017
|
path: "/:conversationId",
|
|
1018
|
+
responseSchema: conversationDetailReportSchema,
|
|
802
1019
|
handler: async (c) => {
|
|
803
1020
|
const { conversationId } = parseParams(
|
|
804
1021
|
conversationParamsSchema,
|
|
805
1022
|
c.req.param()
|
|
806
1023
|
);
|
|
807
|
-
const
|
|
808
|
-
|
|
1024
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
1025
|
+
const report = await readConversationDetail(
|
|
1026
|
+
conversationId,
|
|
1027
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
1028
|
+
);
|
|
1029
|
+
if (!report) throwApiError(404, "Conversation not found.");
|
|
1030
|
+
return report;
|
|
809
1031
|
}
|
|
810
|
-
};
|
|
1032
|
+
});
|
|
811
1033
|
|
|
812
1034
|
// src/api/conversations/stats.query.ts
|
|
813
|
-
import { and as
|
|
1035
|
+
import { and as and6, eq as eq7, gte, isNull as isNull3, lte, sql as sql5 } from "drizzle-orm";
|
|
1036
|
+
import { alias as alias3 } from "drizzle-orm/pg-core";
|
|
814
1037
|
|
|
815
1038
|
// src/api/conversations/aggregate.ts
|
|
816
|
-
import { sql as
|
|
817
|
-
function tokenValue() {
|
|
818
|
-
return
|
|
1039
|
+
import { sql as sql4 } from "drizzle-orm";
|
|
1040
|
+
function tokenValue(source) {
|
|
1041
|
+
return sql4`
|
|
819
1042
|
CASE
|
|
820
|
-
WHEN ${juniorConversations.usage}->>'totalTokens' IS NOT NULL
|
|
821
|
-
THEN (${juniorConversations.usage}->>'totalTokens')::double precision
|
|
822
1043
|
WHEN COALESCE(
|
|
823
|
-
${
|
|
824
|
-
${
|
|
825
|
-
${
|
|
826
|
-
${
|
|
1044
|
+
${source.usage}->>'inputTokens',
|
|
1045
|
+
${source.usage}->>'outputTokens',
|
|
1046
|
+
${source.usage}->>'cachedInputTokens',
|
|
1047
|
+
${source.usage}->>'cacheCreationTokens'
|
|
827
1048
|
) IS NOT NULL
|
|
828
|
-
THEN COALESCE((${
|
|
829
|
-
+ COALESCE((${
|
|
830
|
-
+ COALESCE((${
|
|
831
|
-
+ COALESCE((${
|
|
1049
|
+
THEN COALESCE((${source.usage}->>'inputTokens')::double precision, 0)
|
|
1050
|
+
+ COALESCE((${source.usage}->>'outputTokens')::double precision, 0)
|
|
1051
|
+
+ COALESCE((${source.usage}->>'cachedInputTokens')::double precision, 0)
|
|
1052
|
+
+ COALESCE((${source.usage}->>'cacheCreationTokens')::double precision, 0)
|
|
1053
|
+
WHEN ${source.usage}->>'totalTokens' IS NOT NULL
|
|
1054
|
+
THEN (${source.usage}->>'totalTokens')::double precision
|
|
832
1055
|
ELSE NULL
|
|
833
1056
|
END
|
|
834
1057
|
`;
|
|
835
1058
|
}
|
|
836
|
-
function costValue() {
|
|
837
|
-
return
|
|
1059
|
+
function costValue(source) {
|
|
1060
|
+
return sql4`
|
|
838
1061
|
CASE
|
|
839
|
-
WHEN ${
|
|
840
|
-
THEN (${
|
|
1062
|
+
WHEN ${source.usage}->'cost'->>'total' IS NOT NULL
|
|
1063
|
+
THEN (${source.usage}->'cost'->>'total')::double precision
|
|
841
1064
|
WHEN COALESCE(
|
|
842
|
-
${
|
|
843
|
-
${
|
|
844
|
-
${
|
|
845
|
-
${
|
|
1065
|
+
${source.usage}->'cost'->>'input',
|
|
1066
|
+
${source.usage}->'cost'->>'output',
|
|
1067
|
+
${source.usage}->'cost'->>'cacheRead',
|
|
1068
|
+
${source.usage}->'cost'->>'cacheWrite'
|
|
846
1069
|
) IS NOT NULL
|
|
847
|
-
THEN COALESCE((${
|
|
848
|
-
+ COALESCE((${
|
|
849
|
-
+ COALESCE((${
|
|
850
|
-
+ COALESCE((${
|
|
1070
|
+
THEN COALESCE((${source.usage}->'cost'->>'input')::double precision, 0)
|
|
1071
|
+
+ COALESCE((${source.usage}->'cost'->>'output')::double precision, 0)
|
|
1072
|
+
+ COALESCE((${source.usage}->'cost'->>'cacheRead')::double precision, 0)
|
|
1073
|
+
+ COALESCE((${source.usage}->'cost'->>'cacheWrite')::double precision, 0)
|
|
851
1074
|
ELSE NULL
|
|
852
1075
|
END
|
|
853
1076
|
`;
|
|
854
1077
|
}
|
|
855
|
-
function conversationAggregateColumns() {
|
|
1078
|
+
function conversationAggregateColumns(sources) {
|
|
1079
|
+
const metrics = sources?.metrics ?? juniorConversations;
|
|
1080
|
+
const roots = sources?.roots ?? juniorConversations;
|
|
1081
|
+
const conversationCount = sources ? sql4`COUNT(DISTINCT ${roots.conversationId})` : sql4`COUNT(*)`;
|
|
856
1082
|
return {
|
|
857
|
-
active:
|
|
858
|
-
WHERE ${
|
|
1083
|
+
active: sql4`${conversationCount} FILTER (
|
|
1084
|
+
WHERE ${roots.executionStatus} NOT IN ('idle', 'failed')
|
|
859
1085
|
)::integer`,
|
|
860
|
-
conversations:
|
|
861
|
-
costUsd:
|
|
862
|
-
durationMs:
|
|
863
|
-
failed:
|
|
864
|
-
WHERE ${
|
|
1086
|
+
conversations: sql4`${conversationCount}::integer`,
|
|
1087
|
+
costUsd: sql4`SUM(${costValue(metrics)})::double precision`,
|
|
1088
|
+
durationMs: sql4`COALESCE(SUM(${metrics.durationMs}), 0)::double precision`,
|
|
1089
|
+
failed: sql4`${conversationCount} FILTER (
|
|
1090
|
+
WHERE ${roots.executionStatus} = 'failed'
|
|
865
1091
|
)::integer`,
|
|
866
|
-
tokens:
|
|
1092
|
+
tokens: sql4`SUM(${tokenValue(metrics)})::double precision`
|
|
867
1093
|
};
|
|
868
1094
|
}
|
|
869
1095
|
function conversationRangeColumns() {
|
|
870
1096
|
return {
|
|
871
|
-
firstSeenAt:
|
|
1097
|
+
firstSeenAt: sql4`MIN(${juniorConversations.createdAt})`.mapWith(
|
|
872
1098
|
juniorConversations.createdAt
|
|
873
1099
|
),
|
|
874
|
-
lastSeenAt:
|
|
1100
|
+
lastSeenAt: sql4`MAX(${juniorConversations.lastActivityAt})`.mapWith(
|
|
875
1101
|
juniorConversations.lastActivityAt
|
|
876
1102
|
)
|
|
877
1103
|
};
|
|
878
1104
|
}
|
|
879
1105
|
function conversationActiveDaysColumn() {
|
|
880
|
-
return
|
|
1106
|
+
return sql4`COUNT(DISTINCT (${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC')::date)::integer`;
|
|
881
1107
|
}
|
|
882
1108
|
|
|
883
1109
|
// src/api/conversations/stats.query.ts
|
|
884
1110
|
var WINDOW_DAYS = 90;
|
|
1111
|
+
var treeConversation = alias3(juniorConversations, "stats_tree_conversation");
|
|
1112
|
+
var treeAggregateColumns = conversationAggregateColumns({
|
|
1113
|
+
metrics: treeConversation,
|
|
1114
|
+
roots: juniorConversations
|
|
1115
|
+
});
|
|
885
1116
|
function emptyStatsItem(label) {
|
|
886
1117
|
return {
|
|
887
1118
|
active: 0,
|
|
@@ -936,8 +1167,8 @@ function statsItems(map) {
|
|
|
936
1167
|
);
|
|
937
1168
|
}
|
|
938
1169
|
function statsWhere(start, end) {
|
|
939
|
-
return
|
|
940
|
-
|
|
1170
|
+
return and6(
|
|
1171
|
+
isNull3(juniorConversations.parentConversationId),
|
|
941
1172
|
gte(juniorConversations.lastActivityAt, start),
|
|
942
1173
|
lte(juniorConversations.lastActivityAt, end)
|
|
943
1174
|
);
|
|
@@ -970,12 +1201,18 @@ function metricDays(rows, endMs) {
|
|
|
970
1201
|
}
|
|
971
1202
|
async function aggregateStats(db, start, end) {
|
|
972
1203
|
const where = statsWhere(start, end);
|
|
973
|
-
const activityDate =
|
|
1204
|
+
const activityDate = sql5`TO_CHAR(
|
|
974
1205
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
975
1206
|
'YYYY-MM-DD'
|
|
976
1207
|
)`;
|
|
977
1208
|
const [totalsRows, actorRows, locationRows, metricRows] = await Promise.all([
|
|
978
|
-
db.select(
|
|
1209
|
+
db.select(treeAggregateColumns).from(juniorConversations).innerJoin(
|
|
1210
|
+
treeConversation,
|
|
1211
|
+
eq7(
|
|
1212
|
+
treeConversation.rootConversationId,
|
|
1213
|
+
juniorConversations.conversationId
|
|
1214
|
+
)
|
|
1215
|
+
).where(where),
|
|
979
1216
|
db.select({
|
|
980
1217
|
identityDisplayName: juniorIdentities.displayName,
|
|
981
1218
|
identityEmail: juniorIdentities.emailNormalized,
|
|
@@ -983,11 +1220,17 @@ async function aggregateStats(db, start, end) {
|
|
|
983
1220
|
identitySubjectId: juniorIdentities.providerSubjectId,
|
|
984
1221
|
userDisplayName: juniorUsers.displayName,
|
|
985
1222
|
userEmail: juniorUsers.primaryEmailNormalized,
|
|
986
|
-
...
|
|
987
|
-
}).from(juniorConversations).
|
|
1223
|
+
...treeAggregateColumns
|
|
1224
|
+
}).from(juniorConversations).innerJoin(
|
|
1225
|
+
treeConversation,
|
|
1226
|
+
eq7(
|
|
1227
|
+
treeConversation.rootConversationId,
|
|
1228
|
+
juniorConversations.conversationId
|
|
1229
|
+
)
|
|
1230
|
+
).leftJoin(
|
|
988
1231
|
juniorIdentities,
|
|
989
|
-
|
|
990
|
-
).leftJoin(juniorUsers,
|
|
1232
|
+
eq7(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1233
|
+
).leftJoin(juniorUsers, eq7(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
991
1234
|
juniorIdentities.displayName,
|
|
992
1235
|
juniorIdentities.emailNormalized,
|
|
993
1236
|
juniorIdentities.handle,
|
|
@@ -1002,10 +1245,16 @@ async function aggregateStats(db, start, end) {
|
|
|
1002
1245
|
destinationProvider: juniorDestinations.provider,
|
|
1003
1246
|
destinationVisibility: juniorDestinations.visibility,
|
|
1004
1247
|
source: juniorConversations.source,
|
|
1005
|
-
...
|
|
1006
|
-
}).from(juniorConversations).
|
|
1248
|
+
...treeAggregateColumns
|
|
1249
|
+
}).from(juniorConversations).innerJoin(
|
|
1250
|
+
treeConversation,
|
|
1251
|
+
eq7(
|
|
1252
|
+
treeConversation.rootConversationId,
|
|
1253
|
+
juniorConversations.conversationId
|
|
1254
|
+
)
|
|
1255
|
+
).leftJoin(
|
|
1007
1256
|
juniorDestinations,
|
|
1008
|
-
|
|
1257
|
+
eq7(juniorDestinations.id, juniorConversations.destinationId)
|
|
1009
1258
|
).where(where).groupBy(
|
|
1010
1259
|
juniorConversations.channelName,
|
|
1011
1260
|
juniorConversations.source,
|
|
@@ -1015,11 +1264,17 @@ async function aggregateStats(db, start, end) {
|
|
|
1015
1264
|
juniorDestinations.visibility
|
|
1016
1265
|
),
|
|
1017
1266
|
db.select({
|
|
1018
|
-
costUsd:
|
|
1267
|
+
costUsd: treeAggregateColumns.costUsd,
|
|
1019
1268
|
date: activityDate,
|
|
1020
|
-
durationMs:
|
|
1021
|
-
tokens:
|
|
1022
|
-
}).from(juniorConversations).
|
|
1269
|
+
durationMs: treeAggregateColumns.durationMs,
|
|
1270
|
+
tokens: treeAggregateColumns.tokens
|
|
1271
|
+
}).from(juniorConversations).innerJoin(
|
|
1272
|
+
treeConversation,
|
|
1273
|
+
eq7(
|
|
1274
|
+
treeConversation.rootConversationId,
|
|
1275
|
+
juniorConversations.conversationId
|
|
1276
|
+
)
|
|
1277
|
+
).where(where).groupBy(activityDate)
|
|
1023
1278
|
]);
|
|
1024
1279
|
return { actorRows, locationRows, metricRows, totals: totalsRows[0] };
|
|
1025
1280
|
}
|
|
@@ -1062,17 +1317,18 @@ async function readConversationStats() {
|
|
|
1062
1317
|
await readConversationStatsFromSql()
|
|
1063
1318
|
);
|
|
1064
1319
|
}
|
|
1065
|
-
var stats_default = {
|
|
1320
|
+
var stats_default = defineApiRoute({
|
|
1066
1321
|
method: "get",
|
|
1067
1322
|
path: "/stats",
|
|
1068
|
-
|
|
1069
|
-
|
|
1323
|
+
responseSchema: conversationStatsReportSchema,
|
|
1324
|
+
handler: readConversationStats
|
|
1325
|
+
});
|
|
1070
1326
|
|
|
1071
1327
|
// src/api/conversations/routes.ts
|
|
1072
1328
|
var routes = [list_default, stats_default, archive_default, detail_default];
|
|
1073
1329
|
function createConversationRoutes() {
|
|
1074
1330
|
const app = new Hono();
|
|
1075
|
-
|
|
1331
|
+
registerApiRoutes(app, routes);
|
|
1076
1332
|
return app;
|
|
1077
1333
|
}
|
|
1078
1334
|
|
|
@@ -1080,28 +1336,10 @@ function createConversationRoutes() {
|
|
|
1080
1336
|
import { Hono as Hono2 } from "hono";
|
|
1081
1337
|
|
|
1082
1338
|
// src/api/locations/query.ts
|
|
1083
|
-
import { and as
|
|
1339
|
+
import { and as and7, asc as asc3, desc as desc2, eq as eq8, gte as gte2, isNull as isNull4, sql as sql6 } from "drizzle-orm";
|
|
1340
|
+
import { alias as alias4 } from "drizzle-orm/pg-core";
|
|
1084
1341
|
|
|
1085
1342
|
// src/api/conversations/reporting.ts
|
|
1086
|
-
var PRIVATE_CONVERSATION_LABEL2 = "Private Conversation";
|
|
1087
|
-
function channelFromConversationId(conversationId) {
|
|
1088
|
-
const [provider, channel] = conversationId.split(":");
|
|
1089
|
-
return provider === "slack" && channel ? channel : void 0;
|
|
1090
|
-
}
|
|
1091
|
-
function surfaceFromRow(row) {
|
|
1092
|
-
if (row.source === "api" || row.source === "scheduler" || row.source === "slack") {
|
|
1093
|
-
return row.source;
|
|
1094
|
-
}
|
|
1095
|
-
if (row.conversationId.startsWith("slack:")) return "slack";
|
|
1096
|
-
if (row.conversationId.startsWith("scheduler:")) return "scheduler";
|
|
1097
|
-
if (row.conversationId.startsWith("api:")) return "api";
|
|
1098
|
-
return "internal";
|
|
1099
|
-
}
|
|
1100
|
-
function statusFromRow(row) {
|
|
1101
|
-
if (row.executionStatus === "failed") return "failed";
|
|
1102
|
-
if (row.executionStatus === "idle") return "completed";
|
|
1103
|
-
return "active";
|
|
1104
|
-
}
|
|
1105
1343
|
function surfaceLabel2(surface) {
|
|
1106
1344
|
if (surface === "scheduler") return "Scheduler";
|
|
1107
1345
|
if (surface === "api") return "API";
|
|
@@ -1123,22 +1361,6 @@ function slackLocationLabel(args) {
|
|
|
1123
1361
|
}
|
|
1124
1362
|
return name || channelId;
|
|
1125
1363
|
}
|
|
1126
|
-
function channelNameFromRow(row) {
|
|
1127
|
-
if (row.destinationVisibility !== "public") {
|
|
1128
|
-
return PRIVATE_CONVERSATION_LABEL2;
|
|
1129
|
-
}
|
|
1130
|
-
return row.channelName ?? void 0;
|
|
1131
|
-
}
|
|
1132
|
-
function titleFromRow(row, surface) {
|
|
1133
|
-
if (row.destinationVisibility !== "public") {
|
|
1134
|
-
return PRIVATE_CONVERSATION_LABEL2;
|
|
1135
|
-
}
|
|
1136
|
-
const channel = channelFromConversationId(row.conversationId);
|
|
1137
|
-
return row.title ?? slackLocationLabel({
|
|
1138
|
-
channel,
|
|
1139
|
-
channelName: row.channelName ?? void 0
|
|
1140
|
-
}) ?? surfaceLabel2(surface);
|
|
1141
|
-
}
|
|
1142
1364
|
function actorFromRow(row) {
|
|
1143
1365
|
const actor = {
|
|
1144
1366
|
...row.email ? { email: row.email } : {},
|
|
@@ -1148,34 +1370,42 @@ function actorFromRow(row) {
|
|
|
1148
1370
|
};
|
|
1149
1371
|
return Object.keys(actor).length ? actor : void 0;
|
|
1150
1372
|
}
|
|
1151
|
-
function summaryFromRow(row) {
|
|
1152
|
-
const
|
|
1153
|
-
const
|
|
1154
|
-
const channelName = channelNameFromRow(row);
|
|
1155
|
-
const channelNameRedacted = row.destinationVisibility !== "public";
|
|
1156
|
-
const actorIdentity = actorFromRow(row);
|
|
1157
|
-
return {
|
|
1373
|
+
function summaryFromRow(row, options = {}) {
|
|
1374
|
+
const actor = actorFromRow(row);
|
|
1375
|
+
const conversation = {
|
|
1158
1376
|
conversationId: row.conversationId,
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
...
|
|
1169
|
-
...
|
|
1170
|
-
...channelName ? { channelName } : {},
|
|
1171
|
-
...channelNameRedacted ? { channelNameRedacted: true } : {},
|
|
1172
|
-
...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {}
|
|
1377
|
+
createdAtMs: row.createdAt.getTime(),
|
|
1378
|
+
lastActivityAtMs: row.lastActivityAt.getTime(),
|
|
1379
|
+
updatedAtMs: row.updatedAt.getTime(),
|
|
1380
|
+
execution: {
|
|
1381
|
+
status: row.executionStatus,
|
|
1382
|
+
...row.executionUpdatedAt ? { updatedAtMs: row.executionUpdatedAt.getTime() } : {}
|
|
1383
|
+
},
|
|
1384
|
+
...actor ? { actor } : {},
|
|
1385
|
+
...row.channelName ? { channelName: row.channelName } : {},
|
|
1386
|
+
...row.source ? { source: row.source } : {},
|
|
1387
|
+
...row.title ? { title: row.title } : {}
|
|
1173
1388
|
};
|
|
1389
|
+
return conversationSummaryFromStoredConversation({
|
|
1390
|
+
access: options.access,
|
|
1391
|
+
conversation,
|
|
1392
|
+
durationMs: options.metrics?.durationMs ?? row.durationMs,
|
|
1393
|
+
...options.access?.visibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
1394
|
+
usage: options.metrics?.usage ?? row.usage ?? void 0
|
|
1395
|
+
});
|
|
1174
1396
|
}
|
|
1175
1397
|
|
|
1176
1398
|
// src/api/locations/query.ts
|
|
1177
1399
|
var RECENT_LIMIT = 25;
|
|
1178
1400
|
var ACTIVITY_DAYS = 90;
|
|
1401
|
+
var treeConversation2 = alias4(
|
|
1402
|
+
juniorConversations,
|
|
1403
|
+
"location_tree_conversation"
|
|
1404
|
+
);
|
|
1405
|
+
var treeAggregateColumns2 = conversationAggregateColumns({
|
|
1406
|
+
metrics: treeConversation2,
|
|
1407
|
+
roots: juniorConversations
|
|
1408
|
+
});
|
|
1179
1409
|
function emptyMetrics() {
|
|
1180
1410
|
return {
|
|
1181
1411
|
active: 0,
|
|
@@ -1211,13 +1441,13 @@ function emptyActor(actor) {
|
|
|
1211
1441
|
};
|
|
1212
1442
|
}
|
|
1213
1443
|
function topLevelWhere() {
|
|
1214
|
-
return
|
|
1444
|
+
return isNull4(juniorConversations.parentConversationId);
|
|
1215
1445
|
}
|
|
1216
1446
|
function publicLocationWhere(destinationId) {
|
|
1217
|
-
return
|
|
1447
|
+
return and7(
|
|
1218
1448
|
topLevelWhere(),
|
|
1219
|
-
|
|
1220
|
-
|
|
1449
|
+
eq8(juniorDestinations.id, destinationId),
|
|
1450
|
+
eq8(juniorDestinations.visibility, "public")
|
|
1221
1451
|
);
|
|
1222
1452
|
}
|
|
1223
1453
|
function locationColumns() {
|
|
@@ -1264,26 +1494,32 @@ function locationFromAggregate(row) {
|
|
|
1264
1494
|
async function directoryRows(db) {
|
|
1265
1495
|
return db.select({
|
|
1266
1496
|
...locationColumns(),
|
|
1267
|
-
...
|
|
1497
|
+
...treeAggregateColumns2,
|
|
1268
1498
|
...conversationRangeColumns()
|
|
1269
|
-
}).from(juniorConversations).
|
|
1499
|
+
}).from(juniorConversations).innerJoin(
|
|
1500
|
+
treeConversation2,
|
|
1501
|
+
eq8(
|
|
1502
|
+
treeConversation2.rootConversationId,
|
|
1503
|
+
juniorConversations.conversationId
|
|
1504
|
+
)
|
|
1505
|
+
).leftJoin(
|
|
1270
1506
|
juniorDestinations,
|
|
1271
|
-
|
|
1507
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1272
1508
|
).where(topLevelWhere()).groupBy(...locationGroupBy());
|
|
1273
1509
|
}
|
|
1274
1510
|
async function directoryActivityRows(db, start) {
|
|
1275
|
-
const date =
|
|
1511
|
+
const date = sql6`TO_CHAR(
|
|
1276
1512
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1277
1513
|
'YYYY-MM-DD'
|
|
1278
1514
|
)`;
|
|
1279
1515
|
return db.select({
|
|
1280
|
-
conversations:
|
|
1516
|
+
conversations: sql6`COUNT(*)::integer`,
|
|
1281
1517
|
date,
|
|
1282
1518
|
visibility: juniorDestinations.visibility
|
|
1283
1519
|
}).from(juniorConversations).leftJoin(
|
|
1284
1520
|
juniorDestinations,
|
|
1285
|
-
|
|
1286
|
-
).where(
|
|
1521
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1522
|
+
).where(and7(topLevelWhere(), gte2(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
|
|
1287
1523
|
}
|
|
1288
1524
|
function directoryActivityDays(rows, nowMs) {
|
|
1289
1525
|
const days = /* @__PURE__ */ new Map();
|
|
@@ -1355,9 +1591,8 @@ async function recentLocationRows(db, locationId) {
|
|
|
1355
1591
|
conversationId: juniorConversations.conversationId,
|
|
1356
1592
|
createdAt: juniorConversations.createdAt,
|
|
1357
1593
|
destinationId: juniorDestinations.id,
|
|
1358
|
-
destinationVisibility: juniorDestinations.visibility,
|
|
1359
1594
|
durationMs: juniorConversations.durationMs,
|
|
1360
|
-
email:
|
|
1595
|
+
email: sql6`COALESCE(
|
|
1361
1596
|
${juniorUsers.primaryEmailNormalized},
|
|
1362
1597
|
${juniorIdentities.email}
|
|
1363
1598
|
)`,
|
|
@@ -1366,7 +1601,7 @@ async function recentLocationRows(db, locationId) {
|
|
|
1366
1601
|
fullName: juniorUsers.displayName,
|
|
1367
1602
|
handle: juniorIdentities.handle,
|
|
1368
1603
|
lastActivityAt: juniorConversations.lastActivityAt,
|
|
1369
|
-
providerSubjectId:
|
|
1604
|
+
providerSubjectId: sql6`CASE
|
|
1370
1605
|
WHEN ${juniorIdentities.provider} = 'slack'
|
|
1371
1606
|
THEN ${juniorIdentities.providerSubjectId}
|
|
1372
1607
|
ELSE NULL
|
|
@@ -1377,44 +1612,59 @@ async function recentLocationRows(db, locationId) {
|
|
|
1377
1612
|
usage: juniorConversations.usage
|
|
1378
1613
|
}).from(juniorConversations).innerJoin(
|
|
1379
1614
|
juniorDestinations,
|
|
1380
|
-
|
|
1615
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1381
1616
|
).leftJoin(
|
|
1382
1617
|
juniorIdentities,
|
|
1383
|
-
|
|
1384
|
-
).leftJoin(juniorUsers,
|
|
1385
|
-
|
|
1618
|
+
eq8(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1619
|
+
).leftJoin(juniorUsers, eq8(juniorUsers.id, juniorIdentities.userId)).where(
|
|
1620
|
+
and7(
|
|
1386
1621
|
publicLocationWhere(locationId),
|
|
1387
|
-
|
|
1622
|
+
isNull4(juniorConversations.archivedAt)
|
|
1388
1623
|
)
|
|
1389
1624
|
).orderBy(
|
|
1390
1625
|
desc2(juniorConversations.lastActivityAt),
|
|
1391
|
-
|
|
1626
|
+
asc3(juniorConversations.conversationId)
|
|
1392
1627
|
).limit(RECENT_LIMIT);
|
|
1393
1628
|
}
|
|
1394
|
-
async function readLocationDetailFromSql(locationId) {
|
|
1629
|
+
async function readLocationDetailFromSql(locationId, options = {}) {
|
|
1395
1630
|
const nowMs = Date.now();
|
|
1396
1631
|
const end = new Date(nowMs);
|
|
1397
1632
|
end.setUTCHours(0, 0, 0, 0);
|
|
1398
1633
|
const start = new Date(end);
|
|
1399
1634
|
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
1400
1635
|
const where = publicLocationWhere(locationId);
|
|
1401
|
-
const activityDate =
|
|
1636
|
+
const activityDate = sql6`TO_CHAR(
|
|
1402
1637
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1403
1638
|
'YYYY-MM-DD'
|
|
1404
1639
|
)`;
|
|
1405
1640
|
const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
|
|
1406
1641
|
getDb().select({
|
|
1407
1642
|
...locationColumns(),
|
|
1408
|
-
...
|
|
1643
|
+
...treeAggregateColumns2,
|
|
1409
1644
|
...conversationRangeColumns()
|
|
1410
1645
|
}).from(juniorConversations).innerJoin(
|
|
1646
|
+
treeConversation2,
|
|
1647
|
+
eq8(
|
|
1648
|
+
treeConversation2.rootConversationId,
|
|
1649
|
+
juniorConversations.conversationId
|
|
1650
|
+
)
|
|
1651
|
+
).innerJoin(
|
|
1411
1652
|
juniorDestinations,
|
|
1412
|
-
|
|
1653
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1413
1654
|
).where(where).groupBy(...locationGroupBy()),
|
|
1414
|
-
getDb().select({
|
|
1655
|
+
getDb().select({
|
|
1656
|
+
date: activityDate,
|
|
1657
|
+
...treeAggregateColumns2
|
|
1658
|
+
}).from(juniorConversations).innerJoin(
|
|
1659
|
+
treeConversation2,
|
|
1660
|
+
eq8(
|
|
1661
|
+
treeConversation2.rootConversationId,
|
|
1662
|
+
juniorConversations.conversationId
|
|
1663
|
+
)
|
|
1664
|
+
).innerJoin(
|
|
1415
1665
|
juniorDestinations,
|
|
1416
|
-
|
|
1417
|
-
).where(
|
|
1666
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1667
|
+
).where(and7(where, gte2(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
1418
1668
|
getDb().select({
|
|
1419
1669
|
actorIdentityId: juniorConversations.actorIdentityId,
|
|
1420
1670
|
email: juniorUsers.primaryEmailNormalized,
|
|
@@ -1423,14 +1673,20 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1423
1673
|
identityEmail: juniorIdentities.email,
|
|
1424
1674
|
identityProvider: juniorIdentities.provider,
|
|
1425
1675
|
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
1426
|
-
...
|
|
1676
|
+
...treeAggregateColumns2
|
|
1427
1677
|
}).from(juniorConversations).innerJoin(
|
|
1678
|
+
treeConversation2,
|
|
1679
|
+
eq8(
|
|
1680
|
+
treeConversation2.rootConversationId,
|
|
1681
|
+
juniorConversations.conversationId
|
|
1682
|
+
)
|
|
1683
|
+
).innerJoin(
|
|
1428
1684
|
juniorDestinations,
|
|
1429
|
-
|
|
1685
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1430
1686
|
).leftJoin(
|
|
1431
1687
|
juniorIdentities,
|
|
1432
|
-
|
|
1433
|
-
).leftJoin(juniorUsers,
|
|
1688
|
+
eq8(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1689
|
+
).leftJoin(juniorUsers, eq8(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
1434
1690
|
juniorConversations.actorIdentityId,
|
|
1435
1691
|
juniorUsers.primaryEmailNormalized,
|
|
1436
1692
|
juniorUsers.displayName,
|
|
@@ -1470,6 +1726,15 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1470
1726
|
actors.push(item);
|
|
1471
1727
|
}
|
|
1472
1728
|
const activity = activityDays(days, nowMs, ACTIVITY_DAYS);
|
|
1729
|
+
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
1730
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
1731
|
+
readConversationAccessFromSql(
|
|
1732
|
+
getDb(),
|
|
1733
|
+
recentConversationIds,
|
|
1734
|
+
options.verifiedViewerEmail
|
|
1735
|
+
),
|
|
1736
|
+
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
1737
|
+
]);
|
|
1473
1738
|
return {
|
|
1474
1739
|
...location,
|
|
1475
1740
|
activityDays: activity,
|
|
@@ -1477,7 +1742,12 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1477
1742
|
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
1478
1743
|
),
|
|
1479
1744
|
generatedAt: new Date(nowMs).toISOString(),
|
|
1480
|
-
recentConversations: recentRows.map(
|
|
1745
|
+
recentConversations: recentRows.map(
|
|
1746
|
+
(row) => summaryFromRow(row, {
|
|
1747
|
+
access: accessByConversation.get(row.conversationId),
|
|
1748
|
+
metrics: metricsByRoot.get(row.conversationId)
|
|
1749
|
+
})
|
|
1750
|
+
),
|
|
1481
1751
|
source: "conversation_index",
|
|
1482
1752
|
windowEnd: end.toISOString(),
|
|
1483
1753
|
windowStart: activity[0] ? `${activity[0].date}T00:00:00.000Z` : end.toISOString()
|
|
@@ -1485,19 +1755,25 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1485
1755
|
}
|
|
1486
1756
|
|
|
1487
1757
|
// src/api/locations/detail.ts
|
|
1488
|
-
async function readLocationDetail(locationId) {
|
|
1489
|
-
const report = await readLocationDetailFromSql(locationId);
|
|
1758
|
+
async function readLocationDetail(locationId, options = {}) {
|
|
1759
|
+
const report = await readLocationDetailFromSql(locationId, options);
|
|
1490
1760
|
return report ? locationDetailReportSchema.parse(report) : void 0;
|
|
1491
1761
|
}
|
|
1492
|
-
var detail_default2 = {
|
|
1762
|
+
var detail_default2 = defineApiRoute({
|
|
1493
1763
|
method: "get",
|
|
1494
1764
|
path: "/:locationId",
|
|
1765
|
+
responseSchema: locationDetailReportSchema,
|
|
1495
1766
|
handler: async (c) => {
|
|
1496
1767
|
const { locationId } = parseParams(locationParamsSchema, c.req.param());
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1768
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
1769
|
+
const report = await readLocationDetail(
|
|
1770
|
+
locationId,
|
|
1771
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
1772
|
+
);
|
|
1773
|
+
if (!report) throwApiError(404, "Location not found.");
|
|
1774
|
+
return report;
|
|
1499
1775
|
}
|
|
1500
|
-
};
|
|
1776
|
+
});
|
|
1501
1777
|
|
|
1502
1778
|
// src/api/locations/list.ts
|
|
1503
1779
|
async function readLocationDirectory() {
|
|
@@ -1505,17 +1781,18 @@ async function readLocationDirectory() {
|
|
|
1505
1781
|
await readLocationDirectoryFromSql()
|
|
1506
1782
|
);
|
|
1507
1783
|
}
|
|
1508
|
-
var list_default2 = {
|
|
1784
|
+
var list_default2 = defineApiRoute({
|
|
1509
1785
|
method: "get",
|
|
1510
1786
|
path: "/",
|
|
1511
|
-
|
|
1512
|
-
|
|
1787
|
+
responseSchema: locationDirectoryReportSchema,
|
|
1788
|
+
handler: readLocationDirectory
|
|
1789
|
+
});
|
|
1513
1790
|
|
|
1514
1791
|
// src/api/locations/routes.ts
|
|
1515
1792
|
var routes2 = [list_default2, detail_default2];
|
|
1516
1793
|
function createLocationRoutes() {
|
|
1517
1794
|
const app = new Hono2();
|
|
1518
|
-
|
|
1795
|
+
registerApiRoutes(app, routes2);
|
|
1519
1796
|
return app;
|
|
1520
1797
|
}
|
|
1521
1798
|
|
|
@@ -1523,12 +1800,68 @@ function createLocationRoutes() {
|
|
|
1523
1800
|
import { Hono as Hono3 } from "hono";
|
|
1524
1801
|
|
|
1525
1802
|
// src/api/people/list.query.ts
|
|
1526
|
-
import { and as
|
|
1803
|
+
import { and as and9, eq as eq10, gte as gte3, sql as sql8 } from "drizzle-orm";
|
|
1527
1804
|
|
|
1528
1805
|
// src/api/people/shared.ts
|
|
1529
|
-
import { and as
|
|
1806
|
+
import { and as and8, asc as asc4, desc as desc3, eq as eq9, isNull as isNull5, notExists, or, sql as sql7 } from "drizzle-orm";
|
|
1807
|
+
import { alias as alias5 } from "drizzle-orm/pg-core";
|
|
1530
1808
|
var RECENT_LIMIT2 = 25;
|
|
1531
1809
|
var ACTIVITY_DAYS2 = 365;
|
|
1810
|
+
var peopleTreeConversation = alias5(
|
|
1811
|
+
juniorConversations,
|
|
1812
|
+
"people_tree_conversation"
|
|
1813
|
+
);
|
|
1814
|
+
var peopleRootConversation = alias5(
|
|
1815
|
+
juniorConversations,
|
|
1816
|
+
"people_root_conversation"
|
|
1817
|
+
);
|
|
1818
|
+
var peopleRootIdentity = alias5(juniorIdentities, "people_root_identity");
|
|
1819
|
+
var peopleTreeAggregateColumns = conversationAggregateColumns({
|
|
1820
|
+
metrics: peopleTreeConversation,
|
|
1821
|
+
roots: juniorConversations
|
|
1822
|
+
});
|
|
1823
|
+
function peopleTreeMetricsJoin() {
|
|
1824
|
+
const rootOwnedByActor = getDb().select({ conversationId: peopleRootConversation.conversationId }).from(peopleRootConversation).innerJoin(
|
|
1825
|
+
peopleRootIdentity,
|
|
1826
|
+
eq9(peopleRootIdentity.id, peopleRootConversation.actorIdentityId)
|
|
1827
|
+
).where(
|
|
1828
|
+
and8(
|
|
1829
|
+
eq9(
|
|
1830
|
+
peopleRootConversation.conversationId,
|
|
1831
|
+
juniorConversations.rootConversationId
|
|
1832
|
+
),
|
|
1833
|
+
isNull5(peopleRootConversation.parentConversationId),
|
|
1834
|
+
eq9(
|
|
1835
|
+
peopleRootConversation.rootConversationId,
|
|
1836
|
+
peopleRootConversation.conversationId
|
|
1837
|
+
),
|
|
1838
|
+
eq9(
|
|
1839
|
+
peopleRootIdentity.emailNormalized,
|
|
1840
|
+
juniorUsers.primaryEmailNormalized
|
|
1841
|
+
),
|
|
1842
|
+
eq9(peopleRootIdentity.emailVerified, true)
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
return and8(
|
|
1846
|
+
eq9(
|
|
1847
|
+
peopleTreeConversation.rootConversationId,
|
|
1848
|
+
juniorConversations.rootConversationId
|
|
1849
|
+
),
|
|
1850
|
+
or(
|
|
1851
|
+
eq9(
|
|
1852
|
+
juniorConversations.rootConversationId,
|
|
1853
|
+
juniorConversations.conversationId
|
|
1854
|
+
),
|
|
1855
|
+
and8(
|
|
1856
|
+
eq9(
|
|
1857
|
+
peopleTreeConversation.conversationId,
|
|
1858
|
+
juniorConversations.conversationId
|
|
1859
|
+
),
|
|
1860
|
+
notExists(rootOwnedByActor)
|
|
1861
|
+
)
|
|
1862
|
+
)
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1532
1865
|
function normalizeEmail(email) {
|
|
1533
1866
|
const normalized = email?.trim().toLowerCase();
|
|
1534
1867
|
return normalized || void 0;
|
|
@@ -1570,11 +1903,11 @@ function statsItems2(map) {
|
|
|
1570
1903
|
}
|
|
1571
1904
|
function verifiedActorWhere(email) {
|
|
1572
1905
|
const normalizedEmail = normalizeEmail(email);
|
|
1573
|
-
return
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
normalizedEmail ?
|
|
1906
|
+
return and8(
|
|
1907
|
+
eq9(juniorIdentities.provider, "slack"),
|
|
1908
|
+
eq9(juniorIdentities.emailVerified, true),
|
|
1909
|
+
sql7`${juniorUsers.primaryEmailNormalized} IS NOT NULL`,
|
|
1910
|
+
normalizedEmail ? eq9(juniorUsers.primaryEmailNormalized, normalizedEmail) : void 0
|
|
1578
1911
|
);
|
|
1579
1912
|
}
|
|
1580
1913
|
async function recentActorRows(email) {
|
|
@@ -1583,7 +1916,6 @@ async function recentActorRows(email) {
|
|
|
1583
1916
|
conversationId: juniorConversations.conversationId,
|
|
1584
1917
|
createdAt: juniorConversations.createdAt,
|
|
1585
1918
|
destinationId: juniorDestinations.id,
|
|
1586
|
-
destinationVisibility: juniorDestinations.visibility,
|
|
1587
1919
|
durationMs: juniorConversations.durationMs,
|
|
1588
1920
|
email: juniorUsers.primaryEmailNormalized,
|
|
1589
1921
|
executionStatus: juniorConversations.executionStatus,
|
|
@@ -1598,15 +1930,15 @@ async function recentActorRows(email) {
|
|
|
1598
1930
|
usage: juniorConversations.usage
|
|
1599
1931
|
}).from(juniorConversations).innerJoin(
|
|
1600
1932
|
juniorIdentities,
|
|
1601
|
-
|
|
1602
|
-
).innerJoin(juniorUsers,
|
|
1933
|
+
eq9(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1934
|
+
).innerJoin(juniorUsers, eq9(juniorUsers.id, juniorIdentities.userId)).leftJoin(
|
|
1603
1935
|
juniorDestinations,
|
|
1604
|
-
|
|
1936
|
+
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
1605
1937
|
).where(
|
|
1606
|
-
|
|
1938
|
+
and8(verifiedActorWhere(email), isNull5(juniorConversations.archivedAt))
|
|
1607
1939
|
).orderBy(
|
|
1608
1940
|
desc3(juniorConversations.lastActivityAt),
|
|
1609
|
-
|
|
1941
|
+
asc4(juniorConversations.conversationId)
|
|
1610
1942
|
).limit(RECENT_LIMIT2);
|
|
1611
1943
|
}
|
|
1612
1944
|
|
|
@@ -1632,7 +1964,7 @@ function directoryActivityDays2(rows, nowMs) {
|
|
|
1632
1964
|
async function readPeopleListFromSql() {
|
|
1633
1965
|
const nowMs = Date.now();
|
|
1634
1966
|
const { end, start } = activityWindow(nowMs);
|
|
1635
|
-
const activityDate =
|
|
1967
|
+
const activityDate = sql8`TO_CHAR(
|
|
1636
1968
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1637
1969
|
'YYYY-MM-DD'
|
|
1638
1970
|
)`;
|
|
@@ -1640,24 +1972,24 @@ async function readPeopleListFromSql() {
|
|
|
1640
1972
|
getDb().select({
|
|
1641
1973
|
email: juniorUsers.primaryEmailNormalized,
|
|
1642
1974
|
fullName: juniorUsers.displayName,
|
|
1643
|
-
slackUserId:
|
|
1644
|
-
slackUserName:
|
|
1975
|
+
slackUserId: sql8`MAX(${juniorIdentities.providerSubjectId})`,
|
|
1976
|
+
slackUserName: sql8`MAX(${juniorIdentities.handle})`,
|
|
1645
1977
|
activeDays: conversationActiveDaysColumn(),
|
|
1646
|
-
...
|
|
1978
|
+
...peopleTreeAggregateColumns,
|
|
1647
1979
|
...conversationRangeColumns()
|
|
1648
1980
|
}).from(juniorConversations).innerJoin(
|
|
1649
1981
|
juniorIdentities,
|
|
1650
|
-
|
|
1651
|
-
).innerJoin(juniorUsers,
|
|
1982
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1983
|
+
).innerJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, peopleTreeMetricsJoin()).where(verifiedActorWhere()).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
1652
1984
|
getDb().select({
|
|
1653
|
-
activePeople:
|
|
1654
|
-
conversations:
|
|
1985
|
+
activePeople: sql8`COUNT(DISTINCT ${juniorUsers.id})::int`,
|
|
1986
|
+
conversations: sql8`COUNT(*)::int`,
|
|
1655
1987
|
date: activityDate
|
|
1656
1988
|
}).from(juniorConversations).innerJoin(
|
|
1657
1989
|
juniorIdentities,
|
|
1658
|
-
|
|
1659
|
-
).innerJoin(juniorUsers,
|
|
1660
|
-
|
|
1990
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1991
|
+
).innerJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).where(
|
|
1992
|
+
and9(
|
|
1661
1993
|
verifiedActorWhere(),
|
|
1662
1994
|
gte3(juniorConversations.lastActivityAt, start)
|
|
1663
1995
|
)
|
|
@@ -1698,14 +2030,15 @@ async function readPeopleListFromSql() {
|
|
|
1698
2030
|
async function readPeopleList() {
|
|
1699
2031
|
return actorDirectoryReportSchema.parse(await readPeopleListFromSql());
|
|
1700
2032
|
}
|
|
1701
|
-
var list_default3 = {
|
|
2033
|
+
var list_default3 = defineApiRoute({
|
|
1702
2034
|
method: "get",
|
|
1703
2035
|
path: "/",
|
|
1704
|
-
|
|
1705
|
-
|
|
2036
|
+
responseSchema: actorDirectoryReportSchema,
|
|
2037
|
+
handler: readPeopleList
|
|
2038
|
+
});
|
|
1706
2039
|
|
|
1707
2040
|
// src/api/people/profile.query.ts
|
|
1708
|
-
import { and as
|
|
2041
|
+
import { and as and10, eq as eq11, gte as gte4, sql as sql9 } from "drizzle-orm";
|
|
1709
2042
|
function emptyProfile(email, nowMs) {
|
|
1710
2043
|
const end = new Date(nowMs);
|
|
1711
2044
|
end.setUTCHours(0, 0, 0, 0);
|
|
@@ -1740,7 +2073,7 @@ function addAggregate2(map, label, row) {
|
|
|
1740
2073
|
map.set(label, item);
|
|
1741
2074
|
}
|
|
1742
2075
|
function surfaceExpression() {
|
|
1743
|
-
return
|
|
2076
|
+
return sql9`CASE
|
|
1744
2077
|
WHEN ${juniorConversations.source} IN ('api', 'scheduler', 'slack')
|
|
1745
2078
|
THEN ${juniorConversations.source}
|
|
1746
2079
|
WHEN ${juniorConversations.conversationId} LIKE 'slack:%' THEN 'slack'
|
|
@@ -1758,7 +2091,7 @@ function locationLabel2(row) {
|
|
|
1758
2091
|
channelName: row.channelName ?? void 0
|
|
1759
2092
|
}) ?? "Conversation";
|
|
1760
2093
|
}
|
|
1761
|
-
async function readPeopleProfileFromSql(email) {
|
|
2094
|
+
async function readPeopleProfileFromSql(email, options = {}) {
|
|
1762
2095
|
const nowMs = Date.now();
|
|
1763
2096
|
const normalizedEmail = normalizeEmail(email);
|
|
1764
2097
|
if (!normalizedEmail) return emptyProfile("", nowMs);
|
|
@@ -1767,53 +2100,54 @@ async function readPeopleProfileFromSql(email) {
|
|
|
1767
2100
|
const start = new Date(end);
|
|
1768
2101
|
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
1769
2102
|
const where = verifiedActorWhere(normalizedEmail);
|
|
2103
|
+
const metricsJoin = peopleTreeMetricsJoin();
|
|
1770
2104
|
const surface = surfaceExpression();
|
|
1771
|
-
const activityDate =
|
|
2105
|
+
const activityDate = sql9`TO_CHAR(
|
|
1772
2106
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1773
2107
|
'YYYY-MM-DD'
|
|
1774
2108
|
)`;
|
|
1775
|
-
const channel =
|
|
2109
|
+
const channel = sql9`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
|
|
1776
2110
|
const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
|
|
1777
2111
|
getDb().select({
|
|
1778
2112
|
email: juniorUsers.primaryEmailNormalized,
|
|
1779
2113
|
fullName: juniorUsers.displayName,
|
|
1780
|
-
slackUserId:
|
|
1781
|
-
slackUserName:
|
|
2114
|
+
slackUserId: sql9`MAX(${juniorIdentities.providerSubjectId})`,
|
|
2115
|
+
slackUserName: sql9`MAX(${juniorIdentities.handle})`,
|
|
1782
2116
|
activeDays: conversationActiveDaysColumn(),
|
|
1783
|
-
...
|
|
2117
|
+
...peopleTreeAggregateColumns
|
|
1784
2118
|
}).from(juniorConversations).innerJoin(
|
|
1785
2119
|
juniorIdentities,
|
|
1786
|
-
|
|
1787
|
-
).innerJoin(juniorUsers,
|
|
2120
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2121
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(juniorUsers.primaryEmailNormalized, juniorUsers.displayName),
|
|
1788
2122
|
getDb().select({
|
|
1789
2123
|
date: activityDate,
|
|
1790
|
-
...
|
|
2124
|
+
...peopleTreeAggregateColumns
|
|
1791
2125
|
}).from(juniorConversations).innerJoin(
|
|
1792
2126
|
juniorIdentities,
|
|
1793
|
-
|
|
1794
|
-
).innerJoin(juniorUsers,
|
|
2127
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2128
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(and10(where, gte4(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
1795
2129
|
getDb().select({
|
|
1796
2130
|
channel,
|
|
1797
2131
|
channelName: juniorConversations.channelName,
|
|
1798
2132
|
destinationVisibility: juniorDestinations.visibility,
|
|
1799
2133
|
surface,
|
|
1800
|
-
...
|
|
2134
|
+
...peopleTreeAggregateColumns
|
|
1801
2135
|
}).from(juniorConversations).innerJoin(
|
|
1802
2136
|
juniorIdentities,
|
|
1803
|
-
|
|
1804
|
-
).innerJoin(juniorUsers,
|
|
2137
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2138
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).leftJoin(
|
|
1805
2139
|
juniorDestinations,
|
|
1806
|
-
|
|
2140
|
+
eq11(juniorDestinations.id, juniorConversations.destinationId)
|
|
1807
2141
|
).where(where).groupBy(
|
|
1808
2142
|
channel,
|
|
1809
2143
|
juniorConversations.channelName,
|
|
1810
2144
|
juniorDestinations.visibility,
|
|
1811
2145
|
surface
|
|
1812
2146
|
),
|
|
1813
|
-
getDb().select({ surface, ...
|
|
2147
|
+
getDb().select({ surface, ...peopleTreeAggregateColumns }).from(juniorConversations).innerJoin(
|
|
1814
2148
|
juniorIdentities,
|
|
1815
|
-
|
|
1816
|
-
).innerJoin(juniorUsers,
|
|
2149
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2150
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(surface),
|
|
1817
2151
|
recentActorRows(normalizedEmail)
|
|
1818
2152
|
]);
|
|
1819
2153
|
const totalsRow = totalsRows[0];
|
|
@@ -1847,11 +2181,25 @@ async function readPeopleProfileFromSql(email) {
|
|
|
1847
2181
|
row
|
|
1848
2182
|
);
|
|
1849
2183
|
}
|
|
2184
|
+
const recentConversationIds = recentRows.map((row) => row.conversationId);
|
|
2185
|
+
const [accessByConversation, metricsByRoot] = await Promise.all([
|
|
2186
|
+
readConversationAccessFromSql(
|
|
2187
|
+
getDb(),
|
|
2188
|
+
recentConversationIds,
|
|
2189
|
+
options.verifiedViewerEmail
|
|
2190
|
+
),
|
|
2191
|
+
readRootConversationMetricsFromSql(getDb(), recentConversationIds)
|
|
2192
|
+
]);
|
|
1850
2193
|
return {
|
|
1851
2194
|
activityDays: activityDays2(days, nowMs),
|
|
1852
2195
|
generatedAt: new Date(nowMs).toISOString(),
|
|
1853
2196
|
locations: statsItems2(locations),
|
|
1854
|
-
recentConversations: recentRows.map(
|
|
2197
|
+
recentConversations: recentRows.map(
|
|
2198
|
+
(row) => summaryFromRow(row, {
|
|
2199
|
+
access: accessByConversation.get(row.conversationId),
|
|
2200
|
+
metrics: metricsByRoot.get(row.conversationId)
|
|
2201
|
+
})
|
|
2202
|
+
),
|
|
1855
2203
|
actor,
|
|
1856
2204
|
source: "conversation_index",
|
|
1857
2205
|
surfaces: statsItems2(surfaces),
|
|
@@ -1869,23 +2217,30 @@ async function readPeopleProfileFromSql(email) {
|
|
|
1869
2217
|
}
|
|
1870
2218
|
|
|
1871
2219
|
// src/api/people/profile.ts
|
|
1872
|
-
async function readPeopleProfile(email) {
|
|
1873
|
-
return actorProfileReportSchema.parse(
|
|
2220
|
+
async function readPeopleProfile(email, options = {}) {
|
|
2221
|
+
return actorProfileReportSchema.parse(
|
|
2222
|
+
await readPeopleProfileFromSql(email, options)
|
|
2223
|
+
);
|
|
1874
2224
|
}
|
|
1875
|
-
var profile_default = {
|
|
2225
|
+
var profile_default = defineApiRoute({
|
|
1876
2226
|
method: "get",
|
|
1877
2227
|
path: "/:email",
|
|
2228
|
+
responseSchema: actorProfileReportSchema,
|
|
1878
2229
|
handler: async (c) => {
|
|
1879
2230
|
const { email } = parseParams(personParamsSchema, c.req.param());
|
|
1880
|
-
|
|
2231
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
2232
|
+
return readPeopleProfile(
|
|
2233
|
+
email,
|
|
2234
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2235
|
+
);
|
|
1881
2236
|
}
|
|
1882
|
-
};
|
|
2237
|
+
});
|
|
1883
2238
|
|
|
1884
2239
|
// src/api/people/routes.ts
|
|
1885
2240
|
var routes3 = [list_default3, profile_default];
|
|
1886
2241
|
function createPeopleRoutes() {
|
|
1887
2242
|
const app = new Hono3();
|
|
1888
|
-
|
|
2243
|
+
registerApiRoutes(app, routes3);
|
|
1889
2244
|
return app;
|
|
1890
2245
|
}
|
|
1891
2246
|
|
|
@@ -1901,7 +2256,7 @@ function readDescriptionText(home) {
|
|
|
1901
2256
|
}
|
|
1902
2257
|
}
|
|
1903
2258
|
async function readSkillReports() {
|
|
1904
|
-
const { discoverSkills } = await import("./skills-
|
|
2259
|
+
const { discoverSkills } = await import("./skills-AR4AF32L.js");
|
|
1905
2260
|
const skills = await discoverSkills();
|
|
1906
2261
|
return skillReportsSchema.parse(
|
|
1907
2262
|
skills.map((skill) => ({
|
|
@@ -1911,7 +2266,7 @@ async function readSkillReports() {
|
|
|
1911
2266
|
);
|
|
1912
2267
|
}
|
|
1913
2268
|
async function readPluginReports() {
|
|
1914
|
-
const { pluginCatalogRuntime } = await import("./catalog-runtime-
|
|
2269
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-4GI22PXK.js");
|
|
1915
2270
|
return pluginReportsSchema.parse(
|
|
1916
2271
|
pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
1917
2272
|
name: plugin.manifest.name
|
|
@@ -1921,7 +2276,7 @@ async function readPluginReports() {
|
|
|
1921
2276
|
async function readRuntimeInfoReport() {
|
|
1922
2277
|
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
1923
2278
|
import("./discovery-RMHYSRL7.js"),
|
|
1924
|
-
import("./catalog-runtime-
|
|
2279
|
+
import("./catalog-runtime-4GI22PXK.js"),
|
|
1925
2280
|
readPluginReports(),
|
|
1926
2281
|
readSkillReports()
|
|
1927
2282
|
]);
|
|
@@ -1937,7 +2292,7 @@ async function readRuntimeInfoReport() {
|
|
|
1937
2292
|
}
|
|
1938
2293
|
async function readPluginOperationalReportFeed() {
|
|
1939
2294
|
const nowMs = Date.now();
|
|
1940
|
-
const { getPluginOperationalReports } = await import("./agent-hooks-
|
|
2295
|
+
const { getPluginOperationalReports } = await import("./agent-hooks-ELT4ODFF.js");
|
|
1941
2296
|
return pluginOperationalReportFeedSchema.parse({
|
|
1942
2297
|
source: "plugins",
|
|
1943
2298
|
generatedAt: new Date(nowMs).toISOString(),
|
|
@@ -1946,28 +2301,56 @@ async function readPluginOperationalReportFeed() {
|
|
|
1946
2301
|
}
|
|
1947
2302
|
|
|
1948
2303
|
// src/api.ts
|
|
2304
|
+
var routes4 = [
|
|
2305
|
+
defineApiRoute({
|
|
2306
|
+
method: "get",
|
|
2307
|
+
path: "/api/health",
|
|
2308
|
+
responseSchema: healthReportSchema,
|
|
2309
|
+
handler: readHealthReport
|
|
2310
|
+
}),
|
|
2311
|
+
defineApiRoute({
|
|
2312
|
+
method: "get",
|
|
2313
|
+
path: "/api/runtime",
|
|
2314
|
+
responseSchema: runtimeInfoReportSchema,
|
|
2315
|
+
handler: readRuntimeInfoReport
|
|
2316
|
+
}),
|
|
2317
|
+
defineApiRoute({
|
|
2318
|
+
method: "get",
|
|
2319
|
+
path: "/api/plugins",
|
|
2320
|
+
responseSchema: pluginReportsSchema,
|
|
2321
|
+
handler: readPluginReports
|
|
2322
|
+
}),
|
|
2323
|
+
defineApiRoute({
|
|
2324
|
+
method: "get",
|
|
2325
|
+
path: "/api/skills",
|
|
2326
|
+
responseSchema: skillReportsSchema,
|
|
2327
|
+
handler: readSkillReports
|
|
2328
|
+
}),
|
|
2329
|
+
defineApiRoute({
|
|
2330
|
+
method: "get",
|
|
2331
|
+
path: "/api/plugin-reports",
|
|
2332
|
+
responseSchema: pluginOperationalReportFeedSchema,
|
|
2333
|
+
handler: readPluginOperationalReportFeed
|
|
2334
|
+
})
|
|
2335
|
+
];
|
|
1949
2336
|
function createJuniorApi() {
|
|
1950
2337
|
const app = new Hono4();
|
|
1951
|
-
app
|
|
1952
|
-
return Response.json(await readHealthReport());
|
|
1953
|
-
});
|
|
1954
|
-
app.get("/api/runtime", async () => {
|
|
1955
|
-
return Response.json(await readRuntimeInfoReport());
|
|
1956
|
-
});
|
|
1957
|
-
app.get("/api/plugins", async () => {
|
|
1958
|
-
return Response.json(await readPluginReports());
|
|
1959
|
-
});
|
|
1960
|
-
app.get("/api/skills", async () => {
|
|
1961
|
-
return Response.json(await readSkillReports());
|
|
1962
|
-
});
|
|
1963
|
-
app.get("/api/plugin-reports", async () => {
|
|
1964
|
-
return Response.json(await readPluginOperationalReportFeed());
|
|
1965
|
-
});
|
|
2338
|
+
registerApiRoutes(app, routes4);
|
|
1966
2339
|
app.route("/api/conversations", createConversationRoutes());
|
|
1967
2340
|
app.route("/api/people", createPeopleRoutes());
|
|
1968
2341
|
app.route("/api/locations", createLocationRoutes());
|
|
2342
|
+
app.notFound(
|
|
2343
|
+
() => jsonResponse(
|
|
2344
|
+
apiErrorSchema,
|
|
2345
|
+
{ error: "Resource not found." },
|
|
2346
|
+
{
|
|
2347
|
+
status: 404
|
|
2348
|
+
}
|
|
2349
|
+
)
|
|
2350
|
+
);
|
|
1969
2351
|
return app;
|
|
1970
2352
|
}
|
|
1971
2353
|
export {
|
|
1972
|
-
createJuniorApi
|
|
2354
|
+
createJuniorApi,
|
|
2355
|
+
jsonResponse
|
|
1973
2356
|
};
|