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