@sentry/junior 0.57.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/app.js +1311 -1284
  2. package/dist/chat/agent-dispatch/store.d.ts +4 -2
  3. package/dist/chat/agent-dispatch/types.d.ts +0 -1
  4. package/dist/chat/conversation-privacy.d.ts +23 -0
  5. package/dist/chat/logging.d.ts +2 -0
  6. package/dist/chat/mcp/tool-manager.d.ts +18 -5
  7. package/dist/chat/mcp/tool-name.d.ts +2 -0
  8. package/dist/chat/pi/client.d.ts +2 -0
  9. package/dist/chat/pi/derived-state.d.ts +5 -0
  10. package/dist/chat/pi/traced-stream.d.ts +5 -1
  11. package/dist/chat/prompt.d.ts +3 -9
  12. package/dist/chat/respond-helpers.d.ts +5 -3
  13. package/dist/chat/respond.d.ts +1 -0
  14. package/dist/chat/runtime/conversation-message.d.ts +10 -0
  15. package/dist/chat/runtime/processing-reaction.d.ts +2 -4
  16. package/dist/chat/runtime/reply-executor.d.ts +13 -16
  17. package/dist/chat/runtime/slack-runtime.d.ts +19 -32
  18. package/dist/chat/runtime/thread-state.d.ts +1 -1
  19. package/dist/chat/runtime/turn-input.d.ts +29 -0
  20. package/dist/chat/runtime/turn-preparation.d.ts +4 -24
  21. package/dist/chat/runtime/turn.d.ts +2 -3
  22. package/dist/chat/sentry-links.d.ts +4 -0
  23. package/dist/chat/services/context-compaction.d.ts +3 -4
  24. package/dist/chat/services/pending-auth.d.ts +1 -1
  25. package/dist/chat/services/subscribed-reply-policy.d.ts +2 -13
  26. package/dist/chat/services/timeout-resume.d.ts +1 -2
  27. package/dist/chat/services/turn-session-record.d.ts +82 -0
  28. package/dist/chat/slack/assistant-thread/title.d.ts +4 -1
  29. package/dist/chat/state/artifacts.d.ts +1 -0
  30. package/dist/chat/state/conversation.d.ts +0 -1
  31. package/dist/chat/state/session-log.d.ts +117 -0
  32. package/dist/chat/state/ttl.d.ts +2 -0
  33. package/dist/chat/state/turn-session.d.ts +89 -0
  34. package/dist/chat/tools/advisor/tool.d.ts +2 -0
  35. package/dist/chat/tools/agent-tools.d.ts +2 -1
  36. package/dist/chat/tools/skill/call-mcp-tool.d.ts +7 -3
  37. package/dist/chat/tools/skill/search-mcp-tools.d.ts +15 -3
  38. package/dist/chat/tools/types.d.ts +0 -1
  39. package/dist/{chunk-AA5TIFN5.js → chunk-FKEKRBUB.js} +267 -735
  40. package/dist/{chunk-TTUY467K.js → chunk-H652GMDH.js} +30 -14
  41. package/dist/chunk-I4FDGMFI.js +950 -0
  42. package/dist/{chunk-D3G3YOU4.js → chunk-ITOW4DED.js} +1 -1
  43. package/dist/chunk-QDGD5WVN.js +708 -0
  44. package/dist/cli/check.js +2 -2
  45. package/dist/cli/init.js +0 -1
  46. package/dist/cli/snapshot-warmup.js +5 -3
  47. package/dist/instrumentation.js +3 -0
  48. package/dist/reporting.d.ts +113 -0
  49. package/dist/reporting.js +390 -0
  50. package/package.json +25 -11
  51. package/dist/chat/services/turn-checkpoint.d.ts +0 -74
  52. package/dist/chat/state/pi-session-message-store.d.ts +0 -15
  53. package/dist/chat/state/turn-session-store.d.ts +0 -49
  54. package/dist/handlers/diagnostics-dashboard.d.ts +0 -2
  55. package/dist/handlers/diagnostics.d.ts +0 -2
@@ -0,0 +1,950 @@
1
+ import {
2
+ getChatConfig,
3
+ getConnectedStateContext,
4
+ getStateAdapter
5
+ } from "./chunk-FKEKRBUB.js";
6
+ import {
7
+ isRecord
8
+ } from "./chunk-H652GMDH.js";
9
+ import {
10
+ sentry_exports
11
+ } from "./chunk-Z3YD6NHK.js";
12
+
13
+ // src/handlers/health.ts
14
+ function GET() {
15
+ return Response.json({
16
+ status: "ok",
17
+ service: "junior",
18
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
19
+ });
20
+ }
21
+
22
+ // src/chat/state/turn-session.ts
23
+ import { THREAD_STATE_TTL_MS } from "chat";
24
+
25
+ // src/chat/state/session-log.ts
26
+ import { isDeepStrictEqual } from "util";
27
+ import { z } from "zod";
28
+ var AGENT_SESSION_LOG_PREFIX = "junior:agent-session-log";
29
+ var AGENT_SESSION_LOG_SCHEMA_VERSION = 1;
30
+ var INITIAL_SESSION_ID = "session_0";
31
+ var SESSION_ID_PREFIX = "session_";
32
+ var piMessageSchema = z.object({
33
+ role: z.string()
34
+ }).passthrough().transform((value) => value);
35
+ var piMessageEntrySchema = z.object({
36
+ schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
37
+ type: z.literal("pi_message"),
38
+ sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
39
+ message: piMessageSchema
40
+ });
41
+ var projectionResetEntrySchema = z.object({
42
+ schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
43
+ type: z.literal("projection_reset"),
44
+ sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
45
+ messages: z.array(piMessageSchema)
46
+ });
47
+ var mcpProviderConnectedEntrySchema = z.object({
48
+ schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
49
+ type: z.literal("mcp_provider_connected"),
50
+ sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
51
+ provider: z.string().min(1)
52
+ });
53
+ var authorizationKindSchema = z.union([
54
+ z.literal("plugin"),
55
+ z.literal("mcp")
56
+ ]);
57
+ var authorizationRequestedEntrySchema = z.object({
58
+ schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
59
+ type: z.literal("authorization_requested"),
60
+ sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
61
+ createdAtMs: z.number().int().nonnegative(),
62
+ kind: authorizationKindSchema,
63
+ provider: z.string().min(1),
64
+ requesterId: z.string().min(1),
65
+ authorizationId: z.string().min(1),
66
+ delivery: z.union([
67
+ z.literal("private_link_sent"),
68
+ z.literal("private_link_reused")
69
+ ])
70
+ });
71
+ var authorizationCompletedEntrySchema = z.object({
72
+ schemaVersion: z.literal(AGENT_SESSION_LOG_SCHEMA_VERSION),
73
+ type: z.literal("authorization_completed"),
74
+ sessionId: z.string().min(1).default(INITIAL_SESSION_ID),
75
+ createdAtMs: z.number().int().nonnegative(),
76
+ kind: authorizationKindSchema,
77
+ provider: z.string().min(1),
78
+ requesterId: z.string().min(1),
79
+ authorizationId: z.string().min(1)
80
+ });
81
+ var sessionLogEntrySchema = z.discriminatedUnion("type", [
82
+ piMessageEntrySchema,
83
+ projectionResetEntrySchema,
84
+ mcpProviderConnectedEntrySchema,
85
+ authorizationRequestedEntrySchema,
86
+ authorizationCompletedEntrySchema
87
+ ]);
88
+ function key(scope) {
89
+ const prefix = getChatConfig().state.keyPrefix;
90
+ return [
91
+ ...prefix ? [prefix] : [],
92
+ AGENT_SESSION_LOG_PREFIX,
93
+ scope.conversationId
94
+ ].join(":");
95
+ }
96
+ function rawKey(scope) {
97
+ return [AGENT_SESSION_LOG_PREFIX, scope.conversationId].join(":");
98
+ }
99
+ function normalizeMessageCount(value) {
100
+ return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
101
+ }
102
+ function countMatchingPrefix(left, right) {
103
+ const limit = Math.min(left.length, right.length);
104
+ for (let index = 0; index < limit; index += 1) {
105
+ if (!isDeepStrictEqual(left[index], right[index])) {
106
+ return index;
107
+ }
108
+ }
109
+ return limit;
110
+ }
111
+ function entrySessionId(entry) {
112
+ return entry.sessionId ?? INITIAL_SESSION_ID;
113
+ }
114
+ function latestProjectionResetIndex(entries) {
115
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
116
+ if (entries[index]?.type === "projection_reset") {
117
+ return index;
118
+ }
119
+ }
120
+ return -1;
121
+ }
122
+ function currentSessionId(entries) {
123
+ const resetIndex = latestProjectionResetIndex(entries);
124
+ if (resetIndex < 0) {
125
+ return INITIAL_SESSION_ID;
126
+ }
127
+ return entrySessionId(entries[resetIndex]);
128
+ }
129
+ function nextSessionId(entries) {
130
+ const resetCount = entries.filter(
131
+ (entry) => entry.type === "projection_reset"
132
+ ).length;
133
+ return `${SESSION_ID_PREFIX}${resetCount + 1}`;
134
+ }
135
+ function projectionEntries(entries, sessionId) {
136
+ if (sessionId) {
137
+ const sessionEntries = [];
138
+ let started = false;
139
+ for (const entry of entries) {
140
+ const entryId = entrySessionId(entry);
141
+ if (!started) {
142
+ if (entryId !== sessionId) {
143
+ continue;
144
+ }
145
+ started = true;
146
+ } else if (entry.type === "projection_reset" && entryId !== sessionId) {
147
+ break;
148
+ }
149
+ if (entryId === sessionId) {
150
+ sessionEntries.push(entry);
151
+ }
152
+ }
153
+ return sessionEntries;
154
+ }
155
+ const resetIndex = latestProjectionResetIndex(entries);
156
+ const startIndex = resetIndex < 0 ? 0 : resetIndex;
157
+ const currentId = resetIndex < 0 ? INITIAL_SESSION_ID : entrySessionId(entries[resetIndex]);
158
+ return entries.slice(startIndex).filter((entry) => entrySessionId(entry) === currentId);
159
+ }
160
+ function piEntry(message, sessionId) {
161
+ return {
162
+ schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
163
+ type: "pi_message",
164
+ sessionId,
165
+ message
166
+ };
167
+ }
168
+ function resetEntry(messages, sessionId) {
169
+ return {
170
+ schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
171
+ type: "projection_reset",
172
+ sessionId,
173
+ messages
174
+ };
175
+ }
176
+ function mcpProviderConnectedEntry(provider, sessionId) {
177
+ return {
178
+ schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
179
+ type: "mcp_provider_connected",
180
+ sessionId,
181
+ provider
182
+ };
183
+ }
184
+ function authorizationObservationMessage(entry) {
185
+ const label = entry.kind === "mcp" ? "MCP authorization" : "Authorization";
186
+ return {
187
+ role: "user",
188
+ content: [
189
+ {
190
+ type: "text",
191
+ text: `${label} completed for provider "${entry.provider}". Continue the blocked request and retry the provider operation if needed.`
192
+ }
193
+ ],
194
+ timestamp: entry.createdAtMs
195
+ };
196
+ }
197
+ function authorizationRequestedEntry(args) {
198
+ return {
199
+ schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
200
+ type: "authorization_requested",
201
+ sessionId: args.sessionId,
202
+ createdAtMs: args.createdAtMs,
203
+ kind: args.kind,
204
+ provider: args.provider,
205
+ requesterId: args.requesterId,
206
+ authorizationId: args.authorizationId,
207
+ delivery: args.delivery
208
+ };
209
+ }
210
+ function authorizationCompletedEntry(args) {
211
+ return {
212
+ schemaVersion: AGENT_SESSION_LOG_SCHEMA_VERSION,
213
+ type: "authorization_completed",
214
+ sessionId: args.sessionId,
215
+ createdAtMs: args.createdAtMs,
216
+ kind: args.kind,
217
+ provider: args.provider,
218
+ requesterId: args.requesterId,
219
+ authorizationId: args.authorizationId
220
+ };
221
+ }
222
+ function decode(value) {
223
+ if (typeof value === "string") {
224
+ return decode(JSON.parse(value));
225
+ }
226
+ const parsed = sessionLogEntrySchema.safeParse(value);
227
+ if (parsed.success) {
228
+ return parsed.data;
229
+ }
230
+ return piEntry(piMessageSchema.parse(value), INITIAL_SESSION_ID);
231
+ }
232
+ function project(entries, sessionId) {
233
+ let messages = [];
234
+ for (const entry of projectionEntries(entries, sessionId)) {
235
+ if (entry.type === "pi_message") {
236
+ messages.push(entry.message);
237
+ continue;
238
+ }
239
+ if (entry.type === "authorization_completed") {
240
+ messages.push(authorizationObservationMessage(entry));
241
+ continue;
242
+ }
243
+ if (entry.type === "mcp_provider_connected" || entry.type === "authorization_requested") {
244
+ continue;
245
+ }
246
+ messages = [...entry.messages];
247
+ }
248
+ return messages;
249
+ }
250
+ function connectedMcpProviders(entries, sessionId) {
251
+ const providers = /* @__PURE__ */ new Set();
252
+ for (const entry of projectionEntries(entries, sessionId)) {
253
+ if (entry.type === "mcp_provider_connected") {
254
+ providers.add(entry.provider);
255
+ }
256
+ }
257
+ return [...providers].sort((left, right) => left.localeCompare(right));
258
+ }
259
+ function commitEntries(existingMessages, nextMessages, sessionId, entries) {
260
+ const matchingPrefix = countMatchingPrefix(existingMessages, nextMessages);
261
+ if (matchingPrefix === existingMessages.length) {
262
+ return nextMessages.slice(matchingPrefix).map((message) => piEntry(message, sessionId));
263
+ }
264
+ return [resetEntry(nextMessages, nextSessionId(entries))];
265
+ }
266
+ function redisStore(redisStateAdapter) {
267
+ const client = redisStateAdapter.getClient();
268
+ return {
269
+ async append({ entries, scope, ttlMs }) {
270
+ const listKey = key(scope);
271
+ if (entries.length > 0) {
272
+ await client.rPush(
273
+ listKey,
274
+ entries.map((entry) => JSON.stringify(entry))
275
+ );
276
+ }
277
+ await client.pExpire(listKey, Math.max(1, ttlMs));
278
+ },
279
+ async read(scope) {
280
+ const values = await client.lRange(key(scope), 0, -1);
281
+ return values.map(decode);
282
+ }
283
+ };
284
+ }
285
+ function stateStore() {
286
+ const stateAdapter = getStateAdapter();
287
+ return {
288
+ async append({ entries, scope, ttlMs }) {
289
+ const listKey = rawKey(scope);
290
+ for (const entry of entries) {
291
+ await stateAdapter.appendToList(listKey, entry, {
292
+ ttlMs: Math.max(1, ttlMs)
293
+ });
294
+ }
295
+ },
296
+ async read(scope) {
297
+ const values = await stateAdapter.getList(rawKey(scope));
298
+ return values.map(decode);
299
+ }
300
+ };
301
+ }
302
+ async function defaultStore() {
303
+ const { redisStateAdapter, stateAdapter } = await getConnectedStateContext();
304
+ if (redisStateAdapter) {
305
+ return redisStore(redisStateAdapter);
306
+ }
307
+ await stateAdapter.connect();
308
+ return stateStore();
309
+ }
310
+ async function loadEntries(args) {
311
+ const store = args.store ?? await defaultStore();
312
+ return await store.read(args);
313
+ }
314
+ async function loadMessages(args) {
315
+ const messageCount = normalizeMessageCount(args.messageCount);
316
+ if (messageCount === 0) {
317
+ return [];
318
+ }
319
+ const store = args.store ?? await defaultStore();
320
+ const messages = project(await store.read(args), args.sessionId);
321
+ return messages.length >= messageCount ? messages.slice(0, messageCount) : void 0;
322
+ }
323
+ async function loadProjection(args) {
324
+ const store = args.store ?? await defaultStore();
325
+ return project(await store.read(args), args.sessionId);
326
+ }
327
+ async function loadConnectedMcpProviders(args) {
328
+ return connectedMcpProviders(await loadEntries(args));
329
+ }
330
+ async function recordMcpProviderConnected(args) {
331
+ const store = args.store ?? await defaultStore();
332
+ const entries = await store.read(args);
333
+ const sessionId = currentSessionId(entries);
334
+ if (connectedMcpProviders(entries).includes(args.provider)) {
335
+ return;
336
+ }
337
+ await store.append({
338
+ scope: args,
339
+ entries: [mcpProviderConnectedEntry(args.provider, sessionId)],
340
+ ttlMs: args.ttlMs
341
+ });
342
+ }
343
+ async function recordAuthorizationRequested(args) {
344
+ const store = args.store ?? await defaultStore();
345
+ const entries = await store.read(args);
346
+ const sessionId = currentSessionId(entries);
347
+ if (projectionEntries(entries).some(
348
+ (entry) => entry.type === "authorization_requested" && entry.authorizationId === args.authorizationId
349
+ )) {
350
+ return;
351
+ }
352
+ await store.append({
353
+ scope: args,
354
+ entries: [
355
+ authorizationRequestedEntry({
356
+ createdAtMs: Date.now(),
357
+ kind: args.kind,
358
+ sessionId,
359
+ provider: args.provider,
360
+ requesterId: args.requesterId,
361
+ authorizationId: args.authorizationId,
362
+ delivery: args.delivery
363
+ })
364
+ ],
365
+ ttlMs: args.ttlMs
366
+ });
367
+ }
368
+ async function recordAuthorizationCompleted(args) {
369
+ const store = args.store ?? await defaultStore();
370
+ const entries = await store.read(args);
371
+ const sessionId = currentSessionId(entries);
372
+ if (projectionEntries(entries).some(
373
+ (entry) => entry.type === "authorization_completed" && entry.authorizationId === args.authorizationId
374
+ )) {
375
+ return;
376
+ }
377
+ await store.append({
378
+ scope: args,
379
+ entries: [
380
+ authorizationCompletedEntry({
381
+ createdAtMs: Date.now(),
382
+ kind: args.kind,
383
+ sessionId,
384
+ provider: args.provider,
385
+ requesterId: args.requesterId,
386
+ authorizationId: args.authorizationId
387
+ })
388
+ ],
389
+ ttlMs: args.ttlMs
390
+ });
391
+ }
392
+ async function commitMessages(args) {
393
+ const store = args.store ?? await defaultStore();
394
+ const entries = await store.read(args);
395
+ const existingMessages = project(entries);
396
+ const currentId = currentSessionId(entries);
397
+ const nextEntries = commitEntries(
398
+ existingMessages,
399
+ args.messages,
400
+ currentId,
401
+ entries
402
+ );
403
+ await store.append({
404
+ scope: args,
405
+ entries: nextEntries,
406
+ ttlMs: args.ttlMs
407
+ });
408
+ return {
409
+ sessionId: nextEntries.find((entry) => entry.type === "projection_reset")?.sessionId ?? currentId
410
+ };
411
+ }
412
+
413
+ // src/chat/state/turn-session.ts
414
+ var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
415
+ var AGENT_TURN_SESSION_INDEX_KEY = `${AGENT_TURN_SESSION_PREFIX}:index`;
416
+ var AGENT_TURN_SESSION_INDEX_MAX_LENGTH = 5e3;
417
+ var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS;
418
+ function agentTurnSessionKey(conversationId, sessionId) {
419
+ return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
420
+ }
421
+ function agentTurnSessionConversationIndexKey(conversationId) {
422
+ return `${AGENT_TURN_SESSION_PREFIX}:conversation:${conversationId}:index`;
423
+ }
424
+ function toFiniteNonNegativeNumber(value) {
425
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
426
+ }
427
+ function parseAgentTurnUsage(value) {
428
+ if (!isRecord(value)) {
429
+ return void 0;
430
+ }
431
+ const usage = {};
432
+ for (const field of [
433
+ "inputTokens",
434
+ "outputTokens",
435
+ "cachedInputTokens",
436
+ "cacheCreationTokens",
437
+ "totalTokens"
438
+ ]) {
439
+ const count = toFiniteNonNegativeNumber(value[field]);
440
+ if (count !== void 0) {
441
+ usage[field] = count;
442
+ }
443
+ }
444
+ return Object.keys(usage).length > 0 ? usage : void 0;
445
+ }
446
+ function parseAgentTurnRequester(value) {
447
+ if (!isRecord(value)) {
448
+ return void 0;
449
+ }
450
+ const requester = {};
451
+ for (const field of [
452
+ "email",
453
+ "fullName",
454
+ "slackUserId",
455
+ "slackUserName"
456
+ ]) {
457
+ if (typeof value[field] === "string" && value[field].trim()) {
458
+ requester[field] = value[field].trim();
459
+ }
460
+ }
461
+ return Object.keys(requester).length > 0 ? requester : void 0;
462
+ }
463
+ function parseStoredRecord(value) {
464
+ if (isRecord(value)) {
465
+ return value;
466
+ }
467
+ if (typeof value !== "string") {
468
+ return void 0;
469
+ }
470
+ try {
471
+ const parsed = JSON.parse(value);
472
+ return isRecord(parsed) ? parsed : void 0;
473
+ } catch {
474
+ return void 0;
475
+ }
476
+ }
477
+ function parseAgentTurnSessionStatus(parsed) {
478
+ const status = parsed.state;
479
+ if (status === "running" || status === "awaiting_resume" || status === "completed" || status === "failed" || status === "abandoned") {
480
+ return status;
481
+ }
482
+ return void 0;
483
+ }
484
+ function parseAgentTurnSessionFields(parsed) {
485
+ const status = parseAgentTurnSessionStatus(parsed);
486
+ if (!status) {
487
+ return void 0;
488
+ }
489
+ const channelName = typeof parsed.channelName === "string" && parsed.channelName.trim() ? parsed.channelName.trim() : void 0;
490
+ const conversationTitle = typeof parsed.conversationTitle === "string" && parsed.conversationTitle.trim() ? parsed.conversationTitle.trim() : void 0;
491
+ const conversationId = parsed.conversationId;
492
+ const sessionId = parsed.sessionId;
493
+ const sliceId = toFiniteNonNegativeNumber(parsed.sliceId);
494
+ const version = toFiniteNonNegativeNumber(parsed.version);
495
+ const updatedAtMs = toFiniteNonNegativeNumber(parsed.updatedAtMs);
496
+ const cumulativeDurationMs = toFiniteNonNegativeNumber(
497
+ parsed.cumulativeDurationMs
498
+ );
499
+ const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
500
+ const lastProgressAtMs = toFiniteNonNegativeNumber(parsed.lastProgressAtMs);
501
+ const logSessionId = typeof parsed.logSessionId === "string" ? parsed.logSessionId : void 0;
502
+ const requester = parseAgentTurnRequester(parsed.requester);
503
+ const startedAtMs = toFiniteNonNegativeNumber(parsed.startedAtMs);
504
+ if (typeof conversationId !== "string" || typeof sessionId !== "string" || sliceId === void 0 || version === void 0 || updatedAtMs === void 0) {
505
+ return void 0;
506
+ }
507
+ return {
508
+ version,
509
+ ...channelName ? { channelName } : {},
510
+ ...conversationTitle ? { conversationTitle } : {},
511
+ conversationId,
512
+ sessionId,
513
+ sliceId,
514
+ state: status,
515
+ startedAtMs: startedAtMs ?? updatedAtMs,
516
+ lastProgressAtMs: lastProgressAtMs ?? updatedAtMs,
517
+ updatedAtMs,
518
+ ...logSessionId ? { logSessionId } : {},
519
+ ...cumulativeDurationMs !== void 0 ? { cumulativeDurationMs } : {},
520
+ ...cumulativeUsage ? { cumulativeUsage } : {},
521
+ ...requester ? { requester } : {},
522
+ ...Array.isArray(parsed.loadedSkillNames) ? {
523
+ loadedSkillNames: parsed.loadedSkillNames.filter(
524
+ (value) => typeof value === "string"
525
+ )
526
+ } : {},
527
+ ...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
528
+ ...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
529
+ ...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {},
530
+ ...typeof parsed.traceId === "string" ? { traceId: parsed.traceId } : {}
531
+ };
532
+ }
533
+ function parseAgentTurnSessionRecord(value) {
534
+ const parsed = parseStoredRecord(value);
535
+ if (!parsed) {
536
+ return void 0;
537
+ }
538
+ const fields = parseAgentTurnSessionFields(parsed);
539
+ const committedMessageCount = toFiniteNonNegativeNumber(
540
+ parsed.committedMessageCount
541
+ );
542
+ if (!fields || committedMessageCount === void 0) {
543
+ return void 0;
544
+ }
545
+ return {
546
+ ...fields,
547
+ committedMessageCount
548
+ };
549
+ }
550
+ function parseAgentTurnSessionSummary(value) {
551
+ const stored = parseStoredRecord(value);
552
+ if (!stored) {
553
+ return void 0;
554
+ }
555
+ const parsed = parseAgentTurnSessionFields(stored);
556
+ if (!parsed) {
557
+ return void 0;
558
+ }
559
+ const {
560
+ errorMessage: _errorMessage,
561
+ logSessionId: _logSessionId,
562
+ ...summary
563
+ } = parsed;
564
+ return summary;
565
+ }
566
+ async function appendAgentTurnSessionSummary(summary, ttlMs) {
567
+ const stateAdapter = getStateAdapter();
568
+ await Promise.all([
569
+ stateAdapter.appendToList(AGENT_TURN_SESSION_INDEX_KEY, summary, {
570
+ maxLength: AGENT_TURN_SESSION_INDEX_MAX_LENGTH,
571
+ ttlMs
572
+ }),
573
+ stateAdapter.appendToList(
574
+ agentTurnSessionConversationIndexKey(summary.conversationId),
575
+ summary,
576
+ { ttlMs }
577
+ )
578
+ ]);
579
+ }
580
+ function materializePiMessages(committedMessageCount, includeProjectionTail, sessionMessages, sessionProjection) {
581
+ if (committedMessageCount === 0) {
582
+ return sessionProjection;
583
+ }
584
+ if (includeProjectionTail && sessionProjection.length >= committedMessageCount) {
585
+ return sessionProjection;
586
+ }
587
+ if (sessionMessages) {
588
+ return sessionMessages;
589
+ }
590
+ if (sessionProjection.length >= committedMessageCount) {
591
+ return sessionProjection.slice(0, committedMessageCount);
592
+ }
593
+ return void 0;
594
+ }
595
+ function materializeAgentTurnSessionRecord(stored, piMessages) {
596
+ return {
597
+ version: stored.version,
598
+ ...stored.channelName ? { channelName: stored.channelName } : {},
599
+ ...stored.conversationTitle ? { conversationTitle: stored.conversationTitle } : {},
600
+ conversationId: stored.conversationId,
601
+ sessionId: stored.sessionId,
602
+ sliceId: stored.sliceId,
603
+ state: stored.state,
604
+ startedAtMs: stored.startedAtMs,
605
+ lastProgressAtMs: stored.lastProgressAtMs,
606
+ updatedAtMs: stored.updatedAtMs,
607
+ piMessages,
608
+ ...stored.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: stored.cumulativeDurationMs } : {},
609
+ ...stored.cumulativeUsage ? { cumulativeUsage: stored.cumulativeUsage } : {},
610
+ ...stored.resumeReason ? { resumeReason: stored.resumeReason } : {},
611
+ ...stored.errorMessage ? { errorMessage: stored.errorMessage } : {},
612
+ ...stored.loadedSkillNames ? { loadedSkillNames: stored.loadedSkillNames } : {},
613
+ ...stored.requester ? { requester: stored.requester } : {},
614
+ ...stored.resumedFromSliceId !== void 0 ? { resumedFromSliceId: stored.resumedFromSliceId } : {},
615
+ ...stored.traceId ? { traceId: stored.traceId } : {}
616
+ };
617
+ }
618
+ async function getStoredAgentTurnSessionRecord(conversationId, sessionId) {
619
+ const stateAdapter = getStateAdapter();
620
+ await stateAdapter.connect();
621
+ const value = await stateAdapter.get(
622
+ agentTurnSessionKey(conversationId, sessionId)
623
+ );
624
+ return parseAgentTurnSessionRecord(value);
625
+ }
626
+ async function getAgentTurnSessionRecord(conversationId, sessionId) {
627
+ const parsed = await getStoredAgentTurnSessionRecord(
628
+ conversationId,
629
+ sessionId
630
+ );
631
+ if (!parsed) {
632
+ return void 0;
633
+ }
634
+ const sessionMessages = await loadMessages({
635
+ conversationId,
636
+ messageCount: parsed.committedMessageCount,
637
+ ...parsed.logSessionId ? { sessionId: parsed.logSessionId } : {}
638
+ });
639
+ const sessionProjection = await loadProjection({
640
+ conversationId,
641
+ ...parsed.logSessionId ? { sessionId: parsed.logSessionId } : {}
642
+ });
643
+ const piMessages = materializePiMessages(
644
+ parsed.committedMessageCount,
645
+ parsed.state === "running" || parsed.state === "awaiting_resume",
646
+ sessionMessages,
647
+ sessionProjection
648
+ );
649
+ if (!piMessages) {
650
+ return void 0;
651
+ }
652
+ return materializeAgentTurnSessionRecord(parsed, piMessages);
653
+ }
654
+ function buildStoredRecord(args) {
655
+ const nowMs = Date.now();
656
+ return {
657
+ version: (args.previousVersion ?? 0) + 1,
658
+ ...args.channelName ? { channelName: args.channelName } : {},
659
+ ...args.conversationTitle ? { conversationTitle: args.conversationTitle } : {},
660
+ conversationId: args.conversationId,
661
+ sessionId: args.sessionId,
662
+ sliceId: args.sliceId,
663
+ state: args.state,
664
+ startedAtMs: args.startedAtMs ?? nowMs,
665
+ lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
666
+ updatedAtMs: nowMs,
667
+ committedMessageCount: args.committedMessageCount,
668
+ ...args.logSessionId ? { logSessionId: args.logSessionId } : {},
669
+ ...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
670
+ cumulativeDurationMs: Math.max(
671
+ 0,
672
+ Math.floor(args.cumulativeDurationMs)
673
+ )
674
+ } : {},
675
+ ...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
676
+ ...args.requester ? { requester: args.requester } : {},
677
+ ...Array.isArray(args.loadedSkillNames) ? {
678
+ loadedSkillNames: args.loadedSkillNames.filter(
679
+ (value) => typeof value === "string"
680
+ )
681
+ } : {},
682
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
683
+ ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
684
+ ...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {},
685
+ ...args.traceId ? { traceId: args.traceId } : {}
686
+ };
687
+ }
688
+ async function setStoredRecord(args) {
689
+ const stateAdapter = getStateAdapter();
690
+ await stateAdapter.connect();
691
+ await stateAdapter.set(
692
+ agentTurnSessionKey(args.record.conversationId, args.record.sessionId),
693
+ args.record,
694
+ args.ttlMs
695
+ );
696
+ const {
697
+ committedMessageCount: _committedMessageCount,
698
+ errorMessage: _errorMessage,
699
+ logSessionId: _logSessionId,
700
+ ...summary
701
+ } = args.record;
702
+ await appendAgentTurnSessionSummary(summary, args.ttlMs);
703
+ return materializeAgentTurnSessionRecord(args.record, [...args.piMessages]);
704
+ }
705
+ async function updateAgentTurnSessionState(args) {
706
+ const parsed = await getStoredAgentTurnSessionRecord(
707
+ args.existing.conversationId,
708
+ args.existing.sessionId
709
+ );
710
+ if (!parsed || parsed.version !== args.existing.version) {
711
+ return void 0;
712
+ }
713
+ return await setStoredRecord({
714
+ piMessages: args.existing.piMessages,
715
+ ttlMs: AGENT_TURN_SESSION_TTL_MS,
716
+ record: buildStoredRecord({
717
+ conversationId: args.existing.conversationId,
718
+ sessionId: args.existing.sessionId,
719
+ sliceId: args.existing.sliceId,
720
+ state: args.state,
721
+ committedMessageCount: parsed.committedMessageCount,
722
+ ...parsed.channelName ? { channelName: parsed.channelName } : {},
723
+ ...parsed.conversationTitle ? { conversationTitle: parsed.conversationTitle } : {},
724
+ startedAtMs: parsed.startedAtMs,
725
+ lastProgressAtMs: parsed.lastProgressAtMs,
726
+ previousVersion: parsed.version,
727
+ ...parsed.logSessionId ? { logSessionId: parsed.logSessionId } : {},
728
+ ...args.existing.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: args.existing.cumulativeDurationMs } : {},
729
+ ...args.existing.cumulativeUsage ? { cumulativeUsage: args.existing.cumulativeUsage } : {},
730
+ ...args.existing.loadedSkillNames ? { loadedSkillNames: args.existing.loadedSkillNames } : {},
731
+ ...args.existing.requester ? { requester: args.existing.requester } : {},
732
+ ...args.existing.resumeReason ? { resumeReason: args.existing.resumeReason } : {},
733
+ ...args.existing.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.existing.resumedFromSliceId } : {},
734
+ ...args.existing.traceId ? { traceId: args.existing.traceId } : {},
735
+ ...args.errorMessage ?? args.existing.errorMessage ? { errorMessage: args.errorMessage ?? args.existing.errorMessage } : {}
736
+ })
737
+ });
738
+ }
739
+ async function upsertAgentTurnSessionRecord(args) {
740
+ const existingRecord = await getStoredAgentTurnSessionRecord(
741
+ args.conversationId,
742
+ args.sessionId
743
+ );
744
+ const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
745
+ const commit = await commitMessages({
746
+ conversationId: args.conversationId,
747
+ messages: args.piMessages,
748
+ ttlMs
749
+ });
750
+ return await setStoredRecord({
751
+ piMessages: args.piMessages,
752
+ ttlMs,
753
+ record: buildStoredRecord({
754
+ ...args.channelName ?? existingRecord?.channelName ? { channelName: args.channelName ?? existingRecord?.channelName } : {},
755
+ ...args.conversationTitle ?? existingRecord?.conversationTitle ? {
756
+ conversationTitle: args.conversationTitle ?? existingRecord?.conversationTitle
757
+ } : {},
758
+ conversationId: args.conversationId,
759
+ sessionId: args.sessionId,
760
+ sliceId: args.sliceId,
761
+ state: args.state,
762
+ ...existingRecord?.startedAtMs !== void 0 ? { startedAtMs: existingRecord.startedAtMs } : {},
763
+ ...args.lastProgressAtMs !== void 0 ? { lastProgressAtMs: args.lastProgressAtMs } : {},
764
+ committedMessageCount: args.piMessages.length,
765
+ logSessionId: commit.sessionId,
766
+ previousVersion: existingRecord?.version,
767
+ ...args.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: args.cumulativeDurationMs } : {},
768
+ ...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
769
+ ...args.loadedSkillNames ? { loadedSkillNames: args.loadedSkillNames } : {},
770
+ ...args.requester ?? existingRecord?.requester ? { requester: args.requester ?? existingRecord?.requester } : {},
771
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
772
+ ...args.errorMessage ? { errorMessage: args.errorMessage } : {},
773
+ ...args.resumedFromSliceId !== void 0 ? { resumedFromSliceId: args.resumedFromSliceId } : {},
774
+ ...args.traceId ?? existingRecord?.traceId ? { traceId: args.traceId ?? existingRecord?.traceId } : {}
775
+ })
776
+ });
777
+ }
778
+ async function recordAgentTurnSessionSummary(args) {
779
+ const existing = await getStoredAgentTurnSessionRecord(
780
+ args.conversationId,
781
+ args.sessionId
782
+ );
783
+ const nowMs = Date.now();
784
+ const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
785
+ await appendAgentTurnSessionSummary(
786
+ {
787
+ version: existing?.version ?? 0,
788
+ ...args.channelName ?? existing?.channelName ? { channelName: args.channelName ?? existing?.channelName } : {},
789
+ ...args.conversationTitle ?? existing?.conversationTitle ? {
790
+ conversationTitle: args.conversationTitle ?? existing?.conversationTitle
791
+ } : {},
792
+ conversationId: args.conversationId,
793
+ sessionId: args.sessionId,
794
+ sliceId: args.sliceId,
795
+ startedAtMs: existing?.startedAtMs ?? args.startedAtMs ?? nowMs,
796
+ lastProgressAtMs: args.lastProgressAtMs ?? nowMs,
797
+ state: args.state,
798
+ updatedAtMs: nowMs,
799
+ ...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
800
+ cumulativeDurationMs: Math.max(
801
+ 0,
802
+ Math.floor(args.cumulativeDurationMs)
803
+ )
804
+ } : existing?.cumulativeDurationMs !== void 0 ? { cumulativeDurationMs: existing.cumulativeDurationMs } : {},
805
+ ...args.cumulativeUsage ?? existing?.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage ?? existing?.cumulativeUsage } : {},
806
+ ...args.requester ?? existing?.requester ? { requester: args.requester ?? existing?.requester } : {},
807
+ ...Array.isArray(args.loadedSkillNames) ? {
808
+ loadedSkillNames: args.loadedSkillNames.filter(
809
+ (value) => typeof value === "string"
810
+ )
811
+ } : existing?.loadedSkillNames ? { loadedSkillNames: existing.loadedSkillNames } : {},
812
+ ...args.resumeReason ? { resumeReason: args.resumeReason } : {},
813
+ ...args.traceId ?? existing?.traceId ? { traceId: args.traceId ?? existing?.traceId } : {}
814
+ },
815
+ ttlMs
816
+ );
817
+ }
818
+ async function readAgentTurnSessionSummariesFromIndex(key2) {
819
+ const stateAdapter = getStateAdapter();
820
+ await stateAdapter.connect();
821
+ const values = await stateAdapter.getList(key2);
822
+ const summaries = /* @__PURE__ */ new Map();
823
+ for (const value of [...values].reverse()) {
824
+ const summary = parseAgentTurnSessionSummary(value);
825
+ if (!summary) {
826
+ continue;
827
+ }
828
+ const key3 = `${summary.conversationId}:${summary.sessionId}`;
829
+ if (!summaries.has(key3)) {
830
+ summaries.set(key3, summary);
831
+ }
832
+ }
833
+ return [...summaries.values()].sort(
834
+ (left, right) => right.updatedAtMs - left.updatedAtMs
835
+ );
836
+ }
837
+ async function listAgentTurnSessionSummaries(limit = 50) {
838
+ return (await readAgentTurnSessionSummariesFromIndex(AGENT_TURN_SESSION_INDEX_KEY)).slice(0, Math.max(0, Math.floor(limit)));
839
+ }
840
+ async function listAgentTurnSessionSummariesForConversation(conversationId) {
841
+ const summaries = await readAgentTurnSessionSummariesFromIndex(
842
+ agentTurnSessionConversationIndexKey(conversationId)
843
+ );
844
+ if (summaries.length > 0) {
845
+ return summaries;
846
+ }
847
+ return (await readAgentTurnSessionSummariesFromIndex(AGENT_TURN_SESSION_INDEX_KEY)).filter((summary) => summary.conversationId === conversationId);
848
+ }
849
+ async function abandonAgentTurnSessionRecord(args) {
850
+ const existing = await getAgentTurnSessionRecord(
851
+ args.conversationId,
852
+ args.sessionId
853
+ );
854
+ if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned") {
855
+ return void 0;
856
+ }
857
+ return await updateAgentTurnSessionState({
858
+ existing,
859
+ state: "abandoned",
860
+ errorMessage: args.errorMessage ?? existing.errorMessage
861
+ });
862
+ }
863
+ async function failAgentTurnSessionRecord(args) {
864
+ const existing = await getAgentTurnSessionRecord(
865
+ args.conversationId,
866
+ args.sessionId
867
+ );
868
+ if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "abandoned" || existing.version !== args.expectedVersion) {
869
+ return void 0;
870
+ }
871
+ return await updateAgentTurnSessionState({
872
+ existing,
873
+ state: "failed",
874
+ errorMessage: args.errorMessage ?? existing.errorMessage
875
+ });
876
+ }
877
+
878
+ // src/chat/sentry-links.ts
879
+ function getSentryOrgSlug() {
880
+ const slug = process.env.SENTRY_ORG_SLUG?.trim();
881
+ return slug || void 0;
882
+ }
883
+ function isSentrySaasDsnHost(host) {
884
+ return host === "sentry.io" || host.endsWith(".sentry.io");
885
+ }
886
+ function buildSentryWebBaseUrl(dsn) {
887
+ if (isSentrySaasDsnHost(dsn.host)) {
888
+ return "https://sentry.io";
889
+ }
890
+ const port = dsn.port ? `:${dsn.port}` : "";
891
+ const path = dsn.path ? `/${dsn.path}` : "";
892
+ return `${dsn.protocol}://${dsn.host}${port}${path}`;
893
+ }
894
+ function buildSentryConversationUrl(conversationId) {
895
+ const client = sentry_exports.getClient();
896
+ const dsn = client?.getDsn();
897
+ if (!dsn?.host || !dsn.projectId) {
898
+ return void 0;
899
+ }
900
+ const orgSlug = getSentryOrgSlug();
901
+ if (!orgSlug) {
902
+ return void 0;
903
+ }
904
+ const encodedId = encodeURIComponent(conversationId);
905
+ const params = new URLSearchParams();
906
+ params.set("project", dsn.projectId);
907
+ const path = `explore/conversations/${encodedId}/?${params.toString()}`;
908
+ if (isSentrySaasDsnHost(dsn.host)) {
909
+ return `https://${orgSlug}.sentry.io/${path}`;
910
+ }
911
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
912
+ }
913
+ function buildSentryTraceUrl(traceId) {
914
+ const client = sentry_exports.getClient();
915
+ const dsn = client?.getDsn();
916
+ if (!dsn?.host || !dsn.projectId) {
917
+ return void 0;
918
+ }
919
+ const orgSlug = getSentryOrgSlug();
920
+ if (!orgSlug) {
921
+ return void 0;
922
+ }
923
+ const encodedTraceId = encodeURIComponent(traceId);
924
+ const params = new URLSearchParams();
925
+ params.set("project", dsn.projectId);
926
+ const path = `performance/trace/${encodedTraceId}/?${params.toString()}`;
927
+ if (isSentrySaasDsnHost(dsn.host)) {
928
+ return `https://${orgSlug}.sentry.io/${path}`;
929
+ }
930
+ return `${buildSentryWebBaseUrl(dsn)}/organizations/${orgSlug}/${path}`;
931
+ }
932
+
933
+ export {
934
+ GET,
935
+ loadProjection,
936
+ loadConnectedMcpProviders,
937
+ recordMcpProviderConnected,
938
+ recordAuthorizationRequested,
939
+ recordAuthorizationCompleted,
940
+ commitMessages,
941
+ getAgentTurnSessionRecord,
942
+ upsertAgentTurnSessionRecord,
943
+ recordAgentTurnSessionSummary,
944
+ listAgentTurnSessionSummaries,
945
+ listAgentTurnSessionSummariesForConversation,
946
+ abandonAgentTurnSessionRecord,
947
+ failAgentTurnSessionRecord,
948
+ buildSentryConversationUrl,
949
+ buildSentryTraceUrl
950
+ };