@sentry/junior 0.185.0 → 0.187.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 (96) hide show
  1. package/dist/{agent-hooks-NGZQLL5O.js → agent-hooks-XVAE2554.js} +11 -11
  2. package/dist/api/schema/conversation.d.ts +8 -0
  3. package/dist/api/schema.js +1 -1
  4. package/dist/api.js +44 -36
  5. package/dist/app.js +1079 -264
  6. package/dist/{catalog-runtime-AAEAO2RL.js → catalog-runtime-DWFAC562.js} +4 -4
  7. package/dist/chat/agent/types.d.ts +16 -13
  8. package/dist/chat/api-turns/{accepted-reply.d.ts → assistant-message.d.ts} +3 -2
  9. package/dist/chat/api-turns/cancellation.d.ts +0 -1
  10. package/dist/chat/api-turns/mailbox-input.d.ts +4 -0
  11. package/dist/chat/api-turns/routing.d.ts +23 -7
  12. package/dist/chat/api-turns/work.d.ts +2 -2
  13. package/dist/chat/conversations/location.d.ts +2 -2
  14. package/dist/chat/conversations/store.d.ts +13 -0
  15. package/dist/chat/conversations/turn-lifecycle.d.ts +2 -0
  16. package/dist/chat/providers/slack/runtime.d.ts +0 -2
  17. package/dist/chat/providers/slack/turn.d.ts +0 -2
  18. package/dist/chat/resource-events/actor.d.ts +14 -2
  19. package/dist/chat/resource-events/app-publisher.d.ts +18 -0
  20. package/dist/chat/resource-events/catalog.d.ts +19 -0
  21. package/dist/chat/resource-events/ingest.d.ts +0 -1
  22. package/dist/chat/resource-events/notification.d.ts +28 -3
  23. package/dist/chat/resource-events/runtime-catalog.d.ts +7 -2
  24. package/dist/chat/resource-events/store.d.ts +1 -13
  25. package/dist/chat/resource-events/workspace.d.ts +7 -3
  26. package/dist/chat/sandbox/egress/credentials.d.ts +5 -4
  27. package/dist/chat/sandbox/egress/session.d.ts +9 -9
  28. package/dist/chat/sandbox/errors.d.ts +1 -1
  29. package/dist/chat/sandbox/ref.d.ts +1 -1
  30. package/dist/chat/sandbox/snapshot/events.d.ts +8 -1
  31. package/dist/chat/sandbox/snapshot/not-ready-error.d.ts +6 -1
  32. package/dist/chat/sentry-links.d.ts +2 -0
  33. package/dist/chat/services/turn-session-routing.d.ts +23 -7
  34. package/dist/chat/task-execution/slack-resource-event.d.ts +47 -0
  35. package/dist/chat/task-execution/slack-work.d.ts +0 -21
  36. package/dist/chat/task-input.d.ts +22 -0
  37. package/dist/chat/tools/event-tasks.d.ts +1 -1
  38. package/dist/chat/tools/resource-events.d.ts +1 -1
  39. package/dist/{chunk-Y5G23T2D.js → chunk-27T5HZHO.js} +91 -7
  40. package/dist/{chunk-TVVAMOLS.js → chunk-2VUDJGB6.js} +2 -2
  41. package/dist/{chunk-X7NCJQAA.js → chunk-4A2R3AQB.js} +1 -1
  42. package/dist/{chunk-MWOH6CIK.js → chunk-4YD3DET6.js} +278 -116
  43. package/dist/{chunk-INQHUE72.js → chunk-6FGN7ZEG.js} +6 -2
  44. package/dist/{chunk-ZKKGFUWM.js → chunk-6FKNNUSN.js} +6 -6
  45. package/dist/{chunk-OMQ2J6F6.js → chunk-6YCIYOVA.js} +1 -1
  46. package/dist/{chunk-7GAP2XG4.js → chunk-7LRRVMPH.js} +1 -1
  47. package/dist/{chunk-4HA36IWS.js → chunk-BITZUBPJ.js} +8 -8
  48. package/dist/{chunk-QCT4226Q.js → chunk-D3GDGREI.js} +3 -3
  49. package/dist/{chunk-WLKWN4CD.js → chunk-D5BWM37G.js} +8 -5
  50. package/dist/{chunk-VH4IU3UC.js → chunk-DUE4EUNL.js} +19 -45
  51. package/dist/{chunk-DAPC2HTM.js → chunk-EIWYZXSM.js} +5 -5
  52. package/dist/{chunk-LVK4WJKM.js → chunk-HXR3FHTE.js} +13 -3
  53. package/dist/{chunk-UUULGCKK.js → chunk-ITLYUKGZ.js} +14 -4
  54. package/dist/{chunk-FTVMXDNN.js → chunk-JCRXXEMC.js} +1 -1
  55. package/dist/{chunk-EJYP3NQ4.js → chunk-JZPDYKRG.js} +37 -3
  56. package/dist/{chunk-HJPC32TQ.js → chunk-KRYJ5VOD.js} +20 -14
  57. package/dist/{chunk-QZKHHG3B.js → chunk-MUPPP7A7.js} +436 -283
  58. package/dist/{chunk-D63IUQQE.js → chunk-NIKLTFBP.js} +12 -9
  59. package/dist/{chunk-U4CTAVWN.js → chunk-NU775DLU.js} +2 -2
  60. package/dist/{chunk-W7GCLNIV.js → chunk-OM4SFFJK.js} +3 -3
  61. package/dist/{chunk-X2SS6BF7.js → chunk-P2VU5JNH.js} +32 -36
  62. package/dist/{chunk-4K25ODAO.js → chunk-PFAZSRGR.js} +3 -1
  63. package/dist/{chunk-EIJCPJOY.js → chunk-Q6OCUQ7W.js} +1 -1
  64. package/dist/{chunk-GJTIZQGJ.js → chunk-QH3I7ZOK.js} +1 -1
  65. package/dist/{chunk-4S47VHJR.js → chunk-STF4GSK4.js} +3 -3
  66. package/dist/{chunk-C6TS47CQ.js → chunk-TQHCZXN7.js} +11 -5
  67. package/dist/{chunk-ZIF2S464.js → chunk-U3KMII5Y.js} +1 -1
  68. package/dist/{chunk-6DOAUXLD.js → chunk-U5POFCKM.js} +753 -1718
  69. package/dist/{chunk-PNYJOEVV.js → chunk-U6ZQHRIP.js} +1 -1
  70. package/dist/{chunk-LRI5IRGY.js → chunk-URZ7UD4P.js} +2 -2
  71. package/dist/{chunk-6Q7YZLCL.js → chunk-VC343WHM.js} +9 -1
  72. package/dist/{chunk-ABAX75GG.js → chunk-WKIS4VRK.js} +17 -3
  73. package/dist/{chunk-FFHXG2FX.js → chunk-WL3R2KTS.js} +11 -29
  74. package/dist/{chunk-EWEH2PJW.js → chunk-WRDW3A3D.js} +5 -5
  75. package/dist/{chunk-7MHNIGHP.js → chunk-X5SXFVZT.js} +1 -1
  76. package/dist/{chunk-ATUWN6Z3.js → chunk-Z6OHCDXP.js} +2 -2
  77. package/dist/cli/chat.js +33 -33
  78. package/dist/cli/check.js +5 -5
  79. package/dist/cli/plugins.js +12 -12
  80. package/dist/cli/snapshot-create.js +9 -9
  81. package/dist/cli/upgrade.js +6 -6
  82. package/dist/{db-TRJ77FAU.js → db-FBHAWVXU.js} +6 -6
  83. package/dist/instrumentation.js +3 -3
  84. package/dist/nitro.js +6 -6
  85. package/dist/{oauth-callback-server-XD5SV3MS.js → oauth-callback-server-5HS5ULC6.js} +28 -28
  86. package/dist/{runner-KFX7NG6D.js → runner-46W6NV6B.js} +19 -19
  87. package/dist/{skills-J2AH26CW.js → skills-TG3QC4B6.js} +5 -5
  88. package/dist/spawn-Y6YWBIJS.js +41 -0
  89. package/dist/task-runner-YSGBPN4U.js +34 -0
  90. package/dist/{validation-O5J4VOPF.js → validation-GMRIDBC5.js} +5 -5
  91. package/dist/version.js +1 -1
  92. package/dist/{work-ECPQTG3U.js → work-W4ULBJTU.js} +21 -21
  93. package/dist/{worker-RS3DXUDO.js → worker-M6NVACS2.js} +10 -10
  94. package/package.json +4 -4
  95. package/dist/spawn-VU3CSRI6.js +0 -41
  96. package/dist/task-runner-5JG3VIIY.js +0 -34
@@ -1,40 +1,46 @@
1
1
  import {
2
2
  createWebAuthorization,
3
3
  deleteWebAuthorization
4
- } from "./chunk-UUULGCKK.js";
4
+ } from "./chunk-ITLYUKGZ.js";
5
5
  import {
6
6
  AgentRunError,
7
7
  executeTurn
8
- } from "./chunk-W7GCLNIV.js";
8
+ } from "./chunk-OM4SFFJK.js";
9
9
  import {
10
10
  buildDeliveredTurnStatePatch,
11
11
  completeAuthPauseTurn,
12
12
  finalizeFailedTurnReplyWithEvent,
13
13
  getTurnUserMessage
14
- } from "./chunk-DAPC2HTM.js";
14
+ } from "./chunk-EIWYZXSM.js";
15
15
  import {
16
16
  processPluginTask,
17
17
  scheduleSessionCompletedPluginTasks
18
- } from "./chunk-FFHXG2FX.js";
18
+ } from "./chunk-WL3R2KTS.js";
19
19
  import {
20
20
  ConversationTurnLifecycleService
21
- } from "./chunk-6Q7YZLCL.js";
21
+ } from "./chunk-VC343WHM.js";
22
22
  import {
23
23
  coerceThreadConversationState,
24
24
  getPersistedSandboxState,
25
25
  getPersistedThreadState,
26
26
  persistThreadStateById
27
- } from "./chunk-Y5G23T2D.js";
27
+ } from "./chunk-27T5HZHO.js";
28
28
  import {
29
29
  healthReportSchema
30
30
  } from "./chunk-UTL3MHPC.js";
31
+ import {
32
+ RESOURCE_EVENT_MESSAGE_AUTHOR,
33
+ RESOURCE_EVENT_SYSTEM_ACTOR,
34
+ isResourceEventConversationMessage,
35
+ isResourceEventMailboxMetadata
36
+ } from "./chunk-MUPPP7A7.js";
31
37
  import {
32
38
  parseSlackThreadId
33
- } from "./chunk-X7NCJQAA.js";
39
+ } from "./chunk-4A2R3AQB.js";
34
40
  import {
35
41
  appendAndEnqueueExclusiveInboundMessage,
36
42
  appendAndEnqueueInboundMessage
37
- } from "./chunk-D63IUQQE.js";
43
+ } from "./chunk-NIKLTFBP.js";
38
44
  import {
39
45
  TurnInputCommitLostError,
40
46
  buildDeterministicTurnId,
@@ -45,7 +51,7 @@ import {
45
51
  markTurnClosed,
46
52
  markTurnFailed,
47
53
  startActiveTurn
48
- } from "./chunk-U4CTAVWN.js";
54
+ } from "./chunk-NU775DLU.js";
49
55
  import {
50
56
  abandonTurnRecord,
51
57
  buildConversationContext,
@@ -61,35 +67,42 @@ import {
61
67
  recordDeliveredAssistantMessage,
62
68
  saveTurnCheckpoint,
63
69
  upsertConversationMessage
64
- } from "./chunk-WLKWN4CD.js";
70
+ } from "./chunk-D5BWM37G.js";
65
71
  import {
66
72
  normalizeSlackConversationId
67
- } from "./chunk-VH4IU3UC.js";
73
+ } from "./chunk-DUE4EUNL.js";
68
74
  import {
69
75
  getConversationEventStore,
70
76
  getConversationStore,
71
77
  getDb
72
- } from "./chunk-ABAX75GG.js";
78
+ } from "./chunk-WKIS4VRK.js";
73
79
  import {
74
80
  juniorConversationParticipants,
75
81
  juniorConversations,
76
82
  juniorIdentities,
77
83
  juniorUsers
78
- } from "./chunk-LVK4WJKM.js";
84
+ } from "./chunk-HXR3FHTE.js";
79
85
  import {
80
86
  juniorDestinations
81
87
  } from "./chunk-VFUK3X5B.js";
88
+ import {
89
+ getSlackBotToken
90
+ } from "./chunk-URZ7UD4P.js";
82
91
  import {
83
92
  canExposeConversationPayload
84
- } from "./chunk-EIJCPJOY.js";
93
+ } from "./chunk-Q6OCUQ7W.js";
85
94
  import {
86
95
  credentialContextForActor
87
96
  } from "./chunk-CEMMLV5L.js";
97
+ import {
98
+ createActor
99
+ } from "./chunk-N7TMSOZH.js";
88
100
  import {
89
101
  logException,
102
+ logWarn,
90
103
  setTags,
91
104
  withLogContext
92
- } from "./chunk-OMQ2J6F6.js";
105
+ } from "./chunk-6YCIYOVA.js";
93
106
 
94
107
  // src/handlers/health.ts
95
108
  function readHealthReport() {
@@ -129,6 +142,10 @@ function createVercelAttachmentStorage() {
129
142
 
130
143
  // src/chat/api-turns/routing.ts
131
144
  import { z } from "zod";
145
+ import {
146
+ createLocalSource,
147
+ createWebSource
148
+ } from "@sentry/junior-plugin-api";
132
149
  var apiTurnMailboxMetadataSchema = z.object({
133
150
  authorEmail: z.string().email(),
134
151
  authorFullName: z.string().min(1).optional(),
@@ -137,6 +154,38 @@ var apiTurnMailboxMetadataSchema = z.object({
137
154
  kind: z.literal("api_turn"),
138
155
  messageId: z.string().min(1)
139
156
  }).strict();
157
+ function turnIdentityFromConversationMessage(message) {
158
+ if (isResourceEventConversationMessage(message)) {
159
+ return {
160
+ actor: RESOURCE_EVENT_SYSTEM_ACTOR,
161
+ author: message.author ?? RESOURCE_EVENT_MESSAGE_AUTHOR,
162
+ source: "resource_event"
163
+ };
164
+ }
165
+ const actor = createActor(message.author, {
166
+ platform: "web",
167
+ userId: message.author?.userId
168
+ });
169
+ if (actor?.platform !== "web") {
170
+ return void 0;
171
+ }
172
+ return {
173
+ actor,
174
+ author: message.author ?? { userId: actor.userId },
175
+ source: "web"
176
+ };
177
+ }
178
+ function sourceFromTurnInput(args) {
179
+ switch (args.source) {
180
+ case "resource_event":
181
+ return createLocalSource(args.conversationId);
182
+ case "web":
183
+ return createWebSource(
184
+ args.conversationId,
185
+ args.visibility === "private" ? "private" : "public"
186
+ );
187
+ }
188
+ }
140
189
  async function getActiveApiTurnId(conversationId) {
141
190
  const summaries = await listTurnSummaries(conversationId);
142
191
  const active = summaries.filter(
@@ -170,13 +219,52 @@ function parseApiTurnMessages(messages) {
170
219
  return [];
171
220
  }
172
221
  if (parsed.some((entry) => !entry.metadata.success)) {
173
- throw new Error("Conversation mailbox mixes web turns and other input");
222
+ throw new Error("A Turn cannot combine different kinds of input");
174
223
  }
175
224
  return parsed.map((entry) => {
176
225
  if (!entry.metadata.success) {
177
- throw new Error("API turn mailbox metadata failed validation");
226
+ throw new Error("User message has invalid metadata");
178
227
  }
179
- return { message: entry.message, metadata: entry.metadata.data };
228
+ const metadata = entry.metadata.data;
229
+ const actor = {
230
+ platform: "web",
231
+ userId: metadata.authorUserId,
232
+ email: metadata.authorEmail.trim().toLowerCase(),
233
+ ...metadata.authorFullName ? { fullName: metadata.authorFullName } : void 0,
234
+ ...metadata.authorUserName ? { userName: metadata.authorUserName } : void 0
235
+ };
236
+ return {
237
+ actor,
238
+ author: {
239
+ email: actor.email,
240
+ ...actor.fullName ? { fullName: actor.fullName } : void 0,
241
+ userId: actor.userId,
242
+ ...actor.userName ? { userName: actor.userName } : void 0
243
+ },
244
+ message: entry.message,
245
+ source: "web"
246
+ };
247
+ });
248
+ }
249
+ function parseResourceEventMessages(messages) {
250
+ if (messages.length === 0) {
251
+ return [];
252
+ }
253
+ if (!messages.every(
254
+ (message) => message.source === "resource_event" && isResourceEventMailboxMetadata(message.input.metadata)
255
+ )) {
256
+ return [];
257
+ }
258
+ return messages.map((message) => {
259
+ if (!isResourceEventMailboxMetadata(message.input.metadata)) {
260
+ throw new Error("Resource event has invalid metadata");
261
+ }
262
+ return {
263
+ actor: RESOURCE_EVENT_SYSTEM_ACTOR,
264
+ author: RESOURCE_EVENT_MESSAGE_AUTHOR,
265
+ message,
266
+ source: "resource_event"
267
+ };
180
268
  });
181
269
  }
182
270
  async function resolveApiTurnWork(context) {
@@ -184,6 +272,12 @@ async function resolveApiTurnWork(context) {
184
272
  if (batch.length > 0) {
185
273
  return { kind: "mailbox", batch };
186
274
  }
275
+ if (context.destination?.platform === "local") {
276
+ const resourceBatch = parseResourceEventMessages(context.attempt.messages);
277
+ if (resourceBatch.length > 0) {
278
+ return { kind: "mailbox", batch: resourceBatch };
279
+ }
280
+ }
187
281
  if (context.attempt.messages.length > 0) {
188
282
  return void 0;
189
283
  }
@@ -197,16 +291,15 @@ async function isApiTurnWork(context) {
197
291
  // src/chat/api-turns/work.ts
198
292
  import { createHash } from "crypto";
199
293
  import {
200
- createWebSource,
201
294
  localDestinationSchema
202
295
  } from "@sentry/junior-plugin-api";
203
296
 
204
- // src/chat/api-turns/accepted-reply.ts
205
- async function commitWebAcceptedReply(args) {
297
+ // src/chat/api-turns/assistant-message.ts
298
+ async function commitAssistantMessage(args) {
206
299
  const conversationMessageId = recordDeliveredAssistantMessage({
207
300
  conversation: args.conversation,
208
301
  sessionId: args.sessionId,
209
- source: "web",
302
+ source: args.source,
210
303
  text: args.text,
211
304
  userMessageId: args.userMessageId
212
305
  });
@@ -221,8 +314,8 @@ async function commitWebAcceptedReply(args) {
221
314
  );
222
315
  } catch (error) {
223
316
  logException(
224
- new Error("Accepted assistant message persistence failed"),
225
- "api.assistant.message_post_delivery_persist.failed",
317
+ new Error("Assistant message persistence failed"),
318
+ "conversation.assistant.message_persist.failed",
226
319
  {
227
320
  "error.type": error instanceof Error ? error.name : typeof error
228
321
  }
@@ -287,7 +380,7 @@ function createApiTurnCancellation() {
287
380
  }
288
381
  async function completeCancelledApiTurn(args) {
289
382
  try {
290
- const ownsPendingAuthorization = args.conversation.processing.pendingAuth?.sessionId === args.turnId;
383
+ const pendingAuthorization = args.conversation.processing.pendingAuth?.sessionId === args.turnId ? args.conversation.processing.pendingAuth : void 0;
291
384
  await abandonTurnRecord({
292
385
  conversationId: args.conversationId,
293
386
  turnId: args.turnId,
@@ -303,9 +396,9 @@ async function completeCancelledApiTurn(args) {
303
396
  nowMs: Date.now(),
304
397
  sessionId: args.turnId
305
398
  });
306
- if (ownsPendingAuthorization) {
399
+ if (pendingAuthorization) {
307
400
  await deleteWebAuthorization({
308
- actorId: args.actorId,
401
+ actorId: pendingAuthorization.actorId,
309
402
  conversationId: args.conversationId
310
403
  });
311
404
  }
@@ -329,6 +422,11 @@ async function completeCancelledApiTurn(args) {
329
422
  await args.acknowledge();
330
423
  }
331
424
 
425
+ // src/chat/api-turns/mailbox-input.ts
426
+ function joinMailboxText(messages) {
427
+ return messages.map((message) => message.input.text.trim()).filter(Boolean).join("\n\n");
428
+ }
429
+
332
430
  // src/chat/api-turns/work.ts
333
431
  function normalizeEmail(email) {
334
432
  return email.trim().toLowerCase();
@@ -364,21 +462,6 @@ function resolveApiTurnDestination(args) {
364
462
  }
365
463
  return requireLocalDestination(args.conversationId);
366
464
  }
367
- function webSourceForConversation(args) {
368
- return createWebSource(
369
- args.conversationId,
370
- args.visibility === "private" ? "private" : "public"
371
- );
372
- }
373
- function actorFromMetadata(metadata) {
374
- return {
375
- platform: "web",
376
- userId: metadata.authorUserId,
377
- email: normalizeEmail(metadata.authorEmail),
378
- ...metadata.authorFullName ? { fullName: metadata.authorFullName } : void 0,
379
- ...metadata.authorUserName ? { userName: metadata.authorUserName } : void 0
380
- };
381
- }
382
465
  function storedActorFromApi(actor) {
383
466
  return {
384
467
  ...actor.email ? { email: normalizeEmail(actor.email) } : void 0,
@@ -395,16 +478,6 @@ function webActorFromEmail(email, profile) {
395
478
  ...profile?.userName ? { userName: profile.userName } : void 0
396
479
  };
397
480
  }
398
- function actorFromStoredConversation(stored) {
399
- const email = stored?.email?.trim().toLowerCase();
400
- if (!email) {
401
- return void 0;
402
- }
403
- return webActorFromEmail(
404
- email,
405
- stored?.fullName ? { fullName: stored.fullName } : void 0
406
- );
407
- }
408
481
  function buildApiTurnInboundMessage(args) {
409
482
  const text = args.message.trim();
410
483
  if (!text) {
@@ -450,8 +523,9 @@ async function recordApiConversationActivity(args) {
450
523
  });
451
524
  const isNewRoot = !existing;
452
525
  const visibility = args.rootVisibility === "private" ? "private" : "public";
453
- const source = isNewRoot ? webSourceForConversation({
526
+ const source = isNewRoot ? sourceFromTurnInput({
454
527
  conversationId: args.conversationId,
528
+ source: "web",
455
529
  visibility
456
530
  }) : void 0;
457
531
  await store.recordActivity({
@@ -545,65 +619,84 @@ function createApiTurnWorker(agentRunner, cancellation) {
545
619
  const resolved = await resolveApiTurnWork(context);
546
620
  if (!resolved) {
547
621
  throw new Error(
548
- `Conversation API worker received other work for ${context.conversationId}`
622
+ `Unsupported input for Conversation ${context.conversationId}`
549
623
  );
550
624
  }
551
625
  if (context.publishExternally) {
552
626
  throw new Error(
553
- `Conversation API work cannot publish to a provider for ${context.conversationId}`
627
+ `publishExternally must be false for Conversation ${context.conversationId}`
554
628
  );
555
629
  }
556
630
  const lifecycle = new ConversationTurnLifecycleService(
557
631
  getConversationEventStore()
558
632
  );
559
633
  const isResume = resolved.kind === "resume";
560
- let actor;
561
- let text;
562
- let turnId;
563
- let userMessageId;
564
- let startedAtMs;
565
- let inputMessageIds;
634
+ let turnIdentity;
635
+ let text = "";
636
+ let turnId = "";
637
+ let userMessageId = "";
638
+ let startedAtMs = Date.now();
639
+ let inputMessageIds = [];
566
640
  const storedConversation = await getConversationStore().get({
567
641
  conversationId: context.conversationId
568
642
  });
643
+ const mailboxDestination = resolved.kind === "mailbox" ? resolved.batch[0]?.message.destination : void 0;
644
+ const destination = resolveApiTurnDestination({
645
+ conversationId: context.conversationId,
646
+ existing: context.destination ?? mailboxDestination ?? storedConversation?.destination
647
+ });
569
648
  if (resolved.kind === "mailbox") {
570
649
  const first = resolved.batch[0];
571
- text = resolved.batch.map((entry) => entry.message.input.text.trim()).filter(Boolean).join("\n\n");
572
- actor = actorFromMetadata(first.metadata);
573
- turnId = apiTurnIdForMessage(first.metadata.messageId);
574
- userMessageId = first.metadata.messageId;
650
+ text = joinMailboxText(resolved.batch.map((entry) => entry.message));
575
651
  startedAtMs = first.message.createdAtMs;
576
- inputMessageIds = resolved.batch.map((entry) => entry.metadata.messageId);
652
+ userMessageId = first.message.inboundMessageId;
653
+ turnId = apiTurnIdForMessage(userMessageId);
654
+ inputMessageIds = resolved.batch.map(
655
+ (entry) => entry.message.inboundMessageId
656
+ );
657
+ turnIdentity = first;
577
658
  } else {
578
659
  turnId = resolved.turnId;
579
- const resumedActor = actorFromStoredConversation(
580
- storedConversation?.actor
581
- );
582
- if (!resumedActor) {
660
+ }
661
+ const persisted = await getPersistedThreadState(context.conversationId);
662
+ const conversation = coerceThreadConversationState(persisted);
663
+ await hydrateConversationMessages({
664
+ conversation,
665
+ conversationId: context.conversationId
666
+ });
667
+ if (isResume) {
668
+ const userMessage = getTurnUserMessage(conversation, turnId);
669
+ if (!userMessage) {
583
670
  throw new Error(
584
- `Conversation API resume missing actor for ${context.conversationId}`
671
+ `Unable to locate the persisted user message for Turn "${turnId}"`
585
672
  );
586
673
  }
587
- actor = resumedActor;
588
- text = "";
589
- userMessageId = "";
590
- startedAtMs = Date.now();
591
- inputMessageIds = [];
674
+ turnIdentity = turnIdentityFromConversationMessage(userMessage);
675
+ userMessageId = userMessage.id;
676
+ text = userMessage.text;
677
+ startedAtMs = userMessage.createdAtMs;
678
+ inputMessageIds = [userMessageId];
592
679
  }
593
- const destination = resolveApiTurnDestination({
594
- conversationId: context.conversationId,
595
- existing: context.destination ?? (resolved.kind === "mailbox" ? resolved.batch[0]?.message.destination : void 0) ?? storedConversation?.destination
596
- });
597
- const source = webSourceForConversation({
680
+ if (!turnIdentity) {
681
+ throw new Error(
682
+ `Resumed Turn is missing an Actor for Conversation ${context.conversationId}`
683
+ );
684
+ }
685
+ const { actor, author } = turnIdentity;
686
+ const source = sourceFromTurnInput({
598
687
  conversationId: context.conversationId,
688
+ source: turnIdentity.source,
599
689
  visibility: storedConversation?.visibility
600
690
  });
691
+ const webActor = actor.platform === "web" ? actor : void 0;
601
692
  return await withLogContext(
602
693
  {
603
694
  conversationId: context.conversationId,
604
- platform: "web",
605
- userId: actor.userId,
606
- ...actor.userName ? { userName: actor.userName } : void 0
695
+ platform: actor.platform,
696
+ ...webActor ? {
697
+ userId: webActor.userId,
698
+ ...webActor.userName ? { userName: webActor.userName } : void 0
699
+ } : void 0
607
700
  },
608
701
  async () => {
609
702
  let acknowledged = isResume || context.attempt.messages.length === 0;
@@ -620,12 +713,6 @@ function createApiTurnWorker(agentRunner, cancellation) {
620
713
  }
621
714
  acknowledged = true;
622
715
  };
623
- const persisted = await getPersistedThreadState(context.conversationId);
624
- const conversation = coerceThreadConversationState(persisted);
625
- await hydrateConversationMessages({
626
- conversation,
627
- conversationId: context.conversationId
628
- });
629
716
  let sandboxRef = getPersistedSandboxState(persisted);
630
717
  const initialSandboxRef = sandboxRef;
631
718
  const appSignal = cancellation?.signal(context.conversationId);
@@ -640,7 +727,6 @@ function createApiTurnWorker(agentRunner, cancellation) {
640
727
  try {
641
728
  await completeCancelledApiTurn({
642
729
  acknowledge,
643
- actorId: actor.userId,
644
730
  cancellation,
645
731
  conversation,
646
732
  conversationId: context.conversationId,
@@ -658,16 +744,6 @@ function createApiTurnWorker(agentRunner, cancellation) {
658
744
  return { status: "completed" };
659
745
  };
660
746
  if (isResume) {
661
- const userMessage = getTurnUserMessage(conversation, turnId);
662
- if (!userMessage) {
663
- throw new Error(
664
- `Unable to locate the persisted user message for Turn "${turnId}"`
665
- );
666
- }
667
- userMessageId = userMessage.id;
668
- text = userMessage.text;
669
- startedAtMs = userMessage.createdAtMs;
670
- inputMessageIds = [userMessageId];
671
747
  if (cancellationSignal?.aborted) {
672
748
  return await completeCancelledTurn();
673
749
  }
@@ -677,16 +753,11 @@ function createApiTurnWorker(agentRunner, cancellation) {
677
753
  role: "user",
678
754
  text: normalizeConversationText(text),
679
755
  createdAtMs: startedAtMs,
680
- author: {
681
- ...actor.email ? { email: actor.email } : void 0,
682
- ...actor.fullName ? { fullName: actor.fullName } : void 0,
683
- userId: actor.userId,
684
- ...actor.userName ? { userName: actor.userName } : void 0
685
- },
756
+ author,
686
757
  meta: {
687
758
  explicitMention: true,
688
759
  replied: false,
689
- source: "web"
760
+ ...turnIdentity.source === "web" ? { source: "web" } : void 0
690
761
  }
691
762
  });
692
763
  await persistConversationMessages({
@@ -722,11 +793,12 @@ function createApiTurnWorker(agentRunner, cancellation) {
722
793
  }
723
794
  failureCode = "delivery_failed";
724
795
  assistantMessageDelivered = true;
725
- await commitWebAcceptedReply({
796
+ await commitAssistantMessage({
726
797
  ...agentMessage ? { agentMessage } : void 0,
727
798
  conversation,
728
799
  conversationId: context.conversationId,
729
800
  sessionId: turnId,
801
+ ...turnIdentity.source === "web" ? { source: "web" } : void 0,
730
802
  text: replyText,
731
803
  userMessageId
732
804
  });
@@ -737,6 +809,7 @@ function createApiTurnWorker(agentRunner, cancellation) {
737
809
  return await completeCancelledTurn();
738
810
  }
739
811
  if (!isResume) {
812
+ const pendingAuthorizationActorId = conversation.processing.pendingAuth?.actorId;
740
813
  const authParked = (await listTurnSummaries(context.conversationId)).filter(
741
814
  (summary) => summary.surface === "api" && !summary.dispatchId && summary.state === "paused" && summary.resumeReason === "auth"
742
815
  );
@@ -747,7 +820,7 @@ function createApiTurnWorker(agentRunner, cancellation) {
747
820
  await abandonTurnRecord({
748
821
  conversationId: context.conversationId,
749
822
  turnId: parked.turnId,
750
- errorMessage: "Auth-parked session superseded by a new user message"
823
+ errorMessage: "Auth-paused Turn superseded by new input"
751
824
  });
752
825
  markTurnClosed({
753
826
  conversation,
@@ -755,9 +828,9 @@ function createApiTurnWorker(agentRunner, cancellation) {
755
828
  sessionId: parked.turnId
756
829
  });
757
830
  }
758
- if (authParked.length > 0) {
831
+ if (authParked.length > 0 && pendingAuthorizationActorId) {
759
832
  await deleteWebAuthorization({
760
- actorId: actor.userId,
833
+ actorId: pendingAuthorizationActorId,
761
834
  conversationId: context.conversationId
762
835
  });
763
836
  }
@@ -790,14 +863,17 @@ function createApiTurnWorker(agentRunner, cancellation) {
790
863
  actor,
791
864
  credentialContext: credentialContextForActor(actor),
792
865
  destination,
866
+ ...storedConversation?.location ? { location: storedConversation.location } : void 0,
793
867
  publishExternally: false,
794
868
  source,
795
869
  surface: "api",
796
870
  ...cancellationSignal ? { signal: cancellationSignal } : void 0,
797
- authorization: createWebAuthorization({
798
- actorId: actor.userId,
799
- conversationId: context.conversationId
800
- }),
871
+ ...webActor ? {
872
+ authorization: createWebAuthorization({
873
+ actorId: webActor.userId,
874
+ conversationId: context.conversationId
875
+ })
876
+ } : { disabledFeatures: ["interactive-auth"] },
801
877
  state: {
802
878
  pendingAuth: conversation.processing.pendingAuth,
803
879
  sandboxRef
@@ -1227,6 +1303,91 @@ async function readConversationAccessFromSql(db, conversationIds, viewer) {
1227
1303
  );
1228
1304
  }
1229
1305
 
1306
+ // src/chat/slack/user.ts
1307
+ import { z as z2 } from "zod";
1308
+ var slackUserInfoSchema = z2.object({
1309
+ ok: z2.literal(true),
1310
+ user: z2.object({
1311
+ name: z2.string().nullish(),
1312
+ profile: z2.object({
1313
+ email: z2.string().nullish(),
1314
+ real_name: z2.string().nullish()
1315
+ }).optional()
1316
+ })
1317
+ });
1318
+ var USER_CACHE_TTL_MS = 5 * 60 * 1e3;
1319
+ var userCache = /* @__PURE__ */ new Map();
1320
+ function userCacheKey(teamId, userId) {
1321
+ return `${teamId}:${userId}`;
1322
+ }
1323
+ function readFromCache(teamId, userId) {
1324
+ const hit = userCache.get(userCacheKey(teamId, userId));
1325
+ if (!hit) return null;
1326
+ if (hit.expiresAt < Date.now()) {
1327
+ userCache.delete(userCacheKey(teamId, userId));
1328
+ return null;
1329
+ }
1330
+ return hit.value;
1331
+ }
1332
+ function writeToCache(teamId, userId, value) {
1333
+ userCache.set(userCacheKey(teamId, userId), {
1334
+ value,
1335
+ expiresAt: Date.now() + USER_CACHE_TTL_MS
1336
+ });
1337
+ }
1338
+ async function lookupSlackUser(teamId, userId) {
1339
+ if (!teamId || !userId) {
1340
+ return null;
1341
+ }
1342
+ const cached = readFromCache(teamId, userId);
1343
+ if (cached) {
1344
+ return cached;
1345
+ }
1346
+ const token = getSlackBotToken();
1347
+ if (!token) {
1348
+ return null;
1349
+ }
1350
+ try {
1351
+ const response = await fetch(
1352
+ `https://slack.com/api/users.info?user=${encodeURIComponent(userId)}`,
1353
+ {
1354
+ headers: {
1355
+ authorization: `Bearer ${token}`
1356
+ }
1357
+ }
1358
+ );
1359
+ if (!response.ok) {
1360
+ logWarn("slack.user.lookup.failed", {
1361
+ "enduser.id": userId,
1362
+ "app.slack.team_id": teamId,
1363
+ "http.response.status_code": response.status
1364
+ });
1365
+ return null;
1366
+ }
1367
+ const parsed = slackUserInfoSchema.safeParse(await response.json());
1368
+ if (!parsed.success) {
1369
+ return null;
1370
+ }
1371
+ const payload = parsed.data;
1372
+ const userName = payload.user.name?.trim() || void 0;
1373
+ const fullName = payload.user.profile?.real_name?.trim() || void 0;
1374
+ const result = {
1375
+ userName,
1376
+ fullName,
1377
+ email: payload.user.profile?.email?.trim() || void 0
1378
+ };
1379
+ writeToCache(teamId, userId, result);
1380
+ return result;
1381
+ } catch (error) {
1382
+ logWarn("slack.user.lookup.failed", {
1383
+ "enduser.id": userId,
1384
+ "app.slack.team_id": teamId,
1385
+ "exception.message": error instanceof Error ? error.message : String(error)
1386
+ });
1387
+ return null;
1388
+ }
1389
+ }
1390
+
1230
1391
  // src/chat/slack/conversation-context.ts
1231
1392
  function normalizeConversationName(type, channelName) {
1232
1393
  const trimmed = channelName?.trim();
@@ -1315,6 +1476,7 @@ export {
1315
1476
  readHealthReport,
1316
1477
  GET,
1317
1478
  createVercelAttachmentStorage,
1479
+ lookupSlackUser,
1318
1480
  conversationVisibilityFromSlackChannelType,
1319
1481
  resolveSlackChannelTypeFromMessage,
1320
1482
  resolveSlackConversationContext,
@@ -29,7 +29,9 @@ function assertRunConsistency(run) {
29
29
  switch (source.platform) {
30
30
  case "slack": {
31
31
  if (destination.platform !== "slack") {
32
- throw new TypeError("Run source and destination platforms do not match");
32
+ throw new TypeError(
33
+ "Run source and destination platforms do not match"
34
+ );
33
35
  }
34
36
  if (source.teamId !== destination.teamId) {
35
37
  throw new TypeError("Slack source and destination teams do not match");
@@ -38,7 +40,9 @@ function assertRunConsistency(run) {
38
40
  }
39
41
  case "local": {
40
42
  if (destination.platform !== "local") {
41
- throw new TypeError("Run source and destination platforms do not match");
43
+ throw new TypeError(
44
+ "Run source and destination platforms do not match"
45
+ );
42
46
  }
43
47
  if (source.conversationId !== destination.conversationId) {
44
48
  throw new TypeError(