@sentry/junior 0.108.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-NU5HK3PS.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 +2 -2
- 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} +88 -9
- 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 +838 -369
- package/dist/app.d.ts +1 -0
- package/dist/app.js +1564 -486
- 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/conversations/sql/store.d.ts +0 -7
- package/dist/chat/log-context.d.ts +40 -0
- package/dist/chat/logging.d.ts +3 -26
- package/dist/chat/pi/messages.d.ts +2 -2
- package/dist/chat/pi/sql-model-usage.d.ts +4 -1
- package/dist/chat/runtime/slack-resume.d.ts +2 -2
- package/dist/chat/services/auth-pause.d.ts +4 -0
- package/dist/chat/services/context-compaction.d.ts +1 -0
- package/dist/chat/services/turn-session-record.d.ts +0 -2
- package/dist/chat/slack/reply.d.ts +7 -19
- package/dist/chat/state/conversation.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-UD6THJ2I.js → chunk-2T7TIEXI.js} +197 -12
- package/dist/{chunk-CEA3A3UA.js → chunk-2ZTTXTSZ.js} +24 -1
- package/dist/{chunk-XKB7LGIW.js → chunk-3S7YYHBI.js} +3 -139
- package/dist/chunk-3TV7OEHL.js +10 -0
- package/dist/{chunk-A5CO2EHL.js → chunk-3UBQI73J.js} +11 -15
- package/dist/{chunk-7FBGKXPE.js → chunk-42WRQLJ6.js} +10 -33
- package/dist/{chunk-TE4QHJH4.js → chunk-4QU44P7Q.js} +135 -595
- package/dist/{chunk-RT5BUHR5.js → chunk-6Y5L4BOM.js} +3 -5
- package/dist/{chunk-VIXFG2VC.js → chunk-C4MZ3GP5.js} +1 -2
- package/dist/{chunk-3RGQLX2F.js → chunk-C5XAFTHY.js} +1011 -29
- package/dist/{chunk-PUAMO4PI.js → chunk-FPK7XY3F.js} +139 -99
- package/dist/{chunk-H3QYZL7K.js → chunk-GK47NBDE.js} +28 -3
- package/dist/{chunk-O24R4DCK.js → chunk-IYKPU7H6.js} +94 -56
- package/dist/chunk-JQFJO5WH.js +477 -0
- package/dist/{chunk-VH6KWKG2.js → chunk-L5XQPJ7T.js} +3 -3
- package/dist/{chunk-2XXEKR44.js → chunk-MIAKQNYX.js} +43 -31
- package/dist/{chunk-KPMPQ6AA.js → chunk-RHHRKSPI.js} +249 -223
- package/dist/{chunk-YT72TOEK.js → chunk-SBOBTS27.js} +2 -2
- package/dist/{chunk-B5I5LMSP.js → chunk-W6RJZG7F.js} +5 -7
- package/dist/chunk-W75ZPAGG.js +44 -0
- package/dist/{chunk-SS67LUOK.js → chunk-WNOVRQ67.js} +6 -7
- package/dist/{chunk-YIPD3L6L.js → chunk-Y3H2DN6F.js} +8 -10
- package/dist/{chunk-DY7TRYB5.js → chunk-Y5FK3CNG.js} +1 -1
- package/dist/chunk-ZKIOWEFL.js +125 -0
- package/dist/cli/chat.js +23 -24
- package/dist/cli/check.js +9 -7
- package/dist/cli/plugins.js +16 -17
- package/dist/cli/snapshot-warmup.js +11 -10
- package/dist/cli/upgrade.js +314 -3505
- package/dist/db/db.d.ts +1 -1
- package/dist/db/schema/conversations.d.ts +17 -0
- package/dist/db/schema.d.ts +17 -0
- package/dist/{db-AMRBAT5D.js → db-Y6W6CWWW.js} +8 -7
- package/dist/instrumentation.js +4 -3
- package/dist/nitro.js +4 -4
- package/dist/reporting-schema.d.ts +46 -0
- package/dist/{runner-TQH5GAJ4.js → runner-BBFV5GLR.js} +17 -21
- package/dist/{skills-ITNHBARB.js → skills-AR4AF32L.js} +8 -6
- package/dist/{validation-AGQ46ZGV.js → validation-FBIEPTC2.js} +8 -6
- package/dist/version.d.ts +2 -0
- package/dist/version.js +7 -0
- package/migrations/0006_drop_legacy_conversation_messages.sql +1 -0
- package/migrations/0007_thick_sugar_man.sql +23 -0
- package/migrations/README.md +12 -9
- package/migrations/meta/0006_snapshot.json +948 -0
- package/migrations/meta/0007_snapshot.json +1013 -0
- package/migrations/meta/_journal.json +14 -0
- package/package.json +9 -5
- package/dist/catalog-runtime-FOFNPR3P.js +0 -14
- package/dist/chunk-AHJR2IFF.js +0 -115
- package/dist/chunk-B7PUBQQM.js +0 -53
- package/dist/chunk-G3E7SCME.js +0 -28
- package/dist/chunk-J3B3FPP2.js +0 -1061
- package/dist/chunk-OB42YVAE.js +0 -16
- package/dist/chunk-TWINAEZQ.js +0 -1283
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,50 +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
|
-
getSqlExecutor
|
|
41
|
+
getSqlExecutor
|
|
42
|
+
} from "./chunk-4QU44P7Q.js";
|
|
43
|
+
import {
|
|
39
44
|
juniorConversationEvents,
|
|
40
45
|
juniorConversations,
|
|
41
46
|
juniorIdentities,
|
|
42
47
|
juniorUsers
|
|
43
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-JQFJO5WH.js";
|
|
44
49
|
import "./chunk-4ZNGQH7C.js";
|
|
45
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-WNOVRQ67.js";
|
|
46
51
|
import {
|
|
47
52
|
juniorDestinations
|
|
48
53
|
} from "./chunk-VFUK3X5B.js";
|
|
49
54
|
import {
|
|
50
55
|
canExposeConversationPayload,
|
|
51
|
-
parseSlackThreadId
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
import "./chunk-
|
|
55
|
-
import "./chunk-O24R4DCK.js";
|
|
56
|
-
import "./chunk-OB42YVAE.js";
|
|
56
|
+
parseSlackThreadId
|
|
57
|
+
} from "./chunk-6Y5L4BOM.js";
|
|
58
|
+
import "./chunk-IYKPU7H6.js";
|
|
59
|
+
import "./chunk-42WRQLJ6.js";
|
|
57
60
|
import "./chunk-6GWA276C.js";
|
|
58
|
-
import "./chunk-
|
|
61
|
+
import "./chunk-W75ZPAGG.js";
|
|
62
|
+
import "./chunk-ZKIOWEFL.js";
|
|
59
63
|
import "./chunk-MLKGABMK.js";
|
|
60
64
|
|
|
61
65
|
// src/api.ts
|
|
@@ -64,32 +68,50 @@ import { Hono as Hono4 } from "hono";
|
|
|
64
68
|
// src/api/conversations/routes.ts
|
|
65
69
|
import { Hono } from "hono";
|
|
66
70
|
|
|
67
|
-
// src/api/conversations/archive.ts
|
|
68
|
-
import { and, eq, isNotNull } from "drizzle-orm";
|
|
69
|
-
import { HTTPException as HTTPException2 } from "hono/http-exception";
|
|
70
|
-
import { z } from "zod";
|
|
71
|
-
|
|
72
71
|
// src/api/http.ts
|
|
73
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
|
+
}
|
|
74
83
|
function parseParams(schema, params) {
|
|
75
84
|
const result = schema.safeParse(params);
|
|
76
85
|
if (result.success) return result.data;
|
|
77
|
-
|
|
78
|
-
cause: result.error,
|
|
79
|
-
message: "Invalid route parameters."
|
|
80
|
-
});
|
|
86
|
+
return throwApiError(400, "Invalid route parameters.", result.error);
|
|
81
87
|
}
|
|
82
88
|
function parseQuery(schema, query) {
|
|
83
89
|
const result = schema.safeParse(query);
|
|
84
90
|
if (result.success) return result.data;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
}
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
// src/api/conversations/archive.ts
|
|
92
|
-
|
|
114
|
+
import { and, eq, isNotNull } from "drizzle-orm";
|
|
93
115
|
async function archiveIfUnchanged(args) {
|
|
94
116
|
const db = getDb();
|
|
95
117
|
const rows = await db.update(juniorConversations).set({ archivedAt: args.archived ? /* @__PURE__ */ new Date() : null }).where(
|
|
@@ -107,9 +129,10 @@ async function archiveIfUnchanged(args) {
|
|
|
107
129
|
if (!args.archived && existing.archivedAt === null) return "updated";
|
|
108
130
|
return "conflict";
|
|
109
131
|
}
|
|
110
|
-
var archive_default = {
|
|
132
|
+
var archive_default = defineApiRoute({
|
|
111
133
|
method: "patch",
|
|
112
134
|
path: "/:conversationId/archive",
|
|
135
|
+
responseSchema: archiveConversationResponseSchema,
|
|
113
136
|
handler: async (c) => {
|
|
114
137
|
const { conversationId } = parseParams(
|
|
115
138
|
conversationParamsSchema,
|
|
@@ -119,35 +142,22 @@ var archive_default = {
|
|
|
119
142
|
try {
|
|
120
143
|
input = await c.req.json();
|
|
121
144
|
} catch (error) {
|
|
122
|
-
|
|
123
|
-
cause: error,
|
|
124
|
-
message: "Invalid request body."
|
|
125
|
-
});
|
|
145
|
+
throwApiError(400, "Invalid request body.", error);
|
|
126
146
|
}
|
|
127
|
-
const
|
|
128
|
-
if (!parsed.success) {
|
|
129
|
-
throw new HTTPException2(400, {
|
|
130
|
-
cause: parsed.error,
|
|
131
|
-
message: "Invalid request body."
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
const body = parsed.data;
|
|
147
|
+
const body = parseBody(archiveConversationBodySchema, input);
|
|
135
148
|
const result = await archiveIfUnchanged({ ...body, conversationId });
|
|
136
149
|
if (result === "not_found") {
|
|
137
|
-
|
|
138
|
-
{ error: "Conversation not found." },
|
|
139
|
-
{ status: 404 }
|
|
140
|
-
);
|
|
150
|
+
throwApiError(404, "Conversation not found.");
|
|
141
151
|
}
|
|
142
152
|
if (result === "conflict") {
|
|
143
|
-
|
|
144
|
-
{ error: "Conversation received new activity." },
|
|
145
|
-
{ status: 409 }
|
|
146
|
-
);
|
|
153
|
+
throwApiError(409, "Conversation received new activity.");
|
|
147
154
|
}
|
|
148
|
-
return
|
|
155
|
+
return { archived: body.archived };
|
|
149
156
|
}
|
|
150
|
-
};
|
|
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";
|
|
151
161
|
|
|
152
162
|
// src/chat/pi/sql-model-usage.ts
|
|
153
163
|
import { and as and2, eq as eq2, sql } from "drizzle-orm";
|
|
@@ -216,7 +226,7 @@ function usageFromRow(row) {
|
|
|
216
226
|
};
|
|
217
227
|
return hasAgentTurnUsage(result) ? agentTurnUsageSchema.parse(result) : void 0;
|
|
218
228
|
}
|
|
219
|
-
async function readConversationModelUsageFromSql(executor,
|
|
229
|
+
async function readConversationModelUsageFromSql(executor, options) {
|
|
220
230
|
const modelId = sql`concat(${message}->>'provider', '/', ${message}->>'model')`;
|
|
221
231
|
const inputTokens = token("input", "inputTokens");
|
|
222
232
|
const outputTokens = token("output", "outputTokens");
|
|
@@ -242,9 +252,15 @@ async function readConversationModelUsageFromSql(executor, conversationId) {
|
|
|
242
252
|
costCacheRead: summedCost(nonnegativeNumber(cost, "cacheRead")),
|
|
243
253
|
costCacheWrite: summedCost(nonnegativeNumber(cost, "cacheWrite")),
|
|
244
254
|
costTotal: summedCost(nonnegativeNumber(cost, "total"))
|
|
245
|
-
}).from(juniorConversationEvents).
|
|
255
|
+
}).from(juniorConversationEvents).innerJoin(
|
|
256
|
+
juniorConversations,
|
|
257
|
+
eq2(
|
|
258
|
+
juniorConversations.conversationId,
|
|
259
|
+
juniorConversationEvents.conversationId
|
|
260
|
+
)
|
|
261
|
+
).where(
|
|
246
262
|
and2(
|
|
247
|
-
eq2(juniorConversationEvents.conversationId, conversationId),
|
|
263
|
+
options.includeDescendants ? eq2(juniorConversations.rootConversationId, options.conversationId) : eq2(juniorConversationEvents.conversationId, options.conversationId),
|
|
248
264
|
eq2(juniorConversationEvents.type, "agent_step"),
|
|
249
265
|
sql`${message}->>'role' = 'assistant'`,
|
|
250
266
|
sql`jsonb_typeof(${message}->'provider') = 'string'`,
|
|
@@ -260,9 +276,11 @@ async function readConversationModelUsageFromSql(executor, conversationId) {
|
|
|
260
276
|
}
|
|
261
277
|
|
|
262
278
|
// src/api/conversations/events.ts
|
|
279
|
+
import { z } from "zod";
|
|
263
280
|
var conversationReportSourceEventTypes = [
|
|
264
281
|
"message",
|
|
265
282
|
"message_handled",
|
|
283
|
+
"agent_step",
|
|
266
284
|
"tool_execution_started",
|
|
267
285
|
"turn_started",
|
|
268
286
|
"turn_completed",
|
|
@@ -272,6 +290,80 @@ var conversationReportSourceEventTypes = [
|
|
|
272
290
|
"subagent_started",
|
|
273
291
|
"subagent_ended"
|
|
274
292
|
];
|
|
293
|
+
var reportingAssistantMessageSchema = z.object({
|
|
294
|
+
role: z.literal("assistant"),
|
|
295
|
+
content: z.array(z.unknown())
|
|
296
|
+
}).passthrough();
|
|
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()
|
|
302
|
+
}).passthrough();
|
|
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()
|
|
309
|
+
}).passthrough();
|
|
310
|
+
var reportingToolResultDetailsSchema = z.object({
|
|
311
|
+
ok: z.boolean().optional(),
|
|
312
|
+
status: z.string().optional()
|
|
313
|
+
}).passthrough();
|
|
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()
|
|
318
|
+
}).passthrough();
|
|
319
|
+
function sanitizeModelContentPart(value) {
|
|
320
|
+
const text = reportingTextPartSchema.safeParse(value);
|
|
321
|
+
if (text.success) return { type: "text", text: text.data.text };
|
|
322
|
+
const media = reportingMediaPartSchema.safeParse(value);
|
|
323
|
+
if (!media.success) return void 0;
|
|
324
|
+
return {
|
|
325
|
+
type: media.data.type,
|
|
326
|
+
...media.data.mimeType ? { mimeType: media.data.mimeType } : {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function modelVisibleToolOutput(content) {
|
|
330
|
+
const sanitized = content.map(sanitizeModelContentPart).filter((part) => part !== void 0);
|
|
331
|
+
if (sanitized.length === 0) return void 0;
|
|
332
|
+
if (sanitized.length !== 1) return sanitized;
|
|
333
|
+
const only = sanitized[0];
|
|
334
|
+
return only.type === "text" ? only.text : only;
|
|
335
|
+
}
|
|
336
|
+
function reportToolCalls(args) {
|
|
337
|
+
const message2 = reportingAssistantMessageSchema.safeParse(args.message);
|
|
338
|
+
if (!message2.success) return void 0;
|
|
339
|
+
const calls = message2.data.content.flatMap((part) => {
|
|
340
|
+
const call = reportingToolCallPartSchema.safeParse(part);
|
|
341
|
+
if (!call.success) return [];
|
|
342
|
+
return [
|
|
343
|
+
{
|
|
344
|
+
toolCallId: call.data.id,
|
|
345
|
+
name: call.data.name,
|
|
346
|
+
...args.canExposePayload && call.data.arguments !== void 0 ? { input: call.data.arguments } : {}
|
|
347
|
+
}
|
|
348
|
+
];
|
|
349
|
+
});
|
|
350
|
+
return calls.length > 0 ? { type: "tool_calls", calls } : void 0;
|
|
351
|
+
}
|
|
352
|
+
function reportToolResult(args) {
|
|
353
|
+
const message2 = reportingToolResultMessageSchema.safeParse(args.message);
|
|
354
|
+
if (!message2.success) return void 0;
|
|
355
|
+
const details = reportingToolResultDetailsSchema.safeParse(
|
|
356
|
+
message2.data.details
|
|
357
|
+
);
|
|
358
|
+
const outcome = message2.data.isError === true || details.success && (details.data.ok === false || details.data.status === "error") ? "error" : "completed";
|
|
359
|
+
const output = modelVisibleToolOutput(message2.data.content);
|
|
360
|
+
return {
|
|
361
|
+
type: "tool_result",
|
|
362
|
+
toolCallId: message2.data.toolCallId,
|
|
363
|
+
outcome,
|
|
364
|
+
...args.canExposePayload && output !== void 0 ? { output } : {}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
275
367
|
function reportEventData(args) {
|
|
276
368
|
const { data } = args;
|
|
277
369
|
switch (data.type) {
|
|
@@ -280,6 +372,7 @@ function reportEventData(args) {
|
|
|
280
372
|
type: "message",
|
|
281
373
|
messageId: data.messageId,
|
|
282
374
|
role: data.role,
|
|
375
|
+
...typeof data.meta?.eventType === "string" ? { eventType: data.meta.eventType } : {},
|
|
283
376
|
...args.canExposePayload ? { text: data.text } : { redacted: true }
|
|
284
377
|
};
|
|
285
378
|
case "message_handled":
|
|
@@ -314,21 +407,28 @@ function reportEventData(args) {
|
|
|
314
407
|
}
|
|
315
408
|
function projectConversationReportEvents(args) {
|
|
316
409
|
const subagentStarts = /* @__PURE__ */ new Map();
|
|
317
|
-
const toolStarts = /* @__PURE__ */ new Map();
|
|
318
410
|
const projected = [];
|
|
319
411
|
for (const event of args.events) {
|
|
320
412
|
let data;
|
|
321
|
-
if (event.data.type === "
|
|
322
|
-
|
|
323
|
-
|
|
413
|
+
if (event.data.type === "agent_step") {
|
|
414
|
+
const reportArgs = {
|
|
415
|
+
canExposePayload: args.canExposePayload,
|
|
416
|
+
message: event.data.message
|
|
417
|
+
};
|
|
418
|
+
data = reportToolCalls(reportArgs) ?? reportToolResult(reportArgs);
|
|
419
|
+
} else if (event.data.type === "tool_execution_started") {
|
|
420
|
+
data = {
|
|
421
|
+
type: "tool_started",
|
|
422
|
+
toolCallId: event.data.toolCallId,
|
|
423
|
+
name: event.data.toolName
|
|
424
|
+
};
|
|
324
425
|
} else if (event.data.type === "subagent_started") {
|
|
325
426
|
subagentStarts.set(event.data.subagentInvocationId, event.seq);
|
|
326
|
-
const toolStartedSeq = event.data.parentToolCallId ? toolStarts.get(event.data.parentToolCallId) : void 0;
|
|
327
427
|
data = {
|
|
328
428
|
type: "subagent_started",
|
|
329
429
|
childConversationId: event.data.childConversationId,
|
|
330
430
|
subagentKind: event.data.subagentKind,
|
|
331
|
-
...
|
|
431
|
+
...event.data.parentToolCallId ? { parentToolCallId: event.data.parentToolCallId } : {}
|
|
332
432
|
};
|
|
333
433
|
} else if (event.data.type === "subagent_ended") {
|
|
334
434
|
const startedSeq = subagentStarts.get(event.data.subagentInvocationId);
|
|
@@ -340,10 +440,12 @@ function projectConversationReportEvents(args) {
|
|
|
340
440
|
};
|
|
341
441
|
}
|
|
342
442
|
} else if (event.data.type === "handoff") {
|
|
343
|
-
const toolStartedSeq = event.data.triggeringToolCallId ? toolStarts.get(event.data.triggeringToolCallId) : void 0;
|
|
344
443
|
data = {
|
|
345
444
|
type: "handoff",
|
|
346
|
-
|
|
445
|
+
modelProfile: event.data.modelProfile,
|
|
446
|
+
modelId: event.data.modelId,
|
|
447
|
+
...event.data.reasoningLevel ? { reasoningLevel: event.data.reasoningLevel } : {},
|
|
448
|
+
...event.data.triggeringToolCallId ? { triggeringToolCallId: event.data.triggeringToolCallId } : {}
|
|
347
449
|
};
|
|
348
450
|
} else {
|
|
349
451
|
data = reportEventData({
|
|
@@ -364,7 +466,7 @@ function projectConversationReportEvents(args) {
|
|
|
364
466
|
}
|
|
365
467
|
|
|
366
468
|
// src/api/conversations/list.ts
|
|
367
|
-
import { and as
|
|
469
|
+
import { and as and4, asc, desc, eq as eq5, isNull as isNull2 } from "drizzle-orm";
|
|
368
470
|
|
|
369
471
|
// src/api/conversations/shared.ts
|
|
370
472
|
function slackStatsLocationLabel(input) {
|
|
@@ -435,16 +537,13 @@ function titleFromConversation(args) {
|
|
|
435
537
|
threadId: args.conversation.conversationId,
|
|
436
538
|
channelName: effectiveChannelName
|
|
437
539
|
});
|
|
438
|
-
const privateLabel =
|
|
439
|
-
conversationId: args.conversation.conversationId,
|
|
440
|
-
visibility: args.conversation.visibility
|
|
441
|
-
}) !== "public" ? privateConversationLabel(slackConversation) : void 0;
|
|
540
|
+
const privateLabel = args.canViewPrivateContent ? void 0 : privateConversationLabel(slackConversation);
|
|
442
541
|
return privateLabel ?? args.conversation.title ?? slackStatsLocationLabel({
|
|
443
542
|
channel: slackThread?.channelId,
|
|
444
543
|
channelName: effectiveChannelName
|
|
445
544
|
}) ?? surfaceFallbackLabel(args.surface);
|
|
446
545
|
}
|
|
447
|
-
function channelNameFromConversation(conversation) {
|
|
546
|
+
function channelNameFromConversation(conversation, canViewPrivateContent) {
|
|
448
547
|
const effectiveChannelName = conversation.channelName;
|
|
449
548
|
const slackThread = parseSlackThreadId(conversation.conversationId);
|
|
450
549
|
if (!effectiveChannelName && !slackThread) {
|
|
@@ -454,39 +553,45 @@ function channelNameFromConversation(conversation) {
|
|
|
454
553
|
threadId: conversation.conversationId,
|
|
455
554
|
channelName: effectiveChannelName
|
|
456
555
|
});
|
|
457
|
-
if (
|
|
458
|
-
conversationId: conversation.conversationId,
|
|
459
|
-
visibility: conversation.visibility
|
|
460
|
-
}) !== "public") {
|
|
556
|
+
if (!canViewPrivateContent) {
|
|
461
557
|
return privateConversationLabel(slackConversation);
|
|
462
558
|
}
|
|
463
559
|
return effectiveChannelName;
|
|
464
560
|
}
|
|
465
|
-
function channelNameRedactedFromConversation(conversation) {
|
|
561
|
+
function channelNameRedactedFromConversation(conversation, canViewPrivateContent) {
|
|
466
562
|
const effectiveChannelName = conversation.channelName;
|
|
467
563
|
const slackThread = parseSlackThreadId(conversation.conversationId);
|
|
468
564
|
if (!effectiveChannelName && !slackThread) {
|
|
469
565
|
return false;
|
|
470
566
|
}
|
|
471
|
-
return
|
|
472
|
-
conversationId: conversation.conversationId,
|
|
473
|
-
visibility: conversation.visibility
|
|
474
|
-
}) !== "public";
|
|
567
|
+
return !canViewPrivateContent;
|
|
475
568
|
}
|
|
476
569
|
function conversationSummaryFromStoredConversation(args) {
|
|
477
|
-
const { conversation, durationMs, usage:
|
|
570
|
+
const { conversation, durationMs, usage: usage3 } = args;
|
|
571
|
+
const canViewPrivateContent = args.access?.canViewPrivateContent ?? false;
|
|
478
572
|
const surface = surfaceFromSource(
|
|
479
573
|
conversation.source,
|
|
480
574
|
conversation.conversationId
|
|
481
575
|
);
|
|
482
576
|
const actorIdentity = actorIdentityReport(conversation.actor);
|
|
483
577
|
const slackThread = parseSlackThreadId(conversation.conversationId);
|
|
484
|
-
const channelName = channelNameFromConversation(
|
|
485
|
-
|
|
578
|
+
const channelName = channelNameFromConversation(
|
|
579
|
+
conversation,
|
|
580
|
+
canViewPrivateContent
|
|
581
|
+
);
|
|
582
|
+
const channelNameRedacted = channelNameRedactedFromConversation(
|
|
583
|
+
conversation,
|
|
584
|
+
canViewPrivateContent
|
|
585
|
+
);
|
|
486
586
|
return {
|
|
487
587
|
conversationId: conversation.conversationId,
|
|
488
588
|
cumulativeDurationMs: durationMs,
|
|
489
|
-
displayTitle: titleFromConversation({
|
|
589
|
+
displayTitle: titleFromConversation({
|
|
590
|
+
canViewPrivateContent,
|
|
591
|
+
conversation,
|
|
592
|
+
surface
|
|
593
|
+
}),
|
|
594
|
+
isParticipant: args.access?.isParticipant ?? false,
|
|
490
595
|
lastProgressAt: new Date(
|
|
491
596
|
conversation.execution.updatedAtMs ?? conversation.updatedAtMs
|
|
492
597
|
).toISOString(),
|
|
@@ -494,7 +599,7 @@ function conversationSummaryFromStoredConversation(args) {
|
|
|
494
599
|
startedAt: new Date(conversation.createdAtMs).toISOString(),
|
|
495
600
|
status: statusFromConversation(conversation),
|
|
496
601
|
surface,
|
|
497
|
-
...
|
|
602
|
+
...usage3 ? { cumulativeUsage: usage3 } : {},
|
|
498
603
|
...actorIdentity ? { actorIdentity } : {},
|
|
499
604
|
...conversation.archivedAtMs ? { archivedAt: new Date(conversation.archivedAtMs).toISOString() } : {},
|
|
500
605
|
...slackThread ? { channel: slackThread.channelId } : {},
|
|
@@ -504,6 +609,163 @@ function conversationSummaryFromStoredConversation(args) {
|
|
|
504
609
|
};
|
|
505
610
|
}
|
|
506
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
|
+
|
|
507
769
|
// src/api/conversations/list.ts
|
|
508
770
|
var CONVERSATION_FEED_LIMIT = 50;
|
|
509
771
|
async function conversationRows(db, limit, actorEmail) {
|
|
@@ -520,17 +782,17 @@ async function conversationRows(db, limit, actorEmail) {
|
|
|
520
782
|
userDisplayName: juniorUsers.displayName
|
|
521
783
|
}).from(juniorConversations).leftJoin(
|
|
522
784
|
juniorDestinations,
|
|
523
|
-
|
|
785
|
+
eq5(juniorDestinations.id, juniorConversations.destinationId)
|
|
524
786
|
).leftJoin(
|
|
525
787
|
juniorIdentities,
|
|
526
|
-
|
|
527
|
-
).leftJoin(juniorUsers,
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
actorEmail ?
|
|
532
|
-
|
|
533
|
-
|
|
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)
|
|
534
796
|
) : void 0
|
|
535
797
|
)
|
|
536
798
|
).orderBy(
|
|
@@ -565,10 +827,7 @@ function conversationFromRow(row) {
|
|
|
565
827
|
...value.channelName ? { channelName: value.channelName } : {},
|
|
566
828
|
...value.source ? { source: value.source } : {},
|
|
567
829
|
...value.title ? { title: value.title } : {},
|
|
568
|
-
...value.transcriptPurgedAt ? { transcriptPurgedAtMs: value.transcriptPurgedAt.getTime() } : {}
|
|
569
|
-
...row.destinationVisibility ? {
|
|
570
|
-
visibility: row.destinationVisibility === "public" ? "public" : "private"
|
|
571
|
-
} : {}
|
|
830
|
+
...value.transcriptPurgedAt ? { transcriptPurgedAtMs: value.transcriptPurgedAt.getTime() } : {}
|
|
572
831
|
};
|
|
573
832
|
}
|
|
574
833
|
async function readConversationRecordFromSql(conversationId) {
|
|
@@ -586,76 +845,110 @@ async function readConversationRecordFromSql(conversationId) {
|
|
|
586
845
|
userDisplayName: juniorUsers.displayName
|
|
587
846
|
}).from(juniorConversations).leftJoin(
|
|
588
847
|
juniorDestinations,
|
|
589
|
-
|
|
848
|
+
eq5(juniorDestinations.id, juniorConversations.destinationId)
|
|
590
849
|
).leftJoin(
|
|
591
850
|
juniorIdentities,
|
|
592
|
-
|
|
593
|
-
).leftJoin(juniorUsers,
|
|
851
|
+
eq5(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
852
|
+
).leftJoin(juniorUsers, eq5(juniorUsers.id, juniorIdentities.userId)).where(eq5(juniorConversations.conversationId, conversationId)).limit(1);
|
|
594
853
|
const row = rows[0];
|
|
595
854
|
return row ? {
|
|
596
855
|
conversation: conversationFromRow(row),
|
|
597
856
|
durationMs: row.conversation.durationMs,
|
|
598
857
|
...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
599
|
-
usage: row.conversation.usage
|
|
858
|
+
usage: row.conversation.usage,
|
|
859
|
+
rootConversationId: row.conversation.rootConversationId
|
|
600
860
|
} : void 0;
|
|
601
861
|
}
|
|
602
862
|
async function readConversationFeedFromSql(options = {}) {
|
|
603
863
|
const nowMs = Date.now();
|
|
864
|
+
const db = getDb();
|
|
604
865
|
const rows = await conversationRows(
|
|
605
|
-
|
|
866
|
+
db,
|
|
606
867
|
options.limit ?? CONVERSATION_FEED_LIMIT,
|
|
607
868
|
options.actorEmail
|
|
608
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
|
+
]);
|
|
609
879
|
return {
|
|
610
|
-
conversations: rows.map(
|
|
611
|
-
(row)
|
|
880
|
+
conversations: rows.map((row) => {
|
|
881
|
+
const metrics = metricsByRoot.get(row.conversation.conversationId);
|
|
882
|
+
return conversationSummaryFromStoredConversation({
|
|
612
883
|
conversation: conversationFromRow(row),
|
|
613
|
-
|
|
884
|
+
access: accessByConversation.get(row.conversation.conversationId),
|
|
885
|
+
durationMs: metrics?.durationMs ?? row.conversation.durationMs,
|
|
614
886
|
...row.destinationVisibility === "public" && row.destinationId ? { locationId: row.destinationId } : {},
|
|
615
|
-
usage: row.conversation.usage ?? void 0
|
|
616
|
-
})
|
|
617
|
-
),
|
|
887
|
+
usage: metrics?.usage ?? row.conversation.usage ?? void 0
|
|
888
|
+
});
|
|
889
|
+
}),
|
|
618
890
|
generatedAt: new Date(nowMs).toISOString(),
|
|
619
891
|
source: "conversation_index"
|
|
620
892
|
};
|
|
621
893
|
}
|
|
622
894
|
async function readConversationFeed(options = {}) {
|
|
623
895
|
return conversationFeedSchema.parse(
|
|
624
|
-
await readConversationFeedFromSql(
|
|
896
|
+
await readConversationFeedFromSql(options)
|
|
625
897
|
);
|
|
626
898
|
}
|
|
627
|
-
var list_default = {
|
|
899
|
+
var list_default = defineApiRoute({
|
|
628
900
|
method: "get",
|
|
629
901
|
path: "/",
|
|
902
|
+
responseSchema: conversationFeedSchema,
|
|
630
903
|
handler: async (c) => {
|
|
631
904
|
const { actorEmail } = parseQuery(
|
|
632
905
|
conversationFeedQuerySchema,
|
|
633
906
|
c.req.query()
|
|
634
907
|
);
|
|
635
|
-
|
|
908
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
909
|
+
return readConversationFeed({
|
|
910
|
+
...actorEmail ? { actorEmail } : {},
|
|
911
|
+
...verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
912
|
+
});
|
|
636
913
|
}
|
|
637
|
-
};
|
|
914
|
+
});
|
|
638
915
|
|
|
639
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
|
+
}
|
|
640
940
|
function projectConversationDetail(args) {
|
|
641
941
|
const { conversation } = args;
|
|
642
942
|
const conversationId = conversation.conversationId;
|
|
643
943
|
const transcriptPurgedAtMs = conversation.transcriptPurgedAtMs;
|
|
644
|
-
const
|
|
645
|
-
const authorizedConversation = {
|
|
646
|
-
...conversationWithoutVisibility,
|
|
647
|
-
...args.effectiveVisibility ? { visibility: args.effectiveVisibility } : {}
|
|
648
|
-
};
|
|
649
|
-
const canExposePayload = canExposeConversationPayload({
|
|
650
|
-
conversationId: args.privacyConversationId ?? conversationId,
|
|
651
|
-
visibility: args.effectiveVisibility
|
|
652
|
-
});
|
|
944
|
+
const canExposePayload = args.access?.canViewPrivateContent ?? false;
|
|
653
945
|
const events = transcriptPurgedAtMs === void 0 ? args.events : [];
|
|
654
946
|
const modelUsage = transcriptPurgedAtMs === void 0 ? args.modelUsage : [];
|
|
655
947
|
const sentryConversationUrl = buildSentryConversationUrl(conversationId);
|
|
656
948
|
return {
|
|
657
949
|
...conversationSummaryFromStoredConversation({
|
|
658
|
-
|
|
950
|
+
access: args.access,
|
|
951
|
+
conversation,
|
|
659
952
|
durationMs: args.durationMs,
|
|
660
953
|
...args.locationId ? { locationId: args.locationId } : {},
|
|
661
954
|
usage: args.usage
|
|
@@ -673,19 +966,28 @@ function projectConversationDetail(args) {
|
|
|
673
966
|
...sentryConversationUrl ? { sentryConversationUrl } : {}
|
|
674
967
|
};
|
|
675
968
|
}
|
|
676
|
-
async function readConversationDetailFromSql(conversationId) {
|
|
969
|
+
async function readConversationDetailFromSql(conversationId, options) {
|
|
677
970
|
const record = await readConversationRecordFromSql(conversationId);
|
|
678
971
|
if (!record) return void 0;
|
|
679
972
|
const executor = getSqlExecutor();
|
|
680
|
-
const
|
|
681
|
-
|
|
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, {
|
|
682
982
|
conversationId,
|
|
683
|
-
|
|
684
|
-
}),
|
|
685
|
-
|
|
983
|
+
includeDescendants: includeDescendantMetrics
|
|
984
|
+
}) : Promise.resolve([]),
|
|
985
|
+
readRootConversationMetricsFromSql(
|
|
986
|
+
getDb(),
|
|
987
|
+
includeDescendantMetrics ? [conversationId] : []
|
|
988
|
+
)
|
|
686
989
|
]);
|
|
687
|
-
|
|
688
|
-
const events = snapshot.events.map(
|
|
990
|
+
const events = eventRows.map(
|
|
689
991
|
(row) => decodeStoredConversationEvent({
|
|
690
992
|
schemaVersion: row.schemaVersion,
|
|
691
993
|
seq: row.seq,
|
|
@@ -696,106 +998,121 @@ async function readConversationDetailFromSql(conversationId) {
|
|
|
696
998
|
payload: row.payload
|
|
697
999
|
})
|
|
698
1000
|
);
|
|
699
|
-
const
|
|
1001
|
+
const metrics = metricsByRoot.get(conversationId);
|
|
700
1002
|
return projectConversationDetail({
|
|
701
1003
|
...record,
|
|
702
|
-
|
|
1004
|
+
access: accessByConversation.get(conversationId),
|
|
1005
|
+
durationMs: metrics?.durationMs ?? record.durationMs,
|
|
703
1006
|
events,
|
|
704
1007
|
modelUsage,
|
|
705
|
-
|
|
706
|
-
usage: record.usage ?? void 0
|
|
1008
|
+
usage: metrics?.usage ?? record.usage ?? void 0
|
|
707
1009
|
});
|
|
708
1010
|
}
|
|
709
|
-
async function readConversationDetail(conversationId) {
|
|
710
|
-
const report = await readConversationDetailFromSql(conversationId);
|
|
1011
|
+
async function readConversationDetail(conversationId, options = {}) {
|
|
1012
|
+
const report = await readConversationDetailFromSql(conversationId, options);
|
|
711
1013
|
return report ? conversationDetailReportSchema.parse(report) : void 0;
|
|
712
1014
|
}
|
|
713
|
-
var detail_default = {
|
|
1015
|
+
var detail_default = defineApiRoute({
|
|
714
1016
|
method: "get",
|
|
715
1017
|
path: "/:conversationId",
|
|
1018
|
+
responseSchema: conversationDetailReportSchema,
|
|
716
1019
|
handler: async (c) => {
|
|
717
1020
|
const { conversationId } = parseParams(
|
|
718
1021
|
conversationParamsSchema,
|
|
719
1022
|
c.req.param()
|
|
720
1023
|
);
|
|
721
|
-
const
|
|
722
|
-
|
|
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;
|
|
723
1031
|
}
|
|
724
|
-
};
|
|
1032
|
+
});
|
|
725
1033
|
|
|
726
1034
|
// src/api/conversations/stats.query.ts
|
|
727
|
-
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";
|
|
728
1037
|
|
|
729
1038
|
// src/api/conversations/aggregate.ts
|
|
730
|
-
import { sql as
|
|
731
|
-
function tokenValue() {
|
|
732
|
-
return
|
|
1039
|
+
import { sql as sql4 } from "drizzle-orm";
|
|
1040
|
+
function tokenValue(source) {
|
|
1041
|
+
return sql4`
|
|
733
1042
|
CASE
|
|
734
|
-
WHEN ${juniorConversations.usage}->>'totalTokens' IS NOT NULL
|
|
735
|
-
THEN (${juniorConversations.usage}->>'totalTokens')::double precision
|
|
736
1043
|
WHEN COALESCE(
|
|
737
|
-
${
|
|
738
|
-
${
|
|
739
|
-
${
|
|
740
|
-
${
|
|
1044
|
+
${source.usage}->>'inputTokens',
|
|
1045
|
+
${source.usage}->>'outputTokens',
|
|
1046
|
+
${source.usage}->>'cachedInputTokens',
|
|
1047
|
+
${source.usage}->>'cacheCreationTokens'
|
|
741
1048
|
) IS NOT NULL
|
|
742
|
-
THEN COALESCE((${
|
|
743
|
-
+ COALESCE((${
|
|
744
|
-
+ COALESCE((${
|
|
745
|
-
+ 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
|
|
746
1055
|
ELSE NULL
|
|
747
1056
|
END
|
|
748
1057
|
`;
|
|
749
1058
|
}
|
|
750
|
-
function costValue() {
|
|
751
|
-
return
|
|
1059
|
+
function costValue(source) {
|
|
1060
|
+
return sql4`
|
|
752
1061
|
CASE
|
|
753
|
-
WHEN ${
|
|
754
|
-
THEN (${
|
|
1062
|
+
WHEN ${source.usage}->'cost'->>'total' IS NOT NULL
|
|
1063
|
+
THEN (${source.usage}->'cost'->>'total')::double precision
|
|
755
1064
|
WHEN COALESCE(
|
|
756
|
-
${
|
|
757
|
-
${
|
|
758
|
-
${
|
|
759
|
-
${
|
|
1065
|
+
${source.usage}->'cost'->>'input',
|
|
1066
|
+
${source.usage}->'cost'->>'output',
|
|
1067
|
+
${source.usage}->'cost'->>'cacheRead',
|
|
1068
|
+
${source.usage}->'cost'->>'cacheWrite'
|
|
760
1069
|
) IS NOT NULL
|
|
761
|
-
THEN COALESCE((${
|
|
762
|
-
+ COALESCE((${
|
|
763
|
-
+ COALESCE((${
|
|
764
|
-
+ 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)
|
|
765
1074
|
ELSE NULL
|
|
766
1075
|
END
|
|
767
1076
|
`;
|
|
768
1077
|
}
|
|
769
|
-
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(*)`;
|
|
770
1082
|
return {
|
|
771
|
-
active:
|
|
772
|
-
WHERE ${
|
|
1083
|
+
active: sql4`${conversationCount} FILTER (
|
|
1084
|
+
WHERE ${roots.executionStatus} NOT IN ('idle', 'failed')
|
|
773
1085
|
)::integer`,
|
|
774
|
-
conversations:
|
|
775
|
-
costUsd:
|
|
776
|
-
durationMs:
|
|
777
|
-
failed:
|
|
778
|
-
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'
|
|
779
1091
|
)::integer`,
|
|
780
|
-
tokens:
|
|
1092
|
+
tokens: sql4`SUM(${tokenValue(metrics)})::double precision`
|
|
781
1093
|
};
|
|
782
1094
|
}
|
|
783
1095
|
function conversationRangeColumns() {
|
|
784
1096
|
return {
|
|
785
|
-
firstSeenAt:
|
|
1097
|
+
firstSeenAt: sql4`MIN(${juniorConversations.createdAt})`.mapWith(
|
|
786
1098
|
juniorConversations.createdAt
|
|
787
1099
|
),
|
|
788
|
-
lastSeenAt:
|
|
1100
|
+
lastSeenAt: sql4`MAX(${juniorConversations.lastActivityAt})`.mapWith(
|
|
789
1101
|
juniorConversations.lastActivityAt
|
|
790
1102
|
)
|
|
791
1103
|
};
|
|
792
1104
|
}
|
|
793
1105
|
function conversationActiveDaysColumn() {
|
|
794
|
-
return
|
|
1106
|
+
return sql4`COUNT(DISTINCT (${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC')::date)::integer`;
|
|
795
1107
|
}
|
|
796
1108
|
|
|
797
1109
|
// src/api/conversations/stats.query.ts
|
|
798
1110
|
var WINDOW_DAYS = 90;
|
|
1111
|
+
var treeConversation = alias3(juniorConversations, "stats_tree_conversation");
|
|
1112
|
+
var treeAggregateColumns = conversationAggregateColumns({
|
|
1113
|
+
metrics: treeConversation,
|
|
1114
|
+
roots: juniorConversations
|
|
1115
|
+
});
|
|
799
1116
|
function emptyStatsItem(label) {
|
|
800
1117
|
return {
|
|
801
1118
|
active: 0,
|
|
@@ -850,8 +1167,8 @@ function statsItems(map) {
|
|
|
850
1167
|
);
|
|
851
1168
|
}
|
|
852
1169
|
function statsWhere(start, end) {
|
|
853
|
-
return
|
|
854
|
-
|
|
1170
|
+
return and6(
|
|
1171
|
+
isNull3(juniorConversations.parentConversationId),
|
|
855
1172
|
gte(juniorConversations.lastActivityAt, start),
|
|
856
1173
|
lte(juniorConversations.lastActivityAt, end)
|
|
857
1174
|
);
|
|
@@ -884,12 +1201,18 @@ function metricDays(rows, endMs) {
|
|
|
884
1201
|
}
|
|
885
1202
|
async function aggregateStats(db, start, end) {
|
|
886
1203
|
const where = statsWhere(start, end);
|
|
887
|
-
const activityDate =
|
|
1204
|
+
const activityDate = sql5`TO_CHAR(
|
|
888
1205
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
889
1206
|
'YYYY-MM-DD'
|
|
890
1207
|
)`;
|
|
891
1208
|
const [totalsRows, actorRows, locationRows, metricRows] = await Promise.all([
|
|
892
|
-
db.select(
|
|
1209
|
+
db.select(treeAggregateColumns).from(juniorConversations).innerJoin(
|
|
1210
|
+
treeConversation,
|
|
1211
|
+
eq7(
|
|
1212
|
+
treeConversation.rootConversationId,
|
|
1213
|
+
juniorConversations.conversationId
|
|
1214
|
+
)
|
|
1215
|
+
).where(where),
|
|
893
1216
|
db.select({
|
|
894
1217
|
identityDisplayName: juniorIdentities.displayName,
|
|
895
1218
|
identityEmail: juniorIdentities.emailNormalized,
|
|
@@ -897,11 +1220,17 @@ async function aggregateStats(db, start, end) {
|
|
|
897
1220
|
identitySubjectId: juniorIdentities.providerSubjectId,
|
|
898
1221
|
userDisplayName: juniorUsers.displayName,
|
|
899
1222
|
userEmail: juniorUsers.primaryEmailNormalized,
|
|
900
|
-
...
|
|
901
|
-
}).from(juniorConversations).
|
|
1223
|
+
...treeAggregateColumns
|
|
1224
|
+
}).from(juniorConversations).innerJoin(
|
|
1225
|
+
treeConversation,
|
|
1226
|
+
eq7(
|
|
1227
|
+
treeConversation.rootConversationId,
|
|
1228
|
+
juniorConversations.conversationId
|
|
1229
|
+
)
|
|
1230
|
+
).leftJoin(
|
|
902
1231
|
juniorIdentities,
|
|
903
|
-
|
|
904
|
-
).leftJoin(juniorUsers,
|
|
1232
|
+
eq7(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1233
|
+
).leftJoin(juniorUsers, eq7(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
905
1234
|
juniorIdentities.displayName,
|
|
906
1235
|
juniorIdentities.emailNormalized,
|
|
907
1236
|
juniorIdentities.handle,
|
|
@@ -916,10 +1245,16 @@ async function aggregateStats(db, start, end) {
|
|
|
916
1245
|
destinationProvider: juniorDestinations.provider,
|
|
917
1246
|
destinationVisibility: juniorDestinations.visibility,
|
|
918
1247
|
source: juniorConversations.source,
|
|
919
|
-
...
|
|
920
|
-
}).from(juniorConversations).
|
|
1248
|
+
...treeAggregateColumns
|
|
1249
|
+
}).from(juniorConversations).innerJoin(
|
|
1250
|
+
treeConversation,
|
|
1251
|
+
eq7(
|
|
1252
|
+
treeConversation.rootConversationId,
|
|
1253
|
+
juniorConversations.conversationId
|
|
1254
|
+
)
|
|
1255
|
+
).leftJoin(
|
|
921
1256
|
juniorDestinations,
|
|
922
|
-
|
|
1257
|
+
eq7(juniorDestinations.id, juniorConversations.destinationId)
|
|
923
1258
|
).where(where).groupBy(
|
|
924
1259
|
juniorConversations.channelName,
|
|
925
1260
|
juniorConversations.source,
|
|
@@ -929,11 +1264,17 @@ async function aggregateStats(db, start, end) {
|
|
|
929
1264
|
juniorDestinations.visibility
|
|
930
1265
|
),
|
|
931
1266
|
db.select({
|
|
932
|
-
costUsd:
|
|
1267
|
+
costUsd: treeAggregateColumns.costUsd,
|
|
933
1268
|
date: activityDate,
|
|
934
|
-
durationMs:
|
|
935
|
-
tokens:
|
|
936
|
-
}).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)
|
|
937
1278
|
]);
|
|
938
1279
|
return { actorRows, locationRows, metricRows, totals: totalsRows[0] };
|
|
939
1280
|
}
|
|
@@ -976,17 +1317,18 @@ async function readConversationStats() {
|
|
|
976
1317
|
await readConversationStatsFromSql()
|
|
977
1318
|
);
|
|
978
1319
|
}
|
|
979
|
-
var stats_default = {
|
|
1320
|
+
var stats_default = defineApiRoute({
|
|
980
1321
|
method: "get",
|
|
981
1322
|
path: "/stats",
|
|
982
|
-
|
|
983
|
-
|
|
1323
|
+
responseSchema: conversationStatsReportSchema,
|
|
1324
|
+
handler: readConversationStats
|
|
1325
|
+
});
|
|
984
1326
|
|
|
985
1327
|
// src/api/conversations/routes.ts
|
|
986
1328
|
var routes = [list_default, stats_default, archive_default, detail_default];
|
|
987
1329
|
function createConversationRoutes() {
|
|
988
1330
|
const app = new Hono();
|
|
989
|
-
|
|
1331
|
+
registerApiRoutes(app, routes);
|
|
990
1332
|
return app;
|
|
991
1333
|
}
|
|
992
1334
|
|
|
@@ -994,28 +1336,10 @@ function createConversationRoutes() {
|
|
|
994
1336
|
import { Hono as Hono2 } from "hono";
|
|
995
1337
|
|
|
996
1338
|
// src/api/locations/query.ts
|
|
997
|
-
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";
|
|
998
1341
|
|
|
999
1342
|
// src/api/conversations/reporting.ts
|
|
1000
|
-
var PRIVATE_CONVERSATION_LABEL2 = "Private Conversation";
|
|
1001
|
-
function channelFromConversationId(conversationId) {
|
|
1002
|
-
const [provider, channel] = conversationId.split(":");
|
|
1003
|
-
return provider === "slack" && channel ? channel : void 0;
|
|
1004
|
-
}
|
|
1005
|
-
function surfaceFromRow(row) {
|
|
1006
|
-
if (row.source === "api" || row.source === "scheduler" || row.source === "slack") {
|
|
1007
|
-
return row.source;
|
|
1008
|
-
}
|
|
1009
|
-
if (row.conversationId.startsWith("slack:")) return "slack";
|
|
1010
|
-
if (row.conversationId.startsWith("scheduler:")) return "scheduler";
|
|
1011
|
-
if (row.conversationId.startsWith("api:")) return "api";
|
|
1012
|
-
return "internal";
|
|
1013
|
-
}
|
|
1014
|
-
function statusFromRow(row) {
|
|
1015
|
-
if (row.executionStatus === "failed") return "failed";
|
|
1016
|
-
if (row.executionStatus === "idle") return "completed";
|
|
1017
|
-
return "active";
|
|
1018
|
-
}
|
|
1019
1343
|
function surfaceLabel2(surface) {
|
|
1020
1344
|
if (surface === "scheduler") return "Scheduler";
|
|
1021
1345
|
if (surface === "api") return "API";
|
|
@@ -1037,22 +1361,6 @@ function slackLocationLabel(args) {
|
|
|
1037
1361
|
}
|
|
1038
1362
|
return name || channelId;
|
|
1039
1363
|
}
|
|
1040
|
-
function channelNameFromRow(row) {
|
|
1041
|
-
if (row.destinationVisibility !== "public") {
|
|
1042
|
-
return PRIVATE_CONVERSATION_LABEL2;
|
|
1043
|
-
}
|
|
1044
|
-
return row.channelName ?? void 0;
|
|
1045
|
-
}
|
|
1046
|
-
function titleFromRow(row, surface) {
|
|
1047
|
-
if (row.destinationVisibility !== "public") {
|
|
1048
|
-
return PRIVATE_CONVERSATION_LABEL2;
|
|
1049
|
-
}
|
|
1050
|
-
const channel = channelFromConversationId(row.conversationId);
|
|
1051
|
-
return row.title ?? slackLocationLabel({
|
|
1052
|
-
channel,
|
|
1053
|
-
channelName: row.channelName ?? void 0
|
|
1054
|
-
}) ?? surfaceLabel2(surface);
|
|
1055
|
-
}
|
|
1056
1364
|
function actorFromRow(row) {
|
|
1057
1365
|
const actor = {
|
|
1058
1366
|
...row.email ? { email: row.email } : {},
|
|
@@ -1062,34 +1370,42 @@ function actorFromRow(row) {
|
|
|
1062
1370
|
};
|
|
1063
1371
|
return Object.keys(actor).length ? actor : void 0;
|
|
1064
1372
|
}
|
|
1065
|
-
function summaryFromRow(row) {
|
|
1066
|
-
const
|
|
1067
|
-
const
|
|
1068
|
-
const channelName = channelNameFromRow(row);
|
|
1069
|
-
const channelNameRedacted = row.destinationVisibility !== "public";
|
|
1070
|
-
const actorIdentity = actorFromRow(row);
|
|
1071
|
-
return {
|
|
1373
|
+
function summaryFromRow(row, options = {}) {
|
|
1374
|
+
const actor = actorFromRow(row);
|
|
1375
|
+
const conversation = {
|
|
1072
1376
|
conversationId: row.conversationId,
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
...
|
|
1083
|
-
...
|
|
1084
|
-
...channelName ? { channelName } : {},
|
|
1085
|
-
...channelNameRedacted ? { channelNameRedacted: true } : {},
|
|
1086
|
-
...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 } : {}
|
|
1087
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
|
+
});
|
|
1088
1396
|
}
|
|
1089
1397
|
|
|
1090
1398
|
// src/api/locations/query.ts
|
|
1091
1399
|
var RECENT_LIMIT = 25;
|
|
1092
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
|
+
});
|
|
1093
1409
|
function emptyMetrics() {
|
|
1094
1410
|
return {
|
|
1095
1411
|
active: 0,
|
|
@@ -1125,13 +1441,13 @@ function emptyActor(actor) {
|
|
|
1125
1441
|
};
|
|
1126
1442
|
}
|
|
1127
1443
|
function topLevelWhere() {
|
|
1128
|
-
return
|
|
1444
|
+
return isNull4(juniorConversations.parentConversationId);
|
|
1129
1445
|
}
|
|
1130
1446
|
function publicLocationWhere(destinationId) {
|
|
1131
|
-
return
|
|
1447
|
+
return and7(
|
|
1132
1448
|
topLevelWhere(),
|
|
1133
|
-
|
|
1134
|
-
|
|
1449
|
+
eq8(juniorDestinations.id, destinationId),
|
|
1450
|
+
eq8(juniorDestinations.visibility, "public")
|
|
1135
1451
|
);
|
|
1136
1452
|
}
|
|
1137
1453
|
function locationColumns() {
|
|
@@ -1178,26 +1494,32 @@ function locationFromAggregate(row) {
|
|
|
1178
1494
|
async function directoryRows(db) {
|
|
1179
1495
|
return db.select({
|
|
1180
1496
|
...locationColumns(),
|
|
1181
|
-
...
|
|
1497
|
+
...treeAggregateColumns2,
|
|
1182
1498
|
...conversationRangeColumns()
|
|
1183
|
-
}).from(juniorConversations).
|
|
1499
|
+
}).from(juniorConversations).innerJoin(
|
|
1500
|
+
treeConversation2,
|
|
1501
|
+
eq8(
|
|
1502
|
+
treeConversation2.rootConversationId,
|
|
1503
|
+
juniorConversations.conversationId
|
|
1504
|
+
)
|
|
1505
|
+
).leftJoin(
|
|
1184
1506
|
juniorDestinations,
|
|
1185
|
-
|
|
1507
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1186
1508
|
).where(topLevelWhere()).groupBy(...locationGroupBy());
|
|
1187
1509
|
}
|
|
1188
1510
|
async function directoryActivityRows(db, start) {
|
|
1189
|
-
const date =
|
|
1511
|
+
const date = sql6`TO_CHAR(
|
|
1190
1512
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1191
1513
|
'YYYY-MM-DD'
|
|
1192
1514
|
)`;
|
|
1193
1515
|
return db.select({
|
|
1194
|
-
conversations:
|
|
1516
|
+
conversations: sql6`COUNT(*)::integer`,
|
|
1195
1517
|
date,
|
|
1196
1518
|
visibility: juniorDestinations.visibility
|
|
1197
1519
|
}).from(juniorConversations).leftJoin(
|
|
1198
1520
|
juniorDestinations,
|
|
1199
|
-
|
|
1200
|
-
).where(
|
|
1521
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1522
|
+
).where(and7(topLevelWhere(), gte2(juniorConversations.lastActivityAt, start))).groupBy(date, juniorDestinations.visibility);
|
|
1201
1523
|
}
|
|
1202
1524
|
function directoryActivityDays(rows, nowMs) {
|
|
1203
1525
|
const days = /* @__PURE__ */ new Map();
|
|
@@ -1269,9 +1591,8 @@ async function recentLocationRows(db, locationId) {
|
|
|
1269
1591
|
conversationId: juniorConversations.conversationId,
|
|
1270
1592
|
createdAt: juniorConversations.createdAt,
|
|
1271
1593
|
destinationId: juniorDestinations.id,
|
|
1272
|
-
destinationVisibility: juniorDestinations.visibility,
|
|
1273
1594
|
durationMs: juniorConversations.durationMs,
|
|
1274
|
-
email:
|
|
1595
|
+
email: sql6`COALESCE(
|
|
1275
1596
|
${juniorUsers.primaryEmailNormalized},
|
|
1276
1597
|
${juniorIdentities.email}
|
|
1277
1598
|
)`,
|
|
@@ -1280,7 +1601,7 @@ async function recentLocationRows(db, locationId) {
|
|
|
1280
1601
|
fullName: juniorUsers.displayName,
|
|
1281
1602
|
handle: juniorIdentities.handle,
|
|
1282
1603
|
lastActivityAt: juniorConversations.lastActivityAt,
|
|
1283
|
-
providerSubjectId:
|
|
1604
|
+
providerSubjectId: sql6`CASE
|
|
1284
1605
|
WHEN ${juniorIdentities.provider} = 'slack'
|
|
1285
1606
|
THEN ${juniorIdentities.providerSubjectId}
|
|
1286
1607
|
ELSE NULL
|
|
@@ -1291,44 +1612,59 @@ async function recentLocationRows(db, locationId) {
|
|
|
1291
1612
|
usage: juniorConversations.usage
|
|
1292
1613
|
}).from(juniorConversations).innerJoin(
|
|
1293
1614
|
juniorDestinations,
|
|
1294
|
-
|
|
1615
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1295
1616
|
).leftJoin(
|
|
1296
1617
|
juniorIdentities,
|
|
1297
|
-
|
|
1298
|
-
).leftJoin(juniorUsers,
|
|
1299
|
-
|
|
1618
|
+
eq8(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1619
|
+
).leftJoin(juniorUsers, eq8(juniorUsers.id, juniorIdentities.userId)).where(
|
|
1620
|
+
and7(
|
|
1300
1621
|
publicLocationWhere(locationId),
|
|
1301
|
-
|
|
1622
|
+
isNull4(juniorConversations.archivedAt)
|
|
1302
1623
|
)
|
|
1303
1624
|
).orderBy(
|
|
1304
1625
|
desc2(juniorConversations.lastActivityAt),
|
|
1305
|
-
|
|
1626
|
+
asc3(juniorConversations.conversationId)
|
|
1306
1627
|
).limit(RECENT_LIMIT);
|
|
1307
1628
|
}
|
|
1308
|
-
async function readLocationDetailFromSql(locationId) {
|
|
1629
|
+
async function readLocationDetailFromSql(locationId, options = {}) {
|
|
1309
1630
|
const nowMs = Date.now();
|
|
1310
1631
|
const end = new Date(nowMs);
|
|
1311
1632
|
end.setUTCHours(0, 0, 0, 0);
|
|
1312
1633
|
const start = new Date(end);
|
|
1313
1634
|
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS - 1));
|
|
1314
1635
|
const where = publicLocationWhere(locationId);
|
|
1315
|
-
const activityDate =
|
|
1636
|
+
const activityDate = sql6`TO_CHAR(
|
|
1316
1637
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1317
1638
|
'YYYY-MM-DD'
|
|
1318
1639
|
)`;
|
|
1319
1640
|
const [locationRows, dayRows, actorRows, recentRows] = await Promise.all([
|
|
1320
1641
|
getDb().select({
|
|
1321
1642
|
...locationColumns(),
|
|
1322
|
-
...
|
|
1643
|
+
...treeAggregateColumns2,
|
|
1323
1644
|
...conversationRangeColumns()
|
|
1324
1645
|
}).from(juniorConversations).innerJoin(
|
|
1646
|
+
treeConversation2,
|
|
1647
|
+
eq8(
|
|
1648
|
+
treeConversation2.rootConversationId,
|
|
1649
|
+
juniorConversations.conversationId
|
|
1650
|
+
)
|
|
1651
|
+
).innerJoin(
|
|
1325
1652
|
juniorDestinations,
|
|
1326
|
-
|
|
1653
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1327
1654
|
).where(where).groupBy(...locationGroupBy()),
|
|
1328
|
-
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(
|
|
1329
1665
|
juniorDestinations,
|
|
1330
|
-
|
|
1331
|
-
).where(
|
|
1666
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1667
|
+
).where(and7(where, gte2(juniorConversations.lastActivityAt, start))).groupBy(activityDate),
|
|
1332
1668
|
getDb().select({
|
|
1333
1669
|
actorIdentityId: juniorConversations.actorIdentityId,
|
|
1334
1670
|
email: juniorUsers.primaryEmailNormalized,
|
|
@@ -1337,14 +1673,20 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1337
1673
|
identityEmail: juniorIdentities.email,
|
|
1338
1674
|
identityProvider: juniorIdentities.provider,
|
|
1339
1675
|
providerSubjectId: juniorIdentities.providerSubjectId,
|
|
1340
|
-
...
|
|
1676
|
+
...treeAggregateColumns2
|
|
1341
1677
|
}).from(juniorConversations).innerJoin(
|
|
1678
|
+
treeConversation2,
|
|
1679
|
+
eq8(
|
|
1680
|
+
treeConversation2.rootConversationId,
|
|
1681
|
+
juniorConversations.conversationId
|
|
1682
|
+
)
|
|
1683
|
+
).innerJoin(
|
|
1342
1684
|
juniorDestinations,
|
|
1343
|
-
|
|
1685
|
+
eq8(juniorDestinations.id, juniorConversations.destinationId)
|
|
1344
1686
|
).leftJoin(
|
|
1345
1687
|
juniorIdentities,
|
|
1346
|
-
|
|
1347
|
-
).leftJoin(juniorUsers,
|
|
1688
|
+
eq8(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1689
|
+
).leftJoin(juniorUsers, eq8(juniorUsers.id, juniorIdentities.userId)).where(where).groupBy(
|
|
1348
1690
|
juniorConversations.actorIdentityId,
|
|
1349
1691
|
juniorUsers.primaryEmailNormalized,
|
|
1350
1692
|
juniorUsers.displayName,
|
|
@@ -1384,6 +1726,15 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1384
1726
|
actors.push(item);
|
|
1385
1727
|
}
|
|
1386
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
|
+
]);
|
|
1387
1738
|
return {
|
|
1388
1739
|
...location,
|
|
1389
1740
|
activityDays: activity,
|
|
@@ -1391,7 +1742,12 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1391
1742
|
(left, right) => right.conversations - left.conversations || left.label.localeCompare(right.label)
|
|
1392
1743
|
),
|
|
1393
1744
|
generatedAt: new Date(nowMs).toISOString(),
|
|
1394
|
-
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
|
+
),
|
|
1395
1751
|
source: "conversation_index",
|
|
1396
1752
|
windowEnd: end.toISOString(),
|
|
1397
1753
|
windowStart: activity[0] ? `${activity[0].date}T00:00:00.000Z` : end.toISOString()
|
|
@@ -1399,19 +1755,25 @@ async function readLocationDetailFromSql(locationId) {
|
|
|
1399
1755
|
}
|
|
1400
1756
|
|
|
1401
1757
|
// src/api/locations/detail.ts
|
|
1402
|
-
async function readLocationDetail(locationId) {
|
|
1403
|
-
const report = await readLocationDetailFromSql(locationId);
|
|
1758
|
+
async function readLocationDetail(locationId, options = {}) {
|
|
1759
|
+
const report = await readLocationDetailFromSql(locationId, options);
|
|
1404
1760
|
return report ? locationDetailReportSchema.parse(report) : void 0;
|
|
1405
1761
|
}
|
|
1406
|
-
var detail_default2 = {
|
|
1762
|
+
var detail_default2 = defineApiRoute({
|
|
1407
1763
|
method: "get",
|
|
1408
1764
|
path: "/:locationId",
|
|
1765
|
+
responseSchema: locationDetailReportSchema,
|
|
1409
1766
|
handler: async (c) => {
|
|
1410
1767
|
const { locationId } = parseParams(locationParamsSchema, c.req.param());
|
|
1411
|
-
const
|
|
1412
|
-
|
|
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;
|
|
1413
1775
|
}
|
|
1414
|
-
};
|
|
1776
|
+
});
|
|
1415
1777
|
|
|
1416
1778
|
// src/api/locations/list.ts
|
|
1417
1779
|
async function readLocationDirectory() {
|
|
@@ -1419,17 +1781,18 @@ async function readLocationDirectory() {
|
|
|
1419
1781
|
await readLocationDirectoryFromSql()
|
|
1420
1782
|
);
|
|
1421
1783
|
}
|
|
1422
|
-
var list_default2 = {
|
|
1784
|
+
var list_default2 = defineApiRoute({
|
|
1423
1785
|
method: "get",
|
|
1424
1786
|
path: "/",
|
|
1425
|
-
|
|
1426
|
-
|
|
1787
|
+
responseSchema: locationDirectoryReportSchema,
|
|
1788
|
+
handler: readLocationDirectory
|
|
1789
|
+
});
|
|
1427
1790
|
|
|
1428
1791
|
// src/api/locations/routes.ts
|
|
1429
1792
|
var routes2 = [list_default2, detail_default2];
|
|
1430
1793
|
function createLocationRoutes() {
|
|
1431
1794
|
const app = new Hono2();
|
|
1432
|
-
|
|
1795
|
+
registerApiRoutes(app, routes2);
|
|
1433
1796
|
return app;
|
|
1434
1797
|
}
|
|
1435
1798
|
|
|
@@ -1437,12 +1800,68 @@ function createLocationRoutes() {
|
|
|
1437
1800
|
import { Hono as Hono3 } from "hono";
|
|
1438
1801
|
|
|
1439
1802
|
// src/api/people/list.query.ts
|
|
1440
|
-
import { and as
|
|
1803
|
+
import { and as and9, eq as eq10, gte as gte3, sql as sql8 } from "drizzle-orm";
|
|
1441
1804
|
|
|
1442
1805
|
// src/api/people/shared.ts
|
|
1443
|
-
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";
|
|
1444
1808
|
var RECENT_LIMIT2 = 25;
|
|
1445
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
|
+
}
|
|
1446
1865
|
function normalizeEmail(email) {
|
|
1447
1866
|
const normalized = email?.trim().toLowerCase();
|
|
1448
1867
|
return normalized || void 0;
|
|
@@ -1484,11 +1903,11 @@ function statsItems2(map) {
|
|
|
1484
1903
|
}
|
|
1485
1904
|
function verifiedActorWhere(email) {
|
|
1486
1905
|
const normalizedEmail = normalizeEmail(email);
|
|
1487
|
-
return
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
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
|
|
1492
1911
|
);
|
|
1493
1912
|
}
|
|
1494
1913
|
async function recentActorRows(email) {
|
|
@@ -1497,7 +1916,6 @@ async function recentActorRows(email) {
|
|
|
1497
1916
|
conversationId: juniorConversations.conversationId,
|
|
1498
1917
|
createdAt: juniorConversations.createdAt,
|
|
1499
1918
|
destinationId: juniorDestinations.id,
|
|
1500
|
-
destinationVisibility: juniorDestinations.visibility,
|
|
1501
1919
|
durationMs: juniorConversations.durationMs,
|
|
1502
1920
|
email: juniorUsers.primaryEmailNormalized,
|
|
1503
1921
|
executionStatus: juniorConversations.executionStatus,
|
|
@@ -1512,15 +1930,15 @@ async function recentActorRows(email) {
|
|
|
1512
1930
|
usage: juniorConversations.usage
|
|
1513
1931
|
}).from(juniorConversations).innerJoin(
|
|
1514
1932
|
juniorIdentities,
|
|
1515
|
-
|
|
1516
|
-
).innerJoin(juniorUsers,
|
|
1933
|
+
eq9(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1934
|
+
).innerJoin(juniorUsers, eq9(juniorUsers.id, juniorIdentities.userId)).leftJoin(
|
|
1517
1935
|
juniorDestinations,
|
|
1518
|
-
|
|
1936
|
+
eq9(juniorDestinations.id, juniorConversations.destinationId)
|
|
1519
1937
|
).where(
|
|
1520
|
-
|
|
1938
|
+
and8(verifiedActorWhere(email), isNull5(juniorConversations.archivedAt))
|
|
1521
1939
|
).orderBy(
|
|
1522
1940
|
desc3(juniorConversations.lastActivityAt),
|
|
1523
|
-
|
|
1941
|
+
asc4(juniorConversations.conversationId)
|
|
1524
1942
|
).limit(RECENT_LIMIT2);
|
|
1525
1943
|
}
|
|
1526
1944
|
|
|
@@ -1546,7 +1964,7 @@ function directoryActivityDays2(rows, nowMs) {
|
|
|
1546
1964
|
async function readPeopleListFromSql() {
|
|
1547
1965
|
const nowMs = Date.now();
|
|
1548
1966
|
const { end, start } = activityWindow(nowMs);
|
|
1549
|
-
const activityDate =
|
|
1967
|
+
const activityDate = sql8`TO_CHAR(
|
|
1550
1968
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1551
1969
|
'YYYY-MM-DD'
|
|
1552
1970
|
)`;
|
|
@@ -1554,24 +1972,24 @@ async function readPeopleListFromSql() {
|
|
|
1554
1972
|
getDb().select({
|
|
1555
1973
|
email: juniorUsers.primaryEmailNormalized,
|
|
1556
1974
|
fullName: juniorUsers.displayName,
|
|
1557
|
-
slackUserId:
|
|
1558
|
-
slackUserName:
|
|
1975
|
+
slackUserId: sql8`MAX(${juniorIdentities.providerSubjectId})`,
|
|
1976
|
+
slackUserName: sql8`MAX(${juniorIdentities.handle})`,
|
|
1559
1977
|
activeDays: conversationActiveDaysColumn(),
|
|
1560
|
-
...
|
|
1978
|
+
...peopleTreeAggregateColumns,
|
|
1561
1979
|
...conversationRangeColumns()
|
|
1562
1980
|
}).from(juniorConversations).innerJoin(
|
|
1563
1981
|
juniorIdentities,
|
|
1564
|
-
|
|
1565
|
-
).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),
|
|
1566
1984
|
getDb().select({
|
|
1567
|
-
activePeople:
|
|
1568
|
-
conversations:
|
|
1985
|
+
activePeople: sql8`COUNT(DISTINCT ${juniorUsers.id})::int`,
|
|
1986
|
+
conversations: sql8`COUNT(*)::int`,
|
|
1569
1987
|
date: activityDate
|
|
1570
1988
|
}).from(juniorConversations).innerJoin(
|
|
1571
1989
|
juniorIdentities,
|
|
1572
|
-
|
|
1573
|
-
).innerJoin(juniorUsers,
|
|
1574
|
-
|
|
1990
|
+
eq10(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
1991
|
+
).innerJoin(juniorUsers, eq10(juniorUsers.id, juniorIdentities.userId)).where(
|
|
1992
|
+
and9(
|
|
1575
1993
|
verifiedActorWhere(),
|
|
1576
1994
|
gte3(juniorConversations.lastActivityAt, start)
|
|
1577
1995
|
)
|
|
@@ -1612,14 +2030,15 @@ async function readPeopleListFromSql() {
|
|
|
1612
2030
|
async function readPeopleList() {
|
|
1613
2031
|
return actorDirectoryReportSchema.parse(await readPeopleListFromSql());
|
|
1614
2032
|
}
|
|
1615
|
-
var list_default3 = {
|
|
2033
|
+
var list_default3 = defineApiRoute({
|
|
1616
2034
|
method: "get",
|
|
1617
2035
|
path: "/",
|
|
1618
|
-
|
|
1619
|
-
|
|
2036
|
+
responseSchema: actorDirectoryReportSchema,
|
|
2037
|
+
handler: readPeopleList
|
|
2038
|
+
});
|
|
1620
2039
|
|
|
1621
2040
|
// src/api/people/profile.query.ts
|
|
1622
|
-
import { and as
|
|
2041
|
+
import { and as and10, eq as eq11, gte as gte4, sql as sql9 } from "drizzle-orm";
|
|
1623
2042
|
function emptyProfile(email, nowMs) {
|
|
1624
2043
|
const end = new Date(nowMs);
|
|
1625
2044
|
end.setUTCHours(0, 0, 0, 0);
|
|
@@ -1654,7 +2073,7 @@ function addAggregate2(map, label, row) {
|
|
|
1654
2073
|
map.set(label, item);
|
|
1655
2074
|
}
|
|
1656
2075
|
function surfaceExpression() {
|
|
1657
|
-
return
|
|
2076
|
+
return sql9`CASE
|
|
1658
2077
|
WHEN ${juniorConversations.source} IN ('api', 'scheduler', 'slack')
|
|
1659
2078
|
THEN ${juniorConversations.source}
|
|
1660
2079
|
WHEN ${juniorConversations.conversationId} LIKE 'slack:%' THEN 'slack'
|
|
@@ -1672,7 +2091,7 @@ function locationLabel2(row) {
|
|
|
1672
2091
|
channelName: row.channelName ?? void 0
|
|
1673
2092
|
}) ?? "Conversation";
|
|
1674
2093
|
}
|
|
1675
|
-
async function readPeopleProfileFromSql(email) {
|
|
2094
|
+
async function readPeopleProfileFromSql(email, options = {}) {
|
|
1676
2095
|
const nowMs = Date.now();
|
|
1677
2096
|
const normalizedEmail = normalizeEmail(email);
|
|
1678
2097
|
if (!normalizedEmail) return emptyProfile("", nowMs);
|
|
@@ -1681,53 +2100,54 @@ async function readPeopleProfileFromSql(email) {
|
|
|
1681
2100
|
const start = new Date(end);
|
|
1682
2101
|
start.setUTCDate(start.getUTCDate() - (ACTIVITY_DAYS2 - 1));
|
|
1683
2102
|
const where = verifiedActorWhere(normalizedEmail);
|
|
2103
|
+
const metricsJoin = peopleTreeMetricsJoin();
|
|
1684
2104
|
const surface = surfaceExpression();
|
|
1685
|
-
const activityDate =
|
|
2105
|
+
const activityDate = sql9`TO_CHAR(
|
|
1686
2106
|
${juniorConversations.lastActivityAt} AT TIME ZONE 'UTC',
|
|
1687
2107
|
'YYYY-MM-DD'
|
|
1688
2108
|
)`;
|
|
1689
|
-
const channel =
|
|
2109
|
+
const channel = sql9`SPLIT_PART(${juniorConversations.conversationId}, ':', 2)`;
|
|
1690
2110
|
const [totalsRows, dayRows, locationRows, surfaceRows, recentRows] = await Promise.all([
|
|
1691
2111
|
getDb().select({
|
|
1692
2112
|
email: juniorUsers.primaryEmailNormalized,
|
|
1693
2113
|
fullName: juniorUsers.displayName,
|
|
1694
|
-
slackUserId:
|
|
1695
|
-
slackUserName:
|
|
2114
|
+
slackUserId: sql9`MAX(${juniorIdentities.providerSubjectId})`,
|
|
2115
|
+
slackUserName: sql9`MAX(${juniorIdentities.handle})`,
|
|
1696
2116
|
activeDays: conversationActiveDaysColumn(),
|
|
1697
|
-
...
|
|
2117
|
+
...peopleTreeAggregateColumns
|
|
1698
2118
|
}).from(juniorConversations).innerJoin(
|
|
1699
2119
|
juniorIdentities,
|
|
1700
|
-
|
|
1701
|
-
).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),
|
|
1702
2122
|
getDb().select({
|
|
1703
2123
|
date: activityDate,
|
|
1704
|
-
...
|
|
2124
|
+
...peopleTreeAggregateColumns
|
|
1705
2125
|
}).from(juniorConversations).innerJoin(
|
|
1706
2126
|
juniorIdentities,
|
|
1707
|
-
|
|
1708
|
-
).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),
|
|
1709
2129
|
getDb().select({
|
|
1710
2130
|
channel,
|
|
1711
2131
|
channelName: juniorConversations.channelName,
|
|
1712
2132
|
destinationVisibility: juniorDestinations.visibility,
|
|
1713
2133
|
surface,
|
|
1714
|
-
...
|
|
2134
|
+
...peopleTreeAggregateColumns
|
|
1715
2135
|
}).from(juniorConversations).innerJoin(
|
|
1716
2136
|
juniorIdentities,
|
|
1717
|
-
|
|
1718
|
-
).innerJoin(juniorUsers,
|
|
2137
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2138
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).leftJoin(
|
|
1719
2139
|
juniorDestinations,
|
|
1720
|
-
|
|
2140
|
+
eq11(juniorDestinations.id, juniorConversations.destinationId)
|
|
1721
2141
|
).where(where).groupBy(
|
|
1722
2142
|
channel,
|
|
1723
2143
|
juniorConversations.channelName,
|
|
1724
2144
|
juniorDestinations.visibility,
|
|
1725
2145
|
surface
|
|
1726
2146
|
),
|
|
1727
|
-
getDb().select({ surface, ...
|
|
2147
|
+
getDb().select({ surface, ...peopleTreeAggregateColumns }).from(juniorConversations).innerJoin(
|
|
1728
2148
|
juniorIdentities,
|
|
1729
|
-
|
|
1730
|
-
).innerJoin(juniorUsers,
|
|
2149
|
+
eq11(juniorIdentities.id, juniorConversations.actorIdentityId)
|
|
2150
|
+
).innerJoin(juniorUsers, eq11(juniorUsers.id, juniorIdentities.userId)).leftJoin(peopleTreeConversation, metricsJoin).where(where).groupBy(surface),
|
|
1731
2151
|
recentActorRows(normalizedEmail)
|
|
1732
2152
|
]);
|
|
1733
2153
|
const totalsRow = totalsRows[0];
|
|
@@ -1761,11 +2181,25 @@ async function readPeopleProfileFromSql(email) {
|
|
|
1761
2181
|
row
|
|
1762
2182
|
);
|
|
1763
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
|
+
]);
|
|
1764
2193
|
return {
|
|
1765
2194
|
activityDays: activityDays2(days, nowMs),
|
|
1766
2195
|
generatedAt: new Date(nowMs).toISOString(),
|
|
1767
2196
|
locations: statsItems2(locations),
|
|
1768
|
-
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
|
+
),
|
|
1769
2203
|
actor,
|
|
1770
2204
|
source: "conversation_index",
|
|
1771
2205
|
surfaces: statsItems2(surfaces),
|
|
@@ -1783,23 +2217,30 @@ async function readPeopleProfileFromSql(email) {
|
|
|
1783
2217
|
}
|
|
1784
2218
|
|
|
1785
2219
|
// src/api/people/profile.ts
|
|
1786
|
-
async function readPeopleProfile(email) {
|
|
1787
|
-
return actorProfileReportSchema.parse(
|
|
2220
|
+
async function readPeopleProfile(email, options = {}) {
|
|
2221
|
+
return actorProfileReportSchema.parse(
|
|
2222
|
+
await readPeopleProfileFromSql(email, options)
|
|
2223
|
+
);
|
|
1788
2224
|
}
|
|
1789
|
-
var profile_default = {
|
|
2225
|
+
var profile_default = defineApiRoute({
|
|
1790
2226
|
method: "get",
|
|
1791
2227
|
path: "/:email",
|
|
2228
|
+
responseSchema: actorProfileReportSchema,
|
|
1792
2229
|
handler: async (c) => {
|
|
1793
2230
|
const { email } = parseParams(personParamsSchema, c.req.param());
|
|
1794
|
-
|
|
2231
|
+
const verifiedViewerEmail = c.get("verifiedViewerEmail");
|
|
2232
|
+
return readPeopleProfile(
|
|
2233
|
+
email,
|
|
2234
|
+
verifiedViewerEmail ? { verifiedViewerEmail } : {}
|
|
2235
|
+
);
|
|
1795
2236
|
}
|
|
1796
|
-
};
|
|
2237
|
+
});
|
|
1797
2238
|
|
|
1798
2239
|
// src/api/people/routes.ts
|
|
1799
2240
|
var routes3 = [list_default3, profile_default];
|
|
1800
2241
|
function createPeopleRoutes() {
|
|
1801
2242
|
const app = new Hono3();
|
|
1802
|
-
|
|
2243
|
+
registerApiRoutes(app, routes3);
|
|
1803
2244
|
return app;
|
|
1804
2245
|
}
|
|
1805
2246
|
|
|
@@ -1815,7 +2256,7 @@ function readDescriptionText(home) {
|
|
|
1815
2256
|
}
|
|
1816
2257
|
}
|
|
1817
2258
|
async function readSkillReports() {
|
|
1818
|
-
const { discoverSkills } = await import("./skills-
|
|
2259
|
+
const { discoverSkills } = await import("./skills-AR4AF32L.js");
|
|
1819
2260
|
const skills = await discoverSkills();
|
|
1820
2261
|
return skillReportsSchema.parse(
|
|
1821
2262
|
skills.map((skill) => ({
|
|
@@ -1825,7 +2266,7 @@ async function readSkillReports() {
|
|
|
1825
2266
|
);
|
|
1826
2267
|
}
|
|
1827
2268
|
async function readPluginReports() {
|
|
1828
|
-
const { pluginCatalogRuntime } = await import("./catalog-runtime-
|
|
2269
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-4GI22PXK.js");
|
|
1829
2270
|
return pluginReportsSchema.parse(
|
|
1830
2271
|
pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
1831
2272
|
name: plugin.manifest.name
|
|
@@ -1835,7 +2276,7 @@ async function readPluginReports() {
|
|
|
1835
2276
|
async function readRuntimeInfoReport() {
|
|
1836
2277
|
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
1837
2278
|
import("./discovery-RMHYSRL7.js"),
|
|
1838
|
-
import("./catalog-runtime-
|
|
2279
|
+
import("./catalog-runtime-4GI22PXK.js"),
|
|
1839
2280
|
readPluginReports(),
|
|
1840
2281
|
readSkillReports()
|
|
1841
2282
|
]);
|
|
@@ -1851,7 +2292,7 @@ async function readRuntimeInfoReport() {
|
|
|
1851
2292
|
}
|
|
1852
2293
|
async function readPluginOperationalReportFeed() {
|
|
1853
2294
|
const nowMs = Date.now();
|
|
1854
|
-
const { getPluginOperationalReports } = await import("./agent-hooks-
|
|
2295
|
+
const { getPluginOperationalReports } = await import("./agent-hooks-ELT4ODFF.js");
|
|
1855
2296
|
return pluginOperationalReportFeedSchema.parse({
|
|
1856
2297
|
source: "plugins",
|
|
1857
2298
|
generatedAt: new Date(nowMs).toISOString(),
|
|
@@ -1860,28 +2301,56 @@ async function readPluginOperationalReportFeed() {
|
|
|
1860
2301
|
}
|
|
1861
2302
|
|
|
1862
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
|
+
];
|
|
1863
2336
|
function createJuniorApi() {
|
|
1864
2337
|
const app = new Hono4();
|
|
1865
|
-
app
|
|
1866
|
-
return Response.json(await readHealthReport());
|
|
1867
|
-
});
|
|
1868
|
-
app.get("/api/runtime", async () => {
|
|
1869
|
-
return Response.json(await readRuntimeInfoReport());
|
|
1870
|
-
});
|
|
1871
|
-
app.get("/api/plugins", async () => {
|
|
1872
|
-
return Response.json(await readPluginReports());
|
|
1873
|
-
});
|
|
1874
|
-
app.get("/api/skills", async () => {
|
|
1875
|
-
return Response.json(await readSkillReports());
|
|
1876
|
-
});
|
|
1877
|
-
app.get("/api/plugin-reports", async () => {
|
|
1878
|
-
return Response.json(await readPluginOperationalReportFeed());
|
|
1879
|
-
});
|
|
2338
|
+
registerApiRoutes(app, routes4);
|
|
1880
2339
|
app.route("/api/conversations", createConversationRoutes());
|
|
1881
2340
|
app.route("/api/people", createPeopleRoutes());
|
|
1882
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
|
+
);
|
|
1883
2351
|
return app;
|
|
1884
2352
|
}
|
|
1885
2353
|
export {
|
|
1886
|
-
createJuniorApi
|
|
2354
|
+
createJuniorApi,
|
|
2355
|
+
jsonResponse
|
|
1887
2356
|
};
|